天天看點

MediaCodec 編碼結合 FFmpeg 封裝流

在 Android 平台上合成視訊一般使用 MediaCodec 進行寫死,使用 MediaMuxer 進行封裝,但是因為 MediaMuxer 在某些機型上合成的視訊在其他手機上播放會出現問題,而且隻支援一個音頻軌道,是以可以選用 FFmpeg 來封裝編碼後的音視訊流。

具體流程如下:

1. 建立FFmpeg AVFormatContext

AVFormatContext*ofmt_ctx=nullptr;
intret=avformat_alloc_output_context2(&ofmt_ctx, nullptr, "mp4", filePath);
AVOutputFormat*ofmt=ofmt_ctx->oformat;
ret=avio_open(&ofmt_ctx->pb, filePath, AVIO_FLAG_WRITE);      

2. 添加音視訊流

這裡以添加 h264 視訊流為例:

AVStream*stream=avformat_new_stream(ofmt_ctx, nullptr);
intvideo_stream=stream->index;
AVCodecParameters*codecpar=stream->codecpar;
codecpar->codec_type=AVMEDIA_TYPE_VIDEO;
codecpar->codec_id=AV_CODEC_ID_H264;
codecpar->width=width;
codecpar->height=height;      

3. 設定視訊流sps和pps

sps 和 pps 能在 MediaCodec 産生第一幀畫面之前擷取到,以 java MediaCodec異步編碼方式為例

java 接口擷取 sps 和 pps 資料

@Override
publicvoidonOutputBufferAvailable(@NonNullMediaCodeccodec, intindex, @NonNullMediaCodec.BufferInfoinfo){
ByteBufferbuffer=encoder.getOutputBuffer(index);
if ((info.flags&MediaCodec.BUFFER_FLAG_CODEC_CONFIG) !=0) {
// 傳遞 buffer和info.size到native
    }
// ...
}      

native 擷取 sps 和 pps 資料位址

uint8_t*data=static_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
// 複制給視訊流extradata
AVCodecParameters*codecpar=ofmt_ctx->streams[video_stream]->codecpar;
codecpar->extradata= (uint8_t*) av_mallocz(size+AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(codecpar->extradata, data, size);
codecpar->extradata_size=size;      

4. 寫入視訊檔案頭資訊,放在檔案開頭位置

AVDictionary*dict=nullptr;
av_dict_set(&dict, "movflags", "faststart", 0);
intret=avformat_write_header(ofmt_ctx, &dict);      

5. 寫入視訊流和音頻流已編碼資料

同樣以寫入視訊流資料為例,⚠️注意視訊流和音頻流在不同線程寫入時需要同步

onOutputBufferAvailable 回調中

booleanisKeyFrame= (info.flags&MediaCodec.BUFFER_FLAG_KEY_FRAME) !=0;
// 傳遞buffer, info.size, isKeyFrame, info.presentationTimeUs到native      

擷取視訊編碼資料位址

uint8_t *data = static_cast<uint8_t *>(env->GetDirectBufferAddress(buffer));

AVPacket *packet = av_packet_alloc();
av_init_packet(packet);
packet->stream_index = video_stream;
packet->data = data;
packet->size = size;
packet->pts = av_rescale_q(pts, { 1, 1000000 }, ofmt_ctx->streams[video_stream]->time_base);
if (isKeyFrame) packet->flags |= AV_PKT_FLAG_KEY;

int ret = av_interleaved_write_frame(ofmt_ctx, packet);
av_packet_unref(packet);
av_packet_free(&packet);      

6. 結束并關閉檔案

av_write_trailer(ofmt_ctx);
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);      

至此,整個流程就結束了。

作者:VE 視訊引擎

MediaCodec 編碼結合 FFmpeg 封裝流