FFmpeg学习(三):编码和解码

406 阅读17分钟

0. 前言

在音视频开发任务中,编码和解码也是不可或缺的一环。
由于原始的视频和音频数据数据量太大,直接传输和存储的成本高,所以需要对原始的音视频数据进行压缩,这个过程便是编码。而解码则是反向过程,把压缩后的数据还原成可以播放的数据格式,计算机能够直接处理和应用的还是完整的未压缩数据。
音频和视频数据的编码原理和方式也不相同,因此也有着不同的编码方式,常见的音频编码标准有MP3,AAC等,视频方面常见的编码标准有H264、H265等。
前文我们讨论了封装和解封装任务,操作的对象是压缩数据帧和多媒体容器文件,今天我们主要讨论使用FFmpeg进行编码和解码功能的实现,即是压缩数据帧的生成和还原方式。

1. libavcodec介绍

libavcodec(libavc)是FFmpeg中专门用于处理编码和解码任务的子库,它提供了一套通用的编码/解码框架,并且内置了大量的编码和解码器实现,可以处理音频、视频、字幕流数据。

1.2 主要数据结构

首先需要了解一下使用libavcodec时常用的几个数据结构。风格上,这些数据结构和libavformat中设计的也是比较统一,AVCodec和AVCodecContext也是采用类似的“策略模式”来组织,分别表示编解码器行为的实现,以及编解码器运行过程中的状态信息。AVCodecParameters表示的是数据流的编码信息参数。AVFrame表示的是未经压缩过的原始帧数据。

1.2.1 AVCodecContext

AVCodecContext表示编解码器运行过程中的状态信息,包含不同编解码器用到的公共信息,由于编解码过程的复杂性以及兼容不同的编解码器,这个数据结构非常厚重。笔者对于很多字段也未能完全理解,我们挑一些字段来归纳理解其主要结构。 AVCodecContext.png

1.2.2 AVCodec

AVCodec表示的是FFmpeg具体支持的一个编解码器句柄。在编译FFmpeg代码时,我们使用configure来配置支持的编解码器,或者引入三方的编解码库如x264,x265等,所以FFmpeg所支持的所有编解码器在编译时便已经确定,在内部会使用一个列表来组织维护所有支持的编解码器。AVCodec便是用来描述具体的编解码器的结构。 AVCodec.png 可以看到,AVCodec结构体中内容并不多,而且并未包含任何的方法指针,那么FFmpeg是如何用它来描述不同的编解码器行为的呢?事实上,AVCodec只是提供给用户的公开接口,而FFmpeg内部使用FFCodec结构体来描述具体的编解码器。FFCodecAVCodec可以理解成C语言的封装形式(或接口实现),FFCodec的第一个字段便是AVCodec类型,可以同一个指针表示。因此,可以通过AVCodecContext保存的AVCodec*转换到具体的FFCodec*上。

static av_always_inline const FFCodec *ffcodec(const AVCodec *codec)
{
    return (const FFCodec*)codec;
}

这里我们讨论一下AVCodec和AVCodecContext是如何关联起来的。
在使用avcodec_alloc_context3()函数创建AVCodecContext,或调用 avcodec_open2()打开编解码器时,可以传参设置一个具体的AVCodec,AVCodecContext内部使用codec字段来保存,其指向一个具体的编解码器对象FFCodec实现,如ff_h264_decoderff_aa_encoder等。
AVCodecContext的priv_data字段指向具体的编解码器对象使用到的私有数据结构,如ff_h264_decoder用到的H264ContextAVCodec关联.png

1.2.3 AVCodecParameters

AVCodecParameters顾名思义,便是编解码器的配置参数,我们对于编解码器的很多设置也是放在这个结构体里面, 可以看见它里面的参数和AVCodecContext高度重合,但需要注意的是,这个结构体是“由AVStream保存”,我们在解封装后调用avformat_find_stream_info()可以拿到流对应的编解码信息,在需要解码时,可以设置这些参数给对应的AVCodecContext

AVCodecParameters这个结构是给AVStream的成员codecpar,在编码时,调用avformat_write_header()也会根据这些参数写入header中。
解码时,可以用AVStream读到的codecpar来初始化解码器的AVCodecContext
编码时,可以从“已经设置好,且open过后的编码器”的AVCodecContext拷贝至对应的AVStreamcodecpar中。\

在转码场景下不要直接将源文件流的codecpar拷贝至输出文件流的(即使图像数据也没修改),否则会导致PPS设置错误而出现编码异常(只是单纯的转封装是可以这么操作),只要涉及到重新编码,都不要拷贝codecpar

AVCodecParameters.png

1.2.4 AVFrame

AVFrame表示的是解码之后的原始数据帧,整体的设计思路和AVPacket类似,也是引用计数结构。 AVFrame.png

2. 主要使用流程

2.1 解码

解码的流程如下:
1.首先需要查找解码所需要的解码器。一般在解封装后,使用avformat_find_stream_info()之后,AVStream中就可以获取对应的解码参数,可以通过avcodec_find_decoder()方法和对应的codecid确定解码器。
2.创建AVCodecContext,通过获取到的AVCodec来初始化解码器上下文,调用avcodec_alloc_context3()方法。
3.设置解码器参数。这里要对应的流的信息,设置解码器的解码参数,解码时如果已经获取了流的信息,可以直接调用avcodec_parameters_to_context()方法,将AStreamcodecpar的内容拷贝至AVCodecContext中。

  1. 打开解码器,avcodec_open2()
  2. 解码循环。循环调用调用avcodec_send_packet()送入读到的一帧压缩数据AVPacketavcodec_receive_frame()

两个tips
(1)并不是送入一帧Packet之后就一定能解出一帧Frame。编码器内部可能需要缓存多帧才开始解码。
(2)送入完所有的Packet之后,最后需要刷新解码器的缓存,调用avcodec_send_packet(context, NULL), 送入NULL。

  1. 释放资源,主要是关闭解码器avcodec_close(),和释放解码器上下文avcodec_free_context()

解码.png

2.2 编码

编码的流程如下:

  1. 查找对应的编码器。这里根据需要编码的设置手动传入CodecID,或CodecName进行查找。
  2. 创建AVCodecContext,通过获取到的AVCodec来初始化编码器上下文,调用avcodec_alloc_context3()方法。
  3. 设置编码器参数,这里应该根据需要手动设置。比如视频需要设置的fps,宽高,像素格式等这些数据,音频的采样率,采样点数据类型等,以及其他对于解码器的私有配置。
  4. 打开编码器,avcodec_open2()
  5. 设置流的编码参数。调用avcodec_parameters_from_context(),将打开后的编码器上下文AVCodecContext中的内容拷贝到输出流的codecpar中。
  6. 编码循环。循环调用avcodec_send_frame()送入原始数据帧,调用avcodec_receive_packet()获取压缩后的帧。
  7. 释放资源。主要是关闭编码器avcodec_close(),和释放编码器上下文avcodec_free_context()编码.png

3. 示例

3.1 编码

示例可以参考github.com/zzakafool/M… 目录:6_Encode。 这个示例会在代码中生成YUV全0的帧,并将其编码为10s的视频。

#include "encode.h"
extern "C" {
    #include <libavformat/avformat.h>
    #include <libavcodec/avcodec.h>
    #include <libavutil/opt.h>
}

const int FRAME_WIDTH = 640;
const int FRAME_HEIGHT = 320;

const int TEST_FRAME_SIZE = 30 * 10;

void setDefaultColor(AVFrame *frame) {
    // Y
    for(int y = 0; y < frame->height; ++y) {
        for(int x = 0; x < frame->width; ++x) {
            frame->data[0][y * frame->linesize[0] + x] = 0;
        }
    }

    // U & V
    for(int y = 0; y < frame->height/2; ++y) {
        for(int x = 0; x < frame->width/2; ++x) {
            frame->data[1][y * frame->linesize[1] + x] = 0;
            frame->data[2][y * frame->linesize[2] + x] = 0;
        }
    }
}

void encode(std::string dst) {
    AVFormatContext* fmtCtx = nullptr;
    int ret = avformat_alloc_output_context2(&fmtCtx, NULL, NULL, dst.c_str());
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "allocate output format context failed\n");
        return;
    }

    ret = avio_open(&fmtCtx->pb, dst.c_str(), AVIO_FLAG_WRITE);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "avio open failed\n");
        return;
    }

    AVStream* strm = avformat_new_stream(fmtCtx, nullptr);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "add stream failed\n");
        return;
    }
    strm->time_base = AVRational{1, 30};
    strm->avg_frame_rate = AVRational{30, 0};

    auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if(codec == nullptr) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find codec h264\n");
        return;
    }

    AVCodecContext* codecCtx = avcodec_alloc_context3(codec);
    if(codecCtx == nullptr) {
        av_log(NULL, AV_LOG_ERROR, "Cannot allocate context\n");
        return;
    }

    codecCtx->bit_rate = 100000;
    codecCtx->width = FRAME_WIDTH;
    codecCtx->height = FRAME_HEIGHT;
    codecCtx->time_base = AVRational{1, 30};
    codecCtx->framerate = AVRational{30, 0};
    codecCtx->gop_size = 12;
    codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    av_opt_set(codecCtx->priv_data, "preset", "slow", 0);
    av_opt_set(codecCtx->priv_data, "profile", "high", 0);
    av_opt_set(codecCtx->priv_data, "level", "5.0", 0);

    ret = avcodec_open2(codecCtx, codec, NULL);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "codec open failed\n");
        return;
    }

    ret = avcodec_parameters_from_context(strm->codecpar, codecCtx);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Copy parameters from context failed\n");
        return;
    }

    // write header
    ret = avformat_write_header(fmtCtx, NULL);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "write header failed\n");
        return;
    }

    AVFrame *frame = av_frame_alloc();
    frame->width = FRAME_WIDTH;
    frame->height= FRAME_HEIGHT;
    frame->format= AV_PIX_FMT_YUV420P;

    AVPacket *packet = av_packet_alloc();

    ret = av_frame_get_buffer(frame, 0);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "allocate frame buffer failed\n");
        return;
    }

    ret = av_frame_make_writable(frame);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "av frame make writable failed\n");
        return;
    }

    // 绿色
    setDefaultColor(frame);

    for(int i = 0;i < TEST_FRAME_SIZE; ++i) {
        frame->pts = i;
        int ret = avcodec_send_frame(codecCtx, frame);
        if(ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "send frame failed\n");
            return;
        }

        while(ret >= 0) {
            ret = avcodec_receive_packet(codecCtx, packet);
            if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if(ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "receive packet failed\n");
                return;
            }

            packet->stream_index = strm->index;
            packet->dts = av_rescale_q(packet->dts, AVRational{1, 30}, strm->time_base);
            packet->pts = av_rescale_q(packet->pts, AVRational{1, 30}, strm->time_base);
            
            ret = av_interleaved_write_frame(fmtCtx, packet);
            if(ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "write frame failed\n");
                return;
            }

            av_packet_unref(packet);
        }
    }

    ret = avcodec_send_frame(codecCtx, NULL);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "send NULL failed\n");
        return;
    }

    while(ret >= 0) {
        ret = avcodec_receive_packet(codecCtx, packet);
        if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if(ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "receive packet failed\n");
            return;
        }

        packet->stream_index = strm->index;
        packet->dts = av_rescale_q(packet->dts, AVRational{1, 30}, strm->time_base);
        packet->pts = av_rescale_q(packet->pts, AVRational{1, 30}, strm->time_base);

        ret = av_interleaved_write_frame(fmtCtx, packet);
        if(ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "write frame failed\n");
            return;
        }

        av_packet_unref(packet);
    }

    ret = av_write_trailer(fmtCtx);
    if(ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "write trailer failed\n");
        return;
    }

    avcodec_close(codecCtx);
    avio_closep(&fmtCtx->pb);
    av_frame_free(&frame);
    av_packet_free(&packet);
    avformat_free_context(fmtCtx);
}

3.2 转码

示例可以参考github.com/zzakafool/M… 目录:7_Transcode。
这个示例简单地将封装和编码这些初始化操作代码封装了一下,避免维护太多的释放上下文的逻辑。
这里由于只在一个线程做转封装处理,所以解封装、解码、编码、封装,都是嵌套的逻辑,有点复杂。这里仅作为示例说明使用,一般把单独的子任务放在单独的线程中,代码会更加清晰。

#pragma once
#include <string>
#include <map>
#include <memory>
#include <functional>

template <typename T>
using sp = std::shared_ptr<T>;

extern "C" {
    #include "libavformat/avformat.h"
    #include "libavcodec/avcodec.h"
}

struct FormatItem {
    std::string url;                            // 输入/输出url
    AVFormatContext *fmtCtx = nullptr;          // 输入/输出fmtCtx
    std::map<int, AVCodecContext*> codecMap;    // 编码/解码codecMap,streamid -> codecContext
    
    static sp<FormatItem> openInputFormat(const std::string& url);
    static sp<FormatItem> openOutputFormat(const std::string& url);

    struct CodecSetting {
        // 一般解码器会用到从Stream读到的codecParams
        AVCodecParameters *codecParams = nullptr;

        // 编码器的信息最好手动传入
        enum AVCodecID codecID;
        enum AVMediaType codec_type;
        // video
        int width = 0;
        int height = 0;
        AVRational framerate = {30, 1};
        // audio 
        AVChannelLayout ch_layout;
        int sample_rate = 0;
        // common
        int format = 0;
        AVRational time_base = {1, 30};
        
    };
    bool openCodec(int streamid, const CodecSetting &codecSetting);

    virtual ~FormatItem();
};

#include "formatItem.h"

extern "C" {
    #include "libavutil/opt.h"
}

sp<FormatItem> FormatItem::openInputFormat(const std::string& url) {
    sp<FormatItem> formatItem = std::make_shared<FormatItem>();
    formatItem->url = url;

    // 初始化解封装相关的组件
    //   创建AVFormatContext和AVInputFormat
    if(avformat_open_input(&formatItem->fmtCtx, url.c_str(), NULL, NULL) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Open src file failed.\n");
        return nullptr;
    }

    // 读取输入多媒体文件的相关信息
    if(avformat_find_stream_info(formatItem->fmtCtx, NULL) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Find stream info failed\n");
        return nullptr;
    }

    return formatItem;
}

sp<FormatItem> FormatItem::openOutputFormat(const std::string& url) {
    sp<FormatItem> formatItem = std::make_shared<FormatItem>();
    formatItem->url = url;

    // 初始化封装相关的组件
    //   初始化AVFormatContext,同步文件名确定AVOutputFormat
    if(avformat_alloc_output_context2(&formatItem->fmtCtx, NULL, NULL, url.c_str()) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Allocate output context failed.\n");
        return nullptr;
    }

    // 打开输出的文件
    if(!(formatItem->fmtCtx->oformat->flags & AVFMT_NOFILE)) {
        auto ret = avio_open(&formatItem->fmtCtx->pb, url.c_str(), AVIO_FLAG_WRITE);
        if(ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "IO Open failed.\n");
            return nullptr;
        }
    }

    return formatItem;
}

bool FormatItem::openCodec(int streamid, const CodecSetting &codecSetting) {
    const AVCodec *codec = nullptr;
    // 根据输入还是输出,寻找对应的编解码器AVCodec*
    if(fmtCtx->iformat) {
        codec = avcodec_find_decoder(codecSetting.codecID);
    } else if(fmtCtx->oformat) {
        codec = avcodec_find_encoder(codecSetting.codecID);
    }

    if(codec == nullptr) {
        av_log(NULL, AV_LOG_ERROR, "Open Codec failed.\n");
        return false;
    }

    // 创建AVCodecContext, 加入到codecMap中
    AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
    if(codecCtx == nullptr) {
        av_log(NULL, AV_LOG_ERROR, "Allocate Codec ctx failed.\n");
        return false;
    }
    codecMap.insert({streamid, codecCtx});

    if(codecSetting.codecParams) {
        if(avcodec_parameters_to_context(codecCtx, codecSetting.codecParams) < 0) {
            av_log(NULL, AV_LOG_ERROR, "copy parameters to context failed\n");
            return false;
        }
    }

    codecCtx->codec_type = codecSetting.codec_type;
    if(codecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
        codecCtx->pix_fmt = (AVPixelFormat)codecSetting.format;
        codecCtx->width = codecSetting.width;
        codecCtx->height = codecSetting.height;
    } else if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO){
        codecCtx->sample_fmt = (AVSampleFormat)codecSetting.format;
        codecCtx->ch_layout = codecSetting.ch_layout;
        codecCtx->sample_rate = codecSetting.sample_rate;
    }
    
    codecCtx->time_base = codecSetting.time_base;
    codecCtx->framerate = codecSetting.framerate;
    
    if(fmtCtx->oformat && codecCtx->codec->id == AV_CODEC_ID_H264) {
        // codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
        // codecCtx->mb_decision = 1;
        codecCtx->bit_rate = 1000000;
        av_opt_set(codecCtx->priv_data, "preset", "slow", 0);
        av_opt_set(codecCtx->priv_data, "profile", "high", 0);
        av_opt_set(codecCtx->priv_data, "level", "5.0", 0);
    }

    if(avcodec_open2(codecCtx, codec, NULL) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Open Codec Failed.\n");
        return false;
    }
    
    return true;
}

FormatItem::~FormatItem()
{
    for (auto pair : codecMap) {
        avcodec_close(pair.second);
        avcodec_free_context(&pair.second);
    }

    if (fmtCtx && fmtCtx->iformat) {
        avformat_close_input(&fmtCtx);
        fmtCtx = nullptr;
    }
    if (fmtCtx && fmtCtx->oformat) {
        if (fmtCtx->pb) {
            avio_closep(&fmtCtx->pb);
        }
        avformat_free_context(fmtCtx);
        fmtCtx = nullptr;
    }
    if (fmtCtx) {
        avformat_free_context(fmtCtx);
    }

}

转码的主逻辑:

#include "transcode.h"
#include "formatItem.h"

// 输入是原始文件待解码的流
FormatItem::CodecSetting getDecodeSetting(AVStream *stream) {
    FormatItem::CodecSetting decodeSetting;
    decodeSetting.codecParams = stream->codecpar;
    decodeSetting.codecID = stream->codecpar->codec_id;
    decodeSetting.codec_type = stream->codecpar->codec_type;
    decodeSetting.width = stream->codecpar->width;
    decodeSetting.height = stream->codecpar->height;
    decodeSetting.framerate = stream->avg_frame_rate;
    decodeSetting.ch_layout = stream->codecpar->ch_layout;
    decodeSetting.sample_rate = stream->codecpar->sample_rate;
    decodeSetting.format = stream->codecpar->format;
    decodeSetting.time_base = stream->time_base;

    return decodeSetting;
}

// 输入是原始文件待解码的流,编码参数将用于新的转码流
FormatItem::CodecSetting getEncodeSetting(AVStream *oldStream) {
    // 只用于转码,拷贝部分参数
    FormatItem::CodecSetting encodeSetting;
    encodeSetting.codecParams = nullptr;
    encodeSetting.codecID = oldStream->codecpar->codec_id;
    encodeSetting.codec_type = oldStream->codecpar->codec_type;
    encodeSetting.width = oldStream->codecpar->width;
    encodeSetting.height = oldStream->codecpar->height;
    encodeSetting.framerate = oldStream->avg_frame_rate;
    encodeSetting.ch_layout = oldStream->codecpar->ch_layout;
    encodeSetting.sample_rate = oldStream->codecpar->sample_rate;
    encodeSetting.format = oldStream->codecpar->format;
    encodeSetting.time_base = oldStream->time_base;

    return encodeSetting;
}

// 模拟解码后的图像处理方法,处理解码好的帧,处理完后准备重新编码
void handleVideoFrame(AVFrame* inFrame, AVFrame *outFrame) {
    if(inFrame->format != AV_PIX_FMT_YUV420P) {
        return;
    }

    // 模拟后处理解码后的图片的过程,拷贝一份帧
    outFrame->width = inFrame->width;
    outFrame->height = inFrame->height;
    outFrame->format = inFrame->format;
    av_frame_get_buffer(outFrame, 0);
    av_frame_copy(outFrame, inFrame);
    outFrame->pts = inFrame->pts;

    //// 这里先注释掉,如果取消注释,就是写入全0,视频绿色
    // av_frame_make_writable(outFrame);
    //
    // // Y
    // for(int y = 0; y < outFrame->height; ++y) {
    //     for(int x = 0; x < outFrame->width; ++x) {
    //         outFrame->data[0][y * outFrame->linesize[0] + x] = 0;
    //     }
    // }

    // // U & V
    // for(int y = 0; y < outFrame->height/2; ++y) {
    //     for(int x = 0; x < outFrame->width/2; ++x) {
    //         outFrame->data[1][y * outFrame->linesize[1] + x] = 0;
    //         outFrame->data[2][y * outFrame->linesize[2] + x] = 0;
    //     }
    // }

}

void transcode(std::string src, std::string dst) {
    sp<FormatItem> inItem = FormatItem::openInputFormat(src);
    sp<FormatItem> outItem = FormatItem::openOutputFormat(dst);

    if(inItem == nullptr || outItem == nullptr) {
        av_log(NULL, AV_LOG_ERROR, "open input or output item failed.\n");
        return;
    }

    // 从原文件的流信息,创建新的输出流
    // 这里用一个map来记录 输入流id 和 输出流id 的对应关系
    std::map<int, int> streamIdxMap;
    for(int i = 0; i < inItem->fmtCtx->nb_streams; ++i) {
        if(inItem->fmtCtx->streams[i]->codecpar->codec_id == AV_CODEC_ID_NONE) {
            continue;
        }

        AVStream * strm = avformat_new_stream(outItem->fmtCtx, NULL);
        strm->id = outItem->fmtCtx->nb_streams - 1;
        strm->index = outItem->fmtCtx->nb_streams - 1;
        streamIdxMap[i] = strm->index;
        strm->time_base = inItem->fmtCtx->streams[i]->time_base;

        // 打开解码器
        FormatItem::CodecSetting decodeSetting = getDecodeSetting(inItem->fmtCtx->streams[i]);
        inItem->openCodec(i, decodeSetting);

        // 打开编码器
        FormatItem::CodecSetting encodeSetting = getEncodeSetting(inItem->fmtCtx->streams[i]);
        outItem->openCodec(strm->index, encodeSetting);
        
        avcodec_parameters_from_context(strm->codecpar, outItem->codecMap[strm->index]);
        av_log(NULL, AV_LOG_INFO, "src timebase : %d / %d\n", strm->time_base.num, strm->time_base.den);
    }

    AVPacket *inPacket = av_packet_alloc();
    AVFrame *inFrame = av_frame_alloc();
    AVPacket *outPacket = av_packet_alloc();
    AVFrame *outFrame = av_frame_alloc();

    // 写入头部信息
    if(avformat_write_header(outItem->fmtCtx, NULL) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Write header failed.\n");
        return;
    }

    // 注意,写完header之后,stream的timebase可能会发生改变。
    for(int i = 0; i < outItem->fmtCtx->nb_streams; ++i) {
        auto &strm = outItem->fmtCtx->streams[i];
        av_log(NULL, AV_LOG_INFO, "new timebase : %d / %d\n", strm->time_base.num, strm->time_base.den);
    }

    // 逐帧写入
    while(av_read_frame(inItem->fmtCtx, inPacket) >= 0) {
        if(streamIdxMap.find(inPacket->stream_index) == streamIdxMap.end()) {
            av_packet_unref(inPacket);
            continue;
        }
        if(inItem->codecMap.find(inPacket->stream_index) == inItem->codecMap.end()) {
            av_log(NULL, AV_LOG_ERROR, "Cannt find stream codec\n");
            av_packet_unref(inPacket);
            continue;
        }

        int inStreamId = inPacket->stream_index;
        int outStreamId = streamIdxMap[inPacket->stream_index];
        auto &oldStream = inItem->fmtCtx->streams[inStreamId];
        auto &newStream = outItem->fmtCtx->streams[outStreamId];

        int err = avcodec_send_packet(inItem->codecMap[inPacket->stream_index], inPacket);
        if(err < 0) {
            // 因为尽量消耗解码输出,所以应该不会有EAGAIN,所以这种情况应该无法恢复
            av_log(NULL, AV_LOG_ERROR, "Error when decode send packet.\n");
            return;
        }

        // 送进解码器后可以释放packet了
        av_packet_unref(inPacket);

        // while尽量消耗解码的输出
        while(err >= 0) {
            err = avcodec_receive_frame(inItem->codecMap[inStreamId], inFrame);
            if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
                break;
            } else if(err < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error when decode receive frame \n");
                // 无法恢复的错误
                return;
            }

            // 模拟解码完后的处理,这里音频直接拷贝不处理,视频过handleVideoFrame处理
            if(oldStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                av_frame_move_ref(outFrame, inFrame);
            } else {    // VIDEO
                handleVideoFrame(inFrame, outFrame);
            }
            av_frame_unref(inFrame);

            outFrame->pict_type = AV_PICTURE_TYPE_NONE;
            outFrame->pts = av_rescale_q(outFrame->pts, oldStream->time_base, newStream->time_base);
            outFrame->time_base = newStream->time_base;

            err = avcodec_send_frame(outItem->codecMap[outStreamId], outFrame);
            if(err < 0) {
                // 因为尽量消耗编码输出,所以应该不会有EAGAIN,这种错误情况应该无法恢复
                av_log(NULL, AV_LOG_ERROR, "Error when encode send frame %d \n", err);
                return;
            }
            // 送入编码器后释放掉frame
            av_frame_unref(outFrame);

            // while尽量消耗编码输出
            while(err >= 0) {
                err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
                if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
                    break;
                } else if(err < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error when encode receive packet \n");
                    return;
                }

                outPacket->stream_index = outStreamId;

                // 交叉写入音频和视频帧
                if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
                }
                av_packet_unref(outPacket);
            }
        }
    }

    // 最后需要对所有解码器送NULL,解码完所有的帧
    for(auto &p : inItem->codecMap) {
        auto inStreamId = p.first;
        auto codecCtx = p.second;
        int outStreamId = streamIdxMap[inStreamId];
        auto &oldStream = inItem->fmtCtx->streams[inStreamId];
        auto &newStream = outItem->fmtCtx->streams[outStreamId];

        int err = avcodec_send_packet(codecCtx, NULL);
        if(err < 0) {
            // 因为尽量消耗解码输出,所以应该不会有EAGAIN,所以这种情况应该无法恢复
            av_log(NULL, AV_LOG_ERROR, "Error when decode send NULL.\n");
            return;
        }

        // while尽量消耗解码的输出
        while(err >= 0) {
            err = avcodec_receive_frame(inItem->codecMap[inStreamId], inFrame);
            if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
                break;
            } else if(err < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error when decode receive frame \n");
                // 无法恢复的错误
                return;
            }

            // 模拟解码完后的处理,这里音频直接拷贝不处理,视频过handleVideoFrame处理
            if(oldStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                av_frame_move_ref(outFrame, inFrame);
            } else {    // VIDEO
                handleVideoFrame(inFrame, outFrame);
            }
            av_frame_unref(inFrame);

            outFrame->pict_type = AV_PICTURE_TYPE_NONE;
            outFrame->pts = av_rescale_q(outFrame->pts, oldStream->time_base, newStream->time_base);
            outFrame->time_base = newStream->time_base;

            err = avcodec_send_frame(outItem->codecMap[outStreamId], outFrame);
            if(err < 0) {
                // 因为尽量消耗编码输出,所以应该不会有EAGAIN,这种错误情况应该无法恢复
                av_log(NULL, AV_LOG_ERROR, "Error when encode send frame 2 \n");
                return;
            }
            // 送入编码器后释放掉frame
            av_frame_unref(outFrame);

            // while尽量消耗编码输出
            while(err >= 0) {
                err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
                if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
                    break;
                } else if(err < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error when encode receive packet 2 \n");
                    return;
                }

                outPacket->stream_index = outStreamId;

                // 交叉写入音频和视频帧
                if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
                }
                av_packet_unref(outPacket);
            }
        }
    }

    // 对所有编码器送NULL
    for(auto &p : outItem->codecMap) {
        int outStreamId = p.first;
        auto inStreamId = -1;
        for(auto &p: streamIdxMap) {
            if(p.second == outStreamId) {
                inStreamId = p.first;
            }
        }
        assert(inStreamId != -1);

        auto &oldStream = inItem->fmtCtx->streams[inStreamId];
        auto &newStream = outItem->fmtCtx->streams[outStreamId];

        int err = avcodec_send_frame(outItem->codecMap[outStreamId], NULL);
        if(err < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error when encode send NULL \n");
            return;
        }

        // while尽量消耗编码输出
        while(err >= 0) {
            err = avcodec_receive_packet(outItem->codecMap[outStreamId], outPacket);
            if(err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
                break;
            } else if(err < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error when encode send frame \n");
                return;
            }

            outPacket->stream_index = outStreamId;

            // 交叉写入音频和视频帧
            if(av_interleaved_write_frame(outItem->fmtCtx, outPacket) < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error during write packet\n");
            }
            av_packet_unref(outPacket);
        }
    }

    // 写入尾部数据
    if(av_write_trailer(outItem->fmtCtx) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Write trailer failed\n");
        return;
    }

    av_packet_free(&inPacket);
    av_packet_free(&outPacket);
    av_frame_free(&inFrame);
    av_frame_free(&outFrame);
}

4. 参考资料

  1. 《深入理解FFmpeg》 :刘歧等
  2. 《基于新版FFmpeg(FFmpeg 6.1)的音视频转码》