Skip to content

Commit b5c8caa

Browse files
authored
Merge pull request #14 from membraneframework/support-pixel-format
Support pixel format in RawVideo
2 parents 124f7f9 + 52e16fc commit b5c8caa

8 files changed

Lines changed: 91 additions & 9 deletions

File tree

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: 20 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
"""
@@ -69,6 +85,7 @@ defmodule Membrane.Transcoder do
6985
spec:
7086
stream_format()
7187
| stream_format_module()
88+
| stream_format_tuple()
7289
| stream_format_resolver(),
7390
description: """
7491
An option specifying desired output format.
@@ -206,6 +223,9 @@ defmodule Membrane.Transcoder do
206223
module when is_atom(module) ->
207224
%{state | output_stream_format: struct(module)}
208225

226+
{module, opts} when is_atom(module) and is_list(opts) ->
227+
%{state | output_stream_format: struct(module, opts)}
228+
209229
resolver when is_function(resolver) ->
210230
%{state | output_stream_format: resolver.(state.input_stream_format)}
211231
|> 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: 44 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,38 @@ defmodule Membrane.Transcoder.Video do
135147
builder
136148
end
137149

150+
defp maybe_plug_swscale_converter(builder, input_format, %RawVideo{} = output_format) do
151+
case input_format do
152+
_any when output_format.pixel_format == nil ->
153+
builder
154+
155+
%RawVideo{pixel_format: pixel_format} when pixel_format == output_format.pixel_format ->
156+
builder
157+
158+
_input_format ->
159+
builder
160+
|> child(:raw_video_converter, %SWScale.Converter{format: output_format.pixel_format})
161+
end
162+
end
163+
164+
defp maybe_plug_swscale_converter(builder, input_format, %h26x{}) when h26x in [H264, H265] do
165+
case input_format do
166+
%RawVideo{pixel_format: pixel_format} when pixel_format in [:I420, :I422] ->
167+
builder
168+
169+
%h26x{} when h26x in [H264, H265] ->
170+
builder
171+
172+
_input_format ->
173+
builder
174+
|> child(:raw_video_converter, %SWScale.Converter{format: :I420})
175+
end
176+
end
177+
178+
defp maybe_plug_swscale_converter(builder, _input_format, _output_format) do
179+
builder
180+
end
181+
138182
defp maybe_plug_encoder_and_parser(builder, %H264{} = h264) do
139183
builder
140184
|> 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: 14 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)
@@ -57,7 +57,19 @@ defmodule Membrane.Transcoder.IntegrationTest do
5757

5858
Testing.Pipeline.execute_actions(pid, spec: spec)
5959

60-
assert_sink_stream_format(pid, :sink, %unquote(test_case.output_format){})
60+
case unquote(test_case.output_format) do
61+
{module, opts} when is_atom(module) ->
62+
assert_sink_stream_format(pid, :sink, %^module{} = received_format)
63+
64+
for {key, value} <- opts do
65+
assert Map.get(received_format, key) == value
66+
end
67+
68+
module when is_atom(module) ->
69+
assert_sink_stream_format(pid, :sink, received_format)
70+
assert received_format.__struct__ == module
71+
end
72+
6173
Testing.Pipeline.terminate(pid)
6274
end
6375
end)

test/test_helper.exs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
ExUnit.start(capture_log: true)
1+
# System.schedulers_online() * 2 is the default value of max_cases
2+
3+
max_cases =
4+
if System.get_env("CIRCLECI") == "true",
5+
do: 1,
6+
else: System.schedulers_online() * 2
7+
8+
ExUnit.start(capture_log: true, max_cases: max_cases)

0 commit comments

Comments
 (0)