執法儀app音視訊相關
技術標籤:android
最近公司在搞執法儀專案,涉及到音視訊的錄製和推流需求,因為之前並沒有這方面的經驗,花費了一段時間惡補相關知識,主要涉及到音視訊資料的採集和編碼,給我的感覺就是音視訊這塊還是很複雜的,目前也是出於學習階段,本篇文件主要講述一下在做執法儀的過程中,用到的一些知識和相關問題和大家分享一下,下面就進入正題。
音訊採集
android原生camera錄製視訊一般是通過MediaRecorder來實現的,錄製完成後直接生成mp4或者是3gp檔案,所以我們不需要單獨考慮視訊資料如何採集,音訊資料如何採集,但是如果採用MediaCodec來實現的話,我們就要單獨考慮音訊資料和視訊資料如何採集了,我們先來說一下音訊資料的採集方法。
音訊資料的採集android為我們提供了AudioRecord類,通常情況下,如果不需要對採集到的資料進行處理的話,完全可以採用MediaRecorder來去實現,但是如果你需要將採集到的資料轉化為PCM格式,AAC格式,MP3格式等,就需要使用AudioRecord類進行採集了,使用AudioRecord類之前先需要了解幾個概念:
- 取樣率
取樣率就是取樣的頻率
- 量化精度
量化精度可以理解為位寬,採用多少位來儲存資料
- 聲道數
表聲音錄製時音源數量或播放時相應的揚聲器數量。一般分為單聲道(Mono)和雙聲道(Stereo)
- bufferSizeInBytes
表示AudioRecord內部音訊緩衝區大小,一般通過getMinBufferSize來獲取
使用步驟如下:
- 定義需要設定的引數
private int samplingRate = 44100;
private int bitRate = 16000;
private int BUFFER_SIZE = 1920;
int mSamplingRateIndex = 0;
AudioRecord mAudioRecord;
- 獲取音訊緩衝區的大小
int bufferSize = AudioRecord.getMinBufferSize(samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
- 建立並例項化AudioRecord
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
- 呼叫AudioRecord的startRecording開始錄製
mAudioRecord.startRecording();
- 呼叫AudioRecord的read方法寫入資料
mAudioRecord.read(audioBuffer, BUFFER_SIZE);
- 停止錄製並釋放資源
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
經過以上步驟以後,我們就可以在本地獲取到PCM格式的音訊資料,PCM意為脈衝編碼調製,是一種原始的音訊格式資料,一般播放器不可以進行直接播放。
視訊採集:
上一小節對音訊的採集做了一個介紹,接下來說一下視訊資料的採集,視訊資料的採集一般是獲取Camera的callback資料,獲取camera的callback資料一般有如下兩種方案:
- setPreviewCallback
- setPreviewCallbackWithBuffer
然後利用 Camera.PreviewCallback 回撥介面收集到 YUV資料:
public void onPreviewFrame(byte[] bytes, Camera camera)
但是考慮到實際場景,如果業務中涉及到推流直播的需求,對實時性要求較高,這就需要我們提高預覽資料回撥的效率,而通過setPreviewCallback獲取預覽資料的話,每產生一幀都要開闢一個新的buffer,進行儲存幀資料,這樣不斷開闢和回收記憶體,GC會很頻繁,效率很低。所以我們一般採用第二種方案來獲取camera的callback資料,這種方案在每次回撥資料後都會回收快取,不需要開闢新的記憶體,達到複用記憶體的效果,很大程度上提高了幀資料的回撥效率(這種方案可以解決推流效果抖動的問題)。
mCamera.addCallbackBuffer(new byte[size]);
mCamera.setPreviewCallbackWithBuffer(previewCallback);
previewCallback = new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null)
return;
int result;
if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (camInfo.orientation + mDgree) % 360;
} else { // back-facing
result = (camInfo.orientation - mDgree + 360) % 360;
}
if (i420_buffer == null || i420_buffer.length != data.length) {
i420_buffer = new byte[data.length];
}
JNIUtil.ConvertToI420(data, i420_buffer, width, height, 0, 0, width, height, result % 360, 2);
}
};
上面程式碼就是獲取camera callback資料的實現方法,從上面程式碼我們可以看到在onPreviewFrame方法裡我們對callback上來的資料進行了一次旋轉操作,這是因為camera模組通常情況下並不是豎直安裝的,而是和手機豎直方向成90度(前攝270度),所以我們需要對callback資料進行一次旋轉操作才能達到實際的效果,這裡會有一個問題,為什麼前置攝像頭安裝角度是270度卻只要旋轉90度呢,這個其實在hal層,平臺已經幫我們做了一次旋轉操作,所以我們只需要再次旋轉90度即可。
MediaCodec編碼
通過前面兩小節的講述,我們對音訊資料和視訊資料的採集已經有了一定的瞭解,通過camera的onPreviewFrame獲取到了yuv格式的視訊資料,通過AudioRecord獲取到了pcm格式的音訊資料,不過這兩種格式的資料都是原始資料,如果需要混合成mp4格式的檔案是不行的,音訊資料需要編碼為aac格式的資料,視訊資料需要編碼為h264或者h265格式的資料,這個時候MediaCodec就登場了,MediaCodec是android為我們提供的硬編碼api,依賴於裝置內部的硬編碼器,效率較高,具體可以參考另一篇文件《MediaCodec原理解析》,下面來了解一下MediaCodec的使用步驟:
音訊編碼
上面已經分析了通過AudioRecord來採集音訊的步驟,不過獲取到的是PCM格式的音訊資料,但是由於其佔用頻寬較大,不利於直接進行傳輸,所以在視訊合成和推流之前,需要通過MediaCodec來進行編碼。具體實現步驟和視訊編碼類似
- 例項化MediaCodec例項
mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
- 設定MediaFormat引數
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, samplingRate);
format.setInteger(MediaFormat.KEY_AAC_PROFILE,
MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, BUFFER_SIZE);
-
MediaFormat.KEY_MIME
設定編碼型別,audio/mp4a-latm為aac格式。 -
MediaFormat.KEY_BIT_RATE
設定位元率也就是位元速率,一般設定為44.1khz可以相容大部分裝置 -
MediaFormat.KEY_AAC_PROFILE
設定可用於編解碼器元件的配置檔案
配置好MediaFormat後,執行MediaCodec的configure方法然後呼叫start讓MediaCodec進入到工作狀態
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mMediaCodec.start();
4.開始編碼
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
bufferIndex = mMediaCodec.dequeueInputBuffer(1000);
len = mAudioRecord.read(audioBuffer, BUFFER_SIZE);
mMediaCodec.queueInputBuffer(bufferIndex, 0, len, presentationTimeUs, 0);
獲取到MediaCodec的輸入緩衝區,通過AudioRecord的read方法把採集到的PCM格式資料迴圈填充到MediaCodec的輸入緩衝區內,MediaCodec編碼完成以後,接下來就是從輸出緩衝區內讀取編碼後的音訊資料了:
mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000);
outputBuffer = mMediaCodec.getOutputBuffer(index);
addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7);
mMediaCodec.releaseOutputBuffer(index, false);
獲取輸出緩衝區以後,這裡呼叫了addADTStoPacket方法來新增一些頭部資訊,最後呼叫releaseOutputBuffer釋放掉輸出緩衝區。
視訊編碼
- 例項化MediaCodec
try {
mMediaCodec = MediaCodec.createByCodecName(info.mName);
} catch (IOException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
createByCodecName引數我們一般設定如下兩種型別:
- “video/avc” - H.264
- “video/hevc” - H.265
- 配置MediaCodec
public void configure(
MediaFormat format,
Surface surface,
MediaCrypto crypto,
int flags
);
-
Surface surface
一般用於解碼的時候使用,通過指定surface,編碼後的資料顯示在surface上 -
MediaCrypto crypto
用於媒體資料的加密,一般會傳入null -
int flags
常量值,如果作為編碼使用傳入
MediaCodec.CONFIGURE_FLAG_ENCODE -
MediaFormat format
輸入或輸出資料的格式,這個引數比較重要,使用步驟如下:
- 例項化MediaFormat例項
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
- 設定編碼引數
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate + 3000);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, info.mColorFormat);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
-
MediaFormat.KEY_BIT_RATE
位元率也就是位元速率,單位時間內資料的傳輸量,影響視訊的清晰度。 -
MediaFormat.KEY_FRAME_RATE
幀率,影響視訊的流暢性。 -
MediaFormat.KEY_COLOR_FORMAT
顏色空間,這個需要獲取裝置支援的編碼器以及編碼器所支援的顏色空間,android裝置camera一般callback資料為NV21或者YV12格式,所以在編碼視訊資料之前,需要把callback資料轉換為裝置編碼器所支援的格式,否則編碼得到的資料可能會出現花屏、疊影、顏色失真等現象。 -
MediaFormat.KEY_I_FRAME_INTERVAL
關鍵幀間隔,通常情況下設定為1,單位是s
配置好MediaCodec後,呼叫
mMediaCodec.start();
讓MediaCodec進入工作狀態。
- 資料流處理
準備工作已經就緒,接下來就是編碼視訊資料的具體實現了:
int bufferIndex = mMediaCodec.dequeueInputBuffer(0);
ByteBuffer buffer = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
buffer = mMediaCodec.getInputBuffer(bufferIndex);
} else {
buffer = inputBuffers[bufferIndex];
}
mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME);
MediaCodec維護著一組輸入輸出buffer,編碼的時候首先需要通過
mMediaCodec.dequeueInputBuffer(0)
獲取輸入buffer的索引,然後通過
mMediaCodec.getInputBuffer(bufferIndex);
獲取到輸入buffer,獲取到輸入buffer以後,就可以通過
mMediaCodec.queueInputBuffer(bufferIndex, 0, data.length, System.nanoTime() / 1000, MediaCodec.BUFFER_FLAG_KEY_FRAME);
填充我們的資料交給MediaCodec進行編碼處理了。
MediaCodec編碼完成以後,我們就可以從輸出緩衝區來獲取編碼後的資料了
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
outputBuffers = mMediaCodec.getOutputBuffers(outputBufferIndex);
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
首先通過
mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
獲取到輸出緩衝區索引,然後通過
mMediaCodec.getOutputBuffers(outputBufferIndex);
獲取一組輸出緩衝區來消耗掉裡面的資料,處理完成以後,呼叫
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
來釋放掉輸出緩衝區。
至此視訊資料已經編碼完成,編碼後的視訊資料格式為h264/h265,後面的視訊合成以及推流都會用到此型別的檔案。
至此音視訊資料的採集以及編碼就完成了,編碼後我們得到了h264/h265的視訊資料和aac格式的音訊資料,接下來就是音視訊合成和推流了。
音視訊合成
android平臺Camera錄製視訊一般採用MediaRecorder來實現,MediaRecorder封裝的比較完善,可以直接錄製mp4,3gp視訊檔案,如果沒有特殊需求的話,採用MediaRecorder就可以實現大部分功能,但是如果想了解MP4錄製的整個過程,從採集、編碼、封裝成MP4到解析、解碼、播放就需要採用MediaCodec + MediaMuxer來實現了。
先來了解一下MediaMuxer:
MediaMuxer facilitates muxing elementary streams. Currently MediaMuxer supports MP4, Webm and 3GP file as the output. It also supports muxing B-frames in MP4 since Android Nougat.
這是谷歌官方文件對MediaMuxer的解釋,MediaMuxer用來產生一個混合的音訊和視訊的多媒體檔案,目前支援的格式有MP4,3GP,Webm。下面是谷歌的官方使用例項:
MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);
// More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()
// or MediaExtractor.getTrackFormat().
MediaFormat audioFormat = new MediaFormat(...);
MediaFormat videoFormat = new MediaFormat(...);
int audioTrackIndex = muxer.addTrack(audioFormat);
int videoTrackIndex = muxer.addTrack(videoFormat);
ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);
boolean finished = false;
BufferInfo bufferInfo = new BufferInfo();
muxer.start();
while(!finished) {
// getInputBuffer() will fill the inputBuffer with one frame of encoded
// sample from either MediaCodec or MediaExtractor, set isAudioSample to
// true when the sample is audio data, set up all the fields of bufferInfo,
// and return true if there are no more samples.
finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo);
if (!finished) {
int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex;
muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);
}
};
muxer.stop();
muxer.release();
更多具體解釋可以參考谷歌官方文件
https://developer.android.google.cn/reference/android/media/MediaMuxer?hl=en
下面就來講一下MediaMuxer的具體使用步驟:
- 建立MediaMuxer例項
mMuxer = new MediaMuxer(mFilePath + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
MediaMuxer的建構函式接收兩個引數,第一個引數傳入的是我們需要生成視訊檔案的路徑,第二個引數傳入的是生成視訊檔案的格式,這裡我們傳入了MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,表示我們期望生成的視訊檔案為MP4格式
- 新增媒體通道
public synchronized void addTrack(MediaFormat format, boolean isVideo) {
// now that we have the Magic Goodies, start the muxer
if (mAudioTrackIndex != -1 && mVideoTrackIndex != -1)
throw new RuntimeException("already add all tracks");
try {
int track = mMuxer.addTrack(format);
Log.i(TAG, String.format("addTrack %s result %d", isVideo ? "video" : "audio", track));
if (isVideo) {
mVideoFormat = format;
mVideoTrackIndex = track;
if (mAudioTrackIndex != -1) {
Log.i(TAG, "both audio and video added,and muxer is started");
mMuxer.start();
mBeginMillis = System.currentTimeMillis();
}
} else {
mAudioFormat = format;
mAudioTrackIndex = track;
if (mVideoTrackIndex != -1) {
mMuxer.start();
mBeginMillis = System.currentTimeMillis();
}
}
} catch (RuntimeException e) {
Log.i(TAG, "add track exception :" + e.getMessage());
}
}
新增媒體通道就是新增音訊和視訊通道,通過呼叫addTrack來實現,addTrack接收一個MediaFormat型別的引數,可以通過MediaCodec的getOutputFormat方法獲取。新增好媒體通道後就可以呼叫
mMuxer.start();
開始合成了。
- 音視訊資料寫入
muxer.writeSampleData(videoTrackIndex, byteBuf, bufferInfo);
- videoTrackIndex
媒體通道索引
- byteBuf
MediaCodec輸出緩衝區
- bufferInfo
記錄MediaCodec輸出緩衝區的資訊
writeSampleData就是寫入我們需要合成的音視訊資料,這裡傳入的需要注意每次只能新增一幀視訊資料或者單個Sample的音訊資料,並且BufferInfo物件的值一定要設定正確
。
- 關閉並釋放資源
mMuxer.stop();
mMuxer.release();
音視訊合成完成以後,呼叫如上程式碼來關閉並釋放資源。
以上就是MediaMuxer音視訊合成MP4檔案的具體流程,更詳細的流程可以參考執法儀app原始碼的EasyMuxer類。
音視訊推流
手機直播在最近幾年開始興起,南抖音,北快手,相對於圖片文字,視訊傳遞資訊更加清晰直接,使用者體驗更好,直播的原理其實就是裝置端採集音視訊資料然後經過相應處理後推流到對應的流媒體伺服器,流媒體伺服器負責分發資料,這樣我們就可以用手機看到裝置端實時的畫面。
先來看一張流程圖:
這張圖描述了裝置端音視訊採集到編碼然後推流到流媒體伺服器的流程,關於攝像頭和音訊的的採集以及視訊資料的編碼我們上面已經瞭解過,音訊採集為PCM格式的原始資料經過編碼為aac,視訊採集為yuv的原始資料經過編碼為h264/h265.
目前常用的推流協議有如下三種:
- RTMP(Real Time Messaging Protocol)
RTMP協議基於 TCP,是一種設計用來進行實時資料通訊的網路協議,主要用來在 flash/AIR 平臺和支援 RTMP 協議的流媒體/互動伺服器之間進行音視訊和資料通訊。
RTMP 是目前主流的流媒體傳輸協議,廣泛用於直播領域,可以說市面上絕大多數的直播產品都採用了這個協議
- RTSP(Real Time Streaming Protocol)
實時流傳送協議,是用來控制聲音或影像的多媒體串流協議,相對於RTMP來說,實時性更好,如果對延時性要求較高的話,建議採用RTSP傳輸協議
- HLS(HTTP Live Streaming)
蘋果公司(AppleInc.)實現的基於HTTP的流媒體傳輸協議
音視訊推流到流媒體伺服器後,就是伺服器的分發了,流媒體伺服器的作用是負責直播流的釋出和轉播分發功能。
流媒體伺服器有諸多選擇,我在本地除錯的時候採用的是開源的EasyDarwin,使用起來還是比較方便的,還有Nginx,可以根據個人喜好去選擇。
最後我們就可以通過播放器來去流媒體伺服器去拉流觀看音視訊了,常用的播放器vlc,輸入相應的媒體服務地址就可以觀看了,類似如下:
rtsp://172.16.2.34:8554/live/abc
總結一下,視訊採集處理後推流到流媒體伺服器,第一部分功能完成。第二部分就是流媒體伺服器,負責把從第一部分接收到的流進行處理並分發給觀眾。第三部分就是觀眾啦,只需要擁有支援流傳輸協議的播放器即可。
目前做的執法儀app,採用的是EasyDarwin開源的推流框架,具體實現流程如下:
- 例項化Push物件
mEasyPusher = new EasyPusher();
- 初始化push
mEasyPusher.initPush(mApplicationContext, callback);
這裡我們傳入了一個callback例項,用於監聽推流的狀態
:
mMediaStream.startStream(ip, port, id, new InitCallback() {
@Override
public void onCallback(int code) {
switch (code) {
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_INVALID_KEY:
sendMessage("無效Key");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_SUCCESS:
sendMessage("啟用成功");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECTING:
sendMessage("連線中");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECTED:
sendMessage("連線成功");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECT_FAILED:
sendMessage("連線失敗");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_CONNECT_ABORT:
sendMessage("連線異常中斷");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_PUSHING:
sendMessage("推流中");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_PUSH_STATE_DISCONNECTED:
sendMessage("斷開連線");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_PLATFORM_ERR:
sendMessage("平臺不匹配");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_COMPANY_ID_LEN_ERR:
sendMessage("COMPANY不匹配");
break;
case EasyPusher.OnInitPusherCallback.CODE.EASY_ACTIVATE_PROCESS_NAME_LEN_ERR:
sendMessage("程序名稱長度不匹配");
break;
}
}
});
- 設定推流相關引數
mEasyPusher.setMediaInfo(!mSWCodec && info.hevcEncode ? Pusher.Codec.EASY_SDK_VIDEO_CODEC_H265 : Pusher.Codec.EASY_SDK_VIDEO_CODEC_H264, 25, Pusher.Codec.EASY_SDK_AUDIO_CODEC_AAC, 1, 8000, 16);
這個方法用來設定推流音視訊的相關引數,包括音視訊格式,幀率和位元速率,音訊格式為aac,視訊格式為h265
4. 開始推流
mEasyPusher.start(ip, port, String.format("%s.sdp", id), Pusher.TransType.EASY_RTP_OVER_TCP);
設定流媒體伺服器的地址,因為我們採用的是TCP協議,所以這裡直接設定Pusher.TransType.EASY_RTP_OVER_TCP即可。
在Push.java下面,我們會看到還有如下的一個方法:
public void push(byte[] data, int offset, int length, long timestamp, int type);
從方法命名我們其實就可以猜測到,這個方法就是用來推送我們編碼的音視訊資料的,音訊資料推送在AudioStream.java下面:
ps.push(mBuffer.array(), 0, mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000, 0);
這裡就是傳入了MediaCodec編碼後的aac格式的音訊資料。
視訊資料的推送在HwConsumer.java下面:
mPusher.push(h264/h265, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 2);
這裡傳入的是MediaCodec編碼後的h264/h265格式的視訊資料(執法儀app採用的是h265)。
總結
至此,執法儀app音視訊的採集,編碼,音視訊合成以及如何推送到流媒體伺服器就講完了,下面來總結一下:
- 音訊採集
通過AudioRecord進行音訊的採集,具體過程為採集,量化,編碼(此編碼非MediaCodec編碼)得到pcm格式的音訊檔案。
- 視訊採集
設定Camera的setPreviewCallbackWithBuffer回撥,在onPreviewFrame下面實時獲取到YUV格式的預覽幀資料,採集到的YUV資料需要旋轉為自然方向。
- 音訊編碼
採集到的pcm格式的音訊檔案經過MediaCodec編碼為aac格式的音訊檔案。
- 視訊編碼
原始的YUV格式視訊流資料經過MediaCodec編碼為h264/h265格式的視訊檔案。
- 音視訊合成為MP4檔案
音視訊合成採用MediaMuxer來實現,視訊資料來源為編碼後的h264/h265資料,音訊資料來源為編碼後的aac資料。
- 推流
編碼後的h264/h265的視訊資料和aac音訊資料推送到流媒體伺服器。
由於水平有限,目前還在學習中,文中表述難免有不當之處,文中涉及到的程式碼具體可以參考:
http://192.168.11.104/gitweb/?p=SprocommLawApp.git;a=summary