1_流程图

Encode

Decode

2_封装

2.1_结构体

  • 封装上下文 AVFormatContext
  • 视频流,音频流等 AVStream
  • 编码后的单元数据 AVPacket

2.2_流程

  • 解封装
    • 首先使用 avformat_open_input 函数打开输入文件并生成对应的上下文 AVFormatContext
    • 通过 av_find_best_stream 函数寻找需要的流的索引,并通过 AVFormatContext->streams[index] 获得需要的流 AVStream,也可以通过循环遍历访问所有的流,流的数量可以用 AVFormatContext->nb_streams 获取
    • 通过 av_read_frame 函数可以得到编码后的单元数据 AVPacket,根据 AVPacket.stream_index 可以判断该单元数据对应的流
  • 封装
    • 使用 avformat_alloc_output_context2 函数创建输入文件对应的上下文 AVFormatContext
    • 使用 avformat_new_stream 函数创建绑定到输出上下文的流AVStream,并且通过诸如 avcodec_parameters_copy 的方法设置流的编码参数 AVStream->codecpar
    • 使用 avio_open 方法打开输出文件准备写入
    • avformat_write_header 写入文件头
    • 使用 av_rescale_q_rnd 等方法,根据不同流的时间基(timebase)调整 AVPacket.pts AVPacket.dts AVPacket.duration
    • 使用 av_interleaved_write_frame 将调整后的单元数据 AVPacket 数据写入文件
    • av_write_trailer 写入文件尾

3_编解码

3.1_结构体

  • 编解码上下文 AVCodecContext
  • 编解码器 AVCodec
  • 编码后的单元数据 AVPacket
  • 编码前的原始数据 AVFrame

3.2_流程

3.2_流程

  • 解码
    • 使用 avcodec_find_decoder 函数根据 AVStream->codecpar->codecid 找到合适的解码器 AVCodec
    • 使用 avcodec_alloc_context3 函数创建解码上下文 AVCodecContext
    • 使用 avcodec_parameters_to_context 函数将 `AVStream->codecpar 中的参数拷贝到解码上下文
    • 使用 avcodec_open2 函数打开解码器
    • 使用 avcodec_decode_video2/avcodec_send_packet/avcodec_receive_frame 函数将 AVPacket 解码成 AVFrame
  • 编码
    • 使用 avcodec_find_encoder 或者 avcodec_find_encoder_by_name 创建合适的编码器 AVCodec
    • 使用 avcodec_alloc_context3 函数创建编码上下文 AVCodecContext
    • 手动设置上下文的参数,部分可以从解码上下文中拷贝
    • 使用 avcodec_open2 函数打开编码器
    • 使用 avcodec_encode_video2/avcodec_send_frame/avcodec_receive_packet 函数将 AVFrame 编码成 AVPacket

4_修改原始数据

使用 avcodec_decode_video2 获得的原始数据 AVFrame 往往不能直接编辑。可以通过 av_frame_clone 等方法创建一个新的 AVFrame ,然后使用方法 av_frame_make_writable 让其变得可以修改,其中原始数据就存在 AVFrame->data 中,对于 YUV420P 类型的帧,data[i][j] 中的 i = 0,1,2 分别对应 YUV 三个分量,j 则代表具体第几个,可以用 j = y*new_frame->linesize[i] + x 来表示 (x,y) 坐标的点。

5_代码

#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavutil/timestamp.h>

#define INPUT_NAME "../temp/nergigante.mp4"
#define OUTPUT_NAME "../temp/complete3.mkv"
#define ENCODER_NAME "h264_videotoolbox"

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

int main(int argc, char** argv) {
    // ---------------------
    // 0. 定义及初始化 ffmepg
    char* input_file, *output_file;
    AVFormatContext *ifmt_ctx, *ofmt_ctx;
    int video_index;

    AVCodec *encoder, *decoder;
    AVCodecContext *dec_ctx, *enc_ctx;
    AVPacket in_pkt, out_pkt;
    AVFrame *ori_frame, *new_frame;
    int ret;

#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
    av_register_all();
#endif

    av_log_set_level(AV_LOG_INFO);

//    if (argc != 3) {
//        av_log(NULL, AV_LOG_ERROR, "Usage: %s <input> <output>\n", argv[0]);
//        return 1;
//    }
//    input_file = argv[1];
//    output_file = argv[2];
    input_file = INPUT_NAME;
    output_file = OUTPUT_NAME;

    // ================================================
    // 1. 输入格式上下文
    // ================================================
    // 1.1 打开输入文件并初始化输入格式上下文
    ifmt_ctx = NULL;  // important
    if ((ret = avformat_open_input(&ifmt_ctx, input_file, NULL, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open input file '%s'\n", input_file);
        goto __END;
    }

    // 1.2 查看是否包含流信息
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
        goto __END;
    }

    // 1.3 打印输入流信息
    av_dump_format(ifmt_ctx, 0, input_file, 0);

    // ================================================
    // 2. 输出格式上下文
    // ================================================
    // 2.1 初始化输出格式上下文
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output_file);
    if (!ofmt_ctx) {
        av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto __END;
    }

    // 2.2 针对输入流逐一创建输出流并绑定到ofmt_ctx
    for (unsigned int i = 0; i < ifmt_ctx->nb_streams; ++i) {
        AVStream *out_stream;
        AVStream *in_stream = ifmt_ctx->streams[i];

        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto __END;
        }

        if ((ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar)) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Failed to copy codec parameters\n");
            goto __END;
        }
        out_stream->time_base = in_stream->time_base;
        out_stream->codecpar->codec_tag = 0;
    }

    // 2.3 打开输出文件
    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, output_file, AVIO_FLAG_WRITE);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", output_file);
            goto __END;
        }
    }

    // ================================================
    // 3. 解码器
    // ================================================
    // 3.1 初始化解码器
    video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, -1);
    decoder = avcodec_find_decoder(ifmt_ctx->streams[video_index]->codecpar->codec_id);
    if (!decoder) {
        av_log(NULL, AV_LOG_ERROR, "Failed to find %s codec\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        ret = AVERROR_UNKNOWN;
        goto __END;
    }

    // 3.2 初始化解码上下文
    dec_ctx = avcodec_alloc_context3(decoder);
    if (!dec_ctx) {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
        ret = AVERROR_UNKNOWN;
        goto __END;
    }

    if ((ret = avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[video_index]->codecpar)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to copy %s codec parameters to decoder context\n",
               av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        goto __END;
    }

    dec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, ifmt_ctx->streams[video_index], NULL);

    // 3.3 打开解码器
    if ((ret = avcodec_open2(dec_ctx, decoder, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to open decoder\n");
        goto __END;
    }

    // ================================================
    // 4. 编码器
    // ================================================
    // 4.1 初始化编码器
    encoder = avcodec_find_encoder_by_name(ENCODER_NAME);
    if (!encoder) {
        av_log(NULL, AV_LOG_ERROR, "Codec %s not found\n", ENCODER_NAME);
        ret = AVERROR_UNKNOWN;
        goto __END;
    }

    // 4.2 初始化编码上下文
    enc_ctx = avcodec_alloc_context3(encoder);
    if (!enc_ctx) {
        av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
        ret = AVERROR_UNKNOWN;
        goto __END;
    }

    // 4.3 设置编码上下文参数
    enc_ctx->bit_rate = dec_ctx->bit_rate;
    enc_ctx->width = dec_ctx->width;
    enc_ctx->height = dec_ctx->height;
    enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    enc_ctx->time_base = dec_ctx->time_base;
    enc_ctx->framerate = dec_ctx->framerate;

    // important
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // 4.4 打开编码器
    if ((ret = avcodec_open2(enc_ctx, encoder, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to open encode codec.");
        goto __END;
    }

    // 4.5 将编码器参数拷贝到输出流
    if ((ret = avcodec_parameters_from_context(ofmt_ctx->streams[video_index]->codecpar, enc_ctx)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream.\n");
        return ret;
    }
    ofmt_ctx->streams[video_index]->time_base = enc_ctx->time_base;

    // 4.6 打印输出流信息
    av_dump_format(ofmt_ctx, 0, output_file, 1);

    // ================================================
    // 5. 正式开始编解码
    // ================================================
    // 5.1 写入文件头
    if ((ret = avformat_write_header(ofmt_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
        goto __END;
    }

    // 5.2 处理每个AVPacket
    while (1) {
        in_pkt.data = NULL;
        in_pkt.size = 0;
        av_init_packet(&in_pkt);
        if (av_read_frame(ifmt_ctx, &in_pkt) < 0) {
            break;
        }
        // 判断是否是需要处理的视频流
        if (in_pkt.stream_index == video_index) {
            // 开始解码
            av_packet_rescale_ts(&in_pkt, ifmt_ctx->streams[video_index]->time_base, dec_ctx->time_base);
            if ((ret = avcodec_send_packet(dec_ctx, &in_pkt)) < 0) {
                av_log(NULL, AV_LOG_ERROR, "Failed to decode packet\n");
                goto __END;
            }
            while (1) {
                ori_frame = av_frame_alloc();
                if (!ori_frame) {
                    ret = AVERROR(ENOMEM);
                    goto __END;
                }
                ret = avcodec_receive_frame(dec_ctx, ori_frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    av_frame_free(&ori_frame);
                    av_log(NULL, AV_LOG_ERROR, "Failed to receive frame\n");
                    goto __END;
                }
                ori_frame->pts = ori_frame->best_effort_timestamp;  // important

                // TODO: 编辑 frame 内容
                new_frame = av_frame_clone(ori_frame);
                av_frame_free(&ori_frame);

                // 编码frame
                if ((ret = avcodec_send_frame(enc_ctx, new_frame)) < 0) {
                    av_frame_free(&new_frame);
                    av_log(NULL, AV_LOG_ERROR, "Failed to send frame\n");
                    return ret;
                }

                // 获取packet,并写入文件
                while (1) {
                    out_pkt.data = NULL;
                    out_pkt.size = 0;
                    av_init_packet(&out_pkt);

                    ret = avcodec_receive_packet(enc_ctx, &out_pkt);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                        break;
                    } else if (ret < 0) {
                        av_frame_free(&new_frame);
                        av_log(NULL, AV_LOG_ERROR, "Failed to receive packet\n");
                        return ret;
                    }

                    av_packet_rescale_ts(&out_pkt, enc_ctx->time_base, ofmt_ctx->streams[out_pkt.stream_index]->time_base);
                    log_packet(ofmt_ctx, &out_pkt, "encode");
                    if ((ret = av_interleaved_write_frame(ofmt_ctx, &out_pkt)) < 0) {
                        av_log(NULL, AV_LOG_ERROR, "Failed to write packet\n");
                        return ret;
                    }
                }
                av_frame_free(&new_frame);
            }
        } else {
            av_packet_rescale_ts(&in_pkt, ifmt_ctx->streams[in_pkt.stream_index]->time_base,
                    ofmt_ctx->streams[in_pkt.stream_index]->time_base);
            if ((ret = av_interleaved_write_frame(ofmt_ctx, &in_pkt)) < 0) {
                av_log(NULL, AV_LOG_ERROR, "Failed to write in_pkt\n");
                goto __END;
            }
        }
        av_packet_unref(&in_pkt);
    }

    // 5.3 排空编码器缓存
    if ((ret = avcodec_send_frame(enc_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Failed to clear encoder cache.\n");
        return ret;
    }
    while (1) {
        out_pkt.data = NULL;
        out_pkt.size = 0;
        av_init_packet(&out_pkt);

        ret = avcodec_receive_packet(enc_ctx, &out_pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Failed to receive packet\n");
            return ret;
        }

        av_packet_rescale_ts(&out_pkt, enc_ctx->time_base, ofmt_ctx->streams[out_pkt.stream_index]->time_base);
        log_packet(ofmt_ctx, &out_pkt, "encode");
        if ((ret = av_interleaved_write_frame(ofmt_ctx, &out_pkt)) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Failed to write packet\n");
            return ret;
        }
    }

    // 5.4 写入文件尾
    av_write_trailer(ofmt_ctx);
    ret = 0;

__END:
    // 其他结构体
    av_packet_unref(&in_pkt);
    av_packet_unref(&out_pkt);
    if (ori_frame) {
        av_frame_free(&ori_frame);
    }
    if (new_frame) {
        av_frame_free(&new_frame);
    }

    // 编解码上下文
    if (dec_ctx) {
        avcodec_free_context(&dec_ctx);
    }
    if (enc_ctx) {
        avcodec_free_context(&enc_ctx);
    }

    // 输入输出格式上下文
    if (ofmt_ctx) {
        if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
            avio_closep(&ofmt_ctx->pb);
        }
        avformat_free_context(ofmt_ctx);
    }
    if (ifmt_ctx) {
        avformat_close_input(&ifmt_ctx);
    }

    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Error occurred: %s\n", av_err2str(ret));
    }
    return ret;
}

本文由 sandtears 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

只有地板了

  1. x
    x

    全网就这一个能用的,其实震惊了。

添加新评论