FFmpeg FLV编码器原理深度解析

FFmpeg FLV编码器原理深度解析:从代码到实现的完整流程

目录

  1. 引言
  2. FLV格式概述
  3. [FFmpeg FLV编码器架构](#FFmpeg FLV编码器架构)
  4. 核心数据结构解析
  5. 编码流程详解
  6. 关键函数深度剖析
  7. 元数据处理机制
  8. 关键帧索引系统
  9. 多轨道支持
  10. 性能优化与最佳实践
  11. 总结

引言

FLV(Flash Video)格式作为Adobe公司开发的流媒体容器格式,在视频直播、点播等领域有着广泛的应用。虽然Flash技术已经逐渐退出历史舞台,但FLV格式由于其良好的兼容性和流式传输特性,仍然被许多流媒体平台采用。FFmpeg作为最强大的多媒体处理框架之一,其FLV编码器(flvenc.c)实现了完整的FLV文件封装功能。

本文将从源码层面深入解析FFmpeg中FLV编码器的实现原理,通过详细的代码分析和流程图,帮助读者全面理解FLV编码的完整流程。文章将涵盖从初始化到数据包写入、从元数据生成到关键帧索引构建的每一个环节,力求做到深入浅出、通俗易懂。


FLV格式概述

2.1 FLV文件结构

FLV文件采用Tag-based的流式结构,整个文件由三部分组成:

复制代码
┌─────────────────────────────────────────┐
│         FLV File Header (9 bytes)       │
│  - Signature: "FLV" (3 bytes)          │
│  - Version: 1 (1 byte)                  │
│  - Flags: HasAudio/HasVideo (1 byte)    │
│  - HeaderSize: 9 (4 bytes)              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│         PreviousTagSize0 (4 bytes)      │
│  - Always 0                              │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│              Tag 1                       │
│  ┌───────────────────────────────────┐  │
│  │ Tag Header (11 bytes)              │  │
│  │  - TagType (1 byte)                │  │
│  │  - DataSize (3 bytes)              │  │
│  │  - Timestamp (4 bytes)             │  │
│  │  - StreamID (3 bytes)              │  │
│  └───────────────────────────────────┘  │
│  ┌───────────────────────────────────┐  │
│  │ Tag Data (variable size)           │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│         PreviousTagSize1 (4 bytes)      │
│  - Size of Tag 1 + 11                   │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│              Tag 2                       │
│  ...                                     │
└─────────────────────────────────────────┘

2.2 Tag类型

FLV定义了三种主要的Tag类型:

  1. 音频Tag (0x08): 包含音频编码数据
  2. 视频Tag (0x09): 包含视频编码数据
  3. 脚本Tag (0x12): 包含元数据信息(onMetaData)

2.3 时间戳机制

FLV使用32位时间戳,单位为毫秒。时间戳分为两部分:

  • 低24位:存储在Tag Header的Timestamp字段
  • 高8位:存储在Tag Header的TimestampExtended字段

这种设计允许FLV文件支持最长约24小时(2^31毫秒)的视频内容。


FFmpeg FLV编码器架构

3.1 模块组织结构

FFmpeg的FLV编码器位于libavformat/flvenc.c文件中,它实现了FFmpeg的AVOutputFormat接口。整个编码器采用面向对象的设计思想,通过函数指针实现多态。

复制代码
AVFormatContext
    │
    ├── AVOutputFormat (ff_flv_muxer)
    │       │
    │       ├── init: flv_init
    │       ├── write_header: flv_write_header
    │       ├── write_packet: flv_write_packet
    │       ├── write_trailer: flv_write_trailer
    │       └── deinit: flv_deinit
    │
    └── FLVContext (私有数据)
            │
            ├── 流信息
            │   ├── audio_par
            │   ├── video_par
            │   └── data_par
            │
            ├── 元数据偏移
            │   ├── duration_offset
            │   ├── filesize_offset
            │   └── metadata_size_pos
            │
            └── 关键帧索引
                ├── filepositions
                └── keyframe_index_size

3.2 编码器生命周期

FLV编码器的完整生命周期包括以下几个阶段:

复制代码
开始
  │
  ├─→ flv_init()          [初始化阶段]
  │   ├─ 分配内存
  │   ├─ 验证流参数
  │   └─ 设置时间戳信息
  │
  ├─→ flv_write_header()  [头部写入阶段]
  │   ├─ 写入FLV文件头
  │   ├─ 写入元数据Tag
  │   └─ 写入编解码器头
  │
  ├─→ flv_write_packet()  [数据包写入阶段]
  │   ├─ 循环处理每个AVPacket
  │   ├─ 写入Tag Header
  │   ├─ 写入Tag Data
  │   └─ 更新统计信息
  │
  ├─→ flv_write_trailer() [尾部写入阶段]
  │   ├─ 更新元数据
  │   ├─ 写入关键帧索引
  │   └─ 写入EOS Tag
  │
  └─→ flv_deinit()        [清理阶段]
      └─ 释放资源

核心数据结构解析

4.1 FLVContext结构体

FLVContext是FLV编码器的核心数据结构,存储了编码过程中的所有状态信息:

c 复制代码
typedef struct FLVContext {
    AVClass *av_class;              // AVClass用于选项系统
    
    // 文件位置信息
    int64_t duration_offset;         // duration字段在文件中的偏移
    int64_t filesize_offset;          // filesize字段在文件中的偏移
    int64_t datastart_offset;         // 数据区开始位置
    int64_t datasize_offset;          // datasize字段偏移
    int64_t videosize_offset;         // videosize字段偏移
    int64_t audiosize_offset;         // audiosize字段偏移
    
    // 元数据相关
    int64_t metadata_size_pos;        // 元数据大小位置
    int64_t metadata_totalsize_pos;   // 元数据总大小位置
    int64_t metadata_totalsize;        // 元数据总大小
    
    // 时间戳信息
    int64_t delay;                    // 第一个DTS的延迟值
    int64_t lasttimestamp_offset;     // 最后时间戳偏移
    double lasttimestamp;              // 最后时间戳值
    int64_t lastkeyframetimestamp_offset;
    double lastkeyframetimestamp;
    int64_t lastkeyframelocation_offset;
    int64_t lastkeyframelocation;
    
    // 关键帧索引
    int64_t keyframes_info_offset;    // 关键帧信息偏移
    int64_t keyframe_index_size;       // 关键帧索引大小
    int64_t filepositions_count;       // 关键帧位置数量
    FLVFileposition *filepositions;    // 关键帧位置链表
    FLVFileposition *head_filepositions;
    
    // 流参数
    AVCodecParameters *audio_par;     // 音频流参数
    AVCodecParameters *video_par;     // 视频流参数
    AVCodecParameters *data_par;       // 数据流参数
    double framerate;                  // 帧率
    
    // 控制标志
    int flags;                         // FLV标志位
    int64_t *last_ts;                  // 每个流的最后时间戳
    int *metadata_pkt_written;         // 元数据包是否已写入
    int *track_idx_map;                // 轨道索引映射
} FLVContext;

4.2 FLVFileposition结构体

用于存储关键帧的位置信息,构成链表结构:

c 复制代码
typedef struct FLVFileposition {
    int64_t keyframe_position;         // 关键帧在文件中的位置
    double keyframe_timestamp;         // 关键帧的时间戳
    struct FLVFileposition *next;      // 指向下一个节点
} FLVFileposition;

4.3 FLVFlags枚举

定义了FLV编码器的各种标志位:

c 复制代码
typedef enum {
    FLV_AAC_SEQ_HEADER_DETECT = (1 << 0),  // 检测AAC序列头
    FLV_NO_SEQUENCE_END = (1 << 1),         // 不写入序列结束Tag
    FLV_ADD_KEYFRAME_INDEX = (1 << 2),      // 添加关键帧索引
    FLV_NO_METADATA = (1 << 3),             // 不写入元数据
    FLV_NO_DURATION_FILESIZE = (1 << 4),    // 不写入duration和filesize
} FLVFlags;

编码流程详解

5.1 初始化阶段 (flv_init)

flv_init函数负责编码器的初始化工作,这是整个编码流程的第一步。

5.1.1 函数执行流程
复制代码
flv_init()
    │
    ├─→ 分配内存
    │   ├─ last_ts数组 (每个流的时间戳)
    │   ├─ metadata_pkt_written数组
    │   └─ track_idx_map数组
    │
    ├─→ 遍历所有流
    │   │
    │   ├─→ 视频流处理
    │   │   ├─ 验证多轨道视频编解码器
    │   │   ├─ 计算帧率
    │   │   ├─ 设置track_idx_map
    │   │   ├─ 验证编解码器兼容性
    │   │   └─ 检查MPEG4/H263的严格性
    │   │
    │   ├─→ 音频流处理
    │   │   ├─ 验证多轨道音频编解码器
    │   │   ├─ 设置track_idx_map
    │   │   ├─ 获取音频标志
    │   │   └─ 检查PCM格式警告
    │   │
    │   └─→ 数据流处理
    │       └─ 验证编解码器类型
    │
    ├─→ 设置PTS信息
    │   └─ avpriv_set_pts_info(32, 1, 1000)
    │       (32位PTS,时间基1/1000,即毫秒)
    │
    └─→ 初始化delay为AV_NOPTS_VALUE
5.1.2 关键代码分析

内存分配

c 复制代码
flv->last_ts = av_calloc(s->nb_streams, sizeof(*flv->last_ts));
flv->metadata_pkt_written = av_calloc(s->nb_streams, sizeof(*flv->metadata_pkt_written));
flv->track_idx_map = av_calloc(s->nb_streams, sizeof(*flv->track_idx_map));

这里为每个流分配了独立的时间戳数组和元数据写入标志数组,支持多轨道编码。

视频流验证

c 复制代码
if (video_ctr &&
    par->codec_id != AV_CODEC_ID_VP8 &&
    par->codec_id != AV_CODEC_ID_VP9 &&
    par->codec_id != AV_CODEC_ID_AV1 &&
    par->codec_id != AV_CODEC_ID_H264 &&
    par->codec_id != AV_CODEC_ID_HEVC) {
    av_log(s, AV_LOG_ERROR, "Unsupported multi-track video codec.\n");
    return AVERROR(EINVAL);
}

多轨道视频只支持特定的编解码器,这是FLV格式的限制。

编解码器兼容性检查

c 复制代码
if (!ff_codec_get_tag(flv_video_codec_ids, par->codec_id))
    return unsupported_codec(s, "Video", par->codec_id);

通过查找编解码器ID映射表来验证编解码器是否被FLV支持。

5.2 头部写入阶段 (flv_write_header)

flv_write_header函数负责写入FLV文件的头部信息,包括文件头、元数据Tag和编解码器头。

5.2.1 FLV文件头结构

FLV文件头共9字节:

复制代码
字节位置    大小    内容                说明
0-2         3      "FLV"              文件签名
3           1      0x01               版本号
4           1      Flags              标志位
              bit0: 保留
              bit2: HasAudio (1=有音频)
              bit4: HasVideo (1=有视频)
5-8         4      0x00000009         头部大小(固定为9)
5.2.2 函数执行流程
复制代码
flv_write_header()
    │
    ├─→ 写入FLV文件头
    │   ├─ "FLV" (3 bytes)
    │   ├─ Version: 1 (1 byte)
    │   ├─ Flags: HasAudio|HasVideo (1 byte)
    │   └─ HeaderSize: 9 (4 bytes)
    │
    ├─→ 处理特殊流(codec_tag == 5)
    │   └─ 写入8字节消息类型Tag
    │
    ├─→ 写入元数据Tag(如果未禁用)
    │   └─ write_metadata(s, 0)
    │
    ├─→ 写入编解码器头
    │   └─ 遍历所有流
    │       └─ flv_write_codec_header()
    │
    └─→ 记录数据区开始位置
        └─ flv->datastart_offset = avio_tell(pb)
5.2.3 元数据写入详解

write_metadata函数生成onMetaData脚本Tag,这是FLV文件最重要的元数据信息。

元数据结构

复制代码
FLV Tag Header (11 bytes)
    TagType: 0x12 (脚本Tag)
    DataSize: (待填充)
    Timestamp: 0
    StreamID: 0

Tag Data:
    AMF_DATA_TYPE_STRING: "onMetaData"
    AMF_DATA_TYPE_MIXEDARRAY:
        ArraySize: (元数据项数量)
        
        元数据项列表:
            "duration" -> double (时长,秒)
            "width" -> double (视频宽度)
            "height" -> double (视频高度)
            "videodatarate" -> double (视频码率,kbps)
            "framerate" -> double (帧率)
            "videocodecid" -> double (视频编解码器ID)
            "audiodatarate" -> double (音频码率,kbps)
            "audiosamplerate" -> double (采样率)
            "audiosamplesize" -> double (采样大小)
            "stereo" -> bool (是否立体声)
            "audiocodecid" -> double (音频编解码器ID)
            "filesize" -> double (文件大小,字节)
            ... (用户自定义元数据)
        
        "" -> AMF_END_OF_OBJECT

延迟写入机制

由于duration和filesize在编码完成前无法确定,FLV编码器采用了延迟写入机制:

  1. 在写入元数据时,先写入占位符值(0或估算值)
  2. 记录这些字段在文件中的偏移位置
  3. flv_write_trailer中,回写到这些位置更新真实值
c 复制代码
// 写入duration占位符
put_amf_string(pb, "duration");
flv->duration_offset = avio_tell(pb);  // 记录偏移
put_amf_double(pb, s->duration / AV_TIME_BASE);  // 写入占位值

// 写入filesize占位符
put_amf_string(pb, "filesize");
flv->filesize_offset = avio_tell(pb);  // 记录偏移
put_amf_double(pb, 0);  // 写入0作为占位符
5.2.4 编解码器头写入

flv_write_codec_header函数为每个流写入编解码器特定的头信息。

支持的编解码器头类型

  1. AAC音频

    • 标准模式:AudioTagHeader + AAC Sequence Header
    • 扩展模式:Extended AudioTagHeader + FourCC + AAC Sequence Header
  2. H.264视频

    • 标准模式:VideoTagHeader + AVC Sequence Header
    • 扩展模式:Extended VideoTagHeader + FourCC + AVC Sequence Header
  3. HEVC/AV1/VP9视频

    • 仅支持扩展模式:Extended VideoTagHeader + FourCC + 编解码器配置

扩展头格式

对于支持Enhanced RTMP的编解码器,使用扩展头格式:

复制代码
Extended VideoTagHeader:
    Byte 0: 0x80 | PacketType | FrameType
    Byte 1-4: FourCC ("avc1", "hvc1", "av01", "vp09")
    Byte 5+: 编解码器特定数据

5.3 数据包写入阶段 (flv_write_packet)

flv_write_packet是编码器的核心函数,负责将AVPacket转换为FLV Tag并写入文件。

5.3.1 函数执行流程图
复制代码
flv_write_packet(pkt)
    │
    ├─→ 预处理阶段
    │   ├─ 获取流参数 (par)
    │   ├─ 计算flags_size(Tag Data头部大小)
    │   ├─ 检查extradata更新
    │   └─ 写入元数据包(如果需要)
    │
    ├─→ 时间戳处理
    │   ├─ 初始化delay(第一个包)
    │   ├─ 验证DTS顺序
    │   └─ 计算FLV时间戳
    │
    ├─→ 写入Tag Header
    │   ├─ TagType (Audio/Video/Meta)
    │   ├─ DataSize (3 bytes)
    │   ├─ Timestamp (4 bytes)
    │   └─ StreamID (3 bytes, 通常为0)
    │
    ├─→ 写入Tag Data Header
    │   │
    │   ├─→ 视频Tag
    │   │   ├─ 扩展视频头(HEVC/AV1/VP9/H264多轨道)
    │   │   │   ├─ Extended Header标志
    │   │   │   ├─ PacketType
    │   │   │   ├─ FrameType
    │   │   │   ├─ FourCC
    │   │   │   └─ TrackIdx(多轨道)
    │   │   │   └─ CompositionTime(H264/HEVC,如果PTS!=DTS)
    │   │   │
    │   │   └─ 标准视频头(其他编解码器)
    │   │       ├─ CodecID | FrameType
    │   │       └─ PacketType(H264/MPEG4)
    │   │
    │   ├─→ 音频Tag
    │   │   ├─ 扩展音频头(OPUS/FLAC/AC3/EAC3/多轨道AAC)
    │   │   │   ├─ Extended Header标志
    │   │   │   ├─ PacketType
    │   │   │   ├─ FourCC
    │   │   │   └─ TrackIdx(多轨道)
    │   │   │
    │   │   └─ 标准音频头(其他编解码器)
    │   │       └─ AudioFlags (CodecID|SampleRate|SampleSize|Channels)
    │   │
    │   └─→ 脚本Tag(数据流)
    │       └─ AMF格式数据
    │
    ├─→ 数据转换
    │   ├─ H264/MPEG4: Annex-B -> MP4格式转换
    │   ├─ HEVC: Annex-B -> MP4格式转换
    │   └─ 其他: 直接写入
    │
    ├─→ 写入Tag Data
    │   └─ avio_write(pb, data, size)
    │
    ├─→ 写入PreviousTagSize
    │   └─ avio_wb32(pb, size + flags_size + 11)
    │
    └─→ 更新统计信息
        ├─ 更新duration
        ├─ 更新关键帧索引(如果启用)
        └─ 更新视频/音频大小统计
5.3.2 时间戳处理详解

FLV编码器需要处理复杂的时间戳转换:

DTS延迟计算

c 复制代码
if (flv->delay == AV_NOPTS_VALUE)
    flv->delay = -pkt->dts;

第一个包的DTS值被记录为延迟值,后续所有时间戳都会加上这个延迟,确保FLV文件的时间戳从0开始。

FLV时间戳计算

c 复制代码
ts = pkt->dts;  // 使用DTS作为FLV时间戳

FLV使用DTS作为Tag的时间戳,因为FLV是流式格式,需要按照解码顺序排列。

时间戳验证

c 复制代码
if (pkt->dts < -flv->delay) {
    av_log(s, AV_LOG_WARNING,
           "Packets are not in the proper order with respect to DTS\n");
    return AVERROR(EINVAL);
}

确保所有包的DTS都大于等于第一个包的DTS(即大于等于-flv->delay)。

5.3.3 视频Tag写入详解

标准视频Tag格式(H.264单轨道):

复制代码
Tag Header (11 bytes)
    TagType: 0x09
    DataSize: N + 5
    Timestamp: DTS
    StreamID: 0

Tag Data:
    Byte 0: CodecID(4 bits) | FrameType(4 bits)
            CodecID = 7 (H.264)
            FrameType = 1 (KeyFrame) 或 2 (InterFrame)
    Byte 1: PacketType
            0 = AVC Sequence Header
            1 = AVC NALU
            2 = AVC End of Sequence
    Byte 2-4: CompositionTime (PTS - DTS,仅NALU包)
    Byte 5+: 视频数据

扩展视频Tag格式(HEVC/AV1/VP9):

复制代码
Tag Header (11 bytes)
    TagType: 0x09
    DataSize: N + 5+
    Timestamp: DTS
    StreamID: 0

Tag Data:
    Byte 0: 0x80 | PacketType | FrameType
            0x80 = Extended Header标志
            PacketType = 0 (SequenceStart) 或 1 (CodedFrames)
            FrameType = 1 (KeyFrame) 或 2 (InterFrame)
    Byte 1-4: FourCC
            "hvc1" (HEVC)
            "av01" (AV1)
            "vp09" (VP9)
    Byte 5+: 视频数据(或CompositionTime + 视频数据)

多轨道视频Tag

对于多轨道视频,在扩展头后添加TrackIdx:

复制代码
Tag Data:
    Byte 0: 0x80 | 0x06 | PacketType | FrameType
            0x06 = Multitrack标志
    Byte 1: 0x00 | PacketType
            0x00 = MultitrackTypeOneTrack
    Byte 2-5: FourCC
    Byte 6: TrackIdx (轨道索引)
    Byte 7+: 视频数据
5.3.4 音频Tag写入详解

标准音频Tag格式(AAC单轨道):

复制代码
Tag Header (11 bytes)
    TagType: 0x08
    DataSize: N + 2
    Timestamp: DTS
    StreamID: 0

Tag Data:
    Byte 0: AudioFlags
            Bit 0: SoundType (0=Mono, 1=Stereo)
            Bit 1: SoundSize (0=8bit, 1=16bit)
            Bit 2-3: SoundRate (0=5.5kHz, 1=11kHz, 2=22kHz, 3=44kHz)
            Bit 4-7: SoundFormat (10=AAC)
    Byte 1: AACPacketType
            0 = AAC Sequence Header
            1 = AAC Raw
    Byte 2+: 音频数据

扩展音频Tag格式(OPUS/FLAC/AC3/EAC3):

复制代码
Tag Header (11 bytes)
    TagType: 0x08
    DataSize: N + 5+
    Timestamp: DTS
    StreamID: 0

Tag Data:
    Byte 0: 0x90 | PacketType
            0x90 = Extended Header标志 (CodecID=9)
            PacketType = 0 (SequenceStart) 或 1 (CodedFrames)
    Byte 1-4: FourCC
            "Opus" (OPUS)
            "fLaC" (FLAC)
            "ac-3" (AC3)
            "ec-3" (EAC3)
    Byte 5+: 音频数据
5.3.5 数据格式转换

某些编解码器需要格式转换:

H.264 Annex-B -> MP4格式

H.264数据在FFmpeg内部通常使用Annex-B格式(起始码:0x00000001),但FLV需要MP4格式(长度前缀)。

c 复制代码
if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
        if ((ret = ff_nal_parse_units_buf(pkt->data, &data, &size)) < 0)
            return ret;
}

ff_nal_parse_units_buf函数将Annex-B格式转换为MP4格式:

  • 查找起始码(0x00000001或0x000001)
  • 提取NALU
  • 将每个NALU的长度(4字节大端)写入,后跟NALU数据

HEVC Annex-B -> MP4格式

类似H.264,但使用专门的转换函数:

c 复制代码
if (par->codec_id == AV_CODEC_ID_HEVC) {
    if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
        if ((ret = ff_hevc_annexb2mp4_buf(pkt->data, &data, &size, 0, NULL)) < 0)
            return ret;
}

5.4 尾部写入阶段 (flv_write_trailer)

flv_write_trailer函数在编码结束时被调用,负责更新元数据、写入关键帧索引和EOS Tag。

5.4.1 函数执行流程
复制代码
flv_write_trailer()
    │
    ├─→ 更新关键帧索引元数据(如果启用)
    │   ├─ 更新videosize
    │   ├─ 更新audiosize
    │   ├─ 更新lasttimestamp
    │   ├─ 更新lastkeyframetimestamp
    │   ├─ 更新lastkeyframelocation
    │   ├─ shift_data() (移动数据,为索引腾出空间)
    │   └─ 写入关键帧索引
    │       ├─ "filepositions"数组
    │       └─ "times"数组
    │
    ├─→ 写入EOS Tag(如果未禁用)
    │   └─ 遍历视频流
    │       └─ put_eos_tag() (H.264/MPEG4)
    │
    ├─→ 更新duration和filesize
    │   ├─ 回写duration
    │   └─ 回写filesize
    │
    └─→ 完成
5.4.2 关键帧索引构建

关键帧索引是FLV文件的一个重要特性,它允许播放器快速定位和跳转到任意时间点。

索引结构

在onMetaData中添加keyframes对象:

复制代码
"keyframes": {
    "filepositions": [pos1, pos2, pos3, ...],
    "times": [ts1, ts2, ts3, ...]
}
  • filepositions: 每个关键帧在文件中的字节偏移
  • times: 每个关键帧的时间戳(秒)

索引构建过程

  1. 收集阶段(在flv_write_packet中):
c 复制代码
if (pkt->flags & AV_PKT_FLAG_KEY) {
    flv->lastkeyframetimestamp = flv->lasttimestamp;
    flv->lastkeyframelocation = cur_offset;
    ret = flv_append_keyframe_info(s, flv, flv->lasttimestamp, cur_offset);
}

每个关键帧都会被添加到链表中。

  1. 写入阶段(在flv_write_trailer中):

由于索引大小未知,需要先移动文件数据:

c 复制代码
res = shift_data(s);  // 移动数据,为索引腾出空间

shift_data函数计算索引大小,然后调用ff_format_shift_data移动文件数据。

  1. 写入索引数据
c 复制代码
put_amf_string(pb, "filepositions");
put_amf_dword_array(pb, flv->filepositions_count);
for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; ...) {
    put_amf_double(pb, newflv_posinfo->keyframe_position + flv->keyframe_index_size);
}

put_amf_string(pb, "times");
put_amf_dword_array(pb, flv->filepositions_count);
for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; ...) {
    put_amf_double(pb, newflv_posinfo->keyframe_timestamp);
}

注意:filepositions需要加上keyframe_index_size,因为索引是在数据之后插入的,导致所有数据位置后移。

5.4.3 EOS Tag写入

EOS(End of Sequence)Tag标记流的结束,主要用于H.264和MPEG4:

c 复制代码
static void put_eos_tag(AVIOContext *pb, unsigned ts, enum AVCodecID codec_id)
{
    uint32_t tag = ff_codec_get_tag(flv_video_codec_ids, codec_id);
    tag |= 1 << 4;  // FrameType = KeyFrame
    avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    avio_wb24(pb, 5);  // DataSize = 5
    put_timestamp(pb, ts);
    avio_wb24(pb, 0);  // StreamID = 0
    avio_w8(pb, tag);
    avio_w8(pb, 2);  // PacketType = AVC End of Sequence
    avio_wb24(pb, 0);  // Always 0
    avio_wb32(pb, 16);  // PreviousTagSize = 5 + 11
}

EOS Tag的结构:

  • TagType: 0x09 (Video)
  • DataSize: 5
  • Tag Data: CodecID|FrameType + PacketType(2) + Reserved(3 bytes)

关键函数深度剖析

6.1 get_audio_flags函数

get_audio_flags函数根据音频流参数生成FLV音频Tag的Flags字节。

6.1.1 函数逻辑
复制代码
get_audio_flags(par)
    │
    ├─→ 特殊编解码器处理
    │   ├─ AAC: 强制返回固定值
    │   │   └─ FLV_CODECID_AAC | FLV_SAMPLERATE_44100HZ | 
    │   │       FLV_SAMPLESSIZE_16BIT | FLV_STEREO
    │   │
    │   ├─ OPUS/FLAC/AC3/EAC3: 返回EX_HEADER标志
    │   │   └─ FLV_CODECID_EX_HEADER
    │   │
    │   └─ Speex: 验证参数并返回
    │       ├─ 采样率必须16kHz
    │       ├─ 声道必须单声道
    │       └─ FLV_CODECID_SPEEX | FLV_SAMPLERATE_11025HZ | 
    │           FLV_SAMPLESSIZE_16BIT
    │
    ├─→ 采样率处理
    │   ├─ 48000Hz: MP3特殊处理(使用44.1kHz标识)
    │   ├─ 44100Hz: FLV_SAMPLERATE_44100HZ
    │   ├─ 22050Hz: FLV_SAMPLERATE_22050HZ
    │   ├─ 11025Hz: FLV_SAMPLERATE_11025HZ
    │   ├─ 16000/8000/5512Hz: FLV_SAMPLERATE_SPECIAL(非MP3)
    │   └─ 其他: 错误
    │
    ├─→ 声道处理
    │   └─ 多声道: flags |= FLV_STEREO
    │
    └─→ 编解码器ID处理
        ├─ MP3: FLV_CODECID_MP3 | FLV_SAMPLESSIZE_16BIT
        ├─ PCM_U8: FLV_CODECID_PCM | FLV_SAMPLESSIZE_8BIT
        ├─ PCM_S16BE: FLV_CODECID_PCM | FLV_SAMPLESSIZE_16BIT
        ├─ PCM_S16LE: FLV_CODECID_PCM_LE | FLV_SAMPLESSIZE_16BIT
        ├─ ADPCM_SWF: FLV_CODECID_ADPCM | FLV_SAMPLESSIZE_16BIT
        ├─ NELLYMOSER: 根据采样率选择
        ├─ PCM_MULAW: FLV_CODECID_PCM_MULAW | FLV_SAMPLESSIZE_16BIT
        ├─ PCM_ALAW: FLV_CODECID_PCM_ALAW | FLV_SAMPLESSIZE_16BIT
        └─ 其他: 错误
6.1.2 Flags字节结构

FLV音频Flags字节的位布局:

复制代码
Bit 7  6  5  4  3  2  1  0
┌────┬────┬───┬──────────┐
│CodecID│SR│SS│ST│
└────┴────┴───┴──────────┘

CodecID (4 bits, Bit 4-7):
    0 = PCM
    1 = ADPCM
    2 = MP3
    3 = PCM LE
    4 = Nellymoser 16kHz
    5 = Nellymoser 8kHz
    6 = Nellymoser
    7 = PCM A-law
    8 = PCM μ-law
    9 = Reserved (用于扩展头)
    10 = AAC
    11 = Speex

SR - SampleRate (2 bits, Bit 2-3):
    0 = 5.5kHz (特殊)
    1 = 11kHz
    2 = 22kHz
    3 = 44.1kHz

SS - SampleSize (1 bit, Bit 1):
    0 = 8-bit
    1 = 16-bit

ST - SoundType (1 bit, Bit 0):
    0 = Mono
    1 = Stereo

6.2 write_metadata函数

write_metadata函数生成onMetaData脚本Tag,这是FLV文件最重要的元数据。

6.2.1 AMF格式详解

AMF(Action Message Format)是Adobe开发的数据序列化格式,用于Flash和RTMP协议。

AMF数据类型

  • AMF_DATA_TYPE_NUMBER (0x00): 64位双精度浮点数
  • AMF_DATA_TYPE_BOOL (0x01): 布尔值(1字节)
  • AMF_DATA_TYPE_STRING (0x02): UTF-8字符串
    • 2字节长度(大端)
    • 字符串数据
  • AMF_DATA_TYPE_OBJECT (0x03): 对象
    • 键值对列表
    • 以空字符串+0x09结束
  • AMF_DATA_TYPE_NULL (0x05): null值
  • AMF_DATA_TYPE_MIXEDARRAY (0x08): 混合数组
    • 4字节数组大小(大端)
    • 键值对列表
    • 以空字符串+0x09结束
6.2.2 元数据写入流程
复制代码
write_metadata()
    │
    ├─→ 写入Tag Header
    │   ├─ TagType: 0x12
    │   ├─ DataSize: 0 (占位,稍后填充)
    │   ├─ Timestamp: ts
    │   └─ StreamID: 0
    │
    ├─→ 写入事件名
    │   └─ AMF_STRING: "onMetaData"
    │
    ├─→ 写入混合数组
    │   ├─ AMF_MIXEDARRAY
    │   ├─ ArraySize: metadata_count (占位,稍后填充)
    │   │
    │   ├─→ 基础元数据
    │   │   ├─ "duration" -> double (如果启用)
    │   │   ├─ "width" -> double (视频)
    │   │   ├─ "height" -> double (视频)
    │   │   ├─ "videodatarate" -> double (视频)
    │   │   ├─ "framerate" -> double (视频,如果>0)
    │   │   ├─ "videocodecid" -> double (视频)
    │   │   ├─ "audiodatarate" -> double (音频)
    │   │   ├─ "audiosamplerate" -> double (音频)
    │   │   ├─ "audiosamplesize" -> double (音频)
    │   │   ├─ "stereo" -> bool (音频)
    │   │   ├─ "audiocodecid" -> double (音频)
    │   │   ├─ "datastream" -> double (数据流)
    │   │   └─ "filesize" -> double (如果启用)
    │   │
    │   ├─→ 用户元数据
    │   │   └─ 遍历s->metadata字典
    │   │       └─ 过滤保留关键字
    │   │           └─ "key" -> AMF_STRING: "value"
    │   │
    │   └─→ 关键帧索引元数据(如果启用)
    │       ├─ "hasVideo" -> bool
    │       ├─ "hasKeyframes" -> bool
    │       ├─ "hasAudio" -> bool
    │       ├─ "hasMetadata" -> bool
    │       ├─ "canSeekToEnd" -> bool
    │       ├─ "datasize" -> double (占位)
    │       ├─ "videosize" -> double (占位)
    │       ├─ "audiosize" -> double (占位)
    │       ├─ "lasttimestamp" -> double (占位)
    │       ├─ "lastkeyframetimestamp" -> double (占位)
    │       ├─ "lastkeyframelocation" -> double (占位)
    │       └─ "keyframes" -> AMF_OBJECT (占位,稍后填充)
    │
    ├─→ 结束对象
    │   └─ "" + AMF_END_OF_OBJECT
    │
    ├─→ 回写大小
    │   ├─ 计算metadata_totalsize
    │   ├─ 回写ArraySize
    │   ├─ 回写DataSize
    │   └─ 写入PreviousTagSize
    │
    └─→ 完成
6.2.3 延迟写入实现

由于某些字段的值在编码完成前未知,需要延迟写入:

c 复制代码
// 1. 写入时记录偏移
flv->duration_offset = avio_tell(pb);
put_amf_double(pb, s->duration / AV_TIME_BASE);  // 占位值

// 2. 在trailer中回写
avio_seek(pb, flv->duration_offset, SEEK_SET);
put_amf_double(pb, flv->duration / (double)1000);

6.3 flv_write_codec_header函数

flv_write_codec_header函数为每个流写入编解码器特定的序列头。

6.3.1 支持的编解码器

音频编解码器

  • AAC: Sequence Header包含AAC配置信息
  • OPUS: Sequence Header包含OPUS配置信息
  • FLAC: Sequence Header包含FLAC配置信息
  • AC3/EAC3: Sequence Header包含AC3配置信息
  • MP3: 不需要Sequence Header(多轨道除外)

视频编解码器

  • H.264: Sequence Header包含AVC配置(SPS/PPS)
  • HEVC: Sequence Header包含HEVC配置(VPS/SPS/PPS)
  • AV1: Sequence Header包含AV1配置
  • VP9: Sequence Header包含VP9配置
  • MPEG4: Sequence Header包含MPEG4配置
6.3.2 AAC Sequence Header生成

如果AAC流没有extradata,且启用了FLV_AAC_SEQ_HEADER_DETECT标志,编码器会自动生成:

c 复制代码
static void flv_write_aac_header(AVFormatContext* s, AVCodecParameters* par)
{
    if (!par->extradata_size && (flv->flags & FLV_AAC_SEQ_HEADER_DETECT)) {
        PutBitContext pbc;
        int samplerate_index;
        int channels = par->ch_layout.nb_channels
                - (par->ch_layout.nb_channels == 8 ? 1 : 0);
        uint8_t data[2];

        // 查找采样率索引
        for (samplerate_index = 0; samplerate_index < 16; samplerate_index++)
            if (par->sample_rate == ff_mpeg4audio_sample_rates[samplerate_index])
                break;

        // 生成AAC配置
        init_put_bits(&pbc, data, sizeof(data));
        put_bits(&pbc, 5, par->profile + 1);  // profile (5 bits)
        put_bits(&pbc, 4, samplerate_index);  // sample rate index (4 bits)
        put_bits(&pbc, 4, channels);           // channels (4 bits)
        put_bits(&pbc, 1, 0);                 // frame length - 1024 samples
        put_bits(&pbc, 1, 0);                 // does not depend on core coder
        put_bits(&pbc, 1, 0);                 // is not extension
        flush_put_bits(&pbc);

        avio_w8(pb, data[0]);
        avio_w8(pb, data[1]);
    }
    avio_write(pb, par->extradata, par->extradata_size);
}

AAC配置字节结构:

复制代码
Byte 0: Bit 7-3: AudioObjectType (profile + 1)
        Bit 2-0: (高3位) SampleRateIndex
Byte 1: Bit 7-4: (低4位) SampleRateIndex
        Bit 3-0: ChannelConfiguration

6.4 flv_write_metadata_packet函数

flv_write_metadata_packet函数为HEVC/AV1/VP9视频流写入颜色信息元数据。

6.4.1 Enhanced RTMP颜色信息

Enhanced RTMP规范定义了颜色信息的传输格式,用于HDR视频:

复制代码
VideoTag (Extended Header)
    TagType: 0x09
    DataSize: N
    Timestamp: DTS
    
    Tag Data:
        Byte 0: 0x80 | PacketTypeMetadata | FLV_FRAME_VIDEO_INFO_CMD
        Byte 1-4: FourCC
        Byte 5+: AMF Object "colorInfo"
            "colorConfig": {
                "transferCharacteristics": number (color_trc)
                "matrixCoefficients": number (color_space)
                "colorPrimaries": number (color_primaries)
            }
            "hdrCll": {  // 如果存在
                "maxFall": number
                "maxCLL": number
            }
            "hdrMdcv": {  // 如果存在
                "redX", "redY": number
                "greenX", "greenY": number
                "blueX", "blueY": number
                "whitePointX", "whitePointY": number
                "maxLuminance": number
                "minLuminance": number
            }
6.4.2 函数实现
c 复制代码
static void flv_write_metadata_packet(AVFormatContext *s, AVCodecParameters *par, 
                                      unsigned int ts, int stream_idx)
{
    // 只处理HEVC/AV1/VP9
    if (par->codec_id == AV_CODEC_ID_HEVC || 
        par->codec_id == AV_CODEC_ID_AV1 ||
        par->codec_id == AV_CODEC_ID_VP9) {
        
        // 获取HDR元数据
        AVContentLightMetadata *lightMetadata = NULL;
        AVMasteringDisplayMetadata *displayMetadata = NULL;
        
        // 写入VideoTag Header
        avio_w8(pb, FLV_TAG_TYPE_VIDEO);
        metadata_size_pos = avio_tell(pb);
        avio_wb24(pb, 0 + flags_size);
        put_timestamp(pb, ts);
        avio_wb24(pb, flv->reserved);
        
        // 写入Extended Header
        avio_w8(pb, FLV_IS_EX_HEADER | PacketTypeMetadata | FLV_FRAME_VIDEO_INFO_CMD);
        write_codec_fourcc(pb, par->codec_id);
        
        // 写入colorInfo AMF对象
        avio_w8(pb, AMF_DATA_TYPE_STRING);
        put_amf_string(pb, "colorInfo");
        avio_w8(pb, AMF_DATA_TYPE_OBJECT);
        
        // 写入colorConfig
        put_amf_string(pb, "colorConfig");
        avio_w8(pb, AMF_DATA_TYPE_OBJECT);
        
        if (par->color_trc != AVCOL_TRC_UNSPECIFIED)
            // 写入transferCharacteristics
        if (par->color_space != AVCOL_SPC_UNSPECIFIED)
            // 写入matrixCoefficients
        if (par->color_primaries != AVCOL_PRI_UNSPECIFIED)
            // 写入colorPrimaries
        
        // 写入HDR元数据(如果存在)
        // ...
        
        // 回写大小
        total_size = avio_tell(pb) - metadata_size_pos - 10;
        avio_seek(pb, metadata_size_pos, SEEK_SET);
        avio_wb24(pb, total_size);
        avio_skip(pb, total_size + 10 - 3);
        avio_wb32(pb, total_size + 11);
    }
}

元数据处理机制

7.1 元数据分类

FLV编码器处理的元数据分为三类:

  1. 基础元数据:视频/音频的基本参数(宽高、码率、采样率等)
  2. 用户元数据:通过AVFormatContext->metadata传入的自定义元数据
  3. 统计元数据:编码过程中统计的信息(文件大小、时长、关键帧信息等)

7.2 元数据过滤

编码器会过滤掉一些保留关键字,避免与系统元数据冲突:

c 复制代码
if(   !strcmp(tag->key, "width")
    ||!strcmp(tag->key, "height")
    ||!strcmp(tag->key, "videodatarate")
    // ... 其他保留关键字
){
    av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
    continue;
}

7.3 元数据更新

如果设置了AVSTREAM_EVENT_FLAG_METADATA_UPDATED标志,编码器会在写入数据包时重新写入元数据:

c 复制代码
if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
    write_metadata(s, ts);
    s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
}

关键帧索引系统

8.1 索引的作用

关键帧索引允许播放器:

  1. 快速定位到任意时间点(seek操作)
  2. 计算播放进度
  3. 实现快进/快退功能

8.2 索引数据结构

索引以链表形式存储:

c 复制代码
typedef struct FLVFileposition {
    int64_t keyframe_position;    // 文件位置(字节偏移)
    double keyframe_timestamp;    // 时间戳(秒)
    struct FLVFileposition *next; // 下一个节点
} FLVFileposition;

8.3 索引构建流程

复制代码
编码过程中(flv_write_packet)
    │
    └─→ 检测到关键帧
        ├─ 记录当前位置: cur_offset
        ├─ 记录时间戳: pkt->dts / 1000.0
        └─ flv_append_keyframe_info()
            ├─ 分配FLVFileposition节点
            ├─ 设置keyframe_position和keyframe_timestamp
            └─ 添加到链表尾部

编码结束时(flv_write_trailer)
    │
    ├─→ 计算索引大小
    │   └─ metadata_size = filepositions_count * 9 * 2 + ...
    │
    ├─→ 移动文件数据
    │   └─ shift_data()
    │       ├─ 计算需要移动的数据大小
    │       └─ ff_format_shift_data()
    │           └─ 将keyframes_info_offset之后的数据后移
    │
    ├─→ 更新元数据中的偏移引用
    │   └─ 所有filepositions += keyframe_index_size
    │
    └─→ 写入索引数据
        ├─ "filepositions"数组
        └─ "times"数组

8.4 shift_data函数详解

shift_data函数负责在文件中插入关键帧索引数据:

c 复制代码
static int shift_data(AVFormatContext *s)
{
    int64_t metadata_size = 0;
    FLVContext *flv = s->priv_data;

    // 计算索引大小
    metadata_size = flv->filepositions_count * 9 * 2 + 10; // filepositions和times值
    metadata_size += 2 + 13;  // "filepositions"字符串
    metadata_size += 2 + 5;   // "times"字符串
    metadata_size += 3;        // 对象结束

    flv->keyframe_index_size = metadata_size;

    // 移动数据
    ret = ff_format_shift_data(s, flv->keyframes_info_offset, metadata_size);
    if (ret < 0)
        return ret;

    // 更新元数据Tag的大小
    avio_seek(s->pb, flv->metadata_size_pos, SEEK_SET);
    avio_wb24(s->pb, flv->metadata_totalsize + metadata_size);

    // 更新PreviousTagSize
    avio_seek(s->pb, flv->metadata_totalsize_pos + metadata_size, SEEK_SET);
    avio_wb32(s->pb, flv->metadata_totalsize + 11 + metadata_size);

    return 0;
}

大小计算说明

  • 每个关键帧位置:8字节(double)+ 1字节(AMF类型)= 9字节
  • filepositions数组:filepositions_count * 9字节
  • times数组:filepositions_count * 9字节
  • 字符串和对象开销:约20字节

多轨道支持

9.1 多轨道概念

FLV格式支持在一个文件中包含多个视频轨道或多个音频轨道,这通过Enhanced RTMP扩展实现。

9.2 轨道索引映射

track_idx_map数组将流索引映射到轨道索引:

c 复制代码
// 在flv_init中
for (i = 0; i < s->nb_streams; i++) {
    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        flv->track_idx_map[i] = video_ctr++;
        break;
    case AVMEDIA_TYPE_AUDIO:
        flv->track_idx_map[i] = audio_ctr++;
        break;
    }
}
  • 第一个视频流:track_idx = 0
  • 第二个视频流:track_idx = 1
  • 第一个音频流:track_idx = 0
  • 第二个音频流:track_idx = 1

9.3 多轨道Tag格式

多轨道视频Tag

复制代码
Tag Data:
    Byte 0: 0x80 | 0x06 | PacketType | FrameType
            0x06 = Multitrack标志
    Byte 1: 0x00 | PacketType
            0x00 = MultitrackTypeOneTrack
    Byte 2-5: FourCC
    Byte 6: TrackIdx
    Byte 7+: 视频数据

多轨道音频Tag

复制代码
Tag Data:
    Byte 0: 0x90 | 0x05 | PacketType
            0x90 = Extended Header
            0x05 = AudioPacketTypeMultitrack
    Byte 1: 0x00 | PacketType
            0x00 = MultitrackTypeOneTrack
    Byte 2-5: FourCC
    Byte 6: TrackIdx
    Byte 7+: 音频数据

9.4 多轨道编解码器限制

视频多轨道:仅支持VP8/VP9/AV1/H.264/HEVC

音频多轨道:仅支持AAC/MP3/OPUS/FLAC/AC3/EAC3

9.5 多声道音频支持

对于多声道音频(超过立体声),需要写入额外的多声道配置Tag:

c 复制代码
if (par->codec_type == AVMEDIA_TYPE_AUDIO && 
    (extended_flv || 
     (av_channel_layout_compare(&par->ch_layout, &AV_CHANNEL_LAYOUT_STEREO) == 1 &&
      av_channel_layout_compare(&par->ch_layout, &AV_CHANNEL_LAYOUT_MONO) == 1)))
    flv_write_multichannel_header(s, par, ts, stream_index);

flv_write_multichannel_header函数写入多声道配置:

复制代码
AudioTag (MultichannelConfig)
    TagType: 0x08
    DataSize: N + 5+
    
    Tag Data:
        Byte 0: 0x90 | AudioPacketTypeMultichannelConfig
        Byte 1-4: FourCC
        Byte 5: TrackIdx (如果多轨道)
        Byte 6: ChannelOrder
        Byte 7: ChannelCount
        Byte 8+: ChannelMap (如果ChannelOrder == Native或Custom)

性能优化与最佳实践

10.1 内存管理

FLV编码器在初始化时分配固定大小的数组,避免运行时动态分配:

c 复制代码
flv->last_ts = av_calloc(s->nb_streams, sizeof(*flv->last_ts));
flv->metadata_pkt_written = av_calloc(s->nb_streams, sizeof(*flv->metadata_pkt_written));
flv->track_idx_map = av_calloc(s->nb_streams, sizeof(*flv->track_idx_map));

关键帧索引使用链表结构,只在需要时分配节点。

10.2 文件I/O优化

  1. 批量写入 :使用avio_write批量写入数据,减少系统调用
  2. 延迟写入:元数据字段使用延迟写入,避免多次seek
  3. 缓冲区管理:AVIOContext内部有缓冲区,减少实际I/O次数

10.3 数据格式转换优化

对于H.264/HEVC的Annex-B到MP4格式转换:

  1. 检查extradata格式:如果extradata已经是MP4格式(第一个字节为1),则不需要转换
  2. 原地转换:如果可能,尽量在原地修改数据,避免额外内存分配
  3. 批量处理:一次性处理整个packet,而不是逐个NALU处理

10.4 关键帧索引优化

  1. 延迟构建:索引在trailer阶段一次性构建,避免频繁的文件操作
  2. 大小预计算:提前计算索引大小,一次性分配空间
  3. 链表vs数组:使用链表存储索引,避免频繁的内存重分配

10.5 最佳实践建议

  1. 启用关键帧索引 :对于需要seek功能的场景,启用add_keyframe_index标志
  2. 合理设置关键帧间隔:关键帧过多会增加文件大小,过少会影响seek性能
  3. 使用扩展格式:对于新编解码器(HEVC/AV1/VP9),使用扩展格式以获得更好的兼容性
  4. 元数据精简:避免写入过多不必要的元数据,减少文件大小
  5. 时间戳一致性:确保输入流的时间戳是单调递增的

总结

本文深入解析了FFmpeg中FLV编码器的实现原理,从文件格式到代码实现,从初始化到数据写入,涵盖了编码器的方方面面。

11.1 核心要点回顾

  1. FLV格式:Tag-based流式结构,支持音频、视频和脚本三种Tag类型
  2. 编码流程:初始化 → 写入头部 → 写入数据包 → 写入尾部
  3. 元数据处理:使用AMF格式,支持延迟写入和动态更新
  4. 关键帧索引:通过链表收集,在trailer阶段一次性写入
  5. 多轨道支持:通过Enhanced RTMP扩展实现,支持多视频/音频轨道

11.2 技术亮点

  1. 延迟写入机制:对于未知值的字段(duration、filesize),采用占位+回写的方式
  2. 数据格式转换:自动处理Annex-B到MP4格式的转换
  3. 扩展格式支持:支持Enhanced RTMP,兼容新编解码器
  4. 内存效率:使用链表和固定数组,平衡内存使用和性能

11.3 应用场景

FLV编码器广泛应用于:

  • 视频直播推流(RTMP协议)
  • 视频点播文件生成
  • 视频转码和格式转换
  • 流媒体服务器

11.4 未来展望

随着视频编码技术的发展,FLV格式也在不断演进:

  • Enhanced RTMP规范持续更新
  • 新编解码器支持(AV1、VP9等)
  • HDR和色彩空间信息的完善
  • 多轨道和多声道音频的增强

理解FFmpeg FLV编码器的实现原理,不仅有助于更好地使用FFmpeg工具,也为开发自定义的多媒体处理应用提供了宝贵的参考。


参考文献

  1. Adobe Systems Incorporated. (2009). "Adobe Flash Video File Format Specification Version 10.1"
  2. Enhanced RTMP Specification. https://github.com/veovera/enhanced-rtmp
  3. FFmpeg Documentation. https://ffmpeg.org/documentation.html
  4. FFmpeg Source Code. https://github.com/FFmpeg/FFmpeg

相关推荐
HellowAmy4 小时前
我的C++规范 - 玩一个小游戏
开发语言·c++·代码规范
自学不成才4 小时前
深度复盘:一次flutter应用基于内存取证的黑盒加密破解实录并完善算法推理助手
c++·python·算法·数据挖掘
西***63475 小时前
打破部署桎梏!编码器两大核心架构(NVR/PoE)深度解析
服务器·音视频·视频编解码
myjie05275 小时前
使用ffmpeg访问FileProvider 提供出去的content uri 问题
ffmpeg
玖釉-6 小时前
[Vulkan 学习之路] 08 - 给图片穿马甲:图像视图 (Image Views)
c++·windows·图形渲染
m0_748250037 小时前
C++ 信号处理
c++·算法·信号处理
yuyanjingtao7 小时前
动态规划 背包 之 凑钱
c++·算法·青少年编程·动态规划·gesp·csp-j/s
5Gcamera7 小时前
边缘计算视频分析智能AI盒子使用说明
人工智能·音视频·边缘计算
线束线缆组件品替网7 小时前
IO Audio Technologies 音频线缆抗干扰与带宽设计要点
网络·人工智能·汽车·电脑·音视频·材料工程
scx201310048 小时前
20260112树状数组总结
数据结构·c++·算法·树状数组