🎬 第十二章 AVDevice设备读写理论及案例实战
📄 一句话概括

采音频与采视频是同一段代码,只换前缀 audio=。 但设备给什么格式由它自己定,所以采完第一件事是重采样。

📝 本节和前面几节的关系

12.2 采的是画面,这一节采声音。 第十章的重采样在这里第一次派上真实用场,因为设备不会迁就你。

一、音频设备怎么写

三个平台的写法,与视频那一张表几乎一样。 平台 格式名 设备名


Windows dshow audio=麦克风名 Linux alsa default 或 hw:0 macOS avfoundation :0

audio= 与 video= 是同一套前缀规则,只是接的设备类型不同。 所以「采音频」与「采摄像头」在代码上是同一段,只换前缀。 音频那半边还多一个设备格式:Linux 上视频走 v4l2、音频走 alsa,两条格式名不一样; Windows 上两者都在 dshow 里,靠前缀区分。这套不对称是历史原因,只能记。 macOS 的 :0 不要看漏那个冒号:那一串是「视频序号:音频序号」,0:0 是全都要, 只写 :0 就是「不要视频、只要第 0 个音频」。

二、本机没有话筒

按标准写法试一次。 项 值


设备名 audio=麦克风 结果 OSError

结果与摄像头那一条一模一样,都是 OSError。 因为 dshow 这一层先要拿到设备句柄,拿不到就报 I/O 错,它不区分「没插」与「被占用」。 这条「不区分」是排查里最费时间的地方:开不了先关掉所有可能占用麦克风的程序(会议、 语音输入、浏览器标签),再试一次。很多「没设备」的报障其实是占用。

三、采到之后马上重采样

设备给的格式是它自己定的:常见是 48000 立体声 s16,但也可能是 44100、浮点、甚至 8 声道。 而你的处理链要的是统一格式。 项 进 出


格式 fltp s16 布局 stereo mono 采样率 44100 48000 样本数 480 523

480 变成 523 是算得出来的:480 × 48000 ÷ 44100 = 522.4,进一位就是 523。 比例是 160 比 147,不是整数倍。 不是整数倍就是麻烦的来源:整数倍可以整数拷贝,非整数倍每个输出样本都要在输入里插值, 这就是重采样要留状态、不能一帧一帧独立算的原因。 还有一个坑:调 resample(frame) 拿到的样本会比理论少几个, 因为重采样器内部要攒一点尾巴; 必须再调一次 resample(None) 把尾巴冲出来,总数才对得上 523。 沙箱没有麦克风,但这一段能真跑:拿合成的帧走一遍重采样, 证明「采到之后要做的处理」这条路是通的。

四、音频采集特有的三个坑

视频那头的坑是尺寸与格式,音频这头是时间。 坑 表现


设备被占用 打不开,报 I/O 错,与没设备一样 漂移与抖动 每帧样本数不固定,不能按时长切 首次打开慢 要等设备初始化,别放实时线程

第二条最要命:音频设备的时序由硬件时钟决定,不是标准帧长。 所以「按 1024 个样本一帧」这种假设在采集侧不成立。 这条与第十章的结论正好接上:文件里的 AAC 一帧固定 1024 个样本, 那是编码器定的;采集侧没有编码器,就没有固定帧长。 后果是时间要自己算:靠 sample_rate 与累计样本数推 pts,不能靠「第几帧 × 每帧时长」。 这也是直播里音画不同步最常见的根子。

五、采完往后接什么

采集只是入口,后面三步才是这条链的目的。 下一步 为什么


重采样成统一格式 后面所有处理只认一种格式 按时间打 pts 设备不给你标准时间基 编码或推流 那才是链的出口

真实直播链是「采→转→编→推」四步。 这一章只做到第一步,后面几步在第十三章。 四步里最容易被忽略的是第二步:很多人把设备给的格式直接丢给编码器,结果换个麦克风就崩。 加一层重采样,等于把「设备千奇百怪」挡在外面。 这也是一条通用的架构习惯:入口处做归一化,后面的代码就只需要认一种格式,测试也好写。

六、动手 Demo:话筒采集,以及采到之后马上要做的事

先试 audio=麦克风 看报错,再用合成帧走一遍 44100 立体声浮点到 48000 单声道 s16。

import numpy as np
import av

# 1) 标准写法:向 dshow 要麦克风
try:
    av.open("audio=麦克风", format="dshow").close()
    print("开成功")
except Exception as e:
    print(type(e).__name__)          # OSError:路在、没设备

# 2) 采到之后必做:归一到统一格式
r = av.AudioResampler(format="s16", layout="mono", rate=48000)
a = np.zeros((2, 480), dtype=np.float32)     # 立体声、480 个样本
f = av.AudioFrame.from_ndarray(a, format="fltp", layout="stereo")
f.sample_rate = 44100

out = list(r.resample(f)) + list(r.resample(None))   # 别忘冲空包
print(out[0].format.name, out[0].layout.name, out[0].sample_rate)
print(sum(x.samples for x in out))           # 523

实跑输出:

--------------------------------------------------------------------
话筒采集,以及采到之后马上要做的事
--------------------------------------------------------------------

**① 音频设备的写法**

  平台        格式名           设备名
  ------------------------------------------------
  Windows     dshow            audio=麦克风名
  Linux       alsa             default 或 hw:0
  macOS       avfoundation     :0

**`audio=` 与 `video=` 是同一套前缀规则**,只是接的设备类型不同。
**所以「采音频」与「采摄像头」在代码上是同一段,只换前缀。**

**② 本机没有话筒设备**

  项         值
  ---------------------------
  设备名     audio=麦克风
  结果       OSError

**注意这个结果与摄像头那一条一样**,都是 OSError。
**因为 dshow 这一层先要拿到设备句柄,拿不到就报 I/O 错。**

**③ 采到之后马上要重采样**

**设备给的格式是它自己定的**,常见是 48000 立体声 s16,但也可能是 44100 或浮点。
**而你的处理链要的是统一的格式。**

  项         进         出
  -------------------------------
  格式       fltp       s16
  布局       stereo     mono
  采样率     44100      48000
  样本数     480        523

**没有麦克风,但这一段能真跑**:拿合成的帧走一遍重采样,
**证明「采到之后要做的处理」这条路是通的。**

**④ 音频采集特有的三个坑**

  坑             表现
  ------------------------------------------------------
  设备被占用     打不开,报 I/O 错,与「没设备」一样
  漂移与抖动     每帧样本数不固定,不能按时长切
  首次打开慢     要等设备初始化,别放在实时线程里

**第二条最要命**:音频设备的时序由硬件时钟决定,**不是标准帧长。**
**所以「按 1024 个样本一帧」这种假设在采集侧不成立。**

**⑤ 采完往后接什么**

  下一步               为什么
  -------------------------------------------------
  重采样成统一格式     后面所有处理只认一种格式
  按时间打 pts         设备不给你标准时间基
  编码或推流           那才是链的出口

**采集只是入口。** 真实直播链是「采→转→编→推」,**这一章只做到第一步**,后面几步在第十三章。

audio= 与 video= 同一套前缀,只是设备类型不同。 480 个 44100 的样本换成 48000 是 523 个(522.4 进一位),比例非整数。

30 秒实验

  1. 把 rate=48000 改成 rate=8000,算一下 480 个样本应该出多少个(提示:87.07)。
  2. 去掉 + list(r.resample(None)),看总数少几个(提示:少的是攒在内部的尾巴)。
  3. 把 layout="mono" 改成 "stereo",看输出样本数变不变(提示:不变,变的是通道)。

常见误区

  1. 把设备给的格式直接喂编码器。 换个麦克风就崩,中间要加一层归一的。
  2. 按固定帧长切音频。 采集侧的帧长由硬件时钟决定,不固定。
  3. 以为重采样一次调用就出全。 要再冲一次空包,尾巴才出来。
自测三问(点开对答案) 1. 音频设备与视频设备的写法差在哪? —— 只差 `audio=` 与 `video=` 这个前缀,其余同一段。 2. 44100 的 480 个样本换成 48000 是多少个? —— 523 个(480×48000÷44100=522.4,进一位)。 3. 为什么采完要先重采样? —— 设备给什么格式由它定,归一之后后面只认一种格式。

相关笔记