ffmpeg 为视频添加背景音乐---单音频轨道stream

我们知道多媒体文件是由多个stream组成,每个stream对应不同的内容。比如视频,音频,字幕。

如果要用A视频的图像,B视频的音乐,生成C视频。原理就是提取出A视频的视频流数据,B视频的的音频流数据,写入C视频的视频流和音频流中。

流程图如下

ffmpeg 为视频添加背景音乐---单音频轨道stream

#include <stdlib.h>
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libavcodec/codec_par.h>
int make_out_file(double start_time, double end_time, char *audio_file_name, char *video_file_name, char *out_file_name);

int main(int argc, char *argv[])
{
    //输入4个参数  argv1 开始时间  argv2 结束时间  argv3 输入文件  argv3 输出文件
    double start_time, end_time;
    char *audio_file_name, *video_file_name, *out_file_name;
    av_log_set_level(AV_LOG_INFO);
    if (argc < 6)
    {
        av_log(NULL, AV_LOG_ERROR, "输入参数错误");
        return -1;
    }

    start_time = atoi(argv[1]);
    end_time = atoi(argv[2]);
    audio_file_name = argv[3];
    video_file_name = argv[4];
    out_file_name = argv[5];
    int ret = make_out_file(start_time, end_time, audio_file_name, video_file_name, out_file_name);
    return ret;
}

AVFormatContext *open_input_file(char *file_name)
{
    int ret = 0;
    AVFormatContext *fmt_ctx = NULL;
    ret = avformat_open_input(&fmt_ctx, file_name, NULL, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "打开输入文件失败 %s", file_name);
        return fmt_ctx;
    }
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "获取输入文件流信息失败 %s", file_name);
        return fmt_ctx;
    }
    av_dump_format(fmt_ctx, 0, file_name, 0);
    return fmt_ctx;
}

int createStream(AVFormatContext *ofmt_ctx, AVStream *in_stream)
{
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
    int ret = 0;
    if (!out_stream)
    {
        av_log(NULL, AV_LOG_ERROR, "创建输出流失败");
        return -1;
    }
    ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "copy 流参数错误");
        return ret;
    }
    out_stream->codecpar->codec_tag = 0;
    return ret;
}

int write_data(AVFormatContext* in_fmt_ctx, int in_index, 
                AVFormatContext* out_fmt_ctx, int out_index,
                double start_time, double end_time){
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.size = 0;
    pkt.data = NULL;

    int base_pts = 0;
    int base_dts = 0;
    int ret = 0;

    ret = av_seek_frame(in_fmt_ctx, -1, start_time * AV_TIME_BASE, AVSEEK_FLAG_ANY);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "移动的上下文到指定时间失败");
        return ret;
    }

    //写音频
    while (1)
    {
        AVStream *in_stream, *out_stream;
        ret = av_read_frame(in_fmt_ctx, &pkt);
        if (ret < 0)
            break;
        if (pkt.stream_index != in_index)
        {
            av_packet_unref(&pkt);
            continue;
        }
        in_stream = in_fmt_ctx->streams[in_index];
        out_stream = out_fmt_ctx->streams[out_index];

        //如果当前时间超过了要截取的时间,直接跳出循环
        if (av_q2d(in_stream->time_base) * pkt.pts > end_time)
        {
            av_packet_unref(&pkt);
            break;
        }

        //设置输出文件的dts和pts
        // av_log(NULL, AV_LOG_INFO, "writefreame pkt.pts = %lld\n", pkt.pts);
        if (base_pts == 0 || base_dts == 0)
        {

            base_dts = base_pts = pkt.pts;
            av_log(NULL, AV_LOG_INFO, "writefreame  base_pts = %d\n", base_pts);
        }
        av_log(NULL, AV_LOG_INFO, "writefreame  pkt.pts = %lld, dts = %lld\n", pkt.pts, pkt.dts);
        pkt.pts = av_rescale_q_rnd(pkt.pts - base_pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts - base_dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
        pkt.duration = (int)av_rescale_q((int64_t)pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        pkt.stream_index = out_index;
        av_log(NULL, AV_LOG_INFO, "writefreame 22222 pkt.pts = %lld, dts = %lld\n", pkt.pts, pkt.dts);

        ret = av_write_frame(out_fmt_ctx, &pkt);
        av_packet_unref(&pkt);
        if (ret < 0)
            break;
    }
    return ret;
}


int make_out_file(double start_time,
                  double end_time,
                  char *audio_file_name,
                  char *video_file_name,
                  char *out_file_name)
{
    int ret = 0;
    AVFormatContext *i_audio_fmt_ctx = NULL, *i_video_fmt_ctx = NULL, *ofmt_ctx = NULL;

    //第一步获取输入上下文

    i_audio_fmt_ctx = open_input_file(audio_file_name);
    if (!i_audio_fmt_ctx)
    {
        ret = -1;
        goto __ERROR;
    }

    i_video_fmt_ctx = open_input_file(video_file_name);
    if (!i_audio_fmt_ctx)
    {
        ret = -1;
        goto __ERROR;
    }

    //第二步获取输出上下文
    ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_file_name);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "获取输出文件上下文失败");
        goto __ERROR;
    }

    int video_stream_index = av_find_best_stream(i_video_fmt_ctx,
                                                 AVMEDIA_TYPE_VIDEO,
                                                 -1,
                                                 -1,
                                                 NULL,
                                                 -1);
    av_log(NULL, AV_LOG_ERROR, "输入文件的视频频流索引是 videoStreamIndex=%d", video_stream_index);

    int audio_stream_index = av_find_best_stream(i_audio_fmt_ctx,
                                                 AVMEDIA_TYPE_AUDIO,
                                                 -1,
                                                 -1,
                                                 NULL,
                                                 -1);
    av_log(NULL, AV_LOG_ERROR, "输入文件的视频频流索引是 videoStreamIndex=%d", audio_stream_index);



    //第三步为输出文件创建输出流
    int out_video_stream_index = -1;
    int out_audio_stream_index = -1;
    ret = createStream(ofmt_ctx, i_video_fmt_ctx->streams[video_stream_index]);
    if(ret < 0){
        goto __ERROR;
    }
    out_video_stream_index = 0;

    ret = createStream(ofmt_ctx, i_audio_fmt_ctx->streams[audio_stream_index]);
    if(ret < 0){
        goto __ERROR;
    }
    out_audio_stream_index = 1;
    av_dump_format(ofmt_ctx, 0, out_file_name, 1);


    //第四步为输出文件创建AvIOContext
    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&(ofmt_ctx->pb), out_file_name, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "创建输出文件IO上下文失败");
            goto __ERROR;
        }
    }

    //第五步写输出文件头
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "写多媒体文件头失败");
        goto __ERROR;
    }



    //第六步读取指定时间段的数据,然后进行时间戳转化,再写入输出文件
    ret = write_data(i_video_fmt_ctx, video_stream_index, 
                ofmt_ctx, out_video_stream_index,
                start_time, end_time);
    if (ret < 0)
    {
        goto __ERROR;
    }          
   
    ret =  write_data(i_audio_fmt_ctx, audio_stream_index, 
                ofmt_ctx, out_audio_stream_index,
                start_time, end_time);
    if (ret < 0)
    {
        goto __ERROR;
    }   
   

    //第七步写多媒体文件尾巴
    av_write_trailer(ofmt_ctx);

__ERROR:
    //第八步释放资源
    if (i_audio_fmt_ctx)
    {
        avformat_close_input(&i_audio_fmt_ctx);
        avformat_free_context(i_audio_fmt_ctx);
    }

    if (i_video_fmt_ctx)
    {
        avformat_close_input(&i_video_fmt_ctx);
        avformat_free_context(i_video_fmt_ctx);
    }

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

    if (ofmt_ctx)
    {
        avformat_free_context(ofmt_ctx);
    }
    return ret;
}

坚持输出,才能不停的输入。

学习基础,才能学得更深。

学习要慢点再慢点。

学习原理,对抗碎片。

上一篇:FFmpeg使用c语言sdk实现打印视频的信息


下一篇:Day4练习