ffmpeg实现两个文件的音频、视频流抽取与合成(小咖秀)

整体思路简要分析
1.初始化准备工作
分别打开文件 探测信息给AVstream等一些空白参数赋值
avformat_open_input
avformat_find_stream_info

创建输出上下文
根据文件名分配合适的输出文件AVFormatContext 管理结构
avformat_alloc_output_context2

寻找第一个视频中最好的音频流和第二个文件中最好的视频流,并返回流的索引
av_find_best_stream

2.准备输出内容

创建音频输出流(视频同理)
avformat_new_stream

//拷贝流参数
avcodec_parameters_copy

判断最大时长

打开输出文件
avio_open

写头信息
avformat_write_header

3.循环读取包并写入
while (av_read_frame(ifmt_ctx1, &pkt) >= 0)

转换时间基
av_rescale_q_rnd

av_rescale_q

将包写入文件
av_interleaved_write_frame

写尾信息
av_write_trailer(ofmt_ctx);

代码有详细注释

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

#define ERROR_STR_SIZE 1024

int main(int argc, char *argv[])
{
    int ret = -1;

    int err_code;
    char errors[ERROR_STR_SIZE];

    char *src_file1, *src_file2, *out_file;

    AVFormatContext *ifmt_ctx1 = NULL;
    AVFormatContext *ifmt_ctx2 = NULL;

    AVFormatContext *ofmt_ctx = NULL;
    AVOutputFormat *ofmt = NULL;

    AVStream *in_stream1 = NULL;
    AVStream *in_stream2 = NULL;

    AVStream *out_stream1 = NULL;
    AVStream *out_stream2 = NULL;

    int64_t cur_pts1=0, cur_pts2=0;

    int b_use_video_ts = 1;
    uint32_t packets = 0;
    AVPacket pkt;

    int stream1 = 0, stream2 = 0;

    av_log_set_level(AV_LOG_DEBUG);

    if(argc < 4){
        av_log(NULL, AV_LOG_ERROR, "Usage: \n " \
                            "Command src_file1 src_file2 out_file \n");
        return ret;
    }

    src_file1 = argv[1];
    src_file2 = argv[2];

    out_file = argv[3];

    //register avformat, codec
    av_register_all();

    //open first file
    //初始化AVformatcontext
    if((err_code = avformat_open_input(&ifmt_ctx1, src_file1, 0, 0)) < 0 ){
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Could not open src file, %s, %d(%s)\n",
               src_file1, err_code, errors);
        goto __FAIL;
    }

    if((err_code = avformat_find_stream_info(ifmt_ctx1, 0)) <0){
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Failed to retrieve input stream info, %s, %d(%s) \n",
               src_file1, err_code, errors);
        goto __FAIL;
    }

    av_dump_format(ifmt_ctx1, 0, src_file1, 0);

    //open second file
    if((err_code = avformat_open_input(&ifmt_ctx2, src_file2, 0, 0)) < 0 ){
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Could not open the second src file, %s, %d(%s)\n",
               src_file2, err_code, errors);
        goto __FAIL;
    }
//探测视频的一些参数,给AVstream等一些空白参数赋值
    if((err_code = avformat_find_stream_info(ifmt_ctx2, 0)) <0){
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Failed to retrieve input stream info, %s, %d(%s) \n",
               src_file2, err_code, errors);
        goto __FAIL;
    }

    av_dump_format(ifmt_ctx2, 0, src_file2, 0);

    //create out context
    //根据文件名分配合适的输出文件AVFormatContext 管理结构
    if((err_code = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_file)) < 0 ){
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Failed to create an context of outfile , %d(%s) \n",
               err_code, errors);
    }

    ofmt = ofmt_ctx->oformat;

    // 找到第一个参数里最好的音频流和第二个文件中的视频流下标
    int audio_stream_index = 0;
    int vedio_stream_indes = 0;
    audio_stream_index = av_find_best_stream(ifmt_ctx1, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    vedio_stream_indes = av_find_best_stream(ifmt_ctx2, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

//
    in_stream1 = ifmt_ctx1->streams[audio_stream_index];
    stream1 = 0;

    AVCodecParameters *in_codecpar = in_stream1->codecpar;
    if(in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
       in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
       in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE){
        av_log(NULL, AV_LOG_ERROR, "The Codec type is invalid!\n");
        goto __FAIL;
    }
// 创建音频输出流
    out_stream1 = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream1) {
        av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
        goto __FAIL;
    }
// 拷贝流参数
    if ((err_code = avcodec_parameters_copy(out_stream1->codecpar, in_stream1->codecpar)) < 0) {
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Failed to copy codec parameter, %d(%s)\n",
               err_code, errors);
    }

    out_stream1->codecpar->codec_tag = 0;

// 获取第二个文件中的视频流
    in_stream2 = ifmt_ctx2->streams[vedio_stream_indes];
    stream2 = 1;

    in_codecpar = in_stream2->codecpar;

    if(in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
       in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
       in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE){
        av_log(NULL, AV_LOG_ERROR, "The Codec type is invalid!\n");
        goto __FAIL;
    }
// 创建视频输出流
    out_stream2 = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream2) {
        av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
        goto __FAIL;
    }

// 拷贝流参数
    if ((err_code = avcodec_parameters_copy(out_stream2->codecpar, in_stream2->codecpar)) < 0) {
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Failed to copy codec parameter, %d(%s)\n",
               err_code, errors);
        goto __FAIL;
    }

    out_stream2->codecpar->codec_tag = 0;
    av_dump_format(ofmt_ctx, 0, out_file, 1);


// 文件最大时长,保证音频和视频数据长度一致
    double max_duration = 0;
// 判断两个流的长度,确定最终文件的长度
//AVstream.duration get video time
//duration * time_base= real_time
//av_q2d Convert an AVRational to a double.
    if (in_stream1->duration * av_q2d(in_stream1->time_base) > in_stream2->duration * av_q2d(in_stream2->time_base))
    {
        max_duration = in_stream2->duration * av_q2d(in_stream2->time_base);
    }
    else
    {
        max_duration = in_stream1->duration * av_q2d(in_stream1->time_base);
    }

//打开输出文件
    if (!(ofmt->flags & AVFMT_NOFILE))
    {     //&ofmt_ctx->pb  Used to return the pointer to the created AVIOContext
        if ((err_code = avio_open(&ofmt_ctx->pb, out_file, AVIO_FLAG_WRITE)) < 0) {
            av_strerror(err_code, errors, ERROR_STR_SIZE);
            av_log(NULL, AV_LOG_ERROR,
                   "Could not open output file, %s, %d(%s)\n",
                   out_file, err_code, errors);
            goto __FAIL;
        }
    }

//写头信息
    //write media header
    if((err_code = avformat_write_header(ofmt_ctx, NULL)) < 0)
    {
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "Error occurred when writing media header!\n");
        goto __FAIL;
    }

    av_init_packet(&pkt);

// 读取音频数据并写入输出文件中
// av_read_frame  Return the next frame of a stream
// This function returns what is stored in the file
// If pkt->buf is NULL, then the packet is valid until the next av_read_frame() or until avformat_close_input()
    while (av_read_frame(ifmt_ctx1, &pkt) >= 0)
    {
        // 如果读取的时间超过了最长时间表示不需要该帧,跳过
        if (pkt.pts * av_q2d(in_stream1->time_base) > max_duration)
        {
            av_packet_unref(&pkt);
            continue;
        }
        // 如果是我们需要的音频流,转换时间基后写入文件
        //DTS(Decoding Time Stamp) PTS(Presentation Time Stamp)
        //DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的
        //当视频中没有B帧时,DTS和PTS顺序一致
        //音频的DTS和PTS一致
        if (pkt.stream_index == audio_stream_index)
        {
            //av_rescale_q_rn 转换时间基 将1的时间转化位2的时间
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream1->time_base, out_stream1->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream1->time_base, out_stream1->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.duration = av_rescale_q(max_duration, in_stream1->time_base, out_stream1->time_base);
            pkt.pos = -1;//int64_t pos; < byte position in stream, -1 if unknown
            pkt.stream_index = stream1;
            av_interleaved_write_frame(ofmt_ctx, &pkt);
            av_packet_unref(&pkt);
        }
    }


// 读取视频数据并写入输出文件中
    while (av_read_frame(ifmt_ctx2, &pkt) >= 0) {

        // 如果读取的时间超过了最长时间表示不需要该帧,跳过
        if (pkt.pts * av_q2d(in_stream2->time_base) > max_duration) {
            av_packet_unref(&pkt);
            continue;
        }
        // 如果是我们需要的视频流,转换时间基后写入文件
        if (pkt.stream_index == vedio_stream_indes) {
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream2->time_base, out_stream2->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream2->time_base, out_stream2->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.duration = av_rescale_q(max_duration, in_stream2->time_base, out_stream2->time_base);
            pkt.pos = -1;
            pkt.stream_index = stream2;
            av_interleaved_write_frame(ofmt_ctx, &pkt);
            av_packet_unref(&pkt);
        }
    }

//写尾信息
    av_write_trailer(ofmt_ctx);

    ret = 0;


__FAIL:

    if(ifmt_ctx1){
        avformat_close_input(&ifmt_ctx1);
    }

    if(ifmt_ctx2){
        avformat_close_input(&ifmt_ctx2);
    }

    if(ofmt_ctx){
        if(!(ofmt->flags & AVFMT_NOFILE)){
            avio_closep(&ofmt_ctx->pb);
        }
        avformat_free_context(ofmt_ctx);
    }



    return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值