Skip to content

Commit 6454a00

Browse files
authored
Merge pull request #1 from membraneframework/initial_implementation
Initial implementation
2 parents 896d9a4 + 3ce7acf commit 6454a00

17 files changed

+651
-77
lines changed

Diff for: .github/workflows/fetch_changes.yml

-54
This file was deleted.

Diff for: README.md

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
1-
# Membrane Template Plugin
1+
# Membrane Transcoder Plugin
22

33
[![Hex.pm](https://img.shields.io/hexpm/v/membrane_template_plugin.svg)](https://hex.pm/packages/membrane_template_plugin)
44
[![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_template_plugin)
55
[![CircleCI](https://circleci.com/gh/membraneframework/membrane_template_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_template_plugin)
66

7-
This repository contains a template for new plugins.
8-
9-
Check out different branches for other flavors of this template.
7+
This repository provides `Membrane.Transcoder` which is a bin that is capable
8+
of transcoding the input audio or video stream into the descired one specified
9+
with simple declarative API.
1010

1111
It's a part of the [Membrane Framework](https://membrane.stream).
1212

1313
## Installation
1414

15-
The package can be installed by adding `membrane_template_plugin` to your list of dependencies in `mix.exs`:
15+
The package can be installed by adding `membrane_transcoder_plugin` to your list of dependencies in `mix.exs`:
1616

1717
```elixir
1818
def deps do
1919
[
20-
{:membrane_template_plugin, "~> 0.1.0"}
20+
{:membrane_transcoder_plugin, "~> 0.1.0"}
2121
]
2222
end
2323
```
2424

25-
## Usage
26-
27-
TODO
28-
2925
## Copyright and License
3026

3127
Copyright 2020, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_template_plugin)

Diff for: lib/membrane_template.ex

-2
This file was deleted.

Diff for: lib/transcoder.ex

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
defmodule Membrane.Transcoder do
2+
@moduledoc false
3+
use Membrane.Bin
4+
5+
require __MODULE__.Audio
6+
require __MODULE__.Video
7+
require Membrane.Logger
8+
9+
alias __MODULE__.{Audio, ForwardingFilter, Video}
10+
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8}
11+
12+
@type stream_format ::
13+
H264.t()
14+
| H265.t()
15+
| VP8.t()
16+
| RawVideo.t()
17+
| AAC.t()
18+
| Opus.t()
19+
| RemoteStream.t()
20+
| RawAudio.t()
21+
22+
@type stream_format_module :: H264 | H265 | VP8 | RawVideo | AAC | Opus | RawAudio
23+
24+
@type stream_format_resolver :: (stream_format() -> stream_format() | stream_format_module())
25+
26+
def_input_pad :input,
27+
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format)
28+
29+
def_output_pad :output,
30+
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format)
31+
32+
def_options output_stream_format: [
33+
spec: stream_format() | stream_format_module() | stream_format_resolver()
34+
]
35+
36+
@impl true
37+
def handle_init(_ctx, opts) do
38+
spec = [
39+
bin_input()
40+
|> child(:forwarding_filter, ForwardingFilter),
41+
child(:output_funnel, Funnel)
42+
|> bin_output()
43+
]
44+
45+
state =
46+
opts
47+
|> Map.from_struct()
48+
|> Map.put(:input_stream_format, nil)
49+
50+
{[spec: spec], state}
51+
end
52+
53+
@impl true
54+
def handle_child_notification(
55+
{:stream_format, format},
56+
:forwarding_filter,
57+
_ctx,
58+
%{input_stream_format: nil} = state
59+
) do
60+
state =
61+
%{state | input_stream_format: format}
62+
|> resolve_output_stream_format()
63+
64+
spec =
65+
get_child(:forwarding_filter)
66+
|> plug_transcoding(format, state.output_stream_format)
67+
|> get_child(:output_funnel)
68+
69+
{[spec: spec], state}
70+
end
71+
72+
@impl true
73+
def handle_child_notification(
74+
{:stream_format, new_format},
75+
:forwarding_filter,
76+
_ctx,
77+
%{input_stream_format: non_nil_stream_format} = state
78+
) do
79+
if new_format != non_nil_stream_format do
80+
raise """
81+
Received new stream format on transcoder's input: #{inspect(new_format)}
82+
which doesn't match the first received input stream format: #{inspect(non_nil_stream_format)}
83+
Transcoder doesn't support updating the input stream format.
84+
"""
85+
end
86+
87+
{[], state}
88+
end
89+
90+
@impl true
91+
def handle_child_notification(_notification, _element, _ctx, state) do
92+
{[], state}
93+
end
94+
95+
defp resolve_output_stream_format(state) do
96+
case state.output_stream_format do
97+
format when is_struct(format) ->
98+
state
99+
100+
module when is_atom(module) ->
101+
%{state | output_stream_format: struct(module)}
102+
103+
resolver when is_function(resolver) ->
104+
%{state | output_stream_format: resolver.(state.input_stream_format)}
105+
|> resolve_output_stream_format()
106+
end
107+
end
108+
109+
defp plug_transcoding(builder, input_format, output_format)
110+
when Audio.is_audio_format(input_format) do
111+
builder |> Audio.plug_audio_transcoding(input_format, output_format)
112+
end
113+
114+
defp plug_transcoding(builder, input_format, output_format)
115+
when Video.is_video_format(input_format) do
116+
builder |> Video.plug_video_transcoding(input_format, output_format)
117+
end
118+
end

Diff for: lib/transcoder/audio.ex

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
defmodule Membrane.Transcoder.Audio do
2+
@moduledoc false
3+
4+
import Membrane.ChildrenSpec
5+
require Membrane.Logger
6+
alias Membrane.{AAC, ChildrenSpec, Opus, RawAudio, RemoteStream}
7+
8+
@opus_sample_rate 48_000
9+
@aac_sample_rates [
10+
96_000,
11+
88_200,
12+
64_000,
13+
48_000,
14+
44_100,
15+
32_000,
16+
24_000,
17+
22_050,
18+
16_000,
19+
12_000,
20+
11_025,
21+
8000
22+
]
23+
24+
@type audio_stream_format :: AAC.t() | Opus.t() | RawAudio.t()
25+
26+
defguard is_audio_format(format)
27+
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))
31+
32+
@spec plug_audio_transcoding(
33+
ChildrenSpec.builder(),
34+
audio_stream_format() | RemoteStream.t(),
35+
audio_stream_format()
36+
) :: ChildrenSpec.builder()
37+
def plug_audio_transcoding(builder, input_format, output_format)
38+
when is_audio_format(input_format) and is_audio_format(output_format) do
39+
do_plug_audio_transcoding(builder, input_format, output_format)
40+
end
41+
42+
defp do_plug_audio_transcoding(builder, %format_module{}, %format_module{}) do
43+
Membrane.Logger.debug("""
44+
This bin will only forward buffers, as the input stream format is the same as the output stream format.
45+
""")
46+
47+
builder
48+
end
49+
50+
defp do_plug_audio_transcoding(builder, %RemoteStream{content_format: Opus}, %Opus{}) do
51+
builder |> child(:opus_parser, Opus.Parser)
52+
end
53+
54+
defp do_plug_audio_transcoding(builder, input_format, output_format) do
55+
builder
56+
|> maybe_plug_decoder(input_format)
57+
|> maybe_plug_resampler(input_format, output_format)
58+
|> maybe_plug_encoder(output_format)
59+
end
60+
61+
defp maybe_plug_decoder(builder, %Opus{}) do
62+
builder |> child(:opus_decoder, Opus.Decoder)
63+
end
64+
65+
defp maybe_plug_decoder(builder, %RemoteStream{content_format: Opus, type: :packetized}) do
66+
builder |> child(:opus_decoder, Opus.Decoder)
67+
end
68+
69+
defp maybe_plug_decoder(builder, %AAC{}) do
70+
builder |> child(:aac_decoder, AAC.FDK.Decoder)
71+
end
72+
73+
defp maybe_plug_decoder(builder, %RawAudio{}) do
74+
builder
75+
end
76+
77+
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %Opus{})
78+
when sample_rate != @opus_sample_rate do
79+
builder
80+
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
81+
output_stream_format: %RawAudio{
82+
sample_format: :s16le,
83+
sample_rate: @opus_sample_rate,
84+
channels: input_format.channels
85+
}
86+
})
87+
end
88+
89+
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %AAC{})
90+
when sample_rate not in @aac_sample_rates do
91+
builder
92+
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
93+
output_stream_format: %RawAudio{
94+
sample_format: :s16le,
95+
sample_rate: 44_100,
96+
channels: input_format.channels
97+
}
98+
})
99+
end
100+
101+
defp maybe_plug_resampler(builder, _input_format, _output_format) do
102+
builder
103+
end
104+
105+
defp maybe_plug_encoder(builder, %Opus{}) do
106+
builder |> child(:opus_encoder, Opus.Encoder)
107+
end
108+
109+
defp maybe_plug_encoder(builder, %AAC{}) do
110+
builder |> child(:aac_encoder, AAC.FDK.Encoder)
111+
end
112+
113+
defp maybe_plug_encoder(builder, %RawAudio{}) do
114+
builder
115+
end
116+
end

0 commit comments

Comments
 (0)