diff --git a/README.md b/README.md index 1bf1f0f..511cde0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The package can be installed by adding `membrane_transcoder_plugin` to your list ```elixir def deps do [ - {:membrane_transcoder_plugin, "~> 0.2.2"} + {:membrane_transcoder_plugin, "~> 0.3.0"} ] end ``` diff --git a/lib/transcoder.ex b/lib/transcoder.ex index 625a56d..c6a1420 100644 --- a/lib/transcoder.ex +++ b/lib/transcoder.ex @@ -80,16 +80,35 @@ defmodule Membrane.Transcoder do and is supposed to return the desired output stream format or its module. """ ], - force_transcoding?: [ - spec: boolean() | (stream_format() -> boolean()), - default: false, + transcoding_policy: [ + spec: + :always + | :if_needed + | :never + | (stream_format() -> :always | :if_needed | :never), + default: :if_needed, description: """ - If set to `true`, the input media stream will be decoded and encoded, even - if the input stream format and the output stream format are the same type. + Specifies, when transcoding should be applied. Can be either: - * a boolean, - * a function that receives the input stream format and returns a boolean. + * an atom: `:always`, `:if_needed` (default) or `:never`, + * a function that receives the input stream format and returns either `:always`, + `:if_needed` or `:never`. + + If set to `:always`, the input media stream will be decoded and encoded, even + if the input stream format and the output stream format are the same type. + + If set to `:if_needed`, the input media stream will be transcoded only if the input + stream format and the output stream format are different types. + This is the default behavior. + + If set to `:never`, the input media stream won't be neither decoded nor encoded. + Changing alignment, encapsulation or stream structure is still possible. This option + is helpful when you want to ensure that #{inspect(__MODULE__)} will not use too much + of resources, e.g. CPU or memory. + + If the transition from the input stream format to the output stream format is not + possible without decoding or encoding the stream, an error will be raised. """ ], assumed_input_stream_format: [ @@ -152,8 +171,8 @@ defmodule Membrane.Transcoder do |> resolve_output_stream_format() state = - with %{force_transcoding?: f} when is_function(f) <- state do - %{state | force_transcoding?: f.(format)} + with %{transcoding_policy: f} when is_function(f) <- state do + %{state | transcoding_policy: f.(format)} end spec = @@ -161,7 +180,7 @@ defmodule Membrane.Transcoder do |> plug_transcoding( format, state.output_stream_format, - state.force_transcoding? + state.transcoding_policy ) |> get_child(:output_funnel) @@ -203,15 +222,15 @@ defmodule Membrane.Transcoder do end end - defp plug_transcoding(builder, input_format, output_format, force_transcoding?) + defp plug_transcoding(builder, input_format, output_format, transcoding_policy) when Audio.is_audio_format(input_format) do builder - |> Audio.plug_audio_transcoding(input_format, output_format, force_transcoding?) + |> Audio.plug_audio_transcoding(input_format, output_format, transcoding_policy) end - defp plug_transcoding(builder, input_format, output_format, force_transcoding?) + defp plug_transcoding(builder, input_format, output_format, transcoding_policy) when Video.is_video_format(input_format) do builder - |> Video.plug_video_transcoding(input_format, output_format, force_transcoding?) + |> Video.plug_video_transcoding(input_format, output_format, transcoding_policy) end end diff --git a/lib/transcoder/audio.ex b/lib/transcoder/audio.ex index 559b9b1..e2dfe5f 100644 --- a/lib/transcoder/audio.ex +++ b/lib/transcoder/audio.ex @@ -52,17 +52,18 @@ defmodule Membrane.Transcoder.Audio do audio_stream_format(), boolean() ) :: ChildrenSpec.builder() - def plug_audio_transcoding(builder, input_format, output_format, force_transcoding?) + def plug_audio_transcoding(builder, input_format, output_format, transcoding_policy) when is_audio_format(input_format) and is_audio_format(output_format) do - do_plug_audio_transcoding(builder, input_format, output_format, force_transcoding?) + do_plug_audio_transcoding(builder, input_format, output_format, transcoding_policy) end defp do_plug_audio_transcoding( builder, %format_module{}, %format_module{}, - false = _force_transcoding? - ) do + transcoding_policy + ) + when transcoding_policy in [:if_needed, :never] do Membrane.Logger.debug(""" This bin will only forward buffers, as the input stream format is the same as the output stream format. """) @@ -74,12 +75,20 @@ defmodule Membrane.Transcoder.Audio do builder, %RemoteStream{content_format: Opus}, %Opus{}, - false = _force_transcoding? - ) do + transcoding_policy + ) + when transcoding_policy in [:if_needed, :never] do builder |> child(:opus_parser, Opus.Parser) end - defp do_plug_audio_transcoding(builder, input_format, output_format, _force_transcoding?) do + defp do_plug_audio_transcoding(_builder, input_format, output_format, :never) do + raise """ + Cannot convert input format #{inspect(input_format)} to output format #{inspect(output_format)} \ + with :transcoding_policy option set to :never. + """ + end + + defp do_plug_audio_transcoding(builder, input_format, output_format, _transcoding_policy) do builder |> maybe_plug_parser(input_format) |> maybe_plug_decoder(input_format) diff --git a/lib/transcoder/video.ex b/lib/transcoder/video.ex index 08012f3..4b7a64f 100644 --- a/lib/transcoder/video.ex +++ b/lib/transcoder/video.ex @@ -19,17 +19,13 @@ defmodule Membrane.Transcoder.Video do video_stream_format(), boolean() ) :: ChildrenSpec.builder() - def plug_video_transcoding(builder, input_format, output_format, force_transcoding?) + def plug_video_transcoding(builder, input_format, output_format, transcoding_policy) when is_video_format(input_format) and is_video_format(output_format) do - do_plug_video_transcoding(builder, input_format, output_format, force_transcoding?) + do_plug_video_transcoding(builder, input_format, output_format, transcoding_policy) end - defp do_plug_video_transcoding( - builder, - %H264{}, - %H264{} = output_format, - false = _force_transcoding? - ) do + defp do_plug_video_transcoding(builder, %H264{}, %H264{} = output_format, transcoding_policy) + when transcoding_policy in [:if_needed, :never] do builder |> child(:h264_parser, %H264.Parser{ output_stream_structure: stream_structure_type(output_format), @@ -37,12 +33,8 @@ defmodule Membrane.Transcoder.Video do }) end - defp do_plug_video_transcoding( - builder, - %H265{}, - %H265{} = output_format, - false = _force_transcoding? - ) do + defp do_plug_video_transcoding(builder, %H265{}, %H265{} = output_format, transcoding_policy) + when transcoding_policy in [:if_needed, :never] do builder |> child(:h265_parser, %H265.Parser{ output_stream_structure: stream_structure_type(output_format), @@ -54,8 +46,9 @@ defmodule Membrane.Transcoder.Video do builder, %format_module{}, %format_module{}, - false = _force_transcoding? - ) do + transcoding_policy + ) + when transcoding_policy in [:if_needed, :never] do Membrane.Logger.debug(""" This bin will only forward buffers, as the input stream format is the same type as the output stream format. """) @@ -63,7 +56,14 @@ defmodule Membrane.Transcoder.Video do builder end - defp do_plug_video_transcoding(builder, input_format, output_format, _force_transcoding?) do + defp do_plug_video_transcoding(_builder, input_format, output_format, :never) do + raise """ + Cannot convert input format #{inspect(input_format)} to output format #{inspect(output_format)} \ + with :transcoding_policy option set to :never. + """ + end + + defp do_plug_video_transcoding(builder, input_format, output_format, _transcoding_policy) do builder |> maybe_plug_parser_and_decoder(input_format) |> maybe_plug_encoder_and_parser(output_format) diff --git a/mix.exs b/mix.exs index c62c1c0..2975d38 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Membrane.Transcoder.Plugin.Mixfile do use Mix.Project - @version "0.2.2" + @version "0.3.0" @github_url "https://github.com/membraneframework/membrane_transcoder_plugin" def project do diff --git a/test/integration_test.exs b/test/integration_test.exs index e1bd4b1..7ec250c 100644 --- a/test/integration_test.exs +++ b/test/integration_test.exs @@ -76,14 +76,20 @@ defmodule Membrane.Transcoder.IntegrationTest do do: {[stream_format: {:output, state.format}], state} end - test "if encoder and decoder are spawned or not, depending on the value of `force_transcoding?` option" do + test "if encoder and decoder are spawned or not, depending on the value of `transcoding_policy` option" do for format <- [%AAC{channels: 1}, %H264{alignment: :au, stream_structure: :annexb}], - force_transcoding? <- [true, false] do + transcoding_policy <- [:always, :if_needed, :never] do + output_format = + case format do + %H264{} -> %H264{format | stream_structure: :avc1} + %AAC{} -> format + end + spec = child(:source, %FormatSource{format: format}) |> child(:transcoder, %Membrane.Transcoder{ - output_stream_format: format, - force_transcoding?: force_transcoding? + output_stream_format: output_format, + transcoding_policy: transcoding_policy }) |> child(:sink, Testing.Sink) @@ -98,7 +104,7 @@ defmodule Membrane.Transcoder.IntegrationTest do |> Enum.each(fn child_name -> get_child_result = Testing.Pipeline.get_child_pid(pipeline, [:transcoder, child_name]) - if force_transcoding? do + if transcoding_policy == :always do assert {:ok, child_pid} = get_child_result assert is_pid(child_pid) else @@ -109,4 +115,25 @@ defmodule Membrane.Transcoder.IntegrationTest do Testing.Pipeline.terminate(pipeline) end end + + test "if transcoder raises when `transcoding_policy` is set to `:never` and formats don't match" do + spec = + child(:source, %FormatSource{format: %H264{alignment: :au, stream_structure: :annexb}}) + |> child(:transcoder, %Membrane.Transcoder{ + output_stream_format: VP8, + transcoding_policy: :never + }) + |> child(:sink, Testing.Sink) + + {:ok, supervisor, pipeline} = Testing.Pipeline.start(spec: []) + supervisor_ref = Process.monitor(supervisor) + pipeline_ref = Process.monitor(pipeline) + + Testing.Pipeline.execute_actions(pipeline, spec: spec) + + assert_receive {:DOWN, ^pipeline_ref, :process, _pid, + {:membrane_child_crash, :transcoder, {%RuntimeError{}, _stacktrace}}} + + assert_receive {:DOWN, ^supervisor_ref, :process, _pid, _reason} + end end