V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zoule
V2EX  ›  FFmpeg

MediaCodec 编码结合 FFmpeg 封装流

  •  
  •   zoule · 2021-03-12 14:27:26 +08:00 · 1369 次点击
    这是一个创建于 1387 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 Android 平台上合成视频一般使用 MediaCodec 进行硬编码,使用 MediaMuxer 进行封装,但是因为 MediaMuxer 在某些机型上合成的视频在其他手机上播放会出现问题,而且只支持一个音频轨道,因此可以选用 FFmpeg 来封装编码后的音视频流。  

    创建 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);
    

     

    添加音视频流

    这里以添加 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;
    

     

    设置视频流 sps 和 pps

    sps 和 pps 能在 MediaCodec 产生第一帧画面之前获取到,以 java MediaCodec 异步编码方式为例

    @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;
    

     

    写入视频文件头信息,放在文件开头位置

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

     

    写入视频流和音频流已编码数据

    同样以写入视频流数据为例,⚠️注意视频流和音频流在不同线程写入时需要同步

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

     

    结束并关闭文件

    至此,整个流程就结束了

    av_write_trailer(ofmt_ctx);
    avio_closep(&ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    
    1 条回复    2021-03-12 14:46:48 +08:00
    fgodt
        1
    fgodt  
       2021-03-12 14:46:48 +08:00
    其实 ff 支持 MediaCodec 的,使用内置的 MediaCodec 更方便。
    AVCodec *codec = avcodec_find_decoder_by_name("xx_mediacodec")
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2709 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 14:38 · PVG 22:38 · LAX 06:38 · JFK 09:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.