Android 直播流程3()

3、使用

MediaCodec創(chuàng)建之后,需要通過start()方法進(jìn)行開啟。MediaCodec有輸入緩沖區(qū)隊(duì)列和輸出緩沖區(qū)隊(duì)列,不斷通過往輸入緩沖區(qū)隊(duì)列傳遞數(shù)據(jù),經(jīng)過MediaCodec處理后就可以得到響應(yīng)的輸出數(shù)據(jù)。當(dāng)在編碼的時候,需要向輸入緩沖區(qū)傳入采集到的原始的視音頻數(shù)據(jù),然后獲取輸出緩沖區(qū)的數(shù)據(jù),輸出出來的數(shù)據(jù)也就是編碼處理后的數(shù)據(jù)。當(dāng)在解碼的時候,往輸入緩沖區(qū)輸入需要解碼的數(shù)據(jù),然后獲取輸出緩沖區(qū)的數(shù)據(jù),輸出出來的數(shù)據(jù)也就是解碼后得到的原始的視音頻數(shù)據(jù)。當(dāng)需要清空輸入和輸出緩沖區(qū)的時候,可以調(diào)用MediaCodec的flush()方法。當(dāng)編碼或者解碼結(jié)束時,通過往輸入緩沖區(qū)輸入帶結(jié)束標(biāo)記的數(shù)據(jù),然后從輸出緩沖區(qū)可以得到這個結(jié)束標(biāo)記,從而完成整個編解碼過程。下面一張圖片很好地展示了MediaCodec的狀態(tài)變化。

????????????????????????????????????????????????????????????????MediaCodec狀態(tài)

對于MediaCodec通過處理輸入的數(shù)據(jù),從而得到輸出數(shù)據(jù)。MediaCodec通過一系列的輸入和輸出緩沖區(qū)來處理數(shù)據(jù)。如下圖所示,輸入客戶端通過查詢得到空的輸入緩沖區(qū),然后往里面填充數(shù)據(jù),然后將輸入緩沖區(qū)傳遞給MediaCodec;輸出客戶端通過查詢得到塞滿的輸出緩沖區(qū),然后得到里面的數(shù)據(jù),然后通知MediaCodec釋放這個輸出緩沖區(qū)。

????????????????????????????????????????????????????????????????MediaCodec過程

在API 21及以后可以通過下面這種異步的方式來使用MediaCodec。

MediaCodec codec = MediaCodec.createByCodecName(name);

MediaFormat mOutputFormat; // member variable

codec.setCallback(new MediaCodec.Callback() {

? @Override

? void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {

? ? ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);

? ? // fill inputBuffer with valid data

? ? …

? ? codec.queueInputBuffer(inputBufferId, …);

? }

? @Override

? void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {

? ? ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);

? ? MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A

? ? // bufferFormat is equivalent to mOutputFormat

? ? // outputBuffer is ready to be processed or rendered.

? ? …

? ? codec.releaseOutputBuffer(outputBufferId, …);

? }

? @Override

? void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {

? ? // Subsequent data will conform to new format.

? ? // Can ignore if using getOutputFormat(outputBufferId)

? ? mOutputFormat = format; // option B

? }

? @Override

? void onError(…) {

? ? …

? }

});

codec.configure(format, …);

mOutputFormat = codec.getOutputFormat(); // option B

codec.start();

// wait for processing to complete

codec.stop();

codec.release();

從API 21開始,可以使用下面這種同步的方式來使用MediaCodec。

MediaCodec codec = MediaCodec.createByCodecName(name);

codec.configure(format, …);

MediaFormat outputFormat = codec.getOutputFormat(); // option B

codec.start();

for (;;) {

? int inputBufferId = codec.dequeueInputBuffer(timeoutUs);

? if (inputBufferId >= 0) {

? ? ByteBuffer inputBuffer = codec.getInputBuffer(…);

? ? // fill inputBuffer with valid data

? ? …

? ? codec.queueInputBuffer(inputBufferId, …);

? }

? int outputBufferId = codec.dequeueOutputBuffer(…);

? if (outputBufferId >= 0) {

? ? ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);

? ? MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A

? ? // bufferFormat is identical to outputFormat

? ? // outputBuffer is ready to be processed or rendered.

? ? …

? ? codec.releaseOutputBuffer(outputBufferId, …);

? } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

? ? // Subsequent data will conform to new format.

? ? // Can ignore if using getOutputFormat(outputBufferId)

? ? outputFormat = codec.getOutputFormat(); // option B

? }

}

codec.stop();

codec.release();

在API版本21之前,獲取緩沖區(qū)的方式有所不同,不能直接得到相應(yīng)的緩沖區(qū),需要根據(jù)索引序號從緩沖區(qū)列表中得到相應(yīng)的緩沖區(qū),具體的代碼如下所示:

MediaCodec codec = MediaCodec.createByCodecName(name);

codec.configure(format, …);

codec.start();

ByteBuffer[] inputBuffers = codec.getInputBuffers();

ByteBuffer[] outputBuffers = codec.getOutputBuffers();

for (;;) {

? int inputBufferId = codec.dequeueInputBuffer(…);

? if (inputBufferId >= 0) {

? ? // fill inputBuffers[inputBufferId] with valid data

? ? …

? ? codec.queueInputBuffer(inputBufferId, …);

? }

? int outputBufferId = codec.dequeueOutputBuffer(…);

? if (outputBufferId >= 0) {

? ? // outputBuffers[outputBufferId] is ready to be processed or rendered.

? ? …

? ? codec.releaseOutputBuffer(outputBufferId, …);

? } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

? ? outputBuffers = codec.getOutputBuffers();

? } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

? ? // Subsequent data will conform to new format.

? ? MediaFormat format = codec.getOutputFormat();

? }

}

codec.stop();

codec.release();

當(dāng)數(shù)據(jù)輸入結(jié)束的時候,通過queueInputBuffer的輸入標(biāo)記設(shè)置為BUFFER_FLAG_END_OF_STREAM來通知MediaCodec結(jié)束編碼。當(dāng)MediaCodec作為編碼器的時候, dequeueOutputBuffer方法能夠得到當(dāng)前編碼輸出緩沖區(qū)數(shù)據(jù)的相關(guān)信息,這些信息存儲在bufferInfo里面,通過bufferInfo信息能夠得到數(shù)據(jù)的真實(shí)長度,當(dāng)前數(shù)據(jù)為關(guān)鍵幀或者非關(guān)鍵幀等等信息。

當(dāng)使用Output Surface作為解碼的輸出的時候,可以根據(jù)以下情況來設(shè)置是否將視頻渲染到Surface上。

releaseOutputBuffer(bufferId, false)? //不渲染buffer里面的數(shù)據(jù)

releaseOutputBuffer(bufferId, true)? //渲染buffer里面的數(shù)據(jù)

releaseOutputBuffer(bufferId, timestamp)? //在特定時間渲染buffer里面的數(shù)據(jù)

當(dāng)使用Input Surface作為編碼器輸入的時候,不允許使用dequeueInputBuffer。當(dāng)輸入結(jié)束的時候,使用signalEndOfInputStream()來使得編碼器停止。

MediaMuxer

前面講述了MediaExtractor(視音頻分離器),現(xiàn)在講述MediaMuxer(視音頻合成器)。MediaMuxer是Android提供的視音頻合成器,目前只支持mp4和webm兩種格式的視音頻合成。一般來時視音頻媒體都有視頻軌道和音頻軌道,有些時候也還有字母軌道,MediaMuxer將這些軌道糅合在一起存儲在一個文件中。

MediaMuxer在Android中一個最常使用的場景是錄制mp4文件。一般來說當(dāng)存儲為mp4文件時,視頻軌道一般是經(jīng)過編碼處理后的h264視頻,音頻軌道一般是經(jīng)過編碼后處理的aac音頻。前面已經(jīng)講述了如何對采集的視頻和音頻進(jìn)行硬編,那么這時候如果對硬編后的視頻和音頻使用MediaMuxer進(jìn)行合成,那么就可以合成為mp4文件。

下面是MediaMuxer一般的使用方法。

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();

其實(shí)上面的注釋很好說明了MediaMuxer的使用場景。視音頻軌道的初始化需要傳入MediaFormat,而MediaFormat可以通過MediaCodec.getOutputFormat()獲?。ú杉筮M(jìn)行硬編得到MediaFormat),也可以通過MediaExtractor.getTrackFormat()獲?。ǚ蛛x器分離出視音頻得到MediaFormat)。上面包含了兩個應(yīng)用場景,一個是采集,一個是轉(zhuǎn)碼。

結(jié)合

MediaExtractor和MediaCodec結(jié)合使用可以實(shí)現(xiàn)視頻的播放功能,MediaCodec和MediaMuxer結(jié)合使用可以實(shí)現(xiàn)視頻的錄制功能,MediaExtractor、MediaCodec和MediaMuxer三者一起使用可以實(shí)現(xiàn)視頻的轉(zhuǎn)碼功能。下面講述一下這幾個功能的實(shí)現(xiàn)。

1、視音頻錄制

之前講述了視頻的采集和音頻的采集,將采集到的視音頻通過MediaCodec進(jìn)行編碼處理,之后將編碼數(shù)據(jù)傳遞到MediaMuxer進(jìn)行合成,也就完成了視音頻錄制的功能。

????????????????????????????????????????????????????????????????????????視頻錄制

根據(jù)視音頻采集的相關(guān)參數(shù)創(chuàng)建MediaCodec,當(dāng)MediaCodec的outputBufferId為INFO_OUTPUT_FORMAT_CHANGED時,可以通過codec.getOutputFormat()得到相應(yīng)的MediaFormat,之后便可以用這個MediaFormat為MediaMuxer添加相應(yīng)的視音頻軌道。通過codec.dequeueOutputBuffer(…)可以得到編碼后的數(shù)據(jù)的bufferInfo信息和相應(yīng)的數(shù)據(jù),之后將這個數(shù)據(jù)和bufferInfo通過muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo)傳遞給Muxer,也就將整個視音頻數(shù)據(jù)合成到了mp4中。

2、視音頻播放

????????????????????????????????????????????????????????????????????????????????視音頻播放

利用Android提供的Media API來實(shí)現(xiàn)一個播放器也是可以的,實(shí)際上Google著名的開源項(xiàng)目ExoPlayer就是這么做的。

上面的示意圖簡要描述了一個簡單的本地播放器的結(jié)構(gòu)。利用MediaExtractor分離視音頻文件,得到相應(yīng)的音頻軌道和視頻軌道。之后通過MediaExtractor從相應(yīng)的軌道中獲取數(shù)據(jù),并且將這些數(shù)據(jù)傳遞給MediaCodec的輸入緩沖區(qū),經(jīng)過MediaCodec的解碼便可以得到相應(yīng)的原始數(shù)據(jù)。音頻解碼后可以得到PCM數(shù)據(jù),從而可以傳遞給AudioTrack進(jìn)行播放。視頻解碼后可以渲染到相應(yīng)的Surface,這個Surface可以是通過SurfaceTexture創(chuàng)建,而SurfaceTexture是可以通過紋理創(chuàng)建的,從而將解碼后的視頻數(shù)據(jù)傳遞到紋理上了。

MediaExtractor解析視音頻文件,可以得到相應(yīng)數(shù)據(jù)的pts,之后pts可以傳輸?shù)組ediaCodec,之后在MediaCodec的輸出里面可以得到相應(yīng)的pts,之后在根據(jù)視音頻的pts來控制視音頻的渲染,從而實(shí)現(xiàn)視音頻的同步。

3、視音頻轉(zhuǎn)碼

視音頻的轉(zhuǎn)碼,其實(shí)就是通過MediaExtractor解析相應(yīng)的文件,之后得到相應(yīng)的視頻軌道和音頻軌道,之后將軌道里的數(shù)據(jù)傳輸?shù)組ediaCodec進(jìn)行解碼,然后將解碼后的數(shù)據(jù)進(jìn)行相應(yīng)的處理(例如音頻變聲、視頻裁剪、視頻濾鏡),之后將處理后的數(shù)據(jù)傳遞給MediaCodec進(jìn)行編碼,最后利用MediaMuxer將視頻軌道和音頻軌道進(jìn)行合成,從而完成了整個轉(zhuǎn)碼過程。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?視音頻轉(zhuǎn)碼

講述了,如何使用Media API進(jìn)行相應(yīng)的錄制、播放、轉(zhuǎn)碼,講述了如何將視音頻編解碼和紋理相結(jié)合

以上就是直播功能的基本流程:

采集視頻、音頻數(shù)據(jù) ---- 將視頻數(shù)據(jù)通過h264/aac進(jìn)行編碼 ---- 將編碼好的音頻視頻數(shù)據(jù)混合封裝成flv的格式 ---- 把flv數(shù)據(jù)推送到支持rtmp的服務(wù)器-----獲取音視頻數(shù)據(jù)解碼播放。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容