FFmpeg(7)-av_read_frame()读取帧数据AVPacket和av_seek_frame()改变播放进度

本文详细介绍FFmpeg中av_read_frame(), AVPacket, av_seek_frame()等核心函数的使用及参数解析,涵盖音视频帧读取、包管理和寻址操作,是音视频开发不可或缺的技术指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.av_read_frame()

该函数用于读取具体的音/视频帧数据

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

参数说明:

AVFormatContext *s   // 文件格式上下文

AVPacket *pkt      // 这个值不能传NULL,必须是一个空间,

            // 返回值:return 0 is OK, <0 on error or end of file

 

二.AVPacket

2.1 结构体常见参数说明

AVBufferRef buf;     // 指向一个buf空间,该空间是用来存储引用计数的。有可能多个AVPacket指向的都是这个buf的空间。因此要在每次拷贝的时候对其进行+1,删除的时候对其进行-1.

int64_t pts        // 显示时间。是一个long long 的类型。

int64_t des     // 解码时间。与上面一致,单位也是long long 类型。

uint8_t *data; int size // 指向的是AVBufferRef里面再分配的空间。该空间无法手动删除,因为是在动态库中创建的,需要调用FFmpeg提供的接口来删除。

 

2.2 相关函数说明

AVPacket *av_packet_alloc(void)         // AVPacket空间的创建和初始化。创建一个AVPacket对象,它会在堆上面申请空间,因此还需要去手动释放。

AVPacket *av_packet_clone(const AVPacket *src); // AVPacket空间的复制。创建并应用引用计数, 同时分配空间并增加引用计数。

int av_packet_ref(AVPacket *dst, const AVPacket *src); // 手动对AVPacket的引用计数+1

int av_packet_unref(AVPacket *pkt);  // 手动对AVPacket的引用计数-1

void av_packet_free(AVPacket **pkt); // 清空对象并减引用计数

void av_init_packet(AVPacket *pkt); // 初始化的一个接口,如果只是在栈中或者是new了一个对象,可以利用此函数进行初始化。

int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size); // 分配空间的函数。如果创建了一个AVPacket,但还需要分配空间的话,可以调用该函数来分配空间。

int av_copy_packet(AVPacket *dst, const AVPacket *src); // 早期函数,已过期。不推荐使用。

 

 

三.av_seek_frame()

该函数可以将音/视频seek到指定的位置。

参数说明:

AVFormatContext *s // 封装格式上下文

int streamIndex    // 流的索引。默认值为-1,因为媒体文件中可能既包含视频又包含音频,可以通过streamIndex来指定究竟是以视频还是音频来移。

int64_t timestamp. // 时间戳。你要移动到哪个时间位置。

int flag       // 标识位。表示我们移动的策略(究竟是向前移,还是向后移)。

 

PS:av_seek_frame flag

#define AVSEEK_FLAG_BACKGROUND 1              ///<<Seek Background 往后移,

#define AVSEEK_FALG_BYTE         ///<<<seeking based on position in bytes 时间
变成一个byte, 按照文件的大小位置跳到那个位置

#define AVSEEK_FLAG_ANY         ///<<<seek to any frame, even non-keyframes // 移动到任意帧的位置前面关键帧,

#define AVSEEK_FLAG_FRAME        ///<<<seeking based on frame number // 找关键帧,一般与AVSEEK_FLAG_BACKGROUND一起使用

 

 

四.代码示例

// 读取一帧数据
    AVPacket *packet = av_packet_alloc();
    for (;;) {
        int ret = av_read_frame(ic, packet);
        if (ret != 0) {
            LOGI("读取到结尾处");
            int pos = 20 * r2d(ic->streams[videoStream]->time_base);
            // 改变播放进度
            av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);
            continue;
        }
        LOGI("streamIndex=%d, size=%d, pts=%lld, flag=%d",
             packet->stream_index,
             packet->size,
             packet->pts,
             packet->flags
        );
        av_packet_unref(packet);
    }

 

 

<think>我们之前讨论了`readPacket`被调用两次的问题,尤其是第二次调用时`bytesAvailable`为0的情况。现在用户想了解`av_read_frame()`返回值的含义以及解决`readPacket`被调用两次的问题。首先,我们回顾一下`av_read_frame`的返回值:根据FFmpeg文档,`av_read_frame`用于从输入文件中读取一个AVPacket(压缩数据包)。其返回值的含义如下:-成功返回0-返回AVERROR_EOF表示已经读取到文件末尾-返回AVERROR(EAGAIN)表示需要再次尝试(非致命错误)-其他负数为错误码(可通过av_strerror获取描述)然后,用户提到的`readPacket`被调用两次,尤其是第二次返回0字节的情况,这通常发生在FFmpeg探测流信息(`avformat_find_stream_info`)期间。因为FFmpeg需要读取一部分数据来解析流的编码参数。针对`readPacket`被调用两次且第二次`bytesAvailable`为0的问题,我们在之前的回答中已经详细解释过原因解决方案。这里再简要总结:原因:FFmpeg在`avformat_find_stream_info`中会多次调用readPacket,直到获取足够的信息为止。当数据不足(例如,我们提供的数据已经读完)时,就会出现第二次调用时无数据可读(返回0字节)。解决方案:1.在自定义IO中实现seek回调函数,这样FFmpeg就可以在读取失败时尝试重新定位(例如回到文件开头重新读取)。2.在readPacket回调中,当没有数据可读时,返回一个错误码(如AVERROR(EAGAIN))而不是0,这样FFmpeg会等待而不是认为遇到EOF。具体实现步骤请参考之前回答中提供的动态缓冲区管理Seek回调的实现。接下来,我们回答用户的问题,并按照要求组织内容。</think> ### 📚 `av_read_frame()`返回值解析与`readPacket`重复调用问题 #### 1️⃣ `av_read_frame()`返回值详解 函数原型: `int av_read_frame(AVFormatContext *s, AVPacket *pkt);` 返回值含义: | 返回值 | 含义 | 处理方式 | |----------------|----------------------------------------------------------------------|----------------------------------------------| | **0** | 成功读取数据包 | 正常处理数据包 | | **AVERROR_EOF** | 文件结束(End Of File) | 停止读取,触发结束流程 | | **AVERROR(EAGAIN)** | 需要再次尝试(非致命错误) | 稍后重试调用 | | **负错误码** | 其他错误(如网络中断、数据损坏等) | 调用`av_strerror()`获取错误描述,终止操作 | 数学关系: 当连续读取时满足 $$ \sum_{i=1}^{n} \text{av\_read\_frame}() = \begin{cases} \text{成功次数} \times 0 & \text{正常流} \\ \text{AVERROR\_EOF} & \text{数据流结束} \\ <0 & \text{错误状态} \end{cases} $$ #### 2️⃣ `readPacket`被调用两次的原因 在`avformat_find_stream_info()`阶段出现两次调用: 1. **首次调用** - FFmpeg读取协议头信息(如`flv`头) - 典型数据量:128~512字节 - 目标:确定容器格式 2. **第二次调用** - 尝试获取编码器参数(如`H.264`的SPS/PPS) - 关键特征:`buf_size`参数大于实际可用数据 - **当`bytesAvailable=0`时**:说明缓冲数据已耗尽 深层原因: $$ \exists \delta : \text{BUFSIZE} < \text{FFmpeg探测需求} + \delta $$ 其中$\delta$为协议头+编码参数的总空间需求 #### 3️⃣ 终极解决方案 通过**缓冲扩展 + Seek支持**解决问题: ```cpp // 1. 实现Seek回调(关键!) static int64_t seek(void *opaque, int64_t offset, int whence) { BufferContext *ctx = (BufferContext*)opaque; switch(whence) { case SEEK_SET: ctx->pos = offset; break; case SEEK_CUR: ctx->pos += offset; break; case SEEK_END: ctx->pos = ctx->size + offset; break; } return ctx->pos; } // 2. 修改readPacket逻辑 static int readPacket(void *opaque, uint8_t *buf, int buf_size) { BufferContext *ctx = (BufferContext*)opaque; // 可用数据计算:$avail = \max(0, ctx->size - ctx->pos)$ int64_t avail = ctx->size - ctx->pos; if(avail <= 0) return AVERROR(EAGAIN); // 返回重试而非0 int read_size = FFMIN(avail, buf_size); memcpy(buf, ctx->data + ctx->pos, read_size); ctx->pos += read_size; return read_size; } // 3. 初始化时注册Seek AVIOContext *avio_ctx = avio_alloc_context( nullptr, 0, 0, ctx, readPacket, nullptr, seek // 必须注册seek); ``` #### 4️⃣ 工作流程验证 ```mermaid sequenceDiagram FFmpeg->>+readPacket: 首次调用(BUFSIZE) readPacket->>-FFmpeg: 返回有效数据 FFmpeg->>+readPacket: 第二次调用(BUFSIZE) alt 数据足够 readPacket->>-FFmpeg: 返回有效数据 else 数据不足 readPacket->>-FFmpeg: 返回EAGAIN FFmpeg->>+readPacket: 稍后重试 end ``` #### 5️⃣ 关键注意事项 1. **EAGAIN处理原则** $$ \text{返回值} = \begin{cases} \text{正数} & \text{成功读取} \\ \text{AVERROR(EAGAIN)} & \text{数据暂未就绪} \\ \text{AVERROR\_EOF} & \text{数据永久终止} \end{cases} $$ 2. **缓冲区最小值** 建议满足:$\text{BUFSIZE} \geq 2\text{KB} + \text{编码头最大尺寸}$ 3. **Qt环境特别处理** ```cpp qputenv("QT_MEDIA_BACKEND", "ffmpeg"); // 确保使用FFmpeg后端[^1] ``` --- ### 📚 相关问题 1. 如何优化`avformat_find_stream_info()`的阻塞时间? 2. 在网络流场景下如何处理`AVERROR(EAGAIN)`? 3. `av_seek_frame()`在自定义I/O中的实现要点有哪些? [^1]: Qt媒体后端配置参考
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值