Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMAF demuxer #118

Merged
merged 41 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e4f95d2
Add CMAF demuxer initial implementation
varsill Jan 2, 2025
c35abaa
Implement state machine for CMAF demuxer. Add sample info builder for…
varsill Jan 2, 2025
ed56790
Remove unecessary parts of code
varsill Jan 2, 2025
69c2e85
Perform formatting
varsill Jan 2, 2025
af29a14
Remove unecessary parts of code
varsill Jan 2, 2025
1c88908
Rename test
varsill Jan 2, 2025
94d9651
Remove trun box with version 1
varsill Jan 2, 2025
1f3a991
Read mdat box samples
varsill Jan 3, 2025
ad21ee3
Add calculation of timestamps
varsill Jan 3, 2025
0877eec
Support multiple mdat boxes in a segment
varsill Jan 3, 2025
0f6ead5
Support for delayed output pads linking
varsill Jan 3, 2025
94ef48f
Perform formatting. Refactor do_handle_box
varsill Jan 3, 2025
6ece5a1
Handle actions buffering and eos properly
varsill Jan 3, 2025
aa35bc6
Remove unecessary field
varsill Jan 3, 2025
5198950
Add test checking if tracks resolving works as expected. Fix bugs wit…
varsill Jan 3, 2025
1849640
Fix dialyzer
varsill Jan 3, 2025
ed1507d
Improve types in CMAF.SampleInfo
varsill Jan 3, 2025
7c0c41f
Improve functions naming in the CMAF demuxer
varsill Jan 3, 2025
d57bff9
Remove unused function
varsill Jan 3, 2025
5ee8370
Improve wrong FSM state error message
varsill Jan 3, 2025
f765c4b
Fix credo warning
varsill Jan 3, 2025
9cb7ad8
Rename FSM states
varsill Jan 3, 2025
da6df99
Format files
varsill Jan 7, 2025
5df97b6
Add trun version 1
varsill Jan 7, 2025
5874d89
Use reference list instead of a single entry list in sidx box
varsill Jan 7, 2025
c2e35db
Add tests with single track fixtures
varsill Jan 8, 2025
5f8966e
Format files
varsill Jan 8, 2025
e20f806
Properly handle other versions of fMP4 boxes and default values of so…
varsill Jan 8, 2025
2bc3259
fix stsz box, improve conditional fields handling
mat-hek Jan 8, 2025
6f6013b
Fix tests
varsill Jan 8, 2025
e939f54
Merge branch 'fix-sample-sizes' of https://github.com/membraneframewo…
varsill Jan 8, 2025
ce20483
Fix tests
varsill Jan 8, 2025
62cb099
Format files
varsill Jan 8, 2025
7a5e8b4
Refactor demuxer test
varsill Jan 9, 2025
f27a637
Implement reviewers suggestions
varsill Jan 9, 2025
0a32dee
Add multisource
varsill Jan 13, 2025
7442d30
Make multifiles source work with manual demands
varsill Jan 13, 2025
357df95
Fix credo
varsill Jan 13, 2025
e04d5c4
Fix test
varsill Jan 13, 2025
5ce7693
Remove leftover
varsill Jan 20, 2025
2a10504
Implement reviewers suggestions
varsill Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/membrane_mp4/container.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ defmodule Membrane.MP4.Container do
{:box, box_name_t}
| {:field, field_name_t}
| {:data, bitstring}
| {:reason, :box_header | {:box_size, header: pos_integer, actual: pos_integer}}
| {:reason,
:box_header
| {:box_size, header: pos_integer, actual: pos_integer}
| {:non_empty_leftover, binary}}
]

@type serialize_error_context_t :: [{:box, box_name_t} | {:field, field_name_t}]
Expand Down
95 changes: 50 additions & 45 deletions lib/membrane_mp4/container/parse_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ defmodule Membrane.MP4.Container.ParseHelper do
try:
{:ok, {fields, rest}, context} <- parse_fields(content, box_schema.fields, context),
try:
{:ok, children, <<>>, context} <- parse_boxes(rest, box_schema.children, context, []) do
{:ok, children, rest, context} <- parse_boxes(rest, box_schema.children, context, []),
leftover: <<>> <- rest do
box = %{fields: fields, children: children, size: content_size, header_size: header_size}
parse_boxes(data, schema, context, [{name, box} | acc])
else
Expand All @@ -38,19 +39,33 @@ defmodule Membrane.MP4.Container.ParseHelper do
box = %{content: content, size: content_size, header_size: header_size}
parse_boxes(data, schema, context, [{name, box} | acc])

leftover: leftover ->
{:error, [box: name, reason: {:non_empty_leftover, leftover}]}

try: {:error, context} ->
{:error, [box: name] ++ context}
end
end

defp parse_fields(data, [], context) do
{:ok, {%{}, data}, context}
defp parse_fields(data, fields, context) do
do_parse_fields(data, fields, context, %{})
end

defp parse_fields(data, [{name, type} | fields], context) do
with {:ok, {term, rest}, context} <- parse_field(data, {name, type}, context),
{:ok, {terms, rest}, context} <- parse_fields(rest, fields, context) do
{:ok, {Map.put(terms, name, term), rest}, context}
defp do_parse_fields(data, [], context, acc) do
{:ok, {acc, data}, context}
end

defp do_parse_fields(data, [{name, type} | fields], context, acc) do
case parse_field(data, {name, type}, context) do
{:ok, {term, rest}, context} ->
acc = Map.put(acc, name, term)
do_parse_fields(rest, fields, context, acc)

{:ok, :ignore, context} ->
do_parse_fields(data, fields, context, acc)

{:error, context} ->
{:error, context}
end
end

Expand All @@ -63,27 +78,11 @@ defmodule Membrane.MP4.Container.ParseHelper do
end
end

defp parse_field(data, {name, {type, store: context_name, when: {key, [mask: mask]}}}, context) do
context_object = Map.get(context, key, 0)

if (mask &&& context_object) == mask do
parse_field(data, {name, {type, store: context_name}}, context)
else
{:ok, {[], data}, context}
end
end

defp parse_field(
data,
{name, {type, store: context_name, when: {key, [value: value]}}},
context
) do
context_object = Map.get(context, key, 0)

if context_object == value do
defp parse_field(data, {name, {type, store: context_name, when: when_clause}}, context) do
if handle_when(when_clause, context) do
parse_field(data, {name, {type, store: context_name}}, context)
else
{:ok, {[], data}, context}
{:ok, :ignore, context}
end
end

Expand All @@ -94,23 +93,11 @@ defmodule Membrane.MP4.Container.ParseHelper do
{:ok, result, context}
end

defp parse_field(data, {name, {type, when: {key, [mask: mask]}}}, context) do
context_object = Map.get(context, key, 0)

if (mask &&& context_object) == mask do
parse_field(data, {name, type}, context)
else
{:ok, {[], data}, context}
end
end

defp parse_field(data, {name, {type, when: {key, [value: value]}}}, context) do
context_object = Map.get(context, key, 0)

if context_object == value do
defp parse_field(data, {name, {type, when: when_clause}}, context) do
if handle_when(when_clause, context) do
parse_field(data, {name, type}, context)
else
{:ok, {[], data}, context}
{:ok, :ignore, context}
end
end

Expand All @@ -123,15 +110,21 @@ defmodule Membrane.MP4.Container.ParseHelper do

defp parse_field(data, {name, {:int, size}}, context) do
case data do
<<int::signed-integer-size(size), rest::bitstring>> -> {:ok, {int, rest}, context}
_unknown_format -> parse_field_error(data, name)
<<int::signed-integer-size(size), rest::bitstring>> ->
{:ok, {int, rest}, context}

_unknown_format ->
parse_field_error(data, name)
end
end

defp parse_field(data, {name, {:uint, size}}, context) do
case data do
<<uint::integer-size(size), rest::bitstring>> -> {:ok, {uint, rest}, context}
_unknown_format -> parse_field_error(data, name)
<<uint::integer-size(size), rest::bitstring>> ->
{:ok, {uint, rest}, context}

_unknown_format ->
parse_field_error(data, name)
end
end

Expand Down Expand Up @@ -188,4 +181,16 @@ defmodule Membrane.MP4.Container.ParseHelper do
defp parse_field_error(_data, name, context) do
{:error, [field: name] ++ context}
end

defp handle_when({key, condition}, context) do
with {:ok, value} <- Map.fetch(context, key) do
case condition do
[value: cond_value] -> value == cond_value
[mask: mask] -> (mask &&& value) == mask
end
else
:error ->
raise "MP4 schema field #{key} not found in context"
end
end
end
79 changes: 47 additions & 32 deletions lib/membrane_mp4/container/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ defmodule Membrane.MP4.Container.Schema do
fields:
@full_box ++
[
sample_size: :uint32,
sample_size: {:uint32, store: :sample_size},
sample_count: :uint32,
entry_list: {
{:list,
Expand Down Expand Up @@ -377,35 +377,28 @@ defmodule Membrane.MP4.Container.Schema do
]
],
sidx: [
version: 1,
version: 0,
fields:
@full_box ++
[
reference_id: :uint32,
timescale: :uint32,
earliest_presentation_time: :uint64,
first_offset: :uint64,
earliest_presentation_time: {:uint32, when: {:version, value: 0}},
earliest_presentation_time: {:uint64, when: {:version, value: 1}},
first_offset: {:uint32, when: {:version, value: 0}},
first_offset: {:uint64, when: {:version, value: 1}},
reserved: <<0::16-integer>>,
reference_count: :uint16,
# TODO: make a list once list length is supported
# reference_list: [
# [
# reference_type: :bin1,
# referenced_size: :uint31,
# subsegment_duration: :uint32,
# starts_with_sap: :bin1,
# sap_type: :uint3,
# sap_delta_time: :uint28
# ],
# length: :reference_count
# ]
reference_type: :bin1,
# from the beginning of moof to the end
referenced_size: :uint31,
subsegment_duration: :uint32,
starts_with_sap: :bin1,
sap_type: :uint3,
sap_delta_time: :uint28
reference_list:
{:list,
[
reference_type: :bin1,
referenced_size: :uint31,
subsegment_duration: :uint32,
starts_with_sap: :bin1,
sap_type: :uint3,
sap_delta_time: :uint28
]}
]
],
moof: [
Expand All @@ -424,17 +417,19 @@ defmodule Membrane.MP4.Container.Schema do
@full_box ++
[
track_id: :uint32,
default_sample_duration: :uint32,
default_sample_size: :uint32,
default_sample_flags: :uint32
base_data_offset: {:uint64, when: {:fo_flags, mask: 0x00001}},
default_sample_duration: {:uint32, when: {:fo_flags, mask: 0x000008}},
default_sample_size: {:uint32, when: {:fo_flags, mask: 0x000010}},
default_sample_flags: {:uint32, when: {:fo_flags, mask: 0x000020}}
]
],
tfdt: [
version: 1,
fields:
@full_box ++
[
base_media_decode_time: :uint64
base_media_decode_time: {:uint32, when: {:version, value: 0}},
base_media_decode_time: {:uint64, when: {:version, value: 1}}
]
],
trun: [
Expand All @@ -443,15 +438,35 @@ defmodule Membrane.MP4.Container.Schema do
@full_box ++
[
sample_count: :uint32,
data_offset: :int32,
data_offset: {:int32, when: {:fo_flags, mask: 0x000001}},
first_sample_flags: {:bin32, when: {:fo_flags, mask: 0x000004}},
samples:
{:list,
[
sample_duration: {:uint32, when: {:fo_flags, mask: 0x000100}},
sample_size: {:uint32, when: {:fo_flags, mask: 0x000200}},
sample_flags: {:bin32, when: {:fo_flags, mask: 0x000400}},
sample_composition_offset:
{:uint32, when: {:fo_flags, mask: 0x000800}}
]}
]
],
trun: [
version: 1,
fields:
@full_box ++
[
sample_count: :uint32,
data_offset: {:int32, when: {:fo_flags, mask: 0x000001}},
first_sample_flags: {:bin32, when: {:fo_flags, mask: 0x000004}},
samples:
{:list,
[
sample_duration: :uint32,
sample_size: :uint32,
sample_flags: :bin32,
sample_duration: {:uint32, when: {:fo_flags, mask: 0x000100}},
sample_size: {:uint32, when: {:fo_flags, mask: 0x000200}},
sample_flags: {:bin32, when: {:fo_flags, mask: 0x000400}},
sample_composition_offset:
{:uint32, when: {:fo_flags, mask: 0x800}}
{:int32, when: {:fo_flags, mask: 0x000800}}
]}
]
]
Expand Down
Loading