在浏览器端做音频录制时,采样率(Sample Rate)是一个绕不开的参数。无论是通过MediaRecorder采集音频流,还是用AudioContext对音频做进一步处理,都会接触到sampleRate这个字段。在TypeScript项目中,如果只是简单地把采样率声明为number,类型系统就无法帮你把关,诸如把Hz和kHz搞混、传入非法值这类问题就只能在运行时才暴露。本文围绕如何在TypeScript中为音频采样率定义严谨的类型展开,给出多种方案供不同场景选择。

一、采样率从哪里来:先弄清API中的sampleRate字段
在浏览器音频相关API中,sampleRate至少出现在三个地方。第一处是AudioContext实例,创建上下文时可以通过AudioContextOptions指定采样率,创建后可以通过context.sampleRate读取实际生效的值,单位是Hz,比如44100、48000。第二处是MediaStreamAudioSourceNode或AudioBuffer,每个AudioBuffer都有自己的sampleRate属性,表示缓冲区中每秒的采样点数。第三处是MediaStreamTrack的getSettings方法,通过track.getSettings().sampleRate可以拿到当前音轨的实际采样率配置。
需要注意一个常见误区:采样率的单位是Hz而不是kHz。Web Audio API规范中所有采样率一律以Hz表示,44100就是常说的44.1kHz。有些开发者习惯把44.1直接当采样率传进去,结果得到的音频速度完全不对。另一个要点是,浏览器并非支持任意采样率,规范要求采样率在8000到96000之间,且不同浏览器、不同设备支持的具体档位可能有差异,如果传入不支持的值,浏览器可能会回退到默认采样率,甚至直接抛出异常。
看一个基础的读取示例:
async function getActualSampleRate(): Promise<number> {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const track = stream.getAudioTracks()[0];
const settings = track.getSettings();
// sampleRate 单位是 Hz,例如 48000
return settings.sampleRate ?? 48000;
}这段代码在原生DOM类型定义(lib.dom.d.ts)中其实已经有类型支持,MediaTrackSettings.sampleRate被声明为number。但这个number太宽泛了,我们的业务代码完全可以在其之上做更严格的类型约束。
二、用字面量联合类型约束常见采样率档位
音频领域的采样率其实是相对固定的几个档位:8000(电话音质)、16000、22050、44100(CD音质)、48000(专业音频与视频常用)、96000(高解析度音频)。既然取值集合有限,最直接的方案就是用字面量联合类型定义:
type CommonSampleRate = 8000 | 16000 | 22050 | 44100 | 48000 | 96000;
// 创建 AudioContext 时约束传入值
function createContext(rate: CommonSampleRate): AudioContext {
return new AudioContext({ sampleRate: rate });
}
const ctx1 = createContext(48000); // OK
// const ctx2 = createContext(44100); // OK
// const ctx3 = createContext(44100 / 1000); // 报错,44.1 不在集合中
// const ctx4 = createContext(16000); // OK这种写法的好处显而易见:编译期就能拦截非法值,编辑器还能给出自动补全提示。缺点是灵活性差,一旦遇到某些设备返回了非标准档位(例如32000),就需要修改类型定义。因此在实际项目中,建议把联合类型用在「主动传入配置」的场景,也就是你自己决定采样率的地方;而从设备读取到的值,由于来源不可控,适合先用宽松类型接收再校验。
一种折中做法是配合类型守卫,把读取到的number收窄为联合类型:
const COMMON_RATES = [8000, 16000, 22050, 44100, 48000, 96000] as const;
function isCommonRate(rate: number): rate is CommonSampleRate {
return (COMMON_RATES as readonly number[]).includes(rate);
}
function normalizeRate(raw: number | undefined): CommonSampleRate {
if (raw !== undefined && isCommonRate(raw)) {
return raw;
}
return 48000; // 不在常见档位中时回退到默认值
}三、用Branded Type表示更宽泛但有边界的采样率
如果业务上需要支持8000到96000之间的任意合法值,字面量联合就不够用了。直接用number又无法表达边界,这时候可以借助branded type(品牌类型),给原始number打上一个「标记」,确保只有经过校验的值才能被当作采样率使用:
declare const SampleRateBrand: unique symbol;
type SampleRate = number & { readonly [SampleRateBrand]: true };
const MIN_RATE = 8000;
const MAX_RATE = 96000;
function createSampleRate(value: number): SampleRate {
if (!Number.isInteger(value) || value < MIN_RATE || value > MAX_RATE) {
throw new Error(`非法采样率:${value},必须在 ${MIN_RATE} 到 ${MAX_RATE} 之间的整数`);
}
return value as SampleRate;
}
// 业务函数只接受 SampleRate,普通 number 传不进来
function encodeAudio(rate: SampleRate, samples: Float32Array): ArrayBuffer {
// 编码逻辑,此处省略
return new ArrayBuffer(0);
}
const rate = createSampleRate(44100);
// encodeAudio(44100, samples) // 编译报错,必须走校验函数
encodeAudio(rate, new Float32Array(1024)); // OKbranded type的核心思想是「让非法状态不可表示」。任何采样率都必须通过createSampleRate这个入口创建,入口内部完成整数与范围校验,后续所有函数签名都只接受SampleRate类型。这样即使代码规模变大、多人协作,也不会出现有人随手传一个浮点数或越界值的情况。缺点是类型定义稍显晦涩,团队不熟悉时需要一点学习成本,另外它本质上还是number的 структурный类型,绕过类型检查的强制转换依然可能出问题,所以校验函数中的运行时检查不能省略。
四、在MediaRecorder与约束请求中应用类型
调用getUserMedia时,可以通过MediaTrackConstraints中的sampleRate与sampleSize表达采集偏好。不过要注意,这两个约束项在Chrome中长期是实验性的,Firefox干脆不支持,所以更稳妥的路径是:用getUserMedia拿到流之后,套一层AudioContext按目标采样率重采样,再通过MediaStreamAudioDestinationNode输出给MediaRecorder。
type TargetRate = 16000; // 语音识别常用 16kHz
const TARGET: TargetRate = 16000;
async function recordAtTargetRate(mimeType: string): Promise<Blob> {
// 1. 以目标采样率创建上下文
const ctx = new AudioContext({ sampleRate: TARGET });
// 2. 采集麦克风原始流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// 3. 接入上下文并输出到目标节点,实现重采样
const source = ctx.createMediaStreamSource(stream);
const dest = ctx.createMediaStreamDestination();
source.connect(dest);
// 4. 用重采样后的流录制
const recorder = new MediaRecorder(dest.stream, { mimeType });
const chunks: BlobPart[] = [];
recorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); };
const done = new Promise<Blob>(resolve => {
recorder.onstop = () => resolve(new Blob(chunks, { type: mimeType }));
});
recorder.start();
setTimeout(() => recorder.stop(), 5000); // 录5秒
return done;
}这个模式在实际项目里非常常用,尤其是语音识别场景往往强制要求16kHz。把TargetRate定义成字面量类型,可以让整个函数链路中的采样率在编译期保持一致。如果还想进一步保证类型安全,可以定义一个泛型工具,把采样率与AudioContext的创建绑定起来。
五、方案选型建议
综合来看,三种方案各有定位。字面量联合类型适合档位固定、由我方控制配置的场景,实现成本最低;branded type适合取值连续但有边界、且采样率会在多个模块间传递的项目,安全性最高;而对设备返回值做接收时,先以number或number | undefined接收(因为getSettings返回的sampleRate是可选字段),再通过类型守卫收窄,是最务实的组合拳。
最后提醒两点:一是在单元测试中覆盖采样率校验函数的边界值,包括8000、96000以及越界值;二是别忘了MediaRecorder的mimeType与采样率也会相互影响,例如某些编码格式对采样率档位有额外限制。把类型定义、运行时校验和集成测试结合起来,才能让音频采集模块真正稳定可靠。
TypeScript音频采样率Audio Sample Rate修改时间:2026-09-14 13:39:19