Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The package can be installed by adding `membrane_transcoder_plugin` to your list
```elixir
def deps do
[
{:membrane_transcoder_plugin, "~> 0.2.1"}
{:membrane_transcoder_plugin, "~> 0.2.2"}
]
end
```
Expand Down
2 changes: 1 addition & 1 deletion examples/vp8_to_h264.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ defmodule Example do
end

File.mkdir("tmp")
Example.convert(Path.join("./test/fixtures", "video.ivf"), Path.join("./tmp", "video.h264"))
Example.convert(Path.join("./test/fixtures", "video_vp8.ivf"), Path.join("./tmp", "video.h264"))
37 changes: 34 additions & 3 deletions lib/transcoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ defmodule Membrane.Transcoder do
* `Membrane.H264`
* `Membrane.H265`
* `Membrane.VP8`
* `Membrane.VP9`
* `Membrane.RawVideo`
* `Membrane.RemoteStream{content_type: Membrane.VP8}` (only as an input stream)
* `Membrane.RemoteStream{content_type: Membrane.VP9}` (only as an input stream)

The following audio stream formats are supported:
* `Membrane.AAC`
* `Membrane.Opus`
* `Membrane.MPEGAudio`
* `Membrane.RawAudio`
* `Membrane.RemoteStream{content_type: Membrane.Opus}` (only as an input stream)
* `Membrane.RemoteStream{content_type: Membrane.MPEGAudio}` (only as an input stream)
"""
use Membrane.Bin

Expand All @@ -23,7 +28,7 @@ defmodule Membrane.Transcoder do
require Membrane.Logger

alias __MODULE__.{Audio, Video}
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8}
alias Membrane.{AAC, Funnel, H264, H265, Opus, RawAudio, RawVideo, RemoteStream, VP8, VP9}

@typedoc """
Describes stream formats acceptable on the bin's input and output.
Expand All @@ -32,24 +37,30 @@ defmodule Membrane.Transcoder do
H264.t()
| H265.t()
| VP8.t()
| VP9.t()
| RawVideo.t()
| AAC.t()
| Opus.t()
| Membrane.MPEGAudio.t()
| RemoteStream.t()
| RawAudio.t()

@typedoc """
Describes stream format modules that can be used to define inputs and outputs of the bin.
"""
@type stream_format_module :: H264 | H265 | VP8 | RawVideo | AAC | Opus | RawAudio
@type stream_format_module ::
H264 | H265 | VP8 | VP9 | RawVideo | AAC | Opus | Membrane.MPEGAudio | RawAudio

@typedoc """
Describes a function which can be used to provide output format based on the input format.
"""
@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)
accepted_format:
format
when Audio.is_audio_format(format) or Video.is_video_format(format) or
format.__struct__ == RemoteStream

def_output_pad :output,
accepted_format: format when Audio.is_audio_format(format) or Video.is_video_format(format)
Expand Down Expand Up @@ -80,12 +91,21 @@ defmodule Membrane.Transcoder do
* a boolean,
* a function that receives the input stream format and returns a boolean.
"""
],
override_input_stream_format: [
spec: Membrane.StreamFormat.t() | nil,
default: nil,
description: """
Allows to override stream format of the input stream.
If nil, the input stream format won't be overriden.
"""
]

@impl true
def handle_init(_ctx, opts) do
spec = [
bin_input()
|> maybe_override_input_stream_format(opts.override_input_stream_format)
|> child(:connector, %Membrane.Connector{notify_on_stream_format?: true}),
child(:output_funnel, Funnel)
|> bin_output()
Expand All @@ -101,6 +121,17 @@ defmodule Membrane.Transcoder do
{[spec: spec], state}
end

defp maybe_override_input_stream_format(builder, nil) do
builder
end

defp maybe_override_input_stream_format(builder, stream_format) do
builder
|> child(:stream_format_changer, %Membrane.Transcoder.StreamFormatChanger{
stream_format: stream_format
})
end

@impl true
def handle_child_notification({:stream_format, _pad, format}, :connector, _ctx, state)
when state.input_stream_format == nil do
Expand Down
91 changes: 65 additions & 26 deletions lib/transcoder/audio.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Membrane.Transcoder.Audio do

import Membrane.ChildrenSpec
require Membrane.Logger
alias Membrane.{AAC, ChildrenSpec, Opus, RawAudio, RemoteStream}
alias Membrane.{AAC, ChildrenSpec, MPEGAudio, Opus, RawAudio, RemoteStream}

@opus_sample_rate 48_000
@aac_sample_rates [
Expand All @@ -20,14 +20,17 @@ defmodule Membrane.Transcoder.Audio do
11_025,
8000
]
@mpeg_raw_audio_format %RawAudio{sample_rate: 44_100, sample_format: :s32le, channels: 2}

@type audio_stream_format :: AAC.t() | Opus.t() | RawAudio.t()
@type audio_stream_format :: AAC.t() | Opus.t() | Membrane.MPEGAudio.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))
(format.__struct__ in [AAC, Opus, MPEGAudio, RawAudio] or
(format.__struct__ == RemoteStream and
format.content_format == Opus and
format.type == :packetized) or
(format.__struct__ == RemoteStream and format.content_format == MPEGAudio))

@spec plug_audio_transcoding(
ChildrenSpec.builder(),
Expand Down Expand Up @@ -90,32 +93,64 @@ defmodule Membrane.Transcoder.Audio do
builder |> child(:aac_decoder, AAC.FDK.Decoder)
end

defp maybe_plug_decoder(builder, %RawAudio{}) do
builder
defp maybe_plug_decoder(builder, %MPEGAudio{}) do
builder |> child(:mp3_decoder, Membrane.MP3.MAD.Decoder)
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
}
})
defp maybe_plug_decoder(builder, %RemoteStream{content_format: MPEGAudio}) do
builder |> child(:mp3_decoder, Membrane.MP3.MAD.Decoder)
end

defp maybe_plug_resampler(builder, %{sample_rate: sample_rate} = input_format, %AAC{})
when sample_rate not in @aac_sample_rates do
defp maybe_plug_decoder(builder, %RawAudio{}) 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, %Opus{}) do
if Map.get(input_format, :sample_rate) == @opus_sample_rate do
builder
else
builder
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
output_stream_format: %RawAudio{
sample_format: :s16le,
sample_rate: @opus_sample_rate,
channels: Map.get(input_format, :channels, 1)
}
})
end
end

defp maybe_plug_resampler(builder, input_format, %AAC{}) do
if Map.get(input_format, :sample_rate) in @aac_sample_rates do
builder
else
builder
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
output_stream_format: %RawAudio{
sample_format: :s16le,
sample_rate: 44_100,
channels: Map.get(input_format, :channels, 1)
}
})
end
end

defp maybe_plug_resampler(
builder,
input_format,
%MPEGAudio{}
) do
if Map.get(input_format, :sample_rate) == @mpeg_raw_audio_format.sample_rate and
Map.get(input_format, :sample_format) == @mpeg_raw_audio_format.sample_format and
Map.get(input_format, :channels) ==
@mpeg_raw_audio_format.channels do
builder
else
builder
|> child(:resampler, %Membrane.FFmpeg.SWResample.Converter{
output_stream_format: @mpeg_raw_audio_format
})
end
end

defp maybe_plug_resampler(builder, _input_format, _output_format) do
Expand All @@ -130,6 +165,10 @@ defmodule Membrane.Transcoder.Audio do
builder |> child(:aac_encoder, AAC.FDK.Encoder)
end

defp maybe_plug_encoder(builder, %MPEGAudio{}) do
builder |> child(:mp3_encoder, Membrane.MP3.Lame.Encoder)
end

defp maybe_plug_encoder(builder, %RawAudio{}) do
builder
end
Expand Down
34 changes: 34 additions & 0 deletions lib/transcoder/stream_format_changer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule Membrane.Transcoder.StreamFormatChanger do
@moduledoc false
use Membrane.Filter

def_input_pad :input, accepted_format: %Membrane.RemoteStream{}
def_output_pad :output, accepted_format: _any

def_options stream_format: [
spec: Membrane.StreamFormat.t(),
description: """
Stream format that will be sent on `handle_playing`.
"""
]

@impl true
def handle_init(_ctx, opts) do
{[], %{stream_format: opts.stream_format}}
end

@impl true
def handle_playing(_ctx, state) do
{[stream_format: {:output, state.stream_format}], state}
end

@impl true
def handle_buffer(:input, buffer, _ctx, state) do
{[buffer: {:output, buffer}], state}
end

@impl true
def handle_stream_format(:input, _stream_format, _ctx, state) do
{[], state}
end
end
26 changes: 15 additions & 11 deletions lib/transcoder/video.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ defmodule Membrane.Transcoder.Video do

import Membrane.ChildrenSpec
require Membrane.Logger
alias Membrane.{ChildrenSpec, H264, H265, RawVideo, RemoteStream, VP8}
alias Membrane.{ChildrenSpec, H264, H265, RawVideo, RemoteStream, VP8, VP9}

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

defguard is_video_format(format)
when is_struct(format) and
(format.__struct__ in [VP8, H264, H265, RawVideo] or
(format.__struct__ == RemoteStream and format.content_format == VP8 and
(format.__struct__ in [VP8, VP9, H264, H265, RawVideo] or
(format.__struct__ == RemoteStream and format.content_format in [VP8, VP9] and
format.type == :packetized))

@spec plug_video_transcoding(
Expand Down Expand Up @@ -79,15 +79,18 @@ defmodule Membrane.Transcoder.Video do
|> child(:h265_decoder, %H265.FFmpeg.Decoder{})
end

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

defp maybe_plug_parser_and_decoder(builder, %RemoteStream{
content_format: VP8,
content_format: vpx,
type: :packetized
}) do
builder |> child(:vp8_decoder, %VP8.Decoder{})
})
when vpx in [VP8, VP9] do
decoder_module = Module.concat(vpx, Decoder)
builder |> child(:vp8_decoder, decoder_module)
end

defp maybe_plug_parser_and_decoder(builder, %RawVideo{}) do
Expand All @@ -112,15 +115,16 @@ defmodule Membrane.Transcoder.Video do
})
end

defp maybe_plug_encoder_and_parser(builder, %VP8{}) do
defp maybe_plug_encoder_and_parser(builder, %vpx{}) when vpx in [VP8, VP9] do
cpu_quota = :erlang.system_info(:cpu_quota)

number_of_threads =
if cpu_quota != :unknown,
do: cpu_quota,
else: :erlang.system_info(:logical_processors_available)

builder |> child(:vp8_encoder, %VP8.Encoder{g_threads: number_of_threads, cpu_used: 15})
encoder = Module.concat(vpx, Encoder) |> struct!(g_threads: number_of_threads, cpu_used: 15)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like calling struct!/2 as it allows to create a struct that misses some required fields and I am not sure if it supports non-nil default values

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed struct!() usage for both H26x and VPx

builder |> child(:vp8_encoder, encoder)
end

defp maybe_plug_encoder_and_parser(builder, %RawVideo{}) do
Expand Down
5 changes: 4 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Membrane.Transcoder.Plugin.Mixfile do
use Mix.Project

@version "0.2.1"
@version "0.2.2"
@github_url "https://github.com/membraneframework/membrane_transcoder_plugin"

def project do
Expand Down Expand Up @@ -53,6 +53,9 @@ defmodule Membrane.Transcoder.Plugin.Mixfile do
{:membrane_opus_format, "~> 0.3.0"},
{:membrane_aac_format, "~> 0.8.0"},
{:membrane_funnel_plugin, "~> 0.9.1"},
{:membrane_mpegaudio_format, "~> 0.3.0"},
{:membrane_mp3_mad_plugin, "~> 0.18.4"},
{:membrane_mp3_lame_plugin, "~> 0.18.3"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:dialyxir, ">= 0.0.0", only: :dev, runtime: false},
{:credo, ">= 0.0.0", only: :dev, runtime: false},
Expand Down
Loading