avio是FFmpeg中的一個模組,用於實現多種輸入輸出方式的封裝。
avio提供了一系列API,可以將資料從記憶體讀取到緩衝區中,也可以將緩衝區中的資料寫入到記憶體中。其實現依賴於IOContext結構體,該結構體定義了當前輸入/輸出事件的狀態、資料、回撥函數等資訊,並支援通過自定義回撥函數實現不同的輸入/輸出方式。
記憶體輸入(Memory Input)是指將資料從記憶體中讀取到緩衝區中,常見的應用場景包括:從記憶體中讀取音視訊資料進行解碼或處理。在使用avio實現記憶體輸入時,需要首先建立一個AVIOContext結構體,並將記憶體資料緩衝區作為引數傳遞給avio_open函數進行初始化。之後,可以使用avio_read函數從緩衝區中讀取資料,直至讀取完成。
記憶體輸出(Memory Output)是指將資料從緩衝區中寫入到記憶體中,常見的應用場景包括:將音視訊資料編碼並儲存到記憶體中。在使用avio實現記憶體輸出時,需要首先建立一個AVIOContext結構體,並將記憶體資料緩衝區和緩衝區大小作為引數傳遞給avio_open函數進行初始化。之後,可以使用avio_write函數將資料寫入緩衝區中,並在完成輸出後呼叫avio_close函數關閉AVIOContext結構體。
總的來說,記憶體輸入和輸出是指在使用FFmpeg進行音視訊處理時,將資料從記憶體中讀取或寫入到記憶體中的一種方式。使用avio模組可以方便地實現這種輸入輸出方式,並支援自定義回撥函數以滿足不同的應用需求。
使用FFmpeg的avio模組實現記憶體輸入和輸出有以下幾個優點:
傳統的音視訊處理方式往往需要將音視訊資料儲存到檔案中,然後再進行讀取和處理。而使用avio模組可以將資料直接讀取或寫入到記憶體中,從而提高了音視訊處理的靈活性。這種方式可以避免繁瑣的檔案IO操作,節省磁碟空間。
記憶體輸入和輸出功能可以方便地擴充套件為其他更高階的應用程式,例如:串流媒體伺服器、實時音視訊採集與處理等。這是因為記憶體輸出能夠較為輕鬆地將音視訊資料編碼並儲存到記憶體緩衝區中,進而交由網路傳輸;記憶體輸入則可直接從記憶體緩衝區獲取音視訊資料,快速響應使用者請求。
FFmpeg的avio模組提供了一系列API,可以通過設定回撥函數實現各種自定義功能。例如:自定義網路協定傳輸方式、增加錯誤重試機制、實現多路複用等。這使得處理器可以根據自己的需求對avio模組進行靈活設定,以最大限度地滿足不同場景下的業務需求。
因此,使用FFmpeg的avio模組實現記憶體輸入和輸出可以提高音視訊處理的效率,增加程式的靈活性和擴充套件性,同時還具有良好的可客製化性。
所有需要從輸入源中讀取資料的時刻,都將呼叫回撥函數。和輸入源是普通檔案相比,只不過輸入源變成了記憶體區,其他各種外在表現並無不同。
如下各函數在不同的階段從輸入源讀資料,都會呼叫回撥函數:
avformat_open_input() 從輸入源讀取封裝格式檔案頭
avformat_find_stream_info() 從輸入源讀取一段資料,嘗試解碼,以獲取流資訊
av_read_frame() 從輸入源讀取封包
#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");
//迴圈查詢視訊中包含的流資訊,直到找到視訊型別的流
//便將其記錄下來 儲存到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;
}
//根據解碼器引數來建立解碼器內容
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;
}
所有輸出資料的時刻,都將呼叫回撥函數。和輸出是普通檔案相比,只不過輸出變成了記憶體區,其他各種外在表現並無不同。
如下各函數在不同的階段會輸出資料,都會呼叫回撥函數:
avformat_write_header() 將流頭部資訊寫入輸出區
av_interleaved_write_frame() 將封包寫入輸出區
av_write_trailer() 將流尾部資訊寫入輸出區
//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;
}
/**
* 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中用於建立AVIOContext結構體的函數 avio_alloc_context 的程式碼註釋。
該函數具有以下引數:
buffer:儲存音視訊資料的記憶體緩衝區指標,必須通過 av_malloc() 等函數分配。該記憶體塊會被 AVIOContext 結構體參照,不能在生命週期內被釋放。
buffer_size:緩衝區大小,對於固定塊大小的協定需要設定為固定塊大小,對於其他協定可以設定為典型快取頁大小,例如 4KB。
write_flag:標記是否可寫,1 表示可寫,0 表示唯讀。
opaque:使用者指定的不透明指標,用於在回撥函數中攜帶自定義資料。
read_packet:read_packet 回撥函數,用於本地檔案或網路流傳輸時從輸入源中讀取資料。當 buffer 中的資料被消耗完後,呼叫此函數填充緩衝區。
write_packet:write_packet 回撥函數,在可寫模式下用於將緩衝區中的資料寫入輸出源,例如本地檔案或網路流。
seek:seek 回撥函數,用於跳轉到指定位元組位置。
該函數主要用於在 FFmpeg 內部建立一個 AVIOContext 結構體,該結構體用於管理讀取或寫入記憶體緩衝區的音視訊資料,並提供了一些 API 函數用於處理緩衝區資料。一旦建立了 AVIOContext 結構體,就可以通過呼叫 avio_open2() 函數來開啟對應的輸入或輸出資源,然後即可開始讀寫資料。
在使用完畢後,需要通過呼叫 avio_context_free() 函數來釋放 AVIOContext 結構體佔用的記憶體空間。
作業系統:win10 64位元
開發環境:VS2022
vcpkg命令:
vcpkg install ffmpeg:x64-windows