AVFormatContext协议层


前言

  • 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 目录下
image.png

实例源码:

#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;
}

运行结果

image.png

MediaInfo查看

image.png

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]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值