@audio/mic

中文文档
1.5.1发布于 2026/10/31MITTypeScriptES Module零依赖

Capture audio from microphone in browser/node

原始使用文档
文档目录15 节
  1. Usage
  2. Async iterable
  3. Devices
  4. Record to disk
  5. Node Readable
  6. Backends
  7. API
  8. read = mic(opts?)
  9. read(cb)
  10. read(null)
  11. read.close()
  12. read.backend
  13. Building
  14. Publishing
  15. License

@audio/mic

Try it in the browser: Tuner, Audio recorder, Room and speaker measurement. Runs on this package, nothing is uploaded.

Capture audio from microphone in node or browser.

Usage

import mic from '@audio/mic'

let read = mic({
  sampleRate: 44100,
  channels: 1,
  bitDepth: 16,
  // bufferSize: 50,       // the most one block holds, ms (default 50)
  // backlog: 10000,       // the most held unread, ms (default 10000)
  // backend: 'miniaudio', // force backend
})

read((err, pcmBuffer) => {
  // process chunk
})
read(null) // stop capture

Async iterable

import mic from '@audio/mic'

let read = mic({ sampleRate: 44100, channels: 1 })
for await (let chunk of read) {
  // process PCM chunk
}

A break out of the loop stops capture. An overflow (see read(cb)) is thrown.

Devices

import mic, { devices } from '@audio/mic'

await devices()
// [{ id: 'BuiltInMicrophoneDevice', name: 'MacBook Pro Microphone', default: true }, …]

let read = mic({ device: 'USB' })   // by id, by name, or by part of its name when only one holds it

Ids are the platform's own: CoreAudio's UID, WASAPI's endpoint id, the ALSA or PulseAudio name. A device that isn't there is an error (code: 'ENODEVICE'), never another device. In the browser, devices() reads enumerateDevices() (names show once the page may use the microphone) and device becomes the deviceId getUserMedia asks for.

Record to disk

import { record } from '@audio/mic'

let take = record('take.wav', { device: 'Scarlett', channels: [1, 2] })   // inputs from 1, as the interface labels them
take.status()          // { seconds, peaks, clips, silenced, stalled, rerouted, missed, done, error }: peaks since the last call
take.read()            // Float32Array: the audio written since the last read, interleaved, to draw or hear it as it comes
await take.stop()      // the same, once the file is closed

// several files from one device, one clock: tracks of one take, sample for sample together
record({ 'voice.wav': [1], 'guitar.wav': [2], 'room.wav': [3, 4] }, { device: 'Scarlett' })

// no files: every input's level, nothing written, to set levels before recording
record({}, { device: 'Scarlett' }).status().peaks

The addon writes the file itself, from a ring of 20 s the device fills, so nothing in JS reaches the take: an event loop held for minutes, a crash, a kill. The device runs at its own rate and gives float; nothing is resampled unless sampleRate asks for another rate, and then the device switches to it (on macOS, as Audio MIDI Setup would).

  • On disk as it comes. Every second the header tells the length so far and the file is flushed to the disk itself (F_FULLFSYNC on macOS, where fsync stops at the drive's cache). A process killed or a power cut leaves a WAV that plays to its last second; recover(path) gives it the rest. process.exit() and an ending environment close it whole.
  • Nothing lost quietly. Frames the ring had no room for (a disk stalled 20 s) are written as silence where they were, so the take keeps time, and counted in silenced. A device that stops, unplugged, ends the take with error.code === 'EDEVICELOST', written to its last frame; seconds without audio from it read as stalled; the system moving its default input elsewhere counts in rerouted.
  • Never over a take. A file already there is an error (EEXIST) unless overwrite: true; an input the device lacks is ECHANNEL; either before anything is written.
  • WAV as every program reads it: PCM 16-bit (TPDF-dithered) or 24-bit, or 32-bit float; WAVE_FORMAT_EXTENSIBLE over two channels; past 4 GB the header turns RF64 in place (EBU Tech 3306), into the JUNK chunk kept for it.

On macOS an input opened while the system still asks for the microphone permission waits for the answer, and the thread that opened it with it: an app asks first (AVCaptureDevice.requestAccess), and opens an input once it is answered.

Options: device, channels (inputs, default the first two or the one), bitDepth (16, 24 (default), 32), sampleRate (default the device's own), ring (seconds, default 20), overwrite. The take tells sampleRate, channels (the device's), device (its name) and files ([{ path, channels }]).

import { recover } from '@audio/mic'
recover('take.wav')    // → { frames, before }: the frames the file holds, and those its header told

Node Readable

import MicReadable from '@audio/mic/stream'

MicReadable({ sampleRate: 44100, channels: 1 }).pipe(dest)

Backends

Tried in order; first successful one wins.

BackendHowLatencyInstall
miniaudioN-API addon wrapping miniaudio.hLowPrebuilt via @audio/mic-* packages
processPipes from ffmpeg/sox/arecordHighSystem tool must be installed
nullSilent, maintains timing contract—Built-in (CI/headless fallback)
mediastreamgetUserMedia + AudioWorklet (browser)LowBuilt-in

API

read = mic(opts?)

Returns a source function. Options:

  • sampleRate — default 44100
  • channels — default 1
  • bitDepth — 8, 16 (default), 24, 32
  • bufferSize — the most one block from the device holds, in ms, default 50
  • backlog – the most audio held unread, in ms, default 10000 (see read(cb))
  • device — an input by id or name (devices()), default the system's
  • backend — force a specific backend

read(cb)

Read PCM data. Callback fires with each captured chunk: (err, buffer) => {}.

Capture doesn't wait for reads: what comes between them is held, and the next read takes all of it in one buffer, so a reader that takes its time loses nothing. Past backlog held unread, capture stops: what was held still reads, then an error with code: 'EOVERFLOW', then the end (null). In node the device's own ring holds what comes while the event loop is busy, backlog's worth up to 2 s, and overflows the same way. A device that stops of itself (unplugged) ends it the same way, with code: 'EDEVICELOST'.

read(null)

Stop capture. Closes the audio device. What was captured before it still reads, then null.

read.close()

Immediately close the audio device.

read.backend

Name of the active backend ('miniaudio', 'process', 'null', 'mediastream').

Building

npm run build          # compile native addon locally
npm test               # run tests

Publishing

# JS-only change (no native code changed):
npm version patch && git push && git push --tags
npm publish

# Native code changed — rebuild platform packages:
npm version patch && git push && git push --tags
gh run watch                    # wait for CI
rm -rf artifacts
gh run download --dir artifacts \
  -n mic-darwin-arm64 -n mic-darwin-x64 \
  -n mic-linux-x64 -n mic-linux-arm64 -n mic-win32-x64

# (fallback) If darwin-x64 CI is unavailable, cross-compile locally:
npx node-gyp@latest rebuild --arch=x64
mkdir -p artifacts/mic-darwin-x64
cp build/Release/mic.node artifacts/mic-darwin-x64/

for pkg in packages/mic-*/; do
  cp artifacts/$(basename $pkg)/mic.node $pkg/
  (cd $pkg && npm publish)
done
npm publish

License

MIT

ॐ

版本与发布记录

@audio/mic 共发布 9 个版本,首次发布于 2026/07/09;过去 12 个月发布了 9 个版本。

版本发布日期距今
1.5.1latest2026/10/312 天前
1.4.02026/10/313 天前
1.2.02026/10/0727 天前
1.1.52026/10/0627 天前
1.1.42026/10/0627 天前
1.1.32026/08/292 个月前

关于 @audio/mic 的常见问题

@audio/mic 是什么?

从浏览器/节点的麦克风捕获音频

如何安装 @audio/mic?

在项目目录运行 npm install @audio/mic,或使用 pnpm add / yarn add / bun add。

@audio/mic 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@audio/mic 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@audio/mic 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@audio/mic 可以商用吗?

@audio/mic 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@audio/mic 还在维护吗?

最新版本 1.5.1 发布于 2026/10/31(2 天前);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/07/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。