前提
前面我们学习了 将YUV数据读取到AVFrame,然后将AVFrame通过 h264编码器变成 AVPacket后,然后将avpacket直接存储到了本地就变成了h264文件。
这一节课,学习解码的一部分。我们需要将 本地存储的h264文件进行帧分隔,也就是变成avpacket,然后再次变成 avframe,有了这个avframe就可以通过SDL 显示出来了。
这里有三个问题:
1.如何将h264变成 avpacket?
2.如何将avpacket变成avframe?
3.如何将avframe show出来?(这个在前面已经学习过了)
我们一个一个的解决。
1. 将h264文件进行帧分隔变成avpacket
这个问题的结论是我们需要通过 ffmpeg的api 来完成。
在学习 ffmpeg的api之前,我们可以自己想一下,如果不使用ffmpeg 的 api,我们如果自己实现,应该怎么办呢? 为什么在学习ffmpeg的api之前,我们需要自己想一下怎么实现呢?这是因为ffmpeg的api也是程序员开发的,如果我们想的和ffmpeg的api 的实现差不多,那对于我们理解ffmpeg的api的理解很有好处,而且对于这些原始数据的理解也有很大的好处。
1.1 自己想象中的实现以及遇到的问题和解法思路
前面的知识回顾:
H.264原始码流(裸流)是由⼀个接⼀个NALU组成。
一个h264文件中,存储的都是 avpacket的data;
一个NALU 和 另一个 NALU 是用 0X 00 00 00 01 分隔的(AnnexB 模式情况下)。
1.1.1 data 和 size 的获取
那么理论上我们可以从 h264 裸流中读取一个一个 avpacket->data。进而解析。
那么读取多大呢?最好是读取的大小刚好是一个avpacket的大小。但是avpacket的大小是不确定的,一种方案是,我们从 h264裸流的开始位置,每次读取比较大的一段,例如4096,然后取出第一个 0X 00 00 00 01 和 第二个 0X 00 00 00 01,然后通过极端 就知道第一个avpacket的大小 size1 了,然后再去从h264 裸流位置读取 size1的大小,这样,读取的大小就是一个完成的avpacket->data。然后再去解析avpacket -->data.
到这里。我们已经知道了 avpacket的data 和avpacket的size了,那么其他的 avpacket的 成员变量是如何赋值的呢?
typedef struct AVPacket {
AVBufferRef *buf;
int64_t pts;
int64_t dts;
uint8_t *data; //已经知道
int size; //已经知道
int stream_index;
int flags;
AVPacketSideData *side_data;
int side_data_elems;
int64_t duration;
int64_t pos;
} AVPacket;
1.1.2 AVBufferRef *buf的获取
先来回答一个问题:
avpacket 中 data,size 和 AVBufferRef 中的data ,size 和 AVBuffer 中的data 和size 的关系:
data 最终都是指向同一块内存。size 都是这个data的大小。
1. 内存指向关系
- AVPacket 的
data
:直接指向压缩的音视频数据(如 H.264 NAL 单元或 AAC 音频帧)。 - AVBufferRef 的
data
:与 AVPacket 的data
指向同一内存地址,但通过引用计数机制管理所有权。 - AVBuffer 的
data
:实际存储数据的内存区域,由 AVBufferRef 间接引用。
总结:三者 data
字段最终指向同一块内存,但通过不同层级的结构体实现所有权管理。
2. 引用计数管理机制
- AVBuffer:是底层数据缓冲区的核心结构体,通过
refcount
字段记录被引用的次数。 - AVBufferRef:作为 AVBuffer 的引用句柄,每个 AVBufferRef 共享同一 AVBuffer 的
data
。多个 AVBufferRef 可指向同一 AVBuffer,此时refcount
递增。 - AVPacket:通过
buf
字段(类型为 AVBufferRef)关联到 AVBuffer。若buf
非空,则 AVPacket 的data
由引用计数自动管理;若buf
为空,需手动管理data
内存。
也就是说:如果我们自己从h264裸流中获取avpacket的 buf要考量的问题很多。自己实现要符合ffmepg关于 AVBufferRef 和 AVBuffer 结构体的实现,不实际。
typedef struct AVPacket {
AVBufferRef *buf;
int64_t pts;
int64_t dts;
uint8_t *data; //已经知道
int size; //已经知道
int stream_index;
int flags;
AVPacketSideData *side_data;
int side_data_elems;
int64_t duration;
int64_t pos;
} AVPacket;
typedef struct AVBufferRef {
AVBuffer *buffer;
/**
* The data buffer. It is considered writable if and only if
* this is the only reference to the buffer, in which case
* av_buffer_is_writable() returns 1.
*/
uint8_t *data;
/**
* Size of data in bytes.
*/
size_t size;
} AVBufferRef;
struct AVBuffer {
uint8_t *data; /**< data described by this buffer */
size_t size; /**< size of data in bytes */
/**
* number of existing AVBufferRef instances referring to this buffer
*/
atomic_uint refcount;
/**
* a callback for freeing the data
*/
void (*free)(void *opaque, uint8_t *data);
/**
* an opaque pointer, to be used by the freeing callback
*/
void *opaque;
/**
* A combination of AV_BUFFER_FLAG_*
*/
int flags;
/**
* A combination of BUFFER_FLAG_*
*/
int flags_internal;
};
1.1.3 pts的获取:
在h264 裸流中可以通过计算的方式,获得。
H264裸流本身可能不直接存储PTS信息,这可能需要从其他途径获取或者计算。
H264裸流文件本身可能不包含显式的PTS,需要从以下几个方面处理:
- 解析SPS中的帧率参数,计算每帧的持续时间,累加得到PTS。
- 分析slice_header中的信息,如pic_order_cnt_lsb,结合POC计算方法推导显示顺序。
- 如果裸流来源于编码器,可能需要外部输入的时间戳信息。
- 在封装或传输时,手动生成PTS,例如根据系统时钟或固定帧率。
需要注意场编码的情况,可能导致帧率翻倍,这时候需要调整时间计算。此外,B帧的存在会导致PTS和DTS不同,需要正确处理解码和显示顺序。如果没有B帧,PTS和DTS可能一致,可以简化处理。
一、H.264裸流与PTS的关系
-
H.264裸流结构
H.264裸流由NALU(Network Abstraction Layer Unit)组成,每个NALU包含视频帧或头部信息(如SPS/PPS)。PTS属于封装层(如TS、MP4)的时间戳概念,原生H.264码流中无显式存储的PTS字段15。 -
时间戳的生成逻辑
- 若裸流由编码器直接生成,PTS通常由编码器根据输入帧的采集时间生成,并可能通过外部方式(如SEI信息)传递17。
- 若裸流来自文件或网络传输且未封装,需通过其他途径(如外部时间戳文件)关联PTS46。
二、PTS的推算方法
-
基于SPS帧率参数
SPS(Sequence Parameter Set)中的time_scale
和num_units_in_tick
可计算基准帧率:textCopy Code
fps = time_scale / (2 * num_units_in_tick)
需注意编码器可能因分场编码导致帧率翻倍,需根据实际场景调整15。
-
基于解码顺序与显示顺序
- 无B帧场景:PTS与DTS(解码时间戳)一致,可通过
frame_num
(标识解码顺序)推算26。 - 含B帧场景:需结合
pic_order_cnt_lsb
(Slice Header字段)计算POC(Picture Order Count),进而推导显示顺序26。
- 无B帧场景:PTS与DTS(解码时间戳)一致,可通过
-
手动生成
若裸流无时间信息,可基于固定帧率或系统时钟按以下公式生成PTS:textCopy Code
PTS = 当前帧序号 × (时间基准 / 帧率)
例如,90kHz时间基准下,30fps视频的PTS间隔为
90000 / 30 = 3000
38。
三、实践注意事项
-
时间基准对齐
PTS需根据封装格式要求转换时间单位(如TS流用90kHz,MP4用timescale参数)14。 -
场编码处理
若视频采用分场编码(顶场+底场),需将单帧拆分为两个场,并按场间隔分配PTS15。 -
音视频同步
PTS需与音频时间戳基于同一时钟源,避免播放时唇音不同步78。
四、工具与库支持
- FFmpeg
解码H.264裸流时,可通过AVPacket.dts
和AVFrame.pts
获取时间戳,但需确保输入数据包含有效时间信息68。 - 编码器接口
部分编码器(如x264)支持通过回调函数或SEI插入自定义时间戳7。
综上,H.264裸流的PTS需通过解析编码参数、推算显示顺序或外部关联时间戳实现,具体方案需结合数据来源和应用场景12。
1.1.4 dts 的获取
在h264 裸流中可以通过计算的方式,获得。
我需要先理清楚H.264裸流的结构以及DTS的相关信息。根据之前整理的资料,H.264裸流本身不直接存储DTS和PTS,这些时间戳通常是封装格式里的东西,比如MP4或TS。不过用户可能想知道在裸流中如何获取或计算DTS。
首先,参考搜索结果中的1提到,DTS和PTS是解码端的概念,DTS是解码顺序,而PTS是显示顺序。H.264裸流的NALU中没有显式的DTS字段,但slice_header里的frame_num等同于DTS。这意味着虽然裸流中没有直接存储DTS,但可以通过解析frame_num来推断解码顺序。2也提到DTS是AVPacket中的成员,表示压缩包应该被解码的时间,但在裸流中可能需要通过其他方式获取。
接下来,3说明DTS和PTS是相对于系统参考时间SCR的,解码器根据DTS时间戳来决定何时解码。但H.264裸流没有SCR,所以DTS的计算可能需要依赖编码时的逻辑,比如frame_num的顺序。同时,5提到编码顺序和显示顺序可能不一致,特别是存在B帧的情况下,这时候DTS和PTS会有差异。
另外,6讨论RTP协议中的时间戳,虽然不直接相关,但提到时间戳用于同步,这可能帮助理解时间戳的作用。不过裸流中的DTS需要从编码参数中推导,比如frame_num。此外,8指出可以通过解析每帧的DTS来计算总解码时长,但裸流中没有元数据,可能需要手动计算。
总结一下,H.264裸流中的DTS不直接存在,但可以通过frame_num来代表解码顺序。对于没有B帧的情况,DTS和frame_num一致;存在B帧时,需要结合解码顺序来调整。同时,编码器可能在生成裸流时通过SEI或其他方式传递时间信息,但需要具体分析。
需要注意,如果用户处理的是来自编码器的裸流,可能需要检查是否有SEI信息包含时间戳。另外,解码时需要根据SPS中的参数计算帧率,进而推算时间间隔。例如,根据1中的方法,frame_num标识解码顺序,可以按顺序分配DTS值。当存在B帧时,解码顺序和显示顺序不同,DTS需要根据frame_num和POC来确定。
最后,工具如FFmpeg在解析裸流时,可能会生成默认的DTS,但需要确保输入正确的时间基准。同时,手动生成DTS需要根据固定帧率或编码参数计算每个NALU的解码时间。
H.264裸流文件中的DTS解析与生成机制
H.264裸流(Elementary Stream)本身不显式存储DTS(Decoding Time Stamp),但可通过码流结构和编码逻辑间接推导或生成。以下是其核心机制:
一、DTS与H.264裸流的关系
-
DTS的本质
DTS表示帧的解码顺序,由编码器根据帧的编码顺序生成,用于指导解码器按正确顺序处理数据。在无B帧的场景中,DTS与显示顺序(PTS)一致;含B帧时,解码顺序与显示顺序分离,需通过DTS/PTS同步12。 -
裸流中的DTS映射
H.264裸流的NALU(Network Abstraction Layer Unit)未直接定义DTS字段,但可通过以下方式关联:-
frame_num
字段:位于Slice Header中,标识帧在码流中的存储顺序(即解码顺序),等同于DTS的逻辑值15。 - 解码顺序依赖:I/P/B帧的编码顺序可能与显示顺序不同(如B帧需参考后续帧),此时DTS需通过
frame_num
和pic_order_cnt_lsb
(POC)联合推导12。
-
二、DTS的生成方法
-
无B帧场景
- DTS与
frame_num
严格对应,按编码顺序递增。例如,若GOP(Group of Pictures)内帧顺序为I0-P1-P2-P3,则DTS依次为0、1、2、315。
- DTS与
-
含B帧场景
- 需结合
frame_num
和POC计算解码顺序。例如,编码顺序为I0-B1-B2-P3时,解码顺序需优先处理参考帧(I0和P3),再处理B帧(B1、B2),此时DTS可能为0、3、1、215。
- 需结合
-
外部时间戳注入
- 若裸流来自实时编码器或特定设备,DTS可能通过SEI(Supplemental Enhancement Information)或外部元数据传递8。
三、实践要点
-
时间基准对齐
DTS值需基于时间基准(如90kHz)转换。例如,30fps视频的DTS间隔为90000 / 30 = 3000
38。 -
场编码处理
分场编码时(顶场+底场),单帧解码需拆分为两个场,DTS需按场间隔分配。例如,一帧拆分为两场时,DTS间隔为原帧间隔的一半47。 -
工具支持
- FFmpeg:解码裸流时,通过
AVPacket.dts
获取DTS,需确保输入数据包含有效时间信息(如通过fps
参数指定帧率)8。 - 编码器配置:部分编码器(如x264)支持通过参数强制DTS与PTS一致(无B帧模式)5。
- FFmpeg:解码裸流时,通过
四、示例:DTS推算流程
-
输入数据
- 裸流帧顺序:I0(frame_num=0)、B1(frame_num=1)、B2(frame_num=2)、P3(frame_num=3)。
- 显示顺序:I0、B1、B2、P3 → 解码顺序:I0、P3、B1、B2。
-
DTS生成
- 按解码顺序分配DTS:I0→0,P3→1,B1→2,B2→3(时间间隔由帧率决定)15。
综上,H.264裸流中DTS的生成需依赖frame_num
、编码顺序及时间基准,需根据帧类型和编码逻辑动态调整12。
1.1.5 flags的获取
在h264 裸流中:
可以参考 NALU 的 SPS 是否是关键帧,设定 AV_PKT_FLAG_KEY
可以参考 NALU 的 是否是关键帧,设定 AV_PKT_FLAG_KEY
我得先回想一下之前学过的FFmpeg结构体知识,尤其是AVPacket的结构。记得AVPacket是用来存储压缩后的音视频数据的,比如H.264或AAC的帧。flags字段应该和帧的属性有关,比如关键帧之类的。
根据之前的搜索结果,比如2提到AVPacket的flags字段可以用来判断是否是关键帧,特别是AV_PKT_FLAG_KEY这个标志。这可能就是用户关心的内容。另外,在3的结构体分析中,也提到了flags是AVPacket的一个成员,但具体有哪些可能的值还需要查证。
再想想,AVPacket的flags可能还有其他用途,比如标记损坏的数据包或者需要丢弃的帧。可能需要参考更多的搜索结果。比如在6中,提到AVPacket的使用需要注意引用计数,但flags的具体细节没有展开。而8里提到AVPacket存储的是压缩数据,可能包含多个音频帧或一个视频帧,但flags的具体定义可能要看FFmpeg的文档或源码。
不过用户给的搜索结果中,2和3应该是最相关的。2详细说明了如何通过flags的AV_PKT_FLAG_KEY来判断关键帧,而3提到flags是结构体中的一个字段,但未具体列出所有可能的标志。可能需要结合其他搜索结果中的信息,比如7提到AVPacket用于解码前或编码后的数据,可能涉及不同类型的帧,如I帧、P帧、B帧,这些可能对应不同的flags。
此外,在4中提到AVPacket可能包含空的数据,只有边信息,这时候flags可能有相关的标记。比如是否包含有效数据,或者是否需要特殊处理。
总结一下,用户的问题是关于AVPacket中的flags字段有哪些可能的标志及其含义。根据搜索结果,已知的有AV_PKT_FLAG_KEY表示关键帧,可能还有其他的比如损坏数据、可丢弃等标志。需要确认这些标志是否存在,并正确引用来源。
需要注意的是,不同版本的FFmpeg可能会有不同的标志定义,用户提到的搜索结果中有不同时间的资料,比如3是2021年的,而8是2018年的,可能存在差异。但根据用户提供的信息,目前应该以最新的为准,但用户问题中的参考信息只到2025年7,不过这个时间可能是错误的,需要确认。
最终,回答应该列出常见的flags标志,如关键帧、损坏数据、可丢弃,并给出对应的宏定义和解释,同时引用相关的搜索结果作为支持,比如2和3。另外,确保每个说明都有对应的角标引用,如23。
AVPacket 的 flags
字段用于标记数据包的特性或状态,其值由多个位标志组合而成。以下是常见的 flags
标志及其作用:
1. 关键帧标志 AV_PKT_FLAG_KEY
- 作用:标记当前数据包是否为关键帧(I帧)23。
- 关键帧包含完整的图像信息,解码时无需依赖其他帧。
- 通过
AVPacket.flags & AV_PKT_FLAG_KEY
可判断是否为关键帧。
2. 损坏数据标志 AV_PKT_FLAG_CORRUPT
- 作用:表示数据包可能已损坏或包含错误37。
- 解码器需根据此标志决定是否尝试纠错或丢弃该数据包。
3. 可丢弃帧标志 AV_PKT_FLAG_DISCARD
- 作用:标记数据包可被安全丢弃,不影响后续解码37。
- 常见于非参考帧(如B帧),丢弃后不会导致解码错误。
4. 时间戳相关标志
-
AV_PKT_FLAG_TRUSTED
:表示数据包的时间戳(pts
/dts
)可信,无需校验3。 -
AV_PKT_FLAG_DISPOSABLE
:标记数据包为临时帧(如B帧),可优先丢弃以优化内存或带宽7。
使用示例
cCopy Code
// 判断是否为关键帧 if (pkt->flags & AV_PKT_FLAG_KEY) { // 处理关键帧逻辑 } // 检查数据是否损坏 if (pkt->flags & AV_PKT_FLAG_CORRUPT) { // 错误处理逻辑 }
总结
标志宏 | 值(位掩码) | 用途说明 |
---|---|---|
AV_PKT_FLAG_KEY |
0x0001 | 关键帧(I帧)23 |
AV_PKT_FLAG_CORRUPT |
0x0002 | 数据可能损坏37 |
AV_PKT_FLAG_DISCARD |
0x0004 | 可丢弃的非参考帧37 |
AV_PKT_FLAG_TRUSTED |
0x0008 | 时间戳可信3 |
AV_PKT_FLAG_DISPOSABLE |
0x0010 | 临时帧(如B帧)7 |
这些标志通过位运算组合使用,用于控制数据包的解码、传输和存储策略
1.1.6 AVPacketSideData *side_data;的获取
AVPacketSideData用于存储附加数据,比如非音视频的元数据或编码参数。例如,H.264的SEI信息或者加密数据。用户可能需要知道如何正确使用这个结构体,避免内存问题。
在搜索结果中,提到AVPacketSideData的data字段指向这些附加数据,而size是数据的大小。比如使用av_packet_new_side_data函数来创建,避免栈内存的问题。
需要注意的是,SideData的生命周期需要与AVPacket一致,所以必须动态分配。另外,编码器可能在输出时使用SideData传递参数,解码时读取这些信息。
总结下来,用户可能需要了解SideData的作用、如何正确创建和释放,以及常见的使用场景。需要结合多个参考资料,特别是35的内容,确保覆盖内存管理和实际应用案例。
AVPacket 的 AVPacketSideData
是用于存储与压缩数据包(如视频/音频帧)相关的附加元数据的结构体,其核心机制如下:
1. 数据结构定义
typedef struct AVPacketSideData {
uint8_t *data; // 指向附加数据的指针
int size; // 数据大小(字节)
enum AVPacketSideDataType type; // 数据类型标识
} AVPacketSideData;
-
data
:附加数据的具体内容,例如编码参数、SEI 信息、加密密钥等。 -
type
:通过枚举类型AVPacketSideDataType
标识数据类型(如AV_PKT_DATA_PALETTE
表示调色板信息)。
2. 典型应用场景
数据类型(type ) |
用途说明 |
---|---|
AV_PKT_DATA_NEW_EXTRADATA |
存储编解码器初始化参数(如 SPS/PPS)35 |
AV_PKT_DATA_ENCRYPT_INIT |
加密初始化信息(如 DRM 密钥)7 |
AV_PKT_DATA_DISPLAYMATRIX |
视频旋转/翻转矩阵(如 90 度旋转)35 |
AV_PKT_DATA_MASTERING_DISPLAY_METADATA |
HDR 显示元数据(如亮度范围)57 |
3. 内存管理规则
-
创建方式:
应通过av_packet_new_side_data()
动态分配内存,确保数据生命周期与AVPacket
一致58。
示例:cCopy Code
AVPacket *pkt = av_packet_alloc(); uint8_t *side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, data_size); memcpy(side_data, src_data, data_size);
-
释放方式:
调用av_packet_unref()
时,自动释放side_data
的内存35。
禁止手动释放side_data->data
,否则会导致内存错误8。
4. 常见问题与解决
-
错误示例:
直接使用栈内存初始化AVPacketSideData
:cCopy Code
AVPacketSideData side_data; uint8_t buffer; side_data.data = buffer; // 错误!buffer 为栈内存,离开作用域后失效:ml-citation{ref="8" data="citationList"}
后果:内存访问越界或数据损坏8。
-
正确做法:
始终通过av_packet_new_side_data()
或av_malloc()
动态分配数据内存58。
5. API 函数总结
函数 | 作用 |
---|---|
av_packet_new_side_data() |
为 AVPacket 创建新的附加数据,自动管理内存58 |
av_packet_get_side_data() |
根据类型获取 AVPacket 中的附加数据37 |
av_packet_free_side_data() |
显式释放所有附加数据(通常无需手动调用)35 |
附:代码示例(添加 SEI 数据)
cCopy Code
AVPacket *pkt = av_packet_alloc();
// 创建 SEI 数据(假设 sei_data 已定义)
uint8_t *sei_side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, sei_size);
memcpy(sei_side_data, sei_data, sei_size);
// 处理完成后释放
av_packet_unref(pkt);
1.1.7 side_data_elems的获取
AVPacket 的 side_data_elems
字段表示该数据包中附加数据(side_data
)的元素数量,其核心要点如下:
AVPacket 的 side_data_elems
字段表示该数据包中附加数据(side_data
)的元素数量,其核心要点如下:
1. 字段定义与作用
int side_data_elems; // 附加数据的元素数量
AVPacketSideData *side_data; // 指向附加数据数组的指针
- 作用:
side_data_elems
记录side_data
数组中有效元素的个数,用于遍历或操作附加数据。
2. 典型操作示例
遍历附加数据:
for (int i = 0; i < pkt->side_data_elems; i++) {
AVPacketSideData *sd = &pkt->side_data[i];
if (sd->type == AV_PKT_DATA_NEW_EXTRADATA) {
// 处理编解码参数(如 SPS/PPS):ml-citation{ref="5,7" data="citationList"}
}
}
添加附加数据:
使用 av_packet_add_side_data()
添加数据时,side_data_elems
自动递增。
uint8_t *sei_data = ...;
av_packet_add_side_data(pkt, AV_PKT_DATA_SEI_UNREGISTERED, sei_data, sei_size);
释放所有附加数据:
av_packet_free_side_data()
会清空 side_data
数组并将 side_data_elems
置零。
3. 内存管理规则
- 动态分配:
side_data
数组由 FFmpeg 自动管理内存,用户无需手动分配或释放单个元素。 - 释放时机:
调用av_packet_unref()
或av_packet_free_side_data()
时,自动释放side_data
内存并重置side_data_elems
为 024。
4. 常见应用场景
场景 | 说明 |
---|---|
编解码参数传递 | 存储 SPS/PPS(H.264)或 VPS/SPS/PPS(H.265)等初始化参数57 |
SEI 信息携带 | 嵌入用户自定义元数据(如时间戳、版权信息)8 |
HDR 元数据传递 | 存储 AV_PKT_DATA_MASTERING_DISPLAY_METADATA 类型的 HDR 参数57 |
5. 注意事项
- 空指针检查:
访问side_data
前需确认side_data_elems > 0
,避免空指针异常45。 - 数据类型匹配:
需根据AVPacketSideDataType
筛选特定类型数据,防止误用57。
附:结构关系图
AVPacket
├── side_data_elems → 元素数量
└── side_data → 数组指针
├── AVPacketSideData → {type, data, size}
├── AVPacketSideData:ml-citation{ref="1" data="citationList"} → {type, data, size}
└── ...
1.1.8 duration的获取
H.264 裸流本身不直接存储 duration
信息,其时间相关属性需通过其他方式推导或外部元数据提供,具体如下:
duration
的生成方式
场景 | 说明 |
---|---|
封装格式(如 MP4) | 在 MP4 等封装格式中,duration 由容器层的 moov 原子(包含时间基准和帧率)计算得出24。 |
解码器推导 | 解码器根据帧率(如 avg_frame_rate )或相邻帧的 PTS(Presentation Time Stamp)差值推算单帧时长8。 |
FFmpeg 处理裸流 | 若通过 FFmpeg 读取裸流,可能需手动指定 -r (帧率)参数,否则 duration 可能为 0 或错误值36。 |
裸流处理注意事项
- 时间基准依赖:
从裸流中提取时间信息时,需结合流的时间基准(time_base
)计算全局时间,例如:cCopy Code
double duration_sec = pkt->duration * av_q2d(stream->time_base); // 需外部提供 stream->time_base:ml-citation{ref="8" data="citationList"}
- 手动补全信息:
若需将裸流转换为可播放文件(如 FLV 或 MP4),需通过avformat_write_header
写入封装层的元数据(包括时间基准和帧率)36。
特殊情况
- SEI 信息:
部分 H.264 裸流可能携带 SEI(补充增强信息)数据,但 SEI 通常用于传递版权、时间戳等辅助信息,而非标准化的duration
值7。 - 动态帧率流:
若视频流帧率不固定(如可变帧率编码),裸流无法直接提供准确的duration
,需依赖动态时间戳计算8。
结论
H.264 裸流自身不含 duration
,需通过外部元数据(如帧率、时间戳)或封装格式的上下文推导。处理裸流时,需显式指定时间参数或依赖解码器/封装器的逻辑生成时间信息
AVPacket 的 duration
字段表示该数据包中压缩数据的持续时间,其核心要点如下:
1. 字段定义与单位
- 定义:
duration
表示当前数据包的解码后内容在时间轴上的持续时长,通常用于计算帧的显示时长或音频样本的播放时长36。 - 单位:
- 默认单位:当未明确指定时间基准时,默认以
AV_TIME_BASE
(微秒)为单位4。 - 流时间基准:若数据包关联到具体媒体流(如视频或音频流),则使用该流的
time_base
作为单位6。
- 默认单位:当未明确指定时间基准时,默认以
2. 时间基准转换
- 流时间基准到全局时间:
需结合AVStream.time_base
进行转换,例如:cCopy Code
double global_duration = pkt->duration * av_q2d(stream->time_base); // 转换为秒:ml-citation{ref="4,6" data="citationList"}
- 未关联流时:
直接使用AV_TIME_BASE
单位(微秒),需除以1,000,000
转换为秒4。
3. 典型应用场景
场景 | 说明 |
---|---|
视频帧时长计算 | 通过 duration 确定视频帧的显示时长,辅助同步音视频36。 |
音频样本数计算 | 结合音频流的采样率(sample_rate ),计算音频数据包包含的样本数量6。 |
进度条更新 | 累加数据包的 duration 值,估算媒体播放的进度4。 |
4. 注意事项
- 可能为 0:
部分封装格式(如某些 MP4 文件)可能未明确记录duration
,此时字段值为 0,需通过其他方式(如帧率或采样率)推算6。 - 时间基准一致性:
处理duration
时需确保与关联流的time_base
一致,避免单位混淆46。
附:代码示例(获取全局时长)
AVPacket *pkt = ...;
AVStream *stream = ...;
double duration_sec = pkt->duration * av_q2d(stream->time_base);
printf("Duration: %.2f seconds\n", duration_sec); // 输出以秒为单位的时长
1.1.9 pos的获取
H.264 裸流本身不包含显式的 pos
(位置或时间戳)信息,其定位与时间管理需依赖外部机制,其时间与位置管理需通过封装格式、传输协议或自定义逻辑实现。若需精准定位,建议结合容器层元数据或外部索引机制
1 . 时间与位置信息的来源
场景 | 说明 |
---|---|
封装格式(如 MP4/FLV) | 容器层(如 MP4 的 moov 或 FLV 的 ScriptTag )通过 dts (解码时间戳)和 pts (显示时间戳)记录时间位置。 |
传输协议(如 RTP) | RTP 包头携带时间戳和序号(sequence number ),用于接收端同步和解码顺序管理。 |
自定义处理 | 若直接操作裸流,需通过外部逻辑(如帧计数器、文件偏移量)记录帧的物理位置或时间基准。 |
2. 裸流处理中的定位实践
- 物理位置追踪:
在文件读写时,可通过起始码的位置(如0x000001
)分割 NALU,并记录每个 NALU 在文件中的偏移量(offset
)实现物理位置追踪78。 - 时间基准推导:
若无封装层时间戳,可基于帧率(如avg_frame_rate
)或相邻 NALU 的间隔估算时间位置68。
3. 特殊场景
- SEI 信息:
部分 H.264 裸流可能通过 SEI(补充增强信息)传递自定义时间戳或定位标记,但此非标准实现,需编解码双方约定78。 - 动态帧率流:
可变帧率场景下,裸流无法直接提供时间连续性,需依赖动态时间戳或外部同步机制68。
1.2 ffmpeg 的解决方案(重点)
1.创建一个 对应解析器上下文 - AVCodecParserContext
AVCodecParserContext *av_parser_init(int codec_id);
需要使用 void av_parser_close(AVCodecParserContext *s);释放资源
2.创建解码器 和 解码器上下文。
AVCodec *avco