Skip to main content

bliss_audio/song/decoder/
ffmpeg.rs

1//! The default decoder module. It uses [ffmpeg](https://ffmpeg.org/) in
2//! order to decode and resample songs. A very good choice for most users.
3
4use crate::decoder::{Decoder, PreAnalyzedSong};
5use crate::{BlissError, BlissResult, CHANNELS, SAMPLE_RATE};
6use ::log::warn;
7use ffmpeg_next;
8use ffmpeg_next::codec::threading::{Config, Type as ThreadingType};
9use ffmpeg_next::util::channel_layout::ChannelLayout;
10use ffmpeg_next::util::error::Error;
11use ffmpeg_next::util::error::EINVAL;
12use ffmpeg_next::util::format::sample::{Sample, Type};
13use ffmpeg_next::util::frame::audio::Audio;
14use ffmpeg_next::util::log;
15use ffmpeg_next::util::log::level::Level;
16use ffmpeg_next::{media, util};
17use std::sync::mpsc;
18use std::sync::mpsc::Receiver;
19use std::thread;
20use std::time::Duration;
21
22use std::path::Path;
23
24/// The actual FFmpeg decoder.
25///
26/// To use it, one might write `use FFmpegDecoder as Decoder;`,
27/// `use super::decoder::Decoder as DecoderTrait;`, and then use
28/// `Decoder::song_from_path`
29pub struct FFmpegDecoder;
30
31struct SendChannelLayout(ChannelLayout);
32// Safe because the other thread just reads the channel layout
33unsafe impl Send for SendChannelLayout {}
34
35impl FFmpegDecoder {
36    fn resample_frame(
37        rx: Receiver<Audio>,
38        in_codec_format: Sample,
39        sent_in_channel_layout: SendChannelLayout,
40        in_rate: u32,
41        mut sample_array: Vec<f32>,
42        empty_in_channel_layout: bool,
43    ) -> BlissResult<Vec<f32>> {
44        let in_channel_layout = sent_in_channel_layout.0;
45        let mut resample_context = ffmpeg_next::software::resampling::context::Context::get(
46            in_codec_format,
47            in_channel_layout,
48            in_rate,
49            Sample::F32(Type::Packed),
50            ffmpeg_next::util::channel_layout::ChannelLayout::MONO,
51            SAMPLE_RATE,
52        )
53        .map_err(|e| {
54            BlissError::DecodingError(format!(
55                "while trying to allocate resampling context: {e:?}",
56            ))
57        })?;
58
59        let mut resampled = ffmpeg_next::frame::Audio::empty();
60        let mut something_happened = false;
61        for mut decoded in rx.iter() {
62            #[cfg(not(feature = "ffmpeg_7_0"))]
63            let is_channel_layout_empty = decoded.channel_layout() == ChannelLayout::empty();
64            #[cfg(feature = "ffmpeg_7_0")]
65            let is_channel_layout_empty = decoded.channel_layout().is_empty();
66
67            // If the decoded layout is empty, it means we forced the
68            // "in_channel_layout" to something default, not that
69            // the format is wrong.
70            if empty_in_channel_layout && is_channel_layout_empty {
71                decoded.set_channel_layout(in_channel_layout);
72            } else if in_codec_format != decoded.format()
73                || (in_channel_layout != decoded.channel_layout())
74                || in_rate != decoded.rate()
75            {
76                warn!("received decoded packet with wrong format; file might be corrupted.");
77                continue;
78            }
79            something_happened = true;
80            resampled = ffmpeg_next::frame::Audio::empty();
81            resample_context
82                .run(&decoded, &mut resampled)
83                .map_err(|e| {
84                    BlissError::DecodingError(format!("while trying to resample song: {e:?}"))
85                })?;
86            FFmpegDecoder::push_to_sample_array(&resampled, &mut sample_array);
87        }
88        if !something_happened {
89            return Ok(sample_array);
90        }
91        // TODO when ffmpeg-next will be active again: shouldn't we allocate
92        // `resampled` again?
93        while resample_context
94            .flush(&mut resampled)
95            .map_err(|e| {
96                BlissError::DecodingError(format!("while trying to resample song: {e:?}"))
97            })?
98            .is_some()
99        {
100            if resampled.samples() == 0 {
101                break;
102            }
103            FFmpegDecoder::push_to_sample_array(&resampled, &mut sample_array);
104        }
105        Ok(sample_array)
106    }
107
108    fn push_to_sample_array(frame: &ffmpeg_next::frame::Audio, sample_array: &mut Vec<f32>) {
109        if frame.samples() == 0 {
110            return;
111        }
112        // Account for the padding
113        let actual_size = util::format::sample::Buffer::size(
114            Sample::F32(Type::Packed),
115            CHANNELS,
116            frame.samples(),
117            false,
118        );
119        let f32_frame: Vec<f32> = frame.data(0)[..actual_size]
120            .chunks_exact(4)
121            .map(|x| {
122                let mut a: [u8; 4] = [0; 4];
123                a.copy_from_slice(x);
124                f32::from_le_bytes(a)
125            })
126            .collect();
127        sample_array.extend_from_slice(&f32_frame);
128    }
129}
130
131impl Decoder for FFmpegDecoder {
132    fn decode(path: &Path) -> BlissResult<PreAnalyzedSong> {
133        ffmpeg_next::init().map_err(|e| {
134            BlissError::DecodingError(format!(
135                "ffmpeg init error while decoding file '{}': {:?}.",
136                path.display(),
137                e
138            ))
139        })?;
140        log::set_level(Level::Quiet);
141        let mut song = PreAnalyzedSong {
142            path: path.into(),
143            ..Default::default()
144        };
145        let mut ictx = ffmpeg_next::format::input(&path).map_err(|e| {
146            BlissError::DecodingError(format!(
147                "while opening format for file '{}': {:?}.",
148                path.display(),
149                e
150            ))
151        })?;
152        let (mut decoder, stream, expected_sample_number) = {
153            let input = ictx.streams().best(media::Type::Audio).ok_or_else(|| {
154                BlissError::DecodingError(format!(
155                    "No audio stream found for file '{}'.",
156                    path.display()
157                ))
158            })?;
159            let mut context = ffmpeg_next::codec::context::Context::from_parameters(
160                input.parameters(),
161            )
162            .map_err(|e| {
163                BlissError::DecodingError(format!(
164                    "Could not load the codec context for file '{}': {:?}",
165                    path.display(),
166                    e
167                ))
168            })?;
169            context.set_threading(Config {
170                kind: ThreadingType::Frame,
171                count: 0,
172                #[cfg(not(feature = "ffmpeg_6_0"))]
173                safe: true,
174            });
175            let decoder = context.decoder().audio().map_err(|e| {
176                BlissError::DecodingError(format!(
177                    "when finding decoder for file '{}': {:?}.",
178                    path.display(),
179                    e
180                ))
181            })?;
182
183            // Add SAMPLE_RATE to have one second margin to avoid reallocating if
184            // the duration is slightly more than estimated
185            // TODO>1.0 another way to get the exact number of samples is to decode
186            // everything once, compute the real number of samples from that,
187            // allocate the array with that number, and decode again. Check
188            // what's faster between reallocating, and just have one second
189            // leeway.
190            let expected_sample_number = (SAMPLE_RATE as f32 * input.duration() as f32
191                / input.time_base().denominator() as f32)
192                .ceil()
193                + SAMPLE_RATE as f32;
194            (decoder, input.index(), expected_sample_number)
195        };
196        let sample_array: Vec<f32> = Vec::with_capacity(expected_sample_number as usize);
197        if let Some(title) = ictx.metadata().get("title") {
198            song.title = match title {
199                "" => None,
200                t => Some(t.to_string()),
201            };
202        };
203        if let Some(artist) = ictx.metadata().get("artist") {
204            song.artist = match artist {
205                "" => None,
206                a => Some(a.to_string()),
207            };
208        };
209        if let Some(album) = ictx.metadata().get("album") {
210            song.album = match album {
211                "" => None,
212                a => Some(a.to_string()),
213            };
214        };
215        if let Some(genre) = ictx.metadata().get("genre") {
216            song.genre = match genre {
217                "" => None,
218                g => Some(g.to_string()),
219            };
220        };
221        if let Some(track_number) = ictx.metadata().get("track") {
222            song.track_number = match track_number {
223                "" => None,
224                t => t
225                    .parse::<i32>()
226                    .ok()
227                    .or_else(|| t.split_once('/').and_then(|(n, _)| n.parse::<i32>().ok())),
228            };
229        };
230        if let Some(disc_number) = ictx.metadata().get("disc") {
231            song.disc_number = match disc_number {
232                "" => None,
233                t => t
234                    .parse::<i32>()
235                    .ok()
236                    .or_else(|| t.split_once('/').and_then(|(n, _)| n.parse::<i32>().ok())),
237            };
238        };
239        if let Some(album_artist) = ictx.metadata().get("album_artist") {
240            song.album_artist = match album_artist {
241                "" => None,
242                t => Some(t.to_string()),
243            };
244        };
245
246        #[cfg(not(feature = "ffmpeg_7_0"))]
247        let is_channel_layout_empty = decoder.channel_layout() == ChannelLayout::empty();
248        #[cfg(feature = "ffmpeg_7_0")]
249        let is_channel_layout_empty = decoder.channel_layout().is_empty();
250
251        let (empty_in_channel_layout, in_channel_layout) = {
252            if is_channel_layout_empty {
253                (true, ChannelLayout::default(decoder.channels().into()))
254            } else {
255                (false, decoder.channel_layout())
256            }
257        };
258        decoder.set_channel_layout(in_channel_layout);
259
260        let in_channel_layout_to_send = SendChannelLayout(in_channel_layout);
261
262        let (tx, rx) = mpsc::channel();
263        let in_codec_format = decoder.format();
264        let in_codec_rate = decoder.rate();
265        let child = thread::spawn(move || {
266            FFmpegDecoder::resample_frame(
267                rx,
268                in_codec_format,
269                in_channel_layout_to_send,
270                in_codec_rate,
271                sample_array,
272                empty_in_channel_layout,
273            )
274        });
275        for (s, packet) in ictx.packets() {
276            if s.index() != stream {
277                continue;
278            }
279            match decoder.send_packet(&packet) {
280                Ok(_) => (),
281                Err(Error::Other { errno: EINVAL }) => {
282                    return Err(BlissError::DecodingError(format!(
283                        "wrong codec opened for file '{}.",
284                        path.display(),
285                    )))
286                }
287                Err(Error::Eof) => {
288                    warn!(
289                        "Premature EOF reached while decoding file '{}'.",
290                        path.display()
291                    );
292                    drop(tx);
293                    song.sample_array = child.join().unwrap()?;
294                    return Ok(song);
295                }
296                Err(e) => warn!("{} when decoding file '{}'", e, path.display()),
297            };
298
299            loop {
300                let mut decoded = ffmpeg_next::frame::Audio::empty();
301                match decoder.receive_frame(&mut decoded) {
302                    Ok(_) => {
303                        tx.send(decoded).map_err(|e| {
304                        BlissError::DecodingError(format!(
305                            "while sending decoded frame to the resampling thread for file '{}': {:?}",
306                            path.display(),
307                            e,
308                        ))
309                    })?;
310                    }
311                    Err(_) => break,
312                }
313            }
314        }
315
316        // Flush the stream
317        let packet = ffmpeg_next::codec::packet::Packet::empty();
318        match decoder.send_packet(&packet) {
319            Ok(_) => (),
320            Err(Error::Other { errno: EINVAL }) => {
321                return Err(BlissError::DecodingError(format!(
322                    "wrong codec opened for file '{}'.",
323                    path.display()
324                )))
325            }
326            Err(Error::Eof) => {
327                warn!(
328                    "Premature EOF reached while decoding file '{}'.",
329                    path.display()
330                );
331                drop(tx);
332                song.sample_array = child.join().unwrap()?;
333                return Ok(song);
334            }
335            Err(e) => warn!("error while decoding {}: {}", path.display(), e),
336        };
337
338        loop {
339            let mut decoded = ffmpeg_next::frame::Audio::empty();
340            match decoder.receive_frame(&mut decoded) {
341                Ok(_) => {
342                    tx.send(decoded).map_err(|e| {
343                        BlissError::DecodingError(format!(
344                        "while sending decoded frame to the resampling thread for file '{}': {:?}",
345                        path.display(),
346                        e
347                    ))
348                    })?;
349                }
350                Err(_) => break,
351            }
352        }
353
354        drop(tx);
355        song.sample_array = child.join().unwrap()?;
356        let duration_seconds = song.sample_array.len() as f32 / SAMPLE_RATE as f32;
357        song.duration = Duration::from_nanos((duration_seconds * 1e9_f32).round() as u64);
358        Ok(song)
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use crate::decoder::ffmpeg::FFmpegDecoder as Decoder;
365    use crate::decoder::Decoder as DecoderTrait;
366    use crate::decoder::PreAnalyzedSong;
367    use crate::AnalysisOptions;
368    use crate::BlissError;
369    use crate::Song;
370    use crate::SAMPLE_RATE;
371    use adler32::RollingAdler32;
372    use pretty_assertions::assert_eq;
373    use std::num::NonZero;
374    use std::path::Path;
375
376    fn _test_decode(path: &Path, expected_hash: u32) {
377        let song = Decoder::decode(path).unwrap();
378        let mut hasher = RollingAdler32::new();
379        for sample in song.sample_array.iter() {
380            hasher.update_buffer(&sample.to_le_bytes());
381        }
382
383        assert_eq!(expected_hash, hasher.hash());
384    }
385
386    #[test]
387    fn test_tags() {
388        let song = Decoder::decode(Path::new("data/s16_mono_22_5kHz.flac")).unwrap();
389        assert_eq!(song.artist, Some(String::from("David TMX")));
390        assert_eq!(
391            song.album_artist,
392            Some(String::from("David TMX - Album Artist"))
393        );
394        assert_eq!(song.title, Some(String::from("Renaissance")));
395        assert_eq!(song.album, Some(String::from("Renaissance")));
396        assert_eq!(song.track_number, Some(2));
397        assert_eq!(song.disc_number, Some(1));
398        assert_eq!(song.genre, Some(String::from("Pop")));
399        // Test that there is less than 10ms of difference between what
400        // the song advertises and what we compute.
401        assert!((song.duration.as_millis() as f32 - 11070.).abs() < 10.);
402    }
403
404    #[test]
405    fn test_special_tags() {
406        // This file has tags like `DISC: 02/05` and `TRACK: 06/24`.
407        let song = Decoder::decode(Path::new("data/special-tags.mp3")).unwrap();
408        assert_eq!(song.disc_number, Some(2));
409        assert_eq!(song.track_number, Some(6));
410    }
411
412    #[test]
413    fn test_unsupported_tags_format() {
414        // This file has tags like `TRACK: 02test/05`.
415        let song = Decoder::decode(Path::new("data/unsupported-tags.mp3")).unwrap();
416        assert_eq!(song.track_number, None);
417    }
418
419    #[test]
420    fn test_empty_tags() {
421        let song = Decoder::decode(Path::new("data/no_tags.flac")).unwrap();
422        assert_eq!(song.artist, None);
423        assert_eq!(song.title, None);
424        assert_eq!(song.album, None);
425        assert_eq!(song.track_number, None);
426        assert_eq!(song.disc_number, None);
427        assert_eq!(song.genre, None);
428    }
429
430    #[test]
431    fn test_resample_mono() {
432        let path = Path::new("data/s32_mono_44_1_kHz.flac");
433        let expected_hash = 0xa0f8b8af;
434        _test_decode(&path, expected_hash);
435    }
436
437    #[test]
438    fn test_resample_multi() {
439        let path = Path::new("data/s32_stereo_44_1_kHz.flac");
440        let expected_hash = 0xbbcba1cf;
441        _test_decode(&path, expected_hash);
442    }
443
444    #[test]
445    fn test_resample_stereo() {
446        let path = Path::new("data/s16_stereo_22_5kHz.flac");
447        let expected_hash = 0x1d7b2d6d;
448        _test_decode(&path, expected_hash);
449    }
450
451    #[test]
452    fn test_decode_mono() {
453        let path = Path::new("data/s16_mono_22_5kHz.flac");
454        // Obtained through
455        // ffmpeg -i data/s16_mono_22_5kHz.flac -ar 22050 -ac 1 -c:a pcm_f32le
456        // -f hash -hash addler32 -
457        let expected_hash = 0x5e01930b;
458        _test_decode(&path, expected_hash);
459    }
460
461    #[test]
462    fn test_decode_mp3() {
463        let path = Path::new("data/s32_stereo_44_1_kHz.mp3");
464        // Obtained through
465        // ffmpeg -i data/s16_mono_22_5kHz.mp3 -ar 22050 -ac 1 -c:a pcm_f32le
466        // -f hash -hash addler32 -
467        let expected_hash = 0x69ca6906;
468        _test_decode(&path, expected_hash);
469    }
470
471    #[test]
472    #[cfg(feature = "ffmpeg")]
473    fn test_dont_panic_no_channel_layout() {
474        let path = Path::new("data/no_channel.wav");
475        let expected_hash = 0xd594429c;
476        _test_decode(&path, expected_hash);
477    }
478
479    #[test]
480    fn test_decode_right_capacity_vec() {
481        let path = Path::new("data/s16_mono_22_5kHz.flac");
482        let song = Decoder::decode(&path).unwrap();
483        let sample_array = song.sample_array;
484        assert_eq!(
485            sample_array.len() + SAMPLE_RATE as usize,
486            sample_array.capacity()
487        );
488
489        let path = Path::new("data/s32_stereo_44_1_kHz.flac");
490        let song = Decoder::decode(&path).unwrap();
491        let sample_array = song.sample_array;
492        assert_eq!(
493            sample_array.len() + SAMPLE_RATE as usize,
494            sample_array.capacity()
495        );
496
497        let path = Path::new("data/capacity_fix.ogg");
498        let song = Decoder::decode(&path).unwrap();
499        let sample_array = song.sample_array;
500        assert!(sample_array.len() as f32 / sample_array.capacity() as f32 > 0.90);
501        assert!(sample_array.len() as f32 / (sample_array.capacity() as f32) < 1.);
502    }
503
504    #[test]
505    fn test_decode_errors() {
506        assert_eq!(
507        Decoder::decode(Path::new("nonexistent")).unwrap_err(),
508        BlissError::DecodingError(String::from(
509            "while opening format for file 'nonexistent': ffmpeg::Error(2: No such file or directory)."
510        )),
511    );
512        assert_eq!(
513            Decoder::decode(Path::new("data/picture.png")).unwrap_err(),
514            BlissError::DecodingError(String::from(
515                "No audio stream found for file 'data/picture.png'."
516            )),
517        );
518    }
519
520    #[test]
521    fn test_decode_wav() {
522        let expected_hash = 0xde831e82;
523        _test_decode(Path::new("data/piano.wav"), expected_hash);
524    }
525
526    #[test]
527    fn test_try_from() {
528        let pre_analyzed_song = PreAnalyzedSong::default();
529        assert!(<PreAnalyzedSong as TryInto<Song>>::try_into(pre_analyzed_song).is_err());
530    }
531
532    #[test]
533    fn test_analyze_paths() {
534        let analysis = Decoder::analyze_paths(["data/nonexistent", "data/piano.flac"])
535            .map(|s| s.1.is_ok())
536            .collect::<Vec<_>>();
537        assert_eq!(analysis, vec![false, true]);
538    }
539
540    #[test]
541    fn test_analyze_paths_with_cores() {
542        // Analyze with a number of cores greater than the system's number of cores.
543        let analysis = Decoder::analyze_paths_with_options(
544            [
545                "data/nonexistent",
546                "data/piano.flac",
547                "data/nonexistent.cue",
548            ],
549            AnalysisOptions {
550                number_cores: NonZero::new(usize::MAX).unwrap(),
551                ..Default::default()
552            },
553        )
554        .map(|s| s.1.is_ok())
555        .collect::<Vec<_>>();
556        assert_eq!(analysis, vec![false, true, false]);
557    }
558
559    #[test]
560    fn test_analyze_paths_with_cores_empty_paths() {
561        let analysis = Decoder::analyze_paths_with_options::<&str, [_; 0]>(
562            [],
563            AnalysisOptions {
564                number_cores: NonZero::new(1).unwrap(),
565                ..Default::default()
566            },
567        )
568        .collect::<Vec<_>>();
569        assert_eq!(analysis, vec![]);
570    }
571}