美文网首页FFmpeg小白学习记录
FFmpeg小白学习记录(六)视频格式转换流程

FFmpeg小白学习记录(六)视频格式转换流程

作者: oddly | 来源:发表于2022-06-08 17:10 被阅读0次

    视频格式转换流程

    视频格式转换主要分为两种类型:转封装和转码

    • 转封装:多媒体文件是一个容器,转封装相当于容器内的物品不变只是换了一个容器,其内容不会发生改变
    • 转码:转码就是将流中的数据根据要转换的格式进行转换,可以根据需求更改数据内容

    转封装

    音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如MP4文件,MOV文件,MP3文件等等。容器文件和编码方法是两个不同的概念,容器文件中可以支持多种编码方式,一种编码方式可以存放在不同的容器文件中,在转封装前需要查看对应的容器文件是否支持当前的编码方式

    FFmpeg 转封装流程

    其大致的流程就是将输入文件解码和输出文件编码,只不过因为不涉及数据格式转换的操作,只需要执行到 AVPacket 层面即可,执行转换所需的时间短

    也意味着我们不需要获取编解码器,解码器:AVPacket -> AVFrame 编码器:AVFrame -> AVPacket

    extern"C" {
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libswscale/swscale.h"
    #include "libavutil/imgutils.h"
    }
    #include <iostream>
    
    //本次案例中实现 MP4 -> FLV,视频流编码为h.264 音频流编码为aac,两种容器文件都支持这两种编码
    void exchangeMuxing() {
        AVFormatContext* srcFmtCtx = NULL, * dstFmtCtx = NULL;
        AVPacket* pkt = av_packet_alloc();
    
        const char* srcFile = "target.mp4";
        const char* dstFile = "result.ts";
    
        int ret;
        int* streamMap=NULL;
        int streamMapSize;
    
        do {
            //解码流程[01]:打开多媒体文件
            ret = avformat_open_input(&srcFmtCtx, srcFile, NULL, NULL);
            if (ret < 0) {
                cout << "Could not open input file" << endl;
                break;
            }
    
            //解码流程[02]:获取文件中的流信息
            ret = avformat_find_stream_info(srcFmtCtx, NULL);
            if (ret < 0) {
                cout << "Failed find stream information" << endl;
                break;
            }
    
            av_dump_format(srcFmtCtx, 0, srcFile, 0);
    
            //记录流的数量,并为streamMap分配内存
            streamMapSize = srcFmtCtx->nb_streams;
            streamMap = (int*)av_malloc_array(streamMapSize, sizeof(*streamMap));
            if (streamMap == NULL) {
                cout << "Failed malloc array" << endl;
                break;
            }
    
            //编码流程【01】:打开输出文件
            ret = avformat_alloc_output_context2(&dstFmtCtx, NULL, NULL, dstFile);
            if (ret < 0) {
                cout << "Could not create output context" << endl;
                break;
            }
    
            ret = avio_open(&dstFmtCtx->pb, dstFile, AVIO_FLAG_READ_WRITE);
            if (ret < 0) {
                cout << "Could not open output file" << endl;
                break;
            }
    
            int index = 0;
            bool isError = false;
            for (int i = 0; i < streamMapSize; i++) {
                AVStream* outStream = NULL;
                AVStream* inStream = srcFmtCtx->streams[i];
    
                //创建视频流,并复制参数
                outStream = avformat_new_stream(dstFmtCtx, NULL);
                if (outStream == NULL) {
                    cout << "Failed allocating output stream" << endl;
                    isError = true;
                    break;
                }
    
                ret = avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);
                if (ret < 0) {
                    cout << "Failed to copy codec parameters" << endl;
                    isError = true;
                    break;
                }
    
                //记录对应Stream的索引,[inStream->index : outStream->index]
                streamMap[i] = outStream->index;
            }
    
            if (isError) break;
    
            //编码流程【02】:向文件中写入头信息
            ret = avformat_write_header(dstFmtCtx,NULL);
            if (ret<0) {
                cout << "Failed to write file heade" << endl;
                break;
            }
    
            //解码流程[03]:获取AVPacket数据
            while (av_read_frame(srcFmtCtx, pkt)>=0) {
                AVStream* outStream = NULL,* inStream = NULL;
    
                inStream = srcFmtCtx->streams[pkt->stream_index];
    
                if (pkt->stream_index>=streamMapSize) {
                    av_packet_unref(pkt);
                    continue;
                }
    
                //查找到对应的输出流的索引并赋值
                pkt->stream_index = streamMap[pkt->stream_index];
                outStream = dstFmtCtx->streams[pkt->stream_index];
    
                //转换pkt的时间戳,使得与输出流的时间基相匹配
                pkt->pts = av_rescale_q_rnd(pkt->pts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->dts = av_rescale_q_rnd(pkt->dts, inStream->time_base, outStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->duration = av_rescale_q(pkt->duration, inStream->time_base, outStream->time_base);
                pkt->pos = -1;
    
                //编码流程【03】:将AVPacket数据写入文件中
                ret=av_interleaved_write_frame(dstFmtCtx,pkt);
                if (ret<0) {
                    cout<< "Failed to write packet" <<endl;
                    break;
                }
                av_packet_unref(pkt);
            }
            //编码流程【04】:向文件中写入文件尾部标识,并释放该文件
            av_write_trailer(dstFmtCtx);
        } while (0);
    
        //释放资源
        av_packet_free(&pkt);
        if (srcFmtCtx) avformat_close_input(&srcFmtCtx);
        if (dstFmtCtx) {
            avformat_free_context(dstFmtCtx);
            avio_closep(&dstFmtCtx->pb);
        }
        if (streamMap) av_free(streamMap);
    }
    
    代码分析

    av_q2d

    av_q2d就是将时间基转换为对应的 double 值

    static inline double av_q2d(AVRational a){
        return a.num / (double) a.den;
    }
    

    av_rescale_q_rnd与av_rescale_q

    av_rescale_q_rnd函数进行 a*bq/cq的运算,然后将计算结果进行四舍五入

    int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq){
        return av_rescale_q_rnd(a, bq, cq, AV_ROUND_NEAR_INF);
    }
    
    int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, enum AVRounding rnd){
        int64_t b = bq.num * (int64_t)cq.den;
        int64_t c = cq.num * (int64_t)bq.den;
        return av_rescale_rnd(a, b, c, rnd);
    }
    

    其中enum AVRounding rnd指定四舍五入的方式,共有以下6种类型:

    enum AVRounding {
        AV_ROUND_ZERO     = 0,  //计算结果靠近0   -3/2 → -1
        AV_ROUND_INF      = 1,  //计算结果远离0   -3/2 → -2
        AV_ROUND_DOWN     = 2,  //计算结果趋于负无穷,向下取整    9/5 → 1  
        AV_ROUND_UP       = 3,  //计算结果趋于正无穷,向上取整    6/5 → 2 
        AV_ROUND_NEAR_INF = 5,  //计算结果四舍五入,小于0.5取值趋向0,大于0.5取值趋远于0
        /**
         * AV_ROUND_PASS_MINMAX用于避免出现 AV_NOPTS_VALUE 参与运算的情况
         * 它需要通过位运算符'|'与其他枚举值一起使用,该值是一个位掩码
         *
         * @代码示例
         * 正常:
         * av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
         * // Rescaling 3:
         * //     Calculating 3 * 1 / 2
         * //     3 / 2 is rounded up to 2
         * //     => 2
         *
         * AV_NOPTS_VALUE参与运算:
         * av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
         * // Rescaling AV_NOPTS_VALUE:
         * //     AV_NOPTS_VALUE == INT64_MIN
         * //     AV_NOPTS_VALUE is passed through
         * //     => AV_NOPTS_VALUE
         * @endcode
         */
        AV_ROUND_PASS_MINMAX = 8192,
    };
    

    转码

    源容器格式的音/视频编码方式在目标容器不被支持,也就无法使用转封装方式进行转换,此时就需要先解码再编码实现视频格式转换,即转码

    如果需要改变数据内容,如音视频码率、视频分辨率等,那么也需要使用转码的方式,其实 转码 = 解码 + 编码,不过要注意编码格式是否为有损压缩,可能会导致视频画面模糊

    FFmpeg转码

    因为代码量较多且逻辑较为复杂,这里选择通过类的方式实现并划分为各个模块进行讲解:基础模块、视频模块、音频模块

    TranscodeHandler
    extern"C" {
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libswresample/swresample.h"
    #include "libswscale/swscale.h"
    #include "libavutil/imgutils.h"
    #include "libavutil/opt.h"
    }
    #include <iostream>
    using namespace std;
    
    class TranscodeHandler{
    public:
        //执行具体的转码操作
        void doTranscode();
    private:
        //实现文件从 flv -> mp4
        const char* srcFile = "target.flv";
        const char* dstFile = "result.mp4";
    
        //声明相关变量,基本上都是成对出现:输入与输出
        AVFormatContext* srcFmtCtx = NULL, * dstFmtCtx = NULL;
    
        AVCodecContext* srcVideoCodecCtx = NULL, * srcAudioCodecCtx = NULL;
        AVCodecContext* dstVideoCodecCtx = NULL, * dstAudioCodecCtx = NULL;
    
        AVStream* srcVideoStream = NULL, * srcAudioStream = NULL;
        AVStream* dstVideoStream = NULL, * dstAudioStream = NULL;
    
        AVFrame* srcVideoFrame = NULL, * srcAudioFrame = NULL;
        AVFrame* dstVideoFrame = NULL, * dstAudioFrame = NULL;
        
        SwsContext* videoSwsCtx = NULL;
        SwrContext* audioSwrCtx = NULL;
    
        AVPacket* pkt = av_packet_alloc();
    
        //pts
        int videoPts = 0;
        int audioPts = 0;
    
        //释放资源
        void releaseRes();
        
        //添加流,并设置参数
        int addVideoStream();
        int addAudioStream();
    
        //编解码视频流
        int decodeVideo(AVPacket *pkt);
        int encodeVideo(AVFrame* frame);
    
        //编解码音频流
        int decodeAudio(AVPacket* pkt);
        int encodeAudio(AVFrame* frame);
    };
    
    
    基础模块

    组合视频流与音频流那篇文章中的代码实现流程一致,只是从读取源数据变为了从源媒体文件中读取数据;参数的设置从手动设置全部必要参数变为了拷贝参数+手动修改部分参数

    //这里与组合视频流与音频流的代码很相似
    void TranscodeHandler::doTranscode() {
        int ret;
        do {
            //打开本地文件
            ret = avformat_open_input(&srcFmtCtx, srcFile, NULL, NULL);
            if (ret < 0) {
                cout << "Could not open input" << endl;
                break;
            }
    
            //获取多媒体文件信息
            ret = avformat_find_stream_info(srcFmtCtx, NULL);
            if (ret < 0) {
                cout << "Could not find stream info" << endl;
                break;
            }
    
            av_dump_format(srcFmtCtx, 0, srcFile, 0);
    
            //创建输出结构上下文 AVFormatContext
            ret = avformat_alloc_output_context2(&dstFmtCtx, NULL, NULL, dstFile);
            if (ret < 0) {
                cout << "Could not create output context" << endl;
                break;
            }
    
            //打开文件
            ret = avio_open(&dstFmtCtx->pb, dstFile, AVIO_FLAG_READ_WRITE);
            if (ret < 0) {
                cout << "Could not open output file" << endl;
                break;
            }
    
            //添加一个视频流
            ret = addVideoStream();
            if (ret < 0) {
                cout << "Add video stream fail" << endl;
                break;
            }
    
            //添加一个音频流
            ret = addAudioStream();
            if (ret < 0) {
                cout << "Add audio stream fail" << endl;
                break;
            }
    
            //写入文件头信息
            ret=avformat_write_header(dstFmtCtx,NULL);
            if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
                cout << "Write file header fail" << endl;
                break;
            }
    
            av_dump_format(dstFmtCtx,0,dstFile,1);
    
            //申请视频流使用的Frame
            srcVideoFrame = av_frame_alloc();
            dstVideoFrame = av_frame_alloc();
            //为av_frame_get_buffer设置必要的参数
            dstVideoFrame->width = dstVideoCodecCtx->width;
            dstVideoFrame->height = dstVideoCodecCtx->height;
            dstVideoFrame->format = dstVideoCodecCtx->pix_fmt;
    
            //获取Frame的数据缓冲区
            ret = av_frame_get_buffer(dstVideoFrame, 0);
            if (ret < 0) {
                cout << "video av_frame_get_buffer fail" << endl;
                break;
            }
            
            //判断是否需要进行转换
            if (srcVideoCodecCtx->width != dstVideoCodecCtx->width ||
                srcVideoCodecCtx->height != dstVideoCodecCtx->height ||
                srcVideoCodecCtx->pix_fmt != dstVideoCodecCtx->pix_fmt) {
                videoSwsCtx = sws_getContext(
                    srcVideoCodecCtx->width, srcVideoCodecCtx->height, srcVideoCodecCtx->pix_fmt,
                    dstVideoCodecCtx->width, dstVideoCodecCtx->height, dstVideoCodecCtx->pix_fmt,
                    SWS_BILINEAR,NULL,NULL,NULL
                    );
                if (videoSwsCtx==NULL) {
                    cout << "Get SwsContext fail" << endl;
                    break;
                }
            }
    
            //申请音频流使用的Frame
            srcAudioFrame = av_frame_alloc();
            dstAudioFrame = av_frame_alloc();
    
            //用于判断是否需要进行转换的参数
            //为av_frame_get_buffer设置必要的参数,nb_samples、channel_layout、format
            dstAudioFrame->nb_samples = dstAudioCodecCtx->frame_size;
            dstAudioFrame->channel_layout = dstAudioCodecCtx->channel_layout;
            dstAudioFrame->format = dstAudioCodecCtx->sample_fmt;
            
            dstAudioFrame->channels = dstAudioCodecCtx->channels;
            dstAudioFrame->sample_rate = dstAudioCodecCtx->sample_rate;
            
            //获取Frame的数据缓冲区
            ret = av_frame_get_buffer(dstAudioFrame, 0);
            if (ret < 0) {
                cout << "audio av_frame_get_buffer fail" << endl;
                break;
            }
    
            //判断是否需要进行转换
            if (dstAudioCodecCtx->sample_fmt != srcAudioCodecCtx->sample_fmt ||
                dstAudioCodecCtx->channel_layout != srcAudioCodecCtx->channel_layout||
                dstAudioCodecCtx->sample_rate != srcAudioCodecCtx->sample_rate||
                dstAudioCodecCtx->frame_size != srcAudioCodecCtx->frame_size||
                dstAudioCodecCtx->channels != srcAudioCodecCtx->channels) {
                audioSwrCtx = swr_alloc_set_opts(NULL,
                    dstAudioCodecCtx->channel_layout,dstAudioCodecCtx->sample_fmt,dstAudioCodecCtx->sample_rate,
                    srcAudioCodecCtx->channel_layout,srcAudioCodecCtx->sample_fmt,srcAudioCodecCtx->sample_rate,
                    0,NULL);
                if (audioSwrCtx == NULL) {
                    cout << "Get SwrContext fail" << endl;
                    break;
                }
            }
    
            //从源文件中读取数据
            while (av_read_frame(srcFmtCtx,pkt)>=0) {
                //判断属于哪个流
                if (pkt->stream_index==srcVideoStream->index) {
                    //解码视频流
                    decodeVideo(pkt);
                }else if (pkt->stream_index == srcAudioStream->index) {
                    //解码视频流
                    decodeAudio(pkt);
                }
            }
            //刷新缓冲(视频)
            decodeVideo(NULL);
            encodeVideo(NULL);
            
            //刷新缓冲(音频)
            decodeAudio(NULL);
            if (audioSwrCtx) {
                while (swr_convert_frame(audioSwrCtx, dstAudioFrame, NULL) >= 0) {
                    if (dstAudioFrame->nb_samples == 0) break;
                    
                    dstAudioFrame->pts = audioPts;
                    audioPts += dstAudioFrame->nb_samples;
                    encodeAudio(dstAudioFrame);
                }
            }
            encodeAudio(NULL);
    
            //向文件中写入文件尾部标识,并释放该文件
            av_write_trailer(dstFmtCtx);
        } while (0);
    
        //释放资源
        releaseRes();
    }
    
    //释放相关的资源
    void TranscodeHandler::releaseRes() {
        if (srcFmtCtx) avformat_close_input(&srcFmtCtx);
        if (dstFmtCtx) {
            avformat_free_context(dstFmtCtx);
            avio_close(dstFmtCtx->pb);
            dstFmtCtx = NULL;
        }
    
        if (srcVideoCodecCtx) avcodec_free_context(&srcVideoCodecCtx);
        if (srcAudioCodecCtx) avcodec_free_context(&srcAudioCodecCtx);
        if (dstVideoCodecCtx) avcodec_free_context(&dstVideoCodecCtx);
        if (dstAudioCodecCtx) avcodec_free_context(&dstAudioCodecCtx);
    
        if (srcVideoFrame) av_frame_free(&srcVideoFrame);
        if (srcAudioFrame) av_frame_free(&srcAudioFrame);
        if (dstVideoFrame) av_frame_free(&dstVideoFrame);
        if (dstAudioFrame) av_frame_free(&dstAudioFrame);
    
        if (pkt)av_packet_free(&pkt);
    
        if (videoSwsCtx) sws_freeContext(videoSwsCtx);
        if (audioSwrCtx) swr_free(&audioSwrCtx);
    }
    
    视频模块

    视频模块中又分为 初始化解码编码三个部分

    初始化

    初始化模块中主要工作为给输出文件添加视频流,并为之后的视频流解码与编码设置参数

    //返回0表示成功,返回一个负数表示失败
    int TranscodeHandler::addVideoStream() {
        int ret = 0;
        AVCodec* srcCodec = NULL, * dstCodec = NULL;
        do {
            //查找视频流,并根据视频流格式给srcCodec解码器赋值
            ret = av_find_best_stream(srcFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &srcCodec, 0);
            if (ret < 0) {
                cout << "Could not find video stream" << endl;
                break;
            }
            //ret返回的就是找到的视频流的索引
            srcVideoStream = srcFmtCtx->streams[ret];
            AVCodecParameters* srcParam = srcVideoStream->codecpar;
    
            //申请编码器上下文结构体
            srcVideoCodecCtx = avcodec_alloc_context3(srcCodec);
            if (srcVideoCodecCtx == NULL) {
                ret = -1;
                cout << "Could not alloc video context" << endl;
                break;
            }
            
            //将参数传给解码器上下文
            ret = avcodec_parameters_to_context(srcVideoCodecCtx, srcParam);
            if (ret < 0) {
                cout << "Fail copy video parameters to context" << endl;
                break;
            }
    
            //打开解码器
            ret = avcodec_open2(srcVideoCodecCtx, srcCodec, NULL);
            if (ret < 0) {
                printf("cannot open video decoder\n");
                break;
            }
    
            //--------------------------------------------------
    
            //查找编码器
            dstCodec = avcodec_find_encoder(dstFmtCtx->oformat->video_codec);
            if (dstCodec == NULL) {
                ret = -1;
                cout << "Cannot find any video endcoder" << endl;
                break;
            }
    
            //申请编码器上下文结构体
            dstVideoCodecCtx = avcodec_alloc_context3(dstCodec);
            if (dstVideoCodecCtx == NULL) {
                ret = -1;
                cout << "Cannot alloc video dst context" << endl;
                break;
            }
    
            //创建视频流
            dstVideoStream = avformat_new_stream(dstFmtCtx, dstCodec);
            if (dstVideoStream == NULL) {
                ret = -1;
                cout << "Could not new video stream" << endl;
                break;
            }
    
            AVCodecParameters* dstParam = dstVideoStream->codecpar;
            //拷贝输入文件中视频流的参数
            ret = avcodec_parameters_copy(dstParam, srcParam);
            if (ret < 0) {
                cout << "Fail copy video parameters" << endl;
                break;
            }
    
            //可以在这里更改需要修改的参数
            dstParam->codec_id = dstCodec->id;
            dstParam->codec_tag = 0;
    
            //将参数传给解码器上下文
            ret = avcodec_parameters_to_context(dstVideoCodecCtx, dstParam);
            if (ret < 0) {
                cout << "Fail copy video parameters to dst context" << endl;
                break;
            }
    
            dstVideoCodecCtx->time_base = AVRational{ 1, 25 };
            
            if (dstVideoCodecCtx->codec_id == AV_CODEC_ID_H264) {
                av_opt_set(dstVideoCodecCtx->priv_data, "preset", "slow", 0);
            }
    
            if (dstFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) {
                dstVideoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
                dstVideoCodecCtx->flags |= AV_CODEC_FLAG2_LOCAL_HEADER;
            }
    
            //打开解码器
            ret = avcodec_open2(dstVideoCodecCtx, dstCodec, NULL);
            if (ret < 0) {
                printf("cannot open video encoder\n");
                break;
            }
    
            //再将dstVideoCodecCtx设置的参数传给dstParam,用于写入头文件信息
            ret = avcodec_parameters_from_context(dstParam, dstVideoCodecCtx);
            if (ret < 0) {
                cout << "Fail copy video parameters from dst context" << endl;
                break;
            }
        } while (0);
    
        return ret;
    }
    
    解码
    /*
    * 解码输入文件
    * @param pkt:包含数据的Packet,传入NULL表示刷新缓存
    * @return 返回0表示成功,返回一个负数表示失败
    */
    int TranscodeHandler::decodeVideo(AVPacket* pkt) {
        int ret = 0;
        //发送包数据去进行解析获得帧数据
        if (avcodec_send_packet(srcVideoCodecCtx,pkt)>=0) {
            //获取解码器解析后产生的Frame
            while (avcodec_receive_frame(srcVideoCodecCtx,srcVideoFrame)>=0) {
                //是否需要进行转换操作
                if (videoSwsCtx) {
                    ret=sws_scale(videoSwsCtx, srcVideoFrame->data, srcVideoFrame->linesize, 0, srcVideoCodecCtx->height, dstVideoFrame->data, dstVideoFrame->linesize);
                    if (ret < 0) {
                        cout << "Cannot scale frame" << endl;
                        break;
                    }
                }else {
                    //不需要转换直接拷贝Frame中的数据即可
                    ret = av_frame_copy(dstVideoFrame, srcVideoFrame);
                    if (ret < 0) {
                        cout << "Not copy video frame" << endl;
                        break;
                    }
                }
                dstVideoFrame->pts = videoPts++;
    
                //执行编码操作
                ret = encodeVideo(dstVideoFrame);
                if (ret < 0) {
                    cout<<"Do encodeVideo fail"<<endl;
                    break;
                }
            }
            av_packet_unref(pkt);
        }
    
        return ret;
    }
    

    注:不可以直接将输入文件解析出的 Frame 传给 encodeVideo 进行编码,因为 Frame 不仅仅包含数据,还包含了其他用于播放时的参数

    直接使用 srcVideoFrame 导致出现警告 warning, too many B-frames in a row,最终文件没有视频画面

    编码
    /*
    * 编码输出文件
    * @param frame:包含数据的Frame,传入NULL表示刷新缓存
    * @return 返回0表示成功,返回一个负数表示失败
    */
    int TranscodeHandler::encodeVideo(AVFrame* frame) {
        int ret=0;
        //将frame发送至编码器进行编码,codecCtx中保存了codec
        //当frame为NULL时,表示将缓冲区中的数据读取出来
        if (avcodec_send_frame(dstVideoCodecCtx, frame) >= 0) {
            //接收编码后形成的packet
            //查看源码后,会发现该方法会先调用 av_packet_unref,在执行接收操作
            while (avcodec_receive_packet(dstVideoCodecCtx,pkt)>=0) {
                //设置对应的流索引
                pkt->stream_index = dstVideoStream->index;
                pkt->pos = -1;
                //转换pts至基于时间基的pts,可以理解为视频帧显示的时间戳
                av_packet_rescale_ts(pkt, dstVideoCodecCtx->time_base, dstVideoStream->time_base);
                cout << "encoder success:" << pkt->size << endl;
    
                //将包数据写入文件中,该方法不用使用 av_packet_unref
                ret = av_interleaved_write_frame(dstFmtCtx, pkt);
                if (ret < 0) {
                    char errStr[256];
                    av_strerror(ret, errStr, 256);
                    cout << "error is:" << errStr << endl;
                    break;
                }
            }
        }
        return ret;
    }
    
    音频模块

    音频模块与视频模块一样,分为 初始化解码编码三个部分

    初始化

    初始化模块中主要工作为给输出文件添加音频流,并为之后的音频流解码与编码设置参数

    //返回0表示成功,返回一个负数表示失败
    int TranscodeHandler::addAudioStream(){
        int ret;
        AVCodec* srcCodec = NULL, * dstCodec = NULL;
        do {
            //查找音频流,并根据音频流格式给srcCodec解码器赋值
            ret = av_find_best_stream(srcFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, &srcCodec, 0);
            if (ret < 0) {
                cout << "Could not find audio stream" << endl;
                break;
            }
    
            //ret返回的就是找到的视频流的索引
            srcAudioStream = srcFmtCtx->streams[ret];
            AVCodecParameters* srcParam = srcAudioStream->codecpar;
    
            //申请编码器上下文结构体
            srcAudioCodecCtx = avcodec_alloc_context3(srcCodec);
            if (srcAudioCodecCtx == NULL) {
                ret = -1;
                cout << "Could not alloc audio context" << endl;
                break;
            }
    
            //将参数传给解码器上下文
            ret = avcodec_parameters_to_context(srcAudioCodecCtx, srcParam);
            if (ret < 0) {
                cout << "Fail copy audio parameters to context" << endl;
                break;
            }
    
            //打开解码器
            ret = avcodec_open2(srcAudioCodecCtx, srcCodec, NULL);
            if (ret < 0) {
                printf("cannot open audio decoder\n");
                break;
            }
    
            //--------------------------------------------------
    
            //查找编码器
            dstCodec = avcodec_find_encoder(dstFmtCtx->oformat->audio_codec);
            if (dstCodec == NULL) {
                ret = -1;
                cout << "Cannot find any audio endcoder" << endl;
                break;
            }
    
            //申请编码器上下文结构体
            dstAudioCodecCtx = avcodec_alloc_context3(dstCodec);
            if (dstAudioCodecCtx == NULL) {
                ret = -1;
                cout << "Cannot alloc audio dst context" << endl;
                break;
            }
    
            //创建视频流
            dstAudioStream = avformat_new_stream(dstFmtCtx, dstCodec);
            if (dstAudioStream == NULL) {
                ret = -1;
                cout << "Could not new audio stream" << endl;
                break;
            }
    
            AVCodecParameters* dstParam = dstAudioStream->codecpar;
            //拷贝输入文件中视频流的参数
            ret = avcodec_parameters_copy(dstParam, srcParam);
            if (ret < 0) {
                cout << "Fail copy audio parameters" << endl;
                break;
            }
    
            //可以在这里更改需要修改的参数
            dstParam->codec_id = dstCodec->id;
            dstParam->codec_tag = 0;
    
            //将参数传给解码器上下文
            ret = avcodec_parameters_to_context(dstAudioCodecCtx, dstParam);
            if (ret < 0) {
                cout << "Fail copy video parameters to dst context" << endl;
                break;
            }
    
            //打开解码器
            ret = avcodec_open2(dstAudioCodecCtx, dstCodec, NULL);
            if (ret < 0) {
                printf("cannot open audio encoder\n");
                break;
            }
    
            //再将dstVideoCodecCtx设置的参数传给dstParam,用于写入头文件信息
            ret = avcodec_parameters_from_context(dstParam, dstAudioCodecCtx);
            if (ret < 0) {
                cout << "Fail copy video parameters from dst context" << endl;
                break;
            }
        } while (0);
    
        return ret;
    }
    
    解码
    /*
    * 解码输入文件
    * @param pkt:包含数据的Packet,传入NULL表示刷新缓存
    * @return 返回0表示成功,返回一个负数表示失败
    */
    int TranscodeHandler::decodeAudio(AVPacket* pkt) {
        int ret = 0;
        //发送包数据去进行解析获得帧数据
        if (avcodec_send_packet(srcAudioCodecCtx,pkt)>=0) {
            //获取解码器解析后产生的Frame
            while (avcodec_receive_frame(srcAudioCodecCtx,srcAudioFrame)>=0) {
                //是否需要进行转换操作
                if (audioSwrCtx) {
                    ret = swr_convert_frame(audioSwrCtx, dstAudioFrame, srcAudioFrame);
                    if (ret<0) {
                        cout << "Cannot convert frame" << endl;
                        break;
                    }
                }else {
                    //不需要转换直接拷贝Frame中的数据即可
                    ret = av_frame_copy(dstAudioFrame,srcAudioFrame);
                    if (ret<0) {
                        cout << "Not copy audio frame" << endl;
                        break;
                    }
                }
                dstAudioFrame->pts = audioPts;
                audioPts += dstAudioFrame->nb_samples;
    
                //执行编码操作
                ret=encodeAudio(dstAudioFrame);
                if (ret < 0) {
                    cout << "Do encodeAudio fail" << endl;
                    break;
                }
            }
            av_packet_unref(pkt);
        }
    
        return ret;
    }
    
    编码
    /*
    * 编码输出文件
    * @param frame:包含数据的Frame,传入NULL表示刷新缓存
    * @return 返回0表示成功,返回一个负数表示失败
    */
    int TranscodeHandler::encodeAudio(AVFrame* frame){
        int ret = 0;
        //将frame发送至编码器进行编码,codecCtx中保存了codec
        //当frame为NULL时,表示将缓冲区中的数据读取出来
        if (avcodec_send_frame(dstAudioCodecCtx,frame)>=0) {
            //接收编码后形成的packet
            //查看源码后,会发现该方法会先调用 av_packet_unref,在执行接收操作
            while (avcodec_receive_packet(dstAudioCodecCtx,pkt)>=0) {
                //设置对应的流索引
                pkt->stream_index = dstAudioStream->index;
                pkt->pos = -1;
    
                //转换pts至基于时间基的pts
                av_packet_rescale_ts(pkt,dstAudioCodecCtx->time_base,dstAudioStream->time_base);
    
                cout << "encoder audio success pts:" << pkt->pts << endl;
    
                //将包数据写入文件中,该方法不用使用 av_packet_unref
                ret = av_interleaved_write_frame(dstFmtCtx, pkt);
                if (ret < 0) {
                    char errStr[256];
                    av_strerror(ret, errStr, 256);
                    cout << "error is:" << errStr << endl;
                    break;
                }
            }
        }
    
        return ret;
    }
    

    参考资料

    https://www.jianshu.com/p/97e0ed41d921

    相关文章

      网友评论

        本文标题:FFmpeg小白学习记录(六)视频格式转换流程

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