Helpers for signed 16-bit little-endian interleaved PCM, the format
MobMedia.audio_pcm/2 returns, plus the reference conversion the Android
decoder implements and MobMedia.Engine.Fake uses.
Conversion
convert/5 maps channels, then resamples each channel:
- Channels. Same count: unchanged. To mono: the average of every channel (truncated). Mono to stereo: the sample on both sides. More than two channels to stereo: the first two (front left, front right).
- Downsampling (
in_rate > out_rate): output framekis the average of input framesfloor(k * in / out)up to, not including,floor((k + 1) * in / out): a box filter, which is enough of a low-pass for speech recognition. A box is emitted once all of its input frames exist. - Upsampling (
in_rate < out_rate): output frameksits at input positionk * in / outand is linearly interpolated between its two neighbours (the last input frame stands alone), rounded half up. Emitted while that position is inside the input.
So n input frames give about n * out / in output frames (within one).
iOS uses Core Audio's own converter instead, so its samples differ
slightly; the length is the same within a few milliseconds.
This is integer arithmetic only, so a streaming implementation (the Kotlin bridge converts one decoder buffer at a time) produces exactly the same samples as this one.
Summary
Functions
Convert pcm from in_rate Hz / in_channels to out_rate Hz /
out_channels (1 or 2). See the moduledoc for the algorithm. A trailing
partial frame is dropped.
Length of pcm in milliseconds at rate Hz and channels channels (rounded down).
Parse a WAV file holding 16-bit integer PCM (format 1, or
WAVE_FORMAT_EXTENSIBLE with the PCM subformat).
{:error, :unsupported} for anything else.
A WAV file (RIFF, 16-bit PCM) holding pcm at rate Hz with channels
channels.
Functions
@spec convert(binary(), pos_integer(), pos_integer(), pos_integer(), 1 | 2) :: binary()
Convert pcm from in_rate Hz / in_channels to out_rate Hz /
out_channels (1 or 2). See the moduledoc for the algorithm. A trailing
partial frame is dropped.
@spec duration_ms(binary(), pos_integer(), pos_integer()) :: non_neg_integer()
Length of pcm in milliseconds at rate Hz and channels channels (rounded down).
@spec parse_wav(binary()) :: {:ok, %{rate: pos_integer(), channels: pos_integer(), pcm: binary()}} | {:error, :unsupported}
Parse a WAV file holding 16-bit integer PCM (format 1, or
WAVE_FORMAT_EXTENSIBLE with the PCM subformat).
{:error, :unsupported} for anything else.
@spec wav(binary(), pos_integer(), pos_integer()) :: binary()
A WAV file (RIFF, 16-bit PCM) holding pcm at rate Hz with channels
channels.