Skip to content

Extractaudio rate bitrate - #178

Merged
sobomax merged 4 commits into
sippy:masterfrom
bugtoo:extractaudio-rate-bitrate
Oct 17, 2026
Merged

sobomax merged 4 commits into
sippy:masterfrom
bugtoo:extractaudio-rate-bitrate

Conversation

@bugtoo

@bugtoo bugtoo commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two output controls to extractaudio:

  • -r 8000|16000: output sample rate. At 16000 Hz, G.722 legs keep their wideband audio.
  • -b KBPS (Opus) and -C LEVEL (any libsndfile format): encoder bitrate or compression level.

Also fixes two small bugs found along the way. Defaults are unchanged: without the new options the output is byte-identical to the current code.

Why

We convert PBX call recordings with extractaudio -s so that caller and callee land on separate channels. Each party can then be transcribed separately and the transcript split by speaker. Two limits get in the way:

  • 8 kHz only. G.722 legs are decoded down to narrowband, so wideband calls lose half their bandwidth before speech recognition.
  • No bitrate control. With -F ogg -D opus, libsndfile picks its own default (about 59 kbit/s for stereo). At low bitrates Opus merges the two channels into one, which defeats the stereo split, so we need to choose a rate that keeps them separate at a reasonable size.

Implementation notes

  • -r 16000:
    • Timing stays in 8 kHz RTP ticks; each tick produces rate / 8000 output samples, and the channel start offset is computed at the output rate.
    • G.722 is decoded at its native rate.
    • Narrowband legs (G.711, G.729, GSM) are upn-windowed half-band interpolator. It is flatto 3 kHz, -1.6 dB at 3.4 kHz, with a fixed 1 ms delay.
    • Upsampling narrowband legs lets one stereo file combine legs that use different codecs.
  • -b / -C:
    • Both set SFC_SET_COMPRESSION_LEVEL after sf_open() and before the first write.
    • For Opus, -b converts kbit/s to the level using libsndfile's mapping: ((1 - q) * 250000 + 6000) bit/s per channel.
    • -b requires -D opus; -b and -C are
    • Option errors use warnx() and the common exit path, like the existing options.
  • Fixes:
    • generate_silence() advanced the G.729 ouer 80-sample frame.
    • The "written" count printed for mono output was halved.

Testing

New tests

  • tests/extractaudio/badargs: invalid -r (11025, 16000x), -b outside 6–512 or without -D opus, -b combined with -C, and -C 2.
  • tests/extractaudio/extractaudio16k (new, r runs -r 16000 in mono and stereo on everyexisting recording and checks 16 SHA-256 checksums plus the RTP stats output.

How we ran them. The environment mirrors 24.04 with bcg729 1.1.1, libg722, libsrtp2_x_devand libsndfile 1.2.2 built from source, pluslibssl-dev`, which the GitHub runners have preinstalled.

./configure --enable-basic-tests && make
make -C tests check TESTS="extractaudio/baio1 extractaudio/scanextractaudio/extractaudio16k"
  • Results: all four suites pass with gcc and with clang, and the changed files build without warnings.
  • Defaults unchanged: the existing extractaudio1 checksums still pass.
  • Deterministic output: the 16 kHz checksums came out identical in two separate clean builds.

Opus. Not covered by the checksum tests: scripts/do-build.sh builds libsndfile without installing Opus or Ogg libraries first, so CI likely has no Opus encoder. It was checked by hand on real two-leg recordings, on Linux and on FreeBSD 14.4, using libsndfile 1.2.2 built with Opus.

  • 16 kHz conversion:

    • exactly twice the frames;
    • narrowband samples pass through unchanged;
    • energy above 4.2 kHz is 57–77 dB below the speech band.
  • Stereo channel separation by bitrate:

    Option Separation
    -b 16 0 dB (Opus downmixes to mono)
    -b 24 24 dB
    -b 32 48 dB

    We use -s -r 16000 -b 32 -F ogg -D opus.

@sobomax
sobomax force-pushed the extractaudio-rate-bitrate branch from ec62bc8 to f56e65c Compare October 16, 2026 20:37
bugtoo and others added 3 commits October 16, 2026 18:34
-r 8000|16000 selects the output rate. At 16000 G.722 is decoded at its
native rate and narrowband codecs are upsampled 2x by a delay free
half-band interpolator, so legs using different codecs stay aligned in
one file. 8 kHz output is unchanged.

-b KBPS sets the Opus bitrate (6-256 kbps per channel) and -C LEVEL the
libsndfile compression level. 8 kHz only data formats are rejected at
16 kHz, where mono defaults to MS ADPCM instead of GSM.

Also fix generate_silence() advancing the G.729 output pointer twice too
far, and the "written" count for mono output.
Run extractaudio1 at 16 kHz too (wav, raw, w64, -n) and check that
narrowband output decimates exactly to the 8 kHz output. Add mixed_codec,
spliced from existing recordings to switch codecs mid-leg, the encopts
test for -b/-C, and invalid -r/-b/-C cases to badargs.
@sobomax
sobomax force-pushed the extractaudio-rate-bitrate branch from f56e65c to 7baf3e5 Compare October 17, 2026 03:39
libsndfile only enables Ogg/Vorbis/FLAC/Opus when it finds their headers,
which extractaudio/encopts needs. Install them and stop if libsndfile
still builds without.
@sobomax
sobomax merged commit 7e65396 into sippy:master Oct 17, 2026
93 checks passed
@sobomax

sobomax commented Oct 17, 2026

Copy link
Copy Markdown
Member

@bugtoo thanks for the PR — merged into master. Before merging I made some changes on top of your patches and squashed them into your two commits, so the history still shows you as author. Here's what changed:

Decoder

  • Upsampler alignment. The upsampler delayed narrowband audio by 1 ms, while G.722 at 16 kHz had no delay. That put a G.722 leg and a narrowband leg 1 ms apart in stereo. Its history also carried across codec switches within a leg, so a G.722 → PCMU switch played back old samples and dropped the last 1 ms. It now has no delay and restarts on each switch, so N samples at 8 kHz always become exactly 2N samples at 16 kHz.
  • 8 kHz regression. Enlarging obuf for 16 kHz also raised the per-packet decode limit at 8 kHz. Oversized garbage packets marked as G.722 were therefore decoded further than before, which changed the rtp_analyze/sipit31 checksum. Decoding is now capped at DECODER_MAX_TICKS per packet at either rate, so 8 kHz output is byte-identical to before.
  • Buffer sizes now come from DECODER_MAX_TICKS and DECODER_MAX_RMULT. The old literal 4000-tick cap on inserted silence is now DECODER_MAX_GAP, with a compile-time check that it fits the buffers.

Options

  • -b is checked against libsndfile's Opus range of 6–256 kbps per channel. Previously a value outside it was silently clamped, e.g. mono -b 512 gave 256 kbps.
  • -b and -C reject nan and empty values. strtod() accepts them, and the option was then silently ignored.
  • -r 16000 rejects 8 kHz-only data formats (GSM 06.10, G.721, G.723). Without -D, mono output at 16 kHz defaults to MS ADPCM instead of GSM 06.10.
  • -C on a data format without compression levels is now an error instead of a warning, and the half-written output file is removed.

Tests

  • extractaudio1: extractaudio16k is merged into it. The 16 kHz run now also covers raw, w64 and -n. It also checks that for every narrowband recording, each channel of the 16 kHz output, taken every other sample, matches the 8 kHz output exactly.
  • mixed_codec: a new recording spliced from existing ones. One leg switches G.722 → PCMA → G.722 within one SSRC, and the other leg is PCMU, so it covers codec switches and mixed-rate stereo.
  • encopts: a new test checking that -b gives the requested Opus bitrate within 25% at both rates, mono and stereo, and that a higher -C gives a smaller file. It requires a libsndfile with Opus support.
  • badargs: the -r/-b/-C cases now run against a real recording. With /dev/null as input they would have failed even if the bad value had been accepted. Each case must also leave no output file behind.

CI

  • The test jobs build libsndfile from source. It previously built without Ogg/Vorbis/FLAC/Opus because their development packages weren't installed. The packages are now installed, and the build stops if libsndfile still comes out without them.

@bugtoo

bugtoo commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @sobomax for fixing my PR and merging it!

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