Packages
membrane_hls_plugin
3.0.2
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/aac/aggregator.ex
defmodule Membrane.HLS.AAC.Aggregator do
use Membrane.Filter
def_input_pad(:input,
accepted_format: Membrane.AAC
)
def_output_pad(:output,
accepted_format: Membrane.AAC
)
def_options(
target_duration: [
spec: Membrane.Time.t(),
description: """
Segments will converge to this duration.
"""
]
)
@impl true
def handle_init(_ctx, opts) do
{[], %{target_duration: opts.target_duration, acc: [], pts: nil, accumulated_offset: 0}}
end
@impl true
def handle_end_of_stream(:input, _ctx, state = %{acc: []}) do
{[end_of_stream: :output], state}
end
def handle_end_of_stream(:input, _ctx, state) do
{out_buffer, state} = finalize_segment(state)
{[buffer: {:output, out_buffer}, end_of_stream: :output], state}
end
@impl true
def handle_buffer(:input, buffer, _ctx, state) do
case state.pts do
nil ->
state = init_segment(state, buffer)
{[], state}
pts ->
actual_duration = buffer.pts - pts
duration = state.accumulated_offset + actual_duration
if duration >= state.target_duration do
{out_buffer, state} = finalize_segment(state, actual_duration)
state = init_segment(state, buffer)
{[buffer: {:output, out_buffer}], state}
else
{[], add_buffer(state, buffer)}
end
end
end
defp init_segment(state, buffer) do
state
|> put_in([:pts], buffer.pts)
|> put_in([:acc], [buffer])
end
defp add_buffer(state, buffer) do
update_in(state, [:acc], fn acc -> [buffer | acc] end)
end
defp finalize_segment(state) do
# NOTE: we're loosing the last frame's duration! we can still compute it
# by checking the number of AAC frames in the payload: @ 48000 sample rate and
# 1024 samples per frame, we have 21ms per buffer)
buffers = Enum.reverse(state.acc)
duration = List.last(buffers).pts - state.pts
finalize_segment(state, duration)
end
defp finalize_segment(state, duration) do
payload =
state.acc
|> Enum.reverse()
|> Stream.map(fn x -> x.payload end)
|> Enum.join()
payload = encode_id3v2_priv_timestamp(state.pts) <> payload
metadata = %{
duration: duration
}
buffer = %Membrane.Buffer{
payload: payload,
pts: state.pts,
metadata: metadata
}
offset = duration - state.target_duration
state =
state
|> put_in([:acc], [])
|> put_in([:pts], nil)
|> update_in([:accumulated_offset], fn x -> x + offset end)
{buffer, state}
end
@id3v2_identifier "ID3"
@priv_frame_id "PRIV"
@apple_owner_id "com.apple.streaming.transportStreamTimestamp"
# ID3v2.4.0
@id3v2_version <<4, 0>>
defp encode_id3v2_priv_timestamp(pts) do
payload =
pts
|> MPEG.TS.convert_ns_to_ts()
|> rem(2 ** 33)
|> then(fn ts ->
<<ts::unsigned-big-integer-size(64)>>
end)
encode_id3v2_priv(@apple_owner_id, payload)
end
defp encode_id3v2_priv(owner, payload) do
# Build PRIV frame
priv_frame = build_priv_frame(owner, payload)
# Calculate total tag size (excluding the 10-byte header)
tag_size = byte_size(priv_frame)
# Build complete ID3v2 tag
<<
# "ID3"
@id3v2_identifier::binary,
# Version 4.0
@id3v2_version::binary,
# Flags (none set)
0::8,
# Synchsafe size
encode_synchsafe_int32(tag_size)::binary,
# PRIV frame
priv_frame::binary
>>
end
defp build_priv_frame(owner_id, private_data)
when is_binary(owner_id) and is_binary(private_data) do
# Frame content: null-terminated owner ID + private data
frame_content = <<owner_id::binary, 0, private_data::binary>>
# Calculate frame size (excluding frame header)
frame_size = byte_size(frame_content)
<<
# "PRIV"
@priv_frame_id::binary,
# Frame size (synchsafe)
encode_synchsafe_int32(frame_size)::binary,
# Frame flags (none set)
0::16,
# Frame data
frame_content::binary
>>
end
def encode_synchsafe_int32(value) when is_integer(value) and value >= 0 do
import Bitwise
max_synchsafe_value = (1 <<< 28) - 1
if value > max_synchsafe_value do
raise ArgumentError,
"Value #{value} exceeds maximum synchsafe value (#{max_synchsafe_value})"
end
# Extract 7-bit chunks
byte4 = value &&& 0x7F
byte3 = value >>> 7 &&& 0x7F
byte2 = value >>> 14 &&& 0x7F
byte1 = value >>> 21 &&& 0x7F
<<byte1, byte2, byte3, byte4>>
end
end