feat(audio): channel sample_rate caps Opus bandwidth (narrowband/wideband)
The per-channel sample_rate field was inert after pinning the codec to 48 kHz. Make it meaningful without changing the 48 kHz clock: carry it as OpusParams::max_bandwidth_hz and apply OPUS_SET_MAX_BANDWIDTH in OpusEncoder::init (8000->narrowband, 16000->wideband, 24000->super-wideband, 48000->full). A low-bitrate room can now shed out-of-band content while every endpoint keeps a single 48 kHz clock. Make sample_rate channel-authoritative on the server: conn_session no longer overrides effective sample_rate with the client's always-48000 request (it now behaves like frame_ms/mode). vc_get_stream_audio_config reports the channel's configured rate for own streams too. New ctest channel_samplerate: a 7 kHz tone is attenuated ~1000x on an 8 kHz (narrowband) channel vs a 48 kHz (full-band) channel, proving the cap is in effect. ctest --preset dev 26/26. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,19 @@ namespace voicecat::codec {
|
||||
|
||||
#ifdef VOICECAT_HAS_OPUS
|
||||
|
||||
// Map an intended channel/capture sample rate (Hz) to the Opus max-bandwidth constant. The
|
||||
// codec always runs at 48 kHz internally (docs/voice.md §3); this caps the bandwidth the
|
||||
// encoder will select so a channel can request narrowband/wideband audio for low-bitrate rooms.
|
||||
// 0 (unset) and >= 48000 map to FULLBAND, which is the encoder default (i.e. a no-op cap).
|
||||
static opus_int32 opus_max_bandwidth_for(uint32_t rate_hz) {
|
||||
if (rate_hz == 0) return OPUS_BANDWIDTH_FULLBAND;
|
||||
if (rate_hz <= 8000) return OPUS_BANDWIDTH_NARROWBAND; // ~4 kHz audio
|
||||
if (rate_hz <= 12000) return OPUS_BANDWIDTH_MEDIUMBAND; // ~6 kHz
|
||||
if (rate_hz <= 16000) return OPUS_BANDWIDTH_WIDEBAND; // ~8 kHz
|
||||
if (rate_hz <= 24000) return OPUS_BANDWIDTH_SUPERWIDEBAND; // ~12 kHz
|
||||
return OPUS_BANDWIDTH_FULLBAND; // ~20 kHz
|
||||
}
|
||||
|
||||
// ── OpusEncoder ──────────────────────────────────────────────────────────────
|
||||
|
||||
bool OpusEncoder::init(const OpusParams& p) {
|
||||
@@ -27,6 +40,7 @@ bool OpusEncoder::init(const OpusParams& p) {
|
||||
}
|
||||
|
||||
opus_encoder_ctl(enc_, OPUS_SET_BITRATE(static_cast<opus_int32>(p.bitrate_bps)));
|
||||
opus_encoder_ctl(enc_, OPUS_SET_MAX_BANDWIDTH(opus_max_bandwidth_for(p.max_bandwidth_hz)));
|
||||
opus_encoder_ctl(enc_, OPUS_SET_COMPLEXITY(static_cast<opus_int32>(p.complexity)));
|
||||
opus_encoder_ctl(enc_, OPUS_SET_INBAND_FEC(p.fec ? 1 : 0));
|
||||
opus_encoder_ctl(enc_, OPUS_SET_DTX(p.dtx ? 1 : 0));
|
||||
|
||||
@@ -26,6 +26,11 @@ enum class OpusApplication {
|
||||
|
||||
struct OpusParams {
|
||||
uint32_t sample_rate = 48000;
|
||||
// Intended channel/capture sample rate (Hz), used ONLY to cap the encoder's audio bandwidth
|
||||
// (narrowband/wideband/…) via OPUS_SET_MAX_BANDWIDTH. The codec always runs at 48 kHz
|
||||
// internally (docs/voice.md §3); this lets a low-bitrate channel constrain encoded bandwidth
|
||||
// without changing the PCM clock. 0 = unset → full band. Decoder ignores it.
|
||||
uint32_t max_bandwidth_hz = 0;
|
||||
uint32_t bitrate_bps = 24000;
|
||||
uint32_t frame_ms = 20;
|
||||
bool stereo = false;
|
||||
|
||||
Reference in New Issue
Block a user