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

视频格式转换流程

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

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

转封装

音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容