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