前言
- AVFormatContext是统领全局的基本结构体。主要用于处理封装和解封装,并可以从设备中采集数据。
- 该结构必须通过avformat_alloc_context函数进行初始化。
- AVFormatContext包含协议层,封装层,编解码层
结构体中文注释见下面文章:
FFmpeg源码分析:AVFormatContext结构体-优快云博客
协议层
AVFormatContext 的协议层,主要包括三大数据结构:AVIOContext, URLContext, URLProtocol
协议操作对象结构
相关数据结构关系:
- 协议(文件)操作的顶层结构是 AVIOContext,这个对象实现了带缓冲的读写操作;FFMPEG 的输入对象 AVFormat 的 pb 字段指向一个 AVIOContext。
- AVIOContext 的 opaque 实际指向一个 URLContext 对象, 这个对象封装了协议对象及协议操作对象, 其中 prot 指向具体的协议操作对象, priv_data 指向具体的协议对象。
- URLProtocol 为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联,比如,文件操作对象为 ff_file_protocol, 它关联的结构体是 FileContext。
AVIO实例1:打开本地文件或者网络直播流
准备好本地视频素材,我将其放到了 QT 工程文件的 debug 目录下
实例源码:
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/file.h"
}
int main(int argc, char *argv[])
{
qDebug() << "ffmpeg test sample" ;
avformat_network_init();
qDebug() << "hello ffmpeg!";
AVFormatContext* pFormatCtx = NULL;
AVInputFormat *pInputFmt = NULL;
qDebug() << "hello avformat_alloc_context()";
pFormatCtx = avformat_alloc_context();
qDebug() << "hello avformat_open_input";
if ( avformat_open_input(&pFormatCtx, "./debug/test.mp4", pInputFmt, NULL) < 0 ) {
qDebug() << "avformat open failed!";
goto quit;
} else {
qDebug() << "open stream success!";
}
if ( avformat_find_stream_info(pFormatCtx, NULL) < 0) {
qDebug() << "av_find_stream_info error!";
goto quit;
} else {
qDebug() << "av_find_stream_info success!";
qDebug() << "*******nb_streams = " << pFormatCtx->nb_streams;
}
quit:
//avformat_free_context(pFormatCtx); //打开导致crashed
avformat_close_input(&pFormatCtx); //包含avformat_free_context()
avformat_network_deinit();
return 0;
}
运行结果
MediaInfo查看
AVIO实例2:自定义AVIO
本次实战的目的与实战 1 的目的一致,均是分析输入文件的流数量,只不过本次实战重点突出使用我们自定义的 AVIO 来打开文件。
重点理解 AVFormatContext 的 pb 字段指向一个 AVIOContext,如何完成的关联和绑定
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/file.h"
}
int read_func(void *ptr, uint8_t *buf, int buf_size)
{
FILE *fp = (FILE*)ptr;
qDebug() << "fp: " << fp << "buf_size: " << buf_size << "buf: " << buf;
size_t size = fread(buf, 1, buf_size, fp);
int ret = size;
qDebug() << "Read size: " << size;
return ret;
}
int64_t seek_func(void *opaque, int64_t offset, int whence)
{
FILE *fp = (FILE*)opaque;
qDebug() << "seek fp: " << fp;
if ( whence == AVSEEK_SIZE ) {
return -1;
}
fseek(fp, offset, whence);
return ftell(fp);
}
int main(int argc, char *argv[])
{
qDebug() << "ffmpeg test sample" ;
int nRet = 0;
FILE *pFp = fopen("./debug/test.mp4", "rb");
//FILE *pFp = fopen("./debug/test.ts", "rb");
if ( !pFp ) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
exit(0);
}
size_t nBufSize = 10*1024;
//uint8_t *pBuf = (uint8_t*)malloc(nBufSize); //使用这个会导致程序奔溃,原因暂时未知
uint8_t *pBuf = (uint8_t*)av_malloc(nBufSize);
qDebug() << "pFp: " << pFp << "pBuf: " << pBuf;
AVFormatContext *pFormatCtx = NULL;
AVInputFormat *pInputFmt = NULL;
qDebug() << "hello avio_alloc_context";
AVIOContext *pIOCtx = avio_alloc_context(
pBuf,
nBufSize,
0,
pFp,
read_func,
NULL,
seek_func
);
if ( !pIOCtx ) {
nRet = AVERROR(ENOMEM);
exit(1);
}
qDebug() << "hello avformat_alloc_context";
pFormatCtx = avformat_alloc_context();
if ( ! pFormatCtx ) {
nRet = AVERROR(ENOMEM);
goto quit;
}
pFormatCtx->pb = pIOCtx; //关联,绑定
pFormatCtx->flags = AVFMT_FLAG_CUSTOM_IO;
qDebug() << "hello avformat_open_input";
//打开流
if ( avformat_open_input(&pFormatCtx, NULL, NULL, NULL) < 0 ) {
qDebug() << "avformat open failed!";
goto quit;
} else {
qDebug() << "open stream success!";
}
if ( avformat_find_stream_info(pFormatCtx, NULL) < 0 ) {
qDebug() << "avformat_find_stream_info error!";
goto quit;
} else {
qDebug() << "avformat_find_stream_info success!";
qDebug() << "*********nb_streams = " << pFormatCtx->nb_streams;
}
quit:
//av_free(pBuf); //资源释放导致程序奔溃
avformat_close_input(&pFormatCtx);
fclose(pFp);
return 0;
}
运行结果:
ffmpeg test sample
pFp: 0x7ffed1f44a90 pBuf: 0x9cdcc0
hello avio_alloc_context
hello avformat_alloc_context
hello avformat_open_input
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9cdcc0
Read size: 10240
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9d0d80
Read size: 10240
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9d0d80
Read size: 10240
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9d0d80
Read size: 10240
fp: 0x7ffed1f44a90 buf_size: 10980 buf: 0x9dff78
Read size: 10980
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9d0d80
Read size: 9269
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
open stream success!
seek fp: 0x7ffed1f44a90
fp: 0x7ffed1f44a90 buf_size: 10240 buf: 0x9d0d80
Read size: 10240
fp: 0x7ffed1f44a90 buf_size: 87292 buf: 0x5170816
Read size: 87292
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
seek fp: 0x7ffed1f44a90
avformat_find_stream_info success!
*********nb_streams = 2
avio 实战 3:自定义数据来源
本次实战的在实战 2 的基础上自定义了数据来源,即使用内存映射技术将输入视频文件映射到内存中。参考了 ffmepg 官方提供的测试用例avio_read_callback.c
av_file_map():读取文件,并将其内容放入新分配的缓冲区中。返回的缓冲区必须使用 av_file_unmap() 释放;
av_dump_format():它用于打印关于音频或视频文件格式的详细信息,例如有关音频或视频文件的格式、流和编解码器的详细信息。包括文件格式、编解码器信息、流参数和元数据等;
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavutil/file.h"
}
// 3. 自定义数据来源(可以是文件,内存,网络流)
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) // 0
return AVERROR_EOF;
printf("ptr:%p size:%d\n", bd->ptr, bd->size);
/// 灵活应用[内存buf]:读取的是内存,例如:加密播放器,这里解密
memcpy(buf, bd->ptr, buf_size);
bd->ptr += buf_size;
bd->size -= buf_size;
return buf_size;
}
int main(int argc, char *argv[])
{
struct buffer_data bd = { 0 };
// 1. 将文件映射到内存上
int nRet = av_file_map("./debug/test.mp4", &bd.ptr, &bd.size, 0, NULL);
if (nRet < 0)
{
av_log(NULL, AV_LOG_ERROR, "map file to memory faild!\n");
return -1;
}
av_log(NULL, AV_LOG_INFO, "map file to memory success!\n");
// 2. 创建AVFormatContext对象
AVFormatContext* pAVFmtCtx = NULL;
pAVFmtCtx = avformat_alloc_context();
if (NULL == pAVFmtCtx)
{
av_log(NULL, AV_LOG_ERROR, "create AVFormatContext faild!\n");
av_file_unmap(bd.ptr, bd.size); // 内存映射文件:解绑定
return -1;
}
av_log(NULL, AV_LOG_INFO, "create AVFormatContext success!\n");
// 3. 为avio buffer分配内存
uint8_t* avio_ctx_buffer = NULL;
size_t avio_ctx_buffer_size = 4096; // 回调每次读取 4096 bytes
avio_ctx_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size);
if (!avio_ctx_buffer)
{
av_log(NULL, AV_LOG_ERROR, "av_malloc avio_ctx_buffer faild!\n");
avformat_free_context(pAVFmtCtx);
av_file_unmap(bd.ptr, bd.size);
return -1;
}
av_log(NULL, AV_LOG_INFO, "av_malloc avio_ctx_buffer success!\n");
AVIOContext* avio_ctx = avio_alloc_context(
avio_ctx_buffer,
avio_ctx_buffer_size,
0,
&bd, // 从bd里面读数据
&read_packet,
NULL,
NULL);
if (!avio_ctx)
{
av_log(NULL, AV_LOG_ERROR, "avio_alloc_context AVIOContext faild!\n");
av_freep(avio_ctx_buffer);
avformat_free_context(pAVFmtCtx);
av_file_unmap(bd.ptr, bd.size);
return -1;
}
pAVFmtCtx->pb = avio_ctx;
av_log(NULL, AV_LOG_INFO, "avio_alloc_context AVIOContext success!\n");
/// 打开输入流
nRet = avformat_open_input(&pAVFmtCtx, NULL, NULL, NULL);
if (nRet)
{
av_log(NULL, AV_LOG_ERROR, "avformat_open_input faild!\n");
avio_context_free(&avio_ctx);
av_freep(avio_ctx_buffer);
avformat_free_context(pAVFmtCtx);
av_file_unmap(bd.ptr, bd.size);
return -1;
}
av_log(NULL, AV_LOG_INFO, "avformat_open_input success!\n");
// 查找流信息
nRet = avformat_find_stream_info(pAVFmtCtx, NULL);
if (nRet < 0)
{
av_log(NULL, AV_LOG_ERROR, "avformat_find_stream_info faild!\n");
avformat_close_input(&pAVFmtCtx);
avio_context_free(&avio_ctx);
av_freep(avio_ctx_buffer);
avformat_free_context(pAVFmtCtx);
av_file_unmap(bd.ptr, bd.size);
return -1;
}
av_log(NULL, AV_LOG_INFO, "avformat_find_stream_info success!\n");
printf(">>>nb_streams=%d\n", pAVFmtCtx->nb_streams);
av_dump_format(pAVFmtCtx, 0, "input_5s.mp4", 0);
// 关闭释放资源
avformat_close_input(&pAVFmtCtx);
if (avio_ctx)
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
/// 内存映射文件:解绑定
av_file_unmap(bd.ptr, bd.size);
return 0;
}
输出结果:
[mov,mp4,m4a,3gp,3g2,mj2 @ 000000000088A780] overread end of atom 'stsd' by 15 bytes
avformat_open_input success!
[mov,mp4,m4a,3gp,3g2,mj2 @ 000000000088A780] stream 1, offset 0xa8: partial file
[mov,mp4,m4a,3gp,3g2,mj2 @ 000000000088A780] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 2001 kb/s): unspecified size
Consider increasing the value for the 'analyzeduration' (0) and 'probesize' (5000000) options
avformat_find_stream_info success!
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input_5s.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp42isom
creation_time : 2022-04-01T17:28:33.000000Z
Duration: 00:02:59.97, start: 0.000000, bitrate: N/A
Stream #0:0[0x1](und): Video: h264 (avc1 / 0x31637661), none, 2001 kb/s, 24.93 fps, 25 tbr, 90k tbn (default)
Metadata:
creation_time : 2022-04-01T17:28:33.000000Z
vendor_id : [0][0][0][0]
encoder : JVT/AVC Coding
Stream #0:1[0x2](und): Audio: aac (mp4a / 0x6134706D), 8000 Hz, stereo, fltp, 61 kb/s (default)
Metadata:
creation_time : 2022-04-01T17:28:33.000000Z
vendor_id : [0][0][0][0]