前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AVFormatContext协议层:理论与实战

AVFormatContext协议层:理论与实战

作者头像
Gnep@97
发布2023-12-03 10:41:25
2240
发布2023-12-03 10:41:25
举报

前言

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

在这里插入图片描述
在这里插入图片描述

一、协议操作对象结构

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

在这里插入图片描述
在这里插入图片描述

更详细的看下图:

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

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

初始化 AVIOFormat 函数调用关系:

在这里插入图片描述
在这里插入图片描述

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

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

准备好本地视频素材,我将其放到了 QT 工程文件的 debug 目录下

在这里插入图片描述
在这里插入图片描述

1、示例源码

代码语言:javascript
复制
extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavformat/avio.h>
    #include <libavutil/file.h>
};

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、运行结果

出现程序异常结束的错误,如下图:

在这里插入图片描述
在这里插入图片描述

使用 debug 调试发现在 avformat_close_input() 出现了段错误:

在这里插入图片描述
在这里插入图片描述
①、解决方法 1

查了好多资料,看了好多帖子也无济于事,索性看了一下 ffmepg 官方提供的测试用例,如下:

avio_read_callback.c

代码语言:javascript
复制
/*
 * 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 <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>

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()

在这里插入图片描述
在这里插入图片描述

再次运行,可以看到如下输出结果:

代码语言:javascript
复制
hello,ffmpeg
hello,avformat_alloc_context
hello,avformat_open_input
open stream success!
av_find_stream_info success 
******nb_streams=2
在这里插入图片描述
在这里插入图片描述

可以看到输出信息为找到了两路流,分别是视频流与音频流,用 MediaInfo 查看 test.mp4 可以看到一致的效果

在这里插入图片描述
在这里插入图片描述
②、解决方法 2

使用时可以通过 avformat_alloc_context 分配后使用,也可以直接 avformat_open_input

代码语言:javascript
复制
>//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

修改后的源码:

代码语言:javascript
复制
extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavformat/avio.h>
    #include <libavutil/file.h>
};

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;

运行结果:

代码语言:javascript
复制
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、示例源码

代码语言:javascript
复制
#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;
    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、运行结果

代码语言:javascript
复制
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、示例源码

代码语言:javascript
复制
/**
 * @file
 * libavformat AVIOContext API example.
 *
 * Make libavformat demuxer access media content through a custom
 * AVIOContext read callback.
 * @example avio_reading.c
 */

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>

//自定义缓冲区
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、运行结果

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、协议操作对象结构
  • 二、初始化 AVIOContext 函数调用关系
  • 三、avio 实战 1:打开本地文件或网络直播流
    • 1、示例源码
      • 2、运行结果
        • ①、解决方法 1
        • ②、解决方法 2
    • 四、avio 实战 2:自定义 AVIO
      • 1、示例源码
        • 2、运行结果
        • 五、avio 实战 3:自定义数据来源
          • 1、示例源码
            • 2、运行结果
            相关产品与服务
            云直播
            云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档