#[cfg(feature = "file-decode")]
use anyhow::Context;
#[cfg(feature = "file-decode")]
use anyhow::Result;
#[cfg(feature = "file-decode")]
use super::resample::{RESAMPLE_STAGING_FRAMES, ResampleTo16k, SampleRate};
#[cfg(feature = "file-decode")]
use super::{
WHOLE_BUFFER_MAX_AUDIO_SECS, audio_too_long_err, resolve_budget, whole_buffer_limit_secs,
};
#[cfg(feature = "file-decode")]
const WAV_FORMAT_TAGS_G722_ADPCM: [u16; 2] = [0x0064, 0x028F];
#[cfg(feature = "file-decode")]
const WAV_SNIFF_WINDOW: usize = 512;
#[cfg(feature = "file-decode")]
pub(super) fn sniff_wav_g722_tag(window: &[u8]) -> Option<bool> {
if window.len() < 12 || &window[0..4] != b"RIFF" || &window[8..12] != b"WAVE" {
return None;
}
let mut pos = 12usize;
while pos + 8 <= window.len() {
let id = &window[pos..pos + 4];
let size = u32::from_le_bytes([
window[pos + 4],
window[pos + 5],
window[pos + 6],
window[pos + 7],
]) as usize;
let start = pos + 8;
if id == b"fmt " {
if size < 2 || start + 2 > window.len() {
return None;
}
let tag = u16::from_le_bytes([window[start], window[start + 1]]);
return Some(WAV_FORMAT_TAGS_G722_ADPCM.contains(&tag));
}
pos = start.saturating_add(size).saturating_add(size & 1);
}
None
}
#[cfg(feature = "file-decode")]
pub(super) fn find_riff_chunk<'a>(data: &'a [u8], want: &[u8; 4]) -> Option<&'a [u8]> {
if data.len() < 12 {
return None;
}
let mut pos = 12usize;
while pos + 8 <= data.len() {
let id = &data[pos..pos + 4];
let size = u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
as usize;
let start = pos + 8;
let end = start.saturating_add(size).min(data.len());
if id == want {
return Some(&data[start..end]);
}
pos = start.saturating_add(size).saturating_add(size & 1);
}
None
}
#[cfg(feature = "file-decode")]
pub(super) fn sniffs_as_g722_wav(path: &str) -> Result<bool> {
use std::io::Read as _;
let mut file =
std::fs::File::open(path).with_context(|| format!("Failed to open audio file: {path}"))?;
let mut window = [0u8; WAV_SNIFF_WINDOW];
let mut read = 0usize;
while read < window.len() {
match file.read(&mut window[read..]) {
Ok(0) => break,
Ok(n) => read += n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => {
return Err(e).with_context(|| format!("Failed to read audio file: {path}"));
}
}
}
Ok(sniff_wav_g722_tag(&window[..read]) == Some(true))
}
#[cfg(feature = "file-decode")]
pub(crate) fn try_decode_g722_wav(
data: &[u8],
max_audio_secs: Option<f64>,
) -> Option<Result<Vec<f32>>> {
if sniff_wav_g722_tag(data) != Some(true) {
return None;
}
let payload = match find_riff_chunk(data, b"data") {
Some(p) if !p.is_empty() => p,
_ => return Some(Err(anyhow::anyhow!("G.722 WAV has no data chunk"))),
};
let num_samples = payload.len().saturating_mul(2);
let (max_samples, limit_secs) =
resolve_budget(Some(whole_buffer_limit_secs(max_audio_secs)), 16000);
if num_samples > max_samples {
return Some(Err(audio_too_long_err(num_samples, 16000, limit_secs)));
}
let mut decoder = audio_codec::g722::G722Decoder::new();
let pcm = audio_codec::Decoder::decode(&mut decoder, payload);
tracing::info!(
"Decoded G.722 WAV: {} samples at 16000Hz ({:.1}s)",
pcm.len(),
pcm.len() as f64 / 16000.0
);
Some(Ok(pcm.iter().map(|&s| f32::from(s) / 32768.0).collect()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TelephonyCodec {
Pcmu,
Pcma,
G722,
}
impl TelephonyCodec {
pub fn from_name(name: &str) -> Option<Self> {
match name.to_ascii_lowercase().as_str() {
"pcmu" | "ulaw" => Some(Self::Pcmu),
"pcma" | "alaw" => Some(Self::Pcma),
"g722" => Some(Self::G722),
_ => None,
}
}
pub fn validate_sample_rate(self, sample_rate: u32) -> Result<(), String> {
match self {
Self::G722 if sample_rate != 8000 && sample_rate != 16000 => Err(format!(
"g722 decodes to 16 kHz natively; sample_rate must be 8000 (SDP convention) or 16000, got {sample_rate}"
)),
Self::Pcmu | Self::Pcma if !(8000..=48000).contains(&sample_rate) => Err(format!(
"sample_rate must be within 8000..=48000 Hz for raw G.711, got {sample_rate}"
)),
_ => Ok(()),
}
}
}
#[cfg(feature = "file-decode")]
pub fn decode_telephony_raw(
data: &[u8],
codec: TelephonyCodec,
sample_rate: u32,
) -> Result<Vec<f32>> {
if data.is_empty() {
anyhow::bail!("Empty audio payload");
}
codec
.validate_sample_rate(sample_rate)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let (pcm, rate) = match codec {
TelephonyCodec::Pcmu => {
let mut decoder = audio_codec::pcmu::PcmuDecoder::new();
(
audio_codec::Decoder::decode(&mut decoder, data),
sample_rate,
)
}
TelephonyCodec::Pcma => {
let mut decoder = audio_codec::pcma::PcmaDecoder::new();
(
audio_codec::Decoder::decode(&mut decoder, data),
sample_rate,
)
}
TelephonyCodec::G722 => {
let mut decoder = audio_codec::g722::G722Decoder::new();
(audio_codec::Decoder::decode(&mut decoder, data), 16000)
}
};
let (max_samples, limit_secs) = resolve_budget(Some(WHOLE_BUFFER_MAX_AUDIO_SECS), rate);
if pcm.len() > max_samples {
return Err(audio_too_long_err(pcm.len(), rate, limit_secs));
}
let mut acc = ResampleTo16k::new(SampleRate(rate), Some(pcm.len()));
for piece in pcm.chunks(RESAMPLE_STAGING_FRAMES) {
acc.stage()
.extend(piece.iter().map(|&s| f32::from(s) / 32768.0));
acc.flush_full()?;
}
acc.finish()
}
#[cfg(feature = "file-decode")]
pub fn encode_wav_pcm16(samples: &[f32], sample_rate: u32) -> Vec<u8> {
let data_size = (samples.len() * 2) as u32;
let mut buf = Vec::with_capacity(44 + data_size as usize);
buf.extend_from_slice(b"RIFF");
buf.extend_from_slice(&(36 + data_size).to_le_bytes());
buf.extend_from_slice(b"WAVE");
buf.extend_from_slice(b"fmt ");
buf.extend_from_slice(&16u32.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&sample_rate.to_le_bytes());
buf.extend_from_slice(&(sample_rate * 2).to_le_bytes()); buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&16u16.to_le_bytes()); buf.extend_from_slice(b"data");
buf.extend_from_slice(&data_size.to_le_bytes());
for &s in samples {
let v = if s.is_finite() {
s.clamp(-1.0, 1.0)
} else {
0.0
};
buf.extend_from_slice(&((v * 32767.0).round() as i16).to_le_bytes());
}
buf
}