最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

音視頻八股文(11)-- ffmpeg avio 內存輸入和內存輸出。內存輸出有完整代碼,網上很

2023-05-09 22:21 作者:福大大架構師每日一題  | 我要投稿

1.avio介紹

avio是FFmpeg中的一個模塊,用于實現(xiàn)多種輸入輸出方式的封裝。

avio提供了一系列API,可以將數據從內存讀取到緩沖區(qū)中,也可以將緩沖區(qū)中的數據寫入到內存中。其實現(xiàn)依賴于IOContext結構體,該結構體定義了當前輸入/輸出事件的狀態(tài)、數據、回調函數等信息,并支持通過自定義回調函數實現(xiàn)不同的輸入/輸出方式。

內存輸入(Memory Input)是指將數據從內存中讀取到緩沖區(qū)中,常見的應用場景包括:從內存中讀取音視頻數據進行解碼或處理。在使用avio實現(xiàn)內存輸入時,需要首先創(chuàng)建一個AVIOContext結構體,并將內存數據緩沖區(qū)作為參數傳遞給avio_open函數進行初始化。之后,可以使用avio_read函數從緩沖區(qū)中讀取數據,直至讀取完成。

內存輸出(Memory Output)是指將數據從緩沖區(qū)中寫入到內存中,常見的應用場景包括:將音視頻數據編碼并保存到內存中。在使用avio實現(xiàn)內存輸出時,需要首先創(chuàng)建一個AVIOContext結構體,并將內存數據緩沖區(qū)和緩沖區(qū)大小作為參數傳遞給avio_open函數進行初始化。之后,可以使用avio_write函數將數據寫入緩沖區(qū)中,并在完成輸出后調用avio_close函數關閉AVIOContext結構體。

總的來說,內存輸入和輸出是指在使用FFmpeg進行音視頻處理時,將數據從內存中讀取或寫入到內存中的一種方式。使用avio模塊可以方便地實現(xiàn)這種輸入輸出方式,并支持自定義回調函數以滿足不同的應用需求。

2.為什么要用avio?

使用FFmpeg的avio模塊實現(xiàn)內存輸入和輸出有以下幾個優(yōu)點:

2.1.靈活性高

傳統(tǒng)的音視頻處理方式往往需要將音視頻數據保存到文件中,然后再進行讀取和處理。而使用avio模塊可以將數據直接讀取或寫入到內存中,從而提高了音視頻處理的靈活性。這種方式可以避免繁瑣的文件IO操作,節(jié)省磁盤空間。

2.2.擴展性強

內存輸入和輸出功能可以方便地擴展為其他更高級的應用程序,例如:流媒體服務器、實時音視頻采集與處理等。這是因為內存輸出能夠較為輕松地將音視頻數據編碼并存儲到內存緩沖區(qū)中,進而交由網絡傳輸;內存輸入則可直接從內存緩沖區(qū)獲取音視頻數據,快速響應用戶請求。

2.3.可定制性好

FFmpeg的avio模塊提供了一系列API,可以通過設置回調函數實現(xiàn)各種自定義功能。例如:自定義網絡協(xié)議傳輸方式、增加錯誤重試機制、實現(xiàn)多路復用等。這使得處理器可以根據自己的需求對avio模塊進行靈活配置,以最大限度地滿足不同場景下的業(yè)務需求。

因此,使用FFmpeg的avio模塊實現(xiàn)內存輸入和輸出可以提高音視頻處理的效率,增加程序的靈活性和擴展性,同時還具有良好的可定制性。

3.內存區(qū)作為輸入

3.1.回調函數何時被回調呢?

所有需要從輸入源中讀取數據的時刻,都將調用回調函數。和輸入源是普通文件相比,只不過輸入源變成了內存區(qū),其他各種外在表現(xiàn)并無不同。

如下各函數在不同的階段從輸入源讀數據,都會調用回調函數:

avformat_open_input() 從輸入源讀取封裝格式文件頭

avformat_find_stream_info() 從輸入源讀取一段數據,嘗試解碼,以獲取流信息

av_read_frame() 從輸入源讀取數據包

3.2.該示例作用是統(tǒng)計mp4文件的視頻幀數,代碼如下:

#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)查找視頻中包含的流信息,直到找到視頻類型的流
????//便將其記錄下來?保存到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?說明沒有找到視頻流
????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;
????}
????//根據解碼器參數來創(chuàng)建解碼器內容
????codecCtx?=?avcodec_alloc_context3(codec);
????avcodec_parameters_to_context(codecCtx,?avCodecPara);
????if?(codecCtx?==?NULL)?{
????????printf("Cannot?alloc?context.");
????????goto?end;
????}

????//================================??打開解碼器?===================================//
????if?((ret?=?avcodec_open2(codecCtx,?codec,?NULL))?<?0)?{?//?具體采用什么解碼器ffmpeg經過封裝?我們無須知道
????????printf("cannot?open?decoder\n");
????????goto?end;
????}

????//===========================?分配AVPacket結構體?===============================//
????int???????i?=?0;//用于幀計數
????pkt?=?av_packet_alloc();??????????????????????//分配一個packet
????av_new_packet(pkt,?codecCtx->width?*?codecCtx->height);?//調整packet的數據

????//===========================??讀取視頻信息?===============================//
????while?(av_read_frame(fmt_ctx,?pkt)?>=?0)?{?//讀取的是一幀視頻??數據存入一個AVPacket的結構中
????????if?(pkt->stream_index?==?videoStreamIndex)?{
????????????i++;//只計算視頻幀
????????}
????????av_packet_unref(pkt);//重置pkt的內容
????}
????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.內存區(qū)作為輸出

4.1.回調函數何時被回調呢?

所有輸出數據的時刻,都將調用回調函數。和輸出是普通文件相比,只不過輸出變成了內存區(qū),其他各種外在表現(xiàn)并無不同。

如下各函數在不同的階段會輸出數據,都會調用回調函數:

avformat_write_header() 將流頭部信息寫入輸出區(qū)

av_interleaved_write_frame() 將數據包寫入輸出區(qū)

av_write_trailer() 將流尾部信息寫入輸出區(qū)

4.2.該示例作用是提取mp4文件的視頻幀為h264文件,輸出采用write_packet回調,代碼如下:

//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.內存IO模式非常重要的一個函數: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結構體的函數 avio_alloc_context 的代碼注釋。

該函數具有以下參數:

  • ??buffer:存儲音視頻數據的內存緩沖區(qū)指針,必須通過 av_malloc() 等函數分配。該內存塊會被 AVIOContext 結構體引用,不能在生命周期內被釋放。

  • ??buffer_size:緩沖區(qū)大小,對于固定塊大小的協(xié)議需要設置為固定塊大小,對于其他協(xié)議可以設置為典型緩存頁大小,例如 4KB。

  • ??write_flag:標記是否可寫,1 表示可寫,0 表示只讀。

  • ??opaque:用戶指定的不透明指針,用于在回調函數中攜帶自定義數據。

  • ??read_packet:read_packet 回調函數,用于本地文件或網絡流傳輸時從輸入源中讀取數據。當 buffer 中的數據被消耗完后,調用此函數填充緩沖區(qū)。

  • ??write_packet:write_packet 回調函數,在可寫模式下用于將緩沖區(qū)中的數據寫入輸出源,例如本地文件或網絡流。

  • ??seek:seek 回調函數,用于跳轉到指定字節(jié)位置。

該函數主要用于在 FFmpeg 內部創(chuàng)建一個 AVIOContext 結構體,該結構體用于管理讀取或寫入內存緩沖區(qū)的音視頻數據,并提供了一些 API 函數用于處理緩沖區(qū)數據。一旦創(chuàng)建了 AVIOContext 結構體,就可以通過調用 avio_open2() 函數來打開對應的輸入或輸出資源,然后即可開始讀寫數據。

在使用完畢后,需要通過調用 avio_context_free() 函數來釋放 AVIOContext 結構體占用的內存空間。

6.兩個示例的環(huán)境

操作系統(tǒng):win10 64位

開發(fā)環(huán)境:VS2022

vcpkg命令:

vcpkg?install?ffmpeg:x64-windows

圖片
在這里插入圖片描述

福大大架構師每日一題

java當死,golang當立。最新面試題,涉及golang,rust,mysql,redis,云原生,算法,分布式,網絡,操作系統(tǒng)。

501篇原創(chuàng)內容

公眾號



音視頻八股文(11)-- ffmpeg avio 內存輸入和內存輸出。內存輸出有完整代碼,網上很的評論 (共 條)

分享到微博請遵守國家法律
卢龙县| 晋宁县| 井研县| 安宁市| 岳西县| 益阳市| 霍州市| 乌苏市| 米脂县| 宾阳县| 青阳县| 晋州市| 神木县| 农安县| 民丰县| 新丰县| 锦州市| 秀山| 东安县| 鸡东县| 子洲县| 沁水县| 开阳县| 祁门县| 瑞丽市| 海林市| 南木林县| 大悟县| 喀什市| 开封市| 伽师县| 勃利县| 通江县| 平陆县| 余江县| 兴业县| 玛多县| 阿尔山市| 长岛县| 石泉县| 迁西县|