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]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
ntest = "0.8"
|
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" }
|
utils = { path = "utils" }
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
|
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.
|
/// Path to a Wav source which can be read via a File.
|
||||||
pub const FILE_WAV_TARGET: &str = "resources/loop.wav";
|
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 super::{AudioStream, Metadata, MetadataError, Parsed};
|
||||||
|
|
||||||
use symphonia_core::{
|
use symphonia_core::{
|
||||||
codecs::{CodecRegistry, Decoder, DecoderOptions},
|
codecs::{CodecRegistry, DecoderOptions},
|
||||||
errors::Error as SymphError,
|
errors::Error as SymphError,
|
||||||
formats::FormatOptions,
|
formats::FormatOptions,
|
||||||
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
|
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
|
||||||
@@ -66,31 +66,30 @@ impl LiveInput {
|
|||||||
let format = probe_data.format;
|
let format = probe_data.format;
|
||||||
let meta = probe_data.metadata;
|
let meta = probe_data.metadata;
|
||||||
|
|
||||||
let mut default_track_id = format.default_track().map(|track| track.id);
|
// if default track exists, try to make a decoder
|
||||||
let mut decoder: Option<Box<dyn Decoder>> = None;
|
// if that fails, linear scan and take first that succeeds
|
||||||
|
let decoder = format
|
||||||
// Awkward loop: we need BOTH a track ID, and a decoder matching that track ID.
|
.default_track()
|
||||||
// Take default track (if it exists), take first track to be found otherwise.
|
.and_then(|track| {
|
||||||
for track in format.tracks() {
|
codecs
|
||||||
if default_track_id.is_some() && Some(track.id) != default_track_id {
|
.make(&track.codec_params, &DecoderOptions::default())
|
||||||
continue;
|
.ok()
|
||||||
}
|
.map(|d| (d, track.id))
|
||||||
|
})
|
||||||
let this_decoder = codecs.make(&track.codec_params, &DecoderOptions::default())?;
|
.or_else(|| {
|
||||||
|
format.tracks().iter().find_map(|track| {
|
||||||
decoder = Some(this_decoder);
|
codecs
|
||||||
default_track_id = Some(track.id);
|
.make(&track.codec_params, &DecoderOptions::default())
|
||||||
|
.ok()
|
||||||
break;
|
.map(|d| (d, track.id))
|
||||||
}
|
})
|
||||||
|
});
|
||||||
|
|
||||||
// No tracks is a playout error, a bad default track is also possible.
|
// 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]
|
// These are probably malformed? We could go best-effort, and fall back to tracks[0]
|
||||||
// but drop such tracks for now.
|
// but drop such tracks for now.
|
||||||
let track_id = default_track_id.ok_or(SymphError::DecodeError("no track found"))?;
|
let (decoder, track_id) =
|
||||||
let decoder = decoder.ok_or(SymphError::DecodeError(
|
decoder.ok_or(SymphError::DecodeError("no compatible track found"))?;
|
||||||
"reported default track did not exist",
|
|
||||||
))?;
|
|
||||||
|
|
||||||
let p = Parsed {
|
let p = Parsed {
|
||||||
format,
|
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