diff --git a/README.md b/README.md index 41ade55..2ea3881 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ It is a part of [Membrane Multimedia Framework](https://membrane.stream). Add the following line to your `deps` in `mix.exs`. Run `mix deps.get`. ```elixir - {:membrane_ffmpeg_swresample_plugin, "~> 0.15.0"} + {:membrane_ffmpeg_swresample_plugin, "~> 0.16.0"} ``` You also need to have [FFmpeg](https://www.ffmpeg.org/) library installed. diff --git a/lib/membrane_ffmpeg_swresample_plugin/converter.ex b/lib/membrane_ffmpeg_swresample_plugin/converter.ex index d524f26..fa1fb53 100644 --- a/lib/membrane_ffmpeg_swresample_plugin/converter.ex +++ b/lib/membrane_ffmpeg_swresample_plugin/converter.ex @@ -9,54 +9,56 @@ defmodule Membrane.FFmpeg.SWResample.Converter do require Membrane.Logger - alias Membrane.{Buffer, RawAudio, RemoteStream} - alias Membrane.Caps.Matcher alias __MODULE__.Native + alias Membrane.{Buffer, RawAudio, RemoteStream} - @supported_out_caps {RawAudio, - sample_format: Matcher.one_of([:u8, :s16le, :s32le, :f32le, :f64le]), - channels: Matcher.one_of([1, 2])} + @supported_sample_format [:u8, :s16le, :s32le, :f32le, :f64le] + @supported_channels [1, 2] - def_output_pad :output, demand_mode: :auto, caps: @supported_out_caps + def_output_pad :output, + demand_mode: :auto, + accepted_format: + %RawAudio{sample_format: format, channels: channels} + when format in @supported_sample_format and channels in @supported_channels def_input_pad :input, demand_mode: :auto, demand_unit: :bytes, - caps: [ - @supported_out_caps, - {RemoteStream, content_format: one_of([nil, RawAudio])}, - {RawAudio, sample_format: :s24le, channels: one_of([1, 2])} - ] - - def_options input_caps: [ - type: :caps, + accepted_format: + any_of( + %RawAudio{sample_format: format, channels: channels} + when format in @supported_sample_format and channels in @supported_channels, + %RemoteStream{content_format: format} when format in [nil, RawAudio], + %RawAudio{sample_format: :s24le, channels: channels} when channels in @supported_channels + ) + + def_options input_stream_format: [ spec: RawAudio.t() | nil, default: nil, description: """ - Caps for the input pad. If set to nil (default value), - caps are assumed to be received through the pad. If explicitly set to some - caps, they cannot be changed by caps received through the pad. + Stream format for the input pad. If set to nil (default value), + stream format is assumed to be received through the pad. If explicitly set to some + stream format, it cannot be changed by stream format received through the pad. """ ], - output_caps: [ - type: :caps, + output_stream_format: [ spec: RawAudio.t(), description: """ - Audio caps for source pad (output) + Audio stream format for output pad """ ] @impl true - def handle_init(%__MODULE__{} = options) do - case options.input_caps do + def handle_init(_ctx, %__MODULE__{} = options) do + case options.input_stream_format do %RawAudio{} -> :ok nil -> :ok - _other -> raise ":input_caps must be nil or %RawAudio{}" + _other -> raise ":input_stream_format must be nil or %RawAudio{}" end - case options.output_caps do + case options.output_stream_format do %RawAudio{} -> :ok - _other -> raise ":output_caps must be %RawAudio{}" + _other -> raise ":output_stream_format must be %RawAudio{}" end state = @@ -65,61 +67,74 @@ defmodule Membrane.FFmpeg.SWResample.Converter do |> Map.merge(%{ native: nil, queue: <<>>, - input_caps_provided?: options.input_caps != nil + input_stream_format_provided?: options.input_stream_format != nil }) - {:ok, state} + {[], state} end @impl true - def handle_stopped_to_prepared(_ctx, %{input_caps_provided?: false} = state), do: {:ok, state} + def handle_setup(_ctx, %{input_stream_format_provided?: false} = state), do: {[], state} - def handle_stopped_to_prepared(_ctx, state) do - native = mk_native!(state.input_caps, state.output_caps) - {{:ok, caps: {:output, state.output_caps}}, %{state | native: native}} + def handle_setup(_ctx, state) do + native = mk_native!(state.input_stream_format, state.output_stream_format) + {[], %{state | native: native}} end @impl true - def handle_caps(:input, %RemoteStream{}, _ctx, %{input_caps_provided?: false}) do + def handle_stream_format(:input, %RemoteStream{}, _ctx, %{input_stream_format_provided?: false}) do raise """ - Cannot handle RemoteStream without explicitly providing `input_caps` via element options + Cannot handle RemoteStream without explicitly providing `input_stream_format` via element options """ end @impl true - def handle_caps(:input, %RemoteStream{}, _ctx, %{input_caps: stored_caps} = state) do - native = mk_native!(stored_caps, state.output_caps) + def handle_stream_format( + :input, + %RemoteStream{}, + _ctx, + %{input_stream_format: stored_stream_format} = state + ) do + native = mk_native!(stored_stream_format, state.output_stream_format) state = %{state | native: native} - {{:ok, caps: {:output, state.output_caps}}, state} + {[stream_format: {:output, state.output_stream_format}], state} end @impl true - def handle_caps(:input, caps, _ctx, %{input_caps_provided?: true, input_caps: stored_caps}) - when stored_caps != caps do + def handle_stream_format(:input, stream_format, _ctx, %{ + input_stream_format_provided?: true, + input_stream_format: stored_stream_format + }) + when stored_stream_format != stream_format do raise """ - Received caps #{inspect(caps)} are different then defined in options #{inspect(stored_caps)}. - If you want to allow converter to accept different input caps dynamically, use `nil` as input_caps. + Received stream_format #{inspect(stream_format)} are different then the one defined in options #{inspect(stored_stream_format)}. + If you want to allow converter to accept different input stream formats dynamically, use `nil` as input_stream_format. """ end @impl true - def handle_caps(:input, %RawAudio{} = caps, _ctx, state) do - native = mk_native!(caps, state.output_caps) - state = %{state | native: native, input_caps: caps} - {{:ok, caps: {:output, state.output_caps}}, state} + def handle_stream_format(:input, %RawAudio{} = stream_format, _ctx, state) do + native = mk_native!(stream_format, state.output_stream_format) + state = %{state | native: native, input_stream_format: stream_format} + {[stream_format: {:output, state.output_stream_format}], state} + end + + @impl true + def handle_playing(_ctx, state) do + {[stream_format: {:output, state.output_stream_format}], state} end @impl true def handle_process(:input, %Buffer{payload: payload}, _ctx, state) do conversion_result = - convert!(state.native, RawAudio.frame_size(state.input_caps), payload, state.queue) + convert!(state.native, RawAudio.frame_size(state.input_stream_format), payload, state.queue) case conversion_result do {<<>>, queue} -> - {:ok, %{state | queue: queue}} + {[], %{state | queue: queue}} {converted, queue} -> - {{:ok, buffer: {:output, %Buffer{payload: converted}}}, %{state | queue: queue}} + {[buffer: {:output, %Buffer{payload: converted}}], %{state | queue: queue}} end end @@ -135,19 +150,14 @@ defmodule Membrane.FFmpeg.SWResample.Converter do case flush!(state.native) do <<>> -> - {{:ok, end_of_stream: :output}, %{state | queue: <<>>}} + {[end_of_stream: :output], %{state | queue: <<>>}} converted -> - {{:ok, buffer: {:output, %Buffer{payload: converted}}, end_of_stream: :output}, + {[buffer: {:output, %Buffer{payload: converted}}, end_of_stream: :output], %{state | queue: <<>>}} end end - @impl true - def handle_prepared_to_stopped(_ctx, state) do - {:ok, %{state | native: nil}} - end - defp mk_native!( %RawAudio{ sample_format: in_sample_format, diff --git a/mix.exs b/mix.exs index 24cc2fd..6d19d63 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Membrane.FFmpeg.SWResample.Mixfile do use Mix.Project @github_url "https://github.com/membraneframework/membrane_ffmpeg_swresample_plugin" - @version "0.15.0" + @version "0.16.0" def project do [ @@ -40,15 +40,15 @@ defmodule Membrane.FFmpeg.SWResample.Mixfile do defp deps do [ - {:membrane_core, "~> 0.10.0"}, - {:membrane_raw_audio_format, "~> 0.9.0"}, - {:bunch, "~> 1.3.0"}, + {:membrane_core, "~> 0.11.0"}, + {:membrane_raw_audio_format, "~> 0.10.0"}, + {:bunch, "~> 1.5.0"}, {:unifex, "~> 1.0"}, - {:membrane_common_c, "~> 0.13.0"}, + {:membrane_common_c, "~> 0.14.0"}, {:bundlex, "~> 1.0"}, # Testing {:mockery, "~> 2.1", runtime: false}, - {:membrane_file_plugin, "~> 0.9", only: :test}, + {:membrane_file_plugin, "~> 0.13.0"}, # Development {:ex_doc, "~> 0.28", only: :dev, runtime: false}, {:dialyxir, "~> 1.1", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index 8077e34..7bf9975 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,8 @@ %{ - "bimap": {:hex, :bimap, "1.2.1", "cbfd21894abc15b82018df906594fbc40aca8a2d5e6511473acc6ded12f6d7e2", [:mix], [], "hexpm", "635488554b5db7554aa3379bc219c5d75575a1126a1e94ea0818a20c626fd03e"}, - "bunch": {:hex, :bunch, "1.3.1", "f8fe80042f9eb474ef2801ae2c9372f9b13d11e7053265dcfc24b9d912e3750b", [:mix], [], "hexpm", "00e21b16ff9bb698b728a01a2fc4b3bf7fc0e87c4bb9c6e4a442324aa8c5e567"}, + "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, + "bunch": {:hex, :bunch, "1.5.0", "78530e85bc3f53e1711dba654565692e2015cb6d1685e9b53bf7606b14a36c71", [:mix], [], "hexpm", "2c32f8da5d4c9e7a534c8c25aea150da696dd8624ce64f97c21ee193c12258e5"}, "bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"}, - "bundlex": {:hex, :bundlex, "1.0.0", "358c26a6c027359c6935dcd0716b68736b7604599d376c4e1ac17c6618ef6771", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}], "hexpm", "09b4a0c597a31e6e7ce8e103b94f19539bb2279f5966a3d6d46dcd32a5b6fd44"}, + "bundlex": {:hex, :bundlex, "1.0.1", "fd9e68b3636b8b5b678050f1f340e833e6d3890e110766398f38ec9c41da0b44", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:secure_random, "~> 0.5", [hex: :secure_random, repo: "hexpm", optional: false]}], "hexpm", "c6acfd675a4780c9b66c32c0e3c37b96b5314369857c5b7d3ffa198fa1e0085e"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "credo": {:hex, :credo, "1.6.6", "f51f8d45db1af3b2e2f7bee3e6d3c871737bda4a91bff00c5eec276517d1a19c", [:mix], [{:bunt, "~> 0.2.0", [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", "625520ce0984ee0f9f1f198165cd46fa73c1e59a17ebc520038b8fce056a5bdc"}, @@ -15,10 +15,10 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "membrane_common_c": {:hex, :membrane_common_c, "0.13.0", "c314623f93209eb2fa092379954c686f6e50ac89baa48360f836d24f4d53f5ee", [:mix], [{:membrane_core, "~> 0.10.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "90181fbbe481ccd0a4a76daf0300f8ad1b5b0bf0ebd8b42c133904f8839663ca"}, - "membrane_core": {:hex, :membrane_core, "0.10.2", "d2d17039f6df746e4a3c47da32f51867fbafe528272cdd9b226d16b1032bc337", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 2.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6a4f290f919ada66c772807d64d5830be2962b7c13a2f2bc9ace416a1cd19ee1"}, - "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.12.0", "eb940e7a2f2abf30e048bd0b7c2bef9c17c18aa58875b9a833c0bc7e7b1fd709", [:mix], [{:membrane_core, "~> 0.10.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "281b9bf9467beead3f973adce55b9844bc4206bb3f3f60f0db8320a4af4fc5ca"}, - "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.9.0", "c404a6eb38600dd85ad69dcf974b3c82fe0ef07c92e602cd438763dcdaf3462d", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.10.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7b346bd3be6bcc7ceb9fe28ca6a9dfd8b072e9f6960fe5766ea99582319c05df"}, + "membrane_common_c": {:hex, :membrane_common_c, "0.14.0", "35621d9736829bf675062dc0af66e931b0d82ff8361c2088576d63d4d002692e", [:mix], [{:membrane_core, "~> 0.11.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "262b06e93fe3f1be57a111fa19f5cba4f26000a442ac32a59f3678e83cbb001f"}, + "membrane_core": {:hex, :membrane_core, "0.11.0", "63ae9f56834ec67680d634d8d69f71b2d46b94f4a0ec8fafcf22d8ce216b8f41", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 2.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "097584018fe948fa3013bfd6bcf002b3ad7cbd13f2259be4f1903f37a7aad7ab"}, + "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.13.0", "5d82476706410d372718208b48ace8bbf131f4466bca8f88ca015ebc4dbf5bad", [:mix], [{:membrane_core, "~> 0.11", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "efa63530c1a9cf7e8da6ca5b3af9598573a007c89fc7bedf02ffe4ffc1753e66"}, + "membrane_raw_audio_format": {:hex, :membrane_raw_audio_format, "0.10.0", "4ff07002d95b8e9259c38991ead8d98e907ee66346545c168b8a8ad6ae73d87e", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}, {:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.11", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "d5b4201d0e634e9fdbc2b9528f1fee245eb667c82ace91c745af6604401003ae"}, "mockery": {:hex, :mockery, "2.3.1", "a02fd60b10ac9ed37a7a2ecf6786c1f1dd5c75d2b079a60594b089fba32dc087", [:mix], [], "hexpm", "1d0971d88ebf084e962da3f2cfee16f0ea8e04ff73a7710428500d4500b947fa"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, diff --git a/test/membrane_ffmpeg_swresample_plugin/converter_test.exs b/test/membrane_ffmpeg_swresample_plugin/converter_test.exs index bad62e2..2d2b11b 100644 --- a/test/membrane_ffmpeg_swresample_plugin/converter_test.exs +++ b/test/membrane_ffmpeg_swresample_plugin/converter_test.exs @@ -7,13 +7,13 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do @module Converter @native Converter.Native - @s16le_caps %RawAudio{ + @s16le_format %RawAudio{ channels: 2, sample_format: :s16le, sample_rate: 48_000 } - @u8_caps %RawAudio{ + @u8_format %RawAudio{ channels: 1, sample_format: :u8, sample_rate: 44_100 @@ -22,9 +22,9 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do defp initial_state(_ctx) do %{ state: %{ - input_caps: nil, - input_caps_provided?: false, - output_caps: @u8_caps, + input_stream_format: nil, + input_stream_format_provided?: false, + output_stream_format: @u8_format, frames_per_buffer: 2048, native: nil, queue: <<>> @@ -32,21 +32,22 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do } end - defp test_handle_caps(state) do + defp test_handle_stream_format(state) do Mockery.History.enable_history() mock(@native, [create: 6], {:ok, :mock_handle}) - assert {{:ok, actions}, new_state} = @module.handle_caps(:input, @s16le_caps, nil, state) - assert actions == [caps: {:output, state.output_caps}] + assert {actions, new_state} = @module.handle_stream_format(:input, @s16le_format, nil, state) + + assert actions == [stream_format: {:output, state.output_stream_format}] assert %{native: :mock_handle} = new_state - input_fmt = @s16le_caps.sample_format |> RawAudio.SampleFormat.serialize() - input_rate = @s16le_caps.sample_rate - input_channel = @s16le_caps.channels - out_fmt = @u8_caps.sample_format |> RawAudio.SampleFormat.serialize() - out_rate = @u8_caps.sample_rate - out_channel = @u8_caps.channels + input_fmt = @s16le_format.sample_format |> RawAudio.SampleFormat.serialize() + input_rate = @s16le_format.sample_rate + input_channel = @s16le_format.channels + out_fmt = @u8_format.sample_format |> RawAudio.SampleFormat.serialize() + out_rate = @u8_format.sample_rate + out_channel = @u8_format.channels assert_called( @native, @@ -58,30 +59,32 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do setup_all :initial_state - describe "handle_stopped_to_prepared/2" do - test "should do nothing if input caps are not set", %{state: state} do - assert @module.handle_stopped_to_prepared(nil, state) == {:ok, state} + describe "handle_setup/2" do + test "should do nothing if input stream format is not set", %{state: state} do + assert @module.handle_setup(nil, state) == {[], state} refute_called(@native, :create) end - test "create native converter if caps are set", %{state: initial_state} do - state = %{initial_state | input_caps: @s16le_caps, input_caps_provided?: true} + test "create native converter if stream format is set", %{state: initial_state} do + state = %{ + initial_state + | input_stream_format: @s16le_format, + input_stream_format_provided?: true + } + Mockery.History.enable_history() mock(@native, [create: 6], {:ok, :mock_handle}) - assert {{:ok, actions}, new_state} = @module.handle_stopped_to_prepared(:stopped, state) - - assert length(actions) == 1 - assert {:output, state.output_caps} == actions[:caps] + assert {[], new_state} = @module.handle_setup(:stopped, state) assert %{native: :mock_handle} = new_state - input_fmt = @s16le_caps.sample_format |> RawAudio.SampleFormat.serialize() - input_rate = @s16le_caps.sample_rate - input_channel = @s16le_caps.channels - out_fmt = @u8_caps.sample_format |> RawAudio.SampleFormat.serialize() - out_rate = @u8_caps.sample_rate - out_channel = @u8_caps.channels + input_fmt = @s16le_format.sample_format |> RawAudio.SampleFormat.serialize() + input_rate = @s16le_format.sample_rate + input_channel = @s16le_format.channels + out_fmt = @u8_format.sample_format |> RawAudio.SampleFormat.serialize() + out_rate = @u8_format.sample_rate + out_channel = @u8_format.channels assert_called( @native, @@ -94,32 +97,42 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do test "if native cannot be created returns an error with reason and untouched state", %{ state: initial_state } do - state = %{initial_state | input_caps: @s16le_caps, input_caps_provided?: true} + state = %{ + initial_state + | input_stream_format: @s16le_format, + input_stream_format_provided?: true + } + mock(@native, [create: 6], {:error, :reason}) - assert_raise RuntimeError, fn -> @module.handle_stopped_to_prepared(:stopped, state) end + assert_raise RuntimeError, fn -> @module.handle_setup(nil, state) end end end - describe "handle_caps/4" do - test "given new caps when input_caps were not set should create native resource and store it in state", + describe "handle_stream_format/4" do + test "given new stream format when input_stream_format were not set should create native resource and store it in state", %{state: state} do - test_handle_caps(state) + test_handle_stream_format(state) end - test "given the same caps as set in options should create native resource and store it in state", + test "given the same stream format as set in options should create native resource and store it in state", %{state: initial_state} do - state = %{initial_state | input_caps: @s16le_caps} - test_handle_caps(state) + state = %{initial_state | input_stream_format: @s16le_format} + test_handle_stream_format(state) end - test "should raise when received caps don't match caps input_caps set in options", %{ - state: initial_state - } do - state = %{initial_state | input_caps: @s16le_caps, input_caps_provided?: true} + test "should raise when received stream format don't match stream format input_stream_format set in options", + %{ + state: initial_state + } do + state = %{ + initial_state + | input_stream_format: @s16le_format, + input_stream_format_provided?: true + } assert_raise RuntimeError, fn -> - @module.handle_caps(:input, @u8_caps, nil, state) + @module.handle_stream_format(:input, @u8_format, nil, state) end end @@ -127,18 +140,21 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do state: state } do mock(@native, [create: 6], {:error, :reason}) - assert_raise RuntimeError, fn -> @module.handle_caps(:input, @s16le_caps, nil, state) end + + assert_raise RuntimeError, fn -> + @module.handle_stream_format(:input, @s16le_format, nil, state) + end end end describe "handle_process/4 should" do test "store payload in queue until there are at least 2 frames", %{state: initial_state} do - state = %{initial_state | native: :mock_handle, input_caps: @s16le_caps} + state = %{initial_state | native: :mock_handle, input_stream_format: @s16le_format} payload = <<0::3*8>> buffer = %Membrane.Buffer{payload: payload} mock(@native, [convert: 2], {:error, :reason}) - assert {:ok, new_state} = @module.handle_process(:input, buffer, nil, state) + assert {[], new_state} = @module.handle_process(:input, buffer, nil, state) assert new_state == %{state | queue: payload} refute_called(@native, :convert) @@ -149,7 +165,7 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do initial_state | queue: <<250, 250, 0>>, native: :mock_handle, - input_caps: @s16le_caps + input_stream_format: @s16le_format } payload = <<0::7*8>> @@ -157,16 +173,10 @@ defmodule Membrane.FFmpeg.SWResample.ConverterTest do result = <<250, 0, 0, 0>> mock(@native, [convert: 2], {:ok, result}) - assert {{:ok, actions}, new_state} = @module.handle_process(:input, buffer, nil, state) + assert {actions, new_state} = @module.handle_process(:input, buffer, nil, state) assert actions == [buffer: {:output, %Membrane.Buffer{payload: result}}] assert new_state == %{state | queue: <<0::2*8>>} end end - - test "handle_prepared_to_stopped should remove native from state", %{state: initial_state} do - state = %{initial_state | native: :mock_handle} - assert {:ok, new_state} = @module.handle_prepared_to_stopped(nil, state) - assert new_state == %{state | native: nil} - end end diff --git a/test/membrane_ffmpeg_swresample_plugin/pipeline_test.exs b/test/membrane_ffmpeg_swresample_plugin/pipeline_test.exs index cf95e10..11c6216 100644 --- a/test/membrane_ffmpeg_swresample_plugin/pipeline_test.exs +++ b/test/membrane_ffmpeg_swresample_plugin/pipeline_test.exs @@ -1,38 +1,39 @@ defmodule Membrane.FFmpeg.SWResample.PipelineTest do use ExUnit.Case, async: true + import Membrane.ChildrenSpec import Membrane.Testing.Assertions - alias Membrane.{Testing, RawAudio} alias Membrane.FFmpeg.SWResample.Converter + alias Membrane.{RawAudio, Testing} @tag :tmp_dir test "surrounded by testing source and sink", %{tmp_dir: tmp_dir} do - input_caps = %RawAudio{sample_format: :u8, sample_rate: 8_000, channels: 1} - output_caps = %RawAudio{sample_format: :s16le, sample_rate: 16_000, channels: 2} + input_stream_format = %RawAudio{sample_format: :u8, sample_rate: 8_000, channels: 1} + output_stream_format = %RawAudio{sample_format: :s16le, sample_rate: 16_000, channels: 2} frames = 8_000 - input_time = RawAudio.frames_to_time(frames, input_caps) + input_time = RawAudio.frames_to_time(frames, input_stream_format) fixture_path = Path.expand(Path.join(__DIR__, "/../fixtures/input_u8_mono_8khz.raw")) output_path = Path.expand(Path.join(tmp_dir, "output_s16le_stereo_16khz.raw")) - children = [ - source: %Membrane.File.Source{location: fixture_path}, - resampler: %Converter{input_caps: input_caps, output_caps: output_caps}, - sink: %Membrane.File.Sink{location: output_path} + structure = [ + child(:source, %Membrane.File.Source{location: fixture_path}) + |> child(:resampler, %Converter{ + input_stream_format: input_stream_format, + output_stream_format: output_stream_format + }) + |> child(:sink, %Membrane.File.Sink{location: output_path}) ] - opts = [ - links: Membrane.ParentSpec.link_linear(children) - ] + pipeline = Testing.Pipeline.start_link_supervised!(structure: structure) - assert {:ok, pipeline} = Testing.Pipeline.start_link(opts) - assert_pipeline_playback_changed(pipeline, :prepared, :playing) + assert_pipeline_setup(pipeline) assert_end_of_stream(pipeline, :sink) Testing.Pipeline.terminate(pipeline, blocking?: true) assert result = File.read!(output_path) - assert byte_size(result) == RawAudio.time_to_bytes(input_time, output_caps) + assert byte_size(result) == RawAudio.time_to_bytes(input_time, output_stream_format) end end