Added more unit tests for the subsonic transcoding decision engine
This commit is contained in:
@@ -123,6 +123,65 @@ namespace lms::api::subsonic
|
||||
.expected = { detail::DirectPlayResult{} },
|
||||
},
|
||||
|
||||
// check protocol * and codec * are properly handled
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp4", "flac", "mp3" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::DirectPlayResult{} },
|
||||
},
|
||||
|
||||
// check container * is properly handled
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = {}, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::DirectPlayResult{} },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, bitrateLimitation)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// Needs transcode due to codec limitation
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -213,6 +272,39 @@ namespace lms::api::subsonic
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// want flac but bitrate too high
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "LocalDevice",
|
||||
.platform = "Android",
|
||||
.maxAudioBitrate = 320'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = {}, .maxAudioChannels = 32 },
|
||||
},
|
||||
.transcodingProfiles = { { .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt }, { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 } },
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 1'000'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = 16,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 320'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, sampleRateLimitation)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// Needs transcode due to max audio sample rate not handle by codec limitation
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -243,6 +335,86 @@ namespace lms::api::subsonic
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 192'000, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// want flac but source sample rate is too high
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "SONOS",
|
||||
.platform = "UPnP",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {
|
||||
{ .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 950'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 96'000,
|
||||
.bitsPerSample = 24,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// want flac but source sample rate is too high, no max bitrate
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "SONOS",
|
||||
.platform = "UPnP",
|
||||
.maxAudioBitrate = std::nullopt,
|
||||
.maxTranscodingAudioBitrate = std::nullopt,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "opus", "ogg", "oga", "aac", "webma", "webm", "wav", "flac", "mka" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp4", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {
|
||||
{ .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 950'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 96'000,
|
||||
.bitsPerSample = 24,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, channelLimitation)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// Needs transcode due to max nb channels not handle by profile
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -294,7 +466,14 @@ namespace lms::api::subsonic
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioChannelsNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = 2, .audioBitrate = 192'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, codecFallbackSelection)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// needs transcode because codec not handled
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -373,157 +552,6 @@ namespace lms::api::subsonic
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 256000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// check protocol * and codec * are properly handled
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp4", "flac", "mp3" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::DirectPlayResult{} },
|
||||
},
|
||||
|
||||
// check container * is properly handled
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = {}, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::DirectPlayResult{} },
|
||||
},
|
||||
|
||||
// want flac but bitrate too high
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "LocalDevice",
|
||||
.platform = "Android",
|
||||
.maxAudioBitrate = 320'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = {}, .maxAudioChannels = 32 },
|
||||
},
|
||||
.transcodingProfiles = { { .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt }, { .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 } },
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 1'000'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = 16,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 320'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// want flac but source sample rate is too high
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "SONOS",
|
||||
.platform = "UPnP",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {
|
||||
{ .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 950'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 96'000,
|
||||
.bitsPerSample = 24,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// want flac but source sample rate is too high, no max bitrate
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "SONOS",
|
||||
.platform = "UPnP",
|
||||
.maxAudioBitrate = std::nullopt,
|
||||
.maxTranscodingAudioBitrate = std::nullopt,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "opus", "ogg", "oga", "aac", "webma", "webm", "wav", "flac", "mka" }, .audioCodecs = {}, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
{ .containers = { "m4a", "mp4" }, .audioCodecs = { "aac" }, .protocols = {}, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp4", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "aac", .audioCodec = "aac", .protocol = "http", .maxAudioChannels = 6 },
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {
|
||||
{ .type = "AudioCodec", .name = "flac", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "vorbis", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
{ .type = "AudioCodec", .name = "opus", .limitations = { { .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "48000" }, .required = true } } },
|
||||
},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 950'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 96'000,
|
||||
.bitsPerSample = 24,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// wants a lossy codec not handled -> transcode to lossy
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -595,7 +623,14 @@ namespace lms::api::subsonic
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported, detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, profileMatching)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// no protocol specified
|
||||
{
|
||||
.clientInfo = {
|
||||
@@ -626,6 +661,69 @@ namespace lms::api::subsonic
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitrateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 96000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// needs transcode because audio codec not supported by the direct play profile (container matches, codec does not)
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 320'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "aac" }, .protocols = { "http" }, .maxAudioChannels = 2 },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioCodecNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// needs transcode because the direct play profile does not support the http protocol
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 320'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "hls" }, .maxAudioChannels = 2 },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = 2 },
|
||||
},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ProtocolNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "mp3", .codec = "mp3", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, bitDepth)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// transcoding to a lossy codec must not report a bit depth, even if a bit depth limitation is set on that codec (lossy codecs have no PCM bit depth)
|
||||
{
|
||||
.clientInfo = { .name = "TestClient", .platform = "TestPlatform", .maxAudioBitrate = std::nullopt, .maxTranscodingAudioBitrate = std::nullopt, .directPlayProfiles = { {
|
||||
@@ -673,6 +771,231 @@ namespace lms::api::subsonic
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = 16 } } },
|
||||
},
|
||||
|
||||
// needs transcode because the codec profile bit depth limitation rejects direct play
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "flac", .limitations = {
|
||||
{ .name = Limitation::Type::AudioBitdepth, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "16" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 750'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = 24,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitdepthNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = 16 } } },
|
||||
},
|
||||
|
||||
// a required bit depth limitation cannot be evaluated against a lossy source (no bit depth to check), so it is always treated as incompatible on both the direct play and transcoding target sides
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
{ .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
|
||||
{ .name = Limitation::Type::AudioBitdepth, .comparison = Limitation::ComparisonOperator::LessThanEqual, .values = { "24" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioBitdepthNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, comparisonOperators)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// Equals comparison (single value): mismatch cannot be adjusted -> rejects direct play, falls back to a transcoding profile not covered by that codec profile
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
|
||||
{ .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::Equals, .values = { "44100" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 48'000,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioSampleRateNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// Equals comparison (multiple values): no exact match -> adjusted down to the closest allowed value below the source's
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "flac", .audioCodec = "flac", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "flac", .limitations = {
|
||||
{ .name = Limitation::Type::AudioSamplerate, .comparison = Limitation::ComparisonOperator::Equals, .values = { "44100", "48000", "96000" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::FLAC,
|
||||
.codec = core::media::Codec::FLAC,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 750'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 60'000,
|
||||
.bitsPerSample = 16,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "flac", .codec = "flac", .audioChannels = std::nullopt, .audioBitrate = std::nullopt, .audioProfile = "", .audioSamplerate = 48'000, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// NotEquals comparison: source value is in the forbidden list and cannot be adjusted -> rejects direct play, falls back to a transcoding profile not covered by that codec profile
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "mp3" }, .audioCodecs = { "mp3" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
|
||||
{ .name = Limitation::Type::AudioChannels, .comparison = Limitation::ComparisonOperator::NotEquals, .values = { "2" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::AudioChannelsNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
|
||||
// GreaterThanEqual comparison: source bitrate is below the required minimum and cannot be upscaled -> that transcoding profile is rejected entirely, falls back to the next one
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 1'000'000,
|
||||
.maxTranscodingAudioBitrate = 1'000'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
{ .container = "ogg", .audioCodec = "opus", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = { { .type = "AudioCodec", .name = "mp3", .limitations = {
|
||||
{ .name = Limitation::Type::AudioBitrate, .comparison = Limitation::ComparisonOperator::GreaterThanEqual, .values = { "192000" }, .required = true },
|
||||
} } },
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::TranscodeResult{ .reasons = { detail::TranscodeReason::ContainerNotSupported }, .targetStreamInfo = { .protocol = "http", .container = "ogg", .codec = "opus", .audioChannels = std::nullopt, .audioBitrate = 128'000, .audioProfile = "", .audioSamplerate = std::nullopt, .audioBitdepth = std::nullopt } } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
}
|
||||
|
||||
TEST(TranscodeDecision, failure)
|
||||
{
|
||||
const TestCase testCases[]{
|
||||
// no compatible direct play or transcoding profile at all: transcoding profiles are skipped (unsupported output format, then non-http protocol) -> failure
|
||||
{
|
||||
.clientInfo = {
|
||||
.name = "TestClient",
|
||||
.platform = "TestPlatform",
|
||||
.maxAudioBitrate = 320'000,
|
||||
.maxTranscodingAudioBitrate = 320'000,
|
||||
.directPlayProfiles = {
|
||||
{ .containers = { "flac" }, .audioCodecs = { "flac" }, .protocols = { "http" }, .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.transcodingProfiles = {
|
||||
{ .container = "wma", .audioCodec = "wma", .protocol = "http", .maxAudioChannels = std::nullopt },
|
||||
{ .container = "mp3", .audioCodec = "mp3", .protocol = "hls", .maxAudioChannels = std::nullopt },
|
||||
},
|
||||
.codecProfiles = {},
|
||||
},
|
||||
.source = {
|
||||
.container = core::media::Container::MPEG,
|
||||
.codec = core::media::Codec::MP3,
|
||||
.duration = std::chrono::seconds{ 60 },
|
||||
.bitrate = 128'000,
|
||||
.channelCount = 2,
|
||||
.sampleRate = 44'100,
|
||||
.bitsPerSample = std::nullopt,
|
||||
},
|
||||
|
||||
.expected = { detail::FailureResult{ "No compatible direct play or transcoding profile found" } },
|
||||
},
|
||||
};
|
||||
|
||||
processTests(testCases);
|
||||
|
||||
Reference in New Issue
Block a user