音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。

这篇具有很好参考价值的文章主要介绍了音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.avio介绍

avio是FFmpeg中的一个模块,用于实现多种输入输出方式的封装。

avio提供了一系列API,可以将数据从内存读取到缓冲区中,也可以将缓冲区中的数据写入到内存中。其实现依赖于IOContext结构体,该结构体定义了当前输入/输出事件的状态、数据、回调函数等信息,并支持通过自定义回调函数实现不同的输入/输出方式。

内存输入(Memory Input)是指将数据从内存中读取到缓冲区中,常见的应用场景包括:从内存中读取音视频数据进行解码或处理。在使用avio实现内存输入时,需要首先创建一个AVIOContext结构体,并将内存数据缓冲区作为参数传递给avio_open函数进行初始化。之后,可以使用avio_read函数从缓冲区中读取数据,直至读取完成。

内存输出(Memory Output)是指将数据从缓冲区中写入到内存中,常见的应用场景包括:将音视频数据编码并保存到内存中。在使用avio实现内存输出时,需要首先创建一个AVIOContext结构体,并将内存数据缓冲区和缓冲区大小作为参数传递给avio_open函数进行初始化。之后,可以使用avio_write函数将数据写入缓冲区中,并在完成输出后调用avio_close函数关闭AVIOContext结构体。

总的来说,内存输入和输出是指在使用FFmpeg进行音视频处理时,将数据从内存中读取或写入到内存中的一种方式。使用avio模块可以方便地实现这种输入输出方式,并支持自定义回调函数以满足不同的应用需求。

2.为什么要用avio?

使用FFmpeg的avio模块实现内存输入和输出有以下几个优点:

2.1.灵活性高

传统的音视频处理方式往往需要将音视频数据保存到文件中,然后再进行读取和处理。而使用avio模块可以将数据直接读取或写入到内存中,从而提高了音视频处理的灵活性。这种方式可以避免繁琐的文件IO操作,节省磁盘空间。

2.2.扩展性强

内存输入和输出功能可以方便地扩展为其他更高级的应用程序,例如:流媒体服务器、实时音视频采集与处理等。这是因为内存输出能够较为轻松地将音视频数据编码并存储到内存缓冲区中,进而交由网络传输;内存输入则可直接从内存缓冲区获取音视频数据,快速响应用户请求。

2.3.可定制性好

FFmpeg的avio模块提供了一系列API,可以通过设置回调函数实现各种自定义功能。例如:自定义网络协议传输方式、增加错误重试机制、实现多路复用等。这使得处理器可以根据自己的需求对avio模块进行灵活配置,以最大限度地满足不同场景下的业务需求。

因此,使用FFmpeg的avio模块实现内存输入和输出可以提高音视频处理的效率,增加程序的灵活性和扩展性,同时还具有良好的可定制性。

3.内存区作为输入

3.1.回调函数何时被回调呢?

所有需要从输入源中读取数据的时刻,都将调用回调函数。和输入源是普通文件相比,只不过输入源变成了内存区,其他各种外在表现并无不同。

如下各函数在不同的阶段从输入源读数据,都会调用回调函数:

avformat_open_input() 从输入源读取封装格式文件头

avformat_find_stream_info() 从输入源读取一段数据,尝试解码,以获取流信息

av_read_frame() 从输入源读取数据包

3.2.该示例作用是统计mp4文件的视频帧数,代码如下:

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

#define INPUT_FILE "1.mp4"

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 };
    int  videoStreamIndex = -1;
    AVCodecParameters* avCodecPara = NULL;
    const AVCodec* codec = NULL;
    AVCodecContext* codecCtx = NULL;
    AVPacket* pkt = NULL;

    input_filename = INPUT_FILE;

    /* 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);
    printf("完成\n");

    //循环查找视频中包含的流信息,直到找到视频类型的流
    //便将其记录下来 保存到videoStreamIndex变量中
    for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break;//找到视频流就退出
        }
    }

    //如果videoStream为-1 说明没有找到视频流
    if (videoStreamIndex == -1) {
        printf("cannot find video stream\n");
        goto end;
    }

    //=================================  查找解码器 ===================================//
    avCodecPara = fmt_ctx->streams[videoStreamIndex]->codecpar;
    codec = avcodec_find_decoder(avCodecPara->codec_id);
    if (codec == NULL) {
        printf("cannot find decoder\n");
        goto end;
    }
    //根据解码器参数来创建解码器内容
    codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, avCodecPara);
    if (codecCtx == NULL) {
        printf("Cannot alloc context.");
        goto end;
    }

    //================================  打开解码器 ===================================//
    if ((ret = avcodec_open2(codecCtx, codec, NULL)) < 0) { // 具体采用什么解码器ffmpeg经过封装 我们无须知道
        printf("cannot open decoder\n");
        goto end;
    }

    //=========================== 分配AVPacket结构体 ===============================//
    int       i = 0;//用于帧计数
    pkt = av_packet_alloc();                      //分配一个packet
    av_new_packet(pkt, codecCtx->width * codecCtx->height); //调整packet的数据

    //===========================  读取视频信息 ===============================//
    while (av_read_frame(fmt_ctx, pkt) >= 0) { //读取的是一帧视频  数据存入一个AVPacket的结构中
        if (pkt->stream_index == videoStreamIndex) {
            i++;//只计算视频帧
        }
        av_packet_unref(pkt);//重置pkt的内容
    }
    printf("There are %d frames int total.\n", i);

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_packet_free(&pkt);
    avcodec_close(codecCtx);
    av_file_unmap(buffer, buffer_size);
    avformat_free_context(fmt_ctx);

    if (ret < 0) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }

    return 0;
}

音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。

4.内存区作为输出

4.1.回调函数何时被回调呢?

所有输出数据的时刻,都将调用回调函数。和输出是普通文件相比,只不过输出变成了内存区,其他各种外在表现并无不同。

如下各函数在不同的阶段会输出数据,都会调用回调函数:

avformat_write_header() 将流头部信息写入输出区

av_interleaved_write_frame() 将数据包写入输出区

av_write_trailer() 将流尾部信息写入输出区

4.2.该示例作用是提取mp4文件的视频帧为h264文件,输出采用write_packet回调,代码如下:

//https://www.cnblogs.com/leisure_chn/p/10318145.html
#include <stdio.h>
#include <stdlib.h>

#include <libavformat/avformat.h>

#define INPUT_FILE "1.mp4"
#define OUTPUT_FILE "output.h264"

typedef struct {
    FILE* fp;
} OutputContext;

static int write_packet(void* opaque, uint8_t* buf, int buf_size)
{
    OutputContext* output_ctx = (OutputContext*)opaque;
    FILE* fp = output_ctx->fp;

    fwrite(buf, 1, buf_size, fp);

    return buf_size;
}

int main(int argc, char* argv[])
{
    AVFormatContext* input_ctx = NULL;
    AVOutputFormat* output_fmt = NULL;
    AVFormatContext* output_ctx = NULL;
    OutputContext* output_opaque = NULL;

    int ret = avformat_open_input(&input_ctx, INPUT_FILE, NULL, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not open input file: %s.\n", av_err2str(ret));
        goto end;
    }

    ret = avformat_find_stream_info(input_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not find stream information: %s.\n", av_err2str(ret));
        goto end;
    }

    output_fmt = av_guess_format("h264", NULL, NULL);
    if (!output_fmt) {
        fprintf(stderr, "Error: Could not guess output format.\n");
        ret = AVERROR_MUXER_NOT_FOUND;
        goto end;
    }

    ret = avformat_alloc_output_context2(&output_ctx, output_fmt, NULL, OUTPUT_FILE);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not allocate output context: %s.\n", av_err2str(ret));
        goto end;
    }

    AVStream* in_video_stream = NULL;
    AVCodecParameters* in_codec_params = NULL;
    for (int i = 0; i < input_ctx->nb_streams; i++) {
        AVStream* stream = input_ctx->streams[i];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            in_video_stream = stream;
            in_codec_params = stream->codecpar;
            break;
        }
    }
    if (!in_video_stream) {
        fprintf(stderr, "Error: Could not find video stream.\n");
        ret = AVERROR(ENOSYS);
        goto end;
    }

    AVStream* out_video_stream = avformat_new_stream(output_ctx, NULL);
    if (!out_video_stream) {
        fprintf(stderr, "Error: Could not create new stream.\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ret = avcodec_parameters_copy(out_video_stream->codecpar, in_codec_params);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not copy codec parameters: %s.\n", av_err2str(ret));
        goto end;
    }

    out_video_stream->codecpar->codec_tag = 0;

    output_opaque = av_malloc(sizeof(OutputContext));
    if (!output_opaque) {
        fprintf(stderr, "Error: Could not allocate output context.\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }

    fopen_s(&output_opaque->fp, OUTPUT_FILE, "wb");
    if (!output_opaque->fp) {
        fprintf(stderr, "Error: Could not open output file.\n");
        ret = AVERROR(ENOENT);
        goto end;
    }

    AVIOContext* pb = NULL;
    pb = avio_alloc_context((unsigned char*)av_malloc(32768), 32768, 1, output_opaque, NULL, &write_packet, NULL);
    if (!pb) {
        fprintf(stderr, "Error: Could not allocate output IO context.\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }
    output_ctx->pb = pb;

    ret = avformat_write_header(output_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not write header: %s.\n", av_err2str(ret));
        goto end;
    }

    AVPacket packet = { 0 };
    while (av_read_frame(input_ctx, &packet) >= 0) {
        if (packet.stream_index == in_video_stream->index) {
            av_packet_rescale_ts(&packet, in_video_stream->time_base, out_video_stream->time_base);
            packet.stream_index = out_video_stream->index;

            ret = av_interleaved_write_frame(output_ctx, &packet);
            if (ret < 0) {
                fprintf(stderr, "Error: Could not write frame: %s.\n", av_err2str(ret));
                goto end;
            }
        }

        av_packet_unref(&packet);
    }

    ret = av_write_trailer(output_ctx);
    if (ret < 0) {
        fprintf(stderr, "Error: Could not write trailer: %s.\n", av_err2str(ret));
        goto end;
    }

    printf("Conversion complete!\n");
end:
    if (input_ctx) {
        avformat_close_input(&input_ctx);
    }
    if (output_ctx) {
        if (output_ctx->pb) {
            av_freep(&output_ctx->pb->buffer);
            avio_context_free(&output_ctx->pb);
        }
        if (output_opaque->fp) {
            fclose(output_opaque->fp);
        }
        avformat_free_context(output_ctx);
        av_free(output_opaque);
    }
    return ret;
}

音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。

5.内存IO模式非常重要的一个函数:avio_alloc_context()

/**
 * Allocate and initialize an AVIOContext for buffered I/O. It must be later
 * freed with avio_context_free().
 *
 * @param buffer Memory block for input/output operations via AVIOContext.
 *        The buffer must be allocated with av_malloc() and friends.
 *        It may be freed and replaced with a new buffer by libavformat.
 *        AVIOContext.buffer holds the buffer currently in use,
 *        which must be later freed with av_free().
 * @param buffer_size The buffer size is very important for performance.
 *        For protocols with fixed blocksize it should be set to this blocksize.
 *        For others a typical size is a cache page, e.g. 4kb.
 * @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
 * @param opaque An opaque pointer to user-specific data.
 * @param read_packet  A function for refilling the buffer, may be NULL.
 *                     For stream protocols, must never return 0 but rather
 *                     a proper AVERROR code.
 * @param write_packet A function for writing the buffer contents, may be NULL.
 *        The function may not change the input buffers content.
 * @param seek A function for seeking to specified byte position, may be NULL.
 *
 * @return Allocated AVIOContext or NULL on failure.
 */
AVIOContext *avio_alloc_context(
                  unsigned char *buffer,
                  int buffer_size,
                  int write_flag,
                  void *opaque,
                  int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                  int64_t (*seek)(void *opaque, int64_t offset, int whence));

这是FFmpeg中用于创建AVIOContext结构体的函数 avio_alloc_context 的代码注释。

该函数具有以下参数:

  • buffer:存储音视频数据的内存缓冲区指针,必须通过 av_malloc() 等函数分配。该内存块会被 AVIOContext 结构体引用,不能在生命周期内被释放。

  • buffer_size:缓冲区大小,对于固定块大小的协议需要设置为固定块大小,对于其他协议可以设置为典型缓存页大小,例如 4KB。

  • write_flag:标记是否可写,1 表示可写,0 表示只读。

  • opaque:用户指定的不透明指针,用于在回调函数中携带自定义数据。

  • read_packet:read_packet 回调函数,用于本地文件或网络流传输时从输入源中读取数据。当 buffer 中的数据被消耗完后,调用此函数填充缓冲区。

  • write_packet:write_packet 回调函数,在可写模式下用于将缓冲区中的数据写入输出源,例如本地文件或网络流。

  • seek:seek 回调函数,用于跳转到指定字节位置。

该函数主要用于在 FFmpeg 内部创建一个 AVIOContext 结构体,该结构体用于管理读取或写入内存缓冲区的音视频数据,并提供了一些 API 函数用于处理缓冲区数据。一旦创建了 AVIOContext 结构体,就可以通过调用 avio_open2() 函数来打开对应的输入或输出资源,然后即可开始读写数据。

在使用完毕后,需要通过调用 avio_context_free() 函数来释放 AVIOContext 结构体占用的内存空间。

6.两个示例的环境

操作系统:win10 64位

开发环境:VS2022

vcpkg命令:

vcpkg install ffmpeg:x64-windows

音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。文章来源地址https://www.toymoban.com/news/detail-437569.html

到了这里,关于音视频八股文(11)-- ffmpeg avio 内存输入和内存输出。内存输出有完整代码,网上很少有的。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 音视频八股文(10)-- mp4结构

    mp4⽂件格式⼜被称为MPEG-4 Part 14,出⾃MPEG-4标准第14部分 。它是⼀种多媒体格式容器,⼴泛⽤于包装视频和⾳频数据流、海报、字幕和元数据等。(顺便⼀提,⽬前流⾏的视频编码格式AVC/H264 定义在MPEG-4 Part 10)。 mp4⽂件由box组成,每个box分为Header和Data。其中Header部分包含了

    2024年02月02日
    浏览(32)
  • 音视频八股文(7)-- 音频aac adts三层结构

    AAC(Advanced Audio Coding)是一种现代的音频编码技术,用于数字音频的传输和存储领域。AAC是MPEG-2和MPEG-4标准中的一部分,可提供更高质量的音频数据,并且相比于MP3等旧有音频格式,AAC需要更少的比特率。 AAC通过使用一些高级的音频编码算法来实现更好的声音质量和更低的压

    2024年02月06日
    浏览(40)
  • 什么叫面试八股文,一篇文章带你入门音视频(1),使用指南

    版税方式:按个收取 备注:WMA的全称是Windows Media Audio,它是微软公司推出的与MP3格式齐名的一种新的音频格式。由于WMA在压缩比和音质方面都超过了MP3,更是远胜于RA(Real Audio),即使在较低的采样频率下也能产生较好的音质,再加上WMA有微软的Windows Media Player做其强大的后盾

    2024年04月25日
    浏览(26)
  • 音视频八股文(9)-- flv的h264六层结构和aac六层结构

    FLV(Flash Video)是Adobe公司推出的⼀种流媒体格式,由于其封装后的⾳视频⽂件体积⼩、封装简单等特点,⾮常适合于互联⽹上使⽤。⽬前主流的视频⽹站基本都⽀持FLV。采⽤FLV格式封装的⽂件后缀为.flv。 FLV封装格式是由⼀个⽂件头(file header)和 ⽂件体(file Body)组成。其中,FLV

    2024年02月01日
    浏览(35)
  • ffmpeg@音视频工具@音视频合并

    FFmpeg中文网 (github.net.cn) FFmpeg 是一款强大的开源跨平台音视频处理工具集,它包含了一系列命令行工具以及用于音频和视频编码解码、格式转换、抓取、流化等功能的库。FFmpeg 支持多种视频、音频格式和编解码器,能够进行音视频的压缩、封装、转码、分割、合并、过滤、抓

    2024年03月17日
    浏览(49)
  • 音视频 FFmpeg音视频处理流程

    推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月12日
    浏览(40)
  • 音视频 ffmpeg命令提取音视频数据

    保留封装格式 提取视频 提取音频 推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月10日
    浏览(43)
  • 【FFmpeg】音视频录制 ① ( 查询系统中 ffmpeg 可录制的音视频输入设备 | 使用 ffmpeg 命令录制音视频数据 | 录制视频数据命令 |录制音频数据| 同时录制音频和视频数据命令 )

    在 Windows 系统中 , 使用 ffmpeg 命令 录制 音视频 , 需要先获取 系统的 音视频设备 信息 , 录制 音视频 本质上是从 系统音视频设备 中获取数据 ; 执行 命令 , 可以获取 系统中 ffmpeg 可用的 DirectShow 音视频输入设备 ; 命令参数解析 : -list_devices true : 列出所有 ffmpeg 的 指定类型的可

    2024年04月25日
    浏览(67)
  • 音视频 ffmpeg视频裁剪

    将输入视频帧的宽度和高度从x和y值表示的位置裁剪到指定的宽度和高度;x和y是输出的左上角坐标,协调系统的中心是输入视频帧的左上角。 如果使用了可选的keep_aspect参数,将会改变输出SAR(样本宽比)以补偿新的DAR(显示长宽比) 推荐一个零声学院项目课,个人觉得老师讲得不

    2024年02月10日
    浏览(35)
  • 音视频 ffmpeg命令视频录制(Windows)

    先安装dshow软件 Screen Capturer Recorder, 项目地址:https://sourceforge.net/projects/screencapturer/files/ 然后查看可用设备名字:ffmpeg -list_devices true -f dshow -i dummy 录制视频(默认参数) 录制声音(默认参数) 同时录制声音和视频(默认参数) 查看视频录制的可选参数 查看视频录制的可

    2024年02月10日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包