Input: Fix audio stream selection for video files. (#150)

`youtube-dl` has a tendency to return mp4 files containing video and audio data vs `yt-dlp`. Naturally, the default stream in such cases will be the video data, so we fail to create a decoder.

This PR changes `LiveInput::promote` to first attempt to instantiate a decoder for the default track. If this fails to do so, then we reattempt this for all available tracks until one can be found. Previously, this method chose the default (or first available) track ID, and then failed if *that* track could not be decoded even if a compatible stream existed.

This was tested using `cargo make ready`, as well as manually verifying and adding a test case with a simple mp4 file.
This commit is contained in:
Kyle Simpson
2022-09-07 01:32:57 +01:00
parent 372156e638
commit 03b0803a1d
4 changed files with 46 additions and 23 deletions

View File

@@ -74,7 +74,7 @@ version = "0.5"
[dev-dependencies]
criterion = "0.3"
ntest = "0.8"
symphonia = { version = "0.5", features = ["mp3"], git = "https://github.com/FelixMcFelix/Symphonia", branch = "songbird-fixes" }
symphonia = { version = "0.5", features = ["aac", "isomp4", "mp3"], git = "https://github.com/FelixMcFelix/Symphonia", branch = "songbird-fixes" }
utils = { path = "utils" }
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }

BIN
resources/ting-vid.mp4 Normal file

Binary file not shown.

View File

@@ -114,4 +114,7 @@ pub mod test_data {
/// Path to a Wav source which can be read via a File.
pub const FILE_WAV_TARGET: &str = "resources/loop.wav";
/// Path to an MP4 (H264 + AAC) source which can be read via a File.
pub const FILE_VID_TARGET: &str = "resources/ting-vid.mp4";
}

View File

@@ -1,7 +1,7 @@
use super::{AudioStream, Metadata, MetadataError, Parsed};
use symphonia_core::{
codecs::{CodecRegistry, Decoder, DecoderOptions},
codecs::{CodecRegistry, DecoderOptions},
errors::Error as SymphError,
formats::FormatOptions,
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
@@ -66,31 +66,30 @@ impl LiveInput {
let format = probe_data.format;
let meta = probe_data.metadata;
let mut default_track_id = format.default_track().map(|track| track.id);
let mut decoder: Option<Box<dyn Decoder>> = None;
// Awkward loop: we need BOTH a track ID, and a decoder matching that track ID.
// Take default track (if it exists), take first track to be found otherwise.
for track in format.tracks() {
if default_track_id.is_some() && Some(track.id) != default_track_id {
continue;
}
let this_decoder = codecs.make(&track.codec_params, &DecoderOptions::default())?;
decoder = Some(this_decoder);
default_track_id = Some(track.id);
break;
}
// if default track exists, try to make a decoder
// if that fails, linear scan and take first that succeeds
let decoder = format
.default_track()
.and_then(|track| {
codecs
.make(&track.codec_params, &DecoderOptions::default())
.ok()
.map(|d| (d, track.id))
})
.or_else(|| {
format.tracks().iter().find_map(|track| {
codecs
.make(&track.codec_params, &DecoderOptions::default())
.ok()
.map(|d| (d, track.id))
})
});
// No tracks is a playout error, a bad default track is also possible.
// These are probably malformed? We could go best-effort, and fall back to tracks[0]
// but drop such tracks for now.
let track_id = default_track_id.ok_or(SymphError::DecodeError("no track found"))?;
let decoder = decoder.ok_or(SymphError::DecodeError(
"reported default track did not exist",
))?;
let (decoder, track_id) =
decoder.ok_or(SymphError::DecodeError("no compatible track found"))?;
let p = Parsed {
format,
@@ -145,3 +144,24 @@ impl LiveInput {
}
}
}
#[cfg(test)]
mod tests {
use crate::{
constants::test_data::FILE_VID_TARGET,
input::{codecs::*, File, Input},
};
#[tokio::test]
#[ntest::timeout(10_000)]
async fn promote_finds_valid_audio() {
// Video files often set their default to... the video stream, unsurprisingly.
// In these cases we still want to play the attached audio -- this checks that songbird
// finds the audio on a non-default track via `LiveInput::promote`.
let input = Input::from(File::new(FILE_VID_TARGET));
input
.make_playable_async(&CODEC_REGISTRY, &PROBE)
.await
.unwrap();
}
}