在经过一周的学习后,现将其稍加总结下:附代码及详细注释。
看下图,先搞明白 视频编码的流程。(图源于雷神)
图下紧接着每个步骤的中文含义
1、av_register_all():注册FFmpeg所有编解码器。
2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
3、avio_open():打开输出文件。
4、av_new_stream():创建输出码流的AVStream。
5、avcodec_find_encoder():查找编码器。
6、avcodec_open2():打开编码器。
7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
8、不停地从码流中提取出YUV数据,进行编码。
avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
av_write_frame():将编码后的视频码流写入文件。
9、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。
10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
带着流程看代码,你会感谢我哦 (下面代码源于雷神,加了些中文注释,方便学习)
/**
*************** FFMPEG视频编码流程 *******************
* 01、av_register_all():注册FFmpeg所有编解码器;
* 02、avformat_alloc_output_context2():初始化输出码流的AVFormatContext;
* 03、avio_open():打开输出文件;
* 04、av_new_stream():创建输出码流的AVStream;
* 05、avcodec_find_encoder():查找编码器;
* 06、avcodec_open2():打开编码器;
* 07、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);
* 08、不停地从码流中提取出YUV数据,进行编码;
* avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据);
* av_write_frame():将编码后的视频码流写入文件;
* 09、flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket;
* 10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS);
*/
#include
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#ifdef __cplusplus
};
#endif
#endif
// 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
//编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AVFormatContext* pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
AVOutputFormat* fmt; // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。
AVStream* video_st; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext* pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec* pCodec; // AVCodec是存储编解码器信息的结构体。
AVPacket pkt; // AVPacket是存储压缩编码数据相关信息的结构体
uint8_t* picture_buf;
AVFrame* pFrame; // AVFrame是包含码流参数较多的结构体
int picture_size;
int y_size;
int framecnt=0;
//FILE *in_file = fopen("src01_480x272.yuv", "rb"); // 输入原始YUV数据
FILE *in_file = fopen("../ds_480x272.yuv", "rb"); // 输入原始YUV数据
int in_w=480,in_h=272; // 输入数据的宽度和高度
int framenum=100; // 要编码的帧
//const char* out_file = "src01.h264"; // 输出文件路径
//const char* out_file = "src01.ts";
//const char* out_file = "src01.hevc";
const char* out_file = "ds.h264";
av_register_all(); // 注册ffmpeg所有编解码器
//方法1.
pFormatCtx = avformat_alloc_context(); // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化
//Guess Format
fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。
pFormatCtx->oformat = fmt;
//方法2.
//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file); // 初始化输出码流的AVFormatContext
//fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
// avio_open 打开输出文件
printf("Failed to open output file! \n");
return -1;
}
video_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream
video_st->time_base.num = 1; // num 分子
video_st->time_base.den = 25; // den 分母
if (video_st==NULL){
return -1;
}
// 必须设置的参数
pCodecCtx = video_st->codec;
//pCodecCtx->codec_id =AV_CODEC_ID_HEVC;
pCodecCtx->codec_id = fmt->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;
pCodecCtx->bit_rate = 400000;
pCodecCtx->gop_size=250;
//H264
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
// 可选参数
pCodecCtx->max_b_frames=3;
// 设置选项
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
//av_dict_set(¶m, "profile", "main", 0);
}
//H.265
if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
av_dict_set(¶m, "preset", "ultrafast", 0);
av_dict_set(¶m, "tune", "zero-latency", 0);
}
//Show some Information
av_dump_format(pFormatCtx, 0, out_file, 1); // av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
pCodec = avcodec_find_encoder(pCodecCtx->codec_id); // 查找编码器
if (!pCodec){
printf("Can not find encoder! \n");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){
// 打开编码器
printf("Failed to open encoder! \n");
return -1;
}
pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); //计算这个格式的图片,需要多少字节来存储
picture_buf = (uint8_t *)av_malloc(picture_size);
// 这个函数是为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
// 写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,picture_size); // 分配数据包的有效size并初始化
y_size = pCodecCtx->width * pCodecCtx->height;
// 一帧一帧循环操作
for (int i=0; i<framenum; i++){
// Read raw YUV data
if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){
// fread函数,从文件流中读取数据,如果不成功或读到文件末尾返回 0
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){
// 判断文件是否结束
break;
}
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size*5/4; // V
// PTS
pFrame->pts=i; // pts : 以时间为基本单位的表示时间戳(应该向用户显示帧的时间)。
int got_picture=0;
// 编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。
// 成功时返回0,失败时返回负错误代码 失败时返回错误返回码
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! \n");
return -1;
}
if (got_picture==1){
printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的视频码流写入文件,
av_free_packet(&pkt); // free
}
}
// Flush Encoder
int ret = flush_encoder(pFormatCtx,0); // 输入的像素数据读取完成后调用此函数,用于输出编码器中剩余的AVPacket
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
// 写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)
av_write_trailer(pFormatCtx);
// Clean
if (video_st){
avcodec_close(video_st->codec);
av_free(pFrame);
av_free(picture_buf);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
老规矩,看一下 视频解码 的流程图
图下紧接着每个步骤的中文含义
1、av_register_all():注册所有组件。
2、avformat_open_input():打开输入视频文件。
3、avformat_find_stream_info():获取视频文件信息
4、avcodec_find_decoder():查找解码器。
5、avcodec_open2():打开解码器。
6、av_read_frame():从输入文件读取一帧压缩数据。
7、avcodec_decode_video2():解码一帧压缩数据。
8、avcodec_close():关闭解码器。
9、avformat_close_input():关闭输入视频文件。
建议带着流程看代码
/**
* FFMPEG视频解码流程
* 1、av_register_all():注册所有组件。
* 2、avformat_open_input():打开输入视频文件。
* 3、avformat_find_stream_info():获取视频文件信息
* 4、avcodec_find_decoder():查找解码器。
* 5、avcodec_open2():打开解码器。
* 6、av_read_frame():从输入文件读取一帧压缩数据。
* 7、avcodec_decode_video2():解码一帧压缩数据。
* 8、avcodec_close():关闭解码器。
* 9、avformat_close_input():关闭输入视频文件。
*/
#include "stdafx.h"
#include
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#include
#ifdef __cplusplus
};
#endif
#endif
int main()
{
//文件格式上下文
AVFormatContext *pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
int i = 0, videoindex;
AVCodecContext *pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec *pCodec; // AVCodec是存储编解码器信息的结构体。
AVFrame *pFrame, *pFrameYUV; // AVFrame是包含码流参数较多的结构体
unsigned char *out_buffer;
AVPacket *packet; // AVPacket是存储压缩编码数据相关信息的结构体
int y_size;
int ret, got_picture;
// struct SwsContext结构体位于libswscale类库中, 该类库主要用于处理图片像素数据, 可以完成图片像素格式的转换, 图片的拉伸等工作.
struct SwsContext *img_convert_ctx;
char filepath[] = "input.mkv";
FILE *fp_yuv = fopen("output.yuv", "wb+");
av_register_all(); // 注册所有组件
avformat_network_init(); // 对网络库进行全局初始化。
pFormatCtx = avformat_alloc_context(); // 初始化AVFormatContext结构体指针。使用avformat_free_context()释放内存。
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) // 打开输入流并读取header。必须使用avformat_close_input()接口关闭。
{
printf("Couldn't open input stream.\n");
return -1;
}
//读取一部分视音频数据并且获得一些相关的信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) // 读取媒体文件的包以获取流信息
{
printf("Couldn't find stream information.\n");
return -1;
}
//查找视频编码索引
videoindex = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoindex = i;
break;
}
}
if (videoindex == -1)
{
printf("Didn't find a video stream.\n");
return -1;
}
//编解码上下文
pCodecCtx = pFormatCtx->streams[videoindex]->codec;
//查找解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); // 查找符合ID的已注册解码器
if (pCodec == NULL)
{
printf("Codec not found.\n");
return -1;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("Could not open codec.\n");
return -1;
}
//申请AVFrame,用于原始视频
pFrame = av_frame_alloc();
//申请AVFrame,用于yuv视频
pFrameYUV = av_frame_alloc();
//分配内存,用于图像格式转换
out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));
// 根据指定的图像参数和提供的数组设置参数指针和linesize大小
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("--------------- File Information ----------------\n");
//手工调试函数,输出tbn、tbc、tbr、PAR、DAR的含义
av_dump_format(pFormatCtx, 0, filepath, 0);
printf("-------------------------------------------------\n");
//申请转换上下文。 sws_getContext功能:初始化 SwsContext 结构体指针
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
//读取数据
while (av_read_frame(pFormatCtx, packet) >= 0) // 读取码流中的音频若干帧或者视频一帧
{
if (packet->stream_index == videoindex)
{
// avcodec_decode_video2 功能:解码一帧视频数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.\n");
return -1;
}
if (got_picture >= 1)
{
//成功解码一帧
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式
y_size = pCodecCtx->width*pCodecCtx->height;
// fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。
fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V
printf("Succeed to decode 1 frame!\n");
}
else
{
//未解码到一帧,可能时结尾B帧或延迟帧,在后面做flush decoder处理
}
}
av_free_packet(packet); // free
}
//flush decoder
//FIX: Flush Frames remained in Codec
while (true)
{
if (!(pCodec->capabilities & CODEC_CAP_DELAY))
return 0;
// avcodec_decode_video2 功能:解码一帧视频数据
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if (ret < 0)
{
break;
}
if (!got_picture)
{
break;
}
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); // 转换图像格式
int y_size = pCodecCtx->width*pCodecCtx->height;
// fwrite 功能:把 pFrameYUV 所指向数据写入到 fp_yuv 中。
fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V
printf("Flush Decoder: Succeed to decode 1 frame!\n");
}
sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
fclose(fp_yuv);
return 0;
}
解码后YUV格式的视频像素数据保存在 AVFrame 的 data[0] 、data[1] 、data[2] 中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。以亮度Y数据为例,data[0]中一共包含了 linesize[0]*height 个数据。但是出于优化等方面的考虑,linesize[0] 实际上并不等于宽度 width ,而是一个比宽度大一些的值。因此需要用 sws_scale() 进行转换。转换后去除了无效数据,width 和 linesize[0] 取值相等。
如图:
老规矩,看一下 音频编码 的流程图。 (图源于雷神)
图下紧接着每个步骤的中文含义
1、av_register_all():注册FFmpeg所有编解码器。
2、avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
3、avio_open():打开输出文件。
4、av_new_stream():创建输出码流的AVStream。
5、avcodec_find_encoder():查找编码器。
6、avcodec_open2():打开编码器。
7、avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比 如说MPEG2TS)。
8、avcodec_encode_audio2():编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。
9、av_write_frame():将编码后的视频码流写入文件。
10、av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
建议带着流程图看代码
本程序实现了音频PCM采样数据编码为压缩码流(MP3,WMA,AAC等)。
#include
#include "audio_encoder.h"
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#ifdef __cplusplus
};
#endif
#endif
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
int main(int argc, char* argv[])
{
AudioEncoder audioEncoder("tdjm.pcm","tdjm.aac");
audioEncoder.encode_now();
AVFormatContext* pFormatCtx; // 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息。
AVOutputFormat* fmt; // AVOutputFormat 结构体主要用于muxer,是音视频文件的一个封装器。
AVStream* audio_st; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext* pCodecCtx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec* pCodec; // AVCodec是存储编解码器信息的结构体。
uint8_t* frame_buf;
AVFrame* pFrame; // AVFrame是包含码流参数较多的结构体
AVPacket pkt; // AVPacket是存储压缩编码数据相关信息的结构体
int got_frame=0;
int ret=0;
int size=0;
FILE *in_file=NULL; //Raw PCM data
int framenum=1000; //Audio frame number
const char* out_file = "tdjm.aac"; //Output URL
int i;
in_file= fopen("tdjm.pcm", "rb");
av_register_all(); // 注册ffmpeg所有编解码器
//Method 1.
pFormatCtx = avformat_alloc_context(); // 初始化 pFormatCtx。 AVFormatContext 用 avformat_alloc_context() 进行初始化
fmt = av_guess_format(NULL, out_file, NULL); // av_guess_format 这是一个决定视频输出时封装方式的函数,其中有三个参数,写任何一个参数,都会自动匹配相应的封装方式。
pFormatCtx->oformat = fmt;
//Method 2.
//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
//fmt = pFormatCtx->oformat;
//Open output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
// avio_open 打开输出文件
printf("Failed to open output file!\n");
return -1;
}
audio_st = avformat_new_stream(pFormatCtx, 0); // 创建输出码流的AVStream
if (audio_st==NULL){
return -1;
}
// 必须设置的参数
pCodecCtx = audio_st->codec;
pCodecCtx->codec_id = fmt->audio_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
pCodecCtx->sample_rate= 44100;
pCodecCtx->channel_layout=AV_CH_LAYOUT_STEREO;
pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
pCodecCtx->bit_rate = 64000;
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(pFormatCtx, 0, out_file, 1);
// 查找编码器
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
printf("Can not find encoder!\n");
return -1;
}
// 打开编码器
if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0){
printf("Failed to open encoder!\n");
return -1;
}
pFrame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
pFrame->nb_samples= pCodecCtx->frame_size; // 此帧描述的音频采样数(每个通道)
pFrame->format= pCodecCtx->sample_fmt; // 帧的格式,如果未知或未设置,则为-1
// 获取给定音频参数所需的缓冲区大小。
size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,pCodecCtx->frame_size,pCodecCtx->sample_fmt, 1);
frame_buf = (uint8_t *)av_malloc(size);
// 填充AVFrame音频数据和linesize指针。
avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt,(const uint8_t*)frame_buf, size, 1);
// 写文件头
avformat_write_header(pFormatCtx,NULL);
av_new_packet(&pkt,size); // 分配有效size并初始化
for (i=0; i<framenum; i++){
//读取 PCM 数据
if (fread(frame_buf, 1, size, in_file) <= 0){
printf("Failed to read raw data! \n");
return -1;
}else if(feof(in_file)){
break;
}
pFrame->data[0] = frame_buf; //PCM Data
pFrame->pts=i*100;
got_frame=0;
// 编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。
ret = avcodec_encode_audio2(pCodecCtx, &pkt,pFrame, &got_frame);
if(ret < 0){
printf("Failed to encode!\n");
return -1;
}
if (got_frame==1){
printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);
pkt.stream_index = audio_st->index;
ret = av_write_frame(pFormatCtx, &pkt); // 将编码后的音频数据写入文件
av_free_packet(&pkt);
}
}
// 用于输出编码器中剩余的AVPacket
ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
return -1;
}
// 写文件尾
av_write_trailer(pFormatCtx);
// Clean
if (audio_st){
avcodec_close(audio_st->codec);
av_free(pFrame);
av_free(frame_buf);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
fclose(in_file);
return 0;
}
待学习。
老规矩,再一下编程角度 转码 的流程图。 (图源于雷神)
图下紧接着每个步骤的中文含义
1、open_input_file():打开输入文件,并初始化相关的结构体。
2、open_output_file():打开输出文件,并初始化相关的结构体。
3、init_filters():初始化AVFilter相关的结构体。
4、av_read_frame():从输入文件中读取一个AVPacket。
5、avcodec_decode_video2():解码一个视频AVPacket(存储H.264等压缩码流数据)为AVFrame(存储YUV等非压缩的像素数据)。
6、avcodec_decode_video4():解码一个音频AVPacket(存储MP3等压缩码流数据)为AVFrame(存储PCM采样数据)。
7、filter_encode_write_frame():编码一个AVFrame。
8、flush_encoder():输入文件读取完毕后,输出编码器中剩余的AVPacket。
建议带着流程 看代码
#include "stdafx.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfiltergraph.h"
#include "libavfilter/avcodec.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
};
static AVFormatContext *ifmt_ctx;
static AVFormatContext *ofmt_ctx;
typedef struct FilteringContext {
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
} FilteringContext;
static FilteringContext *filter_ctx;
// 打开输入文件
static int open_input_file(const char *filename)
{
int ret;
unsigned int i;
ifmt_ctx = NULL;
// 打开多媒体数据并且获得一些相关的信息
if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
return ret;
}
// 获取视频流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
return ret;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *stream; // AVStream是存储每一个视频/音频流信息的结构体。
AVCodecContext *codec_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
stream = ifmt_ctx->streams[i];
codec_ctx = stream->codec;
// 重新编码视频、音频和字幕等
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
/* Open decoder */
ret = avcodec_open2(codec_ctx,
avcodec_find_decoder(codec_ctx->codec_id), NULL); // avcodec_open2 该函数用于初始化一个视音频编解码器的AVCodecContext
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
return ret;
}
}
}
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(ifmt_ctx, 0, filename, 0);
return 0;
}
// 打开输出文件
static int open_output_file(const char *filename)
{
AVStream *out_stream; // AVStream是存储每一个视频/音频流信息的结构体。
AVStream *in_stream;
AVCodecContext *dec_ctx, *enc_ctx; // 编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec *encoder; // AVCodec是存储编解码器信息的结构体。
int ret;
unsigned int i;
ofmt_ctx = NULL;
// avformat_alloc_output_context2 负责分配输出 AVFormatContext。
// ffmpeg有各种各样的 Context ,其功能是管理各种各样的模块。
// 例如有一个输出文件:test.mp4,使用 avformat_alloc_output_context2 函数就可以根据文件名分配合适的 AVFormatContext 管理结构。
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
if (!ofmt_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n"); // 无法创建输出上下文
return AVERROR_UNKNOWN;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
out_stream = avformat_new_stream(ofmt_ctx, NULL); // 创建输出码流的AVStream
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n"); // 分配输出流失败
return AVERROR_UNKNOWN;
}
in_stream = ifmt_ctx->streams[i];
dec_ctx = in_stream->codec;
enc_ctx = out_stream->codec;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
//在这个例子中,我们选择转码到同一个编解码器
encoder = avcodec_find_encoder(dec_ctx->codec_id); // 查找编码器
// 在本例中,我们将代码转换为相同的属性(图片大小, 采样率等)。
// 可以为输出更改这些属性使用过滤器轻松地进行流式处理
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
// 解码类型为视频解码
enc_ctx->height = dec_ctx->height; // 高
enc_ctx->width = dec_ctx->width; // 宽
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 长宽比
// 从支持的格式列表中获取第一种格式
enc_ctx->pix_fmt = encoder->pix_fmts[0];
// 视频时间可以设置为任何方便和编码器支持
enc_ctx->time_base = dec_ctx->time_base;
} else {
// 音频解码
enc_ctx->sample_rate = dec_ctx->sample_rate; // 每秒采样数
enc_ctx->channel_layout = dec_ctx->channel_layout; // 音频通道布局。
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 音频通道数
// 从支持的格式列表中获取第一种格式
enc_ctx->sample_fmt = encoder->sample_fmts[0];
AVRational time_base={
1, enc_ctx->sample_rate};
enc_ctx->time_base = time_base;
}
// 第三个参数可用于将设置传递给编码器
ret = avcodec_open2(enc_ctx, encoder, NULL); // 打开编码器
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); // 无法打开流的视频编码器
return ret;
}
} else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN) {
av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\n", i); // 基本流的类型未知,无法继续
return AVERROR_INVALIDDATA;
} else {
// 如果这个流必须被重新计算
ret = avcodec_copy_context(ofmt_ctx->streams[i]->codec,
ifmt_ctx->streams[i]->codec); // avcodec_copy_context :编码参数上下文的拷贝
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Copying stream context failed\n"); // 拷贝流上下文失败
return ret;
}
}
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
// av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
av_dump_format(ofmt_ctx, 0, filename, 1);
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); // 该函数用于打开FFmpeg输出文件
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename); // 无法打开输出文件
return ret;
}
}
// 初始化muxer,写入输出文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n"); // 打开输出文件时出错
return ret;
}
return 0;
}
// 初始化AVFilter相关的结构体。
static int init_filter(FilteringContext* fctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, const char *filter_spec)
{
char args[512];
int ret = 0;
AVFilter *buffersrc = NULL;
AVFilter *buffersink = NULL;
AVFilterContext *buffersrc_ctx = NULL;
AVFilterContext *buffersink_ctx = NULL;
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVFilterGraph *filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph) {
ret = AVERROR(ENOMEM);
goto end;
}
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
// 视频
buffersrc = avfilter_get_by_name("buffer");
buffersink = avfilter_get_by_name("buffersink");
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); // 未找到筛选源或基本元素
ret = AVERROR_UNKNOWN;
goto end;
}
_snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den);
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); // 无法创建缓冲区源
goto end;
}
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); // 无法创建缓冲区接收器
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "pix_fmts",
(uint8_t*)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); // 无法设置输出像素格式
goto end;
}
} else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
// 音频
buffersrc = avfilter_get_by_name("abuffer");
buffersink = avfilter_get_by_name("abuffersink");
if (!buffersrc || !buffersink) {
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n"); // 未找到筛选源或基本元素
ret = AVERROR_UNKNOWN;
goto end;
}
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout =
av_get_default_channel_layout(dec_ctx->channels); // 音频通道布局
_snprintf(args, sizeof(args),
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%I64x",
dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt),
dec_ctx->channel_layout);
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n"); // 无法创建音频缓冲区源
goto end;
}
// 创建过滤器实例并将其添加到现有的图形中
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n"); // 无法创建音频缓冲区接收器
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",
(uint8_t*)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n"); // 无法设置输出样本格式
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "channel_layouts",
(uint8_t*)&enc_ctx->channel_layout,
sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n"); // 无法设置输出通道布局
goto end;
}
// 用来设置AVOption
ret = av_opt_set_bin(buffersink_ctx, "sample_rates",
(uint8_t*)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n"); // 无法设置输出采样率
goto end;
}
} else {
ret = AVERROR_UNKNOWN;
goto end;
}
/* Endpoints for the filter graph. */
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if (!outputs->name || !inputs->name) {
ret = AVERROR(ENOMEM);
goto end;
}
// 将由字符串描述的图形添加到图形中。
if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec,
&inputs, &outputs, NULL)) < 0)
goto end;
// 检查有效性并配置图中的所有链接和格式。
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
goto end;
// 给 fctx 进行填充
fctx->buffersrc_ctx = buffersrc_ctx;
fctx->buffersink_ctx = buffersink_ctx;
fctx->filter_graph = filter_graph;
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
static int init_filters(void)
{
const char *filter_spec;
unsigned int i;
int ret;
filter_ctx = (FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx));
if (!filter_ctx)
return AVERROR(ENOMEM);
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
filter_ctx[i].buffersrc_ctx = NULL;
filter_ctx[i].buffersink_ctx = NULL;
filter_ctx[i].filter_graph = NULL;
if (!(ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO
|| ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO))
continue;
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
filter_spec = "null"; /* 视频直通(虚拟)滤波器 */
else
filter_spec = "anull"; /* 视频直通(虚拟)滤波器 */
ret = init_filter(&filter_ctx[i], ifmt_ctx->streams[i]->codec,
ofmt_ctx->streams[i]->codec, filter_spec);
if (ret)
return ret;
}
return 0;
}
// 编码写入帧
static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) {
int ret;
int got_frame_local;
AVPacket enc_pkt;
int (*enc_func)(AVCodecContext *, AVPacket *, const AVFrame *, int *) =
(ifmt_ctx->streams[stream_index]->codec->codec_type ==
AVMEDIA_TYPE_VIDEO) ? avcodec_encode_video2 : avcodec_encode_audio2;
if (!got_frame)
got_frame = &got_frame_local;
av_log(NULL, AV_LOG_INFO, "Encoding frame\n");
// 编码过滤帧
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt); // 用默认值初始化数据包的可选字段。
ret = enc_func(ofmt_ctx->streams[stream_index]->codec, &enc_pkt,
filt_frame, got_frame);
av_frame_free(&filt_frame); // 释放file_frame
if (ret < 0)
return ret;
if (!(*got_frame))
return 0;
// 准备 enc_pkt
enc_pkt.stream_index = stream_index;
// av_rescale_q_rnd 将64位整数重缩放为2个具有指定舍入的有理数。
enc_pkt.dts = av_rescale_q_rnd(enc_pkt.dts,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.pts = av_rescale_q_rnd(enc_pkt.pts,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
enc_pkt.duration = av_rescale_q(enc_pkt.duration,
ofmt_ctx->streams[stream_index]->codec->time_base,
ofmt_ctx->streams[stream_index]->time_base);
av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n");
/* mux encoded frame */
ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt); // 将数据包写入输出媒体文件,以确保正确的交织。
return ret;
}
// 过滤编码写入帧
static int filter_encode_write_frame(AVFrame *frame, unsigned int stream_index)
{
int ret;
AVFrame *filt_frame;
av_log(NULL, AV_LOG_INFO, "Pushing decoded frame to filters\n"); // 将解码帧推送到过滤器
/* 将解码后的帧推入 filtergraph */
ret = av_buffersrc_add_frame_flags(filter_ctx[stream_index].buffersrc_ctx,
frame, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
return ret;
}
/* 从 filtergraph 中拉出过滤的帧 */
while (1) {
filt_frame = av_frame_alloc(); // 初始化 filt_frame
if (!filt_frame) {
ret = AVERROR(ENOMEM);
break;
}
av_log(NULL, AV_LOG_INFO, "Pulling filtered frame from filters\n");
// av_buffersink_get_frame 从接收器获取一个带有过滤数据的帧,并将其放入帧中
ret = av_buffersink_get_frame(filter_ctx[stream_index].buffersink_ctx,
filt_frame);
if (ret < 0) {
/* 如果没有更多的输出帧 - returns AVERROR(EAGAIN)
* 如果刷新并且没有更多的帧用于输出 - returns AVERROR_EOF
* 将 ret 置为 0 以将其显示为正常过程完成
*/
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
ret = 0;
av_frame_free(&filt_frame); // 释放 filt_frame
break;
}
filt_frame->pict_type = AV_PICTURE_TYPE_NONE;
ret = encode_write_frame(filt_frame, stream_index, NULL);
if (ret < 0)
break;
}
return ret;
}
static int flush_encoder(unsigned int stream_index)
{
int ret;
int got_frame;
if (!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
av_log(NULL, AV_LOG_INFO, "Flushing stream #%u encoder\n", stream_index);
ret = encode_write_frame(NULL, stream_index, &got_frame);
if (ret < 0)
break;
if (!got_frame)
return 0;
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
int ret;
AVPacket packet;
AVFrame *frame = NULL;
enum AVMediaType type;
unsigned int stream_index;
unsigned int i;
int got_frame;
int (*dec_func)(AVCodecContext *, AVFrame *, int *, const AVPacket *);
if (argc != 3) {
av_log(NULL, AV_LOG_ERROR, "Usage: %s , argv[0]);
return 1;
}
av_register_all(); // 注册ffmpeg所有编解码器
avfilter_register_all(); // 初始化过滤系统。注册所有内置过滤器。
if ((ret = open_input_file(argv[1])) < 0) // 打开输入文件,并初始化相关的结构体。
goto end;
if ((ret = open_output_file(argv[2])) < 0) // 打开输出文件,并初始化相关的结构体。
goto end;
if ((ret = init_filters()) < 0) // 初始化AVFilter相关的结构体。
goto end;
// 读取所有数据包
while (1) {
if ((ret = av_read_frame(ifmt_ctx, &packet)) < 0) // 从输入文件中读取一个AVPacket。
break;
stream_index = packet.stream_index;
type = ifmt_ctx->streams[packet.stream_index]->codec->codec_type;
av_log(NULL, AV_LOG_DEBUG, "Demuxer gave frame of stream_index %u\n",
stream_index);
if (filter_ctx[stream_index].filter_graph) {
av_log(NULL, AV_LOG_DEBUG, "Going to reencode&filter the frame\n");
frame = av_frame_alloc(); // AVFrame结构,av_frame_alloc申请内存,av_frame_free释放内存
if (!frame) {
ret = AVERROR(ENOMEM);
break;
}
// 将64位整数重缩放为2个具有指定舍入的有理数。
// 返回重新缩放的值a,或者如果设置了AV\u ROUND\u PASS\u MINMAX并且a是INT64_MIN或INT64_MAX则a以不变的方式通过。
packet.dts = av_rescale_q_rnd(packet.dts,
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.pts = av_rescale_q_rnd(packet.pts,
ifmt_ctx->streams[stream_index]->time_base,
ifmt_ctx->streams[stream_index]->codec->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
// 视频解码或者音频解码
dec_func = (type == AVMEDIA_TYPE_VIDEO) ? avcodec_decode_video2 :
avcodec_decode_audio4;
ret = dec_func(ifmt_ctx->streams[stream_index]->codec, frame,
&got_frame, &packet);
if (ret < 0) {
av_frame_free(&frame);
av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
break;
}
if (got_frame) {
frame->pts = av_frame_get_best_effort_timestamp(frame);
ret = filter_encode_write_frame(frame, stream_index); // 编码一个AVFrame。
av_frame_free(&frame);
if (ret < 0)
goto end;
} else {
av_frame_free(&frame);
}
} else {
// 重新复制此帧而不重新编码
packet.dts = av_rescale_q_rnd(packet.dts,
ifmt_ctx->streams[stream_index]->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.pts = av_rescale_q_rnd(packet.pts,
ifmt_ctx->streams[stream_index]->time_base,
ofmt_ctx->streams[stream_index]->time_base,
(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
ret = av_interleaved_write_frame(ofmt_ctx, &packet); // 将数据包写入输出媒体文件,以确保正确的交织。
if (ret < 0)
goto end;
}
av_free_packet(&packet);
}
/* flush filters and encoders */
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
/* flush filter */
if (!filter_ctx[i].filter_graph)
continue;
ret = filter_encode_write_frame(NULL, i); // 编码一个AVFrame。
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Flushing filter failed\n");
goto end;
}
/* flush encoder */
ret = flush_encoder(i);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");
goto end;
}
}
av_write_trailer(ofmt_ctx); // 写文件尾
end:
av_free_packet(&packet);
av_frame_free(&frame);
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
avcodec_close(ifmt_ctx->streams[i]->codec);
if (ofmt_ctx && ofmt_ctx->nb_streams > i && ofmt_ctx->streams[i] && ofmt_ctx->streams[i]->codec)
avcodec_close(ofmt_ctx->streams[i]->codec);
if (filter_ctx && filter_ctx[i].filter_graph)
avfilter_graph_free(&filter_ctx[i].filter_graph);
}
av_free(filter_ctx);
avformat_close_input(&ifmt_ctx);
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0)
av_log(NULL, AV_LOG_ERROR, "Error occurred\n");
return (ret? 1:0);
}
结束:
分享也是自己对问题再次加深理解的方式,可能不全面,但绝对有用,后面将不断完善~