FFMPEG总结 -- 音视频编解码和转码(全)

在经过一周的学习后,现将其稍加总结下:附代码及详细注释。

一、播放一个视频文件的流程

字不如图,直接看下图,就可以明白
在这里插入图片描述

二、ffmpeg 视频编码 (YUV编码为H.264)

看下图,先搞明白 视频编码的流程。(图源于雷神)
图下紧接着每个步骤的中文含义
在这里插入图片描述
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 <stdio.h>
#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 <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#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(&param, "preset", "slow", 0);
		av_dict_set(&param, "tune", "zerolatency", 0);
		//av_dict_set(&param, "profile", "main", 0);
	}
	//H.265
	if(pCodecCtx->codec_id == AV_CODEC_ID_H265){
    
    
		av_dict_set(&param, "preset", "ultrafast", 0);
		av_dict_set(&param, "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,&param) < 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;
}

三、ffmpeg 视频解码 (解码为YUV)

老规矩,看一下 视频解码 的流程图
图下紧接着每个步骤的中文含义
在这里插入图片描述

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 <stdio.h>

#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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#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;
}

注:解码后的数据为什么要经过 sws_scale() 函数处理?

解码后YUV格式的视频像素数据保存在 AVFrame 的 data[0] 、data[1] 、data[2] 中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。以亮度Y数据为例,data[0]中一共包含了 linesize[0]*height 个数据。但是出于优化等方面的考虑,linesize[0] 实际上并不等于宽度 width ,而是一个比宽度大一些的值。因此需要用 sws_scale() 进行转换。转换后去除了无效数据,width 和 linesize[0] 取值相等。
如图:
在这里插入图片描述

四、ffmpeg 音频编码 (PCM编码为AAC)

老规矩,看一下 音频编码 的流程图。 (图源于雷神)
图下紧接着每个步骤的中文含义
在这里插入图片描述
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 <stdio.h>
#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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#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;
}

五、ffmpeg 音频解码

待学习。

六、ffmpeg 转码 (FLV 转码为AVI)

先看一下 转码的理论流程。 (图源于雷神)
在这里插入图片描述

老规矩,再一下编程角度 转码 的流程图。 (图源于雷神)
图下紧接着每个步骤的中文含义

在这里插入图片描述
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 <input file> <output file>\n", 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);
}


结束:

分享也是自己对问题再次加深理解的方式,可能不全面,但绝对有用,后面将不断完善~

猜你喜欢

转载自blog.csdn.net/hwx802746/article/details/113740680