active-call 0.3.57

A SIP/WebRTC voice agent
Documentation
use crate::media::cache;
use anyhow::{Result, anyhow};
use audio_codec::Resampler;
use hound::WavReader;
use reqwest::Client;
use std::fs::File;
use std::io::{BufReader, Seek, SeekFrom, Write};
use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::DecoderOptions;
use symphonia::core::errors::Error as SymphoniaError;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use symphonia::default::{get_codecs, get_probe};
use std::time::Instant;
use tracing::{info, warn};
use url::Url;

pub async fn download_from_url(url: &str, use_cache: bool) -> Result<File> {
    // Check if file is already cached
    let cache_key = cache::generate_cache_key(url, 0, None, None);
    if use_cache && cache::is_cached(&cache_key).await? {
        match cache::get_cache_path(&cache_key) {
            Ok(path) => return File::open(&path).map_err(|e| anyhow!(e)),
            Err(e) => {
                warn!("loader: Error getting cache path: {}", e);
                return Err(e);
            }
        }
    }

    // Download file if not cached
    let start_time = Instant::now();
    let client = Client::new();
    let response = client.get(url).send().await?;
    let bytes = response.bytes().await?;
    let data = bytes.to_vec();
    let duration = start_time.elapsed();

    info!(
        "loader: Downloaded {} bytes in {:?} for {}",
        data.len(),
        duration,
        url,
    );

    // Store in cache if enabled
    if use_cache {
        cache::store_in_cache(&cache_key, &data).await?;
        match cache::get_cache_path(&cache_key) {
            Ok(path) => return File::open(path).map_err(|e| anyhow!(e)),
            Err(e) => {
                warn!("loader: Error getting cache path: {}", e);
                return Err(e);
            }
        }
    }

    // Return temporary file with downloaded data
    let mut temp_file = tempfile::tempfile()?;
    temp_file.write_all(&data)?;
    temp_file.seek(SeekFrom::Start(0))?;
    Ok(temp_file)
}

pub fn decode_wav(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
    let reader = BufReader::new(file);
    let mut wav_reader = WavReader::new(reader)?;
    let spec = wav_reader.spec();
    let sample_rate = spec.sample_rate;
    let is_stereo = spec.channels == 2;

    info!(
        "WAV file detected with sample rate: {} Hz, channels: {}, bits: {}",
        sample_rate, spec.channels, spec.bits_per_sample
    );

    let mut all_samples = Vec::new();

    // Read all samples based on format and bit depth
    match spec.sample_format {
        hound::SampleFormat::Int => match spec.bits_per_sample {
            16 => {
                for sample in wav_reader.samples::<i16>() {
                    if let Ok(s) = sample {
                        all_samples.push(s);
                    } else {
                        break;
                    }
                }
            }
            8 => {
                for sample in wav_reader.samples::<i8>() {
                    if let Ok(s) = sample {
                        all_samples.push((s as i16) * 256); // Convert 8-bit to 16-bit
                    } else {
                        break;
                    }
                }
            }
            24 | 32 => {
                for sample in wav_reader.samples::<i32>() {
                    if let Ok(s) = sample {
                        all_samples.push((s >> 16) as i16); // Convert 24/32-bit to 16-bit
                    } else {
                        break;
                    }
                }
            }
            _ => {
                return Err(anyhow!(
                    "Unsupported bits per sample: {}",
                    spec.bits_per_sample
                ));
            }
        },
        hound::SampleFormat::Float => {
            for sample in wav_reader.samples::<f32>() {
                if let Ok(s) = sample {
                    all_samples.push((s * 32767.0) as i16); // Convert float to 16-bit
                } else {
                    break;
                }
            }
        }
    }

    // Convert stereo to mono if needed
    if is_stereo {
        let mono_samples = all_samples
            .chunks(2)
            .map(|chunk| {
                if chunk.len() == 2 {
                    ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16
                } else {
                    chunk[0]
                }
            })
            .collect();
        all_samples = mono_samples;
    }

    if sample_rate != target_sample_rate && sample_rate > 0 {
        let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
        all_samples = resampler.resample(&all_samples);
    }

    Ok(all_samples)
}

pub fn decode_mp3(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
    let mss = MediaSourceStream::new(Box::new(file), Default::default());
    let mut hint = Hint::new();
    hint.with_extension("mp3");

    let probed = get_probe().format(
        &hint,
        mss,
        &FormatOptions::default(),
        &MetadataOptions::default(),
    )?;

    let mut format = probed.format;
    let (track_id, codec_params) = {
        let track = format
            .default_track()
            .ok_or_else(|| anyhow!("loader: no default audio track found in mp3"))?;
        (track.id, track.codec_params.clone())
    };

    let mut decoder = get_codecs().make(&codec_params, &DecoderOptions::default())?;
    let mut all_samples = Vec::new();
    let mut sample_rate = codec_params.sample_rate.unwrap_or(0);

    loop {
        let packet = match format.next_packet() {
            Ok(packet) => packet,
            Err(SymphoniaError::IoError(_)) => break,
            Err(SymphoniaError::ResetRequired) => continue,
            Err(e) => return Err(anyhow!("loader: failed reading mp3 packet: {e}")),
        };

        if packet.track_id() != track_id {
            continue;
        }

        match decoder.decode(&packet) {
            Ok(decoded) => {
                if sample_rate == 0 {
                    sample_rate = decoded.spec().rate;
                    info!("MP3 file detected with sample rate: {} Hz", sample_rate);
                }
                let spec = *decoded.spec();
                let channels = spec.channels.count();

                let mut sample_buffer = SampleBuffer::<i16>::new(decoded.capacity() as u64, spec);
                sample_buffer.copy_interleaved_ref(decoded);
                let interleaved = sample_buffer.samples();

                if channels <= 1 {
                    all_samples.extend_from_slice(interleaved);
                } else {
                    for frame in interleaved.chunks(channels) {
                        if frame.is_empty() {
                            continue;
                        }
                        let sum: i32 = frame.iter().map(|s| *s as i32).sum();
                        all_samples.push((sum / frame.len() as i32) as i16);
                    }
                }
            }
            Err(SymphoniaError::DecodeError(_)) => continue,
            Err(SymphoniaError::IoError(_)) => break,
            Err(SymphoniaError::ResetRequired) => continue,
            Err(e) => return Err(anyhow!("loader: failed decoding mp3 packet: {e}")),
        }
    }

    if all_samples.is_empty() {
        return Err(anyhow!("loader: no decodable audio samples found in mp3"));
    }

    if sample_rate != target_sample_rate && sample_rate > 0 {
        let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
        all_samples = resampler.resample(&all_samples);
    }

    Ok(all_samples)
}

pub async fn load_audio_as_pcm(
    path: &str,
    target_sample_rate: u32,
    use_cache: bool,
) -> Result<Vec<i16>> {
    let extension = if path.starts_with("http://") || path.starts_with("https://") {
        path.parse::<Url>()?
            .path()
            .split(".")
            .last()
            .unwrap_or("")
            .to_string()
    } else {
        path.split('.').last().unwrap_or("").to_string()
    };

    let file = if path.starts_with("http://") || path.starts_with("https://") {
        download_from_url(path, use_cache).await?
    } else {
        File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?
    };

    match extension.to_lowercase().as_str() {
        "wav" => decode_wav(file, target_sample_rate),
        "mp3" => decode_mp3(file, target_sample_rate),
        _ => Err(anyhow!("loader: Unsupported file extension: {}", extension)),
    }
}