1.avio介紹
avio是FFmpeg中的一個(gè)模塊,用于實(shí)現(xiàn)多種輸入輸出方式的封裝。
avio提供了一系列API,可以將數(shù)據(jù)從內(nèi)存讀取到緩沖區(qū)中,也可以將緩沖區(qū)中的數(shù)據(jù)寫(xiě)入到內(nèi)存中。其實(shí)現(xiàn)依賴于IOContext結(jié)構(gòu)體,該結(jié)構(gòu)體定義了當(dāng)前輸入/輸出事件的狀態(tài)、數(shù)據(jù)、回調(diào)函數(shù)等信息,并支持通過(guò)自定義回調(diào)函數(shù)實(shí)現(xiàn)不同的輸入/輸出方式。
內(nèi)存輸入(Memory Input)是指將數(shù)據(jù)從內(nèi)存中讀取到緩沖區(qū)中,常見(jiàn)的應(yīng)用場(chǎng)景包括:從內(nèi)存中讀取音視頻數(shù)據(jù)進(jìn)行解碼或處理。在使用avio實(shí)現(xiàn)內(nèi)存輸入時(shí),需要首先創(chuàng)建一個(gè)AVIOContext結(jié)構(gòu)體,并將內(nèi)存數(shù)據(jù)緩沖區(qū)作為參數(shù)傳遞給avio_open函數(shù)進(jìn)行初始化。之后,可以使用avio_read函數(shù)從緩沖區(qū)中讀取數(shù)據(jù),直至讀取完成。
內(nèi)存輸出(Memory Output)是指將數(shù)據(jù)從緩沖區(qū)中寫(xiě)入到內(nèi)存中,常見(jiàn)的應(yīng)用場(chǎng)景包括:將音視頻數(shù)據(jù)編碼并保存到內(nèi)存中。在使用avio實(shí)現(xiàn)內(nèi)存輸出時(shí),需要首先創(chuàng)建一個(gè)AVIOContext結(jié)構(gòu)體,并將內(nèi)存數(shù)據(jù)緩沖區(qū)和緩沖區(qū)大小作為參數(shù)傳遞給avio_open函數(shù)進(jìn)行初始化。之后,可以使用avio_write函數(shù)將數(shù)據(jù)寫(xiě)入緩沖區(qū)中,并在完成輸出后調(diào)用avio_close函數(shù)關(guān)閉AVIOContext結(jié)構(gòu)體。
總的來(lái)說(shuō),內(nèi)存輸入和輸出是指在使用FFmpeg進(jìn)行音視頻處理時(shí),將數(shù)據(jù)從內(nèi)存中讀取或?qū)懭氲絻?nèi)存中的一種方式。使用avio模塊可以方便地實(shí)現(xiàn)這種輸入輸出方式,并支持自定義回調(diào)函數(shù)以滿足不同的應(yīng)用需求。
2.為什么要用avio?
使用FFmpeg的avio模塊實(shí)現(xiàn)內(nèi)存輸入和輸出有以下幾個(gè)優(yōu)點(diǎn):
2.1.靈活性高
傳統(tǒng)的音視頻處理方式往往需要將音視頻數(shù)據(jù)保存到文件中,然后再進(jìn)行讀取和處理。而使用avio模塊可以將數(shù)據(jù)直接讀取或?qū)懭氲絻?nèi)存中,從而提高了音視頻處理的靈活性。這種方式可以避免繁瑣的文件IO操作,節(jié)省磁盤(pán)空間。
2.2.擴(kuò)展性強(qiáng)
內(nèi)存輸入和輸出功能可以方便地?cái)U(kuò)展為其他更高級(jí)的應(yīng)用程序,例如:流媒體服務(wù)器、實(shí)時(shí)音視頻采集與處理等。這是因?yàn)閮?nèi)存輸出能夠較為輕松地將音視頻數(shù)據(jù)編碼并存儲(chǔ)到內(nèi)存緩沖區(qū)中,進(jìn)而交由網(wǎng)絡(luò)傳輸;內(nèi)存輸入則可直接從內(nèi)存緩沖區(qū)獲取音視頻數(shù)據(jù),快速響應(yīng)用戶請(qǐng)求。
2.3.可定制性好
FFmpeg的avio模塊提供了一系列API,可以通過(guò)設(shè)置回調(diào)函數(shù)實(shí)現(xiàn)各種自定義功能。例如:自定義網(wǎng)絡(luò)協(xié)議傳輸方式、增加錯(cuò)誤重試機(jī)制、實(shí)現(xiàn)多路復(fù)用等。這使得處理器可以根據(jù)自己的需求對(duì)avio模塊進(jìn)行靈活配置,以最大限度地滿足不同場(chǎng)景下的業(yè)務(wù)需求。
因此,使用FFmpeg的avio模塊實(shí)現(xiàn)內(nèi)存輸入和輸出可以提高音視頻處理的效率,增加程序的靈活性和擴(kuò)展性,同時(shí)還具有良好的可定制性。
3.內(nèi)存區(qū)作為輸入
3.1.回調(diào)函數(shù)何時(shí)被回調(diào)呢?
所有需要從輸入源中讀取數(shù)據(jù)的時(shí)刻,都將調(diào)用回調(diào)函數(shù)。和輸入源是普通文件相比,只不過(guò)輸入源變成了內(nèi)存區(qū),其他各種外在表現(xiàn)并無(wú)不同。
如下各函數(shù)在不同的階段從輸入源讀數(shù)據(jù),都會(huì)調(diào)用回調(diào)函數(shù):
avformat_open_input() 從輸入源讀取封裝格式文件頭
avformat_find_stream_info() 從輸入源讀取一段數(shù)據(jù),嘗試解碼,以獲取流信息
av_read_frame() 從輸入源讀取數(shù)據(jù)包
3.2.該示例作用是統(tǒng)計(jì)mp4文件的視頻幀數(shù),代碼如下:
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>
#define INPUT_FILE "1.mp4"
struct buffer_data {
uint8_t* ptr;
size_t size; ///< size left in the buffer
};
static int read_packet(void* opaque, uint8_t* buf, int buf_size)
{
struct buffer_data* bd = (struct buffer_data*)opaque;
buf_size = FFMIN(buf_size, bd->size);
if (!buf_size)
return AVERROR_EOF;
printf("ptr:%p size:%zu\n", bd->ptr, bd->size);
/* copy internal buffer data to buf */
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
return buf_size;
}
int main(int argc, char* argv[])
{
AVFormatContext* fmt_ctx = NULL;
AVIOContext* avio_ctx = NULL;
uint8_t* buffer = NULL, * avio_ctx_buffer = NULL;
size_t buffer_size, avio_ctx_buffer_size = 4096;
char* input_filename = NULL;
int ret = 0;
struct buffer_data bd = { 0 };
int videoStreamIndex = -1;
AVCodecParameters* avCodecPara = NULL;
const AVCodec* codec = NULL;
AVCodecContext* codecCtx = NULL;
AVPacket* pkt = NULL;
input_filename = INPUT_FILE;
/* slurp file content into buffer */
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
goto end;
/* fill opaque structure used by the AVIOContext read callback */
bd.ptr = buffer;
bd.size = buffer_size;
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
goto end;
}
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
0, &bd, &read_packet, NULL, NULL);
if (!avio_ctx) {
ret = AVERROR(ENOMEM);
goto end;
}
fmt_ctx->pb = avio_ctx;
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Could not open input\n");
goto end;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Could not find stream information\n");
goto end;
}
//av_dump_format(fmt_ctx, 0, input_filename, 0);
printf("完成\n");
//循環(huán)查找視頻中包含的流信息,直到找到視頻類型的流
//便將其記錄下來(lái) 保存到videoStreamIndex變量中
for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;//找到視頻流就退出
}
}
//如果videoStream為-1 說(shuō)明沒(méi)有找到視頻流
if (videoStreamIndex == -1) {
printf("cannot find video stream\n");
goto end;
}
//================================= 查找解碼器 ===================================//
avCodecPara = fmt_ctx->streams[videoStreamIndex]->codecpar;
codec = avcodec_find_decoder(avCodecPara->codec_id);
if (codec == NULL) {
printf("cannot find decoder\n");
goto end;
}
//根據(jù)解碼器參數(shù)來(lái)創(chuàng)建解碼器內(nèi)容
codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx, avCodecPara);
if (codecCtx == NULL) {
printf("Cannot alloc context.");
goto end;
}
//================================ 打開(kāi)解碼器 ===================================//
if ((ret = avcodec_open2(codecCtx, codec, NULL)) < 0) { // 具體采用什么解碼器ffmpeg經(jīng)過(guò)封裝 我們無(wú)須知道
printf("cannot open decoder\n");
goto end;
}
//=========================== 分配AVPacket結(jié)構(gòu)體 ===============================//
int i = 0;//用于幀計(jì)數(shù)
pkt = av_packet_alloc(); //分配一個(gè)packet
av_new_packet(pkt, codecCtx->width * codecCtx->height); //調(diào)整packet的數(shù)據(jù)
//=========================== 讀取視頻信息 ===============================//
while (av_read_frame(fmt_ctx, pkt) >= 0) { //讀取的是一幀視頻 數(shù)據(jù)存入一個(gè)AVPacket的結(jié)構(gòu)中
if (pkt->stream_index == videoStreamIndex) {
i++;//只計(jì)算視頻幀
}
av_packet_unref(pkt);//重置pkt的內(nèi)容
}
printf("There are %d frames int total.\n", i);
end:
avformat_close_input(&fmt_ctx);
/* note: the internal buffer could have changed, and be != avio_ctx_buffer */
if (avio_ctx)
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
av_packet_free(&pkt);
avcodec_close(codecCtx);
av_file_unmap(buffer, buffer_size);
avformat_free_context(fmt_ctx);
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
4.內(nèi)存區(qū)作為輸出
4.1.回調(diào)函數(shù)何時(shí)被回調(diào)呢?
所有輸出數(shù)據(jù)的時(shí)刻,都將調(diào)用回調(diào)函數(shù)。和輸出是普通文件相比,只不過(guò)輸出變成了內(nèi)存區(qū),其他各種外在表現(xiàn)并無(wú)不同。
如下各函數(shù)在不同的階段會(huì)輸出數(shù)據(jù),都會(huì)調(diào)用回調(diào)函數(shù):
avformat_write_header() 將流頭部信息寫(xiě)入輸出區(qū)
av_interleaved_write_frame() 將數(shù)據(jù)包寫(xiě)入輸出區(qū)
av_write_trailer() 將流尾部信息寫(xiě)入輸出區(qū)
4.2.該示例作用是提取mp4文件的視頻幀為h264文件,輸出采用write_packet回調(diào),代碼如下:
//https://www.cnblogs.com/leisure_chn/p/10318145.html
#include <stdio.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#define INPUT_FILE "1.mp4"
#define OUTPUT_FILE "output.h264"
typedef struct {
FILE* fp;
} OutputContext;
static int write_packet(void* opaque, uint8_t* buf, int buf_size)
{
OutputContext* output_ctx = (OutputContext*)opaque;
FILE* fp = output_ctx->fp;
fwrite(buf, 1, buf_size, fp);
return buf_size;
}
int main(int argc, char* argv[])
{
AVFormatContext* input_ctx = NULL;
AVOutputFormat* output_fmt = NULL;
AVFormatContext* output_ctx = NULL;
OutputContext* output_opaque = NULL;
int ret = avformat_open_input(&input_ctx, INPUT_FILE, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Error: Could not open input file: %s.\n", av_err2str(ret));
goto end;
}
ret = avformat_find_stream_info(input_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error: Could not find stream information: %s.\n", av_err2str(ret));
goto end;
}
output_fmt = av_guess_format("h264", NULL, NULL);
if (!output_fmt) {
fprintf(stderr, "Error: Could not guess output format.\n");
ret = AVERROR_MUXER_NOT_FOUND;
goto end;
}
ret = avformat_alloc_output_context2(&output_ctx, output_fmt, NULL, OUTPUT_FILE);
if (ret < 0) {
fprintf(stderr, "Error: Could not allocate output context: %s.\n", av_err2str(ret));
goto end;
}
AVStream* in_video_stream = NULL;
AVCodecParameters* in_codec_params = NULL;
for (int i = 0; i < input_ctx->nb_streams; i++) {
AVStream* stream = input_ctx->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
in_video_stream = stream;
in_codec_params = stream->codecpar;
break;
}
}
if (!in_video_stream) {
fprintf(stderr, "Error: Could not find video stream.\n");
ret = AVERROR(ENOSYS);
goto end;
}
AVStream* out_video_stream = avformat_new_stream(output_ctx, NULL);
if (!out_video_stream) {
fprintf(stderr, "Error: Could not create new stream.\n");
ret = AVERROR(ENOMEM);
goto end;
}
ret = avcodec_parameters_copy(out_video_stream->codecpar, in_codec_params);
if (ret < 0) {
fprintf(stderr, "Error: Could not copy codec parameters: %s.\n", av_err2str(ret));
goto end;
}
out_video_stream->codecpar->codec_tag = 0;
output_opaque = av_malloc(sizeof(OutputContext));
if (!output_opaque) {
fprintf(stderr, "Error: Could not allocate output context.\n");
ret = AVERROR(ENOMEM);
goto end;
}
fopen_s(&output_opaque->fp, OUTPUT_FILE, "wb");
if (!output_opaque->fp) {
fprintf(stderr, "Error: Could not open output file.\n");
ret = AVERROR(ENOENT);
goto end;
}
AVIOContext* pb = NULL;
pb = avio_alloc_context((unsigned char*)av_malloc(32768), 32768, 1, output_opaque, NULL, &write_packet, NULL);
if (!pb) {
fprintf(stderr, "Error: Could not allocate output IO context.\n");
ret = AVERROR(ENOMEM);
goto end;
}
output_ctx->pb = pb;
ret = avformat_write_header(output_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error: Could not write header: %s.\n", av_err2str(ret));
goto end;
}
AVPacket packet = { 0 };
while (av_read_frame(input_ctx, &packet) >= 0) {
if (packet.stream_index == in_video_stream->index) {
av_packet_rescale_ts(&packet, in_video_stream->time_base, out_video_stream->time_base);
packet.stream_index = out_video_stream->index;
ret = av_interleaved_write_frame(output_ctx, &packet);
if (ret < 0) {
fprintf(stderr, "Error: Could not write frame: %s.\n", av_err2str(ret));
goto end;
}
}
av_packet_unref(&packet);
}
ret = av_write_trailer(output_ctx);
if (ret < 0) {
fprintf(stderr, "Error: Could not write trailer: %s.\n", av_err2str(ret));
goto end;
}
printf("Conversion complete!\n");
end:
if (input_ctx) {
avformat_close_input(&input_ctx);
}
if (output_ctx) {
if (output_ctx->pb) {
av_freep(&output_ctx->pb->buffer);
avio_context_free(&output_ctx->pb);
}
if (output_opaque->fp) {
fclose(output_opaque->fp);
}
avformat_free_context(output_ctx);
av_free(output_opaque);
}
return ret;
}
5.內(nèi)存IO模式非常重要的一個(gè)函數(shù):avio_alloc_context()
/**
* Allocate and initialize an AVIOContext for buffered I/O. It must be later
* freed with avio_context_free().
*
* @param buffer Memory block for input/output operations via AVIOContext.
* The buffer must be allocated with av_malloc() and friends.
* It may be freed and replaced with a new buffer by libavformat.
* AVIOContext.buffer holds the buffer currently in use,
* which must be later freed with av_free().
* @param buffer_size The buffer size is very important for performance.
* For protocols with fixed blocksize it should be set to this blocksize.
* For others a typical size is a cache page, e.g. 4kb.
* @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
* @param opaque An opaque pointer to user-specific data.
* @param read_packet A function for refilling the buffer, may be NULL.
* For stream protocols, must never return 0 but rather
* a proper AVERROR code.
* @param write_packet A function for writing the buffer contents, may be NULL.
* The function may not change the input buffers content.
* @param seek A function for seeking to specified byte position, may be NULL.
*
* @return Allocated AVIOContext or NULL on failure.
*/
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence));
這是FFmpeg中用于創(chuàng)建AVIOContext結(jié)構(gòu)體的函數(shù) avio_alloc_context 的代碼注釋。
該函數(shù)具有以下參數(shù):
-
buffer:存儲(chǔ)音視頻數(shù)據(jù)的內(nèi)存緩沖區(qū)指針,必須通過(guò) av_malloc() 等函數(shù)分配。該內(nèi)存塊會(huì)被 AVIOContext 結(jié)構(gòu)體引用,不能在生命周期內(nèi)被釋放。
-
buffer_size:緩沖區(qū)大小,對(duì)于固定塊大小的協(xié)議需要設(shè)置為固定塊大小,對(duì)于其他協(xié)議可以設(shè)置為典型緩存頁(yè)大小,例如 4KB。
-
write_flag:標(biāo)記是否可寫(xiě),1 表示可寫(xiě),0 表示只讀。
-
opaque:用戶指定的不透明指針,用于在回調(diào)函數(shù)中攜帶自定義數(shù)據(jù)。
-
read_packet:read_packet 回調(diào)函數(shù),用于本地文件或網(wǎng)絡(luò)流傳輸時(shí)從輸入源中讀取數(shù)據(jù)。當(dāng) buffer 中的數(shù)據(jù)被消耗完后,調(diào)用此函數(shù)填充緩沖區(qū)。
-
write_packet:write_packet 回調(diào)函數(shù),在可寫(xiě)模式下用于將緩沖區(qū)中的數(shù)據(jù)寫(xiě)入輸出源,例如本地文件或網(wǎng)絡(luò)流。
-
seek:seek 回調(diào)函數(shù),用于跳轉(zhuǎn)到指定字節(jié)位置。
該函數(shù)主要用于在 FFmpeg 內(nèi)部創(chuàng)建一個(gè) AVIOContext 結(jié)構(gòu)體,該結(jié)構(gòu)體用于管理讀取或?qū)懭雰?nèi)存緩沖區(qū)的音視頻數(shù)據(jù),并提供了一些 API 函數(shù)用于處理緩沖區(qū)數(shù)據(jù)。一旦創(chuàng)建了 AVIOContext 結(jié)構(gòu)體,就可以通過(guò)調(diào)用 avio_open2() 函數(shù)來(lái)打開(kāi)對(duì)應(yīng)的輸入或輸出資源,然后即可開(kāi)始讀寫(xiě)數(shù)據(jù)。
在使用完畢后,需要通過(guò)調(diào)用 avio_context_free() 函數(shù)來(lái)釋放 AVIOContext 結(jié)構(gòu)體占用的內(nèi)存空間。
6.兩個(gè)示例的環(huán)境
操作系統(tǒng):win10 64位
開(kāi)發(fā)環(huán)境:VS2022
vcpkg命令:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-437569.html
vcpkg install ffmpeg:x64-windows
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-437569.html
到了這里,關(guān)于音視頻八股文(11)-- ffmpeg avio 內(nèi)存輸入和內(nèi)存輸出。內(nèi)存輸出有完整代碼,網(wǎng)上很少有的。的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!