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