-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from membraneframework/initial_implementation
Initial implementation
- Loading branch information
Showing
17 changed files
with
651 additions
and
77 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
defmodule Membrane.Transcoder do | ||
@moduledoc false | ||
use Membrane.Bin | ||
|
||
require __MODULE__.Audio | ||
require __MODULE__.Video | ||
require Membrane.Logger | ||
|
||
alias __MODULE__.{Audio, ForwardingFilter, Video} | ||
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8} | ||
|
||
@type stream_format :: | ||
H264.t() | ||
| H265.t() | ||
| VP8.t() | ||
| RawVideo.t() | ||
| AAC.t() | ||
| Opus.t() | ||
| RemoteStream.t() | ||
| RawAudio.t() | ||
|
||
@type stream_format_module :: H264 | H265 | VP8 | RawVideo | AAC | Opus | RawAudio | ||
|
||
@type stream_format_resolver :: (stream_format() -> stream_format() | stream_format_module()) | ||
|
||
def_input_pad :input, | ||
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format) | ||
|
||
def_output_pad :output, | ||
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format) | ||
|
||
def_options output_stream_format: [ | ||
spec: stream_format() | stream_format_module() | stream_format_resolver() | ||
] | ||
|
||
@impl true | ||
def handle_init(_ctx, opts) do | ||
spec = [ | ||
bin_input() | ||
|> child(:forwarding_filter, ForwardingFilter), | ||
child(:output_funnel, Funnel) | ||
|> bin_output() | ||
] | ||
|
||
state = | ||
opts | ||
|> Map.from_struct() | ||
|> Map.put(:input_stream_format, nil) | ||
|
||
{[spec: spec], state} | ||
end | ||
|
||
@impl true | ||
def handle_child_notification( | ||
{:stream_format, format}, | ||
:forwarding_filter, | ||
_ctx, | ||
%{input_stream_format: nil} = state | ||
) do | ||
state = | ||
%{state | input_stream_format: format} | ||
|> resolve_output_stream_format() | ||
|
||
spec = | ||
get_child(:forwarding_filter) | ||
|> plug_transcoding(format, state.output_stream_format) | ||
|> get_child(:output_funnel) | ||
|
||
{[spec: spec], state} | ||
end | ||
|
||
@impl true | ||
def handle_child_notification( | ||
{:stream_format, new_format}, | ||
:forwarding_filter, | ||
_ctx, | ||
%{input_stream_format: non_nil_stream_format} = state | ||
) do | ||
if new_format != non_nil_stream_format do | ||
raise """ | ||
Received new stream format on transcoder's input: #{inspect(new_format)} | ||
which doesn't match the first received input stream format: #{inspect(non_nil_stream_format)} | ||
Transcoder doesn't support updating the input stream format. | ||
""" | ||
end | ||
|
||
{[], state} | ||
end | ||
|
||
@impl true | ||
def handle_child_notification(_notification, _element, _ctx, state) do | ||
{[], state} | ||
end | ||
|
||
defp resolve_output_stream_format(state) do | ||
case state.output_stream_format do | ||
format when is_struct(format) -> | ||
state | ||
|
||
module when is_atom(module) -> | ||
%{state | output_stream_format: struct(module)} | ||
|
||
resolver when is_function(resolver) -> | ||
%{state | output_stream_format: resolver.(state.input_stream_format)} | ||
|> resolve_output_stream_format() | ||
end | ||
end | ||
|
||
defp plug_transcoding(builder, input_format, output_format) | ||
when Audio.is_audio_format(input_format) do | ||
builder |> Audio.plug_audio_transcoding(input_format, output_format) | ||
end | ||
|
||
defp plug_transcoding(builder, input_format, output_format) | ||
when Video.is_video_format(input_format) do | ||
builder |> Video.plug_video_transcoding(input_format, output_format) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
defmodule Membrane.Transcoder.Audio do | ||
@moduledoc false | ||
|
||
import Membrane.ChildrenSpec | ||
require Membrane.Logger | ||
alias Membrane.{AAC, ChildrenSpec, Opus, RawAudio, RemoteStream} | ||
|
||
@opus_sample_rate 48_000 | ||
@aac_sample_rates [ | ||
96_000, | ||
88_200, | ||
64_000, | ||
48_000, | ||
44_100, | ||
32_000, | ||
24_000, | ||
22_050, | ||
16_000, | ||
12_000, | ||
11_025, | ||
8000 | ||
] | ||
|
||
@type audio_stream_format :: AAC.t() | Opus.t() | RawAudio.t() | ||
|
||
defguard is_audio_format(format) | ||
when is_struct(format) and | ||
(format.__struct__ in [AAC, Opus, RawAudio] or | ||
(format.__struct__ == RemoteStream and format.content_format == Opus and | ||
format.type == :packetized)) | ||
|
||
@spec plug_audio_transcoding( | ||
ChildrenSpec.builder(), | ||
audio_stream_format() | RemoteStream.t(), | ||
audio_stream_format() | ||
) :: ChildrenSpec.builder() | ||
def plug_audio_transcoding(builder, input_format, output_format) | ||
when is_audio_format(input_format) and is_audio_format(output_format) do | ||
do_plug_audio_transcoding(builder, input_format, output_format) | ||
end | ||
|
||
defp do_plug_audio_transcoding(builder, %format_module{}, %format_module{}) do | ||
Membrane.Logger.debug(""" | ||
This bin will only forward buffers, as the input stream format is the same as the output stream format. | ||
""") | ||
|
||
builder | ||
end | ||
|
||
defp do_plug_audio_transcoding(builder, %RemoteStream{content_format: Opus}, %Opus{}) do | ||
builder |> child(:opus_parser, Opus.Parser) | ||
end | ||
|
||
defp do_plug_audio_transcoding(builder, input_format, output_format) do | ||
builder | ||
|> maybe_plug_decoder(input_format) | ||
|> maybe_plug_resampler(input_format, output_format) | ||
|> maybe_plug_encoder(output_format) | ||
end | ||
|
||
defp maybe_plug_decoder(builder, %Opus{}) do | ||
builder |> child(:opus_decoder, Opus.Decoder) | ||
end | ||
|
||
defp maybe_plug_decoder(builder, %RemoteStream{content_format: Opus, type: :packetized}) do | ||
builder |> child(:opus_decoder, Opus.Decoder) | ||
end | ||
|
||
defp maybe_plug_decoder(builder, %AAC{}) do | ||
builder |> child(:aac_decoder, AAC.FDK.Decoder) | ||
end | ||
|
||
defp maybe_plug_decoder(builder, %RawAudio{}) do | ||
builder | ||
end | ||
|
||
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %Opus{}) | ||
when sample_rate != @opus_sample_rate do | ||
builder | ||
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{ | ||
output_stream_format: %RawAudio{ | ||
sample_format: :s16le, | ||
sample_rate: @opus_sample_rate, | ||
channels: input_format.channels | ||
} | ||
}) | ||
end | ||
|
||
defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %AAC{}) | ||
when sample_rate not in @aac_sample_rates do | ||
builder | ||
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{ | ||
output_stream_format: %RawAudio{ | ||
sample_format: :s16le, | ||
sample_rate: 44_100, | ||
channels: input_format.channels | ||
} | ||
}) | ||
end | ||
|
||
defp maybe_plug_resampler(builder, _input_format, _output_format) do | ||
builder | ||
end | ||
|
||
defp maybe_plug_encoder(builder, %Opus{}) do | ||
builder |> child(:opus_encoder, Opus.Encoder) | ||
end | ||
|
||
defp maybe_plug_encoder(builder, %AAC{}) do | ||
builder |> child(:aac_encoder, AAC.FDK.Encoder) | ||
end | ||
|
||
defp maybe_plug_encoder(builder, %RawAudio{}) do | ||
builder | ||
end | ||
end |
Oops, something went wrong.