aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs18
-rw-r--r--Jellyfin.Api/Helpers/DynamicHlsHelper.cs23
-rw-r--r--Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs62
4 files changed, 105 insertions, 4 deletions
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 429cc542ca..329dd2c4cb 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1760,6 +1760,12 @@ public class DynamicHlsController : BaseJellyfinApiController
{
args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
+ else if (state.AudioStream?.CodecTag is not null && state.AudioStream.CodecTag.Equals("ac-4", StringComparison.Ordinal))
+ {
+ // ac-4 audio tends to hava a super weird sample rate that will fail most encoders
+ // force resample it to 48KHz
+ args += " -ar 48000";
+ }
args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 9087945128..fe73534967 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -17,6 +17,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -139,6 +140,8 @@ public class UniversalAudioController : BaseJellyfinApiController
// set device specific data
foreach (var sourceInfo in info.MediaSources)
{
+ sourceInfo.TranscodingContainer = transcodingContainer;
+ sourceInfo.TranscodingSubProtocol = transcodingProtocol ?? sourceInfo.TranscodingSubProtocol;
_mediaInfoHelper.SetDeviceSpecificData(
item,
sourceInfo,
@@ -173,6 +176,8 @@ public class UniversalAudioController : BaseJellyfinApiController
return Redirect(mediaSource.Path);
}
+ // This one is currently very misleading as the SupportsDirectStream actually means "can direct play"
+ // The definition of DirectStream also seems changed during development
var isStatic = mediaSource.SupportsDirectStream;
if (!isStatic && mediaSource.TranscodingSubProtocol == MediaStreamProtocol.hls)
{
@@ -180,20 +185,25 @@ public class UniversalAudioController : BaseJellyfinApiController
// ffmpeg option -> file extension
// mpegts -> ts
// fmp4 -> mp4
- // TODO: remove this when we switch back to the segment muxer
var supportedHlsContainers = new[] { "ts", "mp4" };
+ // fallback to mpegts if device reports some weird value unsupported by hls
+ var requestedSegmentContainer = Array.Exists(
+ supportedHlsContainers,
+ element => string.Equals(element, transcodingContainer, StringComparison.OrdinalIgnoreCase)) ? transcodingContainer : "ts";
+ var segmentContainer = Array.Exists(
+ supportedHlsContainers,
+ element => string.Equals(element, mediaSource.TranscodingContainer, StringComparison.OrdinalIgnoreCase)) ? mediaSource.TranscodingContainer : requestedSegmentContainer;
var dynamicHlsRequestDto = new HlsAudioRequestDto
{
Id = itemId,
Container = ".m3u8",
Static = isStatic,
PlaySessionId = info.PlaySessionId,
- // fallback to mpegts if device reports some weird value unsupported by hls
- SegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts",
+ SegmentContainer = segmentContainer,
MediaSourceId = mediaSourceId,
DeviceId = deviceId,
- AudioCodec = audioCodec,
+ AudioCodec = mediaSource.TranscodeReasons == TranscodeReason.ContainerNotSupported ? "copy" : audioCodec,
EnableAutoStreamCopy = true,
AllowAudioStreamCopy = true,
AllowVideoStreamCopy = true,
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
index f8d89119a4..6f040cfae7 100644
--- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -151,6 +151,14 @@ public class DynamicHlsHelper
var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();
+ // from universal audio service, need to override the AudioCodec when the actual request differs from original query
+ if (!string.Equals(state.OutputAudioCodec, _httpContextAccessor.HttpContext.Request.Query["AudioCodec"].ToString(), StringComparison.OrdinalIgnoreCase))
+ {
+ var newQuery = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_httpContextAccessor.HttpContext.Request.QueryString.ToString());
+ newQuery["AudioCodec"] = state.OutputAudioCodec;
+ queryString = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(string.Empty, newQuery);
+ }
+
// from universal audio service
if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer)
&& !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase))
@@ -714,6 +722,21 @@ public class DynamicHlsHelper
return HlsCodecStringHelpers.GetAv1String(profile, level, false, bitDepth);
}
+ // VP9 HLS is for video remuxing only, everything is probed from the original video
+ if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ var width = state.VideoStream.Width ?? 0;
+ var height = state.VideoStream.Height ?? 0;
+ var framerate = state.VideoStream.AverageFrameRate ?? 30;
+ var bitDepth = state.VideoStream.BitDepth ?? 8;
+ return HlsCodecStringHelpers.GetVp9String(
+ width,
+ height,
+ state.VideoStream.PixelFormat,
+ framerate,
+ bitDepth);
+ }
+
return string.Empty;
}
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
index ec67b4c1bf..d0bfa1fbe6 100644
--- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -183,6 +183,68 @@ public static class HlsCodecStringHelpers
}
/// <summary>
+ /// Gets a VP9 codec string.
+ /// </summary>
+ /// <param name="width">Video width.</param>
+ /// <param name="height">Video height.</param>
+ /// <param name="pixelFormat">Video pixel format.</param>
+ /// <param name="framerate">Video framerate.</param>
+ /// <param name="bitDepth">Video bitDepth.</param>
+ /// <returns>The VP9 codec string.</returns>
+ public static string GetVp9String(int width, int height, string pixelFormat, float framerate, int bitDepth)
+ {
+ // refer: https://www.webmproject.org/vp9/mp4/
+ StringBuilder result = new StringBuilder("vp09", 13);
+
+ var profileString = pixelFormat switch
+ {
+ "yuv420p" => "00",
+ "yuvj420p" => "00",
+ "yuv422p" => "01",
+ "yuv444p" => "01",
+ "yuv420p10le" => "02",
+ "yuv420p12le" => "02",
+ "yuv422p10le" => "03",
+ "yuv422p12le" => "03",
+ "yuv444p10le" => "03",
+ "yuv444p12le" => "03",
+ _ => "00"
+ };
+
+ var lumaPictureSize = width * height;
+ var lumaSampleRate = lumaPictureSize * framerate;
+ var levelString = lumaPictureSize switch
+ {
+ <= 0 => "00",
+ <= 36864 => "10",
+ <= 73728 => "11",
+ <= 122880 => "20",
+ <= 245760 => "21",
+ <= 552960 => "30",
+ <= 983040 => "31",
+ <= 2228224 => lumaSampleRate <= 83558400 ? "40" : "41",
+ <= 8912896 => lumaSampleRate <= 311951360 ? "50" : (lumaSampleRate <= 588251136 ? "51" : "52"),
+ <= 35651584 => lumaSampleRate <= 1176502272 ? "60" : (lumaSampleRate <= 4706009088 ? "61" : "62"),
+ _ => "00" // This should not happen
+ };
+
+ if (bitDepth != 8
+ && bitDepth != 10
+ && bitDepth != 12)
+ {
+ // Default to 8 bits
+ bitDepth = 8;
+ }
+
+ result.Append('.').Append(profileString).Append('.').Append(levelString);
+ var bitDepthD2 = bitDepth.ToString("D2", CultureInfo.InvariantCulture);
+ result.Append('.')
+ .Append(bitDepthD2);
+
+ return result.ToString();
+ }
+
+ /// <summary>
/// Gets an AV1 codec string.
/// </summary>
/// <param name="profile">AV1 profile.</param>