视频格式转换流程
视频格式转换主要分为两种类型:转封装和转码
- 转封装:多媒体文件是一个容器,转封装相当于容器内的物品不变只是换了一个容器,其内容不会发生改变
- 转码:转码就是将流中的数据根据要转换的格式进行转换,可以根据需求更改数据内容
转封装
音视频封装指的是将编码后的数据放入具有一定规则的容器文件中,比如MP4文件,MOV文件,MP3文件等等。容器文件和编码方法是两个不同的概念,容器文件中可以支持多种编码方式,一种编码方式可以存放在不同的容器文件中,在转封装前需要查看对应的容器文件是否支持当前的编码方式
其大致的流程就是将输入文件解码和输出文件编码,只不过因为不涉及数据格式转换的操作,只需要执行到 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,
};
转码
源容器格式的音/视频编码方式在目标容器不被支持,也就无法使用转封装方式进行转换,此时就需要先解码再编码实现视频格式转换,即转码
如果需要改变数据内容,如音视频码率、视频分辨率等,那么也需要使用转码的方式,其实 转码 = 解码 + 编码,不过要注意编码格式是否为有损压缩,可能会导致视频画面模糊
因为代码量较多且逻辑较为复杂,这里选择通过类的方式实现并划分为各个模块进行讲解:基础模块、视频模块、音频模块
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;
}