基于新版FFmpeg(FFmpeg 6.1)的音视频复用(不涉及编解码)

本文介绍了如何使用FFmpeg6.1版本实现音频和视频文件的复用,包括选择合适的比特流过滤器、处理没有pts的码流、解决二倍速问题以及处理流索引匹配。代码示例详细展示了整个过程。
摘要由CSDN通过智能技术生成

1 项目中使用的FFmpeg函数介绍

FFmpeg库常用函数介绍(一)-CSDN博客

FFmpeg库常用函数介绍(二)-CSDN博客

FFmpeg库常用函数介绍(三)-CSDN博客

2 介绍

这篇文章介绍的是基于新版FFmpeg(FFmpeg 6.1)的音视频复用器的实现,可以实现音频和视频文件复用为一个视频文件,具体功能如下表所示。

输入视频文件

输入音频文件

输出视频文件

input.h264

input.aac

output.mp4 (avi、mkv、wmv等)

input.h264

input.mp3

input.mp4

input.mp3

input.mp4

input.aac

input.mp4

input.mp4

…等等…

3 代码逻辑

  1. 根据输出文件的格式选择是否开启比特流过滤器(AAC_ADTS_TO_ASC和H264_AVCC_TO_ANNEXB宏)。例如,输出格式为avi,就需要开启H264_AVCC_TO_ANNEXB(置为1);
  2. 打开输入音视频文件,创建并初始化输入AVFormatContext,创建输出AVFormatContext;
  3. 根据输入视频文件的视频流创建输出文件的视频流,拷贝编解码器参数;
  4. 根据输入音频文件的音频流创建输出文件的音频流,拷贝编解码器参数;
  5. 打开输出文件,写入文件头;
  6. 根据过滤器的开启情况创建并初始化对应比特流过滤器;
  7. 根据av_compare_ts的输出判断先读取音频还是视频文件,然后读取帧;
  8. 时间戳转换、送入过滤器过滤、交错写入;
  9. 所有帧写完后写入文件尾;

4 问题汇总

4.1 没有pts

有的码流没有pts,例如原始的H.264码流,因此需要自己手动设置pts。pts是以输入流时间基表示的ffmpeg内部时间。以输入流时间基表示的意思是有几个输入流时间基。ffmpeg内部时间是AV_TIME_BASE (1000000),换算关系是1s = 1000000。

计算过程是首先计算出ffmpeg内部时间表示的两帧之间的间隔:

int frame_duration = AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);

1 / av_q2d(in_stream->r_frame_rate)表示的是以秒表示的间隔,AV_TIME_BASE / av_q2d(in_stream->r_frame_rate)表示的是ffmpeg内部时间表示的间隔。

接着就是算出真正的pts,也就是以输入流时间基表示的ffmpeg内部时间。

pkt.pts = frame_index * frame_duration / (av_q2d(in_stream->time_base) * AV_TIME_BASE);

frame_index * frame_duration表示当前帧以ffmpeg内部时间表示的显示时间。(av_q2d(in_stream->time_base) * AV_TIME_BASE)表示输入流时间基以ffmpeg内部时间表示的结果。二者相除表示以输入流时间基表示的ffmpeg内部时间,也就是真正的pts。

4.2 二倍速问题

写完代码后,使用没有pts的码流进行测试,发现画面变成了二倍速,并且视频长度也减半了,猜测是pts的设置有问题。最终将pts乘以2解决了问题,但是目前还不知道原理是什么。

//解决2倍速问题...

pkt.pts *= 2;

4.3 packet里的stream_index的设置

输出文件的音视频流来自不同的文件,因此packet中流的索引与输出文件中流的索引可能不匹配,可能出现packet中音频帧和视频帧所对应的stream_index是一样的的情况。因此将packet中的音频或视频帧与输出流的音视频流的索引匹配上。

pkt.stream_index = out_stream->index;

5 代码

#include <stdio.h>
extern "C"
{
    #include "libavformat/avformat.h"
    #include "libavcodec/bsf.h"
};

#define AAC_ADTS_TO_ASC 0
#define H264_AVCC_TO_ANNEXB 0

void release_context(AVFormatContext *in_fmt_ctx1, AVFormatContext *in_fmt_ctx2, AVFormatContext *out_fmt_ctx, AVPacket *pkt1, AVPacket *pkt2,
AVBSFContext *bsf_ctx1, AVBSFContext *bsf_ctx2)
{
    if (out_fmt_ctx && !((out_fmt_ctx->oformat->flags) & AVFMT_NOFILE))
    {
        avio_close(out_fmt_ctx->pb);
    }

    avformat_close_input(&in_fmt_ctx1);
    avformat_close_input(&in_fmt_ctx2);
    avformat_free_context(out_fmt_ctx);
    av_packet_unref(pkt1);
    av_packet_unref(pkt2);
    av_bsf_free(&bsf_ctx1);
    av_bsf_free(&bsf_ctx2);
}

int main(int argc, char *argv[])
{
    if (argc < 4)
    {
        printf("argument error, caller should pass 3 filenames as arguments, for example, \"./main input_video.h264 input_audio.aac output_video.mp4\"\n");
        return -1;
    }

    const char *in_filename_video = argv[1];
    const char *in_filename_audio = argv[2];
    const char *out_filename_video = argv[3];

    AVFormatContext *in_fmt_ctx_video = NULL, *in_fmt_ctx_audio = NULL, *out_fmt_ctx = NULL;
    AVPacket pkt, pkt_filtered;
    memset(&pkt, 0, sizeof(pkt));
    memset(&pkt_filtered, 0, sizeof(pkt_filtered));
    AVBSFContext *bsf_ctx_video = NULL, *bsf_ctx_audio = NULL;

    int64_t ts_video = 0, ts_audio = 0;
    AVRational time_base_in_video, time_base_in_audio;

    int in_video_index = -1, in_audio_index = -1, out_video_index = -1, out_audio_index = -1;

    //打开输入视频,初始化AVFormatContext
    if (avformat_open_input(&in_fmt_ctx_video, in_filename_video, NULL, NULL) < 0)
    {
        printf("failed to open input video file\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }
    //寻找输入视频流信息
    if (avformat_find_stream_info(in_fmt_ctx_video, NULL) < 0)
    {
        printf("failed to find input video stream info\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }

    //格式化输出输入流信息
    av_dump_format(in_fmt_ctx_video, 0, in_filename_video, 0);

    //打开输入音频,初始化AVFormatContext
    if (avformat_open_input(&in_fmt_ctx_audio, in_filename_audio, NULL, NULL) < 0)
    {
        printf("failed to open input audio file\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }
    //寻找输入音频流信息
    if (avformat_find_stream_info(in_fmt_ctx_audio, NULL) < 0)
    {
        printf("failed to find input audio stream info\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }

    //格式化输出输入流信息
    av_dump_format(in_fmt_ctx_audio, 0, in_filename_audio, 0);

    //分配输出AVFormatContext
    if (avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_filename_video) < 0)
    {
        printf("failed to alloc output AVFormatContext\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }

    //遍历输入视频,寻找视频流,为输出AVFormatContext创建新流,拷贝编解码器参数
    for (int i = 0; i < in_fmt_ctx_video->nb_streams; i++)
    {
        if (in_fmt_ctx_video->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            AVStream *in_stream = in_fmt_ctx_video->streams[i];
            time_base_in_video = in_stream->time_base;
            //创建流
            AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
            if (!out_stream)
            {
                printf("failed to create new stream for output AVFormatContext\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
            //拷贝编解码器参数
            if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
            {
                printf("failed to copy codec parameters form input video to output video\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
            //自动选择符合输出格式的码流类型
            out_stream->codecpar->codec_tag = 0;
            //输入视频流索引
            in_video_index = i;
            //输出视频流索引
            out_video_index = 0;
            break;
        }
    }

    //遍历输入音频,寻找音频流,为输出AVFormatContext创建新流,拷贝编解码器参数
    for (int i = 0; i < in_fmt_ctx_audio->nb_streams; i++)
    {
        if (in_fmt_ctx_audio->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            AVStream *in_stream = in_fmt_ctx_audio->streams[i];
            time_base_in_audio = in_stream->time_base;
            //创建流
            AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
            if (!out_stream)
            {
                printf("failed to create new stream for output AVFormatContext\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
            //拷贝编解码器参数
            if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
            {
                printf("failed to copy codec parameters form input audio to output video\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
            //自动选择符合输出格式的码流类型
            out_stream->codecpar->codec_tag = 0;
            //输入音频流索引
            in_audio_index = i;
            //输出音频流索引
            out_audio_index = 1;
            break;
        }
    }

    //格式化输出输出音视频流信息
    av_dump_format(out_fmt_ctx, 0, out_filename_video, 1);

    //打开输出文件
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        if (avio_open2(&out_fmt_ctx->pb, out_filename_video, AVIO_FLAG_WRITE, NULL, NULL) < 0)
        {
            printf("failed to open output file\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
    }

    //写入文件头
    if (avformat_write_header(out_fmt_ctx, NULL) < 0)
    {
        printf("failed to write header to the output file\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }

    #if AAC_ADTS_TO_ASC
        //获取比特流过滤器
        const AVBitStreamFilter *bsf_audio = av_bsf_get_by_name("aac_adtstoasc");
    #endif

    #if H264_AVCC_TO_ANNEXB
        //获取比特流过滤器
        const AVBitStreamFilter *bsf_video = av_bsf_get_by_name("h264_mp4toannexb");
    #endif

    #if AAC_ADTS_TO_ASC
        //分配比特流过滤器上下文AVBSFContext
        if (av_bsf_alloc(bsf_audio, &bsf_ctx_audio) < 0)
        {
            printf("failed to alloc AVBSFContext\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
        //拷贝编解码器参数
        if (avcodec_parameters_copy(bsf_ctx_audio->par_in, in_fmt_ctx_audio->streams[in_audio_index]->codecpar) < 0)
        {
            printf("failed to copy codec parameters from input audio to the bi stream filter context\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
        //初始化AVBSFContext
        if (av_bsf_init(bsf_ctx_audio) < 0)
        {
            printf("failed to init AVBSFContext\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
    #endif

    #if H264_AVCC_TO_ANNEXB
        //分配比特流过滤器上下文AVBSFContext
        if (av_bsf_alloc(bsf_video, &bsf_ctx_video) < 0)
        {
            printf("failed to alloc AVBSFContext\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
        //拷贝编解码器参数
        if (avcodec_parameters_copy(bsf_ctx_video->par_in, in_fmt_ctx_video->streams[in_video_index]->codecpar) < 0)
        {
            printf("failed to copy codec parameters from input video to the bit stream filter context\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
        //初始化AVBSFContext
        if (av_bsf_init(bsf_ctx_video) < 0)
        {
            printf("failed to init AVBSFContext\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }
    #endif

    int frame_index = 0;
    AVFormatContext *fmt_ctx_v_or_a = NULL;
    while (true)
    {
        AVStream *in_stream = NULL, *out_stream = NULL;
        //比较时间戳,以判断先处理并写入音频还是视频帧
        int ret = av_compare_ts(ts_video, time_base_in_video, ts_audio, time_base_in_audio);
        switch (ret)
        {
            case -1:
            case 0:
            {
                fmt_ctx_v_or_a = in_fmt_ctx_video;
                in_stream = in_fmt_ctx_video->streams[in_video_index];
                out_stream = out_fmt_ctx->streams[out_video_index];
                break;
            }
            case 1:
            {
                fmt_ctx_v_or_a = in_fmt_ctx_audio;
                in_stream = in_fmt_ctx_audio->streams[in_audio_index];
                out_stream = out_fmt_ctx->streams[out_audio_index];
                break;
            }
            default:
            {
                printf("undefined result\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
        }

        //读取音视频帧
        ret = av_read_frame(fmt_ctx_v_or_a, &pkt);
        if (ret < 0)
        {
            if (ret == AVERROR_EOF)
            {
                break;
            }

            printf("failed to read frame from input file\n");
            release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
            return -1;
        }

        //读取的是输入的视频文件
        //如果读取到的帧不是视频帧则重新读取
        if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            if (pkt.stream_index != in_video_index)
            {
                av_packet_unref(&pkt);
                continue;
            }
        }
        //读取的是输入的音频文件
        //如果读取到的不是音频帧则重新读取
        else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            if (pkt.stream_index != in_audio_index)
            {
                av_packet_unref(&pkt);
                continue;
            }
        }

        //有的码流没有pts,例如原始的H.264码流
        //因此需要自己手动设置pts
        if (pkt.pts == AV_NOPTS_VALUE)
        {
            //两帧之间的间隔
            int frame_duration = AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);
            //计算pts以输入流时间基表示的ffmpeg内部时间
            pkt.pts = frame_index * frame_duration / (av_q2d(in_stream->time_base) * AV_TIME_BASE);
            //解决2倍速问题...
            pkt.pts *= 2;
            //计算duration以输入流时间基表示的ffmpeg内部时间
            pkt.duration = frame_duration / (av_q2d(in_stream->time_base) * AV_TIME_BASE);
            pkt.dts = pkt.pts;
            frame_index++;
        }

        if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            ts_video = pkt.pts;
        }
        else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            ts_audio = pkt.pts;
        }

        //时间戳转换
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q_rnd(pkt.duration, in_stream->time_base, out_stream->time_base, (AVRounding)AV_ROUND_INF);
        //输出文件的音视频流来自不同的文件,因此packet中流的索引与输出文件中流的索引可能不匹配,可能出现packet中音频帧和视频帧所对应的stream_index是一样的的情况
        //因此将packet中的音频或视频帧与输出流的音视频流的索引匹配上
        pkt.stream_index = out_stream->index;

        AVBSFContext *bsf_ctx = NULL;

        #if AAC_ADTS_TO_ASC
            if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
            {
                bsf_ctx = bsf_ctx_audio;
            }
        #endif

        #if H264_AVCC_TO_ANNEXB
            if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                bsf_ctx = bsf_ctx_video;
            }
        #endif

        if ((AAC_ADTS_TO_ASC && in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) || (H264_AVCC_TO_ANNEXB && in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO))
        {                
            //将packet送入过滤器
            int ans = av_bsf_send_packet(bsf_ctx, &pkt);
            if (ans < 0)
            {
                //需要多个packet才能过滤
                if (ans == AVERROR(EAGAIN))
                {
                    av_packet_unref(&pkt);
                    continue;
                }
                printf("failed to send packet to filter\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }

            //一个输入packet可能产生多个输出packet
            do
            {
                ans = av_bsf_receive_packet(bsf_ctx, &pkt_filtered);
                if (ans < 0 && ans != AVERROR(EAGAIN))
                {
                    if (ans == AVERROR_EOF)
                    {
                        break;
                    }
                    else
                    {
                        printf("failed to receive packet from filter\n");
                        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                        return -1;
                    }
                }
                //交错写入
                if (av_interleaved_write_frame(out_fmt_ctx, &pkt_filtered) < 0)
                {
                    printf("failed to write frame to the output file\n");
                    release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                    return -1;                    
                }
                av_packet_unref(&pkt_filtered);
            } while (ans == AVERROR(EAGAIN));
        }
        else
        {
            //交错写入
            if (av_interleaved_write_frame(out_fmt_ctx, &pkt) < 0)
            {
                printf("failed to write frame to the output file\n");
                release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
                return -1;
            }
        }

        av_packet_unref(&pkt);
    }

    //写入文件尾
    if (av_write_trailer(out_fmt_ctx) < 0)
    {
        printf("failed to write tail to the output file\n");
        release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
        return -1;
    }

    release_context(in_fmt_ctx_video, in_fmt_ctx_audio, out_fmt_ctx, &pkt, &pkt_filtered, bsf_ctx_video, bsf_ctx_audio);
    return 0;
}

项目代码及使用方法:FFmpeg_Learning_Projects/Mux_Audio_Video at master · zn111111/FFmpeg_Learning_Projects (github.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值