AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的协议层,主要包括三大数据结构:AVIOContext
, URLContext
, URLProtocol
。
首先放两张图,具体看一下相关数据结构的关系
更详细的看下图:
AVIOContext
,这个对象实现了带缓冲的读写操作;FFMPEG 的输入对象 AVFormat
的 pb 字段指向一个 AVIOContext
。AVIOContext
的 opaque 实际指向一个 URLContext
对象, 这个对象封装了协议对象及协议操作对象, 其中 prot 指向具体的协议操作对象, priv_data 指向具体的协议对象。URLProtocol
为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联,比如,文件操作对象为 ff_file_protocol
, 它关联的结构体是 FileContext
。本次实战的目的是熟悉使用 avformat 相关 API,分析输入文件的流数量。
准备好本地视频素材,我将其放到了 QT 工程文件的 debug 目录下
extern "C"
{
#include
#include
#include
#include
};
int main(int argc, char* argv[])
{
/av_register_all();
avformat_network_init();
printf("hello,ffmpeg\n");
AVFormatContext* pFormatCtx = NULL;
AVInputFormat *piFmt = NULL;
printf("hello,avformat_alloc_context\n");
// Allocate the AVFormatContext:
pFormatCtx = avformat_alloc_context();
printf("hello,avformat_open_input\n");
//打开本地文件或网络直播流
//rtsp://127.0.0.1:8554/rtsp1 ----> 使用 VLC 推流
//./debug/test.mp4 ----> 使用本地文件
if (avformat_open_input(&pFormatCtx, "./debug/test.mp4", piFmt, NULL) < 0) {
printf("avformat open failed.\n");
goto quit;
}
else {
printf("open stream success!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0)
{
printf("av_find_stream_info error \n");
goto quit;
}
else {
printf("av_find_stream_info success \n");
printf("******nb_streams=%d\n",pFormatCtx->nb_streams);
}
quit:
avformat_free_context(pFormatCtx);
avformat_close_input(&pFormatCtx);
avformat_network_deinit();
return 0;
}
avformat_network_init()
:初始化网络模块。它在使用网络相关功能之前被调用,以确保网络功能的正确运行。avformat_alloc_context()
:用于分配和初始化 AVFormatContext 结构体,该结构体用于表示音视频格式上下文。avformat_open_input()
:用于打开音视频文件或流并将其解析为 AVFormatContext 结构体。avformat_find_stream_info()
:用于获取音视频文件或流的详细流信息并填充到 AVFormatContext 结构体中,如音视频流、时长、元数据等;出现程序异常结束的错误,如下图:
使用 debug 调试发现在 avformat_close_input()
出现了段错误:
查了好多资料,看了好多帖子也无济于事,索性看了一下 ffmepg 官方提供的测试用例,如下:
avio_read_callback.c
/*
* Copyright (c) 2014 Stefano Sabatini
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file libavformat AVIOContext read callback API usage example
* @example avio_read_callback.c
*
* Make libavformat demuxer access media content through a custom
* AVIOContext read callback.
*/
#include
#include
#include
#include
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 };
if (argc != 2) {
fprintf(stderr, "usage: %s input_file\n"
"API example program to show how to read from a custom buffer "
"accessed through AVIOContext.\n", argv[0]);
return 1;
}
input_filename = argv[1];
/* 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);
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_file_unmap(buffer, buffer_size);
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
这里有 avformat_alloc_context
操作,但是没有 avformat_free_context()
操作,既然官方用例都这么做,那我也索性按照官方测试用例的方法来。
我看到网上相关的资料要求必须要有这个 free 操作,但这里一旦加了 free 操作就会出现这样的错误,这个问题暂时放在这儿吧,也希望有懂得兄弟可以解释一下。
屏蔽 avformat_free_context()
:
再次运行,可以看到如下输出结果:
hello,ffmpeg
hello,avformat_alloc_context
hello,avformat_open_input
open stream success!
av_find_stream_info success
******nb_streams=2
可以看到输出信息为找到了两路流,分别是视频流与音频流,用 MediaInfo 查看 test.mp4 可以看到一致的效果
使用时可以通过 avformat_alloc_context
分配后使用,也可以直接 avformat_open_input
。
>//1、方法一
AVFormatContext *fmt_ctx = NULL;
string filename = "test.avi" ;
fmt_ctx = avformat_alloc_context();
avformat_open_input(&fmt_ctx, ilename.c_str(), NULL, NULL);
avformat_close_input(&fmt_ctx);
>//2、方法二
AVFormatContext *fmt_ctx = NULL;
string filename = "test.avi" ;
int ret = avformat_open_input(&fmt_ctx, filename.c_str(), NULL, NULL);
avformat_close_input(&fmt_ctx);
推荐使用方法二,因为若传进 avformat_open_input
的 fmt_ctx
为 NULL
,该函数内部会调用 avformat_alloc_context
函数。相应的 avformat_close_input
内部会调用 avformat_free_context
。
修改后的源码:
extern "C"
{
#include
#include
#include
#include
};
int main(int argc, char* argv[])
{
/av_register_all();
avformat_network_init();
printf("hello,ffmpeg\n");
AVFormatContext* pFormatCtx = NULL;
printf("hello,avformat_open_input\n");
//打开本地文件或网络直播流
//rtsp://127.0.0.1:8554/rtsp1
//./debug/test.mp4
if (avformat_open_input(&pFormatCtx, "./debug/test.mp4", NULL, NULL) < 0) {
printf("avformat open failed.\n");
goto quit;
}
else {
printf("open stream success!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0)
{
printf("av_find_stream_info error \n");
goto quit;
}
else {
printf("av_find_stream_info success \n");
printf("******nb_streams=%d\n",pFormatCtx->nb_streams);
}
quit:
avformat_close_input(&pFormatCtx);
avformat_network_deinit();
return 0;
运行结果:
hello,ffmpeg
hello,avformat_open_input
open stream success!
av_find_stream_info success
******nb_streams=2
本次实战的目的与实战 1 的目的一致,均是分析输入文件的流数量,只不过本次实战重点突出使用我们自定义的 AVIO 来打开文件。
重点理解 AVFormatContext
的 pb
字段指向一个 AVIOContext
,如何完成的关联和绑定
#include
#include
#include
#include
int read_func(void* ptr, uint8_t* buf, int buf_size)
{
FILE* fp = (FILE*)ptr;
size_t size = fread(buf, 1, buf_size, fp);
int ret = size;
printf("Read Bytes:%d\n", size);
return ret;
}
int64_t seek_func(void *opaque, int64_t offset, int whence)
{
int64_t ret;
FILE *fp = (FILE*)opaque;
if (whence == AVSEEK_SIZE) {
return -1;
}
fseek(fp, offset, whence);
return ftell(fp);
}
int main(int argc, char *argv[]){
///av_register_all();
printf("hello,ffmpeg\n");
int ret = 0;
FILE* fp = fopen("./debug/test.mp4", "rb");
int nBufferSize = 1024;
unsigned char* pBuffer = (unsigned char*)malloc(nBufferSize);
AVFormatContext* pFormatCtx = NULL;
AVInputFormat *piFmt = NULL;
printf("hello,avio_alloc_context\n");
// Allocate the AVIOContext:
AVIOContext* pIOCtx = avio_alloc_context(
pBuffer,
nBufferSize,
0,
fp,
read_func,
0,
seek_func);
printf("hello,avformat_alloc_context\n");
// Allocate the AVFormatContext:
pFormatCtx = avformat_alloc_context();
// Set the IOContext:
pFormatCtx->pb = pIOCtx;//关联,绑定
pFormatCtx->flags = AVFMT_FLAG_CUSTOM_IO;
printf("hello,avformat_open_input\n");
//打开流
if (avformat_open_input(&pFormatCtx, "", piFmt, NULL) < 0) {
printf("avformat open failed.\n");
goto quit;
}
else {
printf("open stream success!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0)
{
printf("av_find_stream_info error \n");
goto quit;
}
else {
printf("av_find_stream_info success \n");
printf("******nb_streams=%d\n",pFormatCtx->nb_streams);
}
quit:
//avformat_free_context(pFormatCtx);
avformat_close_input(&pFormatCtx);
free(pBuffer);
return 0;
}
avio_alloc_context()
:用于分配和初始化 AVIOContext 结构体。AVIOContext 结构体表示用于读取或写入数据的 I/O 上下文,它提供了一个抽象的层次,用于访问各种类型的数据。10:38:51: Starting D:\Project\Qt_Project\1-2_fmpeg431s5qtcodes\build-HelloFFmpeg-Desktop_Qt_5_14_2_MinGW_32_bit-Debug\debug\HelloFFmpeg.exe ...
hello,ffmpeg
hello,avio_alloc_context
hello,avformat_alloc_context
hello,avformat_open_input
Read Bytes:2048
Read Bytes:1024
Read Bytes:11457
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:21496
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:1024
Read Bytes:580
open stream success!
Read Bytes:1024
Read Bytes:105165
av_find_stream_info success
******nb_streams=2
avio 自定义数据来源:可以是文件, 可以是内存, 可以是网络
本次实战的在实战 2 的基础上自定义了数据来源,即使用内存映射技术将输入视频文件映射到内存中。参考了 ffmepg 官方提供的测试用例avio_read_callback.c
/**
* @file
* libavformat AVIOContext API example.
*
* Make libavformat demuxer access media content through a custom
* AVIOContext read callback.
* @example avio_reading.c
*/
#include
#include
#include
#include
//自定义缓冲区
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:%d\n", bd->ptr, bd->size);
/* copy internal buffer data to buf */
/// 灵活应用[内存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 };
printf("Hello,ffmpeg\n");
input_filename = "./debug/test.mp4";
/* slurp file content into buffer */
///内存映射文件
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
goto end;
printf("av_file_map,ok\n");
/* fill opaque structure used by the AVIOContext read callback */
bd.ptr = buffer;
bd.size = buffer_size;
/// 创建对象:AVFormatContext
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
printf("avformat_alloc_context,ok\n");
/// 分配内存
avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer) {
ret = AVERROR(ENOMEM);
goto end;
}
/// 创建对象:AVIOContext,注意参数
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;
printf("avio_alloc_context,ok\n");
/// 打开输入流
ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Could not open input\n");
goto end;
}
printf("avformat_open_input,ok\n");
/// 查找流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Could not find stream information\n");
goto end;
}
printf("avformat_find_stream_info,ok\n");
printf("******nb_streams=%d\n",fmt_ctx->nb_streams);
av_dump_format(fmt_ctx, 0, input_filename, 0);
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_file_unmap(buffer, buffer_size);
if (ret < 0) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
av_file_map()
:读取文件,并将其内容放入新分配的缓冲区中。返回的缓冲区必须使用 av_file_unmap()
释放;av_dump_format()
:它用于打印关于音频或视频文件格式的详细信息,例如有关音频或视频文件的格式、流和编解码器的详细信息。包括文件格式、编解码器信息、流参数和元数据等;Hello,ffmpeg
av_file_map,ok
avformat_alloc_context,ok
avio_alloc_context,ok
ptr:04650000 size:1247737
....
ptr:04780000 size:2553
avformat_open_input,ok
avformat_find_stream_info,ok
******nb_streams=2
[mov,mp4,m4a,3gp,3g2,mj2 @ 01eb0c40] stream 0, offset 0x30: partial file
[mov,mp4,m4a,3gp,3g2,mj2 @ 01eb0c40] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 1280x720, 1638 kb/s): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from './debug/output.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf60.3.100
Duration: 00:00:05.00, start: 0.000000, bitrate: N/A
Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 1280x720, 1638 kb/s, SAR 1:1 DAR 16:9, 25 fps, 25 tbr, 12800 tbn, 25600 tbc (default)
Metadata:
handler_name : VideoHandler
encoder : Lavc60.3.100 libx264
Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, 5.1, fltp, 348 kb/s (default)
Metadata:
handler_name : SoundHandler
我的qq:2442391036,欢迎交流!