[音視訊處理] FFmpeg使用指北1-視訊解碼

2023-05-31 12:00:33

本文將詳細介紹如何使用ffmpeg 4.4在C++中解碼多種格式的媒體檔案,這些媒體檔案可以是視訊、視訊流、圖片,或是桌面截圖或USB攝像頭的實時圖片。解碼檔案後,還將每幀圖片轉換為OpenCV的Mat格式以供後續使用。

1 基於ffmpeg的媒體檔案解碼

1.1 簡介

在開始之前,需要先安裝FFmpeg。對於Windows使用者,可以參考FFmpeg + Visual studio 開發環境搭建;對於Linux使用者,可以參考FFmpeg4.4編譯

本文主要參考了ffmpeg-libav-tutorial/0_hello_world.c提供的程式碼。值得注意的是,由於FFmpeg版本變化較大,本文所使用的FFmpeg介面和以往有所不同。如果想進一步學習FFmpeg程式碼的使用,可以閱讀FFmpeg-libav-tutorialffmpeg-learning-indexes視音訊編解碼技術零基礎學習方法(由於作者雷霄驊不幸英年早逝,哀悼!該文主要基於舊ffmpeg版本,但是仍然有很好的學習價值)。

涉及的步驟如下圖所示:

解封裝

在音視訊處理過程中,解封裝是指將輸入的音視訊檔進行解析,提取出音訊流和視訊流等多種串流媒體資料,以便後續的資料處理和解碼。在解封裝過程中,首先需要判斷輸入源的格式,即判斷輸入的音視訊檔是屬於哪種格式。然後開啟檔案,查詢流資訊和視訊索引。

解碼

解碼是指將音視訊資料進行解碼,將壓縮後的資料轉換成原始的音視訊資料,以便後續的資料處理和播放。在解碼過程中,需要初始化解碼器,並開啟解碼器。本文只解碼視訊,音訊則不進行處理。

取資料

在取資料過程中,需要初始化資料結構,讀取視訊幀,並將視訊幀傳送給解碼器。隨後,從解碼器獲取解碼結果。

資料處理

資料處理是指對音視訊資料進行各種處理,比如色彩空間轉換、影象尺寸變換、影象格式轉換等。

釋放資源

在完成解碼和資料處理後,需要釋放結構體,以釋放資源。釋放資源是指對音視訊處理過程中佔用的各種資源進行釋放,包括解碼器、資料結構、緩衝區等。

1.2 詳細程式碼

詳細程式碼如下:

/**
 * @brief 程式碼主要參考https://github.com/leandromoreira/ffmpeg-libav-tutorial/blob/master/0_hello_world.c
 *
 */

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
}
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <stdarg.h>
#include <string.h>
#include <inttypes.h>

// 紀錄檔列印宏
#define LOG(msg, ...)\
fprintf(stderr,"LOG [line %d] ",__LINE__);\
fprintf(stderr,msg, ##__VA_ARGS__);\
fprintf(stderr, "\n");

// 支援的輸入檔案形式
enum URLType { file, usbcam, desktop, yuvfile };

// usb攝像頭讀取函數
void usbcam_get(const char * url, AVInputFormat ** ifmt);

// 解碼函數
static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame, int skip = 0);

/**
 * 涉及到結構體
 * AVFormatContext	儲存媒體檔案所有資訊的結構體
 * AVInputFormat 儲存媒體檔案的格式資訊
 * AVStream	表示音視訊流資訊的結構體
 * AVCodecContext	儲存解碼音視訊所有資訊的結構體
 * AVCodec	儲存視訊或音訊的編解碼器的結構體
 * AVCodecParameters	儲存音視訊編解碼器的相關引數資訊的結構體
 * AVPacket	儲存解碼前資料的結構體
 * AVFrame	儲存解碼後資料的結構體
 * AVRational	表示有理數的結構體
 * SwsContext	用於影象轉換的結構體
 */
int main()
{
	// 設定資料型別
	URLType urltype = yuvfile;
	// 初始化結構體
	const char *url = NULL;
	AVFormatContext *pFormatContext = NULL;
	AVInputFormat *ifmt = NULL;
	AVDictionary *options = NULL;
	// AVCodec負責編解碼音視訊流
	AVCodecContext *pCodecContext = NULL;
	AVCodec *pCodec = NULL;
	AVCodecParameters *pCodecParameters = NULL;
	// 負責儲存資料
	AVPacket *pPacket = NULL;
	AVFrame *pFrame = NULL;

	LOG("FFMPEG VERSION: %s", av_version_info());
	LOG("開始執行");

	// 儲存音視訊封裝格式中包含的資訊
	// avformat_alloc_context初始化AVFormatContext結構體
	pFormatContext = avformat_alloc_context();

	if (!pFormatContext)
	{
		LOG("pFormatContext分配記憶體失敗");
		return -1;
	}

	// 註冊能操作的輸入輸出裝置
	avdevice_register_all();
	if (urltype == file)
	{
		// rtsp流
		//url = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
		// 輸入圖片
		// url = "demo.png";
		// 輸入視訊
		url = "demo.mp4";
		// 設定超時時間為5秒
		av_dict_set(&options, "stimeout", "5000000", 0);
	}
	else if (urltype == usbcam)
	{
		url = "0";
		// 如果使用以下方式讀取本機攝像頭,需要自行獲得攝像頭名稱
		// 使用指令:ffmpeg -list_devices true -f dshow -i dummy
		//url = "video=HD WebCam";
		// 輸出ffmpeg版本
		usbcam_get(url, &ifmt);
		// 設定圖片尺寸
		av_dict_set(&options, "video_size", "640x480", 0);
		av_dict_set(&options, "framerate", "30", 0);
	}
	else if (urltype == desktop)
	{
		// Windows
#ifdef _WIN32
	// 根據不同的url選擇不同的格式
		url = "desktop";
		ifmt = av_find_input_format("gdigrab");
		// linux處理
#elif defined linux
		// linux命令列輸入echo $DISPALY獲得
		url = ":1";
		ifmt = av_find_input_format("x11grab");
#endif
		av_dict_set(&options, "video_size", "1920x1080", 0);
		av_dict_set(&options, "framerate", "15", 0);
	}
	else if (urltype == yuvfile)
	{
		url = "akiyo_cif.yuv";
		// yuv影象尺寸需要提前設定
		av_dict_set(&options, "video_size", "352x288", 0);
	}

	// avformat_open_input開啟輸入的媒體檔案
	if (avformat_open_input(&pFormatContext, url, ifmt, &options) != 0)
	{
		LOG("開啟檔案失敗");
		return -1;
	}

	LOG("開啟檔案 %s", url);

	// 讀取檔案音視訊編解碼器的資訊
	LOG("檔案格式 %s, 檔案時長 %lld us, 位元率 %lld bit/s",
		pFormatContext->iformat->name,
		pFormatContext->duration,
		pFormatContext->bit_rate);

	LOG("獲取輸入音視訊檔的流資訊");
	// avformat_find_stream_info獲取輸入音視訊檔的流資訊
	if (avformat_find_stream_info(pFormatContext, NULL) < 0)
	{
		LOG("無法獲取流資訊");
		return -1;
	}

	// 設定是否讀取到視訊流
	int video_stream_index = -1;

	// 迴圈瀏覽所有流並列印其主要資訊
	for (int i = 0; i < int(pFormatContext->nb_streams); i++)
	{
		AVCodecParameters *pLocalCodecParameters = NULL;
		// 提取當前流的編解碼器引數
		pLocalCodecParameters = pFormatContext->streams[i]->codecpar;

		AVCodec *pLocalCodec = NULL;

		// 查詢指定編解碼器的解碼器
		pLocalCodec = avcodec_find_decoder(pLocalCodecParameters->codec_id);

		if (pLocalCodec == NULL)
		{
			LOG("不支援該解碼器!");
			continue;
		}

		// 當流是視訊時,我們儲存其索引、解碼器和編解碼器引數
		if (pLocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			if (video_stream_index == -1)
			{
				video_stream_index = i;
				pCodec = pLocalCodec;
				pCodecParameters = pLocalCodecParameters;
			}

			LOG("視訊編解碼器型別: %s ID: %d", pLocalCodec->name, pLocalCodec->id);
			LOG("視訊流影格率為:%f", av_q2d(pFormatContext->streams[i]->r_frame_rate));
			LOG("視訊流共有:%d幀", pFormatContext->streams[i]->nb_frames);
			LOG("視訊影象解析度為:(%d,%d)", pLocalCodecParameters->width, pLocalCodecParameters->height);
		}
		else if (pLocalCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			LOG("音訊編解碼器型別: %s ID: %d", pLocalCodec->name, pLocalCodec->id);
			LOG("音訊通道數:%d channels, 取樣率:%d", pLocalCodecParameters->channels, pLocalCodecParameters->sample_rate);
		}
	}

	if (video_stream_index == -1)
	{
		LOG("%s檔案不包含視訊流!", url);
		return -1;
	}

	// 分配AVCodecContext結構體並進行初始化
	pCodecContext = avcodec_alloc_context3(pCodec);
	if (!pCodecContext)
	{
		LOG("AVCodecContext初始失敗");
		return -1;
	}

	// 將AVCodecParameters中的引數設定到AVCodecContext中
	if (avcodec_parameters_to_context(pCodecContext, pCodecParameters) < 0)
	{
		LOG("AVCodecParameters引數拷貝失敗");
		return -1;
	}

	// 開啟解碼器
	if (avcodec_open2(pCodecContext, pCodec, NULL) < 0)
	{
		LOG("開啟解碼器失敗");
		return -1;
	}

	// 建立AVPacket
	pPacket = av_packet_alloc();
	if (!pPacket)
	{
		LOG("AVPacket初始化失敗");
		return -1;
	}

	// 建立AVFrame
	pFrame = av_frame_alloc();
	if (!pFrame)
	{
		LOG("AVFrame初始化失敗");
		return -1;
	}

	int response = 0;
	// 最多讀取幀數
	int how_many_packets_to_process = 500;
	// 幀處理跨度
	int skip_span = 50;

	// 讀取媒體檔案中的音視訊幀
	while (av_read_frame(pFormatContext, pPacket) >= 0)
	{
		// 判斷是否為視訊幀
		if (pPacket->stream_index == video_stream_index)
		{
			// 只解碼關鍵幀,關鍵幀不依賴於其他幀進行解碼,所以可以跳過其他幀

			// 關鍵幀間隔由媒體流資料來源決定
			// if (!(pPacket->flags & AV_PKT_FLAG_KEY)) {
			//	continue;
			//}
			int skip = 1;
			// 如果已讀取幀數除以skip_span為0,則下一幀進行處理
			if (pCodecContext->frame_number % skip_span == 0)
			{
				skip = 0;
			}

			// 計算時間
			auto start = std::chrono::system_clock::now();
			// 影象解碼函數
			response = decode_packet(pPacket, pCodecContext, pFrame, skip);
			auto end = std::chrono::system_clock::now();
			auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
			if (skip == 0)
			{
				LOG("解碼和處理一幀影象耗時:%d ms", duration);
			}
			else
			{
				LOG("僅解碼一幀影象耗時:%d ms", duration);
			}
			// 影象解碼狀態判定
			if (response < 0)
				break;
			// 超過讀取影象上限
			if (--how_many_packets_to_process <= 0)
			{
				LOG("讀圖完畢!");
				break;
			}
		}
		// 釋放AVPacket結構體中的記憶體
		av_packet_unref(pPacket);
	}

	LOG("銷燬所有結構體");

	// 銷燬結構體
	avformat_close_input(&pFormatContext);
	av_packet_free(&pPacket);
	av_frame_free(&pFrame);
	avcodec_free_context(&pCodecContext);
	av_dict_free(&options);
	system("pause");
	return 0;
}

void usbcam_get(const char * url, AVInputFormat ** ifmt)
{
	// Windows
#ifdef _WIN32
	// 根據不同的url選擇不同的格式
	if (url == "0")
		*ifmt = av_find_input_format("vfwcap");
	else
		*ifmt = av_find_input_format("dshow");
	// linux
#elif defined linux
	url = "/dev/video0";
	*ifmt = av_find_input_format("video4linux2");
#endif
}

static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame, int skip)
{
	// 將pPacket資料送入pCodecContext進行解碼
	int response = avcodec_send_packet(pCodecContext, pPacket);

	if (response < 0)
	{
		return response;
	}

	while (response >= 0)
	{
		// 用於從解碼器中獲取解碼後的視訊幀
		response = avcodec_receive_frame(pCodecContext, pFrame);
		if (response == AVERROR(EAGAIN) || response == AVERROR_EOF)
		{
			break;
		}
		else if (response < 0)
		{
			LOG("讀圖出錯: %d", response);
			return response;
		}

		// 僅讀取當前幀
		if (skip != 0)
		{
			return 0;
		}
		if (response >= 0)
		{
			LOG("Frame %d,幀型別=%c,視訊格式=%d,pts=%d,是否為關鍵幀=%d",
				pCodecContext->frame_number,
				av_get_picture_type_char(pFrame->pict_type),
				pFrame->format,
				pFrame->pts,
				pFrame->key_frame);

			// 影象儲存名
			char frame_filename[1024];
			snprintf(frame_filename, sizeof(frame_filename), "%s-%d.jpg", "frame", pCodecContext->frame_number);

			// 將解碼後的幀轉換為BGR格式
			// 建立影象轉換器,設定影象尺寸縮小一倍
			int dst_w = int(pCodecContext->width / 2);
			int dst_h = int(pCodecContext->height / 2);
			SwsContext *swsCtx = sws_getContext(
				pCodecContext->width, pCodecContext->height, (AVPixelFormat)pCodecContext->pix_fmt,
				dst_w, dst_h, AV_PIX_FMT_BGR24,
				SWS_POINT, NULL, NULL, NULL);
			cv::Mat bgrMat(dst_h, dst_w, CV_8UC3);
			// 拿出opencv的資料
			uint8_t *dest[1] = { bgrMat.data };
			int destStride[1] = { int(bgrMat.step) };
			// 執行格式轉換
			sws_scale(swsCtx, pFrame->data, pFrame->linesize, 0, pFrame->height, dest, destStride);
			// 儲存圖片
			cv::imwrite(frame_filename, bgrMat);
			// 釋放swsCtx資料
			sws_freeContext(swsCtx);
		}
	}
	return 0;
}

以上程式碼參考下圖閱讀最好。圖片來自ffmpeg-libav-tutorial/decoding.png

如果是linux下使用該程式碼檔案還需編寫CMakeLists.txt,CMakeLists.txt內容如下:

# 最低cmake版本
cmake_minimum_required(VERSION 3.2)
# 工程名
project(ffmpeg_demo)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})

# --- opencv
find_package(OpenCV REQUIRED)

# --- ffmpeg
set(FFMPEG_INCLUDE_DIRS "/usr/local/include/")
set(FFMPEG_LIB_DIRS "/usr/local/lib/")
set(FFMPEG_LIBS "avcodec;avformat;avutil;swresample;avdevice;swscale")

include_directories(${FFMPEG_INCLUDE_DIRS})
link_directories(${FFMPEG_LIB_DIRS})

# 生成可執行檔案
add_executable(ffmpeg_demo demo.cpp)
target_link_libraries(ffmpeg_demo  ${FFMPEG_LIBS} ${OpenCV_LIBS} pthread)

2 ffmpeg函數解釋

2.1 解封裝

解封裝的作用是從輸入的封裝格式資料(例如MP4、AVI、MKV)中提取視訊流壓縮編碼資料和音訊流壓縮編碼資料。封裝格式的作用是將已經壓縮編碼的視訊資料和音訊資料按照一定的格式放在一起。例如,將MP4封裝格式的資料輸出H.264編碼格式的視訊流和AAC格式的音訊流。一般解封裝的流程如下:

  1. 在使用FFmpeg解碼音視訊檔時,需要通過AVFormatContext來獲取檔案資訊和流資訊。AVFormatContext中包含AVInputFormat結構體指標,指向當前媒體檔案的輸入格式。
  2. AVInputFormat結構體描述了媒體檔案的封裝格式,如MP4、AVI、MKV等。
  3. avformat_alloc_context用於建立並初始化AVFormatContext結構體,為後續的音視訊檔解碼或編碼做好準備。
  4. avformat_open_input用於開啟音視訊檔並讀取檔案資訊到AVFormatContext結構體中。
  5. avformat_find_stream_info用於獲取音視訊流資訊並儲存到AVFormatContext結構體中。
  6. avformat_close_input用於關閉音視訊檔並釋放AVFormatContext結構體佔用的記憶體空間。

AVFormatContext

AVFormatContext是一個儲存串流媒體相關資訊的上下文結構體(統領相關操作全域性的結構體)。幾乎所有的音視訊操作都需要先建立一個AVFormatContext物件。AVFormatContext使用完畢需要手動釋放記憶體。AVFormatContext的主要屬性及使用說明:

  • AVInputFormat *iformat:輸入格式結構體指標,用於指定輸入檔案的格式,一般由FFmpeg自動探測獲取。
  • AVOutputFormat *oformat:輸出封裝格式的結構體指標。
  • AVIOContext *pb:輸入輸出的AVIOContext結構體指標。
  • unsigned int nb_streams:音視訊流個數。
  • int64_t duration:音視訊檔的時長,單位為微秒(μs),一般由FFmpeg解析後賦值。
  • int64_t bit_rate:音視訊檔的位元速率,單位為bit/s,一般由FFmpeg解析後賦值。
  • AVStream **streams:音視訊流列表的指標陣列。
  • AVDictionary *metadata:後設資料資訊,例如標題、作者、描述等等。

該結構體涉及以下函數:

avformat_alloc_context

avformat_alloc_context用於分配AVFormatContext結構體並初始化。該函數返回一個指向AVFormatContext結構體的指標,如果分配失敗則返回NULL。

AVFormatContext *avformat_alloc_context(void);

avformat_open_input

avformat_open_input用於開啟輸入的媒體檔案,將音視訊檔的後設資料資訊讀取到AVFormatContext結構體中。函數返回0表示成功開啟檔案。

int avformat_open_input(AVFormatContext ** ps,
                        const char * url, 
                        const AVInputFormat * fmt,
                        AVDictionary ** options )
  • **ps:指向AVFormatContext結構體指標的指標,用於存放開啟的媒體檔案的相關資訊。
  • *url:輸入媒體檔案的URL地址。可以是本地檔案路徑或者網路地址。
  • *fmt:輸入媒體檔案的格式,如果為NULL,則根據URL自動探測輸入媒體的格式。
  • **options:輸入媒體檔案的選項引數。

avformat_find_stream_info

avformat_find_stream_info用於獲取輸入檔案的流資訊。它會讀取輸入檔案的所有封包,並嘗試從中獲取流的引數,如流的編解碼器、影格率、解析度等等。在呼叫avformat_find_stream_info之後,可以通過 AVFormatContext結構體中的streams欄位存取到每個流的詳細資訊。函數返回值大於等於0表示成功。

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
  • ic:指向AVFormatContext結構體的指標。
  • options:指向AVDictionary結構體指標的指標,用於傳遞選項給解複用器。

avformat_close_input

avformat_close_input用於關閉AVFormatContext檔案並釋放相關資源。一般情況下avformat_close_input和avformat_open_input成對使用,該函數也在內部會呼叫avformat_free_context函數釋放AVFormatContext結構體。

void avformat_close_input(AVFormatContext **ps);
  • ps:指向AVFormatContext結構體指標的指標,該指標會在函數執行完畢後被置為NULL。

avformat_free_context

avformat_free_context用於釋放AVFormatContext結構體。


void avformat_free_context(AVFormatContext * s)
  • ps:指向AVFormatContext結構體的指標。

AVInputFormat

AVInputFormat用於表示輸入的媒體檔案的格式。主要作用為通過解析輸入的媒體檔案,並將其轉換成FFmpeg內部所使用的資料結構。AVInputFormat結構體的記憶體由FFmpeg庫自動分配和釋放,在呼叫avformat_close_input函數後,FFmpeg庫將自動釋放AVInputFormat結構體。AVInputFormat的主要屬性及使用說明:

  • const char * name:輸入檔案型別的名稱。
  • const char * long_name:輸入檔案型別的詳細描述。
  • const char * extensions:輸入檔案型別的擴充套件名列表。

2.2 解碼

解碼的作用是將視訊或聲頻壓縮編碼資料轉換成為非壓縮的視訊或音訊原始資料。例如將H.264的視訊壓縮資料解碼為逐幀YUV影象資料。一般解碼的流程如下:

  1. 每個AVStream結構都儲存一個視訊/音訊流的相關資料,例如流的編號、流的型別、流的位元速率等等。AVStream結構中還包含一個指向對應AVCodecContext結構的指標,該結構用於儲存該視訊/音訊流解碼方式的所有資訊,如編碼器的名稱、編碼器的屬性、編碼器的狀態等等。
  2. AVCodecContext結構中又包含一個指向對應AVCodec結構的指標,AVCodec結構包含該視訊/音訊對應的解碼器的基本資訊,如編碼器的名稱、編碼器的型別、編碼器的能力等。當需要使用某個編解碼器時,需要先通過編解碼器的名稱來查詢對應的AVCodec結構體,然後再將這個結構體中的資訊賦值給AVCodecContext結構體中的相應欄位。
  3. AVCodecParameters結構體是一個描述編解碼器引數結構體,它包含了一個編解碼器的引數資訊,如編碼器的寬度、編碼器的高度、編碼器的位元速率等等。對一個視訊或音訊流進行編解碼時,需要使用AVCodecParameters結構體來描述這個流的引數資訊,然後再將這個結構體中的資訊賦值給AVCodecContext結構體中的相應欄位。

AVStream

AVStream是FFmpeg中表示音視訊流的結構體,每個AVStream結構體都對應一個視訊或音訊流的相關資料。AVStream結構體的記憶體由FFmpeg庫自動分配和釋放。AVStream的主要屬性及使用說明:

  • AVCodecParameters *codecpar:指向AVCodecParameters結構體的指標,儲存了該流的編解碼器引數。
  • AVRational ime_base:時間基準,表示每個取樣的持續時間,以分數形式表示。
  • int64_t start_time:流的開始時間,以時間戳的形式表示。
  • int64_t duration:流的持續時間,以時間戳的形式表示。
  • int64_t nb_frames:該流中的幀數。
  • AVRational r_frame_rate:用於表示實際影格率的AVRational結構體。
  • AVRational avg_frame_rate: 用於表示平均影格率的AVRational結構體。

AVCodecContext

AVCodecContext包含解碼音視訊所有資訊的上下文結構體。在進行音視訊編解碼時,通過對AVCodecContext的相關引數進行設定,來控制編解碼器的行為。AVCodecContext使用完畢需要手動釋放記憶體。AVCodecContext的一些常用引數包括:

  • enum AVCodecID codec_id:指定音視訊編解碼器的ID。
  • AVRational time_base:音視訊幀的時間基準,用於計算時間戳等。
  • int64_t bit_rate:音視訊的位元率,影響編碼後的檔案大小和質量。
  • int widthint height:視訊的寬高。
  • int sample_rateint channels:音訊的取樣率和聲道數。
  • enum AVPixelFormat pix_fmt:視訊的畫素格式,如YUV420P、RGB24等。
  • attribute_deprecated int frame_number:獲得已處理幀數,但是該屬性已經被廢棄,因為如果編碼/解碼導致錯誤,則計數器不遞增。

該結構體涉及以下函數:

avcodec_alloc_context3

avcodec_alloc_context3函數用於分配AVCodecContext結構體並進行初始化。該函數返回一個指向AVCodecContext結構體的指標,如果分配失敗則返回NULL。

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
  • codec:指向AVCodec結構體的指標,表示要使用的解碼器。

avcodec_parameters_to_context

avcodec_parameters_to_context函數的作用是將AVCodecParameters中的引數設定到AVCodecContext中。返回值小於0表示設定失敗。

int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
  • AVCodecContext *codec:需要設定引數的AVCodecContext結構體指標。
  • const AVCodecParameters *par:需要從中獲取引數的AVCodecParameters結構體指標。

avcodec_open2

avcodec_open2用於開啟AVCodec並初始化AVCodecContext。返回0表示成功,否則表示失敗。

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
  • avctx:指向一個已經分配好記憶體的AVCodecContext結構體。
  • codec:指向一個已經註冊好的編碼器或解碼器的AVCodec結構體。
  • options:指向一個AVDictionary型別的指標,用於傳遞開啟編碼器或解碼器時的引數,可以為NULL。

avcodec_send_packet

avcodec_send_packet函數用於將一個未解碼的AVPacket資料送入解碼器AVCodecContext進行解碼。該函數執行成功後,解碼器AVCodecContext內部的快取將會被填充上相應的資料,可以通過呼叫avcodec_receive_frame函數來獲取解碼結果。返回值為0表示成功,否則表示失敗。

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  • avctx:已經被開啟的編解碼器。
  • avpkt:待解碼的AVPacket。

avcodec_receive_frame

avcodec_receive_frame用於從解碼器中獲取解碼後的視訊幀。avcodec_receive_frame一般會外嵌while迴圈,可以保證在沒有接收到可用幀之前不會退出迴圈,從而避免封包丟失或者解碼錯誤的情況發生。

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
  • avctx:解碼器物件。
  • frame:存放解碼後的視訊幀的AVFrame物件。

返回值表示獲取到的視訊幀的狀態,具體取值如下:

  • 0:成功獲取到一幀視訊幀。
  • AVERROR(EAGAIN):緩衝區中沒有可用的視訊幀,需要再次呼叫該函數。
  • AVERROR_EOF:解碼器中的所有視訊幀都已經讀取完成。

avcodec_free_context

avcodec_free_context函數用於釋放AVCodecContext結構體所佔用的記憶體。

void avcodec_free_context(AVCodecContext **avctx);
  • avctx: 指向需要釋放的AVCodecContext結構體指標。`

AVCodec

AVCodec用於表示視訊或音訊的編解碼器。AVCodec資料結構的記憶體由FFmpeg庫自動分配和釋放。AVCodec的主要屬性及使用說明:

  • const char * name:編解碼器的名稱。
  • const char * long_name:編解碼器的詳細描述。
  • enum AVMediaType type:表示編解碼器的型別,可以是視訊、音訊或其他型別。
  • enum AVCodecID id:表示編解碼器的ID。
  • int capabilities:表示編解碼器的功能特性,例如是否支援多執行緒等。
  • const AVRational * supported_framerates:表示編解碼器支援的影格率列表。
  • enum AVPixelFormat * pix_fmts:表示編解碼器支援的畫素格式列表。
  • const int * supported_samplerates:表示編解碼器支援的取樣率列表。
  • enum AVSampleFormat * sample_fmts:表示編解碼器支援的取樣格式列表。

該結構體涉及以下函數:

avcodec_find_decoder

avcodec_find_decoder用於通過codec_id查詢指定已經註冊的解碼器。 如果找到了指定的解碼器,返回指向該解碼器的AVCodec指標。如果未找到指定的解碼器,返回 NULL。

AVCodec *avcodec_find_decoder(enum AVCodecID id);
  • id:要查詢的解碼器的AVCodecID格式codec_id,AVCodecID 是一個列舉型別表示不同的編解碼器。。

AVCodecParameters

AVCodecParameters主要用於儲存音視訊編解碼器的相關引數資訊。AVCodecParameters資料結構的記憶體由FFmpeg庫自動分配和釋放。AVCodecParameters常用屬性介紹:

  • enum AVMediaType codec_type:音視訊流型別。
  • enum AVCodecID codec_id:指定解碼器的ID,如AV_CODEC_ID_H264表示使用H.264解碼器。
  • int64_t bit_rate:指定音視訊的位元率,單位為bps。
  • int widthint height:指定視訊的寬度和高度。
  • int channels:表示聲道數
  • int sample_rate:指定音訊取樣率,單位為Hz。
  • uint8_t * extradata/int extradata_size:指定音視訊流的附加資料和附加資料的大小。

2.3 資料儲存

AVPacket用於儲存解碼前的資料,AVFrame則用於儲存解碼後的資料。在解碼器中,AVPacket中的資料會被解碼成AVFrame。在編碼器中,AVFrame中的資料會被編碼成AVPacket。

AVPacket

AVPacket是用於儲存壓縮音訊或視訊資料的結構體。它包含了一段壓縮後的資料和對應的時間戳資訊,以及一些其他的附加資訊,如資料流索引、關鍵幀標識等。在解碼過程中,AVPacket會被送到解碼器中進行解碼,得到AVFrame。AVPacket使用完畢需要手動釋放記憶體。AVPacket的主要屬性如下:

  • uint8_t* data:指向音視訊資料框的指標。
  • int size:音視訊資料框的大小。
  • int64_t pts:音視訊資料框的顯示時間。
  • int64_t dts:音視訊資料框的解碼時間。
  • int stream_index:音視訊資料框所屬的流的索引。
  • int flags:用於描述AVPacket的一些特性。常見選項如下:
    • AV_PKT_FLAG_KEY:表示該AVPacket所包含的資料是一個關鍵幀。
    • AV_PKT_FLAG_CORRUPT:表示該AVPacket所包含的資料可能已經損壞。當解碼器無法正確解碼一個AVPacket時,就會設定該標誌位,通知應用程式此AVPacket已經損壞。
    • AV_PKT_FLAG_DISCARD:表示該AVPacket所包含的資料可以被丟棄。當解碼器對於某些時刻無法解碼出正確的影象時,就會設定該標誌位。可以選擇丟棄該AVPacket,以保證視訊的流暢性。
    • AV_PKT_FLAG_TRUSTED:表示該AVPacket所包含的資料是可信的。當解碼器在解碼AVPacket時,會校驗AVPacket的CRC校驗碼,如果校驗碼正確,則會設定該標誌位。這個標誌位通常用於保證視訊的完整性,以防止篡改或者損壞。

該結構體涉及以下函數:

av_packet_alloc

av_packet_alloc用於建立AVPacket結構體併為其分配記憶體空間。函數返回一個指向新分配的AVPacket結構體的指標。如果分配失敗,則返回NULL。

AVPacket *av_packet_alloc(void);

av_packet_unref

av_packet_unref函數用於清除AVPacket結構體中的資料,但是並不會釋放這個結構體本身,以便可以重新使用或銷燬AVPacket結構體。

void av_packet_unref(AVPacket *pkt);
  • pkt:AVPacket結構體指標。

av_packet_free

av_packet_free用於釋放AVPacket結構體所佔用記憶體。

void av_packet_free(AVPacket **pkt);
  • pkt:指向AVPacket結構體指標的指標。

AVFrame

AVFrame是用於儲存解碼後的資料的結構體。它包含了一幀影象或音訊解碼後的資料,以及一些相關的資訊,如寬度、高度、畫素格式等。在解碼過程中,AVFrame是解碼器輸出的資料,它可以被送到渲染器中進行渲染,也可以被編碼器編碼成新的AVPacket。AVFrame使用完畢需要手動釋放記憶體。AVFrame結構體中常用的屬性介紹和說明:

  • uint8_t * data:指向一個指標陣列,其中包含了這一幀的所有資料。對於視訊幀,通常包含了YUV或RGB資料;對於音訊幀,通常包含了PCM資料。具體的資料格式和分佈,可以通過其他引數進行描述。
  • int linesize:指向一個整型陣列,用於描述每個資料平面的行大小(即每一行佔用的位元組數)。對於視訊幀,通常會有三個資料平面(分別對應Y、U、V或R、G、B三個分量);對於音訊幀,通常只有一個資料平面。linesize陣列的大小應該與data陣列的大小相同。
  • uint8_t ** extended_data:指向一個指標陣列,其中包含了所有資料平面的指標。對於一些特殊的資料格式,data陣列可能無法直接描述所有資料平面。這時,extended_data可以用於補充缺失的資料平面。
  • int widthint height:分別表示這一幀的寬度和高度。對於音訊幀,這兩個引數均為0。
  • int format:表示這一幀的資料格式。對於視訊幀,常用的格式有YUV420、YUV422、YUV444、RGB24等;對於音訊幀,常用的格式有PCM_S16LE、PCM_S16BE、PCM_F32LE等。
  • int64_t pts:表示這一幀在整個多媒體流中的時間戳(Presentation Time Stamp)。它通常以視訊影格率或音訊取樣率為單位,用於確定這一幀的播放時間。
  • int64_t pkt_ptsint64_t pkt_dts:分別表示這一幀所屬的AVPacket中的時間戳和解碼時間戳(Decode Time Stamp)。它們與pts的含義類似,但是它們是從AVPacket中直接獲取的,可能會存在一些偏差或不準確的情況。
  • int sample_rateint channel_layout:僅用於音訊幀,分別表示取樣率和聲道佈局。其中,channel_layout可以用於指定聲道數和聲道位置的具體資訊。
  • enum AVPictureType pict_type:表示幀的影象型別是I幀、P幀、B幀還是S幀。關鍵幀(I幀)是一種特殊的幀,它包含完整的影象資訊,不依賴於前面或後面的幀。P幀(預測幀)和B幀(雙向預測幀)則只包含部分影象資訊,需要參考前面或後面的幀才能正確解碼。通過使用關鍵幀,可以提高視訊的壓縮比以及解碼效率。S幀是跳幀,它直接複製前一幀的影象,用於視訊壓縮。
  • int key_frame:表示當前幀是否為關鍵幀。

該結構體涉及以下函數:

av_frame_alloc

av_frame_alloc用於建立AVFrame結構體併為其分配記憶體空間。函數返回一個指向新分配的AVFrame結構體的指標。如果分配失敗,則返回NULL。

AVFrame *av_frame_alloc(void);

av_read_frame

av_read_frame用於從AVFormatContext中讀取媒體檔案中的音訊或視訊幀,並將資料儲存到AVPacket中。返回值為0表示讀取成功,為負數表示讀取失敗。

int av_read_frame(AVFormatContext *s, AVPacket *pkt);
  • s: 指向表示媒體檔案AVFormatContext結構體的指標。
  • pkt: 指向AVPacket結構體的指標,用於儲存讀取到的音視訊幀的資料。

av_frame_free

av_frame_free函數用於釋放AVFrame結構體佔用的記憶體空間。

void av_frame_free(AVFrame **frame);
  • frame:需要釋放的AVFrame結構體指標的地址。

2.4 功能結構

AVRational

AVRational結構體是FFmpeg中表示有理數的結構體,用於表示時間戳、影格率、取樣率等一些基本的時間和頻率相關的屬性。AVRational結構體包含兩個整型成員,num和den,分別表示分子和分母。用AVRational結構體表示的有理數值為num/den。AVRational結構體中的成員變數由FFmpeg內部自動分配和釋放。AVRational屬性如下:

  • int num: 有理數值的分子
  • int den: 有理數值的分母

該結構體涉及以下函數:

av_q2d

av_q2d是一個用於將AVRational轉換為double型別的函數,也就是將AVRational中的分子和分母相除。

double av_q2d(AVRational a);
  • a:需要轉換的AVRational型別的數值。

SwsContext

SwsContext是FFmpeg中用於影象轉換的資料結構,它包含了影象轉換所需的所有引數。要注意的是該結構體的原始定義在swscale_internal.h檔案中,普通編譯的ffmpeg工程沒有該檔案。所以該結構體一般僅僅是使用。SwsContext使用完畢需要手動釋放記憶體。SwsContext的主要引數如下:

  • int srcWint srcH:源影象的寬度和高度。
  • int dstWint dstH:目標影象的寬度和高度。
  • enum AVPixelFormat srcFormatenum AVPixelFormat dstFormat:源影象和目標影象的畫素格式。
  • int flags:影象轉換時的一些特殊選項,如是否進行區間縮放等。
  • double param:一些額外的引數,如亮度、對比度等。

在使用FFmpeg解碼時,預設解碼後影象的顏色格式為YUV420p,關於YUV420p介紹見YUV影象處理入門1

該結構體涉及以下函數:

sws_getContext

sws_getContext的作用是建立一個用於影象轉換的SwsContext結構體。如果建立成功,返回一個指向SwsContext結構體的指標,否則返回NULL。函數原型如下:

struct SwsContext *sws_getContext(
    int srcW,
    int srcH,
    enum AVPixelFormat srcFormat,
    int dstW,
    int dstH,
    enum AVPixelFormat dstFormat,
    int flags,
    SwsFilter *srcFilter,
    SwsFilter *dstFilter,
    const double *param
);
  • srcW:輸入影象寬度。
  • srcH:輸入影象高度。
  • srcFormat:輸入影象畫素格式。
  • dstW:輸出影象寬度。
  • dstH:輸出影象高度。
  • dstFormat:輸出影象畫素格式。
  • flags:轉換標誌,用於指定轉換演演算法和引數。常用設定如下:
    • SWS_FAST_BILINEAR:較快的雙線性轉換,適用於實時應用,但可能會有些失真。
    • SWS_BILINEAR:雙線性轉換,速度較快,但輸出質量較低。
    • SWS_BICUBIC:雙三次轉換,速度較慢,但輸出質量較高。
    • SWS_X:可自定義的轉換演演算法,速度和質量取決於具體實現。
    • SWS_POINT:轉換的速度非常快的最近鄰插值演演算法,但是轉換後的影象質量相對其他方法低。因為SWS_POINT將目標畫素點對映到影象時,直接使用最近的畫素點來進行對映,會導致轉換後的影象出現鋸齒狀的邊緣,而且影象的細節資訊也會丟失。
  • srcFilter:輸入影象過濾器,用於影象縮放和裁剪。
  • dstFilter:輸出影象過濾器,用於影象縮放和裁剪。
  • param:轉換引數,用於指定轉換演演算法的引數。

sws_scale

sws_scale用於執行多種不同的畫素格式轉換。sws_scale函數的返回值為輸出影象的高度,返回值小於等於0表示轉換失敗。

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);
  • c:可以通過sws_getContext函數獲取。
  • srcSlice[]:輸入影象資料指標陣列。
  • srcStride[]:輸入影象每行的位元組陣列。
  • srcSliceY:輸入影象的起始行。
  • srcSliceH:輸入影象的高度。
  • dst[]:輸出影象資料指標陣列。
  • dstStride[]:輸出影象每行的位元組陣列。

sws_freeContext

sws_freeContext的作用是釋放SwsContext結構體佔用的記憶體空間,避免記憶體洩露。

void sws_freeContext(struct SwsContext *context);
  • context:要釋放的SwsContext結構體指標。

AVDictionary

AVDictionary是FFmpeg中的一個字典結構體,用於儲存鍵值對資料。AVDictionary使用後一般不需要手動釋放記憶體,但是建議手動釋放記憶體。以下是AVDictionary屬性的說明:

  • int count:AVDictionary中鍵值對的數量。
  • AVDictionaryEntry * elems:指向AVDictionaryEntry結構體陣列的指標,每個元素包含一個鍵值對。

該結構體涉及以下函數:

av_dict_set

av_dict_set用於向字典中新增或修改鍵值對。該函數的返回值為0表示成功,否則表示失敗。av_dict_set可以設定的有效鍵值對需要參閱。

int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags)
  • pm:指向字典指標的指標。
  • key:要新增或修改的鍵名。
  • value:要新增或修改的鍵值。
  • flags:標誌位,控制鍵名是否可以覆蓋已存在的鍵名。

av_dict_free

av_dict_free用於釋放字典(dictionary)結構體佔用的記憶體空間。

void av_dict_free(AVDictionary **m)
  • m:指向AVDictionary指標的指標。

其他函數

avdevice_register_all

avdevice_register_all用於註冊所有可用的音視訊輸入/輸出裝置,以方便進行資料採集。

void avdevice_register_all(void);

av_find_input_format

av_find_input_format用於查詢輸入視訊流格式。該函數返回值是一個指向AVInputFormat結構體的指標。

AVInputFormat *av_find_input_format(const char *short_name);
  • short_name是待查詢的輸入流格式的短名稱。短名稱是該格式的簡稱,例如:
    • mp4:表示MP4格式。
    • vfwcap:是一個視訊捕獲裝置的輸入格式,用於Windows平臺。它使用VFW(Video for Windows)API來捕獲視訊資料。
    • dshow:是一個視訊捕獲裝置的輸入格式,用於Windows平臺。它使用DirectShow API來捕獲視訊資料。
    • video4linux2:是一個視訊捕獲裝置的輸入格式,用於Linux平臺。它使用Video4Linux2 API來捕獲視訊資料。
    • gdigrab:用於在Windows上捕獲螢幕的輸入格式。
    • x11grab:用於在Linux上捕獲螢幕的輸入格式。

3 參考

3.1 參考文章

3.2 ffmpeg結構體

3.3 ffmpeg函數