AVFormatContext协议层:理论与实战

文章目录

  • 前言
  • 一、协议操作对象结构
  • 二、初始化 AVIOContext 函数调用关系
  • 三、avio 实战 1:打开本地文件或网络直播流
    • 1、示例源码
    • 2、运行结果
      • ①、解决方法 1
      • ②、解决方法 2
  • 四、avio 实战 2:自定义 AVIO
    • 1、示例源码
    • 2、运行结果
  • 五、avio 实战 3:自定义数据来源
    • 1、示例源码
    • 2、运行结果


前言

AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的协议层,主要包括三大数据结构:AVIOContext, URLContext, URLProtocol

AVFormatContext协议层:理论与实战_第1张图片


一、协议操作对象结构

首先放两张图,具体看一下相关数据结构的关系

更详细的看下图:
AVFormatContext协议层:理论与实战_第2张图片

  • 协议(文件)操作的顶层结构是 AVIOContext,这个对象实现了带缓冲的读写操作;FFMPEG 的输入对象 AVFormat pb 字段指向一个 AVIOContext
  • AVIOContextopaque 实际指向一个 URLContext 对象, 这个对象封装了协议对象及协议操作对象, 其中 prot 指向具体的协议操作对象, priv_data 指向具体的协议对象。
  • URLProtocol 为协议操作对象,针对每种协议,会有一个这样的对象,每个协议操作对象和一个协议对象关联,比如,文件操作对象为 ff_file_protocol, 它关联的结构体是 FileContext

二、初始化 AVIOContext 函数调用关系

初始化 AVIOFormat 函数调用关系:
AVFormatContext协议层:理论与实战_第3张图片

三、avio 实战 1:打开本地文件或网络直播流

本次实战的目的是熟悉使用 avformat 相关 API,分析输入文件的流数量

准备好本地视频素材,我将其放到了 QT 工程文件的 debug 目录下
AVFormatContext协议层:理论与实战_第4张图片

1、示例源码

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 结构体中,如音视频流、时长、元数据等;

2、运行结果

出现程序异常结束的错误,如下图:
AVFormatContext协议层:理论与实战_第5张图片
使用 debug 调试发现在 avformat_close_input() 出现了段错误:
AVFormatContext协议层:理论与实战_第6张图片

①、解决方法 1

查了好多资料,看了好多帖子也无济于事,索性看了一下 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()
AVFormatContext协议层:理论与实战_第7张图片
再次运行,可以看到如下输出结果:

hello,ffmpeg
hello,avformat_alloc_context
hello,avformat_open_input
open stream success!
av_find_stream_info success 
******nb_streams=2

AVFormatContext协议层:理论与实战_第8张图片
可以看到输出信息为找到了两路流,分别是视频流与音频流,用 MediaInfo 查看 test.mp4 可以看到一致的效果
AVFormatContext协议层:理论与实战_第9张图片

②、解决方法 2

使用时可以通过 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_inputfmt_ctxNULL,该函数内部会调用 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

四、avio 实战 2:自定义 AVIO

本次实战的目的与实战 1 的目的一致,均是分析输入文件的流数量,只不过本次实战重点突出使用我们自定义的 AVIO 来打开文件。

重点理解 AVFormatContextpb 字段指向一个 AVIOContext,如何完成的关联和绑定

1、示例源码

#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 上下文,它提供了一个抽象的层次,用于访问各种类型的数据。

2、运行结果

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 实战 3:自定义数据来源

avio 自定义数据来源:可以是文件, 可以是内存, 可以是网络

本次实战的在实战 2 的基础上自定义了数据来源,即使用内存映射技术将输入视频文件映射到内存中。参考了 ffmepg 官方提供的测试用例avio_read_callback.c

1、示例源码

/**
 * @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():它用于打印关于音频或视频文件格式的详细信息,例如有关音频或视频文件的格式、流和编解码器的详细信息。包括文件格式、编解码器信息、流参数和元数据等;

2、运行结果

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,欢迎交流!


你可能感兴趣的:(音视频开发,音视频,AVFormatContext,AVIOContext)