Packages
membrane_hls_plugin
3.0.10
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.16
2.0.15
2.0.14
2.0.13
2.0.12
2.0.11
2.0.10
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
Adaptive live streaming (HLS) plugin for the Membrane Framework.
Current section
Files
Jump to
Current section
Files
lib/membrane/hls/ts_sink.ex
defmodule Membrane.HLS.TSSink do
@moduledoc false
use Membrane.Sink
def_input_pad(:input,
accepted_format: Membrane.RemoteStream
)
def_options(
track_id: [
spec: String.t(),
description: "ID of the track."
],
build_stream: [
spec: (map() -> HLS.VariantStream.t() | HLS.AlternativeRendition.t()),
description: "Build the stream with the given stream format"
],
target_segment_duration: [
spec: Membrane.Time.t()
]
)
@impl true
def handle_init(_context, opts) do
{[], %{opts: opts}}
end
@impl true
def handle_stream_format(:input, format, _ctx, state) do
track_id = state.opts.track_id
target_segment_duration =
Membrane.Time.as_seconds(state.opts.target_segment_duration, :exact)
|> Ratio.ceil()
{stream, legacy_codecs} =
case state.opts.build_stream.(format) do
{aac_format, stream} ->
info = %{
mp4a: %{
aot_id: Membrane.AAC.profile_to_aot_id(aac_format.profile),
channels: Membrane.AAC.channels_to_channel_config_id(aac_format.channels),
frequency: aac_format.sample_rate
}
}
{stream, Membrane.HLS.serialize_codecs(info)}
stream ->
{stream, List.wrap(Map.get(stream, :codecs))}
end
Membrane.HLS.maybe_warn_deprecated_stream_fields(track_id, stream)
{derived_codecs, derived_complete?} = derive_codecs_from_ts_format(format)
{codecs, codecs_complete?} =
cond do
derived_complete? -> {derived_codecs, true}
derived_codecs == [] and legacy_codecs != [] -> {legacy_codecs, true}
true -> {derived_codecs, false}
end
add_track_opts = [
codecs: codecs,
codecs_complete?: codecs_complete?,
stream: stream,
segment_extension: ".ts",
target_segment_duration: target_segment_duration
]
{[notify_parent: {:packager_add_track, track_id, add_track_opts}], state}
end
@impl true
def handle_buffer(:input, buffer, _ctx, state) do
duration =
buffer.metadata.duration
|> Membrane.Time.as_seconds()
|> Ratio.to_float()
actions = [
notify_parent:
{:packager_put_segment, state.opts.track_id, buffer.payload, duration, buffer.pts,
Map.get(buffer, :dts)}
]
{actions, state}
end
defp derive_codecs_from_ts_format(%Membrane.RemoteStream{content_format: content_format})
when is_struct(content_format, Membrane.MPEG.TS.StreamFormat) do
streams = Map.get(content_format, :elementary_streams, [])
if streams == [] do
{[], false}
else
codecs =
streams
|> Enum.flat_map(fn stream ->
stream
|> Map.get(:upstream_format)
|> serialize_upstream_codec()
end)
|> Enum.uniq()
complete? =
Enum.all?(streams, fn stream ->
stream
|> Map.get(:upstream_format)
|> codec_known?()
end)
{codecs, complete?}
end
end
defp derive_codecs_from_ts_format(_format), do: {[], false}
defp codec_known?(%Membrane.AAC{}), do: true
defp codec_known?(%Membrane.H264{stream_structure: {avc, _dcr}})
when avc in [:avc1, :avc3],
do: true
defp codec_known?(%Membrane.H264{profile: profile, width: w, height: h})
when not is_nil(profile) and is_integer(w) and is_integer(h),
do: true
defp codec_known?(_other), do: false
defp serialize_upstream_codec(%Membrane.AAC{} = format) do
info = %{
mp4a: %{
aot_id: Membrane.AAC.profile_to_aot_id(format.profile),
channels: Membrane.AAC.channels_to_channel_config_id(format.channels),
frequency: format.sample_rate
}
}
Membrane.HLS.serialize_codecs(info)
end
defp serialize_upstream_codec(%Membrane.H264{
stream_structure: {avc, <<1, profile, compat, level, _rest::binary>>}
})
when avc in [:avc1, :avc3] do
Membrane.HLS.serialize_codecs([
{:avc1, %{profile: profile, compatibility: compat, level: level}}
])
end
defp serialize_upstream_codec(%Membrane.H264{profile: profile, width: w, height: h} = format)
when not is_nil(profile) and is_integer(w) and is_integer(h) do
profile_idc = h264_profile_idc(profile)
fps = h264_framerate(format.framerate)
level_idc = h264_min_level_idc(w, h, fps)
if profile_idc != nil and level_idc != nil do
compat = h264_compatibility(profile, format)
Membrane.HLS.serialize_codecs([
{:avc1, %{profile: profile_idc, compatibility: compat, level: level_idc}}
])
else
[]
end
end
defp serialize_upstream_codec(_format), do: []
# Maps H264 profile atoms to profile_idc values (ITU-T H.264 Table A-1).
defp h264_profile_idc(:constrained_baseline), do: 66
defp h264_profile_idc(:baseline), do: 66
defp h264_profile_idc(:main), do: 77
defp h264_profile_idc(:high), do: 100
defp h264_profile_idc(:high_10), do: 110
defp h264_profile_idc(:high_10_intra), do: 110
defp h264_profile_idc(:high_422), do: 122
defp h264_profile_idc(:high_422_intra), do: 122
defp h264_profile_idc(:high_444), do: 244
defp h264_profile_idc(:high_444_intra), do: 244
defp h264_profile_idc(_), do: nil
# Derives constraint_set flags from profile where deterministic.
# Constrained Baseline requires constraint_set1_flag (bit 6).
defp h264_compatibility(:constrained_baseline, _format), do: 0xC0
defp h264_compatibility(_profile, _format), do: 0x00
defp h264_framerate({num, den}) when den > 0, do: num / den
defp h264_framerate(_nil_or_invalid), do: 30.0
# H264 level limits table (ITU-T H.264 Table A-1).
# Each entry: {level_idc, max_frame_size_macroblocks, max_macroblocks_per_second}
@h264_levels [
{10, 99, 1_485},
{11, 396, 3_000},
{12, 396, 6_000},
{13, 396, 11_880},
{20, 396, 11_880},
{21, 792, 19_800},
{22, 1_620, 20_250},
{30, 1_620, 40_500},
{31, 3_600, 108_000},
{32, 5_120, 216_000},
{40, 8_192, 245_760},
{41, 8_192, 245_760},
{42, 8_704, 522_240},
{50, 22_080, 589_824},
{51, 36_864, 983_040},
{52, 36_864, 2_073_600}
]
# Derives the minimum H264 level_idc from resolution and framerate.
defp h264_min_level_idc(width, height, fps) do
fs = ceil(width / 16) * ceil(height / 16)
mbps = ceil(fs * fps)
Enum.find_value(@h264_levels, fn {level_idc, max_fs, max_mbps} ->
if fs <= max_fs and mbps <= max_mbps, do: level_idc
end)
end
end