diff --git a/.formatter.exs b/.formatter.exs index 4c3febe..e88289c 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,6 +1,6 @@ [ inputs: [ - "{lib,test,config}/**/*.{ex,exs}", + "{lib,test,config,examples}/**/*.{ex,exs}", ".formatter.exs", "*.exs" ], diff --git a/.gitignore b/.gitignore index 94bafe2..54358c6 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,5 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +output.mp4 # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode diff --git a/README.md b/README.md index cdee74c..8035099 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,25 @@ The package can be installed by adding `membrane_aac_plugin` to your list of dep ```elixir def deps do [ - {:membrane_aac_plugin, "~> 0.18.1"} + {:membrane_aac_plugin, "~> 0.18.2"} ] end ``` ## Usage example +You can find examples of usage in the `examples/` directory. -TODO +To see how the parser can be used to payload AAC stream so that it can be put in the MP4 container, run: +``` +elixir examples/add_and_put_in_mp4.exs +``` + +When the script terminates, you can play the result .mp4 file with the following command: +``` +ffplay output.mp4 +``` -The docs can be found at [Hex Docs](https://hexdocs.pm/membrane_aac_plugin). +The documentation can be found at [Hex Docs](https://hexdocs.pm/membrane_aac_plugin). ## Copyright and License diff --git a/examples/parse_and_put_in_mp4.exs b/examples/parse_and_put_in_mp4.exs new file mode 100644 index 0000000..8ed7c2b --- /dev/null +++ b/examples/parse_and_put_in_mp4.exs @@ -0,0 +1,46 @@ +Mix.install([ + :membrane_hackney_plugin, + :membrane_mp4_plugin, + :membrane_file_plugin, + {:membrane_aac_plugin, path: Path.expand("./"), override: true} +]) + +defmodule MP4MuxingPipeline do + use Membrane.Pipeline + + alias Membrane.{AAC, File, Hackney, MP4} + + @impl true + def handle_init(_ctx, _opts) do + spec = + child(:source, %Hackney.Source{ + location: + "https://raw.githubusercontent.com/membraneframework/static/gh-pages/samples/test-audio.aac", + hackney_opts: [follow_redirect: true] + }) + |> child(:parser, %AAC.Parser{out_encapsulation: :none, output_config: :esds}) + |> child(:muxer, %MP4.Muxer.ISOM{}) + |> child(:sink, %File.Sink{location: "output.mp4"}) + + {[spec: spec], %{}} + end + + # When end of stream arrives, terminate the pipeline + @impl + def handle_element_end_of_stream(:sink, _pad, _ctx, state) do + {[terminate: :normal], state} + end + + @impl true + def handle_element_end_of_stream(_child, _pad, _ctx, state) do + {[], state} + end +end + +{:ok, _supervisor, pid} = Membrane.Pipeline.start_link(MP4MuxingPipeline) +monitor_ref = Process.monitor(pid) + +receive do + {:DOWN, ^monitor_ref, :process, _pid, _reason} -> + :ok +end diff --git a/lib/membrane/aac/filler.ex b/lib/membrane/aac/filler.ex index 8419715..539e702 100644 --- a/lib/membrane/aac/filler.ex +++ b/lib/membrane/aac/filler.ex @@ -1,13 +1,17 @@ defmodule Membrane.AAC.Filler do + @moduledoc deprecated: + "Please use `Membrane.AudioFiller` from the `:membrane_audio_filler_plugin` to fill gaps in raw audio stream with silence." @moduledoc """ + Element that fills gaps in AAC stream with silent frames. """ use Membrane.Filter + require Membrane.Logger alias Membrane.{Buffer, Time} # Silence frame per channel configuration @silent_frames %{ - 1 => <<222, 2, 0, 76, 97, 118, 99, 53, 56, 46, 53, 52, 46, 49, 48, 48, 0, 2, 48, 64, 14>>, + 1 => <<222, 2, 0, 76, 97, 118, 99, 54, 48, 46, 51, 49, 46, 49, 48, 50, 0, 2, 48, 64, 14>>, 2 => <<255, 241, 80, 128, 3, 223, 252, 222, 2, 0, 76, 97, 118, 99, 53, 56, 46, 57, 49, 46, 49, 48, 48, 0, 66, 32, 8, 193, 24, 56>> @@ -50,6 +54,11 @@ defmodule Membrane.AAC.Filler do @impl true def handle_init(_ctx, _opts) do + Membrane.Logger.warning(""" + `#{__MODULE__}` element is deprecated now. + Please use `Membrane.AudioFiller` from the `:membrane_audio_filler_plugin` to fill gaps in raw audio stream with silence. + """) + {[], %State{frame_duration: nil}} end @@ -60,7 +69,9 @@ defmodule Membrane.AAC.Filler do @impl true def handle_stream_format(:input, stream_format, _ctx, state) do - new_duration = stream_format.samples_per_frame / stream_format.sample_rate * Time.second() + new_duration = + stream_format.samples_per_frame / stream_format.sample_rate * Time.second() + state = %State{state | frame_duration: new_duration, channels: stream_format.channels} {[forward: stream_format], state} @@ -70,7 +81,7 @@ defmodule Membrane.AAC.Filler do def handle_buffer(:input, buffer, _ctx, state) do use Numbers, overload_operators: true, comparison: true - %{timestamp: current_timestamp} = buffer.metadata + current_timestamp = buffer.pts || buffer.dts %{expected_timestamp: expected_timestamp, frame_duration: frame_duration} = state expected_timestamp = expected_timestamp || current_timestamp @@ -82,8 +93,12 @@ defmodule Membrane.AAC.Filler do buffers = Enum.map(silent_frames_timestamps, fn timestamp -> - %Buffer{buffer | payload: silent_frame_payload} - |> Bunch.Struct.put_in([:metadata, :timestamp], timestamp) + %Buffer{ + buffer + | payload: silent_frame_payload, + pts: round(timestamp), + dts: round(timestamp) + } end) ++ [buffer] expected_timestamp = expected_timestamp + length(buffers) * frame_duration diff --git a/mix.exs b/mix.exs index 99ad6b3..2375fad 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Membrane.AAC.MixProject do use Mix.Project - @version "0.18.1" + @version "0.18.2" @github_url "https://github.com/membraneframework/membrane_aac_plugin" def project do diff --git a/mix.lock b/mix.lock index 997e5bb..1ba9389 100644 --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,18 @@ %{ "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, "bunch": {:hex, :bunch, "1.6.0", "4775f8cdf5e801c06beed3913b0bd53fceec9d63380cdcccbda6be125a6cfd54", [:mix], [], "hexpm", "ef4e9abf83f0299d599daed3764d19e8eac5d27a5237e5e4d5e2c129cfeb9a22"}, - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, - "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.38", "b42252eddf63bda05554ba8be93a1262dc0920c721f1aaf989f5de0f73a2e367", [:mix], [], "hexpm", "2cd0907795aaef0c7e8442e376633c5b3bd6edc8dbbdc539b22f095501c1cdb6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, "membrane_core": {:hex, :membrane_core, "1.0.0", "1b543aefd952283be1f2a215a1db213aa4d91222722ba03cd35280622f1905ee", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "352c90fd0a29942143c4bf7a727cc05c632e323f50a1a4e99321b1e8982f1533"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.16.0", "7917f6682c22b9bcfc2ca20ed960eee0f7d03ad31fd5f59ed850f1fe3ddd545a", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "b0727998f75a9b4dab8a2baefdfc13c3eac00a04e061ab1b0e61dc5566927acc"}, diff --git a/test/membrane/aac/filler_test.exs b/test/membrane/aac/filler_test.exs index 24c1f85..d100017 100644 --- a/test/membrane/aac/filler_test.exs +++ b/test/membrane/aac/filler_test.exs @@ -19,7 +19,7 @@ defmodule Membrane.AAC.FillerTest do state: state, current_timestamp: current_timestamp } do - current_buffer = %Buffer{metadata: %{timestamp: current_timestamp}, payload: ""} + current_buffer = %Buffer{pts: current_timestamp, payload: ""} assert {actions, new_state} = Filler.handle_buffer(:input, current_buffer, nil, state) @@ -35,7 +35,7 @@ defmodule Membrane.AAC.FillerTest do skipped_frames = 10 current_buffer = %Buffer{ - metadata: %{timestamp: current_timestamp + skipped_frames}, + pts: current_timestamp + skipped_frames, payload: "" } @@ -64,7 +64,7 @@ defmodule Membrane.AAC.FillerTest do |> Enum.map( &%Membrane.Buffer{ payload: &1, - metadata: %{timestamp: &1} + pts: &1 } ) @@ -118,7 +118,7 @@ defmodule Membrane.AAC.FillerTest do state = %{state | channels: channels} buffer = %Buffer{ - metadata: %{timestamp: current_timestamp + skipped_frames}, + pts: current_timestamp + skipped_frames, payload: "" }