Skip to content

refactor(livekit): Record audio through shared audio pipeline - #2657

Merged
David Elner (delner) merged 5 commits into
mainfrom
delner/audio-recorder-pipeline
Oct 27, 2026
Merged

David Elner (delner) merged 5 commits into
mainfrom
delner/audio-recorder-pipeline

Conversation

@delner

@delner David Elner (delner) commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

To facilitate transforms on recorded audio, for the purposes of compression, encoding, or filtering, this adds a configurable hook+middleware stack to the audio recording pipeline.

What it does

Moves recording out of the LiveKit plugin into a reusable AudioRecorder with an internal middleware pipeline that can transform each segment before upload.

No outward changes to data or UX.

Opus compression and Ogg plugins leverage this pipeline in a separate, optional package.

Design

Internal pipeline:

Screenshot 2026-10-20 at 3 20 41 PM
  • AudioRecorder places PCM frames from each source on a shared timeline and cuts bounded segments. It runs each segment through the middleware and hands the result to the integration.
    • Middleware works like Rack or Koa: (segment, meta, next), transform on the way in, observe on the way out. Each recording builds its own stack, so state is never shared between calls. Middleware can change the format or channel layout and record facts in meta.attributes, published with the file.
  • The integration (LiveKit) owns capture, playback, spans and uploads. It publishes what each file contains: format, channels and sources.
  • Bounded and safe. One segment is in flight at a time, and buffered audio has a memory limit. A failing middleware omits its segment without affecting the call.
js/src/
  pcm-buffer.ts        PCM timeline, resampling, segment cuts (renamed from pcm-audio.ts)
  audio-recorder.ts    buffering, segment rotation, finish
  audio-middleware.ts  the middleware contract and runner

@delner David Elner (delner) self-assigned this Oct 23, 2026
@delner
David Elner (delner) force-pushed the delner/audio-recorder-pipeline branch from 302df9e to 8f0d11b Compare October 27, 2026 03:13
@delner David Elner (delner) changed the title feat(livekit): Audio middleware for voice recordings refactor(livekit): Record audio through shared audio pipeline Oct 27, 2026
@delner
David Elner (delner) force-pushed the delner/audio-recorder-pipeline branch from 8f0d11b to efc93db Compare October 27, 2026 03:32
@delner
David Elner (delner) force-pushed the delner/audio-recorder-pipeline branch from f7364f3 to d57d162 Compare October 27, 2026 16:16
@delner
David Elner (delner) marked this pull request as ready for review October 27, 2026 16:29
@delner
David Elner (delner) merged commit 0b7f2ff into main Oct 27, 2026
57 checks passed
@delner
David Elner (delner) deleted the delner/audio-recorder-pipeline branch October 27, 2026 19:16
David Elner (delner) added a commit that referenced this pull request Oct 27, 2026
)

Based on #2657

To provide more efficient, out-of-the-box encoding of audio, this adds
`opus` and `ogg` plugins as middleware.

## What it does

By default, voice recordings upload as standard Ogg Opus files instead
of raw PCM.

Install the package to activate:

```sh
npm install @braintrust/audio
```

Encoder settings are optional and declarative:

```ts
import { configureInstrumentation } from "braintrust/instrumentation";

configureInstrumentation({
  integrations: {
    livekit: {
      audio: {
        codec: { name: "opus", options: { bitrate: 32_000 } },
        container: { name: "ogg" },
      },
    },
  },
});
```

- **Optional.** `braintrust` doesn't depend on this package. Without it,
recordings fall back to raw PCM, with a warning.
- **Per integration.** Each integration owns its defaults: LiveKit
records Opus in Ogg.
- **Opt out in code.** Set `codec: { name: "pcm" }` to always record raw
PCM.

## How it works

```
livekit.audio ─► encoding config ─► loads @braintrust/audio ─► createStack(config) ─► AudioRecorder
                  (+ defaults)       (Node only, once)           opus → ogg
```

- **Opus** encodes speech with libopus (WebAssembly, via
`@audio/encode-opus`) 20 ms at a time, pausing for the event loop every
second of audio, so the agent stays responsive.
- **Ogg** wraps the packets in a standard file with no extra
dependencies. The file plays back at exactly the recorded length.
- **Declarative config, middleware underneath.** The SDK turns `codec`
and `container` into the internal middleware pipeline; users never write
middleware.

### Performance

Measured on one 44-second recording segment: about 0.4 s of CPU in
total, with the longest pause to the event loop around 10 ms, and 4 MB
of PCM uploaded as 344 KB.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants