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:
@@ -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
BIN
resources/ting-vid.mp4
Normal file
Binary file not shown.
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user