Skip to main content

active_call/media/
loader.rs

1use crate::media::cache;
2use anyhow::{Result, anyhow};
3use audio_codec::Resampler;
4use audio_codec::opus::OpusDecoder;
5use hound::WavReader;
6use ogg::reading::PacketReader;
7use reqwest::Client;
8use std::fs::File;
9use std::io::{BufReader, Seek, SeekFrom, Write};
10use std::time::Instant;
11use symphonia::core::audio::SampleBuffer;
12use symphonia::core::codecs::DecoderOptions;
13use symphonia::core::errors::Error as SymphoniaError;
14use symphonia::core::formats::FormatOptions;
15use symphonia::core::io::MediaSourceStream;
16use symphonia::core::meta::MetadataOptions;
17use symphonia::core::probe::Hint;
18use symphonia::default::{get_codecs, get_probe};
19use tracing::{info, warn};
20use url::Url;
21
22pub async fn download_from_url(url: &str, use_cache: bool) -> Result<(File, Option<String>)> {
23    let cache_key = cache::generate_cache_key(url, 0, None, None);
24    if use_cache && cache::is_cached(&cache_key).await? {
25        match cache::get_cache_path(&cache_key) {
26            Ok(path) => return Ok((File::open(&path).map_err(|e| anyhow!(e))?, None)),
27            Err(e) => {
28                warn!("loader: Error getting cache path: {}", e);
29                return Err(e);
30            }
31        }
32    }
33
34    let start_time = Instant::now();
35    let client = Client::new();
36    let response = client.get(url).send().await?;
37    let content_type = response
38        .headers()
39        .get(reqwest::header::CONTENT_TYPE)
40        .and_then(|v| v.to_str().ok())
41        .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
42    let bytes = response.bytes().await?;
43    let data = bytes.to_vec();
44    let duration = start_time.elapsed();
45
46    info!(
47        "loader: Downloaded {} bytes in {:?} for {} (content-type: {:?})",
48        data.len(),
49        duration,
50        url,
51        content_type,
52    );
53
54    if use_cache {
55        cache::store_in_cache(&cache_key, &data).await?;
56        match cache::get_cache_path(&cache_key) {
57            Ok(path) => return Ok((File::open(path).map_err(|e| anyhow!(e))?, content_type)),
58            Err(e) => {
59                warn!("loader: Error getting cache path: {}", e);
60                return Err(e);
61            }
62        }
63    }
64
65    let mut temp_file = tempfile::tempfile()?;
66    temp_file.write_all(&data)?;
67    temp_file.seek(SeekFrom::Start(0))?;
68    Ok((temp_file, content_type))
69}
70
71fn is_ogg(extension: &str, mime_type: Option<&str>) -> bool {
72    matches!(extension, "ogg" | "opus")
73        || matches!(
74            mime_type,
75            Some("audio/ogg") | Some("audio/opus") | Some("application/ogg")
76        )
77}
78
79enum OggCodec {
80    Opus { channels: u16 },
81    Other,
82}
83
84fn detect_ogg_codec(file: &mut File) -> Result<OggCodec> {
85    let mut reader = PacketReader::new(BufReader::new(&mut *file));
86    let head = reader
87        .read_packet_expected()
88        .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
89    let codec = if head.data.starts_with(b"OpusHead") {
90        let channels = if head.data.len() > 9 {
91            head.data[9] as u16
92        } else {
93            2
94        };
95        OggCodec::Opus { channels }
96    } else {
97        OggCodec::Other
98    };
99    file.seek(SeekFrom::Start(0))?;
100    Ok(codec)
101}
102
103fn decode_opus_ogg(file: File, channels: u16, target_sample_rate: u32) -> Result<Vec<i16>> {
104    let mut reader = PacketReader::new(BufReader::new(file));
105
106    // Consume OpusHead (already peeked, but file was seeked back)
107    let head = reader
108        .read_packet_expected()
109        .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
110    let channels = if head.data.len() > 9 {
111        head.data[9] as u16
112    } else {
113        channels
114    };
115
116    // Skip OpusTags packet
117    reader
118        .read_packet_expected()
119        .map_err(|e| anyhow!("loader: failed reading OpusTags: {e}"))?;
120
121    // Opus always encodes at 48 kHz; decode there and resample afterwards
122    let mut decoder = OpusDecoder::new(48000, channels);
123    let mut all_samples: Vec<i16> = Vec::new();
124
125    loop {
126        let packet = match reader.read_packet() {
127            Ok(Some(p)) => p,
128            Ok(None) => break,
129            Err(e) => return Err(anyhow!("loader: failed reading OGG packet: {e}")),
130        };
131        let samples = audio_codec::Decoder::decode(&mut decoder, &packet.data);
132        all_samples.extend_from_slice(&samples);
133    }
134
135    if all_samples.is_empty() {
136        return Err(anyhow!(
137            "loader: no decodable audio samples found in Opus stream"
138        ));
139    }
140
141    info!(
142        "loader: decoded Opus stream at 48000 Hz, {} channel(s)",
143        channels
144    );
145
146    if target_sample_rate != 48000 {
147        let mut resampler = Resampler::new(48000, target_sample_rate as usize);
148        all_samples = resampler.resample(&all_samples);
149    }
150
151    Ok(all_samples)
152}
153
154pub fn decode_wav(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
155    let reader = BufReader::new(file);
156    let mut wav_reader = WavReader::new(reader)?;
157    let spec = wav_reader.spec();
158    let sample_rate = spec.sample_rate;
159    let is_stereo = spec.channels == 2;
160
161    info!(
162        "WAV file detected with sample rate: {} Hz, channels: {}, bits: {}",
163        sample_rate, spec.channels, spec.bits_per_sample
164    );
165
166    let mut all_samples = Vec::new();
167
168    // Read all samples based on format and bit depth
169    match spec.sample_format {
170        hound::SampleFormat::Int => match spec.bits_per_sample {
171            16 => {
172                for sample in wav_reader.samples::<i16>() {
173                    if let Ok(s) = sample {
174                        all_samples.push(s);
175                    } else {
176                        break;
177                    }
178                }
179            }
180            8 => {
181                for sample in wav_reader.samples::<i8>() {
182                    if let Ok(s) = sample {
183                        all_samples.push((s as i16) * 256); // Convert 8-bit to 16-bit
184                    } else {
185                        break;
186                    }
187                }
188            }
189            24 | 32 => {
190                for sample in wav_reader.samples::<i32>() {
191                    if let Ok(s) = sample {
192                        all_samples.push((s >> 16) as i16); // Convert 24/32-bit to 16-bit
193                    } else {
194                        break;
195                    }
196                }
197            }
198            _ => {
199                return Err(anyhow!(
200                    "Unsupported bits per sample: {}",
201                    spec.bits_per_sample
202                ));
203            }
204        },
205        hound::SampleFormat::Float => {
206            for sample in wav_reader.samples::<f32>() {
207                if let Ok(s) = sample {
208                    all_samples.push((s * 32767.0) as i16); // Convert float to 16-bit
209                } else {
210                    break;
211                }
212            }
213        }
214    }
215
216    // Convert stereo to mono if needed
217    if is_stereo {
218        let mono_samples = all_samples
219            .chunks(2)
220            .map(|chunk| {
221                if chunk.len() == 2 {
222                    ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16
223                } else {
224                    chunk[0]
225                }
226            })
227            .collect();
228        all_samples = mono_samples;
229    }
230
231    if sample_rate != target_sample_rate && sample_rate > 0 {
232        let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
233        all_samples = resampler.resample(&all_samples);
234    }
235
236    Ok(all_samples)
237}
238
239pub fn decode_audio(
240    mut file: File,
241    extension: &str,
242    mime_type: Option<&str>,
243    target_sample_rate: u32,
244) -> Result<Vec<i16>> {
245    if matches!(extension, "wav")
246        || matches!(
247            mime_type,
248            Some("audio/wav") | Some("audio/wave") | Some("audio/x-wav")
249        )
250    {
251        return decode_wav(file, target_sample_rate);
252    }
253
254    if is_ogg(extension, mime_type) {
255        match detect_ogg_codec(&mut file)? {
256            OggCodec::Opus { channels } => {
257                return decode_opus_ogg(file, channels, target_sample_rate);
258            }
259            OggCodec::Other => {} // fall through to symphonia (e.g. Vorbis)
260        }
261    }
262
263    let mss = MediaSourceStream::new(Box::new(file), Default::default());
264    let mut hint = Hint::new();
265    if !extension.is_empty() {
266        hint.with_extension(extension);
267    }
268    if let Some(mime) = mime_type {
269        hint.mime_type(mime);
270    }
271
272    let probed = get_probe().format(
273        &hint,
274        mss,
275        &FormatOptions::default(),
276        &MetadataOptions::default(),
277    )?;
278
279    let mut format = probed.format;
280    let (track_id, codec_params) = {
281        let track = format
282            .default_track()
283            .ok_or_else(|| anyhow!("loader: no default audio track found"))?;
284        (track.id, track.codec_params.clone())
285    };
286
287    let mut decoder = get_codecs().make(&codec_params, &DecoderOptions::default())?;
288    let mut all_samples = Vec::new();
289    let mut sample_rate = codec_params.sample_rate.unwrap_or(0);
290
291    loop {
292        let packet = match format.next_packet() {
293            Ok(packet) => packet,
294            Err(SymphoniaError::IoError(_)) => break,
295            Err(SymphoniaError::ResetRequired) => continue,
296            Err(e) => return Err(anyhow!("loader: failed reading audio packet: {e}")),
297        };
298
299        if packet.track_id() != track_id {
300            continue;
301        }
302
303        match decoder.decode(&packet) {
304            Ok(decoded) => {
305                if sample_rate == 0 {
306                    sample_rate = decoded.spec().rate;
307                    info!(
308                        "loader: detected {:?} with sample rate: {} Hz, channels: {}",
309                        codec_params.codec,
310                        sample_rate,
311                        decoded.spec().channels.count()
312                    );
313                }
314                let spec = *decoded.spec();
315                let channels = spec.channels.count();
316
317                let mut sample_buffer = SampleBuffer::<i16>::new(decoded.capacity() as u64, spec);
318                sample_buffer.copy_interleaved_ref(decoded);
319                let interleaved = sample_buffer.samples();
320
321                if channels <= 1 {
322                    all_samples.extend_from_slice(interleaved);
323                } else {
324                    for frame in interleaved.chunks(channels) {
325                        if frame.is_empty() {
326                            continue;
327                        }
328                        let sum: i32 = frame.iter().map(|s| *s as i32).sum();
329                        all_samples.push((sum / frame.len() as i32) as i16);
330                    }
331                }
332            }
333            Err(SymphoniaError::DecodeError(_)) => continue,
334            Err(SymphoniaError::IoError(_)) => break,
335            Err(SymphoniaError::ResetRequired) => continue,
336            Err(e) => return Err(anyhow!("loader: failed decoding audio packet: {e}")),
337        }
338    }
339
340    if all_samples.is_empty() {
341        return Err(anyhow!("loader: no decodable audio samples found"));
342    }
343
344    if sample_rate != target_sample_rate && sample_rate > 0 {
345        let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
346        all_samples = resampler.resample(&all_samples);
347    }
348
349    Ok(all_samples)
350}
351
352pub async fn load_audio_as_pcm(
353    path: &str,
354    target_sample_rate: u32,
355    use_cache: bool,
356) -> Result<Vec<i16>> {
357    let is_url = path.starts_with("http://") || path.starts_with("https://");
358
359    let (file, content_type) = if is_url {
360        download_from_url(path, use_cache).await?
361    } else {
362        (File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?, None)
363    };
364
365    let extension = if is_url {
366        path.parse::<Url>()?.path().split('.').last().unwrap_or("").to_string()
367    } else {
368        path.split('.').last().unwrap_or("").to_string()
369    };
370
371    decode_audio(
372        file,
373        &extension,
374        content_type.as_deref(),
375        target_sample_rate,
376    )
377}