主頁 >  其他 > FFMPEG總結 -- 音視頻編解碼和轉碼(全)

FFMPEG總結 -- 音視頻編解碼和轉碼(全)

2021-02-08 10:20:35 其他

在經過一周的學習后,現將其稍加總結下:附代碼及詳細注釋,

簡單分為如下六個部分

  • 一、播放一個視頻檔案的流程
  • 二、ffmpeg 視頻編碼 (YUV編碼為H.264)
  • 三、ffmpeg 視頻解碼 (解碼為YUV)
    • 注:解碼后的資料為什么要經過 sws_scale() 函式處理?
  • 四、ffmpeg 音頻編碼 (PCM編碼為AAC)
  • 五、ffmpeg 音頻解碼
  • 六、ffmpeg 轉碼 (FLV 轉碼為AVI)

一、播放一個視頻檔案的流程

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

二、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);
}


結束:

分享也是自己對問題再次加深理解的方式,可能不全面,但絕對有用,后面將不斷完善~

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/257717.html

標籤:其他

上一篇:MATLAB 撰寫簡易電子琴

下一篇:C語言----完成對輸入的字串中C關鍵詞的查找統計。

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more