-
Notifications
You must be signed in to change notification settings - Fork 4
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
Add :force_transcoding option #65
Merged
Merged
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9457ef3
Add :enforce_transcoding? option
FelonEkonom e0ee3e5
Add warning if transcoding is disabled but might be helpful
FelonEkonom 328ce5b
Fix output codec resoulution
FelonEkonom 14cc17f
Assert video codecs in browser tests
FelonEkonom 972acc8
Remove leftover
FelonEkonom 0b33c54
Rename one private function
FelonEkonom f4ccbfc
Fix typo
FelonEkonom 3374365
Split new option into two
FelonEkonom b4d3c59
mix formaT
FelonEkonom 2e33430
Fix bug in VP8 transcoding:
FelonEkonom 813f611
Add enforce_transcoding to output options
FelonEkonom 16409b6
Fix parsing enforce-transcoding in CLI
Noarkhh 8ada813
enforce_transcoding -> force_transcoding
FelonEkonom 65e44a3
Fix CLI
FelonEkonom b01abb1
Merge remote-tracking branch 'origin/master' into enforce-transcoding…
FelonEkonom ce6c769
Implement CR suggestions
FelonEkonom 90263c7
Remove outdated docs
FelonEkonom c356960
Remove leftovers
FelonEkonom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
|
@@ -4,10 +4,13 @@ defmodule Boombox do | |
|
||
See `run/1` for details and [examples.livemd](examples.livemd) for examples. | ||
""" | ||
require Logger | ||
require Membrane.Time | ||
|
||
alias Membrane.RTP | ||
|
||
@type force_transcoding_value() :: boolean() | :audio | :video | ||
|
||
@type webrtc_signaling :: Membrane.WebRTC.Signaling.t() | String.t() | ||
@type in_stream_opts :: [ | ||
{:audio, :binary | boolean()} | ||
|
@@ -65,6 +68,7 @@ defmodule Boombox do | |
| {:address, :inet.ip_address() | String.t()} | ||
| {:port, :inet.port_number()} | ||
| {:target, String.t()} | ||
| {:force_transcoding, force_transcoding_value()} | ||
] | ||
|
||
@type input :: | ||
|
@@ -79,15 +83,22 @@ defmodule Boombox do | |
|
||
@type output :: | ||
(path_or_uri :: String.t()) | ||
| {path_or_uri :: String.t(), [{:force_transcoding, force_transcoding_value()}]} | ||
| {:mp4, location :: String.t()} | ||
| {:mp4, location :: String.t(), [{:force_transcoding, force_transcoding_value()}]} | ||
| {:webrtc, webrtc_signaling()} | ||
| {:webrtc, webrtc_signaling(), [{:force_transcoding, force_transcoding_value()}]} | ||
| {:whip, uri :: String.t(), [{:token, String.t()} | {bandit_option :: atom(), term()}]} | ||
| {:hls, location :: String.t()} | ||
| {:hls, location :: String.t(), [{:force_transcoding, force_transcoding_value()}]} | ||
| {:rtp, out_rtp_opts()} | ||
| {:stream, out_stream_opts()} | ||
|
||
@typep procs :: %{pipeline: pid(), supervisor: pid()} | ||
@typep opts_map :: %{input: input(), output: output()} | ||
@typep opts_map :: %{ | ||
input: input(), | ||
output: output() | ||
} | ||
|
||
@doc """ | ||
Runs boombox with given input and output. | ||
|
@@ -103,13 +114,35 @@ defmodule Boombox do | |
|
||
If the input is `{:stream, opts}`, a `Stream` or other `Enumerable` is expected | ||
as the first argument. | ||
|
||
If `:enforce_audio_transcoding?` or `:enforce_video_transcoding?` option is set to `true`, | ||
boombox will perform audio and/or video transcoding, even if it is not necessary. By default | ||
both options are set to `false`. | ||
|
||
``` | ||
Boombox.run( | ||
input: "path/to/file.mp4", | ||
output: {:webrtc, "ws://0.0.0.0:1234"}, | ||
enforce_video_transcoding?: true, | ||
enforce_audio_transcoding?: true | ||
) | ||
``` | ||
""" | ||
@spec run(Enumerable.t() | nil, input: input(), output: output()) :: :ok | Enumerable.t() | ||
@spec run(Enumerable.t() | nil, | ||
input: input(), | ||
output: output() | ||
) :: :ok | Enumerable.t() | ||
@endpoint_opts [:input, :output] | ||
def run(stream \\ nil, opts) do | ||
opts_keys = [:input, :output] | ||
opts = Keyword.validate!(opts, opts_keys) |> Map.new(fn {k, v} -> {k, parse_opt!(k, v)} end) | ||
opts = | ||
opts | ||
|> Keyword.validate!(@endpoint_opts) | ||
|> Map.new(fn {key, value} -> {key, parse_endpoint_opt!(key, value)} end) | ||
|> resolve_force_transcoding() | ||
|
||
:ok = maybe_log_transcoding_related_warning(opts) | ||
|
||
if key = Enum.find(opts_keys, fn k -> not is_map_key(opts, k) end) do | ||
if key = Enum.find(@endpoint_opts, fn k -> not is_map_key(opts, k) end) do | ||
raise "#{key} is not provided" | ||
end | ||
|
||
|
@@ -157,29 +190,33 @@ defmodule Boombox do | |
end | ||
end | ||
|
||
@spec parse_opt!(:input, input()) :: input() | ||
@spec parse_opt!(:output, output()) :: output() | ||
defp parse_opt!(direction, value) when is_binary(value) do | ||
@spec parse_endpoint_opt!(:input, input()) :: input() | ||
@spec parse_endpoint_opt!(:output, output()) :: output() | ||
defp parse_endpoint_opt!(direction, value) when is_binary(value) do | ||
parse_endpoint_opt!(direction, {value, []}) | ||
end | ||
|
||
defp parse_endpoint_opt!(direction, {value, opts}) when is_binary(value) do | ||
uri = URI.parse(value) | ||
scheme = uri.scheme | ||
extension = if uri.path, do: Path.extname(uri.path) | ||
|
||
case {scheme, extension, direction} do | ||
{scheme, ".mp4", :input} when scheme in [nil, "http", "https"] -> {:mp4, value} | ||
{nil, ".mp4", :output} -> {:mp4, value} | ||
{scheme, ".mp4", :input} when scheme in [nil, "http", "https"] -> {:mp4, value, opts} | ||
{nil, ".mp4", :output} -> {:mp4, value, opts} | ||
{scheme, _ext, :input} when scheme in ["rtmp", "rtmps"] -> {:rtmp, value} | ||
{"rtsp", _ext, :input} -> {:rtsp, value} | ||
{nil, ".m3u8", :output} -> {:hls, value} | ||
{nil, ".m3u8", :output} -> {:hls, value, opts} | ||
_other -> raise ArgumentError, "Unsupported URI: #{value} for direction: #{direction}" | ||
end | ||
|> then(&parse_opt!(direction, &1)) | ||
|> then(&parse_endpoint_opt!(direction, &1)) | ||
end | ||
|
||
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity | ||
defp parse_opt!(direction, value) when is_tuple(value) do | ||
defp parse_endpoint_opt!(direction, value) when is_tuple(value) do | ||
case value do | ||
{:mp4, location} when is_binary(location) and direction == :input -> | ||
parse_opt!(:input, {:mp4, location, []}) | ||
parse_endpoint_opt!(:input, {:mp4, location, []}) | ||
|
||
{:mp4, location, opts} when is_binary(location) and direction == :input -> | ||
if Keyword.keyword?(opts), | ||
|
@@ -188,14 +225,23 @@ defmodule Boombox do | |
{:mp4, location} when is_binary(location) and direction == :output -> | ||
{:mp4, location} | ||
|
||
{:mp4, location, _opts} when is_binary(location) and direction == :output -> | ||
value | ||
|
||
{:webrtc, %Membrane.WebRTC.Signaling{}} -> | ||
value | ||
|
||
{:webrtc, %Membrane.WebRTC.Signaling{}, _opts} when direction == :output -> | ||
value | ||
|
||
{:webrtc, uri} when is_binary(uri) -> | ||
value | ||
|
||
{:webrtc, uri, _opts} when is_binary(uri) and direction == :output -> | ||
value | ||
|
||
{:whip, uri} when is_binary(uri) -> | ||
parse_opt!(direction, {:whip, uri, []}) | ||
parse_endpoint_opt!(direction, {:whip, uri, []}) | ||
|
||
{:whip, uri, opts} when is_binary(uri) and is_list(opts) -> | ||
if Keyword.keyword?(opts) do | ||
|
@@ -208,6 +254,10 @@ defmodule Boombox do | |
{:hls, location} when direction == :output and is_binary(location) -> | ||
value | ||
|
||
{:hls, location, opts} | ||
when direction == :output and is_binary(location) and is_list(opts) -> | ||
value | ||
|
||
{:rtsp, location} when direction == :input and is_binary(location) -> | ||
value | ||
|
||
|
@@ -226,6 +276,24 @@ defmodule Boombox do | |
end | ||
end | ||
|
||
defguardp is_webrtc_endpoint(endpoint) | ||
when is_tuple(endpoint) and elem(endpoint, 0) in [:webrtc, :whip] | ||
|
||
@spec maybe_log_transcoding_related_warning(opts_map()) :: :ok | ||
def maybe_log_transcoding_related_warning(opts) do | ||
if is_webrtc_endpoint(opts.output) and not is_webrtc_endpoint(opts.input) and | ||
opts.force_transcoding not in [true, :video] do | ||
Logger.warning(""" | ||
Boombox output protocol is WebRTC, while Boombox input doesn't support keyframe requests. This \ | ||
might lead to issues with the output video if the output stream isn't sent only by localhost. You \ | ||
can solve this by setting `:force_transcoding` output option to `true` or `:video`, but be aware \ | ||
that it will increase Boombox CPU usage. | ||
""") | ||
end | ||
|
||
:ok | ||
end | ||
|
||
@spec consume_stream(Enumerable.t(), opts_map()) :: term() | ||
defp consume_stream(stream, opts) do | ||
procs = start_pipeline(opts) | ||
|
@@ -359,4 +427,23 @@ defmodule Boombox do | |
raise ArgumentError, "Invalid transport: #{inspect(transport)}" | ||
end | ||
end | ||
|
||
defp resolve_force_transcoding(opts) do | ||
maybe_keyword = | ||
opts.output | ||
|> Tuple.to_list() | ||
|> List.last() | ||
|
||
force_transcoding = | ||
Keyword.keyword?(maybe_keyword) && Keyword.get(maybe_keyword, :force_transcoding, false) | ||
|
||
opts | ||
|> Map.put(:force_transcoding, force_transcoding) | ||
|> Map.update!(:output, fn | ||
{:webrtc, signaling, _opts} -> {:webrtc, signaling} | ||
{:hls, location, _opts} -> {:hls, location} | ||
{:mp4, location, _opts} -> {:mp4, location} | ||
other -> other | ||
end) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks bad. Why not keep |
||
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
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 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 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make it full tuple