• Android 音视频深入 十二 FFmpeg视频替换声音(附源码下载)


    项目地址,求star
    https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E7%BB%99%E8%A7%86%E9%A2%91%E6%8D%A2%E5%A3%B0%E9%9F%B3

    一个视频有三个流,视频流,音频流,字幕流,我将视频A的视频流拿出来,将音乐B的音频流拿出来,合在一起成新的视频

    还是老规矩直接说c代码如何运行

    注册组件,打开并获得MP4文件和MP3文件的信息

        av_register_all();
        //Input
        if ((ret = avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0)) < 0) {//打开输入的视频文件
            LOGE( "Could not open input file.");
            goto end;
        }
        if ((ret = avformat_find_stream_info(ifmt_ctx_v, 0)) < 0) {//获取视频文件信息
            LOGE( "Failed to retrieve input stream information");
            goto end;
        }
    
        if ((ret = avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0)) < 0) {//打开输入的音频文件
            LOGE( "Could not open input file.");
            goto end;
        }
        if ((ret = avformat_find_stream_info(ifmt_ctx_a, 0)) < 0) {//获取音频文件信息
            LOGE( "Failed to retrieve input stream information");
            goto end;
        }

    创建输出文件

         //Output
        avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);//初始化输出码流的AVFormatContext。
        if (!ofmt_ctx) {
            LOGE( "Could not create output context
    ");
            ret = AVERROR_UNKNOWN;
            return -1;
        }
        ofmt = ofmt_ctx->oformat;

    获取MP4的视频流和MP3的音频流

        //从输入的AVStream中获取一个输出的out_stream
        for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {
            //Create output AVStream according to input AVStream
            if(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
                AVStream *in_stream = ifmt_ctx_v->streams[i];
                AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);//创建流通道AVStream
                videoindex_v=i;
                if (!out_stream) {
                    LOGE( "Failed allocating output stream
    ");
                    ret = AVERROR_UNKNOWN;
                    break;
                }
                videoindex_out=out_stream->index;
                //Copy the settings of AVCodecContext
                if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
                    LOGE( "Failed to copy context from input to output stream codec context
    ");
                    break;
                }
                out_stream->codec->codec_tag = 0;
                if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
                    out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
                break;
            }
        }
    
        for (i = 0; i < ifmt_ctx_a->nb_streams; i++) {
            //Create output AVStream according to input AVStream
            if(ifmt_ctx_a->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO){
                AVStream *in_stream = ifmt_ctx_a->streams[i];
                AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
                audioindex_a=i;
                if (!out_stream) {
                    LOGE( "Failed allocating output stream
    ");
                    ret = AVERROR_UNKNOWN;
                    goto end;
                }
                audioindex_out=out_stream->index;
                //Copy the settings of AVCodecContext
                if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
                    LOGE( "Failed to copy context from input to output stream codec context
    ");
                    goto end;
                }
                out_stream->codec->codec_tag = 0;
                if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
                    out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    
                break;
            }
        }


    获取输出文件的信息并打开输出文件,获得输出流

        LOGE("==========Output Information==========
    ");
        av_dump_format(ofmt_ctx, 0, out_filename, 1);
        LOGE("======================================
    ");
        //Open output file
        if (!(ofmt->flags & AVFMT_NOFILE)) {
            if (avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {//打开输出文件。
                LOGE( "Could not open output file '%s'", out_filename);
                return -1;
            }
        }
        //Write file header
        if (avformat_write_header(ofmt_ctx, NULL) < 0) {
            LOGE( "Error occurred when opening output file
    ");
            return -1;
        }

    接下来就是边解码边编码了,这个解码是解码视频流和音频流,这个两个流解码速度需要保持一致
    这个通过双方时间轴来判断,然后就将解码的数据编码放入输出文件

            //Get an AVPacket .   av_compare_ts是比较时间戳用的。通过该函数可以决定该写入视频还是音频。
            if(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0)

    开始是先解码视频,放入cur_pts_v

                if(av_read_frame(ifmt_ctx, &pkt) >= 0){
                    do{
                        in_stream  = ifmt_ctx->streams[pkt.stream_index];
                        out_stream = ofmt_ctx->streams[stream_index];
    
                        if(pkt.stream_index==videoindex_v){
                            //FIX:No PTS (Example: Raw H.264) H.264裸流没有PTS,因此必须手动写入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_v=pkt.pts;
                            break;
                        }
                    }while(av_read_frame(ifmt_ctx, &pkt) >= 0);
                }


    接着是解码音频放入cur_pts_a

                if(av_read_frame(ifmt_ctx, &pkt) >= 0){
                    do{
                        in_stream  = ifmt_ctx->streams[pkt.stream_index];
                        out_stream = ofmt_ctx->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;
    
                            break;
                        }
                    }while(av_read_frame(ifmt_ctx, &pkt) >= 0);
                }else{
                    break;
                }
    

    然后就是将之前解码的数据编码放入输出文件,释放这个pkt

            //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));
            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	pts:%lld
    ",pkt.size,pkt.pts);
            //Write AVPacket 音频或视频裸流
            if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {
                LOGE( "Error muxing packet
    ");
                break;
            }
    
    av_free_packet(&pkt);

    通过不断地循环,解码一下视频流,解码一下音频流,编码放入输出文件,这三个步骤不断循环完成合成视频


    完成输出视频,并释放资源

        //Write file trailer
        av_write_trailer(ofmt_ctx);
    
    #if USE_H264BSF
        av_bitstream_filter_close(h264bsfc);
    #endif
    #if USE_AACBSF
        av_bitstream_filter_close(aacbsfc);
    #endif
    
        end:
        avformat_close_input(&ifmt_ctx_v);
        avformat_close_input(&ifmt_ctx_a);
        /* close output */
        if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
            avio_close(ofmt_ctx->pb);
        avformat_free_context(ofmt_ctx);
        if (ret < 0 && ret != AVERROR_EOF) {
            LOGE( "Error occurred.
    ");
            return -1;
        }

    下一次依旧与音频有关系

  • 相关阅读:
    JVM Ecosystem Report 2020
    TiDB 简介
    Docker镜像分层打包方案
    Promethues + Grafana + AlertManager使用总结
    Spring Boot自动注入原理
    Spring Boot 2.x 自定义Endpoint
    Oracle 等待事件 Enq: CF
    1000行MySQL学习笔记
    PostgreSQL DBA常用SQL查询语句
    MongoDB DBA常用的NoSQL语句
  • 原文地址:https://www.cnblogs.com/jianpanwuzhe/p/8427429.html
Copyright © 2020-2023  润新知