Skip to content

Commit

Permalink
Add an option to specify FFmpeg encoding parameters (#114)
Browse files Browse the repository at this point in the history
* Add an option to specify FFmpeg encoding parameters

---------

Co-authored-by: Mateusz Front <mateusz.front@swmansion.com>
  • Loading branch information
varsill and mat-hek committed Sep 18, 2024
1 parent aa5b05f commit f13fc93
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 47 deletions.
74 changes: 30 additions & 44 deletions c_src/membrane_h264_ffmpeg_plugin/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,55 @@ void handle_destroy_state(UnifexEnv *env, State *state) {
}
}

static void set_x264_defaults(AVDictionary **params, char* preset) {
// Override FFmpeg defaults from https://github.com/mirror/x264/blob/eaa68fad9e5d201d42fde51665f2d137ae96baf0/encoder/encoder.c#L674
static void set_x264_defaults(AVDictionary **params, char *preset) {
// Override FFmpeg defaults from
// https://github.com/mirror/x264/blob/eaa68fad9e5d201d42fde51665f2d137ae96baf0/encoder/encoder.c#L674
av_dict_set(params, "qcomp", "0.6", 0);
av_dict_set(params, "me_range", "16", 0);
av_dict_set(params, "qdiff", "4", 0);
av_dict_set(params, "qmin", "0", 0);
av_dict_set(params, "qmax", "69", 0);
av_dict_set(params, "i_qfactor", "1.4", 0);
av_dict_set(params, "f_pb_factor", "1.3", 0);

if (strcmp(preset, "ultrafast") == 0)
{

if (strcmp(preset, "ultrafast") == 0) {
av_dict_set(params, "partitions", "none", 0);
av_dict_set(params, "subq", "0", 0);
}
else if (strcmp(preset, "superfast") == 0)
{
} else if (strcmp(preset, "superfast") == 0) {
av_dict_set(params, "partitions", "i8x8,i4x4", 0);
av_dict_set(params, "subq", "1", 0);
}
else if (strcmp(preset, "veryfast") == 0)
{
} else if (strcmp(preset, "veryfast") == 0) {
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "2", 0);
}
else if (strcmp(preset, "faster") == 0)
{
} else if (strcmp(preset, "faster") == 0) {
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "4", 0);
}
else if (strcmp(preset, "fast") == 0)
{
} else if (strcmp(preset, "fast") == 0) {
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "6", 0);
}
else if (strcmp(preset, "medium") == 0)
{
} else if (strcmp(preset, "medium") == 0) {
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "7", 0);
}
else if (strcmp(preset, "slow") == 0)
{
} else if (strcmp(preset, "slow") == 0) {
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "8", 0);
}
else if (strcmp(preset, "slower") == 0)
{
} else if (strcmp(preset, "slower") == 0) {
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "9", 0);
}
else if (strcmp(preset, "veryslow") == 0)
{
} else if (strcmp(preset, "veryslow") == 0) {
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "10", 0);
}
else if (strcmp(preset, "placebo") == 0)
{
} else if (strcmp(preset, "placebo") == 0) {
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "11", 0);
}
}
}


UNIFEX_TERM create(UnifexEnv *env, int width, int height, char *pix_fmt,
char *preset, char *tune, char *profile, int max_b_frames, int gop_size,
int timebase_num, int timebase_den, int crf, int sc_threshold) {
char *preset, char *tune, char *profile, int max_b_frames,
int gop_size, int timebase_num, int timebase_den, int crf,
int sc_threshold, ffmpeg_param *ffmpeg_params,
unsigned int ffmpeg_params_length) {
UNIFEX_TERM res;
AVDictionary *params = NULL;
State *state = unifex_alloc_state(env);
Expand Down Expand Up @@ -113,7 +96,7 @@ UNIFEX_TERM create(UnifexEnv *env, int width, int height, char *pix_fmt,

state->codec_ctx->time_base.num = timebase_num;
state->codec_ctx->time_base.den = timebase_den;

if (max_b_frames > -1) {
state->codec_ctx->max_b_frames = max_b_frames;
}
Expand All @@ -123,6 +106,10 @@ UNIFEX_TERM create(UnifexEnv *env, int width, int height, char *pix_fmt,

set_x264_defaults(&params, preset);

for (unsigned int i = 0; i < ffmpeg_params_length; i++) {
av_dict_set(&params, ffmpeg_params[i].key, ffmpeg_params[i].value, 0);
}

av_dict_set(&params, "preset", preset, 0);

if (strcmp("nil", profile) != 0) {
Expand Down Expand Up @@ -164,8 +151,7 @@ static int get_frames(UnifexEnv *env, AVFrame *frame,
int use_shm, State *state) {
AVPacket *pkt = av_packet_alloc();
UnifexPayload **frames = unifex_alloc((*max_frames) * sizeof(*frames));
int64_t *decoding_ts =
unifex_alloc((*max_frames) * sizeof(*decoding_ts));
int64_t *decoding_ts = unifex_alloc((*max_frames) * sizeof(*decoding_ts));
int64_t *presentation_ts =
unifex_alloc((*max_frames) * sizeof(*presentation_ts));

Expand All @@ -185,10 +171,10 @@ static int get_frames(UnifexEnv *env, AVFrame *frame,
if (*frame_cnt >= (*max_frames)) {
*max_frames *= 2;
frames = unifex_realloc(frames, (*max_frames) * sizeof(*frames));
decoding_ts = unifex_realloc(decoding_ts,
(*max_frames) * sizeof(*decoding_ts));
presentation_ts = unifex_realloc(presentation_ts,
(*max_frames) * sizeof(*presentation_ts));
decoding_ts =
unifex_realloc(decoding_ts, (*max_frames) * sizeof(*decoding_ts));
presentation_ts = unifex_realloc(
presentation_ts, (*max_frames) * sizeof(*presentation_ts));
}

decoding_ts[*frame_cnt] = pkt->dts;
Expand Down Expand Up @@ -228,7 +214,7 @@ UNIFEX_TERM encode(UnifexEnv *env, UnifexPayload *payload, int64_t pts,
frame->format = state->codec_ctx->pix_fmt;
frame->width = state->codec_ctx->width;
frame->height = state->codec_ctx->height;
if(keyframe_requested) {
if (keyframe_requested) {
frame->pict_type = AV_PICTURE_TYPE_I;
}
av_image_fill_arrays(frame->data, frame->linesize, payload->data,
Expand Down
10 changes: 9 additions & 1 deletion c_src/membrane_h264_ffmpeg_plugin/encoder.spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ module Membrane.H264.FFmpeg.Encoder.Native

state_type "State"

type(
ffmpeg_param :: %Membrane.H264.FFmpeg.Encoder.FFmpegParam{
key: string,
value: string
}
)

spec create(
width :: int,
height :: int,
Expand All @@ -14,7 +21,8 @@ spec create(
timebase_num :: int,
timebase_den :: int,
crf :: int,
sc_threshold :: int
sc_threshold :: int,
ffmpeg_params :: [ffmpeg_param]
) :: {:ok :: label, state} | {:error :: label, reason :: atom}

spec get_frame_size(state) :: {:ok :: label, frame_size :: int} | {:error :: label}
Expand Down
47 changes: 46 additions & 1 deletion lib/membrane_h264_ffmpeg/encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Membrane.H264.FFmpeg.Encoder do
Please check `t:t/0` for available options.
"""
use Membrane.Filter
require Membrane.Logger, as: Logger
alias __MODULE__.Native
alias Membrane.Buffer
alias Membrane.H264
Expand Down Expand Up @@ -121,10 +122,30 @@ defmodule Membrane.H264.FFmpeg.Encoder do
See [this page](https://en.wikibooks.org/wiki/MeGUI/x264_Settings#scenecut) for more info.
""",
default: @default_sc_threshold
],
ffmpeg_params: [
spec: %{String.t() => String.t()},
description: """
A map with parameters that are passed to the encoder.
You can use options from: https://ffmpeg.org/ffmpeg-codecs.html#libx264_002c-libx264rgb
and https://ffmpeg.org/ffmpeg-codecs.html#Codec-Options
Options available in the element options (`t:#{inspect(__MODULE__)}.t/0`), like `sc_threshold` or `crf`,
must be set there and not through `ffmpeg_params`.
""",
default: %{}
]

defmodule FFmpegParam do
@moduledoc false
@enforce_keys [:key, :value]
defstruct @enforce_keys
end

@impl true
def handle_init(_ctx, opts) do
warn_if_ffmpeg_params_overwrite_module_options(opts.ffmpeg_params)

state =
opts
|> Map.put(:encoder_ref, nil)
Expand Down Expand Up @@ -165,6 +186,9 @@ defmodule Membrane.H264.FFmpeg.Encoder do
frames_per_second when is_integer(frames_per_second) -> {1, frames_per_second}
end

ffmpeg_params =
Enum.map(state.ffmpeg_params, fn {key, value} -> %FFmpegParam{key: key, value: value} end)

with buffers <- flush_encoder_if_exists(state),
{:ok, new_encoder_ref} <-
Native.create(
Expand All @@ -179,7 +203,8 @@ defmodule Membrane.H264.FFmpeg.Encoder do
timebase_num,
timebase_den,
state.crf,
state.sc_threshold
state.sc_threshold,
ffmpeg_params
) do
stream_format = create_new_stream_format(stream_format, state)
actions = buffers ++ [stream_format: stream_format]
Expand Down Expand Up @@ -257,4 +282,24 @@ defmodule Membrane.H264.FFmpeg.Encoder do
other -> other
end
end

defp warn_if_ffmpeg_params_overwrite_module_options(ffmpeg_params) do
params_to_options_mapping = %{
"crf" => "crf",
"preset" => "preset",
"profile" => "profile",
"tune" => "tune",
"max_b_frames" => "max_b_frames",
"g" => "gop_size",
"sc_threshold" => "sc_threshold"
}

Map.keys(ffmpeg_params)
|> Enum.filter(fn param_name -> params_to_options_mapping[param_name] != nil end)
|> Enum.each(fn param_name ->
Logger.warning(
"The parameter: `#{param_name}` you provided in the `ffmpeg_params` map overwrites the setting from the modules option: `#{params_to_options_mapping[param_name]}`."
)
end)
end
end
Loading

0 comments on commit f13fc93

Please sign in to comment.