diff --git a/README.md b/README.md index 97b7fcb..d8816ac 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The package can be installed by adding `membrane_mp4_plugin` to your list of dep ```elixir defp deps do [ - {:membrane_mp4_plugin, "~> 0.34.1"} + {:membrane_mp4_plugin, "~> 0.34.2"} ] end ``` diff --git a/lib/membrane_mp4/container/header.ex b/lib/membrane_mp4/container/header.ex index e86215d..7023dbf 100644 --- a/lib/membrane_mp4/container/header.ex +++ b/lib/membrane_mp4/container/header.ex @@ -4,6 +4,7 @@ defmodule Membrane.MP4.Container.Header do The `content_size` field is equal to the box size minus the size of the header (8 bytes). """ + use Bunch.Access @enforce_keys [:name, :content_size, :header_size] diff --git a/lib/membrane_mp4/container/parse_helper.ex b/lib/membrane_mp4/container/parse_helper.ex index 5c32a35..35afd9a 100644 --- a/lib/membrane_mp4/container/parse_helper.ex +++ b/lib/membrane_mp4/container/parse_helper.ex @@ -18,7 +18,8 @@ defmodule Membrane.MP4.Container.ParseHelper do def parse_boxes(data, schema, context, acc) do withl header_content: - {:ok, %{name: name, content_size: content_size}, rest} <- Header.parse(data), + {:ok, %{name: name, content_size: content_size, header_size: header_size}, rest} <- + Header.parse(data), header_content: <> <- rest, do: box_schema = schema[name], known?: true <- box_schema && not box_schema.black_box?, @@ -26,7 +27,7 @@ defmodule Membrane.MP4.Container.ParseHelper do {:ok, {fields, rest}, context} <- parse_fields(content, box_schema.fields, context), try: {:ok, children, <<>>, context} <- parse_boxes(rest, box_schema.children, context, []) do - box = %{fields: fields, children: children} + box = %{fields: fields, children: children, size: content_size, header_size: header_size} parse_boxes(data, schema, context, [{name, box} | acc]) else header_content: _error -> @@ -34,7 +35,7 @@ defmodule Membrane.MP4.Container.ParseHelper do {:ok, Enum.reverse(acc), data, context} known?: _ -> - box = %{content: content} + box = %{content: content, size: content_size, header_size: header_size} parse_boxes(data, schema, context, [{name, box} | acc]) try: {:error, context} -> @@ -62,11 +63,24 @@ defmodule Membrane.MP4.Container.ParseHelper do end end - defp parse_field(data, {name, {type, store: context_name, when: condition}}, context) do - {flag, key} = condition + defp parse_field(data, {name, {type, store: context_name, when: {key, [mask: mask]}}}, context) do context_object = Map.get(context, key, 0) - if (flag &&& context_object) == flag do + 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 parse_field(data, {name, {type, store: context_name}}, context) else {:ok, {[], data}, context} @@ -80,11 +94,20 @@ defmodule Membrane.MP4.Container.ParseHelper do {:ok, result, context} end - defp parse_field(data, {name, {type, when: condition}}, context) do - {flag, key} = condition + 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 (flag &&& context_object) == flag do + if context_object == value do parse_field(data, {name, type}, context) else {:ok, {[], data}, context} diff --git a/lib/membrane_mp4/container/schema.ex b/lib/membrane_mp4/container/schema.ex index d0483d4..54724c3 100644 --- a/lib/membrane_mp4/container/schema.ex +++ b/lib/membrane_mp4/container/schema.ex @@ -10,7 +10,7 @@ defmodule Membrane.MP4.Container.Schema do """ @full_box [ - version: :uint8, + version: {:uint8, store: :version}, flags: {:uint24, store: :fo_flags} ] @@ -70,10 +70,13 @@ defmodule Membrane.MP4.Container.Schema do fields: @full_box ++ [ - creation_time: :uint32, - modification_time: :uint32, + creation_time: {:uint32, when: {:version, value: 0}}, + creation_time: {:uint64, when: {:version, value: 1}}, + modification_time: {:uint32, when: {:version, value: 0}}, + modification_time: {:uint64, when: {:version, value: 1}}, timescale: :uint32, - duration: :uint32, + duration: {:uint32, when: {:version, value: 0}}, + duration: {:uint64, when: {:version, value: 1}}, rate: :fp16d16, volume: :fp8d8, reserved: <<0::size(80)>>, @@ -101,11 +104,14 @@ defmodule Membrane.MP4.Container.Schema do fields: @full_box ++ [ - creation_time: :uint32, - modification_time: :uint32, + creation_time: {:uint32, when: {:version, value: 0}}, + creation_time: {:uint64, when: {:version, value: 1}}, + modification_time: {:uint32, when: {:version, value: 0}}, + modification_time: {:uint64, when: {:version, value: 1}}, track_id: :uint32, reserved: <<0::32>>, - duration: :uint32, + duration: {:uint32, when: {:version, value: 0}}, + duration: {:uint64, when: {:version, value: 1}}, reserved: <<0::64>>, layer: :int16, alternate_group: :int16, @@ -130,10 +136,13 @@ defmodule Membrane.MP4.Container.Schema do fields: @full_box ++ [ - creation_time: :uint32, - modification_time: :uint32, + creation_time: {:uint32, when: {:version, value: 0}}, + creation_time: {:uint64, when: {:version, value: 1}}, + modification_time: {:uint32, when: {:version, value: 0}}, + modification_time: {:uint64, when: {:version, value: 1}}, timescale: :uint32, - duration: :uint32, + duration: {:uint32, when: {:version, value: 0}}, + duration: {:uint64, when: {:version, value: 1}}, reserved: <<0::1>>, language: :uint15, reserved: <<0::16>> @@ -280,7 +289,7 @@ defmodule Membrane.MP4.Container.Schema do {:list, [ sample_count: :uint32, - sample_offset: :uint32 + sample_composition_offset: :uint32 ]} ] ], @@ -325,6 +334,19 @@ defmodule Membrane.MP4.Container.Schema do chunk_offset: :uint32 ]} ] + ], + co64: [ + version: 0, + fields: + @full_box ++ + [ + entry_count: :uint32, + entry_list: + {:list, + [ + chunk_offset: :uint64 + ]} + ] ] ] ] @@ -426,7 +448,8 @@ defmodule Membrane.MP4.Container.Schema do sample_duration: :uint32, sample_size: :uint32, sample_flags: :bin32, - sample_offset: {:uint32, when: {0x800, :fo_flags}} + sample_composition_offset: + {:uint32, when: {:fo_flags, mask: 0x800}} ]} ] ] diff --git a/lib/membrane_mp4/container/schema_parser.ex b/lib/membrane_mp4/container/schema_parser.ex index a3410af..f3d53c2 100644 --- a/lib/membrane_mp4/container/schema_parser.ex +++ b/lib/membrane_mp4/container/schema_parser.ex @@ -62,10 +62,10 @@ defmodule Membrane.MP4.Container.Schema.Parser do {name, type} end - defp parse_field({name, {type, store: context_name, when: {flag, context_name}}}) + defp parse_field({name, {type, store: context_name, when: {context_name, opts}}}) when is_atom(name) do {name, type} = parse_field({name, type}) - type = {type, store: context_name, when: {flag, context_name}} + type = {type, store: context_name, when: {context_name, opts}} {name, type} end @@ -75,9 +75,9 @@ defmodule Membrane.MP4.Container.Schema.Parser do {name, type} end - defp parse_field({name, {type, when: {flag, context_name}}}) when is_atom(name) do + defp parse_field({name, {type, when: {context_name, opts}}}) when is_atom(name) do {name, type} = parse_field({name, type}) - type = {type, when: {flag, context_name}} + type = {type, when: {context_name, opts}} {name, type} end diff --git a/lib/membrane_mp4/container/serialize_helper.ex b/lib/membrane_mp4/container/serialize_helper.ex index bd75899..6c4ff27 100644 --- a/lib/membrane_mp4/container/serialize_helper.ex +++ b/lib/membrane_mp4/container/serialize_helper.ex @@ -64,11 +64,20 @@ defmodule Membrane.MP4.Container.SerializeHelper do end end - defp serialize_field(term, {type, store: context_name, when: condition}, context) do - {flag, key} = condition + defp serialize_field(term, {type, store: context_name, when: {key, [mask: mask]}}, context) do context_object = Map.get(context, key) - if context_object != nil and (flag &&& context_object) == flag do + if context_object != nil and (mask &&& context_object) == mask do + serialize_field(term, {type, store: context_name}, context) + else + {{:ok, <<>>}, context} + end + end + + defp serialize_field(term, {type, store: context_name, when: {key, [value: value]}}, context) do + context_object = Map.get(context, key) + + if context_object != nil and context_object == value do serialize_field(term, {type, store: context_name}, context) else {{:ok, <<>>}, context} @@ -80,11 +89,20 @@ defmodule Membrane.MP4.Container.SerializeHelper do serialize_field(term, type, context) end - defp serialize_field(term, {type, when: condition}, context) do - {flag, key} = condition + defp serialize_field(term, {type, when: {key, [mask: mask]}}, context) do + context_object = Map.get(context, key, 0) + + if (mask &&& context_object) == mask do + serialize_field(term, type, context) + else + {{:ok, <<>>}, context} + end + end + + defp serialize_field(term, {type, when: {key, [value: value]}}, context) do context_object = Map.get(context, key, 0) - if (flag &&& context_object) == flag do + if context_object == value do serialize_field(term, type, context) else {{:ok, <<>>}, context} diff --git a/lib/membrane_mp4/demuxer/isom.ex b/lib/membrane_mp4/demuxer/isom.ex index 53271da..0ebd104 100644 --- a/lib/membrane_mp4/demuxer/isom.ex +++ b/lib/membrane_mp4/demuxer/isom.ex @@ -142,7 +142,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do } = state ) do {samples, rest, samples_info} = - SamplesInfo.get_samples(state.samples_info, state.partial <> buffer.payload) + SamplesInfo.get_samples( + state.samples_info, + state.partial <> buffer.payload + ) buffers = get_buffer_actions(samples) @@ -157,7 +160,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do ) do # Until all pads are connected we are storing all the samples {samples, rest, samples_info} = - SamplesInfo.get_samples(state.samples_info, state.partial <> buffer.payload) + SamplesInfo.get_samples( + state.samples_info, + state.partial <> buffer.payload + ) state = store_samples(state, samples) @@ -176,33 +182,22 @@ defmodule Membrane.MP4.Demuxer.ISOM do maybe_header = parse_header(rest) - state = - if maybe_header, - do: %{ - state - | mdat_size: maybe_header.content_size, - mdat_header_size: maybe_header.header_size - }, - else: state - update_fsm_state_ctx = if :mdat in Keyword.keys(state.boxes) or (maybe_header != nil and maybe_header.name == :mdat) do :started_parsing_mdat end - state = update_fsm_state(state, update_fsm_state_ctx) |> set_partial(rest) + state = + set_mdat_metadata(state, update_fsm_state_ctx, maybe_header) + |> update_fsm_state(update_fsm_state_ctx) + |> set_partial(rest) cond do state.fsm_state == :mdat_reading -> handle_can_read_mdat_box(ctx, state) state.optimize_for_non_fast_start? -> - state = - if state.fsm_state == :skip_mdat, - do: %{state | mdat_beginning: state.boxes_size}, - else: state - handle_non_fast_start_optimization(state) true -> @@ -210,6 +205,20 @@ defmodule Membrane.MP4.Demuxer.ISOM do end end + defp set_mdat_metadata(state, context, maybe_header) do + if context == :started_parsing_mdat do + %{ + state + | mdat_beginning: state.mdat_beginning || get_mdat_header_beginning(state.boxes), + mdat_header_size: + state.mdat_header_size || maybe_header[:header_size] || state.boxes[:mdat].header_size, + mdat_size: state.mdat_size || maybe_header[:content_size] || state.boxes[:mdat].size + } + else + state + end + end + defp set_partial(state, rest) do partial = if state.fsm_state in [:skip_mdat, :go_back_to_mdat], do: <<>>, else: rest %{state | partial: partial} @@ -285,7 +294,12 @@ defmodule Membrane.MP4.Demuxer.ISOM do end defp handle_non_fast_start_optimization(%{fsm_state: :go_back_to_mdat} = state) do - seek(state, state.mdat_beginning, state.mdat_size + state.mdat_header_size, false) + seek( + state, + state.mdat_beginning, + state.mdat_size + state.mdat_header_size, + false + ) end defp handle_non_fast_start_optimization(state) do @@ -315,7 +329,14 @@ defmodule Membrane.MP4.Demuxer.ISOM do end state = - %{state | samples_info: SamplesInfo.get_samples_info(state.boxes[:moov])} + %{ + state + | samples_info: + SamplesInfo.get_samples_info( + state.boxes[:moov], + state.mdat_beginning + state.mdat_header_size + ) + } |> update_fsm_state() # Parse the data we received so far (partial or the whole mdat box in a single buffer) and @@ -330,7 +351,9 @@ defmodule Membrane.MP4.Demuxer.ISOM do content end - {samples, rest, samples_info} = SamplesInfo.get_samples(state.samples_info, data) + {samples, rest, samples_info} = + SamplesInfo.get_samples(state.samples_info, data) + state = %{state | samples_info: samples_info, partial: rest} all_pads_connected? = all_pads_connected?(ctx, state) @@ -472,4 +495,16 @@ defmodule Membrane.MP4.Demuxer.ISOM do {:end_of_stream, pad_ref} end) end + + defp get_mdat_header_beginning([]) do + 0 + end + + defp get_mdat_header_beginning([{:mdat, _box} | _rest]) do + 0 + end + + defp get_mdat_header_beginning([{_other_name, box} | rest]) do + box.header_size + box.size + get_mdat_header_beginning(rest) + end end diff --git a/lib/membrane_mp4/demuxer/isom/samples_info.ex b/lib/membrane_mp4/demuxer/isom/samples_info.ex index 7f9dec7..aa79122 100644 --- a/lib/membrane_mp4/demuxer/isom/samples_info.ex +++ b/lib/membrane_mp4/demuxer/isom/samples_info.ex @@ -15,7 +15,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do :tracks_number, :timescales, :last_dts, - :sample_tables + :sample_tables, + :mdat_iterator ] defstruct @enforce_keys @@ -42,7 +43,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do (track_id :: pos_integer()) => last_dts :: Ratio.t() | nil }, tracks_number: pos_integer(), - sample_tables: %{(track_id :: pos_integer()) => SampleTable.t()} + sample_tables: %{(track_id :: pos_integer()) => SampleTable.t()}, + mdat_iterator: non_neg_integer() } @doc """ @@ -51,9 +53,10 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do a whole sample, and has yet to be parsed. """ @spec get_samples(t, data :: binary()) :: - {[{Buffer.t(), track_id :: pos_integer()}], rest :: binary, t} + {[{Buffer.t(), track_id :: pos_integer()}], rest :: binary(), t()} def get_samples(samples_info, data) do - {samples_info, rest, buffers} = do_get_samples(samples_info, data, []) + {samples_info, rest, buffers} = + do_get_samples(samples_info, data, []) {buffers, rest, samples_info} end @@ -63,24 +66,32 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do end defp do_get_samples(samples_info, data, buffers) do - [%{size: size, track_id: track_id} = sample | samples] = samples_info.samples + [%{size: size, track_id: track_id, sample_offset: sample_offset} = sample | samples] = + samples_info.samples - if size <= byte_size(data) do - <> = data + to_skip = sample_offset - samples_info.mdat_iterator - {dts, pts, samples_info} = get_dts_and_pts(samples_info, sample) + case data do + <<_to_skip::binary-size(to_skip), payload::binary-size(size), rest::binary>> -> + {dts, pts, samples_info} = get_dts_and_pts(samples_info, sample) - buffer = - {%Buffer{ - payload: payload, - dts: dts, - pts: pts - }, track_id} + buffer = + {%Buffer{ + payload: payload, + dts: dts, + pts: pts + }, track_id} - samples_info = %{samples_info | samples: samples} - do_get_samples(samples_info, rest, [buffer | buffers]) - else - {samples_info, data, Enum.reverse(buffers)} + samples_info = %{samples_info | samples: samples} + + do_get_samples( + %{samples_info | mdat_iterator: samples_info.mdat_iterator + to_skip + size}, + rest, + [buffer | buffers] + ) + + _other -> + {samples_info, data, Enum.reverse(buffers)} end end @@ -118,8 +129,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do present in the `mdat` box. The list of samples in the returned struct is used to extract data from the `mdat` box and get output buffers. """ - @spec get_samples_info(%{children: boxes :: Container.t()}) :: t - def get_samples_info(%{children: boxes}) do + @spec get_samples_info(%{children: boxes :: Container.t()}, non_neg_integer()) :: t + def get_samples_info(%{children: boxes}, mdat_beginning) do tracks = boxes |> Enum.filter(fn {type, _content} -> type == :trak end) @@ -159,7 +170,8 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do :decoding_deltas, :sample_sizes, :samples_per_chunk, - :composition_offsets + :composition_offsets, + :chunk_offset ])} end) @@ -167,7 +179,9 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do {samples, _acc} = chunk_offsets |> Enum.flat_map_reduce(tracks_data, fn %{track_id: track_id} = chunk, tracks_data -> - {new_samples, track_data} = get_chunk_samples(chunk, tracks_data[track_id]) + {new_samples, {track_data, _sample_offset}} = + get_chunk_samples(chunk, tracks_data[track_id]) + {new_samples, %{tracks_data | track_id => track_data}} end) @@ -183,19 +197,28 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do tracks_number: map_size(tracks), timescales: timescales, last_dts: last_dts, - sample_tables: sample_tables + sample_tables: sample_tables, + mdat_iterator: mdat_beginning } end defp get_chunk_samples(chunk, track_data) do - %{chunk_no: chunk_no, track_id: track_id} = chunk + %{chunk_no: chunk_no, track_id: track_id, chunk_offset: chunk_offset} = chunk {track_data, samples_no} = get_samples_no(chunk_no, track_data) - Enum.map_reduce(1..samples_no, track_data, fn _no, track_data -> + Enum.map_reduce(1..samples_no, {track_data, chunk_offset}, fn _no, + {track_data, sample_offset} -> {sample, track_data} = get_sample_description(track_data) - sample = Map.put(sample, :track_id, track_id) - {sample, track_data} + + sample = + Map.merge(sample, %{ + track_id: track_id, + chunk_offset: chunk_offset, + sample_offset: sample_offset + }) + + {sample, {track_data, sample_offset + sample.size}} end) end @@ -250,14 +273,19 @@ defmodule Membrane.MP4.Demuxer.ISOM.SamplesInfo do {sample_composition_offset, composition_offsets} = case composition_offsets do - [%{sample_count: 1, sample_offset: offset} | composition_offsets] -> + [%{sample_count: 1, sample_composition_offset: offset} | composition_offsets] -> {offset, composition_offsets} - [%{sample_count: count, sample_offset: offset} | composition_offsets] -> - {offset, [%{sample_count: count - 1, sample_offset: offset} | composition_offsets]} + [%{sample_count: count, sample_composition_offset: offset} | composition_offsets] -> + {offset, + [%{sample_count: count - 1, sample_composition_offset: offset} | composition_offsets]} end - {%{size: size, sample_delta: delta, sample_composition_offset: sample_composition_offset}, + {%{ + size: size, + sample_delta: delta, + sample_composition_offset: sample_composition_offset + }, %{ track_data | decoding_deltas: deltas, diff --git a/lib/membrane_mp4/movie_box/sample_table_box.ex b/lib/membrane_mp4/movie_box/sample_table_box.ex index 59a1af1..66bf91e 100644 --- a/lib/membrane_mp4/movie_box/sample_table_box.ex +++ b/lib/membrane_mp4/movie_box/sample_table_box.ex @@ -240,7 +240,7 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do sample_description: unpack_sample_description(boxes[:stsd]), sample_count: boxes[:stsz].fields.sample_count, sample_sizes: unpack_sample_sizes(boxes[:stsz]), - chunk_offsets: unpack_chunk_offsets(boxes[:stco]), + chunk_offsets: unpack_chunk_offsets(boxes[:stco] || boxes[:co64]), decoding_deltas: boxes[:stts].fields.entry_list, composition_offsets: get_composition_offsets(boxes), samples_per_chunk: boxes[:stsc].fields.entry_list, @@ -255,7 +255,7 @@ defmodule Membrane.MP4.MovieBox.SampleTableBox do # if no :ctts box is available, assume that the offset between # composition time and the decoding time is equal to 0 Enum.map(boxes[:stts].fields.entry_list, fn entry -> - %{sample_count: entry.sample_count, sample_offset: 0} + %{sample_count: entry.sample_count, sample_composition_offset: 0} end) end end diff --git a/lib/membrane_mp4/muxer/cmaf.ex b/lib/membrane_mp4/muxer/cmaf.ex index b4aef52..75da4ed 100644 --- a/lib/membrane_mp4/muxer/cmaf.ex +++ b/lib/membrane_mp4/muxer/cmaf.ex @@ -568,7 +568,7 @@ defmodule Membrane.MP4.Muxer.CMAF do sample.metadata.duration |> Helper.timescalify(timescale) |> Ratio.trunc(), - sample_offset: Helper.timescalify(sample.pts - sample.dts, timescale) + sample_composition_offset: Helper.timescalify(sample.pts - sample.dts, timescale) } end) end diff --git a/lib/membrane_mp4/track/sample_table.ex b/lib/membrane_mp4/track/sample_table.ex index 90e2008..fa262be 100644 --- a/lib/membrane_mp4/track/sample_table.ex +++ b/lib/membrane_mp4/track/sample_table.ex @@ -28,7 +28,7 @@ defmodule Membrane.MP4.Track.SampleTable do ], composition_offsets: [ %{ - sample_offset: Ratio.t(), + sample_composition_offset: Ratio.t(), sample_count: pos_integer } ], diff --git a/mix.exs b/mix.exs index 8f90432..e747b07 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Membrane.MP4.Plugin.MixProject do use Mix.Project - @version "0.34.1" + @version "0.34.2" @github_url "https://github.com/membraneframework/membrane_mp4_plugin" def project do diff --git a/test/fixtures/isom/ref_64_bit_boxes.mp4 b/test/fixtures/isom/ref_64_bit_boxes.mp4 new file mode 100644 index 0000000..6cad410 Binary files /dev/null and b/test/fixtures/isom/ref_64_bit_boxes.mp4 differ diff --git a/test/fixtures/isom/ref_zeros_at_mdat_beginning.mp4 b/test/fixtures/isom/ref_zeros_at_mdat_beginning.mp4 new file mode 100644 index 0000000..0addd58 Binary files /dev/null and b/test/fixtures/isom/ref_zeros_at_mdat_beginning.mp4 differ diff --git a/test/membrane_mp4/demuxer/isom/demuxer_test.exs b/test/membrane_mp4/demuxer/isom/demuxer_test.exs index 55973cd..a442b58 100644 --- a/test/membrane_mp4/demuxer/isom/demuxer_test.exs +++ b/test/membrane_mp4/demuxer/isom/demuxer_test.exs @@ -91,6 +91,44 @@ defmodule Membrane.MP4.Demuxer.ISOM.DemuxerTest do perform_test(pipeline, "aac", out_path) end + + @tag :tmp_dir + test "an .mp4 file with 64-bit versions of boxes", %{tmp_dir: dir} do + in_path = "test/fixtures/isom/ref_64_bit_boxes.mp4" + video_output_path = Path.join(dir, "out.h264") + audio_output_path = Path.join(dir, "out.aac") + + pipeline = + start_testing_pipeline_with_two_tracks!( + input_file: in_path, + video_output_file: video_output_path, + audio_output_file: audio_output_path + ) + + assert_end_of_stream(pipeline, :video_sink) + assert_end_of_stream(pipeline, :audio_sink) + assert :ok == Pipeline.terminate(pipeline) + end + + @tag :tmp_dir + test "an .mp4 file with media chunks not starting at the beginning of the mdat box", %{ + tmp_dir: dir + } do + in_path = "test/fixtures/isom/ref_zeros_at_mdat_beginning.mp4" + video_output_path = Path.join(dir, "out.h264") + audio_output_path = Path.join(dir, "out.aac") + + pipeline = + start_testing_pipeline_with_two_tracks!( + input_file: in_path, + video_output_file: video_output_path, + audio_output_file: audio_output_path + ) + + assert_end_of_stream(pipeline, :video_sink) + assert_end_of_stream(pipeline, :audio_sink) + assert :ok == Pipeline.terminate(pipeline) + end end describe "Demuxer with `non_fast_start_optimization: true` should allow for demuxing" do @@ -127,6 +165,44 @@ defmodule Membrane.MP4.Demuxer.ISOM.DemuxerTest do perform_test(pipeline, "video", out_path) end + + @tag :tmp_dir + test "an .mp4 file with 64-bit versions of boxes", %{tmp_dir: dir} do + in_path = "test/fixtures/isom/ref_64_bit_boxes.mp4" + video_output_path = Path.join(dir, "out.h264") + audio_output_path = Path.join(dir, "out.aac") + + pipeline = + start_testing_pipeline_with_two_tracks!( + input_file: in_path, + video_output_file: video_output_path, + audio_output_file: audio_output_path + ) + + assert_end_of_stream(pipeline, :video_sink) + assert_end_of_stream(pipeline, :audio_sink) + assert :ok == Pipeline.terminate(pipeline) + end + + @tag :tmp_dir + test "an .mp4 file with media chunks not starting at the beginning of the mdat box", %{ + tmp_dir: dir + } do + in_path = "test/fixtures/isom/ref_zeros_at_mdat_beginning.mp4" + video_output_path = Path.join(dir, "out.h264") + audio_output_path = Path.join(dir, "out.aac") + + pipeline = + start_testing_pipeline_with_two_tracks!( + input_file: in_path, + video_output_file: video_output_path, + audio_output_file: audio_output_path + ) + + assert_end_of_stream(pipeline, :video_sink) + assert_end_of_stream(pipeline, :audio_sink) + assert :ok == Pipeline.terminate(pipeline) + end end describe "output pad connected after new_tracks_t() notification" do @@ -218,6 +294,20 @@ defmodule Membrane.MP4.Demuxer.ISOM.DemuxerTest do Pipeline.start_link_supervised!(spec: structure) end + defp start_testing_pipeline_with_two_tracks!(opts) do + structure = [ + child(:file, %Membrane.File.Source{location: opts[:input_file]}) + |> child(:demuxer, Membrane.MP4.Demuxer.ISOM) + |> via_out(Pad.ref(:output, 1)) + |> child(:video_sink, %Membrane.File.Sink{location: opts[:video_output_file]}), + get_child(:demuxer) + |> via_out(Pad.ref(:output, 2)) + |> child(:audio_sink, %Membrane.File.Sink{location: opts[:audio_output_file]}) + ] + + Pipeline.start_link_supervised!(spec: structure) + end + defp start_remote_pipeline!(opts) do spec = child(:file, %Membrane.File.Source{