Skip to content

Commit

Permalink
Handle keyframe request it in the encoder, use x264 defaults, bump to…
Browse files Browse the repository at this point in the history
… v0.32.0 (#111)

* Generate keyframe in the encoder when requested with `Membrane.KeyframeRequestEvent` 

* Use x264 defaults in the encoder

* Bump to v0.32.0
  • Loading branch information
varsill authored Jul 10, 2024
1 parent b834dcf commit c63cd5d
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Note: `Membrane.H264.FFmpeg.Parser` has been removed. Now you can use our pure E
Add the following line to your `deps` in `mix.exs`. Run `mix deps.get`.

```elixir
{:membrane_h264_ffmpeg_plugin, "~> 0.31.8"}
{:membrane_h264_ffmpeg_plugin, "~> 0.32.0"}
```

This package depends on the [ffmpeg](https://www.ffmpeg.org) libraries. The precompiled builds will be pulled and linked automatically. However, should there be any problems, consider installing it manually.
Expand Down
73 changes: 69 additions & 4 deletions c_src/membrane_h264_ffmpeg_plugin/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,68 @@ 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
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)
{
av_dict_set(params, "partitions", "none", 0);
av_dict_set(params, "subq", "0", 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)
{
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "2", 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)
{
av_dict_set(params, "partitions", "p8x8,b8x8,i8x8,i4x4", 0);
av_dict_set(params, "subq", "6", 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)
{
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "8", 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)
{
av_dict_set(params, "partitions", "all", 0);
av_dict_set(params, "subq", "10", 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) {
Expand Down Expand Up @@ -58,6 +120,8 @@ UNIFEX_TERM create(UnifexEnv *env, int width, int height, char *pix_fmt,
state->codec_ctx->gop_size = gop_size;
}

set_x264_defaults(&params, preset);

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

if (strcmp("nil", profile) != 0) {
Expand All @@ -67,9 +131,7 @@ UNIFEX_TERM create(UnifexEnv *env, int width, int height, char *pix_fmt,
if (strcmp("nil", tune) != 0) {
av_dict_set(&params, "tune", tune, 0);
}

av_dict_set_int(&params, "crf", crf, 0);

av_dict_set_int(&params, "sc_threshold", sc_threshold, 0);

if (avcodec_open2(state->codec_ctx, codec, &params) < 0) {
Expand Down Expand Up @@ -153,7 +215,7 @@ static int get_frames(UnifexEnv *env, AVFrame *frame,
}

UNIFEX_TERM encode(UnifexEnv *env, UnifexPayload *payload, int64_t pts,
int use_shm, State *state) {
int use_shm, int keyframe_requested, State *state) {
UNIFEX_TERM res_term;
int res = 0;
int max_frames = 16, frame_cnt = 0;
Expand All @@ -165,6 +227,9 @@ 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) {
frame->pict_type = AV_PICTURE_TYPE_I;
}
av_image_fill_arrays(frame->data, frame->linesize, payload->data,
frame->format, frame->width, frame->height, 1);

Expand Down Expand Up @@ -239,4 +304,4 @@ UNIFEX_TERM flush(UnifexEnv *env, int use_shm, State *state) {
unifex_free(dts_list);
}
return res_term;
}
}
2 changes: 1 addition & 1 deletion c_src/membrane_h264_ffmpeg_plugin/encoder.spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec create(

spec get_frame_size(state) :: {:ok :: label, frame_size :: int} | {:error :: label}

spec encode(payload, pts :: int64, use_shm :: bool, state) ::
spec encode(payload, pts :: int64, use_shm :: bool, keyframe_requested :: bool, state) ::
{:ok :: label, dts_list :: [int64], pts_list :: [int64], [payload]}
| {:error :: label, reason :: atom}

Expand Down
19 changes: 18 additions & 1 deletion lib/membrane_h264_ffmpeg/encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ defmodule Membrane.H264.FFmpeg.Encoder do
state =
opts
|> Map.put(:encoder_ref, nil)
|> Map.put(:keyframe_requested?, false)

{[], state}
end
Expand All @@ -141,12 +142,13 @@ defmodule Membrane.H264.FFmpeg.Encoder do
buffer.payload,
pts,
use_shm?,
state.keyframe_requested?,
encoder_ref
) do
{:ok, dts_list, pts_list, frames} ->
bufs = wrap_frames(dts_list, pts_list, frames)

{bufs, state}
{bufs, %{state | keyframe_requested?: false}}

{:error, reason} ->
raise "Native encoder failed to encode the payload: #{inspect(reason)}"
Expand Down Expand Up @@ -194,6 +196,21 @@ defmodule Membrane.H264.FFmpeg.Encoder do
{actions, state}
end

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

@impl true
def handle_event(:output, %Membrane.KeyframeRequestEvent{}, _ctx, state) do
{[], %{state | keyframe_requested?: true}}
end

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

defp flush_encoder_if_exists(%{encoder_ref: nil}), do: []

defp flush_encoder_if_exists(%{encoder_ref: encoder_ref, use_shm?: use_shm?}) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Membrane.H264.FFmpeg.Plugin.MixProject do
use Mix.Project

@version "0.31.8"
@version "0.32.0"
@github_url "https://github.com/membraneframework/membrane_h264_ffmpeg_plugin"

def project do
Expand Down
3 changes: 2 additions & 1 deletion test/encoder/encoder_native_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ defmodule Encoder.NativeTest do
Enc.encode(
frame,
Common.to_h264_time_base_truncated(seconds(timestamp)),
false,
_use_shm? = false,
_keyframe_requested? = false,
ref
)
end
Expand Down

0 comments on commit c63cd5d

Please sign in to comment.