Skip to content

Commit 86f1558

Browse files
committed
Support pixel format conversions in raw video
1 parent f68c245 commit 86f1558

File tree

7 files changed

+70
-8
lines changed

7 files changed

+70
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The package can be installed by adding `membrane_transcoder_plugin` to your list
1717
```elixir
1818
def deps do
1919
[
20-
{:membrane_transcoder_plugin, "~> 0.3.2"}
20+
{:membrane_transcoder_plugin, "~> 0.3.3"}
2121
]
2222
end
2323
```

lib/transcoder.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ defmodule Membrane.Transcoder do
2020
* `Membrane.RawAudio`
2121
* `Membrane.RemoteStream{content_format: Membrane.Opus}` (only as an input stream)
2222
* `Membrane.RemoteStream{content_format: Membrane.MPEGAudio}` (only as an input stream)
23+
24+
While `#{inspect(__MODULE__)}` can transcode between different stream formats, it can also be used
25+
to change some parameters of the stream format.
26+
Now, the only supported stream parameters are:
27+
* `:pixel_format` in `Membrane.RawVideo`
28+
* `:alignment` and `:stream_structure` in `Membrane.H264` and `Membrane.H265`
2329
"""
2430
use Membrane.Bin
2531

@@ -51,6 +57,16 @@ defmodule Membrane.Transcoder do
5157
@type stream_format_module ::
5258
H264 | H265 | VP8 | VP9 | RawVideo | AAC | Opus | Membrane.MPEGAudio | RawAudio
5359

60+
@typedoc """
61+
Describes a tuple consisting of a stream format module and its options.
62+
63+
An alternative to `t:#{inspect(__MODULE__)}.stream_format/0`.
64+
65+
Allows you to specify some fields of the output stream format, without the need to
66+
set all keys required by the struct.
67+
"""
68+
@type stream_format_tuple :: {stream_format_module(), keyword()}
69+
5470
@typedoc """
5571
Describes a function which can be used to provide output format based on the input format.
5672
"""
@@ -206,6 +222,9 @@ defmodule Membrane.Transcoder do
206222
module when is_atom(module) ->
207223
%{state | output_stream_format: struct(module)}
208224

225+
{module, opts} when is_atom(module) and is_list(opts) ->
226+
%{state | output_stream_format: struct(module, opts)}
227+
209228
resolver when is_function(resolver) ->
210229
%{state | output_stream_format: resolver.(state.input_stream_format)}
211230
|> resolve_output_stream_format()

lib/transcoder/audio.ex

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ defmodule Membrane.Transcoder.Audio do
5858
defguard is_mp3_compliant(format)
5959
when is_map_key(format, :sample_rate) and format.sample_rate == 44_100 and
6060
is_map_key(format, :sample_format) and format.sample_format == :s32le and
61-
is_map_key(
62-
format,
63-
:channels
64-
) and format.channels == 2
61+
is_map_key(format, :channels) and format.channels == 2
6562

6663
@spec plug_audio_transcoding(
6764
ChildrenSpec.builder(),

lib/transcoder/video.ex

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Membrane.Transcoder.Video do
44
import Membrane.ChildrenSpec
55
require Membrane.Logger
66
alias Membrane.{ChildrenSpec, H264, H265, RawVideo, RemoteStream, VP8, VP9}
7+
alias Membrane.FFmpeg.SWScale
78

89
@type video_stream_format :: VP8.t() | VP9.t() | H264.t() | H265.t() | RawVideo.t()
910

@@ -72,6 +73,16 @@ defmodule Membrane.Transcoder.Video do
7273
})
7374
end
7475

76+
defp do_plug_video_transcoding(
77+
builder,
78+
%RawVideo{} = input_format,
79+
%RawVideo{} = output_format,
80+
_transcoding_policy
81+
) do
82+
builder
83+
|> maybe_plug_swscale_converter(input_format, output_format)
84+
end
85+
7586
defp do_plug_video_transcoding(
7687
builder,
7788
%format_module{},
@@ -96,6 +107,7 @@ defmodule Membrane.Transcoder.Video do
96107
defp do_plug_video_transcoding(builder, input_format, output_format, _transcoding_policy) do
97108
builder
98109
|> maybe_plug_parser_and_decoder(input_format)
110+
|> maybe_plug_swscale_converter(input_format, output_format)
99111
|> maybe_plug_encoder_and_parser(output_format)
100112
end
101113

@@ -135,6 +147,21 @@ defmodule Membrane.Transcoder.Video do
135147
builder
136148
end
137149

150+
defp maybe_plug_swscale_converter(builder, input_format, output_format) do
151+
# output pixel format is nil when the transcoder outptu format was set to
152+
# Membrane.RawVideo module without specifying pixel_format field
153+
154+
if pixel_format(output_format) not in [nil, pixel_format(input_format)] do
155+
builder
156+
|> child(:raw_video_converter, %SWScale.Converter{format: pixel_format(output_format)})
157+
else
158+
builder
159+
end
160+
end
161+
162+
defp pixel_format(%RawVideo{pixel_format: pixel_format}), do: pixel_format
163+
defp pixel_format(_encoded_video), do: :I420
164+
138165
defp maybe_plug_encoder_and_parser(builder, %H264{} = h264) do
139166
builder
140167
|> child(:h264_encoder, %H264.FFmpeg.Encoder{preset: :ultrafast})

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Membrane.Transcoder.Plugin.Mixfile do
22
use Mix.Project
33

4-
@version "0.3.2"
4+
@version "0.3.3"
55
@github_url "https://github.com/membraneframework/membrane_transcoder_plugin"
66

77
def project do
@@ -46,6 +46,7 @@ defmodule Membrane.Transcoder.Plugin.Mixfile do
4646
{:membrane_h264_ffmpeg_plugin, "~> 0.32.0"},
4747
{:membrane_h265_ffmpeg_plugin, "~> 0.4.2"},
4848
{:membrane_ffmpeg_swresample_plugin, "~> 0.20.0"},
49+
{:membrane_ffmpeg_swscale_plugin, "~> 0.16.2"},
4950
{:membrane_timestamp_queue, "~> 0.2.2"},
5051
{:membrane_h264_format, "~> 0.6.1"},
5152
{:membrane_h265_format, "~> 0.2.0"},

mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.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", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"},
2727
"membrane_core": {:hex, :membrane_core, "1.2.2", "14c1c5f6b5cae0defb849309bc0b18b5d284e2e4b9c35b9486077a3d0763b8cd", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f53bf0101d0583e959534895117d47c615c7fe5fcf37f8f1e4a3f7a5ca468a93"},
2828
"membrane_ffmpeg_swresample_plugin": {:hex, :membrane_ffmpeg_swresample_plugin, "0.20.2", "2e669f0b25418d10b51a73bc52d2e12e4a3a26b416c5c1199d852c3f781a18b3", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_audio_format, "~> 0.12.0", [hex: :membrane_raw_audio_format, repo: "hexpm", optional: false]}, {:mockery, "~> 2.1", [hex: :mockery, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6c8d3bcd61d568dd94cabb9b45f29e8926e0076e4432d8f419378e004e02147c"},
29+
"membrane_ffmpeg_swscale_plugin": {:hex, :membrane_ffmpeg_swscale_plugin, "0.16.2", "581909312d6d12ed560ee99caa1b1674a339760ab2ad6835d243326806c23da1", [:mix], [{:bundlex, "~> 1.2", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.1", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "46c185dacff1e1b404d0ceb74d0d5224f0931fe1e8b951cc3776ebd099e39afc"},
2930
"membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"},
3031
"membrane_funnel_plugin": {:hex, :membrane_funnel_plugin, "0.9.1", "9e108f4ef9d905ebff2da3ba5e58a5b756b58812f4fa68bd576add68fda310a0", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "39fdef1bf29eac949f65a37ea941f997c22ed042c55af044d27a781b63e82f6b"},
3132
"membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.4", "5548a37642125d37b9ae4df26d59ef5397781b84101b7b60fe8b4fdb70dc536d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "6878c763b2f0e0fd1651e8cbac3707b3c58a51858acceca7ad0d1c1786f5e3ec"},

test/integration_test.exs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ defmodule Membrane.Transcoder.IntegrationTest do
1414
%{input_format: VP8, input_file: "video_vp8.ivf", preprocess: &Preprocessors.parse_vpx/1},
1515
%{input_format: VP9, input_file: "video_vp9.ivf", preprocess: &Preprocessors.parse_vpx/1}
1616
]
17-
@video_outputs [RawVideo, H264, H265, VP8, VP9]
17+
@video_outputs [RawVideo, {RawVideo, pixel_format: :RGB}, H264, H265, VP8, VP9]
1818
@video_cases for input <- @video_inputs,
1919
output <- @video_outputs,
2020
do: Map.put(input, :output_format, output)
@@ -37,6 +37,11 @@ defmodule Membrane.Transcoder.IntegrationTest do
3737
@test_cases @video_cases ++ @audio_cases
3838

3939
Enum.map(@test_cases, fn test_case ->
40+
if test_case.input_format == RawVideo and
41+
test_case.output_format == {RawVideo, pixel_format: :RGB} do
42+
@tag :xd
43+
end
44+
4045
test "if transcoder supports #{inspect(test_case.input_format)} input and #{inspect(test_case.output_format)} output" do
4146
pid = Testing.Pipeline.start_link_supervised!()
4247

@@ -57,7 +62,19 @@ defmodule Membrane.Transcoder.IntegrationTest do
5762

5863
Testing.Pipeline.execute_actions(pid, spec: spec)
5964

60-
assert_sink_stream_format(pid, :sink, %unquote(test_case.output_format){})
65+
case unquote(test_case.output_format) do
66+
{module, opts} when is_atom(module) ->
67+
assert_sink_stream_format(pid, :sink, %^module{} = received_format)
68+
69+
for {key, value} <- opts do
70+
assert Map.get(received_format, key) == value
71+
end
72+
73+
module when is_atom(module) ->
74+
assert_sink_stream_format(pid, :sink, received_format)
75+
assert received_format.__struct__ == module
76+
end
77+
6178
Testing.Pipeline.terminate(pid)
6279
end
6380
end)

0 commit comments

Comments
 (0)