草庐IT

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

oddly 2023-10-07 原文

视频格式转换流程

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

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

转封装

音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如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小白学习记录(六)视频格式转换流程的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  4. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  5. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  6. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  7. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  8. ruby-on-rails - Ruby url 到 html 链接转换 - 2

    我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.

  9. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  10. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

随机推荐