当前位置: 首页 > news >正文

【音视频】AVIO输入模式

内存IO模式

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

参数说明:

  • opaque是 read_packet / write_packet 的第⼀个参数,指向⽤户数据。
  • buffer和buffer_size是 read_packet / write_packet 的第⼆个和第三个参数,是供FFmpeg使⽤的数据区。
  • buffer ⽤作FFmpeg输⼊时,由⽤户负责向 buffer 中填充数据,FFmpeg取⾛数据。
  • buffer ⽤作FFmpeg输出时,由FFmpeg负责向 buffer 中填充数据,⽤户取⾛数据。
  • write_flag是缓冲区读写标志,读写的主语是指FFmpeg。
  • write_flag 为1时, buffer ⽤于写,即作为FFmpeg输出。
  • write_flag 为0时, buffer ⽤于读,即作为FFmpeg输⼊。
  • read_packet和write_packet是函数指针,指向⽤户编写的回调函数。
  • seek也是函数指针,需要⽀持seek时使⽤。 可以类⽐fseek的机制

一、avio_alloc_context 的环形缓冲本质

avio_alloc_context 创建的 AVIOContext 结构体 内部维护了一个环形缓冲区,其核心特性包括:

  1. 循环存储机制
    • 缓冲区逻辑上首尾相连,数据写入时自动回绕(Wrap-Around),避免频繁内存分配。
    • 例如,当缓冲区写满后,新数据会覆盖最早写入的数据(取决于配置)。
  2. 双指针管理
    • 读指针(buf_ptr):指向当前读取位置。
    • 写指针(buf_end):指向当前写入位置。
    • 通过模运算(%)实现指针的循环移动,例如:
      buf_ptr = (buf_ptr + size) % buffer_size
  3. 缓冲与性能优化
    • 预分配固定大小的内存(如 4KB、8KB),减少系统调用次数。
    • 适用于网络流、内存数据流等需要连续读写的场景。

实现流程

准备文件

build路径下准备相关mp3aac文件

在这里插入图片描述

添加main函数参数,表示输入文件和输出文件

在这里插入图片描述

打开文件

使用FILE二进制打开输入文件和输出文件

const char *in_file_name = argv[1];
const char *out_file_name = argv[2];
FILE *in_file = NULL;
FILE *out_file = NULL;// 1. 打开参数文件
in_file = fopen(in_file_name, "rb");
if(!in_file) {printf("open file %s failed\n", in_file_name);return  -1;
}
out_file = fopen(out_file_name, "wb");
if(!out_file) {printf("open file %s failed\n", out_file_name);return  -1;
}
自定义IO读取
  • AVFormatContex添加自定义读取规则,即自己实现一个AVIOContext,而不是使用默认的
  • AVIOContext使用的是环形缓冲区,即缓冲区满的时候覆盖前面的缓冲区
  • 需要设置环形缓冲区的大小、文件指针、以及缓冲内存回调函数,在内存数据读完的时候触发回调函数,继续从文件读取数据到环形缓冲区
  uint8_t *io_buffer = av_malloc(BUF_SIZE);
AVIOContext *avio_ctx = avio_alloc_context(io_buffer, BUF_SIZE, 0, (void *)in_file,    \read_packet, NULL, NULL);
AVFormatContext *format_ctx = avformat_alloc_context();
format_ctx->pb = avio_ctx;
int ret = avformat_open_input(&format_ctx, NULL, NULL, NULL);
if(ret < 0) {printf("avformat_open_input failed:%s\n", av_err2str(ret));return -1;
}

read_packet回调函数

static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{FILE *in_file = (FILE *)opaque;int read_size = fread(buf, 1, buf_size, in_file);// printf("read_packet read/*_*/size:%d, buf_size:%d\n", read_size, buf_size);if(read_size <=0) {return AVERROR_EOF;     // 数据读取完毕}return read_size;
}
查找解码器
  • 根据ID查找解码器,这里直接查找AAC解码器
  • 分配解码器上下文,将解码器绑定到上下文中
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if(!codec_ctx) {printf("avcodec_alloc_context3 failed\n");return -1;
}
ret = avcodec_open2(codec_ctx, codec, NULL);
if(ret < 0) {printf("avcodec_open2 failed:%s\n", av_err2str(ret));return -1;
}
解码并写入文件
  • 将格式上下文信息拷贝到数据包(packet)中
AVPacket *packet = av_packet_alloc();
ret = av_read_frame(format_ctx, packet);
  • 发送packet到解码器
ret = avcodec_send_packet(dec_ctx, packet);
  • 使用帧(frame)接收解码后的裸流数据
ret = avcodec_receive_frame(dec_ctx, frame);
  • 获取单个采样点的数据大小,左右声道依次写入数据
int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
for(int i = 0; i < frame->nb_samples; i++) {
for(int ch = 0; ch < dec_ctx->channels; ch++) {fwrite(frame->data[ch] + data_size *i, 1, data_size, outfile);}
}
  • 操作代码如下
while (1) {ret = av_read_frame(format_ctx, packet);if(ret < 0) {printf("av_read_frame failed:%s\n", av_err2str(ret));break;}decode(codec_ctx, packet, frame, out_file);
}

decode函数

static void decode(AVCodecContext *dec_ctx, AVPacket *packet, AVFrame *frame,FILE *outfile)
{int ret = 0;ret = avcodec_send_packet(dec_ctx, packet);if(ret == AVERROR(EAGAIN)) {printf("Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");} else if(ret < 0) {printf("Error submitting the packet to the decoder, err:%s\n",av_get_err(ret));return;}while (ret >= 0) {ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return;} else if (ret < 0)  {printf("Error during decoding\n");exit(1);}if(!packet) {printf("get flush frame\n");}int data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);//        print_sample_format(frame);/**P表示Planar(平面),其数据格式排列方式为 :LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)而不带P的数据格式(即交错排列)排列方式为:LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm并不是每一种都是这样的格式*/// 这里的写法不是通用,通用要调用重采样的函数去实现// 这里只是针对解码出来是planar格式的转换for(int i = 0; i < frame->nb_samples; i++) {for(int ch = 0; ch < dec_ctx->channels; ch++) {fwrite(frame->data[ch] + data_size *i, 1, data_size, outfile);}}}
}
冲刷解码器
  • 解码结束后要冲刷解码器,刷新解码器数据
decode(codec_ctx, NULL, frame, out_file);
结束操作

退出之前要释放内存、关闭文件

fclose(in_file);
fclose(out_file);av_free(io_buffer);
av_frame_free(frame);
av_packet_free(packet);avformat_close_input(&format_ctx);
avcodec_free_context(&codec_ctx);

更多资料:https://github.com/0voice

http://www.xdnf.cn/news/141517.html

相关文章:

  • 红队系列-网络安全知识锦囊-CTF(持续更新)
  • Mac「brew」快速安装Redis
  • 猫咪如厕检测与分类识别系统系列【十三】猫咪进出事件逻辑及日志优化【下】
  • 第六章 进阶06 读书群第一次团建
  • Kubernetes in action-初相识
  • 从StandardMaterial和PBRMaterial到PBRMetallicRoughnessMaterial:Babylon.js材质转换完全指南
  • linux 部署express项目,并使用pm2守护进程
  • yum包管理器
  • systemctl 命令详解与常见问题解决
  • .NET中,const和readonly区别
  • NLP高频面试题(五十五)——DeepSeek系列概览与发展背景
  • 自动清空 maven 项目临时文件,vue 的 node_modules 文件
  • Virtuoso ADE采用Spectre仿真中出现MOS管最小长宽比满足要求依然报错的情况解决方法
  • 【高频考点精讲】async/await原理剖析:Generator和Promise的完美结合
  • RTMP 入门指南
  • Aloudata Agent :基于 NoETL 明细语义层的分析决策智能体
  • Linux阻塞与非阻塞I/O:从原理到实践详解
  • 学硕热度下降,25西电数学与统计学院(考研录取情况)
  • 高频面试题:如何保证数据库和es数据一致性
  • ES历史版本下载
  • 第TR5周:Transformer实战:文本分类
  • 图像识别系统 - Ubuntu部署指南(香橙派开发板测试)-学习记录1
  • MySQL 详解之函数:数据处理与计算的利器
  • HOW - 如何模拟实现 gpt 展示答案的交互效果
  • form表单提交前设置请求头request header及文件下载
  • 线程怎么创建?Java 四种方式一网打尽
  • uniapp 仿企微左边公司切换页
  • FreeRTOS
  • 斗鱼娱乐电玩平台源码搭建实录
  • 短视频矩阵系统可视化剪辑功能开发,支持OEM