美文网首页
FFmpeg添加背景音乐

FFmpeg添加背景音乐

作者: 张俊峰0613 | 来源:发表于2019-01-02 11:39 被阅读0次

本篇我们就做一个用ffmpeg给视频添加背景音乐的功能


UI界面

UI界面有三个输入框,第一个填入要加背景音乐的视频文件,第二个填入要添加音乐的文件,第三个填入生成之后的视频文件,然后点击按钮。

实现原理

其实现原理主要是剥开视频文件拿到视频裸流,然后拿到音频文件根据时间戳一帧一帧的封装成一个新的视频文件。

实现代码

定义变量

该需求中涉及一个输入的音频文件、一个输入的视频文件、一个输出的视频文件,所以最起码有三个AVFormatContext、一个AVOutputFormat、三个文件路径;

AVOutputFormat *pOutputFmt = NULL;
AVFormatContext *pVideoInFmtCxt = NULL;//输入的视频的FormatContext
AVFormatContext *pAudioInFmtCxt = NULL;//输入的音频的FormatContext
AVFormatContext *pOutFmtCxt = NULL;//输出的音视频的FormatContext

const char *pVideoInFileName = env->GetStringUTFChars(input_video,NULL);
const char *pAudioInFileName = env ->GetStringUTFChars(input_music,NULL);
const char *pVideoOutFilePath = env->GetStringUTFChars(output_file,NULL);

打开要输入的音视频文件及文件信息


av_register_all();

//打开输入的视频文件
if ((ret = avformat_open_input(&pVideoInFmtCxt,pVideoInFileName,NULL,NULL)) < 0) {
    LOGE( "Could not open input video file.");
    goto end;
}
//获取视频文件信息
if ((ret = avformat_find_stream_info(pVideoInFmtCxt,NULL)) < 0) {
    LOGE( "Failed to retrieve input video stream information");
    goto end;
}

//打开输入的音频文件
if ((ret = avformat_open_input(&pAudioInFmtCxt,pAudioInFileName,NULL,NULL)) < 0) {
    LOGE( "Could not open input audio file.");
    goto end;
}
//获取音频文件信息
if ((ret = avformat_find_stream_info(pAudioInFmtCxt,NULL)) < 0) {
    LOGE( "Failed to retrieve input stream information");
    goto end;
}

初始化输出

//初始化输出码流的AVFormatContext
avformat_alloc_output_context2(&pOutFmtCxt,NULL,NULL,pVideoOutFilePath);
if (!pOutFmtCxt) {
    LOGE( "Could not create output context\n");
    ret = AVERROR_UNKNOWN;
    return -1;
}

//输出格式赋值(输出文件格式 xxx.mp4/xxx.flv...)
pOutputFmt = pOutFmtCxt->oformat;//初始化输出码流的

从音视频文件中得到输出流

//从输入的AVStream中获取一个输出的out_stream,视频输出流
for (i = 0;i < pVideoInFmtCxt->nb_streams;i++){
    //根据输入的视频流创建一个输出的视频流
    if (pVideoInFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        AVStream *in_stream = pVideoInFmtCxt->streams[i];
        //创建输出流通道AVStream
        AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
        if (pCodecCtx == NULL)
        {
            printf("Could not allocate AVCodecContext\n");
            return -1;
        }
        avcodec_parameters_to_context(pCodecCtx, in_stream->codecpar);

        AVStream *out_stream = avformat_new_stream(pOutFmtCxt,pCodecCtx->codec);
        videoindex_v = i;
        if (!out_stream) {
            LOGE( "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            break;
        }
        videoindex_out = out_stream->index;

        //Copy the settings of AVCodecContext
        if ((ret = avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx)) < 0) {
            printf("Failed to copy codec context to out_stream codecpar context\n");
            goto end;
        }

        pCodecCtx->codec_tag = 0;
        if (pOutFmtCxt->oformat->flags & AVFMT_GLOBALHEADER)
            pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
        break;
    }
}

//从输入的AVStream中获取一个输出的out_stream,音频输出流
for (i = 0;i < pAudioInFmtCxt->nb_streams;i++){
    if (pAudioInFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
        AVStream *in_stream = pAudioInFmtCxt->streams[i];
        //创建输出流通道AVStream
        AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
        if (pCodecCtx == NULL)
        {
            printf("Could not allocate AVCodecContext\n");
            return -1;
        }
        avcodec_parameters_to_context(pCodecCtx, in_stream->codecpar);
        AVStream *out_stream = avformat_new_stream(pOutFmtCxt,pCodecCtx->codec);

        audioindex_a = i;
        if (!out_stream) {
            LOGE( "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }
        audioindex_out = out_stream->index;

        if ((ret = avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx)) < 0) {
            printf("Failed to copy codec context to out_stream codecpar context\n");
            goto end;
        }

        pCodecCtx->codec_tag = 0;
        if (pOutFmtCxt->oformat->flags & AVFMT_GLOBALHEADER)
            pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
        break;
    }
}

打开输出文件、写文件头

if (!(pOutputFmt->flags & AVFMT_NOFILE)) {
    if (avio_open(&pOutFmtCxt->pb, pVideoOutFilePath, AVIO_FLAG_WRITE) < 0) {
        LOGE( "Could not open output file '%s'", pVideoOutFilePath);
        return -1;
    }
}

//写文件头
if (avformat_write_header(pOutFmtCxt, NULL) < 0) {
    LOGE( "Error occurred when opening output file\n");
    return -1;
}

向输出文件中写入视频和音频

while(1){
    AVFormatContext *pInFmtCtx;
    int stream_index = 0;
    AVStream *in_stream,*out_stream;
    // av_compare_ts是比较时间戳用的。通过该函数可以决定该写入视频还是音频
    if (av_compare_ts(cur_pts_v,pVideoInFmtCxt->streams[videoindex_v]->time_base,
                cur_pts_a,pAudioInFmtCxt->streams[audioindex_a]->time_base) <= 0) {
        //LOGE("写视频数据");
        pInFmtCtx = pVideoInFmtCxt;//视频
        //这里要赋值了,注意注意
        stream_index = videoindex_out;

        if (av_read_frame(pInFmtCtx,&pkt) >= 0) {//读取流
            do {
                in_stream  = pInFmtCtx->streams[pkt.stream_index];
                out_stream = pOutFmtCxt->streams[stream_index];

                if(pkt.stream_index == videoindex_v){
                    // H.264裸流没有PTS,因此必须手动写入PTS
                    if(pkt.pts == AV_NOPTS_VALUE){
                        //写PTS
                        AVRational time_base1 = in_stream->time_base;
                        //Duration between 2 frames (us)
                        int64_t calc_duration = (double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
                        //Parameters
                        pkt.pts = (double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                        pkt.dts=pkt.pts;
                        pkt.duration = (double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                        frame_index++;
                    }
                    cur_pts_v = pkt.pts;
                    //LOGE("cur_pts_v === %lld",cur_pts_v);
                    break;
                }
            }while (av_read_frame(pInFmtCtx,&pkt) >= 0);
        } else {
            break;
        }
    } else {
        LOGE("写音频数据");
        pInFmtCtx = pAudioInFmtCxt;
        stream_index = audioindex_out;
        if(av_read_frame(pInFmtCtx, &pkt) >= 0){
            do{
                in_stream  = pInFmtCtx->streams[pkt.stream_index];
                out_stream = pOutFmtCxt->streams[stream_index];

                if(pkt.stream_index == audioindex_a){
                    //FIX:No PTS
                    //Simple Write PTS
                    if(pkt.pts==AV_NOPTS_VALUE){
                        //Write PTS
                        AVRational time_base1=in_stream->time_base;
                        //Duration between 2 frames (us)
                        int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
                        //Parameters
                        pkt.pts = (double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                        pkt.dts = pkt.pts;
                        pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                        frame_index++;
                    }
                    cur_pts_a = pkt.pts;
                    LOGE("cur_pts_a === %lld",cur_pts_a);
                    break;
                }
            }while(av_read_frame(pInFmtCtx, &pkt) >= 0);
        } else {
            break;
        }
    }

    //FIX:Bitstream Filter
#if USE_H264BSF
    av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
#if USE_AACBSF
    av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
    LOGE("pkt.pts = %lld ",pkt.pts);
    //转变 PTS/DTS
    pkt.pts = av_rescale_q_rnd(pkt.pts,in_stream->time_base,out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    LOGE("pkt.pts == %lld",pkt.pts);
    pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    pkt.duration = av_rescale_q(pkt.duration,in_stream->time_base, out_stream->time_base);
    pkt.pos = -1;
    pkt.stream_index = stream_index;
    LOGE("Write 1 Packet. size:%5d\tpts:%lld\n",pkt.size,pkt.pts);
    //Write AVPacket 音频或视频裸流
    if ((ret = av_interleaved_write_frame(pOutFmtCxt, &pkt)) < 0) {
        av_strerror(ret,errorbuf, sizeof(errorbuf));
        LOGE("ERROR : %s",errorbuf);
        LOGE( "Error muxing packet error code === %d\n",ret);
        av_packet_unref(&pkt);
        break;
    }
    av_packet_unref(&pkt);
}

释放资源

end:
    avformat_close_input(&pVideoInFmtCxt);
    avformat_close_input(&pAudioInFmtCxt);
    if (pOutFmtCxt && !(pOutputFmt->flags & AVFMT_NOFILE))
        avio_close(pOutFmtCxt->pb);
    avformat_free_context(pOutFmtCxt);
    if (ret < 0 && ret != AVERROR_EOF) {
        LOGE( "Error occurred.\n");
        return -1;
    }
    return 0;

代码

JNIEXPORT jint JNICALL Java_com_example_zjf_ffmpeg_FFmpeg_addBgm
        (JNIEnv *env, jobject obj, jstring input_video, jstring input_music, jstring output_file){
    AVOutputFormat *pOutputFmt = NULL;
    AVFormatContext *pVideoInFmtCxt = NULL;//输入的视频的FormatContext
    AVFormatContext *pAudioInFmtCxt = NULL;//输入的音频的FormatContext
    AVFormatContext *pOutFmtCxt = NULL;//输出的音视频的FormatContext
    AVPacket pkt;
    int ret, i;
    int videoindex_v=-1,videoindex_out=-1;
    int audioindex_a=-1,audioindex_out=-1;
    int frame_index=0;
    int64_t cur_pts_v=0;//视频的pts
    int64_t cur_pts_a=0;//音频的pts

    char errorbuf[1024] = { 0 };

    const char *pVideoInFileName = env->GetStringUTFChars(input_video,NULL);
    const char *pAudioInFileName = env ->GetStringUTFChars(input_music,NULL);
    const char *pVideoOutFilePath = env->GetStringUTFChars(output_file,NULL);

    LOGD("pVideoOutFilePath === %s",pVideoOutFilePath);
    av_register_all();

    //打开输入的视频文件
    if ((ret = avformat_open_input(&pVideoInFmtCxt,pVideoInFileName,NULL,NULL)) < 0) {
        LOGE( "Could not open input video file.");
        goto end;
    }
    //获取视频文件信息
    if ((ret = avformat_find_stream_info(pVideoInFmtCxt,NULL)) < 0) {
        LOGE( "Failed to retrieve input video stream information");
        goto end;
    }

    //打开输入的音频文件
    if ((ret = avformat_open_input(&pAudioInFmtCxt,pAudioInFileName,NULL,NULL)) < 0) {
        LOGE( "Could not open input audio file.");
        goto end;
    }
    //获取音频文件信息
    if ((ret = avformat_find_stream_info(pAudioInFmtCxt,NULL)) < 0) {
        LOGE( "Failed to retrieve input stream information");
        goto end;
    }

    LOGE("===========Input Information==========\n");
    av_dump_format(pVideoInFmtCxt, 0, pVideoInFileName, 0);
    av_dump_format(pAudioInFmtCxt, 0, pAudioInFileName, 0);
    LOGE("======================================\n");

    //初始化输出码流的AVFormatContext
    avformat_alloc_output_context2(&pOutFmtCxt,NULL,NULL,pVideoOutFilePath);
    if (!pOutFmtCxt) {
        LOGE( "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        return -1;
    }

    //输出格式赋值
    pOutputFmt = pOutFmtCxt->oformat;

    //从输入的AVStream中获取一个输出的out_stream,视频输出流
    for (i = 0;i < pVideoInFmtCxt->nb_streams;i++){
        //根据输入的视频流创建一个输出的视频流
        if (pVideoInFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            AVStream *in_stream = pVideoInFmtCxt->streams[i];
            //创建输出流通道AVStream
            AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
            if (pCodecCtx == NULL)
            {
                printf("Could not allocate AVCodecContext\n");
                return -1;
            }
            avcodec_parameters_to_context(pCodecCtx, in_stream->codecpar);

            AVStream *out_stream = avformat_new_stream(pOutFmtCxt,pCodecCtx->codec);
            videoindex_v = i;
            if (!out_stream) {
                LOGE( "Failed allocating output stream\n");
                ret = AVERROR_UNKNOWN;
                break;
            }
            videoindex_out = out_stream->index;

            //Copy the settings of AVCodecContext
            if ((ret = avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx)) < 0) {
                printf("Failed to copy codec context to out_stream codecpar context\n");
                goto end;
            }

            pCodecCtx->codec_tag = 0;
            if (pOutFmtCxt->oformat->flags & AVFMT_GLOBALHEADER)
                pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
            break;
        }
    }

    //从输入的AVStream中获取一个输出的out_stream,音频输出流
    for (i = 0;i < pAudioInFmtCxt->nb_streams;i++){
        if (pAudioInFmtCxt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
            AVStream *in_stream = pAudioInFmtCxt->streams[i];
            //创建输出流通道AVStream
            AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);
            if (pCodecCtx == NULL)
            {
                printf("Could not allocate AVCodecContext\n");
                return -1;
            }
            avcodec_parameters_to_context(pCodecCtx, in_stream->codecpar);
            AVStream *out_stream = avformat_new_stream(pOutFmtCxt,pCodecCtx->codec);

            audioindex_a = i;
            if (!out_stream) {
                LOGE( "Failed allocating output stream\n");
                ret = AVERROR_UNKNOWN;
                goto end;
            }
            audioindex_out = out_stream->index;

            if ((ret = avcodec_parameters_from_context(out_stream->codecpar, pCodecCtx)) < 0) {
                printf("Failed to copy codec context to out_stream codecpar context\n");
                goto end;
            }

            pCodecCtx->codec_tag = 0;
            if (pOutFmtCxt->oformat->flags & AVFMT_GLOBALHEADER)
                pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
            break;
        }
    }

    LOGE("==========Output Information==========\n");
    av_dump_format(pOutFmtCxt, 0, pVideoOutFilePath, 1);
    LOGE("======================================\n");

    //打开输出文件
    if (!(pOutputFmt->flags & AVFMT_NOFILE)) {
        if (avio_open(&pOutFmtCxt->pb, pVideoOutFilePath, AVIO_FLAG_WRITE) < 0) {//打开输出文件。
            LOGE( "Could not open output file '%s'", pVideoOutFilePath);
            return -1;
        }
    }

    //写文件头
    if (avformat_write_header(pOutFmtCxt, NULL) < 0) {
        LOGE( "Error occurred when opening output file\n");
        return -1;
    }

    //FIX
#if USE_H264BSF
    AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb");
#endif
#if USE_AACBSF
    AVBitStreamFilterContext* aacbsfc =  av_bitstream_filter_init("aac_adtstoasc");
#endif

    while(1){
        AVFormatContext *pInFmtCtx;
        int stream_index = 0;
        AVStream *in_stream,*out_stream;
        // av_compare_ts是比较时间戳用的。通过该函数可以决定该写入视频还是音频
        if (av_compare_ts(cur_pts_v,pVideoInFmtCxt->streams[videoindex_v]->time_base,
                    cur_pts_a,pAudioInFmtCxt->streams[audioindex_a]->time_base) <= 0) {
            //LOGE("写视频数据");
            pInFmtCtx = pVideoInFmtCxt;//视频
            //这里要赋值了,注意注意
            stream_index = videoindex_out;

            if (av_read_frame(pInFmtCtx,&pkt) >= 0) {//读取流
                do {
                    in_stream  = pInFmtCtx->streams[pkt.stream_index];
                    out_stream = pOutFmtCxt->streams[stream_index];

                    if(pkt.stream_index == videoindex_v){
                        // H.264裸流没有PTS,因此必须手动写入PTS
                        if(pkt.pts == AV_NOPTS_VALUE){
                            //写PTS
                            AVRational time_base1 = in_stream->time_base;
                            //Duration between 2 frames (us)
                            int64_t calc_duration = (double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
                            //Parameters
                            pkt.pts = (double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                            pkt.dts=pkt.pts;
                            pkt.duration = (double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                            frame_index++;
                        }
                        cur_pts_v = pkt.pts;
                        //LOGE("cur_pts_v === %lld",cur_pts_v);
                        break;
                    }
                }while (av_read_frame(pInFmtCtx,&pkt) >= 0);
            } else {
                break;
            }
        } else {
            LOGE("写音频数据");
            pInFmtCtx = pAudioInFmtCxt;
            stream_index = audioindex_out;
            if(av_read_frame(pInFmtCtx, &pkt) >= 0){
                do{
                    in_stream  = pInFmtCtx->streams[pkt.stream_index];
                    out_stream = pOutFmtCxt->streams[stream_index];

                    if(pkt.stream_index == audioindex_a){
                        //FIX:No PTS
                        //Simple Write PTS
                        if(pkt.pts==AV_NOPTS_VALUE){
                            //Write PTS
                            AVRational time_base1=in_stream->time_base;
                            //Duration between 2 frames (us)
                            int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
                            //Parameters
                            pkt.pts = (double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                            pkt.dts = pkt.pts;
                            pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
                            frame_index++;
                        }
                        cur_pts_a = pkt.pts;
                        LOGE("cur_pts_a === %lld",cur_pts_a);
                        break;
                    }
                }while(av_read_frame(pInFmtCtx, &pkt) >= 0);
            } else {
                break;
            }
        }

        //FIX:Bitstream Filter
#if USE_H264BSF
        av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
#if USE_AACBSF
        av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
        LOGE("pkt.pts = %lld ",pkt.pts);
        //Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts,in_stream->time_base,out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        LOGE("pkt.pts == %lld",pkt.pts);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q(pkt.duration,in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        pkt.stream_index = stream_index;
        LOGE("Write 1 Packet. size:%5d\tpts:%lld\n",pkt.size,pkt.pts);
        //Write AVPacket 音频或视频裸流
        if ((ret = av_interleaved_write_frame(pOutFmtCxt, &pkt)) < 0) {
            av_strerror(ret,errorbuf, sizeof(errorbuf));
            LOGE("ERROR : %s",errorbuf);
            LOGE( "Error muxing packet error code === %d\n",ret);
            av_packet_unref(&pkt);
            break;
        }
        //av_free_packet(&pkt);
        av_packet_unref(&pkt);
    }

    //写文件尾
    av_write_trailer(pOutFmtCxt);

#if USE_H264BSF
    av_bitstream_filter_close(h264bsfc);
#endif
#if USE_AACBSF
    av_bitstream_filter_close(aacbsfc);
#endif

    end:
    avformat_close_input(&pVideoInFmtCxt);
    avformat_close_input(&pAudioInFmtCxt);
    if (pOutFmtCxt && !(pOutputFmt->flags & AVFMT_NOFILE))
        avio_close(pOutFmtCxt->pb);
    avformat_free_context(pOutFmtCxt);
    if (ret < 0 && ret != AVERROR_EOF) {
        LOGE( "Error occurred.\n");
        return -1;
    }
    return 0;
}

源码地址:https://github.com/Xiaoben336/FFmpeg

相关文章

网友评论

      本文标题:FFmpeg添加背景音乐

      本文链接:https://www.haomeiwen.com/subject/epwslqtx.html