Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/lib/c/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ and `target/include/moq.h`.
- **Threading.** Any function from any thread. Raw publish calls block until the codec takes the frame, which paces a publisher.
- **Connection health.** `moq_session_stats()` reports available metrics with per-field validity flags. `moq_session_snapshot()` samples those metrics and the negotiated draft name together from the same connection. Its protocol string is backed by static storage. Both return an offline error between reconnects and leave the destination untouched. `moq_session_bandwidth()` mints an allocator over the send estimate; `moq_bandwidth_reserve` claims a share for an app-owned track, and `moq_encode_video` / `moq_encode_audio` take the same handle so the built-in video encoder follows the grant.
- **Raw playback.** Raw audio and video consumers start at the newest cached group when opened, so rebuilding a live decoder skips the retained backlog.
- **Raw audio encode.** `moq_audio_encoder_output.codec` names the codec (only `"opus"` today), and `frame_duration_us` sets the Opus frame length: 2500, 5000, 10000, 20000, 40000, or 60000, with 0 meaning the 20 ms default.
- **Raw audio encode.** `moq_audio_encoder_output.codec` names the codec: `"opus"`, `"pcm"`, or `"aac"`. `frame_duration_us` sets the Opus frame length: 2500, 5000, 10000, 20000, 40000, or 60000, with 0 meaning the codec's default (20 ms for Opus, 1024 samples for AAC). AAC-LC encodes through the platform's encoder, so a host without one refuses it.
- **Audio channel layouts.** A `channels` count also names the speaker layout, by the WAVE convention: 1 is mono, 2 stereo, 3 2.1, 4 quad, 5 5.0, 6 5.1, 7 6.1, and 8 7.1, interleaved front left, front right, center, LFE, back, then side. `moq_decode_audio` remixes to the count you ask for; past 8 channels the samples pass through but can't be remixed.
- **Raw decode output.** `moq_video_decoder_output` selects the decoded CPU pixel format (`MOQ_VIDEO_PIXEL_FORMAT_I420` or `_RGBA`) and target size (`width`/`height`, both zero for native; otherwise even and non-zero). Unknown formats and invalid sizes fail `moq_decode_video` before subscribing; accepted requests deliver exactly that layout or fail on the terminal callback.
- **Encoded video metadata.** `moq_video_init.hint` is a zero-initialized `moq_video_hint` with `has_*` flags for coded dimensions, bitrate (bits per second), frame rate, and latency preference. Hints seed a video codec track's catalog; detected dimensions take precedence.
Expand Down
6 changes: 6 additions & 0 deletions doc/lib/go/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ one: `FetchGroup`/`FetchMediaGroup`, `Dynamic()` with `Requests(ctx)`,
`AppendDatagram`/`Datagrams(ctx)`, `SetCatalogSection`, `Demand()` for `Used`/`Unused`,
`Session().Stats()`. `moq.IsAuthError` and `moq.IsShutdown` classify errors. `moq.ProtocolError(err)` is the structured protocol failure (scope, verbatim code, kind) when the peer sent one.

`EncodeAudio` encodes raw PCM inside the binding. Its codec is `OpusAudioCodec()`
or `AacAudioCodec()`, and `AudioEncoderOutput.FrameDurationUs` sets the Opus
frame length: 2500, 5000, 10000, 20000 (the default), 40000, or 60000. 0 takes
the codec's own frame, which AAC needs. AAC-LC encodes through the platform's
encoder, so a host without one refuses it.

Audio `Channels` also names the speaker layout, by the WAVE convention: 1 is
mono, 2 stereo, 3 2.1, 4 quad, 5 5.0, 6 5.1, 7 6.1, and 8 7.1, interleaved
front left, front right, center, LFE, back, then side. Decoding remixes to the
Expand Down
7 changes: 5 additions & 2 deletions doc/lib/kt/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ connection's send estimate; pass it to `encodeVideo` / `encodeAudio` or
native side.

`encodeAudio` encodes raw PCM inside the binding. Its codec is an object,
`AudioCodec.opus()`, and `AudioEncoderOutput.frameDurationUs` sets the Opus
frame length: 2500, 5000, 10000, 20000 (the default), 40000, or 60000.
`AudioCodec.opus()` or `AudioCodec.aac()`, and
`AudioEncoderOutput.frameDurationUs` sets the Opus frame length: 2500, 5000,
10000, 20000 (the default), 40000, or 60000. 0 takes the codec's own frame,
which AAC needs. AAC-LC encodes through the platform's encoder, so a host
without one refuses it.

Audio `channels` also names the speaker layout, by the WAVE convention: 1 is
mono, 2 stereo, 3 2.1, 4 quad, 5 5.0, 6 5.1, 7 6.1, and 8 7.1, interleaved
Expand Down
7 changes: 5 additions & 2 deletions doc/lib/py/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ Each server request reports a `moq.Transport` enum, including QUIC, Iroh,
WebSocket, TCP, and Unix sockets.

`encode_audio` encodes raw PCM inside the binding. Its codec is an object,
`moq.AudioCodec.opus()`, and `AudioEncoderOutput.frame_duration_us` sets the
Opus frame length: 2500, 5000, 10000, 20000 (the default), 40000, or 60000.
`moq.AudioCodec.opus()` or `moq.AudioCodec.aac()`, and
`AudioEncoderOutput.frame_duration_us` sets the Opus frame length: 2500, 5000,
10000, 20000 (the default), 40000, or 60000. 0 takes the codec's own frame,
which AAC needs. AAC-LC encodes through the platform's encoder, so a host
without one refuses it.

Audio `channels` also names the speaker layout, by the WAVE convention: 1 is
mono, 2 stereo, 3 2.1, 4 quad, 5 5.0, 6 5.1, 7 6.1, and 8 7.1, interleaved
Expand Down
21 changes: 20 additions & 1 deletion doc/lib/rs/moq-audio.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ policy. Decoding likewise separates low-level `decode::Config`, PCM
| Module | Does |
| --- | --- |
| `capture` | Microphones via CoreAudio, WASAPI, ALSA (and PipeWire/PulseAudio hosts), plus macOS system audio |
| `encode` | PCM to Opus (with DTX and voice-activity signaling) or raw PCM for the lowest latency |
| `encode` | PCM to Opus (with DTX and voice-activity signaling), raw PCM for the lowest latency, or AAC-LC through a platform encoder |
| `decode` | Opus, PCM, and AAC-LC back to PCM, resampled to the rate you want |
| `playback` | One output device mixing every track in a call, with click-free volume ramps |
| `aec` | Acoustic echo cancellation (a port of WebRTC's), so a laptop with no headset doesn't feed itself back |
Expand All @@ -48,6 +48,25 @@ its config are refused at construction on every host. HE-AAC signaled only in
band plays as its half-rate LC core. Linux has no OS audio decoder, so it will
stay that way there.

`encode` selects the same way, through `encode::Settings::kind`, and
`Encoder::name()` reports what opened.

| Backend | Encodes | Hosts |
| --- | --- | --- |
| `libopus` | Opus, mono or stereo | all |
| `pcm` | PCM | all |

`encode::Codec::Aac` is AAC-LC (`mp4a.40.2`) at the input's rate and layout:
mono, stereo, 3.0, 4.0, 5.0, 5.1, or 7.1, the layouts with an AAC
channelConfiguration. Frames are 1024 samples, so `Settings::from_input` sets
`frame_duration` to match. The catalog's AudioSpecificConfig is built from the
settings when the track is registered, and since it has no field for the
encoder's delay, packets are stamped that much earlier so the first input
sample still lands at the first timestamp. There is no software AAC encoder,
and no platform encoder is wired in yet, so `Codec::Aac` is refused at
construction on every host for now. Linux has no OS encoder, so it will stay
that way there.

Highlights:

- **`encode::Publication`** advertises the track and opens the microphone only while someone listens. Stop, swap devices, and restart without changing the track subscribers know; read a level meter for the UI.
Expand Down
7 changes: 5 additions & 2 deletions doc/lib/swift/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,11 @@ divides the connection's send estimate; pass it to `encodeVideo` /
(scope, verbatim code, kind) when the peer sent one.

`encodeAudio` encodes raw PCM inside the binding. Its codec is an object,
`AudioCodec.opus()`, and `AudioEncoderOutput.frameDurationUs` sets the Opus
frame length: 2500, 5000, 10000, 20000 (the default), 40000, or 60000.
`AudioCodec.opus()` or `AudioCodec.aac()`, and
`AudioEncoderOutput.frameDurationUs` sets the Opus frame length: 2500, 5000,
10000, 20000 (the default), 40000, or 60000. 0 takes the codec's own frame,
which AAC needs. AAC-LC encodes through the platform's encoder, so a host
without one refuses it.

Audio `channels` also names the speaker layout, by the WAVE convention: 1 is
mono, 2 stereo, 3 2.1, 4 quad, 5 5.0, 6 5.1, 7 6.1, and 8 7.1, interleaved
Expand Down
2 changes: 1 addition & 1 deletion go/wrapper/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func (b *BroadcastProducer) PublishContainerStream(format ContainerFormat) (*Con

// EncodeAudio publishes a raw-audio track with an in-process encoder.
//
// Select the codec with OpusAudioCodec (currently the only constructor).
// Select the codec with OpusAudioCodec or AacAudioCodec.
// Pass bandwidth to reserve this track's bitrate against the session's
// allocator so a co-resident video encoder sizes itself against what is left.
func (b *BroadcastProducer) EncodeAudio(name string, input AudioEncoderInput, output AudioEncoderOutput, bandwidth *Bandwidth) (*AudioProducer, error) {
Expand Down
10 changes: 8 additions & 2 deletions go/wrapper/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import ffi "moq.dev/moq-ffi/moq"
type (
// Audio describes one audio rendition in a broadcast catalog: codec, sample rate, channel count, and container.
Audio = ffi.MoqAudio
// AudioCodec selects the audio encoder codec. Build one with OpusAudioCodec;
// AudioCodec selects the audio encoder codec. Build one with OpusAudioCodec or AacAudioCodec;
// adding a codec later adds a constructor, not a breaking enum change.
AudioCodec = ffi.MoqAudioCodec
// AudioDecoderOutput configures the PCM format, sample rate, and channels DecodeAudio delivers.
AudioDecoderOutput = ffi.MoqAudioDecoderOutput
// AudioEncoderInput declares the PCM sample format, sample rate, and channel count of frames written to an audio producer.
AudioEncoderInput = ffi.MoqAudioEncoderInput
// AudioEncoderOutput configures the Opus encoder: codec, optional sample rate, channels, bitrate, and frame duration.
// AudioEncoderOutput configures the encoder: codec, optional sample rate, channels, bitrate, and frame duration.
AudioEncoderOutput = ffi.MoqAudioEncoderOutput
// AudioSampleFormat is a raw PCM sample layout, mirroring WebCodecs AudioData.format.
AudioSampleFormat = ffi.MoqAudioSampleFormat
Expand Down Expand Up @@ -184,6 +184,12 @@ func OpusAudioCodec() *AudioCodec {
return ffi.MoqAudioCodecOpus()
}

// AacAudioCodec selects AAC-LC through the platform's encoder for EncodeAudio.
// A host without one refuses it. Leave FrameDurationUs at 0 for AAC's own frame.
func AacAudioCodec() *AudioCodec {
return ffi.MoqAudioCodecAac()
Comment on lines +187 to +190

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the Go guide for the new AAC constructor

The new public AacAudioCodec constructor is exposed to Go, but doc/lib/go/index.md still gives no EncodeAudio codec-selection guidance or AAC frame-duration requirement. This leaves Go users without the required FrameDurationUs: 0 usage in the language guide, despite the repository's explicit requirement that an rs/moq-ffi surface change update doc/lib/go.

AGENTS.md reference: AGENTS.md:L94-L96

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the EncodeAudio note to doc/lib/go: OpusAudioCodec or AacAudioCodec, and FrameDurationUs 0 for AAC's own frame.

(Written by Grok 4.7)

}

// VideoPixelFormat values: the raw pixel layout fed to the in-process encoder,
// and the one the in-process decoder delivers.
const (
Expand Down
2 changes: 1 addition & 1 deletion kt/moq/src/jvmAndAndroidMain/kotlin/dev/moq/Aliases.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ typealias FetchGroupOptions = uniffi.moq.MoqFetchGroupOptions
typealias TrackInfo = uniffi.moq.MoqTrackInfo
/** One audio frame: PCM payload bytes plus a presentation timestamp. */
typealias AudioFrame = uniffi.moq.MoqAudioFrame
/** Selects the audio encoder codec. Build one with `AudioCodec.opus()`. */
/** Selects the audio encoder codec. Build one with `AudioCodec.opus()` or `AudioCodec.aac()`. */
typealias AudioCodec = uniffi.moq.MoqAudioCodec
/** A raw PCM sample format, mirroring WebCodecs `AudioData.format`. */
typealias AudioSampleFormat = uniffi.moq.MoqAudioSampleFormat
Expand Down
2 changes: 1 addition & 1 deletion py/moq-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ client = moq.Client(
- `.publish_audio(format, init, *, label=None, track=None) → MediaProducer`. `init` is required: an OpusHead or AudioSpecificConfig resolves the whole rendition. `track` names the track; otherwise a unique name is derived from the format.
- `.publish_video(format, init=b"", *, label=None, hint=None, track=None) → MediaProducer`. `init` may be empty for a format that resolves in band; a `VideoHint` pins catalog fields the stream can't reveal (bitrate) or publishes the catalog before the first keyframe. `track` names the track as in `publish_audio`.
- `.encode_video(input, output, *, bandwidth=None) → VideoProducer`. Encode raw `VideoFrame`s inside the binding; `.write(frame)` each one.
- `.encode_audio(name, input, output, *, bandwidth=None) → AudioProducer`. Encode raw PCM `AudioFrame`s; the codec is `output.codec`, e.g. `AudioCodec.opus()`, with `output.frame_duration_us` setting the Opus frame length.
- `.encode_audio(name, input, output, *, bandwidth=None) → AudioProducer`. Encode raw PCM `AudioFrame`s; the codec is `output.codec`, e.g. `AudioCodec.opus()` or `AudioCodec.aac()`, with `output.frame_duration_us` setting the Opus frame length (0 takes the codec's own frame, which AAC needs).
- `.finish()`
- **`BroadcastDynamic`**. Async source of tracks requested by subscribers.
- `await .requested_track() → TrackRequest`. Call `.accept()` on it for a `TrackProducer`, or `.abort(code)` to reject.
Expand Down
4 changes: 2 additions & 2 deletions py/moq-rs/moq/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,8 +749,8 @@ def encode_audio(
) -> AudioProducer:
"""Publish a raw-audio track with an in-process encoder.

Select the codec with ``moq.AudioCodec.opus()`` (currently the only
constructor), placed in ``output``.
Select the codec with ``moq.AudioCodec.opus()`` or
``moq.AudioCodec.aac()``, placed in ``output``.

Pass ``bandwidth`` to reserve this track's bitrate against the session's
allocator so a co-resident video encoder sizes itself against what is left.
Expand Down
1 change: 0 additions & 1 deletion quest/m1/audio-codecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ its own decode and encode quest so verification stays per host.

- [AudioToolbox decode](/quest/m1/audio-codecs/decode-audiotoolbox.md) - macOS and iOS decode HE-AAC, multichannel AAC, and what else the framework offers
- [Opus surround](/quest/m1/audio-codecs/opus-surround.md) - mapping family 1 decodes on every host through the multistream decoder
- [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - `encode::backend` and `Codec::Aac`, so a native publisher can produce AAC-LC
- [AudioToolbox encode](/quest/m1/audio-codecs/encode-audiotoolbox.md) - macOS and iOS encode AAC-LC

## Related
Expand Down
6 changes: 5 additions & 1 deletion quest/m1/audio-codecs/encode-audiotoolbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ the encode seam as the platform candidate on macOS and iOS.
`kAudioConverterPrimeInfo` gives the delay the timestamps fold in.
- Bitrate through `kAudioConverterEncodeBitRate`, updated live where the
converter allows.
- The seam assumes one packet per frame. If the converter holds output back,
the backend needs a `flush` and a zero-or-more return, which changes
`Encoder::encode` and so targets `dev`.
- Gate the seam's "AAC refused without a platform encoder" test to hosts
without one.
- Regression: a stereo and a 5.1 encode round-trip through the AudioToolbox
decoder and through symphonia (stereo only), with timestamps continuous
across the priming.

## Required

- [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - the candidate order this backend joins
- [AudioToolbox decode](/quest/m1/audio-codecs/decode-audiotoolbox.md) - the round-trip regression decodes through it
40 changes: 0 additions & 40 deletions quest/m1/audio-codecs/encode-backend.md

This file was deleted.

6 changes: 1 addition & 5 deletions quest/m1/obs-moq-video/audio-publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ MoQ publishing can encode OBS's mixed audio with moq-audio Opus while preserving

- Expose `moq_audio::encode::Encoder` through a codec-only moq-ffi type with owned encoder/packet handles, so every binding gets it. The existing raw audio producer combines encoding and publication and must not create a second publication alongside the OBS encoded output. Reuse frame sizing, bitrate updates, catalog configuration, and finish/padding behavior.
- Implement the OBS audio encoder interface, including fixed input frame size, mono/stereo PCM conversion, timestamps, codec headers, final padding, and packet release. Ask OBS for the input layout the encoder supports; moq-audio does not implement arbitrary channel remapping. Keep capture/mixing/device ownership in OBS.
- Publish Opus initially. Leave AAC with the existing OBS mode and defer PCM publishing UI, since the output currently declares AAC/Opus. Correct stale binding documentation that describes the raw codec parser as Opus-only if that API is touched.
- Publish Opus initially. `encode::Codec::Aac` lets the adapter offer AAC where a platform encoder exists; until then leave AAC with the existing OBS mode and defer PCM publishing UI, since the output currently declares AAC/Opus. Correct stale binding documentation that describes the raw codec parser as Opus-only if that API is touched.
- Apply the shared presets and independent audio bitrate. Test 10/20 ms packetization, frame-size changes at stream boundaries, partial final frames, silence, reconnect, saturation, and stop with pending output. Verify decoded audio and A/V timestamps with a real subscriber.
- Land the internal adapter independently; the combined Use MoQ encoders UI becomes available when the video adapter also lands. Do not expose a temporary video/audio mix-and-match product UI.

## Required

- [OBS migration](/quest/m1/cpp/obs.md) - the plugin is on the generated C++ first
- [Encoder presets](/quest/m1/obs-moq-video/presets.md) - shared policy and truthful reporting

## Related

- [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - `Codec::Aac` lets the adapter offer AAC instead of leaving it to the OBS encoder mode
4 changes: 3 additions & 1 deletion quest/m2/audio-encode-mediacodec.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ behind the `mediacodec` feature and the encode seam.
- `audio/mp4a-latm` with `KEY_AAC_PROFILE` = LC. The catalog ASC is
synthesized at construction per the encode seam, since `csd-0` only arrives
with the first output buffer; assert the two match.
- MediaCodec pipelines output, which the one-packet-per-frame seam does not
allow yet: add a `flush` and a zero-or-more return, a change to
`Encoder::encode` that targets `dev`, unless the AudioToolbox quest already did.
- Multichannel is device-dependent; probe the encoder's capabilities at open
and refuse a layout it does not list.
- Round-trip regression through the MediaCodec decoder; runtime proof on a
device or emulator.

## Required

- [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - the candidate order this backend joins
- [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - the round-trip regression decodes through it
1 change: 0 additions & 1 deletion quest/m2/audio-encode-mediafoundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ behind the encode seam on Windows.

## Required

- [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - the candidate order this backend joins
- [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - the round-trip regression decodes through it

## Related
Expand Down
Loading
Loading