Skip to content

Commit 880855b

Browse files
committed
add Mpeg2TsMuxerRecordController
1 parent 166a330 commit 880855b

1 file changed

Lines changed: 279 additions & 0 deletions

File tree

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
package com.pedro.library.util
2+
3+
import android.media.MediaCodec
4+
import android.media.MediaFormat
5+
import android.util.Log
6+
import com.pedro.common.AudioCodec
7+
import com.pedro.common.VideoCodec
8+
import com.pedro.common.frame.MediaFrame
9+
import com.pedro.common.toMediaCodecBufferInfo
10+
import com.pedro.encoder.video.VideoEncoderHelper
11+
import com.pedro.library.base.recording.AsyncBaseRecordController
12+
import com.pedro.library.base.recording.RecordController
13+
import com.pedro.library.base.recording.RecordController.RecordTracks
14+
import com.pedro.srt.mpeg2ts.MpegTsPacket
15+
import com.pedro.srt.mpeg2ts.MpegTsPacketizer
16+
import com.pedro.srt.mpeg2ts.MpegType
17+
import com.pedro.srt.mpeg2ts.Pid
18+
import com.pedro.srt.mpeg2ts.packets.AacPacket
19+
import com.pedro.srt.mpeg2ts.packets.BasePacket
20+
import com.pedro.srt.mpeg2ts.packets.H26XPacket
21+
import com.pedro.srt.mpeg2ts.packets.OpusPacket
22+
import com.pedro.srt.mpeg2ts.psi.Psi
23+
import com.pedro.srt.mpeg2ts.psi.PsiManager
24+
import com.pedro.srt.mpeg2ts.service.Mpeg2TsService
25+
import com.pedro.srt.srt.packets.SrtPacket
26+
import com.pedro.srt.srt.packets.data.PacketPosition
27+
import com.pedro.srt.utils.Constants
28+
import com.pedro.srt.utils.chunkPackets
29+
import com.pedro.srt.utils.toCodec
30+
import java.io.FileDescriptor
31+
import java.io.FileOutputStream
32+
import java.io.IOException
33+
import java.io.OutputStream
34+
import java.nio.ByteBuffer
35+
36+
class Mpeg2TsMuxerRecordController : AsyncBaseRecordController() {
37+
38+
private var outputStream: OutputStream? = null
39+
40+
//metadata config
41+
private var service = Mpeg2TsService()
42+
private val psiManager = PsiManager(service).apply {
43+
upgradePatVersion()
44+
upgradeSdtVersion()
45+
}
46+
private val limitSize: Int
47+
get() {
48+
return Constants.MTU - SrtPacket.headerSize
49+
}
50+
51+
private val mpegTsPacketizer = MpegTsPacketizer(psiManager)
52+
private var audioPacket: BasePacket = AacPacket(limitSize, psiManager)
53+
private val videoPacket = H26XPacket(limitSize, psiManager)
54+
private val chunkSize = limitSize / MpegTsPacketizer.packetSize
55+
private var sampleRate = 0
56+
private var isStereo = true
57+
private var sendInfo = false
58+
59+
@Throws(IOException::class)
60+
override fun startRecordImp(
61+
path: String,
62+
listener: RecordController.Listener?,
63+
tracks: RecordTracks
64+
) {
65+
outputStream = FileOutputStream(path)
66+
start(listener, tracks)
67+
}
68+
69+
@Throws(IOException::class)
70+
override fun startRecordImp(
71+
fd: FileDescriptor,
72+
listener: RecordController.Listener?,
73+
tracks: RecordTracks
74+
) {
75+
outputStream = FileOutputStream(fd)
76+
start(listener, tracks)
77+
}
78+
79+
@Throws(IOException::class)
80+
private fun start(listener: RecordController.Listener?, tracks: RecordTracks) {
81+
audioPacket = when (getAudioCodec()) {
82+
AudioCodec.AAC -> AacPacket(limitSize, psiManager).apply {
83+
sendAudioInfo(
84+
sampleRate,
85+
isStereo
86+
)
87+
}
88+
AudioCodec.OPUS -> OpusPacket(limitSize, psiManager)
89+
else -> {
90+
throw IOException("Unsupported AudioCodec: " + getAudioCodec().name)
91+
}
92+
}
93+
if (getVideoCodec() == VideoCodec.AV1) {
94+
throw IOException("Unsupported VideoCodec: " + getVideoCodec().name)
95+
}
96+
audioPacket.setLimitSize(limitSize)
97+
videoPacket.setLimitSize(limitSize)
98+
outputStream?.let {
99+
val videoEnabled = tracks == RecordTracks.VIDEO || tracks == RecordTracks.ALL
100+
val audioEnabled = tracks == RecordTracks.AUDIO || tracks == RecordTracks.ALL
101+
setTrackConfig(videoEnabled, audioEnabled)
102+
103+
val psiList = mutableListOf<Psi>(psiManager.getPat())
104+
psiManager.getPmt()?.let { psiList.add(0, it) }
105+
psiList.add(psiManager.getSdt())
106+
val psiPacketsConfig = mpegTsPacketizer.write(psiList).chunkPackets(chunkSize).map { buffer ->
107+
MpegTsPacket(buffer, MpegType.PSI, PacketPosition.SINGLE, isKey = false)
108+
}
109+
writePackets(psiPacketsConfig)
110+
}
111+
if (tracks == RecordTracks.AUDIO) recordStatus = RecordController.Status.RECORDING
112+
listener?.onStatusChange(recordStatus)
113+
}
114+
115+
private fun setTrackConfig(videoEnabled: Boolean, audioEnabled: Boolean) {
116+
Pid.reset()
117+
service.clearTracks()
118+
if (audioEnabled) service.addTrack(getAudioCodec().toCodec())
119+
if (videoEnabled) service.addTrack(getVideoCodec().toCodec())
120+
service.generatePmt()
121+
psiManager.updateService(service)
122+
}
123+
124+
override fun stopRecordImp() {
125+
psiManager.reset()
126+
mpegTsPacketizer.reset()
127+
audioPacket.reset(false)
128+
videoPacket.reset(false)
129+
try {
130+
outputStream?.close()
131+
} catch (_: Exception) {
132+
} finally {
133+
outputStream = null
134+
}
135+
sendInfo = false
136+
}
137+
138+
private suspend fun getMpegTsPackets(
139+
mediaFrame: MediaFrame?,
140+
callback: suspend (List<MpegTsPacket>) -> Unit
141+
) {
142+
if (mediaFrame == null) return
143+
when (mediaFrame.type) {
144+
MediaFrame.Type.VIDEO -> videoPacket.createAndSendPacket(mediaFrame) { callback(it) }
145+
MediaFrame.Type.AUDIO -> audioPacket.createAndSendPacket(mediaFrame) { callback(it) }
146+
}
147+
}
148+
149+
private fun writePackets(mpegTsPackets: List<MpegTsPacket>) {
150+
try {
151+
outputStream?.let { outputStream ->
152+
mpegTsPackets.forEach { mpegTsPacket ->
153+
outputStream.write(mpegTsPacket.buffer)
154+
}
155+
}
156+
} catch (_: Exception) { }
157+
}
158+
159+
override suspend fun onWriteFrame(frame: MediaFrame) {
160+
when (frame.type) {
161+
MediaFrame.Type.VIDEO -> {
162+
if (tracks != RecordTracks.AUDIO) {
163+
if (recordStatus == RecordController.Status.STARTED) {
164+
getVideoInfo(frame.data, frame.info.toMediaCodecBufferInfo())
165+
} else if (recordStatus == RecordController.Status.RECORDING) {
166+
writeMpeg2TsPacket(frame)
167+
}
168+
}
169+
}
170+
MediaFrame.Type.AUDIO -> {
171+
if (recordStatus == RecordController.Status.RECORDING && tracks != RecordTracks.VIDEO) {
172+
writeMpeg2TsPacket(frame)
173+
}
174+
}
175+
}
176+
}
177+
178+
private suspend fun writeMpeg2TsPacket(frame: MediaFrame) {
179+
getMpegTsPackets(frame) { mpegTsPackets ->
180+
val isKey = mpegTsPackets[0].isKey
181+
val psiPackets = psiManager.checkSendInfo(isKey, mpegTsPacketizer, chunkSize)
182+
writePackets(psiPackets)
183+
writePackets(mpegTsPackets)
184+
}
185+
}
186+
187+
private fun getVideoInfo(buffer: ByteBuffer, info: MediaCodec.BufferInfo) {
188+
if (info.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME || isKeyFrame(buffer)) {
189+
if (!sendInfo) {
190+
when (getVideoCodec()) {
191+
VideoCodec.H264 -> {
192+
val buffers =
193+
VideoEncoderHelper.decodeSpsPpsFromBuffer(buffer.duplicate(), info.size)
194+
if (buffers != null) {
195+
Log.i(TAG, "manual sps/pps extraction success")
196+
val oldSps = buffers.first
197+
val oldPps = buffers.second
198+
videoPacket.sendVideoInfo(oldSps, oldPps, null)
199+
sendInfo = true
200+
} else {
201+
Log.e(TAG, "manual sps/pps extraction failed")
202+
}
203+
}
204+
205+
VideoCodec.H265 -> {
206+
val byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(buffer.duplicate())
207+
if (byteBufferList.size == 3) {
208+
Log.i(TAG, "manual vps/sps/pps extraction success")
209+
val oldSps = byteBufferList[1]
210+
val oldPps = byteBufferList[2]
211+
val oldVps = byteBufferList[0]
212+
videoPacket.sendVideoInfo(oldSps, oldPps, oldVps)
213+
sendInfo = true
214+
} else {
215+
Log.e(TAG, "manual vps/sps/pps extraction failed")
216+
}
217+
}
218+
219+
else -> {
220+
Log.e(TAG, "Unsupported codec: ${getVideoCodec()}")
221+
}
222+
}
223+
}
224+
if (sendInfo && recordStatus == RecordController.Status.STARTED) {
225+
myRequestKeyFrame = null
226+
recordStatus = RecordController.Status.RECORDING
227+
listener?.onStatusChange(recordStatus)
228+
}
229+
} else if (myRequestKeyFrame != null) {
230+
myRequestKeyFrame?.onRequestKeyFrame()
231+
myRequestKeyFrame = null
232+
}
233+
}
234+
235+
override fun setVideoFormat(videoFormat: MediaFormat) {
236+
when (getVideoCodec()) {
237+
VideoCodec.H264 -> {
238+
val sps = videoFormat.getByteBuffer("csd-0")
239+
val pps = videoFormat.getByteBuffer("csd-1")
240+
if (sps != null && pps != null) {
241+
videoPacket.sendVideoInfo(sps.duplicate(), pps.duplicate(), null)
242+
sendInfo = true
243+
}
244+
}
245+
246+
VideoCodec.H265 -> {
247+
val bufferInfo = videoFormat.getByteBuffer("csd-0")
248+
if (bufferInfo != null) {
249+
val byteBufferList = VideoEncoderHelper.extractVpsSpsPpsFromH265(bufferInfo.duplicate())
250+
if (byteBufferList.size == 3) {
251+
val sps = byteBufferList[1]
252+
val pps = byteBufferList[2]
253+
val vps = byteBufferList[0]
254+
videoPacket.sendVideoInfo(sps, pps, vps)
255+
sendInfo = true
256+
}
257+
}
258+
}
259+
260+
else -> {}
261+
}
262+
if (sendInfo && recordStatus == RecordController.Status.STARTED) {
263+
myRequestKeyFrame = null
264+
recordStatus = RecordController.Status.RECORDING
265+
listener?.onStatusChange(recordStatus)
266+
}
267+
}
268+
269+
override fun setAudioFormat(audioFormat: MediaFormat) {
270+
val sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)
271+
val channels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
272+
this.sampleRate = sampleRate
273+
this.isStereo = channels > 1
274+
(audioPacket as? AacPacket)?.sendAudioInfo(sampleRate, isStereo)
275+
}
276+
277+
override fun resetFormats() {
278+
}
279+
}

0 commit comments

Comments
 (0)