bliss_audio/song/decoder/
ffmpeg.rs1use 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
24pub struct FFmpegDecoder;
30
31struct SendChannelLayout(ChannelLayout);
32unsafe 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 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 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 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 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 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 assert!((song.duration.as_millis() as f32 - 11070.).abs() < 10.);
402 }
403
404 #[test]
405 fn test_special_tags() {
406 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 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 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 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 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}