在經過一周的學習后,現將其稍加總結下:附代碼及詳細注釋,
簡單分為如下六個部分
- 一、播放一個視頻檔案的流程
- 二、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(¶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;
}
三、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 撰寫簡易電子琴
