FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析

本文介绍FFmpeg如何通过AVIOContext API从内存中读取音视频数据。使用自定义缓冲区并配合回调函数实现文件内容映射到内存,便于后续处理。文中详细解释了avio_reading.c例程的工作原理。

相关博客列表 :

    FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析 

    FFMPEG内存操作(二)从内存中读取数及数据格式的转换 

    FFmpeg内存操作(三)内存转码器

   在FFMPEG的官方例程中,有个avio_reading.c 的文件,他的主要功能是将音视频文件读取到内存,如果FFMPEG需要使用输入文件的数据,则直接从内存中调用。初学FFMPEG,给avio_reading.c 文件做了一个注释,如果不对,欢迎指正。

 

[objc]  view plain  copy
 
 print?
  1. /* 
  2.  * Copyright (c) 2014 Stefano Sabatini 
  3.  * 
  4.  * Permission is hereby granted, free of charge, to any person obtaining a copy 
  5.  * of this software and associated documentation files (the "Software"), to deal 
  6.  * in the Software without restriction, including without limitation the rights 
  7.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  8.  * copies of the Software, and to permit persons to whom the Software is 
  9.  * furnished to do so, subject to the following conditions: 
  10.  * 
  11.  * The above copyright notice and this permission notice shall be included in 
  12.  * all copies or substantial portions of the Software. 
  13.  * 
  14.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  15.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  16.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
  17.  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  18.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
  19.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
  20.  * THE SOFTWARE. 
  21.  */  
  22.   
  23. /** 
  24.  * @file 
  25.  * libavformat AVIOContext API example. 
  26.  * 
  27.  * Make libavformat demuxer access media content through a custom 
  28.  * AVIOContext read callback. 
  29.  * @example avio_reading.c 
  30.  */  
  31.   
  32. #include <libavcodec/avcodec.h>  
  33. #include <libavformat/avformat.h>  
  34. #include <libavformat/avio.h>  
  35. #include <libavutil/file.h>  
  36.   
  37. struct buffer_data {  
  38.     uint8_t *ptr; /* 文件中对应位置指针 */  
  39.     size_t size; ///< size left in the buffer /* 文件当前指针到末尾 */   
  40. };  
  41.   
  42. /* 将文件中数据拷贝到缓冲区,同时文件指针位置偏移,数据大小改变 */  
  43. static int read_packet(voidvoid *opaque, uint8_t *buf, int buf_size)  
  44. {  
  45.     struct buffer_data *bd = (struct buffer_data *)opaque;  
  46.     buf_size = FFMIN(buf_size, bd->size);  
  47.   
  48.     printf("ptr:%p size:%zu\n", bd->ptr, bd->size);  
  49.   
  50.     /* copy internal buffer data to buf */  
  51.     memcpy(buf, bd->ptr, buf_size);  
  52.     bd->ptr  += buf_size;  
  53.     bd->size -= buf_size;  
  54.   
  55.     return buf_size;  
  56. }  
  57.   
  58. int main(int argc, charchar *argv[])  
  59. {  
  60.     AVFormatContext *fmt_ctx = NULL;  
  61.     AVIOContext *avio_ctx = NULL;  
  62.     uint8_t *buffer = NULL, *avio_ctx_buffer = NULL;  
  63.     size_t buffer_size, avio_ctx_buffer_size = 4096;  
  64.     charchar *input_filename = NULL;  
  65.     int ret = 0;  
  66.     struct buffer_data bd = { 0 };  
  67.   
  68.     if (argc != 2) {  
  69.         fprintf(stderr, "usage: %s input_file\n"  
  70.                 "API example program to show how to read from a custom buffer "  
  71.                 "accessed through AVIOContext.\n", argv[0]);  
  72.         return 1;  
  73.     }  
  74.     input_filename = argv[1];  
  75.   
  76.     /* register codecs and formats and other lavf/lavc components*/  
  77.     av_register_all();  
  78.   
  79.     /* slurp file content into buffer */  
  80.     /* input_filename : 输入文件的文件名 
  81.      * buffer : 文件开始地址 
  82.      * buffer_size : 文件大小 
  83.      * 类似于UNIX下的mmap函数所实现的功能,返回文件开始指针,文件大小 
  84.      * 经测试,并不耗内存,可视为UNIX下的文件映射 
  85.     */  
  86.     ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);  
  87.     if (ret < 0)  
  88.         goto end;  
  89.   
  90.     /* fill opaque structure used by the AVIOContext read callback */  
  91.     /* bd 是指经过文件映射之后的文件,并不是指需要缓存区 */  
  92.     bd.ptr  = buffer;  
  93.     bd.size = buffer_size;  
  94.   
  95.     /* 初始化文件格式的结构体,就是分配内存 */  
  96.     if (!(fmt_ctx = avformat_alloc_context())) {  
  97.         ret = AVERROR(ENOMEM);  
  98.         goto end;  
  99.     }  
  100.   
  101.     /* 分配内存, 可以自己设置缓冲大小,这里设置的是4K */  
  102.     avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);  
  103.     if (!avio_ctx_buffer) {  
  104.         ret = AVERROR(ENOMEM);  
  105.         goto end;  
  106.     }  
  107.   
  108.     /* avio_ctx_buffer是缓冲区, 
  109.      * avio_ctx_buffer 的初始地址赋值到 avio_ctx->buffer 
  110.      * avio_ctx_buffer_size是缓冲区大小 , 也是每次读取数据的大小 
  111.      * bd 是输入文件文件的映射文件 
  112.      * read_packet 回调函数,读取数据的功能 , 具体在什么情况下才会回调 ? 
  113.      */  
  114.     avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,  
  115.                                   0, &bd, &read_packet, NULL, NULL);  
  116.     if (!avio_ctx) {  
  117.         ret = AVERROR(ENOMEM);  
  118.         goto end;  
  119.     }  
  120.     fmt_ctx->pb = avio_ctx;  
  121.     /* 配置初始化信息 
  122.     * read_packet 回调函数会在这里被调用, 它将输入文件的所有数据都先存入缓存中, 
  123.     * 如果后面有需要用到数据,那么它就从缓存中直接调用数据 
  124.     */  
  125.     ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);  
  126.     if (ret < 0) {  
  127.         fprintf(stderr, "Could not open input\n");  
  128.         goto end;  
  129.     }  
  130.   
  131.     ret = avformat_find_stream_info(fmt_ctx, NULL);  
  132.     if (ret < 0) {  
  133.         fprintf(stderr, "Could not find stream information\n");  
  134.         goto end;  
  135.     }  
  136.       
  137.     /* 输出基本信息 */  
  138.     av_dump_format(fmt_ctx, 0, input_filename, 0);  
  139.   
  140. end:  
  141.     avformat_close_input(&fmt_ctx);  
  142.     /* note: the internal buffer could have changed, and be != avio_ctx_buffer */  
  143.     //应该就是av_free(&avio_ctx_buffer),但位置不对  
  144.     //两者有差异  
  145.     //printf("%p %p\n",avio_ctx_buffer,avio_ctx->buffer);  
  146.     if (avio_ctx) {  
  147.         av_freep(&avio_ctx->buffer);  
  148.         av_freep(&avio_ctx);  
  149.     }  
  150.     av_file_unmap(buffer, buffer_size);  
  151.   
  152.     if (ret < 0) {  
  153.         fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));  
  154.         return 1;  
  155.     }  
  156.   
  157.     return 0;  
  158. }  

    这里定义了一个avio_ctx_buffer_size 变量表示每次从文件中读取到少数据到内存。在该例子中,FFMPEG是先把输入的音视频数据全部读取到内存,而并不是需要使用的时候才去读取。

<think>我们之前讨论了av_read_frame()返回-541478725(即AVERROR_EOF)的问题,现在用户想了解avio_alloc_context()中数据读取回调函数的触发条件,以及如何解决在自定义I/O场景下av_read_frame()返回AVERROR_EOF的问题。用户特别提到引用[1]中关于自定义I/O(avio_alloc_context)的回调函数在win7及低版本win10上无法录制指定窗口的问题,这提示我们需要关注自定义I/O实现中的平台兼容性和回调触发机制。##深入分析avio_alloc_context回调触发机制avio_alloc_context用于创建自定义I/O上下文,其核心是四个回调函数:```cAVIOContext*avio_alloc_context(unsignedchar*buffer,//FFmpeg内部缓冲区intbuffer_size,//缓冲区大小intwrite_flag,//0表示读,1表示写void*opaque,//传递给回调的用户数据int(*read_packet)(void*opaque,uint8_t*buf,intbuf_size),//关键读回调int(*write_packet)(void*opaque,uint8_t*buf,intbuf_size),int64_t(*seek)(void*opaque,int64_toffset,intwhence));```###📌回调函数触发条件1.**read_packet**(读回调):-触发时机:当FFmpeg内部缓冲区数据不足时自动调用-调用频率:取决于数据消耗速率,可能密集调用(例如解码高清视频时)-数据量要求:每次调用应尽量填满`buf_size`大小的缓冲区-**关键细节**:必须返回实际读取字节数,若暂时无数据应返回`EAGAIN`错误码[^1]2.**seek**(定位回调):-触发时机:当调用`av_seek_frame()`或格式解析需要随机访问时-定位模式:`whence`参数决定定位方式:-`SEEK_SET`:相对于文件起始的偏移(offset≥0)-`SEEK_CUR`:相对于当前位置的偏移(offset可正可负)-`SEEK_END`:相对于文件结尾的偏移(offset通常≤0)-**实现要求**:必须正确更新内部指针并返回新位置###⚠️FFmpeg内部缓冲机制FFmpeg采用两层缓冲结构:```mermaidgraphLRA[read_packet回调]-->|填充|B[自定义I/O的buf_size缓冲区]B-->|批量读入|C[FFmpeg内部缓存]C-->|分片取出|D[解码器]```-**关键点**:`read_packet`每次被调用时,应尽可能返回最多`buf_size`字节-**缓冲不足后果**:若回调返回数据量持续小于请求量,会导致频繁回调降低性能###🔥自定义I/O引发AVERROR_EOF的核心原因当`read_packet`回调错误地返回0(而非`EAGAIN`)时,FFmpeg会认为数据源已结束(EOF),导致后续`av_read_frame()`返回`AVERROR_EOF`[^1]。$$\text{read\_packet}()\to0\quad\Rightarrow\quad\text{AVERROR\_EOF}$$##解决方案:修复自定义I/O实现###✅正确实现read_packet回调```cstaticintread_packet(void*opaque,uint8_t*buf,intbuf_size){MyIOContext*io_ctx=(MyIOContext*)opaque;intbytes_available=get_available_data(io_ctx);//用户函数获取可用数据量if(bytes_available<=0){returnAVERROR(EAGAIN);//✨关键:无数据时返回EAGAIN而非0}intreal_size=FFMIN(bytes_available,buf_size);memcpy(buf,io_ctx->data_ptr,real_size);io_ctx->data_ptr+=real_size;//更新读取位置returnreal_size;//返回实际读取字节数}```###✅实现必要的seek回调即使不需要随机访问,也应实现基本定位功能:```cstaticint64_tseek(void*opaque,int64_toffset,intwhence){MyIOContext*io_ctx=(MyIOContext*)opaque;switch(whence){caseSEEK_SET:io_ctx->data_ptr=io_ctx->origin_ptr+offset;break;caseSEEK_CUR:io_ctx->data_ptr+=offset;break;caseSEEK_END:io_ctx->data_ptr=io_ctx->origin_ptr+io_ctx->total_size+offset;break;default:return-1;//不支持的操作}//确保指针在有效范围内int64_tpos=io_ctx->data_ptr-io_ctx->origin_ptr;if(pos<0)pos=0;if(pos>io_ctx->total_size)pos=io_ctx->total_size;io_ctx->data_ptr=io_ctx->origin_ptr+pos;returnpos;//返回当前位置}```###⚠️平台兼容性修复(针对引用[1])Win7/低版本Win10的特殊问题通常由这些原因导致:1.**时间戳问题**:旧系统的高精度计时器不准确-解决方案:使用`QueryPerformanceCounter`替代`GetTickCount`2.**内存对齐**:旧系统对非对齐访问更敏感-解决方案:确保回调返回的内存地址16字节对齐```c//创建对齐的内存区域#ifdef_WIN32uint8_t*aligned_buf=_aligned_malloc(buffer_size,16);#elseuint8_t*aligned_buf=memalign(16,buffer_size);#endif```##调试与验证###🔧检测虚假EOF的步骤1.**错误检查**:在调用`av_read_frame()`后立即检查错误类型```cintret=av_read_frame(fmt_ctx,&pkt);if(ret<0){charerr_buf[256];av_strerror(ret,err_buf,sizeof(err_buf));//如果返回的是EAGAIN说明是正常的数据不足if(ret==AVERROR(EAGAIN)){av_usleep(10000);//等待10mscontinue;}}```2.**回调日志**:在`read_packet`中添加日志记录调用参数```cprintf("Requested:%dbytes,returned:%d\n",buf_size,real_size);```##相关技术问题1.如何在自定义I/O中实现动态数据生成(如实时屏幕采集)?2.FFmpeg自定义I/O如何应对网络抖动导致的延迟?3.是否必须为实时流媒体实现seek回调?通过以上方法,用户应能解决因自定义I/O实现不当导致的虚假EOF问题,并理解回调函数的正确触发逻辑。</think> ### 🧠 FFmpeg 自定义I/O机制详解与`av_read_frame`错误解决方案 #### 📌 `avio_alloc_context()` 回调函数触发机制 `avio_alloc_context()` 用于创建自定义I/O上下文,其数据读取回调函数 (`Fill_IOBuffer`) 的触发条件取决于协议层的需求,核心规则如下: 1. **缓冲区触发机制** - 当FFmpeg内部缓冲区的数据量低于阈值时自动触发 - 触发条件满足不等式:$$ \text{内部缓冲剩余} \leq \frac{\text{bufferSize}}{4} $$ - 回调每次应尽可能填充整个`bufferSize`[^1] 2. **定位操作强制触发** - 当调用`av_seek_frame()`或`avformat_seek_file()`时: ```c avformat_seek_file(fmt_ctx, -1, min_ts, target_ts, max_ts, 0); ``` - 会强制调用`seek`回调,然后触发新的数据读取[^3] 3. **初始化解复用器时** - 打开媒体流(`avformat_open_input()`)时需预填充约128KB数据用于格式探测 4. **特殊协议处理** - 实时流媒体(UDP/RTP)会在丢包时触发重读 - 网络中断后恢复连接会自动触发 #### ⚠️ `av_read_frame()`返回-541478725的深度分析 错误码 `-541478725` (`AVERROR_EOF`) 在自定义I/O场景的特殊成因: ```mermaid graph TD A[av_read_frame 返回 EOF] --> B{自定义I/O 原因} B --> C[read_packet 返回0] B --> D[seek 未实现] B --> E[缓冲策略错误] C --> F[未返回 EAGAIN] D --> G[无法重定位读取] E --> H[数据供给不足] ``` ##### 关键问题诊断 1. **回调返回规则错误** - ✅ **正确行为**:无数据时应返回 `AVERROR(EAGAIN)` - ❌ **致命错误**:返回 `0` 会被FFmpeg解析为EOF[^1] ```c // 错误实现(导致虚假EOF) if(no_data) return 0; // 正确实现 if(no_data) return AVERROR(EAGAIN); ``` 2. **缺失定位功能** - FFmpeg解析格式时默认要求 `seek` 回调 - 未实现时导致解复用器错误判断流结束: ```c // 必须实现的基础seek回调 static int64_t seek(void *opaque, int64_t offset, int whence) { return 0; // 简单实现也需存在 } ``` 3. **实时流缓冲策略** - 数据生产速率 < 消费速率时触发EOF - 需满足不等式:$$ \text{生成速率} \geq \frac{\text{帧大小} \times \text{帧率}}{\text{缓冲深度}} $$ #### 🔧 完整解决方案 ##### 1. 修正回调函数实现 ```c // 正确读取回调模板 static int Fill_IOBuffer(void *opaque, uint8_t *buf, int buf_size) { MyContext *ctx = (MyContext*)opaque; size_t avail = ctx->data_size - ctx->pos; // 关键处理:无数据时返回EAGAIN if(avail == 0) { #if WIN32 // Windows特殊处理 if(IsWin7OrLower()) Sleep(10); // Win7兼容性修复[^1] #endif return AVERROR(EAGAIN); } int read_size = FFMIN(avail, buf_size); memcpy(buf, ctx->data_ptr + ctx->pos, read_size); ctx->pos += read_size; return read_size; // 返回实际读取字节数 } // 必须实现的seek回调 static int64_t seek(void *opaque, int64_t offset, int whence) { MyContext *ctx = (MyContext*)opaque; switch(whence) { case SEEK_SET: ctx->pos = offset; break; case SEEK_CUR: ctx->pos += offset; break; case SEEK_END: ctx->pos = ctx->data_size + offset; break; } return ctx->pos; // 返回新位置 } ``` ##### 2. 数据消费层优化 ```c // 防止虚假EOF的读取策略 AVPacket pkt; while (1) { int ret = av_read_frame(fmt_ctx, &pkt); // 处理自定义I/O的特殊EOF if (ret == AVERROR_EOF) { if (is_custom_io) { av_usleep(5000); // 5ms等待数据 reset_eof_flag(fmt_ctx); // 重置内部EOF状态 continue; } break; // 真实EOF退出 } // ... 正常处理数据包 } ``` ##### 3. 平台兼容性处理(针对引用[1]) ```c // Windows低版本优化方案 #if defined(_WIN32) #include <windows.h> bool IsWin7OrLower() { return IsWindows7OrGreater() && !IsWindows8OrGreater(); } // 内存分配兼容性优化 void* alloc_io_buffer(size_t size) { #if (_WIN32_WINNT <= 0x0601) // Win7及以下 return _aligned_malloc(size, 16); // 16字节对齐 #else return av_malloc(size); #endif } #endif ``` #### 🧪 验证与调试技巧 1. **实时监控回调行为** ```c printf("回调触发: 请求%d字节, 可用=%ld, 返回=%d\n", buf_size, avail, read_size); ``` 2. **强制EOF恢复测试** ```c // 测试协议层恢复能力 avio_context->eof_reached = 1; // 强制设置EOF标志 av_read_frame(fmt_ctx, &pkt); // 应触发重置 ``` 3. **性能统计分析** $$ \text{数据利用率} = \frac{\text{总读取量}}{\text{回调次数} \times \text{bufferSize}} \times 100\% $$ - 建议保持在70%~90%区间 #### 🧩 相关问题 1. 如何在Windows 7下优化FFmpeg内存对齐提升性能? 2. 实时流媒体传输如何动态调整`avio`缓冲区大小? 3. FFmpeg自定义协议如何实现超时重连机制? [^1]: 在win7以及部分低版本win10上无法录制指定的使用osg的程序窗口画面。 [^3]: 主要的seek操作通过avformat_seek_file完成。 [^2]: 如果解复⽤器有打开则关闭avformat_close_input调⽤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值