Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transcoding #40

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f13964d
Add forwarding filter
FelonEkonom Sep 18, 2024
73647ed
WIP
FelonEkonom Sep 18, 2024
6487d4e
Fix typo
FelonEkonom Sep 18, 2024
324787e
Fix tests wip
FelonEkonom Sep 19, 2024
e279830
Almost fix tests
FelonEkonom Sep 19, 2024
8da2108
Refactor transcoding bin
FelonEkonom Sep 19, 2024
ca6b1e2
Update test fixtures
FelonEkonom Sep 19, 2024
cc70735
Small refactor of TranscodingBin
FelonEkonom Sep 19, 2024
5196644
Small project refactor
FelonEkonom Sep 24, 2024
b75a387
Add VideoTranscoder
FelonEkonom Sep 24, 2024
7b9e5bf
VideoTranscoder impl
FelonEkonom Sep 24, 2024
462d88f
Fix tests
FelonEkonom Sep 25, 2024
beb3bcd
Refactor files naming
FelonEkonom Sep 25, 2024
beadf34
Delete unnevesary options description
FelonEkonom Sep 25, 2024
a5eada4
Support input h265 in transcoding bin
FelonEkonom Sep 25, 2024
a4e6359
Add test for mp4 H265 input
FelonEkonom Sep 25, 2024
eec8335
mix format
FelonEkonom Sep 25, 2024
2465e57
Video transcoder code refactor
FelonEkonom Sep 25, 2024
98073c7
Remove StreamFormatResolver
FelonEkonom Sep 27, 2024
8545276
wip
FelonEkonom Oct 23, 2024
23006c3
Fix deps, fix bugs
FelonEkonom Oct 23, 2024
1264a4f
Fix tests
FelonEkonom Oct 25, 2024
8c109fe
Fix typo
FelonEkonom Oct 25, 2024
c41693b
Refactor
FelonEkonom Oct 25, 2024
62adab7
Bump deps
FelonEkonom Oct 25, 2024
54c39ed
Implememnt reviewers comments
FelonEkonom Oct 25, 2024
4d52b34
Bump mix.lock
FelonEkonom Oct 25, 2024
69e87c8
Refactor transcoders wip
FelonEkonom Oct 31, 2024
b2897b5
Refactor wip
FelonEkonom Nov 4, 2024
79e39a8
Fix tests
FelonEkonom Nov 5, 2024
f002ab6
Fix credo
FelonEkonom Nov 5, 2024
150752a
refactor transcoder
FelonEkonom Nov 5, 2024
d007f8f
Add mp4 H265 test
FelonEkonom Nov 5, 2024
d1aa388
mix format
FelonEkonom Nov 5, 2024
125f82a
Fix dialyzer
FelonEkonom Nov 5, 2024
51a0796
Merge remote-tracking branch 'origin/master' into transcoding-v2
FelonEkonom Nov 5, 2024
8aaf83e
Adjust to changes in webrtc plugin
FelonEkonom Nov 6, 2024
814694a
Remove leftowvers
FelonEkonom Nov 6, 2024
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
15 changes: 12 additions & 3 deletions lib/boombox/elixir_stream.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ defmodule Boombox.ElixirStream do

import Membrane.ChildrenSpec
require Membrane.Pad, as: Pad

alias __MODULE__.{Sink, Source}
alias Boombox.Pipeline.Ready
alias Boombox.Transcoders

@options_audio_keys [:audio_format, :audio_rate, :audio_channels]

Expand Down Expand Up @@ -53,15 +56,21 @@ defmodule Boombox.ElixirStream do
Enum.map(track_builders, fn
{:audio, builder} ->
builder
|> child(:mp4_audio_transcoder, %Transcoders.Audio{
output_stream_format_module: Membrane.RawAudio
})
|> then(&maybe_plug_resampler(&1, options))
|> via_in(Pad.ref(:input, :audio))
|> get_child(:elixir_stream_sink)

{:video, builder} ->
builder
|> child(%Membrane.H264.Parser{output_stream_structure: :annexb})
|> child(Membrane.H264.FFmpeg.Decoder)
|> child(%Membrane.FFmpeg.SWScale.Converter{format: :RGB})
|> child(:elixir_stream_video_transcoder, %Transcoders.Video{
output_stream_format: Membrane.RawVideo
})
|> child(:elixir_stream_rgb_converter, %Membrane.FFmpeg.SWScale.Converter{
format: :RGB
})
|> via_in(Pad.ref(:input, :video))
|> get_child(:elixir_stream_sink)
end),
Expand Down
2 changes: 1 addition & 1 deletion lib/boombox/elixir_stream/sink.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Sink do
defmodule Boombox.ElixirStream.Sink do
@moduledoc false
use Membrane.Sink

Expand Down
2 changes: 1 addition & 1 deletion lib/boombox/elixir_stream/source.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Source do
defmodule Boombox.ElixirStream.Source do
@moduledoc false
use Membrane.Source

Expand Down
9 changes: 8 additions & 1 deletion lib/boombox/hls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule Boombox.HLS do

require Membrane.Pad, as: Pad
alias Boombox.Pipeline.Ready
alias Boombox.Transcoders
alias Membrane.H264
alias Membrane.Time

@spec link_output(
Expand Down Expand Up @@ -42,14 +44,19 @@ defmodule Boombox.HLS do
Enum.map(track_builders, fn
{:audio, builder} ->
builder
|> child(:hls_out_aac_encoder, Membrane.AAC.FDK.Encoder)
|> child(:mp4_audio_transcoder, %Transcoders.Audio{
output_stream_format_module: Membrane.AAC
})
|> via_in(Pad.ref(:input, :audio),
options: [encoding: :AAC, segment_duration: Time.milliseconds(2000)]
)
|> get_child(:hls_sink_bin)

{:video, builder} ->
builder
|> child(:hls_video_transcoder, %Transcoders.Video{
output_stream_format: %H264{alignment: :au, stream_structure: :avc3}
})
|> via_in(Pad.ref(:input, :video),
options: [encoding: :H264, segment_duration: Time.milliseconds(2000)]
)
Expand Down
25 changes: 21 additions & 4 deletions lib/boombox/mp4.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ defmodule Boombox.MP4 do
import Membrane.ChildrenSpec
require Membrane.Pad, as: Pad
alias Boombox.Pipeline.{Ready, Wait}
alias Boombox.Transcoders
alias Membrane.H264
alias Membrane.H265

defguardp is_h264_or_h265(format) when is_struct(format) and format.__struct__ in [H264, H265]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
defguardp is_h264_or_h265(format) when is_struct(format) and format.__struct__ in [H264, H265]
defguardp is_h26x(format) when is_struct(format) and format.__struct__ in [H264, H265]


@spec create_input(String.t(), transport: :file | :http) :: Wait.t()
def create_input(location, opts) do
Expand Down Expand Up @@ -33,11 +38,10 @@ defmodule Boombox.MP4 do
get_child(:mp4_demuxer)
|> via_out(Pad.ref(:output, id))
|> child(:mp4_in_aac_parser, Membrane.AAC.Parser)
|> child(:mp4_in_aac_decoder, Membrane.AAC.FDK.Decoder)

{:audio, spec}

{id, %Membrane.H264{}} ->
{id, video_format} when is_h264_or_h265(video_format) ->
spec =
get_child(:mp4_demuxer)
|> via_out(Pad.ref(:output, id))
Expand All @@ -62,7 +66,9 @@ defmodule Boombox.MP4 do
Enum.map(track_builders, fn
{:audio, builder} ->
builder
|> child(:mp4_out_aac_encoder, Membrane.AAC.FDK.Encoder)
|> child(:mp4_audio_transcoder, %Transcoders.Audio{
output_stream_format_module: Membrane.AAC
})
|> child(:mp4_out_aac_parser, %Membrane.AAC.Parser{
out_encapsulation: :none,
output_config: :esds
Expand All @@ -72,7 +78,18 @@ defmodule Boombox.MP4 do

{:video, builder} ->
builder
|> child(:mp4_out_h264_parser, %Membrane.H264.Parser{output_stream_structure: :avc3})
|> child(:mp4_video_transcoder, %Transcoders.Video{
output_stream_format: fn
%H264{stream_structure: :annexb} = h264 ->
%{h264 | stream_structure: :avc3, alignment: :au}

%H264{} = h264 ->
%{h264 | alignment: :au}

_not_h264 ->
%H264{stream_structure: :avc3, alignment: :au}
Copy link
Member

Choose a reason for hiding this comment

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

what about H265?

Copy link
Member Author

Choose a reason for hiding this comment

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

@varsill what types of H265 can be put inside MP4 and what types of conversion should be avoided there?

Copy link
Contributor

@varsill varsill Oct 29, 2024

Choose a reason for hiding this comment

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

You can put there :hvc1 and :hev1 and you need to treat :hvc1 just as you treat :avc1 and :hev1 as :avc3

end
})
|> via_in(Pad.ref(:input, :video))
|> get_child(:mp4_muxer)
end)
Expand Down
3 changes: 1 addition & 2 deletions lib/boombox/rtmp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ defmodule Boombox.RTMP do
child(:rtmp_source, %RTMP.SourceBin{client_ref: client_ref})
|> via_out(:audio)
|> child(:rtmp_in_aac_parser, Membrane.AAC.Parser)
|> child(:rtmp_in_aac_decoder, Membrane.AAC.FDK.Decoder)

track_builders = %{
audio: get_child(:rtmp_in_aac_decoder),
audio: get_child(:rtmp_in_aac_parser),
video: get_child(:rtmp_source) |> via_out(:video)
}

Expand Down
2 changes: 0 additions & 2 deletions lib/boombox/rtsp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ defmodule Boombox.RTSP do
spec =
get_child(:rtsp_source)
|> via_out(Membrane.Pad.ref(:output, ssrc))
|> child(Membrane.Debug.Sink)

{spec, track_builders}

Expand All @@ -82,7 +81,6 @@ defmodule Boombox.RTSP do
get_child(:rtsp_source)
|> via_out(Membrane.Pad.ref(:output, ssrc))
|> child(:rtsp_in_aac_parser, Membrane.AAC.Parser)
|> child(:rtsp_in_aac_decoder, Membrane.AAC.FDK.Decoder)

{[], Map.put(track_builders, :audio, audio_spec)}

Expand Down
139 changes: 139 additions & 0 deletions lib/boombox/transcoders/audio.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
defmodule Boombox.Transcoders.Audio do
@moduledoc false
use Membrane.Bin

require Membrane.Logger

alias Boombox.Transcoders.Helpers.ForwardingFilter
alias Membrane.{AAC, Funnel, Opus, RawAudio, RemoteStream}

@type stream_format :: AAC.t() | Opus.t() | RemoteStream.t() | RawAudio.t()
@type stream_format_module :: AAC | Opus | RawAudio

@opus_sample_rate 48_000

def_input_pad :input,
accepted_format:
any_of(AAC, Opus, %RemoteStream{content_format: Opus, type: :packetized}, RawAudio)

def_output_pad :output, accepted_format: any_of(AAC, Opus, RawAudio)

def_options input_stream_format: [
spec: stream_format(),
default: nil
],
output_stream_format_module: [
spec: stream_format_module()
]

@impl true
def handle_init(_ctx, opts) do
spec = [
bin_input()
|> child(:forwarding_filter, ForwardingFilter),
child(:output_funnel, Funnel)
|> bin_output()
]

state =
Map.from_struct(opts)
|> Map.put(:input_linked_with_output?, false)

{link_actions, state} = maybe_link_input_with_output(state)
{[spec: spec] ++ link_actions, state}
end

@impl true
def handle_child_notification({:stream_format, stream_format}, :forwarding_filter, _ctx, state) do
%{state | input_stream_format: stream_format}
|> maybe_link_input_with_output()
end

@impl true
def handle_child_notification(_notification, _element, _ctx, state) do
{[], state}
end

defp maybe_link_input_with_output(state)
when state.input_linked_with_output? or state.input_stream_format == nil do
{[], state}
end

defp maybe_link_input_with_output(state) do
spec =
link_input_with_output(
state.input_stream_format,
state.output_stream_format_module
)

state = %{state | input_linked_with_output?: true}
{[spec: spec], state}
end

defp link_input_with_output(%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.
""")

get_child(:forwarding_filter)
|> get_child(:output_funnel)
end

defp link_input_with_output(%RemoteStream{content_format: Opus}, Opus) do
get_child(:forwarding_filter)
|> child(:opus_parser, Opus.Parser)
|> get_child(:output_funnel)
end

defp link_input_with_output(input_format, output_format_module) do
get_child(:forwarding_filter)
|> maybe_plug_decoder(input_format)
|> maybe_plug_resampler(input_format, output_format_module)
|> maybe_plug_encoder(output_format_module)
|> get_child(:output_funnel)
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, _input_format, _output_format_module) 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
Loading