Skip to content

Commit 407d663

Browse files
committed
Merge remote-tracking branch 'origin/master' into transcoding_policy
2 parents 3115694 + d490293 commit 407d663

File tree

12 files changed

+200
-52
lines changed

12 files changed

+200
-52
lines changed

examples/vp8_to_h264.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ defmodule Example do
2828
end
2929

3030
File.mkdir("tmp")
31-
Example.convert(Path.join("./test/fixtures", "video.ivf"), Path.join("./tmp", "video.h264"))
31+
Example.convert(Path.join("./test/fixtures", "video_vp8.ivf"), Path.join("./tmp", "video.h264"))

lib/transcoder.ex

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ defmodule Membrane.Transcoder do
88
* `Membrane.H264`
99
* `Membrane.H265`
1010
* `Membrane.VP8`
11+
* `Membrane.VP9`
1112
* `Membrane.RawVideo`
13+
* `Membrane.RemoteStream{content_type: Membrane.VP8}` (only as an input stream)
14+
* `Membrane.RemoteStream{content_type: Membrane.VP9}` (only as an input stream)
1215
1316
The following audio stream formats are supported:
1417
* `Membrane.AAC`
1518
* `Membrane.Opus`
19+
* `Membrane.MPEGAudio`
1620
* `Membrane.RawAudio`
1721
* `Membrane.RemoteStream{content_type: Membrane.Opus}` (only as an input stream)
22+
* `Membrane.RemoteStream{content_type: Membrane.MPEGAudio}` (only as an input stream)
1823
"""
1924
use Membrane.Bin
2025

@@ -23,7 +28,7 @@ defmodule Membrane.Transcoder do
2328
require Membrane.Logger
2429

2530
alias __MODULE__.{Audio, Video}
26-
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8}
31+
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8, VP9}
2732

2833
@typedoc """
2934
Describes stream formats acceptable on the bin's input and output.
@@ -32,24 +37,30 @@ defmodule Membrane.Transcoder do
3237
H264.t()
3338
| H265.t()
3439
| VP8.t()
40+
| VP9.t()
3541
| RawVideo.t()
3642
| AAC.t()
3743
| Opus.t()
44+
| Membrane.MPEGAudio.t()
3845
| RemoteStream.t()
3946
| RawAudio.t()
4047

4148
@typedoc """
4249
Describes stream format modules that can be used to define inputs and outputs of the bin.
4350
"""
44-
@type stream_format_module :: H264 | H265 | VP8 | RawVideo | AAC | Opus | RawAudio
51+
@type stream_format_module ::
52+
H264 | H265 | VP8 | VP9 | RawVideo | AAC | Opus | Membrane.MPEGAudio | RawAudio
4553

4654
@typedoc """
4755
Describes a function which can be used to provide output format based on the input format.
4856
"""
4957
@type stream_format_resolver :: (stream_format() -> stream_format() | stream_format_module())
5058

5159
def_input_pad :input,
52-
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format)
60+
accepted_format:
61+
format
62+
when Audio.is_audio_format(format) or Video.is_video_format(format) or
63+
format.__struct__ == RemoteStream
5364

5465
def_output_pad :output,
5566
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format)
@@ -99,12 +110,22 @@ defmodule Membrane.Transcoder do
99110
If the transition from the input stream format to the output stream format is not
100111
possible without decoding or encoding the stream, an error will be raised.
101112
"""
113+
],
114+
assumed_input_stream_format: [
115+
spec: %Membrane.RemoteStream{content_format: Membrane.MPEGAudio} | nil,
116+
default: nil,
117+
description: """
118+
Allows to override stream format of the input stream with
119+
`%Membrane.RemoteStream{content_format: Membrane.MPEGAudio}`
120+
If nil, the input stream format won't be overriden.
121+
"""
102122
]
103123

104124
@impl true
105125
def handle_init(_ctx, opts) do
106126
spec = [
107127
bin_input()
128+
|> maybe_override_input_stream_format(opts.assumed_input_stream_format)
108129
|> child(:connector, %Membrane.Connector{notify_on_stream_format?: true}),
109130
child(:output_funnel, Funnel)
110131
|> bin_output()
@@ -120,6 +141,28 @@ defmodule Membrane.Transcoder do
120141
{[spec: spec], state}
121142
end
122143

144+
defp maybe_override_input_stream_format(
145+
builder,
146+
%Membrane.RemoteStream{content_format: Membrane.MPEGAudio} = stream_format
147+
) do
148+
builder
149+
|> child(:stream_format_changer, %Membrane.Transcoder.StreamFormatChanger{
150+
stream_format: stream_format
151+
})
152+
end
153+
154+
defp maybe_override_input_stream_format(builder, nil) do
155+
builder
156+
end
157+
158+
defp maybe_override_input_stream_format(_builder, stream_format) do
159+
raise """
160+
The only input stream format that can be assumed is \
161+
`%Membrane.RemoteStream{content_format: Membrane.MPEGAudio}`, while you wanted to assume: \
162+
#{inspect(stream_format)}
163+
"""
164+
end
165+
123166
@impl true
124167
def handle_child_notification({:stream_format, _pad, format}, :connector, _ctx, state)
125168
when state.input_stream_format == nil do

lib/transcoder/audio.ex

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ defmodule Membrane.Transcoder.Audio do
33

44
import Membrane.ChildrenSpec
55
require Membrane.Logger
6-
alias Membrane.{AAC, ChildrenSpec, Opus, RawAudio, RemoteStream}
6+
alias Membrane.{AAC, ChildrenSpec, MPEGAudio, Opus, RawAudio, RemoteStream}
77

8-
@opus_sample_rate 48_000
98
@aac_sample_rates [
109
96_000,
1110
88_200,
@@ -21,13 +20,31 @@ defmodule Membrane.Transcoder.Audio do
2120
8000
2221
]
2322

24-
@type audio_stream_format :: AAC.t() | Opus.t() | RawAudio.t()
23+
@type audio_stream_format :: AAC.t() | Opus.t() | Membrane.MPEGAudio.t() | RawAudio.t()
2524

2625
defguard is_audio_format(format)
2726
when is_struct(format) and
28-
(format.__struct__ in [AAC, Opus, RawAudio] or
29-
(format.__struct__ == RemoteStream and format.content_format == Opus and
30-
format.type == :packetized))
27+
(format.__struct__ in [AAC, Opus, MPEGAudio, RawAudio] or
28+
(format.__struct__ == RemoteStream and
29+
format.content_format == Opus and
30+
format.type == :packetized) or
31+
(format.__struct__ == RemoteStream and format.content_format == MPEGAudio))
32+
33+
defguard is_opus_compliant(format)
34+
when is_map_key(format, :content_type) and format.content_type == :s16le and
35+
is_map_key(format, :sample_rate) and format.sample_rate == 48_000
36+
37+
defguard is_aac_compliant(format)
38+
when is_map_key(format, :content_type) and format.content_type == :s16le and
39+
is_map_key(format, :sample_rate) and format.sample_rate in @aac_sample_rates
40+
41+
defguard is_mp3_compliant(format)
42+
when is_map_key(format, :sample_rate) and format.sample_rate == 44_100 and
43+
is_map_key(format, :sample_format) and format.sample_format == :s32le and
44+
is_map_key(
45+
format,
46+
:channels
47+
) and format.channels == 2
3148

3249
@spec plug_audio_transcoding(
3350
ChildrenSpec.builder(),
@@ -99,34 +116,50 @@ defmodule Membrane.Transcoder.Audio do
99116
builder |> child(:aac_decoder, AAC.FDK.Decoder)
100117
end
101118

119+
defp maybe_plug_decoder(builder, %MPEGAudio{}) do
120+
builder |> child(:mp3_decoder, Membrane.MP3.MAD.Decoder)
121+
end
122+
123+
defp maybe_plug_decoder(builder, %RemoteStream{content_format: MPEGAudio}) do
124+
builder |> child(:mp3_decoder, Membrane.MP3.MAD.Decoder)
125+
end
126+
102127
defp maybe_plug_decoder(builder, %RawAudio{}) do
103128
builder
104129
end
105130

106-
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %Opus{})
107-
when sample_rate != @opus_sample_rate do
131+
defp maybe_plug_resampler(builder, input_format, %Opus{})
132+
when not is_opus_compliant(input_format) do
108133
builder
109134
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
110135
output_stream_format: %RawAudio{
111136
sample_format: :s16le,
112-
sample_rate: @opus_sample_rate,
113-
channels: input_format.channels
137+
sample_rate: 48_000,
138+
channels: 1
114139
}
115140
})
116141
end
117142

118-
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %AAC{})
119-
when sample_rate not in @aac_sample_rates do
143+
defp maybe_plug_resampler(builder, input_format, %AAC{})
144+
when not is_aac_compliant(input_format) do
120145
builder
121146
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
122147
output_stream_format: %RawAudio{
123148
sample_format: :s16le,
124149
sample_rate: 44_100,
125-
channels: input_format.channels
150+
channels: 1
126151
}
127152
})
128153
end
129154

155+
defp maybe_plug_resampler(builder, input_format, %MPEGAudio{})
156+
when not is_mp3_compliant(input_format) do
157+
builder
158+
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
159+
output_stream_format: %RawAudio{sample_rate: 44_100, sample_format: :s32le, channels: 2}
160+
})
161+
end
162+
130163
defp maybe_plug_resampler(builder, _input_format, _output_format) do
131164
builder
132165
end
@@ -139,6 +172,10 @@ defmodule Membrane.Transcoder.Audio do
139172
builder |> child(:aac_encoder, AAC.FDK.Encoder)
140173
end
141174

175+
defp maybe_plug_encoder(builder, %MPEGAudio{}) do
176+
builder |> child(:mp3_encoder, Membrane.MP3.Lame.Encoder)
177+
end
178+
142179
defp maybe_plug_encoder(builder, %RawAudio{}) do
143180
builder
144181
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
defmodule Membrane.Transcoder.StreamFormatChanger do
2+
@moduledoc false
3+
use Membrane.Filter
4+
5+
def_input_pad :input, accepted_format: %Membrane.RemoteStream{}
6+
def_output_pad :output, accepted_format: _any
7+
8+
def_options stream_format: [
9+
spec: Membrane.StreamFormat.t(),
10+
description: """
11+
Stream format that will be sent on `handle_playing`.
12+
"""
13+
]
14+
15+
@impl true
16+
def handle_init(_ctx, opts) do
17+
{[], %{stream_format: opts.stream_format}}
18+
end
19+
20+
@impl true
21+
def handle_playing(_ctx, state) do
22+
{[stream_format: {:output, state.stream_format}], state}
23+
end
24+
25+
@impl true
26+
def handle_buffer(:input, buffer, _ctx, state) do
27+
{[buffer: {:output, buffer}], state}
28+
end
29+
30+
@impl true
31+
def handle_stream_format(:input, _stream_format, _ctx, state) do
32+
{[], state}
33+
end
34+
end

lib/transcoder/video.ex

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ defmodule Membrane.Transcoder.Video do
33

44
import Membrane.ChildrenSpec
55
require Membrane.Logger
6-
alias Membrane.{ChildrenSpec, H264, H265, RawVideo, RemoteStream, VP8}
6+
alias Membrane.{ChildrenSpec, H264, H265, RawVideo, RemoteStream, VP8, VP9}
77

8-
@type video_stream_format :: VP8.t() | H264.t() | H265.t() | RawVideo.t()
8+
@type video_stream_format :: VP8.t() | VP9.t() | H264.t() | H265.t() | RawVideo.t()
99

1010
defguard is_video_format(format)
1111
when is_struct(format) and
12-
(format.__struct__ in [VP8, H264, H265, RawVideo] or
13-
(format.__struct__ == RemoteStream and format.content_format == VP8 and
12+
(format.__struct__ in [VP8, VP9, H264, H265, RawVideo] or
13+
(format.__struct__ == RemoteStream and format.content_format in [VP8, VP9] and
1414
format.type == :packetized))
1515

1616
@spec plug_video_transcoding(
@@ -24,22 +24,22 @@ defmodule Membrane.Transcoder.Video do
2424
do_plug_video_transcoding(builder, input_format, output_format, transcoding_policy)
2525
end
2626

27-
defp do_plug_video_transcoding(
28-
builder,
29-
%h26x{},
30-
%h26x{} = output_format,
31-
transcoding_policy
32-
)
33-
when h26x in [H264, H265] and transcoding_policy in [:if_needed, :never] do
34-
parser =
35-
h26x
36-
|> Module.concat(Parser)
37-
|> struct!(
38-
output_stream_structure: stream_structure_type(output_format),
39-
output_alignment: output_format.alignment
40-
)
27+
defp do_plug_video_transcoding(builder, %H264{}, %H264{} = output_format, transcoding_policy)
28+
when transcoding_policy in [:if_needed, :never] do
29+
builder
30+
|> child(:h264_parser, %H264.Parser{
31+
output_stream_structure: stream_structure_type(output_format),
32+
output_alignment: output_format.alignment
33+
})
34+
end
4135

42-
builder |> child(:h264_parser, parser)
36+
defp do_plug_video_transcoding(builder, %H265{}, %H265{} = output_format, transcoding_policy)
37+
when transcoding_policy in [:if_needed, :never] do
38+
builder
39+
|> child(:h265_parser, %H265.Parser{
40+
output_stream_structure: stream_structure_type(output_format),
41+
output_alignment: output_format.alignment
42+
})
4343
end
4444

4545
defp do_plug_video_transcoding(
@@ -87,15 +87,18 @@ defmodule Membrane.Transcoder.Video do
8787
|> child(:h265_decoder, %H265.FFmpeg.Decoder{})
8888
end
8989

90-
defp maybe_plug_parser_and_decoder(builder, %VP8{}) do
91-
builder |> child(:vp8_decoder, %VP8.Decoder{})
90+
defp maybe_plug_parser_and_decoder(builder, %vpx{}) when vpx in [VP8, VP9] do
91+
decoder_module = Module.concat(vpx, Decoder)
92+
builder |> child(:vp8_decoder, decoder_module)
9293
end
9394

9495
defp maybe_plug_parser_and_decoder(builder, %RemoteStream{
95-
content_format: VP8,
96+
content_format: vpx,
9697
type: :packetized
97-
}) do
98-
builder |> child(:vp8_decoder, %VP8.Decoder{})
98+
})
99+
when vpx in [VP8, VP9] do
100+
decoder_module = Module.concat(vpx, Decoder)
101+
builder |> child(:vp8_decoder, decoder_module)
99102
end
100103

101104
defp maybe_plug_parser_and_decoder(builder, %RawVideo{}) do
@@ -131,6 +134,17 @@ defmodule Membrane.Transcoder.Video do
131134
builder |> child(:vp8_encoder, %VP8.Encoder{g_threads: number_of_threads, cpu_used: 15})
132135
end
133136

137+
defp maybe_plug_encoder_and_parser(builder, %VP9{}) do
138+
cpu_quota = :erlang.system_info(:cpu_quota)
139+
140+
number_of_threads =
141+
if cpu_quota != :unknown,
142+
do: cpu_quota,
143+
else: :erlang.system_info(:logical_processors_available)
144+
145+
builder |> child(:vp8_encoder, %VP9.Encoder{g_threads: number_of_threads, cpu_used: 15})
146+
end
147+
134148
defp maybe_plug_encoder_and_parser(builder, %RawVideo{}) do
135149
builder
136150
end

mix.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ defmodule Membrane.Transcoder.Plugin.Mixfile do
5353
{:membrane_opus_format, "~> 0.3.0"},
5454
{:membrane_aac_format, "~> 0.8.0"},
5555
{:membrane_funnel_plugin, "~> 0.9.1"},
56+
{:membrane_mpegaudio_format, "~> 0.3.0"},
57+
{:membrane_mp3_mad_plugin, "~> 0.18.4"},
58+
{:membrane_mp3_lame_plugin, "~> 0.18.3"},
5659
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
5760
{:dialyxir, ">= 0.0.0", only: :dev, runtime: false},
5861
{:credo, ">= 0.0.0", only: :dev, runtime: false},

0 commit comments

Comments
 (0)