Skip to main content

ff_format/stream/
audio.rs

1//! Audio stream info and builder.
2
3use std::time::Duration;
4
5use crate::channel::ChannelLayout;
6use crate::codec::AudioCodec;
7use crate::sample::SampleFormat;
8
9/// Information about an audio stream within a media file.
10///
11/// This struct contains all metadata needed to understand and process
12/// an audio stream, including sample rate, channel layout, and codec
13/// information.
14///
15/// # Construction
16///
17/// Use [`AudioStreamInfo::builder()`] for fluent construction:
18///
19/// ```
20/// use ff_format::stream::AudioStreamInfo;
21/// use ff_format::SampleFormat;
22/// use ff_format::codec::AudioCodec;
23///
24/// let info = AudioStreamInfo::builder()
25///     .index(1)
26///     .codec(AudioCodec::Aac)
27///     .sample_rate(48000)
28///     .channels(2)
29///     .build();
30/// ```
31#[derive(Debug, Clone)]
32pub struct AudioStreamInfo {
33    /// Stream index within the container
34    index: u32,
35    /// Audio codec
36    codec: AudioCodec,
37    /// Codec name as reported by the demuxer
38    codec_name: String,
39    /// Sample rate in Hz
40    sample_rate: u32,
41    /// Number of channels
42    channels: u32,
43    /// Channel layout
44    channel_layout: ChannelLayout,
45    /// Sample format
46    sample_format: SampleFormat,
47    /// Stream duration (if known)
48    duration: Option<Duration>,
49    /// Bitrate in bits per second (if known)
50    bitrate: Option<u64>,
51    /// Language code (e.g., "eng", "jpn")
52    language: Option<String>,
53}
54
55impl AudioStreamInfo {
56    /// Creates a new builder for constructing `AudioStreamInfo`.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use ff_format::stream::AudioStreamInfo;
62    /// use ff_format::codec::AudioCodec;
63    /// use ff_format::SampleFormat;
64    ///
65    /// let info = AudioStreamInfo::builder()
66    ///     .index(1)
67    ///     .codec(AudioCodec::Aac)
68    ///     .sample_rate(48000)
69    ///     .channels(2)
70    ///     .build();
71    /// ```
72    #[must_use]
73    pub fn builder() -> AudioStreamInfoBuilder {
74        AudioStreamInfoBuilder::default()
75    }
76
77    /// Returns the stream index within the container.
78    #[must_use]
79    #[inline]
80    pub const fn index(&self) -> u32 {
81        self.index
82    }
83
84    /// Returns the audio codec.
85    #[must_use]
86    #[inline]
87    pub const fn codec(&self) -> AudioCodec {
88        self.codec
89    }
90
91    /// Returns the codec name as reported by the demuxer.
92    #[must_use]
93    #[inline]
94    pub fn codec_name(&self) -> &str {
95        &self.codec_name
96    }
97
98    /// Returns the sample rate in Hz.
99    #[must_use]
100    #[inline]
101    pub const fn sample_rate(&self) -> u32 {
102        self.sample_rate
103    }
104
105    /// Returns the number of audio channels.
106    ///
107    /// The type is `u32` to match `FFmpeg`'s `AVCodecParameters::ch_layout.nb_channels`
108    /// and professional audio APIs. When passing to `rodio` or `cpal` (which require
109    /// `u16`), cast with `info.channels() as u16` — channel counts never exceed
110    /// `u16::MAX` in practice.
111    #[must_use]
112    #[inline]
113    pub const fn channels(&self) -> u32 {
114        self.channels
115    }
116
117    /// Returns the channel layout.
118    #[must_use]
119    #[inline]
120    pub const fn channel_layout(&self) -> ChannelLayout {
121        self.channel_layout
122    }
123
124    /// Returns the sample format.
125    #[must_use]
126    #[inline]
127    pub const fn sample_format(&self) -> SampleFormat {
128        self.sample_format
129    }
130
131    /// Returns the stream duration, if known.
132    #[must_use]
133    #[inline]
134    pub const fn duration(&self) -> Option<Duration> {
135        self.duration
136    }
137
138    /// Returns the bitrate in bits per second, if known.
139    #[must_use]
140    #[inline]
141    pub const fn bitrate(&self) -> Option<u64> {
142        self.bitrate
143    }
144
145    /// Returns the language code, if specified.
146    #[must_use]
147    #[inline]
148    pub fn language(&self) -> Option<&str> {
149        self.language.as_deref()
150    }
151
152    /// Returns `true` if this is a mono stream.
153    #[must_use]
154    #[inline]
155    pub const fn is_mono(&self) -> bool {
156        self.channels == 1
157    }
158
159    /// Returns `true` if this is a stereo stream.
160    #[must_use]
161    #[inline]
162    pub const fn is_stereo(&self) -> bool {
163        self.channels == 2
164    }
165
166    /// Returns `true` if this is a surround sound stream (more than 2 channels).
167    #[must_use]
168    #[inline]
169    pub const fn is_surround(&self) -> bool {
170        self.channels > 2
171    }
172}
173
174impl Default for AudioStreamInfo {
175    fn default() -> Self {
176        Self {
177            index: 0,
178            codec: AudioCodec::default(),
179            codec_name: String::new(),
180            sample_rate: 48000,
181            channels: 2,
182            channel_layout: ChannelLayout::default(),
183            sample_format: SampleFormat::default(),
184            duration: None,
185            bitrate: None,
186            language: None,
187        }
188    }
189}
190
191/// Builder for constructing `AudioStreamInfo`.
192#[derive(Debug, Clone, Default)]
193pub struct AudioStreamInfoBuilder {
194    index: u32,
195    codec: AudioCodec,
196    codec_name: String,
197    sample_rate: u32,
198    channels: u32,
199    channel_layout: Option<ChannelLayout>,
200    sample_format: SampleFormat,
201    duration: Option<Duration>,
202    bitrate: Option<u64>,
203    language: Option<String>,
204}
205
206impl AudioStreamInfoBuilder {
207    /// Sets the stream index.
208    #[must_use]
209    pub fn index(mut self, index: u32) -> Self {
210        self.index = index;
211        self
212    }
213
214    /// Sets the audio codec.
215    #[must_use]
216    pub fn codec(mut self, codec: AudioCodec) -> Self {
217        self.codec = codec;
218        self
219    }
220
221    /// Sets the codec name string.
222    #[must_use]
223    pub fn codec_name(mut self, name: impl Into<String>) -> Self {
224        self.codec_name = name.into();
225        self
226    }
227
228    /// Sets the sample rate in Hz.
229    #[must_use]
230    pub fn sample_rate(mut self, rate: u32) -> Self {
231        self.sample_rate = rate;
232        self
233    }
234
235    /// Sets the number of channels.
236    ///
237    /// This also updates the channel layout if not explicitly set.
238    #[must_use]
239    pub fn channels(mut self, channels: u32) -> Self {
240        self.channels = channels;
241        self
242    }
243
244    /// Sets the channel layout explicitly.
245    #[must_use]
246    pub fn channel_layout(mut self, layout: ChannelLayout) -> Self {
247        self.channel_layout = Some(layout);
248        self
249    }
250
251    /// Sets the sample format.
252    #[must_use]
253    pub fn sample_format(mut self, format: SampleFormat) -> Self {
254        self.sample_format = format;
255        self
256    }
257
258    /// Sets the stream duration.
259    #[must_use]
260    pub fn duration(mut self, duration: Duration) -> Self {
261        self.duration = Some(duration);
262        self
263    }
264
265    /// Sets the bitrate in bits per second.
266    #[must_use]
267    pub fn bitrate(mut self, bitrate: u64) -> Self {
268        self.bitrate = Some(bitrate);
269        self
270    }
271
272    /// Sets the language code.
273    #[must_use]
274    pub fn language(mut self, lang: impl Into<String>) -> Self {
275        self.language = Some(lang.into());
276        self
277    }
278
279    /// Builds the `AudioStreamInfo`.
280    #[must_use]
281    pub fn build(self) -> AudioStreamInfo {
282        let channel_layout = self.channel_layout.unwrap_or_else(|| {
283            log::warn!(
284                "channel_layout not set, deriving from channel count \
285                 channels={} fallback=from_channels",
286                self.channels
287            );
288            ChannelLayout::from_channels(self.channels)
289        });
290
291        AudioStreamInfo {
292            index: self.index,
293            codec: self.codec,
294            codec_name: self.codec_name,
295            sample_rate: self.sample_rate,
296            channels: self.channels,
297            channel_layout,
298            sample_format: self.sample_format,
299            duration: self.duration,
300            bitrate: self.bitrate,
301            language: self.language,
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_builder_basic() {
312        let info = AudioStreamInfo::builder()
313            .index(1)
314            .codec(AudioCodec::Aac)
315            .codec_name("aac")
316            .sample_rate(48000)
317            .channels(2)
318            .sample_format(SampleFormat::F32)
319            .build();
320
321        assert_eq!(info.index(), 1);
322        assert_eq!(info.codec(), AudioCodec::Aac);
323        assert_eq!(info.codec_name(), "aac");
324        assert_eq!(info.sample_rate(), 48000);
325        assert_eq!(info.channels(), 2);
326        assert_eq!(info.sample_format(), SampleFormat::F32);
327        assert_eq!(info.channel_layout(), ChannelLayout::Stereo);
328    }
329
330    #[test]
331    fn test_builder_full() {
332        let info = AudioStreamInfo::builder()
333            .index(2)
334            .codec(AudioCodec::Flac)
335            .codec_name("flac")
336            .sample_rate(96000)
337            .channels(6)
338            .channel_layout(ChannelLayout::Surround5_1)
339            .sample_format(SampleFormat::I32)
340            .duration(Duration::from_secs(300))
341            .bitrate(1_411_200)
342            .language("jpn")
343            .build();
344
345        assert_eq!(info.codec(), AudioCodec::Flac);
346        assert_eq!(info.sample_rate(), 96000);
347        assert_eq!(info.channels(), 6);
348        assert_eq!(info.channel_layout(), ChannelLayout::Surround5_1);
349        assert_eq!(info.duration(), Some(Duration::from_secs(300)));
350        assert_eq!(info.bitrate(), Some(1_411_200));
351        assert_eq!(info.language(), Some("jpn"));
352    }
353
354    #[test]
355    fn test_default() {
356        let info = AudioStreamInfo::default();
357        assert_eq!(info.index(), 0);
358        assert_eq!(info.codec(), AudioCodec::default());
359        assert_eq!(info.sample_rate(), 48000);
360        assert_eq!(info.channels(), 2);
361        assert!(info.duration().is_none());
362    }
363
364    #[test]
365    fn test_auto_channel_layout() {
366        // Should auto-detect layout from channel count
367        let mono = AudioStreamInfo::builder().channels(1).build();
368        assert_eq!(mono.channel_layout(), ChannelLayout::Mono);
369
370        let stereo = AudioStreamInfo::builder().channels(2).build();
371        assert_eq!(stereo.channel_layout(), ChannelLayout::Stereo);
372
373        let surround = AudioStreamInfo::builder().channels(6).build();
374        assert_eq!(surround.channel_layout(), ChannelLayout::Surround5_1);
375
376        // Explicit layout should override
377        let custom = AudioStreamInfo::builder()
378            .channels(6)
379            .channel_layout(ChannelLayout::Other(6))
380            .build();
381        assert_eq!(custom.channel_layout(), ChannelLayout::Other(6));
382    }
383
384    #[test]
385    fn test_channel_checks() {
386        let mono = AudioStreamInfo::builder().channels(1).build();
387        assert!(mono.is_mono());
388        assert!(!mono.is_stereo());
389        assert!(!mono.is_surround());
390
391        let stereo = AudioStreamInfo::builder().channels(2).build();
392        assert!(!stereo.is_mono());
393        assert!(stereo.is_stereo());
394        assert!(!stereo.is_surround());
395
396        let surround = AudioStreamInfo::builder().channels(6).build();
397        assert!(!surround.is_mono());
398        assert!(!surround.is_stereo());
399        assert!(surround.is_surround());
400    }
401
402    #[test]
403    fn test_debug() {
404        let info = AudioStreamInfo::builder()
405            .index(1)
406            .codec(AudioCodec::Aac)
407            .sample_rate(48000)
408            .channels(2)
409            .build();
410        let debug = format!("{info:?}");
411        assert!(debug.contains("AudioStreamInfo"));
412        assert!(debug.contains("48000"));
413    }
414
415    #[test]
416    fn test_clone() {
417        let info = AudioStreamInfo::builder()
418            .index(1)
419            .codec(AudioCodec::Aac)
420            .codec_name("aac")
421            .sample_rate(48000)
422            .channels(2)
423            .language("eng")
424            .build();
425        let cloned = info.clone();
426        assert_eq!(info.sample_rate(), cloned.sample_rate());
427        assert_eq!(info.channels(), cloned.channels());
428        assert_eq!(info.language(), cloned.language());
429        assert_eq!(info.codec_name(), cloned.codec_name());
430    }
431}