Android 音視頻深入 五 完美的錄視頻(附源碼下載)

來源:互聯網
上載者:User

標籤:_for   android   protected   結束   而不是   semi   interval   prepare   oid   

本篇項目地址,名字是錄視頻,求star

https://github.com/979451341/Audio-and-video-learning-materials

這一次的代碼錄視頻在各個播放器都可以用,有時間長度顯示,對比上一次的代碼說說為何兩者效果不同,但是我先補充一些之前漏掉的MediaCodec的官方說明還有MediaCodec.BufferInfo

1.MediaCodec的補充

buffer_flag_codec_config:提示標誌等含有編碼初始化/轉碼器的具體資料,而不是媒體資料緩衝區。

buffer_flag_end_of_stream:這個訊號流的結束

buffer_flag_sync_frame提:包含資料的同步框架緩衝區。

info_output_buffers_changed:輸出緩衝區發生了變化,客戶必須向輸出緩衝區新設定的返回getoutputbuffers()這一點上。

info_output_format_changed:輸出格式發生了變化,隨後的資料將按照新格式。

info_try_again_later:表明呼叫逾時,逾時時調用dequeueoutputbuffer

dequeueInputBuffer(long timeoutUs):返回輸入緩衝區的索引以填充有效資料或-如果當前沒有這樣的緩衝區,則返回1。

dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs):將輸出緩衝器,擋住了timeoutUs微妙

flush():重新整理輸入和輸出連接埠的組件,所有指標以前返回調用dequeueinputbuffer(長)和dequeueoutputbuffer(mediacodec.bufferinfo,長)無效。

mediacodecinfo getcodecinfo():擷取轉碼器資訊。

getinputbuffers():這start()返回後調用。

getoutputbuffers():在start()返回時dequeueoutputbuffer訊號輸出緩衝的變化通過返回info_output_buffers_changed

mediaformat getoutputformat():這叫dequeueoutputbuffer訊號後返回info_output_format_changed格式變化

queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags):在指定索引上填充一個輸入緩衝區之後,將其提交給組件。

MediaCodec.BufferInfo每個緩衝區中繼資料套件括一個位移量和大小,指定相關聯轉碼器緩衝區中有效資料的範圍。 我就理解為將緩衝區資料寫入本地的時候需要做出一些調整的 參數

2.代碼對比
不廢話直接來看Video編碼這部分,首先對比MediaFormat

    mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, this.mWidth, this.mHeight);    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);    final MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);    format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);  // API >= 18    format.setInteger(MediaFormat.KEY_BIT_RATE, calcBitRate());    format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);

兩者什麼寬高視頻格式都一樣,甚至幀率、採集點大小都一樣,只有一個不一樣MediaFormat.KEY_COLOR_FORMAT,這個官方說明是:
由使用者佈建編碼器,在解碼器的輸出格式中可讀。

也就是說它能夠設定編碼器,能設定編碼方式,也就是說這個兩個工程最大的不同是編碼,我們繼續對比

private void encodeFrame(byte[] input) {
Log.w(TAG, "VideoEncoderThread.encodeFrame()");

    // 將原始的N21資料轉為I420    NV21toI420SemiPlanar(input, mFrameData, this.mWidth, this.mHeight);    ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();    ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();    int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);    if (inputBufferIndex >= 0) {        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];        inputBuffer.clear();        inputBuffer.put(mFrameData);        mMediaCodec.queueInputBuffer(inputBufferIndex, 0, mFrameData.length, System.nanoTime() / 1000, 0);    } else {        Log.e(TAG, "input buffer not available");    }

—————-省略
}

protected void encode(final ByteBuffer buffer, final int length, final long presentationTimeUs) {    if (!mIsCapturing) return;    final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();    while (mIsCapturing) {        final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);        if (inputBufferIndex >= 0) {            final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];            inputBuffer.clear();            if (buffer != null) {                inputBuffer.put(buffer);            }

// if (DEBUG) Log.v(TAG, "encode:queueInputBuffer");
if (length <= 0) {
// send EOS
mIsEOS = true;
if (DEBUG) Log.i(TAG, "send BUFFER_FLAG_END_OF_STREAM");
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, 0,
presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
break;
} else {
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, length,
presentationTimeUs, 0);
}
break;
} else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
// wait for MediaCodec encoder is ready to encode
// nothing to do here because MediaCodec#dequeueInputBuffer(TIMEOUT_USEC)
// will wait for maximum TIMEOUT_USEC(10msec) on each call
}
}
}

一樣,說實話沒啥不同,只有一個不同那就是第一個編碼函數開頭用了這個,然後在編碼的時候使用了i420bytes,這個i420bytes通過xnv21bytes變換而來

private static void NV21toI420SemiPlanar(byte[] nv21bytes, byte[] i420bytes, int width, int height) {    System.arraycopy(nv21bytes, 0, i420bytes, 0, width * height);    for (int i = width * height; i < nv21bytes.length; i += 2) {        i420bytes[i] = nv21bytes[i + 1];        i420bytes[i + 1] = nv21bytes[i];    }}

我們再看每一次編碼一個幀放入混合器分別是咋搞的,下面這個代碼意思是監聽編碼一幀前後編碼器的狀態的變化並將編碼後的資料放入MP4檔案裡,然後釋放記憶體

            drain();            // request stop recording            signalEndOfInputStream();            // process output data again for EOS signale            drain();            // release all related objects            release();

我們在看看drain()裡面說啥,
開頭就mMediaCodec.getOutputBuffers輸出資料,然後得到編碼器的狀態,如果逾時了就退出當前迴圈,如果輸出緩衝區發生了變化,那就在執行一次mMediaCodec.getOutputBuffers,如果輸出格式變化了重新給編碼器配置MediaFormat,然後編碼器再次加入混合器,狀態的值小於0就是不可預料的狀態了,既然是不可預料那就沒辦法了,剩下來的就是正常的狀態,配合著BufferInfo將資料寫入混合器

protected void drain() {    if (mMediaCodec == null) return;    ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers();    int encoderStatus, count = 0;    final MediaMuxerWrapper muxer = mWeakMuxer.get();    if (muxer == null) {

// throw new NullPointerException("muxer is unexpectedly null");
Log.w(TAG, "muxer is unexpectedly null");
return;
}
LOOP: while (mIsCapturing) {
// get encoded data with maximum timeout duration of TIMEOUT_USEC(=10[msec])
encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// wait 5 counts(=TIMEOUT_USEC x 5 = 50msec) until data/EOS come
if (!mIsEOS) {
if (++count > 5)
break LOOP; // out of while
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (DEBUG) Log.v(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
// this shoud not come when encoding
encoderOutputBuffers = mMediaCodec.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
if (DEBUG) Log.v(TAG, "INFO_OUTPUT_FORMAT_CHANGED");
// this status indicate the output format of codec is changed
// this should come only once before actual encoded data
// but this status never come on Android4.3 or less
// and in that case, you should treat when MediaCodec.BUFFER_FLAG_CODEC_CONFIG come.
if (mMuxerStarted) { // second time request is error
throw new RuntimeException("format changed twice");
}
// get output format from codec and pass them to muxer
// getOutputFormat should be called after INFO_OUTPUT_FORMAT_CHANGED otherwise crash.
final MediaFormat format = mMediaCodec.getOutputFormat(); // API >= 16
mTrackIndex = muxer.addTrack(format);
mMuxerStarted = true;
if (!muxer.start()) {
// we should wait until muxer is ready
synchronized (muxer) {
while (!muxer.isStarted())
try {
muxer.wait(100);
} catch (final InterruptedException e) {
break LOOP;
}
}
}
} else if (encoderStatus < 0) {
// unexpected status
if (DEBUG) Log.w(TAG, "drain:unexpected result from encoder#dequeueOutputBuffer: " + encoderStatus);
} else {
final ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
if (encodedData == null) {
// this never should come...may be a MediaCodec internal error
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// You shoud set output format to muxer here when you target Android4.3 or less
// but MediaCodec#getOutputFormat can not call here(because INFO_OUTPUT_FORMAT_CHANGED don‘t come yet)
// therefor we should expand and prepare output format from buffer data.
// This sample is for API>=18(>=Android 4.3), just ignore this flag here
if (DEBUG) Log.d(TAG, "drain:BUFFER_FLAG_CODEC_CONFIG");
mBufferInfo.size = 0;
}

            if (mBufferInfo.size != 0) {                // encoded data is ready, clear waiting counter                count = 0;                if (!mMuxerStarted) {                    // muxer is not ready...this will prrograming failure.                    throw new RuntimeException("drain:muxer hasn‘t started");                }                // write encoded data to muxer(need to adjust presentationTimeUs.                mBufferInfo.presentationTimeUs = getPTSUs();                muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);                prevOutputPTSUs = mBufferInfo.presentationTimeUs;            }            // return buffer to encoder            mMediaCodec.releaseOutputBuffer(encoderStatus, false);            if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {                // when EOS come.                mIsCapturing = false;                break;      // out of while            }        }    }}

打完收工,代碼很多,多多抽象理解,重在理解過程,細節。。。。。,自我總結

Android 音視頻深入 五 完美的錄視頻(附源碼下載)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.