diff --git a/examples/muxer_isom.exs b/examples/muxer_isom.exs index 1eba07bb..fd42eb19 100644 --- a/examples/muxer_isom.exs +++ b/examples/muxer_isom.exs @@ -30,7 +30,9 @@ defmodule Example do }) |> child(:audio_parser, %Membrane.AAC.Parser{out_encapsulation: :none}) |> child(:audio_payloader, Membrane.MP4.Payloader.AAC), - child(:muxer, Membrane.MP4.Muxer.ISOM) + child(:muxer, %Membrane.MP4.Muxer.ISOM{ + fast_start: true + }) |> child(:sink, %Membrane.File.Sink{location: @output_file}), get_child(:audio_payloader) |> get_child(:muxer), get_child(:video_payloader) |> get_child(:muxer) diff --git a/lib/membrane_mp4/depayloader/aac.ex b/lib/membrane_mp4/depayloader/aac.ex index 658461e6..30dbef67 100644 --- a/lib/membrane_mp4/depayloader/aac.ex +++ b/lib/membrane_mp4/depayloader/aac.ex @@ -62,8 +62,13 @@ defmodule Membrane.MP4.Depayloader.AAC do depends_on_core_coder = 0 extension_flag = 0 - <> = section_5 + <> = section_5 + + custom_frequency_length = if frequency_id == 15, do: 24, else: 0 + + <<_maybe_custom_frequency::integer-size(custom_frequency_length), _channel_config_id::4, + frame_length_id::1, ^depends_on_core_coder::1, ^extension_flag::1>> = section_5_rest + %{ profile: AAC.aot_id_to_profile(aot_id), diff --git a/lib/membrane_mp4/payloader/aac.ex b/lib/membrane_mp4/payloader/aac.ex index 7a6f9f4c..aa246594 100644 --- a/lib/membrane_mp4/payloader/aac.ex +++ b/lib/membrane_mp4/payloader/aac.ex @@ -7,22 +7,25 @@ defmodule Membrane.MP4.Payloader.AAC do """ use Membrane.Filter - def_input_pad :input, + def_input_pad(:input, demand_unit: :buffers, accepted_format: %Membrane.AAC{encapsulation: :none} - - def_output_pad :output, accepted_format: Membrane.MP4.Payload - - def_options avg_bit_rate: [ - spec: non_neg_integer(), - default: 0, - description: "Average stream bitrate. Should be set to 0 if unknown." - ], - max_bit_rate: [ - spec: non_neg_integer(), - default: 0, - description: "Maximal stream bitrate. Should be set to 0 if unknown." - ] + ) + + def_output_pad(:output, accepted_format: Membrane.MP4.Payload) + + def_options( + avg_bit_rate: [ + spec: non_neg_integer(), + default: 0, + description: "Average stream bitrate. Should be set to 0 if unknown." + ], + max_bit_rate: [ + spec: non_neg_integer(), + default: 0, + description: "Maximal stream bitrate. Should be set to 0 if unknown." + ] + ) @impl true def handle_stream_format(:input, stream_format, _ctx, state) do @@ -59,9 +62,11 @@ defmodule Membrane.MP4.Payloader.AAC do depends_on_core_coder = 0 extension_flag = 0 + custom_frequency = if frequency_id == 15, do: <>, else: <<>> + section5 = - <> + <> |> make_esds_section(5) # 64 = mpeg4-audio diff --git a/test/fixtures/isom/ref_aac_fast_start_sr_20000.mp4 b/test/fixtures/isom/ref_aac_fast_start_sr_20000.mp4 new file mode 100644 index 00000000..9de1e93d Binary files /dev/null and b/test/fixtures/isom/ref_aac_fast_start_sr_20000.mp4 differ diff --git a/test/membrane_mp4/transmuxing_transpayloading_test.exs b/test/membrane_mp4/transmuxing_transpayloading_test.exs new file mode 100644 index 00000000..31e86aca --- /dev/null +++ b/test/membrane_mp4/transmuxing_transpayloading_test.exs @@ -0,0 +1,52 @@ +defmodule Membrane.MP4.TransmuxingTranspayloadingTest do + @moduledoc false + + use ExUnit.Case, async: true + + import Membrane.ChildrenSpec + import Membrane.Testing.Assertions + + require Membrane.Pad + + alias Membrane.MP4.Container + alias Membrane.Pad + + alias Membrane.Testing.Pipeline + + @tag :tmp_dir + test "an AAC track with custom sample rate after demuxing, depayloading, payloading and muxing is identical to the original one", + %{tmp_dir: dir} do + in_path = "test/fixtures/isom/ref_aac_fast_start_sr_20000.mp4" + out_path = Path.join(dir, "ref_aac_fast_start_sr_20000.mp4") + + structure = [ + child(:file, %Membrane.File.Source{location: in_path}) + |> child(:demuxer, Membrane.MP4.Demuxer.ISOM) + |> via_out(Pad.ref(:output, 1)) + |> child(:depayloader, Membrane.MP4.Depayloader.AAC) + |> child(:payloader, Membrane.MP4.Payloader.AAC) + |> child(:muxer, %Membrane.MP4.Muxer.ISOM{ + chunk_duration: Membrane.Time.seconds(1), + fast_start: true + }) + |> child(:sink, %Membrane.File.Sink{location: out_path}) + ] + + pipeline = Pipeline.start_link_supervised!(structure: structure) + + assert_end_of_stream(pipeline, :sink, :input, 6000) + refute_sink_buffer(pipeline, :sink, _buffer, 0) + + assert :ok == Pipeline.terminate(pipeline) + + {in_mp4, <<>>} = File.read!(in_path) |> Container.parse!() + {out_mp4, <<>>} = File.read!(out_path) |> Container.parse!() + + assert out_mp4[:moov].children[:mvhd] == in_mp4[:moov].children[:mvhd] + + assert out_mp4[:moov].children[:trak].children[:thkd] == + in_mp4[:moov].children[:trak].children[:thkd] + + assert out_mp4[:mdat] == in_mp4[:mdat] + end +end