FFmpeg 4.3 H265十五,将h264文件通过ffmpeg ap变成avpacket,在通过解码器变成avframe,最后存储成YUV格式, 播放,以及avpacket结构体再研究

前提

前面我们学习了 将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,需要从以下几个方面处理:

  1. 解析SPS中的帧率参数,计算每帧的持续时间,累加得到PTS。
  2. 分析slice_header中的信息,如pic_order_cnt_lsb,结合POC计算方法推导显示顺序。
  3. 如果裸流来源于编码器,可能需要外部输入的时间戳信息。
  4. 在封装或传输时,手动生成PTS,例如根据系统时钟或固定帧率。

需要注意场编码的情况,可能导致帧率翻倍,这时候需要调整时间计算。此外,B帧的存在会导致PTS和DTS不同,需要正确处理解码和显示顺序。如果没有B帧,PTS和DTS可能一致,可以简化处理。

一、H.264裸流与PTS的关系
  1. H.264裸流结构
    H.264裸流由NALU(Network Abstraction Layer Unit)组成,每个NALU包含视频帧或头部信息(如SPS/PPS)。PTS属于封装层(如TS、MP4)的时间戳概念,原生H.264码流中无显式存储的PTS字段‌15。

  2. 时间戳的生成逻辑

    • 若裸流由编码器直接生成,PTS通常由编码器根据输入帧的采集时间生成,并可能通过外部方式(如SEI信息)传递‌17。
    • 若裸流来自文件或网络传输且未封装,需通过其他途径(如外部时间戳文件)关联PTS‌46。
二、PTS的推算方法
  1. 基于SPS帧率参数
    SPS(Sequence Parameter Set)中的time_scalenum_units_in_tick可计算基准帧率:

     

    textCopy Code

    fps = time_scale / (2 * num_units_in_tick)

    需注意编码器可能因分场编码导致帧率翻倍,需根据实际场景调整‌15。

  2. 基于解码顺序与显示顺序

    • 无B帧场景‌:PTS与DTS(解码时间戳)一致,可通过frame_num(标识解码顺序)推算‌26。
    • 含B帧场景‌:需结合pic_order_cnt_lsb(Slice Header字段)计算POC(Picture Order Count),进而推导显示顺序‌26。
  3. 手动生成
    若裸流无时间信息,可基于固定帧率或系统时钟按以下公式生成PTS:

     

    textCopy Code

    PTS = 当前帧序号 × (时间基准 / 帧率)

    例如,90kHz时间基准下,30fps视频的PTS间隔为 90000 / 30 = 3000‌38。


三、实践注意事项
  1. 时间基准对齐
    PTS需根据封装格式要求转换时间单位(如TS流用90kHz,MP4用timescale参数)‌14。

  2. 场编码处理
    若视频采用分场编码(顶场+底场),需将单帧拆分为两个场,并按场间隔分配PTS‌15。

  3. 音视频同步
    PTS需与音频时间戳基于同一时钟源,避免播放时唇音不同步‌78。


四、工具与库支持
  1. FFmpeg
    解码H.264裸流时,可通过AVPacket.dtsAVFrame.pts获取时间戳,但需确保输入数据包含有效时间信息‌68。
  2. 编码器接口
    部分编码器(如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裸流的关系
  1. DTS的本质
    DTS表示帧的解码顺序,由编码器根据帧的编码顺序生成,用于指导解码器按正确顺序处理数据。在无B帧的场景中,DTS与显示顺序(PTS)一致;含B帧时,解码顺序与显示顺序分离,需通过DTS/PTS同步‌12。

  2. 裸流中的DTS映射
    H.264裸流的NALU(Network Abstraction Layer Unit)未直接定义DTS字段,但可通过以下方式关联:

    • frame_num字段‌:位于Slice Header中,标识帧在码流中的存储顺序(即解码顺序),等同于DTS的逻辑值‌15。
    • 解码顺序依赖‌:I/P/B帧的编码顺序可能与显示顺序不同(如B帧需参考后续帧),此时DTS需通过frame_numpic_order_cnt_lsb(POC)联合推导‌12。

二、DTS的生成方法
  1. 无B帧场景

    • DTS与frame_num严格对应,按编码顺序递增。例如,若GOP(Group of Pictures)内帧顺序为I0-P1-P2-P3,则DTS依次为0、1、2、3‌15。
  2. 含B帧场景

    • 需结合frame_num和POC计算解码顺序。例如,编码顺序为I0-B1-B2-P3时,解码顺序需优先处理参考帧(I0和P3),再处理B帧(B1、B2),此时DTS可能为0、3、1、2‌15。
  3. 外部时间戳注入

    • 若裸流来自实时编码器或特定设备,DTS可能通过SEI(Supplemental Enhancement Information)或外部元数据传递‌8。

三、实践要点
  1. 时间基准对齐
    DTS值需基于时间基准(如90kHz)转换。例如,30fps视频的DTS间隔为 90000 / 30 = 3000‌38。

  2. 场编码处理
    分场编码时(顶场+底场),单帧解码需拆分为两个场,DTS需按场间隔分配。例如,一帧拆分为两场时,DTS间隔为原帧间隔的一半‌47。

  3. 工具支持

    • FFmpeg‌:解码裸流时,通过AVPacket.dts获取DTS,需确保输入数据包含有效时间信息(如通过fps参数指定帧率)‌8。
    • 编码器配置‌:部分编码器(如x264)支持通过参数强制DTS与PTS一致(无B帧模式)‌5。

四、示例:DTS推算流程
  1. 输入数据

    • 裸流帧顺序:I0(frame_num=0)、B1(frame_num=1)、B2(frame_num=2)、P3(frame_num=3)。
    • 显示顺序:I0、B1、B2、P3 → 解码顺序:I0、P3、B1、B2。
  2. 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 为 0‌24。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值