Rust output adapters
Implement bounded PCM and Opus consumers and configure encoding.
Media output contract
Implement MediaOutput for an output consumer. Each session owns one output
instance. write_frame takes ownership of a PcmFrame and returns after that
frame is accepted. This synchronous call applies backpressure directly: while an
output is full, the session worker does not decode more audio. An output may use
its own buffer, but it must bound that buffer and honor cancellation.
Outputs implement flush to discard pre-seek audio, pause and resume for
downstream suspension, finish for natural end-of-stream, and thread-safe
cancel to interrupt an in-progress operation during stop or failure.
played_position may report a consumed/audible timeline. Return None when the
output cannot provide one. The session holds no lock while calling output
methods.
Output methods execute on the playback worker, except cancel, which can run
concurrently to unblock them. A custom output must make cancel safe alongside
write_frame, flush, and finish. stop() waits for those methods to return
and then joins the worker. The current implementation uses one OS thread per
active session and does not introduce a global scheduler or real-time pacing.
Opus encoding output
OpusEncoderOutput adapts normalized PCM to owned EncodedOpusPacket values and
passes them to an OpusPacketSink. The default packet duration is 20 ms (960
samples per channel at 48 kHz), stereo. It accumulates variable-sized decoder
frames and does not pace encoding. A final incomplete frame is zero-padded to
960 samples. Packet metadata reports both the encoded duration and the shorter
amount of source audio represented. A seek drops partial PCM and resets the
native encoder before sending frames from the new timeline.
Each Opus output owns its encoder and sink. The sink contract is synchronous and
backpressured: implementations should use bounded storage and make cancel
safe alongside a blocked send. The adapter does not expose Discord types and can
be used with other packet consumers. Cargo builds libopus from bundled source
and statically links it. A system libopus development package is not required.
Encoder configuration
OpusEncoderConfig::default() explicitly selects 128000 bits per second (128 kbit/s for the
stereo stream), complexity 9, and variable bitrate (vbr: true). Higher bitrate generally improves
quality while using more bandwidth. Complexity 0–10 trades encoding CPU for quality. Higher values
use more computation. VBR distributes bits according to audio complexity. Disabling it requests
constant bitrate. The validated bitrate range is 500–512000 bits per second, following the
libopus control documentation.
The application mode remains Application::Audio.
Rust consumers can pass reusable settings to an output:
use remedia_core::{OpusEncoderConfig, OpusEncoderOutput, OpusPacketSink, OutputError};
fn configured_output(sink: impl OpusPacketSink) -> Result<OpusEncoderOutput, OutputError> {
let config = OpusEncoderConfig {
bitrate: 96_000,
complexity: 5,
..Default::default()
};
OpusEncoderOutput::new_with_config(sink, config)
}config.validate() checks ranges without creating libopus state. new_with_config validates
immediately. Native allocation and control errors propagate on the first PCM write. Use
new_with_config_and_observability(sink, config, handle) to attach diagnostics as well. Existing
new and new_with_observability constructors use the defaults. Each output owns its configuration
and encoder. There are no global encoder settings. Seek resets retain the configured controls.
These controls apply only to PCM encoding. Compatible source Opus packets still pass through
unchanged without initializing libopus. Their original bitrate and quality are unaffected. Filters
or gain that require PCM activate encoding with the output's settings. The server resolves settings
from audio.opus. Embedded Node.js uses core
defaults without a configuration file or a new JavaScript API.