鴥彼晚风
发布于 2026-07-01 / 2 阅读
0
0

PyAV 媒体流 (Streams) 核心速查手册

以下是基于 PyAV Streams 模块整理的媒体流 (Streams) 核心速查手册。该文档聚焦于 StreamContainer 的灵活检索机制与 Stream 对象的元数据/时序属性,是进行多轨处理、格式探测和同步计算的基础。


1. StreamContainer:流的检索与选择

container.streams 是一个类元组容器,提供三种检索范式。优先使用 Typed Collections,仅在动态场景下使用 get()

三种检索方式对比

方式

语法

适用场景

返回值

Typed Collections

streams.video[0]

✅ 已知类型,代码可读性优先

tuple[VideoStream]

Index Access

streams[0]

按绝对索引访问(不区分类型)

Stream

Dynamic get()

streams.get(audio=(0,1))

运行时动态选择、批量提取

list[Stream]

Typed Collections(推荐默认使用)

video_streams  = container.streams.video      # tuple[VideoStream]
audio_streams  = container.streams.audio      # tuple[AudioStream]
sub_streams    = container.streams.subtitles  # tuple[SubtitleStream]
data_streams   = container.streams.data       # tuple[DataStream]
other_streams  = container.streams.other      # tuple[Stream] (未知类型)

💡 空安全:Typed Collections 返回空元组而非 None,可直接 for s in container.streams.video 遍历而无需判空。

Dynamic get() 详解

# 按类型+索引组合选择
container.streams.get(video=0, audio=(0, 1))   # 第1路视频 + 前2路音频

# 按绝对索引选择(跨类型)
container.streams.get(0)                        # 第1个流(不论类型)
container.streams.get((0, 2))                   # 第1和第3个流

# 传入 dict 等价于关键字参数
container.streams.get({'video': 0, 'audio': [0, 1]})

# ⚠️ 无参数 = 返回所有流
all_streams = container.streams.get()

⚠️ 检索陷阱

陷阱

说明

解决方案

streams.video[0] IndexError

文件无视频轨

先检查 len(streams.video) > 0 或用 get(video=0) + 判空

get() 返回顺序不确定

多类型混合选择时顺序不保证

stream.index 排序后再使用

绝对索引 ≠ 类型索引

streams[2] 可能是音频,不是第3路视频

始终用 Typed Collections 或 get(video=N)

other 包含未识别类型

私有/非标准流归入 other

检查 stream.type 进一步分类


2. Stream 核心属性

基础标识

属性

类型

说明

type

str

'video' / 'audio' / 'subtitle' / 'data'

index

int

在 Container 中的绝对索引(从0开始)

id

int

格式特定的流 ID(如 MPEG-TS PID),≠ index

profile

str

编码配置档次(如 "High", "Main"

language

str | None

ISO 639 语言码(如 "eng", "zho"

codec_context

CodecContext

底层编解码器上下文,Stream 缺失的属性会透传至此

💡 属性透传机制stream.widthstream.pix_fmtstream.options 等实际来自 codec_context。PyAV 做了透明代理,无需手动 .codec_context.xxx

时序系统(关键)

属性

类型

说明

time_base

Fraction | None

时间戳单位(如 1/30, 1/90000

start_time

int | None

首帧 PTS(time_base 单位),可能非零

duration

int | None

流总时长(time_base 单位)

frames

int

总帧数,未知时返回 0

⚠️ 时序计算注意事项

# ✅ 安全的时长计算(秒)
if stream.duration is not None and stream.time_base is not None:
    duration_sec = float(stream.duration * stream.time_base)
else:
    # 降级方案:用 container.duration 或解码计数
    duration_sec = float(container.duration) / av.time_base if container.duration else 0

# ✅ 考虑 start_time 偏移的精确 PTS
actual_pts = frame.pts - (stream.start_time or 0)

陷阱

说明

解决方案

duration 为 None

直播流/部分容器不提供

container.duration 或实时解码计数

frames == 0

容器头未记录帧数

不代表无帧;需完整 demux 计数

start_time != 0

编辑点/延迟头导致

同步计算时必须减去此偏移

time_base 为 None

极少数异常容器

回退到 codec_context.time_baseav.time_base

跨流时间比较

各流 time_base 不同

av.rescale_q() 统一基准


3. 常见工作流示例

多轨音频选择(按语言)

target_audio = None
for s in container.streams.audio:
    if s.language == 'zho':
        target_audio = s
        break
if target_audio is None and container.streams.audio:
    target_audio = container.streams.audio[0]  # fallback to first

流信息摘要打印

for stream in container.streams:
    print(f"[{stream.index}] {stream.type} | "
          f"codec={stream.codec_context.name} | "
          f"profile={stream.profile} | "
          f"lang={stream.language} | "
          f"tb={stream.time_base} | "
          f"dur={stream.duration}")

安全获取主视频流

video = container.streams.video[0] if container.streams.video else None
if video is None:
    raise ValueError("No video stream found")
print(f"{video.codec_context.width}x{video.codec_context.height} "
      f"{video.codec_context.pix_fmt} @ {float(video.average_rate):.2f}fps")

4. Stream vs CodecContext 属性归属

属于 Stream

属于 CodecContext(透传)

说明

index, id, type

name, codec

标识 vs 编码参数

time_base, start_time, duration, frames

width, height, pix_fmt, sample_rate, channels

容器级时序 vs 编码级格式

language, profile

bit_rate, gop_size, options

元数据 vs 编码器配置

metadata (dict)

extradata

容器标签 vs 编码私有数据

💡 实践建议:日常使用无需区分,PyAV 透传机制让两者无缝衔接。仅在需要明确修改编码器参数时才显式访问 stream.codec_context


5. 开发排查备忘

症状

可能原因

诊断步骤

IndexError on streams.video[0]

无视频轨

检查 len(streams.video)streams.get(video=0)

时长计算为 0

duration 为 None 未处理

加 None 检查;用 container.duration 降级

音画不同步

忽略 start_time 偏移

打印各流 start_time;同步时减去偏移

选错音频轨

用绝对索引而非类型索引

改用 streams.audio[N]get(audio=N)

frames == 0 但能解码

容器头未记录

正常现象;不要以此判断流是否为空

属性 AttributeError

既不在 Stream 也不在 CodecContext

查 FFmpeg AVStream/AVCodecContext 文档确认字段名

语言匹配失败

language 为 None 或非标码

同时检查 stream.metadata.get('language')

💡 核心记忆点

  • 取流首选 Typed Collectionsstreams.video[audio/subtitles],安全且可读

  • get() 用于动态场景:运行时按类型+索引组合选择,无参返回全部

  • duration/frames/start_time 都可能为 None/0:防御性编程必不可少

  • 跨流时间运算必须先 rescale:各流 time_base 独立,不可直接加减

  • Stream 透明代理 CodecContext:日常无需区分,改编码参数时才显式访问


评论