Skip to content

Commit

Permalink
fix stsz box, improve conditional fields handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mat-hek committed Jan 8, 2025
1 parent 2c439e6 commit 2bc3259
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 60 deletions.
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
81 changes: 40 additions & 41 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 Down Expand Up @@ -188,4 +175,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
2 changes: 1 addition & 1 deletion 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
40 changes: 23 additions & 17 deletions lib/membrane_mp4/movie_box/sample_table_box.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do
sample_deltas = assemble_sample_deltas(table)
maybe_sample_sync = maybe_sample_sync(table)
sample_to_chunk = assemble_sample_to_chunk(table)
sample_sizes = assemble_sample_sizes(table)
chunk_offsets = assemble_chunk_offsets(table)

[
Expand Down Expand Up @@ -47,15 +46,7 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do
entry_list: sample_to_chunk
}
},
stsz: %{
fields: %{
version: 0,
flags: 0,
sample_size: 0,
sample_count: table.sample_count,
entry_list: sample_sizes
}
},
stsz: assemble_stsz(table),
stco: %{
fields: %{
version: 0,
Expand Down Expand Up @@ -256,12 +247,27 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do
}
)

defp assemble_sample_sizes(%{sample_sizes: sample_sizes}),
do: Enum.map(sample_sizes, &%{entry_size: &1})

defp assemble_chunk_offsets(%{chunk_offsets: chunk_offsets}),
do: Enum.map(chunk_offsets, &%{chunk_offset: &1})

defp assemble_stsz(%{sample_sizes: sample_sizes, sample_count: sample_count}) do
fields =
if sample_sizes != [] and Enum.all?(sample_sizes) == hd(sample_sizes) do
%{sample_size: hd(sample_sizes), entry_list: []}
else
%{sample_size: 0, entry_list: Enum.map(sample_sizes, &%{entry_size: &1})}
end

%{
fields:
Map.merge(fields, %{
version: 0,
flags: 0,
sample_count: sample_count
})
}
end

@spec unpack(%{children: Container.t(), fields: map()}, timescale :: pos_integer()) ::
SampleTable.t()
def unpack(%{children: boxes}, timescale) do
Expand Down Expand Up @@ -293,12 +299,12 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do
offsets |> Enum.map(fn %{chunk_offset: offset} -> offset end)
end

defp unpack_sample_sizes(%{fields: %{entry_list: [], sample_count: 1, sample_size: sample_size}}) do
[sample_size]
defp unpack_sample_sizes(%{fields: %{sample_size: 0, entry_list: sizes}}) do
Enum.map(sizes, fn %{entry_size: size} -> size end)
end

defp unpack_sample_sizes(%{fields: %{entry_list: sizes}}) do
sizes |> Enum.map(fn %{entry_size: size} -> size end)
defp unpack_sample_sizes(%{fields: %{sample_size: sample_size, sample_count: sample_count}}) do
Bunch.Enum.repeated(sample_size, sample_count)
end

defp unpack_sample_description(%{children: [{avc, %{children: boxes, fields: fields}}]})
Expand Down

0 comments on commit 2bc3259

Please sign in to comment.