bliss_audio/song/decoder/
ffmpeg.rs1use 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
25pub struct FFmpegDecoder;
31
32struct SendChannelLayout(ChannelLayout);
33unsafe 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 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 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 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 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 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 assert!((song.duration.as_millis() as f32 - 11070.).abs() < 10.);
406 }
407
408 #[test]
409 fn test_special_tags() {
410 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 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 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 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 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}