From a6d42593874695cb40adec9344e7d29383aa0771 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 11:27:06 +0200 Subject: [PATCH 01/24] Create encoder project skeleton --- bundlex.exs | 19 +++-- c_src/membrane_vpx_plugin/vpx_encoder.c | 74 ++++++++++++++++++ c_src/membrane_vpx_plugin/vpx_encoder.h | 12 +++ .../membrane_vpx_plugin/vpx_encoder.spec.exs | 18 +++++ lib/membrane_vpx/encoder/vp8_encoder.ex | 30 +++++++ lib/membrane_vpx/encoder/vp9_encoder.ex | 30 +++++++ lib/membrane_vpx/encoder/vpx_encoder.ex | 71 +++++++++++++++++ .../encoder/vpx_encoder_native.ex | 15 ++++ mix.exs | 3 +- mix.lock | 3 +- test/fixtures/{input_vp8.ivf => ref_vp8.ivf} | Bin test/fixtures/{input_vp9.ivf => ref_vp9.ivf} | Bin test/membrane_vpx_plugin/vpx_decoder_test.exs | 4 +- test/membrane_vpx_plugin/vpx_encoder_test.exs | 56 +++++++++++++ 14 files changed, 323 insertions(+), 12 deletions(-) create mode 100644 c_src/membrane_vpx_plugin/vpx_encoder.c create mode 100644 c_src/membrane_vpx_plugin/vpx_encoder.h create mode 100644 c_src/membrane_vpx_plugin/vpx_encoder.spec.exs create mode 100644 lib/membrane_vpx/encoder/vp8_encoder.ex create mode 100644 lib/membrane_vpx/encoder/vp9_encoder.ex create mode 100644 lib/membrane_vpx/encoder/vpx_encoder.ex create mode 100644 lib/membrane_vpx/encoder/vpx_encoder_native.ex rename test/fixtures/{input_vp8.ivf => ref_vp8.ivf} (100%) rename test/fixtures/{input_vp9.ivf => ref_vp9.ivf} (100%) create mode 100644 test/membrane_vpx_plugin/vpx_encoder_test.exs diff --git a/bundlex.exs b/bundlex.exs index e7d8486..d512c54 100644 --- a/bundlex.exs +++ b/bundlex.exs @@ -19,15 +19,18 @@ defmodule Membrane.VPx.BundlexProject do ] ], preprocessor: Unifex + ], + vpx_encoder: [ + interface: :nif, + sources: ["vpx_encoder.c"], + os_deps: [ + libvpx: [ + {:precompiled, Membrane.PrecompiledDependencyProvider.get_dependency_url(:libvpx)}, + {:pkg_config, "vpx"} + ] + ], + preprocessor: Unifex ] - # vpx_encoder: [ - # interface: :nif, - # sources: ["vpx_encoder.c"], - # os_deps: [ - # libvpx: [{:pkg_config, "vpx"}] - # ], - # preprocessor: Unifex - # ] ] end end diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c new file mode 100644 index 0000000..480783e --- /dev/null +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -0,0 +1,74 @@ +#include "vpx_encoder.h" + +void handle_destroy_state(UnifexEnv *env, State *state) { + UNIFEX_UNUSED(env); + + vpx_codec_destroy(&state->codec_context); +} + +vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { + switch (pixel_format) { + case PIXEL_FORMAT_I420: + return VPX_IMG_FMT_I420; + case PIXEL_FORMAT_I422: + return VPX_IMG_FMT_I422; + case PIXEL_FORMAT_I444: + return VPX_IMG_FMT_I444; + case PIXEL_FORMAT_YV12: + return VPX_IMG_FMT_YV12; + case PIXEL_FORMAT_NV12: + return VPX_IMG_FMT_NV12; + } +} + +UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, + unsigned int height, PixelFormat pixel_format) { + UNIFEX_TERM result; + State *state = unifex_alloc_state(env); + vpx_codec_enc_cfg_t config; + + switch (codec) { + case CODEC_VP8: + state->codec_interface = vpx_codec_vp8_cx(); + break; + case CODEC_VP9: + state->codec_interface = vpx_codec_vp9_cx(); + break; + } + + if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { + result = create_result_error(env, "Failed to get default codec config"); + unifex_release_state(env, state); + return result; + } + + config.g_h = height; + config.g_w = width; + config.g_timebase.num = 1; + config.g_timebase.den = 1000000000; // 1e9 + config.rc_target_bitrate = 200; + config.g_error_resilient = 1; + + vpx_codec_err_t res = vpx_codec_enc_init(&state->codec_context, + state->codec_interface, &config, 0); + if (res) { + printf("dupa: %d\n", res); + result = create_result_error(env, "Failed to initialize encoder"); + unifex_release_state(env, state); + return result; + } + if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, + height, 1)) { + result = create_result_error(env, "Failed to allocate image"); + unifex_release_state(env, state); + return result; + } + result = create_result_ok(env, state); + unifex_release_state(env, state); + return result; +} + +UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, + vpx_codec_pts_t pts, State *state) {} + +UNIFEX_TERM flush(UnifexEnv *env, State *state) {} \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.h b/c_src/membrane_vpx_plugin/vpx_encoder.h new file mode 100644 index 0000000..5946ed6 --- /dev/null +++ b/c_src/membrane_vpx_plugin/vpx_encoder.h @@ -0,0 +1,12 @@ +#pragma once +#include "vpx/vp8cx.h" +#include "vpx/vpx_encoder.h" +#include + +typedef struct State { + vpx_codec_ctx_t codec_context; + vpx_codec_iface_t *codec_interface; + vpx_image_t img; +} State; + +#include "_generated/vpx_encoder.h" diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs new file mode 100644 index 0000000..0278a35 --- /dev/null +++ b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs @@ -0,0 +1,18 @@ +module Membrane.VPx.Encoder.Native + +state_type "State" + +type codec :: :vp8 | :vp9 + +type pixel_format :: :I420 | :I422 | :I444 | :NV12 | :YV12 + +spec create(codec, width :: unsigned, height :: unsigned, pixel_format) :: + {:ok :: label, state} | {:error :: label, reason :: atom} + +spec encode_frame(payload, pts :: int64, state) :: + {:ok :: label, frames :: [payload]} | {:error :: label, reason :: atom} + +spec flush(state) :: + {:ok :: label, frames :: [payload]} | {:error :: label, reason :: atom} + +dirty :cpu, encode_frame: 3 diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex new file mode 100644 index 0000000..f437964 --- /dev/null +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -0,0 +1,30 @@ +defmodule Membrane.VP8.Encoder do + @moduledoc """ + Element that encodes a VP8 stream + """ + use Membrane.Filter + + alias Membrane.{VP8, VPx} + + def_options real_time: [ + spec: boolean(), + default: true + ] + + def_input_pad :input, + accepted_format: Membrane.RawVideo + + def_output_pad :output, + accepted_format: VP8 + + @impl true + def handle_init(ctx, opts) do + VPx.Encoder.handle_init(ctx, opts, :vp8) + end + + @impl true + defdelegate handle_stream_format(pad, stream_format, ctx, state), to: VPx.Encoder + + @impl true + defdelegate handle_buffer(pad, buffer, ctx, state), to: VPx.Encoder +end diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex new file mode 100644 index 0000000..1ff2eb8 --- /dev/null +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -0,0 +1,30 @@ +defmodule Membrane.VP9.Encoder do + @moduledoc """ + Element that encodes a VP9 stream + """ + use Membrane.Filter + + alias Membrane.{VP9, VPx} + + def_options real_time: [ + spec: boolean(), + default: true + ] + + def_input_pad :input, + accepted_format: Membrane.RawVideo + + def_output_pad :output, + accepted_format: VP9 + + @impl true + def handle_init(ctx, opts) do + VPx.Encoder.handle_init(ctx, opts, :vp9) + end + + @impl true + defdelegate handle_stream_format(pad, stream_format, ctx, state), to: VPx.Encoder + + @impl true + defdelegate handle_buffer(pad, buffer, ctx, state), to: VPx.Encoder +end diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex new file mode 100644 index 0000000..85f8b0c --- /dev/null +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -0,0 +1,71 @@ +defmodule Membrane.VPx.Encoder do + @moduledoc false + + alias Membrane.{Buffer, RawVideo, VP8, VP9} + alias Membrane.Element.CallbackContext + alias Membrane.VPx.Encoder.Native + + defmodule State do + @moduledoc false + + @type t :: %__MODULE__{ + codec: :vp8 | :vp9, + codec_module: VP8 | VP9, + encoder_ref: reference() | nil + } + + @enforce_keys [:codec, :codec_module] + defstruct @enforce_keys ++ + [ + encoder_ref: nil + ] + end + + @type callback_return :: {[Membrane.Element.Action.t()], State.t()} + + @spec handle_init(CallbackContext.t(), VP8.Decoder.t() | VP9.Decoder.t(), :vp8 | :vp9) :: + callback_return() + def handle_init(_ctx, opts, codec) do + state = %State{ + codec: codec, + codec_module: + case codec do + :vp8 -> VP8 + :vp9 -> VP9 + end + } + + {[], state} + end + + @spec handle_stream_format(:input, RawVideo.t(), CallbackContext.t(), State.t()) :: + callback_return() + def handle_stream_format( + :input, + raw_video_format, + _ctx, + %State{codec_module: codec_module} = state + ) do + %RawVideo{ + width: width, + height: height, + framerate: framerate, + pixel_format: pixel_format + } = raw_video_format + + output_stream_format = + struct(codec_module, width: width, height: height, framerate: framerate) + + native = Native.create!(state.codec, width, height, pixel_format) + + {[stream_format: {:output, output_stream_format}], state} + end + + @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: + callback_return() + def handle_buffer(:input, %Buffer{payload: payload, pts: pts}, _ctx, state) do + # buffers = Enum.map(decoded_frames, &%Buffer{payload: &1, pts: pts}) + # {[buffer: {:output, buffers}], state} + {[], state} + end +end diff --git a/lib/membrane_vpx/encoder/vpx_encoder_native.ex b/lib/membrane_vpx/encoder/vpx_encoder_native.ex new file mode 100644 index 0000000..e57e611 --- /dev/null +++ b/lib/membrane_vpx/encoder/vpx_encoder_native.ex @@ -0,0 +1,15 @@ +defmodule Membrane.VPx.Encoder.Native do + @moduledoc false + use Unifex.Loader + + alias Membrane.RawVideo + + @spec create!(:vp8 | :vp9, non_neg_integer(), non_neg_integer(), RawVideo.pixel_format()) :: + reference() + def create!(codec, width, height, pixel_format) do + case create(codec, width, height, pixel_format) do + {:ok, decoder_ref} -> decoder_ref + {:error, reason} -> raise "Failed to create native encoder: #{inspect(reason)}" + end + end +end diff --git a/mix.exs b/mix.exs index 29f7638..fbe4c52 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,7 @@ defmodule Membrane.VPx.Plugin.Mixfile do [ {:membrane_core, "~> 1.0"}, {:unifex, "~> 1.2"}, - {:membrane_raw_video_format, "~> 0.4.0"}, + {:membrane_raw_video_format, "~> 0.4.0", override: true}, # {:membrane_vp8_format, "~> 0.4.0"}, {:membrane_vp8_format, github: "membraneframework/membrane_vp8_format", branch: "add-fields", override: true}, @@ -54,6 +54,7 @@ defmodule Membrane.VPx.Plugin.Mixfile do branch: "fix-plugin", override: true, only: :test}, + {:membrane_raw_video_parser_plugin, "~> 0.12.1", only: :test}, {:membrane_file_plugin, "~> 0.17.0", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index 087e1ea..4d3f4f5 100644 --- a/mix.lock +++ b/mix.lock @@ -5,7 +5,7 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, - "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, @@ -24,6 +24,7 @@ "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "6fc0824073c1637b990b5e2eceb915a0f78e003c", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.0", "99fdf3a5cd5f64118e550b80d79ffed0d4be7becb3878a657f8726550c8c756f", [:mix], [], "hexpm", "1768c8137e4cfc6470117f71318a1dd9726c3b305a78bd56d90f796be716bd99"}, + "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, "membrane_vp8_format": {:git, "https://github.com/membraneframework/membrane_vp8_format.git", "b9d9e4d56d7f25c6ac341b2d4581445d22c1d190", [branch: "add-fields"]}, "membrane_vp9_format": {:git, "https://github.com/membraneframework/membrane_vp9_format.git", "93b2d68f4a3c9b06a1a3d7246bfd9374b58935ea", [branch: "add-fields"]}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, diff --git a/test/fixtures/input_vp8.ivf b/test/fixtures/ref_vp8.ivf similarity index 100% rename from test/fixtures/input_vp8.ivf rename to test/fixtures/ref_vp8.ivf diff --git a/test/fixtures/input_vp9.ivf b/test/fixtures/ref_vp9.ivf similarity index 100% rename from test/fixtures/input_vp9.ivf rename to test/fixtures/ref_vp9.ivf diff --git a/test/membrane_vpx_plugin/vpx_decoder_test.exs b/test/membrane_vpx_plugin/vpx_decoder_test.exs index 77d21c6..00fbf91 100644 --- a/test/membrane_vpx_plugin/vpx_decoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_decoder_test.exs @@ -11,7 +11,7 @@ defmodule Membrane.VPx.DecoderTest do test "VP8 codec", %{tmp_dir: tmp_dir} do perform_decoder_test( tmp_dir, - "input_vp8.ivf", + "ref_vp8.ivf", "output_vp8.raw", "ref_vp8.raw", %Membrane.VP8.Decoder{framerate: {30, 1}} @@ -21,7 +21,7 @@ defmodule Membrane.VPx.DecoderTest do test "VP9 codec", %{tmp_dir: tmp_dir} do perform_decoder_test( tmp_dir, - "input_vp9.ivf", + "ref_vp9.ivf", "output_vp9.raw", "ref_vp9.raw", %Membrane.VP9.Decoder{framerate: {30, 1}} diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs new file mode 100644 index 0000000..6cbac81 --- /dev/null +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -0,0 +1,56 @@ +defmodule Membrane.VPx.EncoderTest do + use ExUnit.Case, async: true + + import Membrane.Testing.Assertions + import Membrane.ChildrenSpec + + @fixtures_dir "test/fixtures" + + describe "Encoder encodes correctly for" do + @describetag :tmp_dir + test "VP8 codec", %{tmp_dir: tmp_dir} do + perform_decoder_test( + tmp_dir, + "ref_vp8.raw", + "output_vp8.ivf", + "ref_vp8.ivf", + %Membrane.VP8.Encoder{} + ) + end + + test "VP9 codec", %{tmp_dir: tmp_dir} do + perform_decoder_test( + tmp_dir, + "ref_vp9.ivf", + "output_vp9.ivf", + "ref_vp9.ivf", + %Membrane.VP9.Encoder{} + ) + end + end + + defp perform_decoder_test(tmp_dir, input_file, output_file, ref_file, decoder_struct) do + output_path = Path.join(tmp_dir, output_file) + ref_path = Path.join(@fixtures_dir, ref_file) + + pid = + Membrane.Testing.Pipeline.start_link_supervised!( + spec: + child(:source, %Membrane.File.Source{ + location: Path.join(@fixtures_dir, input_file) + }) + |> child(:parser, %Membrane.RawVideo.Parser{ + pixel_format: :I420, + width: 1080, + height: 720 + }) + |> child(:decoder, decoder_struct) + |> child(:serializer, %Membrane.IVF.Serializer{width: 0, height: 0}) + |> child(:sink, %Membrane.File.Sink{location: output_path}) + ) + + assert_end_of_stream(pid, :sink, :input, 2000) + + # assert File.read(ref_path) == File.read(output_path) + end +end From c9d95756755e74ef8992d9086258f944738a4c63 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 11:28:36 +0200 Subject: [PATCH 02/24] Finish the native part --- bundlex.exs | 4 +- c_src/membrane_vpx_plugin/vpx_common.c | 29 ++++++++ c_src/membrane_vpx_plugin/vpx_common.h | 14 ++++ c_src/membrane_vpx_plugin/vpx_decoder.c | 28 ++------ c_src/membrane_vpx_plugin/vpx_decoder.h | 6 +- c_src/membrane_vpx_plugin/vpx_encoder.c | 92 +++++++++++++++++++++---- c_src/membrane_vpx_plugin/vpx_encoder.h | 1 + 7 files changed, 130 insertions(+), 44 deletions(-) create mode 100644 c_src/membrane_vpx_plugin/vpx_common.c create mode 100644 c_src/membrane_vpx_plugin/vpx_common.h diff --git a/bundlex.exs b/bundlex.exs index d512c54..bdac040 100644 --- a/bundlex.exs +++ b/bundlex.exs @@ -11,7 +11,7 @@ defmodule Membrane.VPx.BundlexProject do [ vpx_decoder: [ interface: :nif, - sources: ["vpx_decoder.c"], + sources: ["vpx_decoder.c", "vpx_common.c"], os_deps: [ libvpx: [ {:precompiled, Membrane.PrecompiledDependencyProvider.get_dependency_url(:libvpx)}, @@ -22,7 +22,7 @@ defmodule Membrane.VPx.BundlexProject do ], vpx_encoder: [ interface: :nif, - sources: ["vpx_encoder.c"], + sources: ["vpx_encoder.c", "vpx_common.c"], os_deps: [ libvpx: [ {:precompiled, Membrane.PrecompiledDependencyProvider.get_dependency_url(:libvpx)}, diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c new file mode 100644 index 0000000..74fe09a --- /dev/null +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -0,0 +1,29 @@ +#include "vpx_common.h" + +Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { + const int height = (plane > 0 && img->y_chroma_shift > 0) + ? (img->d_h + 1) >> img->y_chroma_shift + : img->d_h; + + int width = (plane > 0 && img->x_chroma_shift > 0) + ? (img->d_w + 1) >> img->x_chroma_shift + : img->d_w; + + // Fixing NV12 chroma width if it is odd + if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) + width = (width + 1) & ~1; + + return (Dimensions){width, height}; +} + +void free_payloads(UnifexEnv *env, UnifexPayload **payloads, + unsigned int payloads_cnt) { + + for (unsigned int i = 0; i < payloads_cnt; i++) { + if (payloads[i] != NULL) { + unifex_payload_release(payloads[i]); + unifex_free(payloads[i]); + } + } + unifex_free(payloads); +} diff --git a/c_src/membrane_vpx_plugin/vpx_common.h b/c_src/membrane_vpx_plugin/vpx_common.h new file mode 100644 index 0000000..2829b64 --- /dev/null +++ b/c_src/membrane_vpx_plugin/vpx_common.h @@ -0,0 +1,14 @@ +#pragma once +#include "vpx/vpx_image.h" +#include +#include + +typedef struct Dimensions { + unsigned int width; + unsigned int height; +} Dimensions; + +Dimensions get_plane_dimensions(const vpx_image_t *img, int plane); + +void free_payloads(UnifexEnv *env, UnifexPayload **payloads, + unsigned int payloads_cnt); \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index 59bfc4c..95597e1 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -29,19 +29,6 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec) { return result; } -Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { - const int height = - (plane > 0 && img->y_chroma_shift > 0) ? (img->d_h + 1) >> img->y_chroma_shift : img->d_h; - - int width = - (plane > 0 && img->x_chroma_shift > 0) ? (img->d_w + 1) >> img->x_chroma_shift : img->d_w; - - // Fixing NV12 chroma width if it is odd - if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) - width = (width + 1) & ~1; - - return (Dimensions){width, height}; -} size_t get_image_byte_size(const vpx_image_t *img) { const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; @@ -55,12 +42,12 @@ size_t get_image_byte_size(const vpx_image_t *img) { return image_size; } -void get_output_frame_from_image(const vpx_image_t *img, UnifexPayload *output_frame) { +void get_raw_frame_from_image(const vpx_image_t *img, UnifexPayload *raw_frame) { const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; // Assuming that for nv12 we write all chroma data at once const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; - unsigned char *frame_data = output_frame->data; + unsigned char *frame_data = raw_frame->data; for (int plane = 0; plane < number_of_planes; ++plane) { const unsigned char *buf = img->planes[plane]; @@ -121,19 +108,14 @@ UNIFEX_TERM decode_frame(UnifexEnv *env, UnifexPayload *frame, State *state) { } alloc_output_frame(env, img, &output_frames[frames_cnt]); - get_output_frame_from_image(img, output_frames[frames_cnt]); + get_raw_frame_from_image(img, output_frames[frames_cnt]); pixel_format = get_pixel_format_from_image(img); frames_cnt++; } UNIFEX_TERM result = decode_frame_result_ok(env, output_frames, frames_cnt, pixel_format); - for (unsigned int i = 0; i < frames_cnt; i++) { - if (output_frames[i] != NULL) { - unifex_payload_release(output_frames[i]); - unifex_free(output_frames[i]); - } - } - unifex_free(output_frames); + + free_payloads(env, output_frames, frames_cnt); return result; } diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.h b/c_src/membrane_vpx_plugin/vpx_decoder.h index e743aa5..5eced85 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.h +++ b/c_src/membrane_vpx_plugin/vpx_decoder.h @@ -1,6 +1,7 @@ #pragma once #include "vpx/vp8dx.h" #include "vpx/vpx_decoder.h" +#include "vpx_common.h" #include typedef struct State { @@ -8,9 +9,4 @@ typedef struct State { vpx_codec_iface_t *codec_interface; } State; -typedef struct Dimensions { - unsigned int width; - unsigned int height; -} Dimensions; - #include "_generated/vpx_decoder.h" \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 480783e..56f5620 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -6,6 +6,19 @@ void handle_destroy_state(UnifexEnv *env, State *state) { vpx_codec_destroy(&state->codec_context); } +UNIFEX_TERM error(UnifexEnv *env, const char *reason, State *state) { + if (&state->codec_context) { + const char *detail = vpx_codec_error_detail(&state->codec_context); + fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(&state->codec_context)); + if (detail) { + fprintf(stderr, " %s\n", detail); + } + } + UNIFEX_TERM result = create_result_error(env, reason); + unifex_release_state(env, state); + return result; +} + vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { switch (pixel_format) { case PIXEL_FORMAT_I420: @@ -37,9 +50,7 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, } if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { - result = create_result_error(env, "Failed to get default codec config"); - unifex_release_state(env, state); - return result; + return error(env, "Failed to get default codec config", state); } config.g_h = height; @@ -49,26 +60,79 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, config.rc_target_bitrate = 200; config.g_error_resilient = 1; - vpx_codec_err_t res = vpx_codec_enc_init(&state->codec_context, - state->codec_interface, &config, 0); - if (res) { - printf("dupa: %d\n", res); - result = create_result_error(env, "Failed to initialize encoder"); - unifex_release_state(env, state); - return result; + if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, + 0)) { + return error(env, "Failed to initialize encoder", state); } if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, height, 1)) { - result = create_result_error(env, "Failed to allocate image"); - unifex_release_state(env, state); - return result; + return error(env, "Failed to allocate image", state); } result = create_result_ok(env, state); unifex_release_state(env, state); return result; } +void get_image_from_raw_frame(vpx_image_t *img, UnifexPayload *raw_frame) { + const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; + + // Assuming that for nv12 we write all chroma data at once + const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; + unsigned char *frame_data = raw_frame->data; + + for (int plane = 0; plane < number_of_planes; ++plane) { + const unsigned char *buf = img->planes[plane]; + const int stride = img->stride[plane]; + Dimensions plane_dimensions = get_plane_dimensions(img, plane); + + for (unsigned int y = 0; y < plane_dimensions.height; ++y) { + size_t bytes_to_write = bytes_per_pixel * plane_dimensions.width; + memcpy(buf, frame_data, bytes_to_write); + buf += stride; + frame_data += bytes_to_write; + } + } +} + UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, - vpx_codec_pts_t pts, State *state) {} + vpx_codec_pts_t pts, State *state) { + vpx_codec_iter_t iter = NULL; + int got_pkts = 0; + const vpx_codec_cx_pkt_t *packet = NULL; + unsigned int frames_cnt = 0, max_frames = 2; + UnifexPayload **encoded_frames = + unifex_alloc(max_frames * sizeof(*encoded_frames)); + + get_image_from_raw_frame(&state->img, raw_frame); + if (vpx_codec_encode(&state->codec_context, &state->img, pts, 1, 0, + VPX_DL_GOOD_QUALITY) != VPX_CODEC_OK) { + return error(env, "Failed to encode frame", state); + } + + while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != + NULL) { + got_pkts = 1; + if (frames_cnt >= max_frames) { + max_frames *= 2; + encoded_frames = + unifex_realloc(encoded_frames, max_frames * sizeof(*encoded_frames)); + } + + if (packet->kind == VPX_CODEC_CX_FRAME_PKT) { + // const int keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; + unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, + &encoded_frames[frames_cnt]); + memcpy(encoded_frames[frames_cnt], packet->data.frame.buf, + packet->data.frame.sz); + frames_cnt++; + } + } + + UNIFEX_TERM result = encode_frame_result_ok(env, encoded_frames, frames_cnt); + + free_payloads(env, encoded_frames, frames_cnt); + + return result; +} UNIFEX_TERM flush(UnifexEnv *env, State *state) {} \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.h b/c_src/membrane_vpx_plugin/vpx_encoder.h index 5946ed6..df1190c 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.h +++ b/c_src/membrane_vpx_plugin/vpx_encoder.h @@ -1,6 +1,7 @@ #pragma once #include "vpx/vp8cx.h" #include "vpx/vpx_encoder.h" +#include "vpx_common.h" #include typedef struct State { From be0e026da88f7919df6af3f47f56cbe79d02b8a4 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 19 Jun 2024 13:15:17 +0200 Subject: [PATCH 03/24] Encoder working --- c_src/membrane_vpx_plugin/vpx_common.c | 4 +--- c_src/membrane_vpx_plugin/vpx_common.h | 3 +-- c_src/membrane_vpx_plugin/vpx_decoder.c | 2 +- c_src/membrane_vpx_plugin/vpx_encoder.c | 13 +++++++------ lib/membrane_vpx/encoder/vpx_encoder.ex | 8 ++++---- mix.lock | 2 +- test/membrane_vpx_plugin/vpx_encoder_test.exs | 10 ++++++++-- 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c index 74fe09a..b287cee 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.c +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -16,9 +16,7 @@ Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { return (Dimensions){width, height}; } -void free_payloads(UnifexEnv *env, UnifexPayload **payloads, - unsigned int payloads_cnt) { - +void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt) { for (unsigned int i = 0; i < payloads_cnt; i++) { if (payloads[i] != NULL) { unifex_payload_release(payloads[i]); diff --git a/c_src/membrane_vpx_plugin/vpx_common.h b/c_src/membrane_vpx_plugin/vpx_common.h index 2829b64..05a50ca 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.h +++ b/c_src/membrane_vpx_plugin/vpx_common.h @@ -10,5 +10,4 @@ typedef struct Dimensions { Dimensions get_plane_dimensions(const vpx_image_t *img, int plane); -void free_payloads(UnifexEnv *env, UnifexPayload **payloads, - unsigned int payloads_cnt); \ No newline at end of file +void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt); \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index 95597e1..c0429d1 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -115,7 +115,7 @@ UNIFEX_TERM decode_frame(UnifexEnv *env, UnifexPayload *frame, State *state) { UNIFEX_TERM result = decode_frame_result_ok(env, output_frames, frames_cnt, pixel_format); - free_payloads(env, output_frames, frames_cnt); + free_payloads(output_frames, frames_cnt); return result; } diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 56f5620..7739093 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -49,6 +49,7 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, break; } + // return error(env, "Failed to get default codec config", state); if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { return error(env, "Failed to get default codec config", state); } @@ -81,7 +82,7 @@ void get_image_from_raw_frame(vpx_image_t *img, UnifexPayload *raw_frame) { unsigned char *frame_data = raw_frame->data; for (int plane = 0; plane < number_of_planes; ++plane) { - const unsigned char *buf = img->planes[plane]; + unsigned char *buf = img->planes[plane]; const int stride = img->stride[plane]; Dimensions plane_dimensions = get_plane_dimensions(img, plane); @@ -106,7 +107,7 @@ UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, get_image_from_raw_frame(&state->img, raw_frame); if (vpx_codec_encode(&state->codec_context, &state->img, pts, 1, 0, VPX_DL_GOOD_QUALITY) != VPX_CODEC_OK) { - return error(env, "Failed to encode frame", state); + return error(env, "Encoding frame failed", state); } while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != @@ -119,10 +120,10 @@ UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, } if (packet->kind == VPX_CODEC_CX_FRAME_PKT) { - // const int keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; + encoded_frames[frames_cnt] = unifex_alloc(sizeof(UnifexPayload)); unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, - &encoded_frames[frames_cnt]); - memcpy(encoded_frames[frames_cnt], packet->data.frame.buf, + encoded_frames[frames_cnt]); + memcpy(encoded_frames[frames_cnt]->data, packet->data.frame.buf, packet->data.frame.sz); frames_cnt++; } @@ -130,7 +131,7 @@ UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, UNIFEX_TERM result = encode_frame_result_ok(env, encoded_frames, frames_cnt); - free_payloads(env, encoded_frames, frames_cnt); + free_payloads(encoded_frames, frames_cnt); return result; } diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index 85f8b0c..e09cec4 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -58,14 +58,14 @@ defmodule Membrane.VPx.Encoder do native = Native.create!(state.codec, width, height, pixel_format) - {[stream_format: {:output, output_stream_format}], state} + {[stream_format: {:output, output_stream_format}], %{state | encoder_ref: native}} end @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: callback_return() def handle_buffer(:input, %Buffer{payload: payload, pts: pts}, _ctx, state) do - # buffers = Enum.map(decoded_frames, &%Buffer{payload: &1, pts: pts}) - # {[buffer: {:output, buffers}], state} - {[], state} + {:ok, encoded_frames} = Native.encode_frame(payload, pts, state.encoder_ref) + buffers = Enum.map(encoded_frames, &%Buffer{payload: &1, pts: pts}) + {[buffer: {:output, buffers}], state} end end diff --git a/mix.lock b/mix.lock index 4d3f4f5..b132b3c 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "membrane_core": {:hex, :membrane_core, "1.1.0", "c3bbaa5af7c26a7c3748e573efe343c2104801e3463b9e491a607e82860334a4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3209d7f7e86d736cb7caffbba16b075c571cebb9439ab939ed6119c50fb59a5"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.0", "e855a848e84eaed537b41fd4436712038fc5518059eadc8609c83cd2d819653a", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "9c3653ca9f13bb409b36257d6094798d4625c739ab7a4035c12308622eb16e0b"}, - "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "6fc0824073c1637b990b5e2eceb915a0f78e003c", [branch: "fix-plugin"]}, + "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "7f261b0328980eb851f8c33ff83fa149a573991d", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.0", "99fdf3a5cd5f64118e550b80d79ffed0d4be7becb3878a657f8726550c8c756f", [:mix], [], "hexpm", "1768c8137e4cfc6470117f71318a1dd9726c3b305a78bd56d90f796be716bd99"}, "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 6cbac81..69b9ece 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -42,10 +42,16 @@ defmodule Membrane.VPx.EncoderTest do |> child(:parser, %Membrane.RawVideo.Parser{ pixel_format: :I420, width: 1080, - height: 720 + height: 720, + framerate: {30, 1} }) + |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts")}) |> child(:decoder, decoder_struct) - |> child(:serializer, %Membrane.IVF.Serializer{width: 0, height: 0}) + |> child(:serializer, %Membrane.IVF.Serializer{ + width: 1080, + height: 720, + rate: 1_000_000_000 + }) |> child(:sink, %Membrane.File.Sink{location: output_path}) ) From 6b69f47413ac26635ce9c67617cd0c026605c8a6 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 19 Jun 2024 16:32:55 +0200 Subject: [PATCH 04/24] Implement flushing --- c_src/membrane_vpx_plugin/vpx_common.c | 31 ++++ c_src/membrane_vpx_plugin/vpx_common.h | 11 +- c_src/membrane_vpx_plugin/vpx_decoder.c | 29 +--- c_src/membrane_vpx_plugin/vpx_encoder.c | 136 +++++++++++------- c_src/membrane_vpx_plugin/vpx_encoder.h | 1 + .../membrane_vpx_plugin/vpx_encoder.spec.exs | 10 +- lib/membrane_vpx/encoder/vp8_encoder.ex | 3 + lib/membrane_vpx/encoder/vp9_encoder.ex | 3 + lib/membrane_vpx/encoder/vpx_encoder.ex | 21 ++- .../encoder/vpx_encoder_native.ex | 12 +- test/membrane_vpx_plugin/vpx_encoder_test.exs | 3 +- 11 files changed, 175 insertions(+), 85 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c index b287cee..75ea2f1 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.c +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -16,6 +16,37 @@ Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { return (Dimensions){width, height}; } +void convert_between_image_and_raw_frame(vpx_image_t *img, + UnifexPayload *raw_frame, + ConversionType conversion_type) { + const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; + + // Assuming that for nv12 we write all chroma data at once + const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; + unsigned char *frame_data = raw_frame->data; + + for (int plane = 0; plane < number_of_planes; ++plane) { + unsigned char *image_buf = img->planes[plane]; + const int stride = img->stride[plane]; + Dimensions plane_dimensions = get_plane_dimensions(img, plane); + + for (unsigned int y = 0; y < plane_dimensions.height; ++y) { + size_t bytes_to_write = bytes_per_pixel * plane_dimensions.width; + switch (conversion_type) { + case RAW_FRAME_TO_IMAGE: + memcpy(image_buf, frame_data, bytes_to_write); + break; + + case IMAGE_TO_RAW_FRAME: + memcpy(frame_data, image_buf, bytes_to_write); + break; + } + image_buf += stride; + frame_data += bytes_to_write; + } + } +} + void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt) { for (unsigned int i = 0; i < payloads_cnt; i++) { if (payloads[i] != NULL) { diff --git a/c_src/membrane_vpx_plugin/vpx_common.h b/c_src/membrane_vpx_plugin/vpx_common.h index 05a50ca..72bc5f6 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.h +++ b/c_src/membrane_vpx_plugin/vpx_common.h @@ -8,6 +8,15 @@ typedef struct Dimensions { unsigned int height; } Dimensions; +typedef enum ConversionType { + IMAGE_TO_RAW_FRAME, + RAW_FRAME_TO_IMAGE +} ConversionType; + Dimensions get_plane_dimensions(const vpx_image_t *img, int plane); -void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt); \ No newline at end of file +void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt); + +void convert_between_image_and_raw_frame(vpx_image_t *img, + UnifexPayload *raw_frame, + ConversionType conversion_type); \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index c0429d1..d814ba3 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -1,5 +1,8 @@ #include "vpx_decoder.h" +// The following code is based on the simple_decoder example provided by libvpx +// (https://github.com/webmproject/libvpx/blob/main/examples/simple_decoder.c) + void handle_destroy_state(UnifexEnv *env, State *state) { UNIFEX_UNUSED(env); @@ -42,25 +45,8 @@ size_t get_image_byte_size(const vpx_image_t *img) { return image_size; } -void get_raw_frame_from_image(const vpx_image_t *img, UnifexPayload *raw_frame) { - const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; - - // Assuming that for nv12 we write all chroma data at once - const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; - unsigned char *frame_data = raw_frame->data; - - for (int plane = 0; plane < number_of_planes; ++plane) { - const unsigned char *buf = img->planes[plane]; - const int stride = img->stride[plane]; - Dimensions plane_dimensions = get_plane_dimensions(img, plane); - - for (unsigned int y = 0; y < plane_dimensions.height; ++y) { - size_t bytes_to_write = bytes_per_pixel * plane_dimensions.width; - memcpy(frame_data, buf, bytes_to_write); - buf += stride; - frame_data += bytes_to_write; - } - } +void get_raw_frame_from_image(vpx_image_t *img, UnifexPayload *raw_frame) { + convert_between_image_and_raw_frame(img, raw_frame, IMAGE_TO_RAW_FRAME); } void alloc_output_frame(UnifexEnv *env, const vpx_image_t *img, UnifexPayload **output_frame) { @@ -72,19 +58,14 @@ PixelFormat get_pixel_format_from_image(vpx_image_t *img) { switch (img->fmt) { case VPX_IMG_FMT_I422: return PIXEL_FORMAT_I422; - case VPX_IMG_FMT_I420: return PIXEL_FORMAT_I420; - case VPX_IMG_FMT_I444: return PIXEL_FORMAT_I444; - case VPX_IMG_FMT_YV12: return PIXEL_FORMAT_YV12; - case VPX_IMG_FMT_NV12: return PIXEL_FORMAT_NV12; - default: return PIXEL_FORMAT_I420; } diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 7739093..56ad1e1 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -1,12 +1,17 @@ #include "vpx_encoder.h" +// The following code is based on the simple_encoder example provided by libvpx +// (https://github.com/webmproject/libvpx/blob/main/examples/simple_encoder.c) + void handle_destroy_state(UnifexEnv *env, State *state) { UNIFEX_UNUSED(env); vpx_codec_destroy(&state->codec_context); } -UNIFEX_TERM error(UnifexEnv *env, const char *reason, State *state) { +UNIFEX_TERM error(UnifexEnv *env, const char *reason, + UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), + State *state) { if (&state->codec_context) { const char *detail = vpx_codec_error_detail(&state->codec_context); fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(&state->codec_context)); @@ -14,7 +19,7 @@ UNIFEX_TERM error(UnifexEnv *env, const char *reason, State *state) { fprintf(stderr, " %s\n", detail); } } - UNIFEX_TERM result = create_result_error(env, reason); + UNIFEX_TERM result = result_error_fun(env, reason); unifex_release_state(env, state); return result; } @@ -34,8 +39,20 @@ vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { } } +int translate_encoding_quality(EncodingQuality encoding_quality) { + switch (encoding_quality) { + case ENCODING_QUALITY_BEST: + return 0; + case ENCODING_QUALITY_GOOD: + return 1000000; + case ENCODING_QUALITY_REALTIME: + return 1; + } +} + UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, - unsigned int height, PixelFormat pixel_format) { + unsigned int height, PixelFormat pixel_format, + EncodingQuality encoding_quality) { UNIFEX_TERM result; State *state = unifex_alloc_state(env); vpx_codec_enc_cfg_t config; @@ -48,10 +65,12 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, state->codec_interface = vpx_codec_vp9_cx(); break; } + state->encoding_quality = translate_encoding_quality(encoding_quality); // return error(env, "Failed to get default codec config", state); if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { - return error(env, "Failed to get default codec config", state); + return error(env, "Failed to get default codec config", create_result_error, + state); } config.g_h = height; @@ -63,11 +82,12 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, 0)) { - return error(env, "Failed to initialize encoder", state); + return error(env, "Failed to initialize encoder", create_result_error, + state); } if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, height, 1)) { - return error(env, "Failed to allocate image", state); + return error(env, "Failed to allocate image", create_result_error, state); } result = create_result_ok(env, state); unifex_release_state(env, state); @@ -75,65 +95,81 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, } void get_image_from_raw_frame(vpx_image_t *img, UnifexPayload *raw_frame) { - const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; - - // Assuming that for nv12 we write all chroma data at once - const int number_of_planes = (img->fmt == VPX_IMG_FMT_NV12) ? 2 : 3; - unsigned char *frame_data = raw_frame->data; - - for (int plane = 0; plane < number_of_planes; ++plane) { - unsigned char *buf = img->planes[plane]; - const int stride = img->stride[plane]; - Dimensions plane_dimensions = get_plane_dimensions(img, plane); - - for (unsigned int y = 0; y < plane_dimensions.height; ++y) { - size_t bytes_to_write = bytes_per_pixel * plane_dimensions.width; - memcpy(buf, frame_data, bytes_to_write); - buf += stride; - frame_data += bytes_to_write; - } - } + convert_between_image_and_raw_frame(img, raw_frame, RAW_FRAME_TO_IMAGE); } -UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, - vpx_codec_pts_t pts, State *state) { +void alloc_output_frame(UnifexEnv *env, const vpx_codec_cx_pkt_t *packet, + UnifexPayload **output_frame) { + *output_frame = unifex_alloc(sizeof(UnifexPayload)); + unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, + *output_frame); +} + +UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, + State *state) { vpx_codec_iter_t iter = NULL; - int got_pkts = 0; + int flushing = (img == NULL), got_packets = 0; const vpx_codec_cx_pkt_t *packet = NULL; unsigned int frames_cnt = 0, max_frames = 2; UnifexPayload **encoded_frames = unifex_alloc(max_frames * sizeof(*encoded_frames)); - - get_image_from_raw_frame(&state->img, raw_frame); - if (vpx_codec_encode(&state->codec_context, &state->img, pts, 1, 0, - VPX_DL_GOOD_QUALITY) != VPX_CODEC_OK) { - return error(env, "Encoding frame failed", state); - } - - while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != - NULL) { - got_pkts = 1; - if (frames_cnt >= max_frames) { - max_frames *= 2; - encoded_frames = - unifex_realloc(encoded_frames, max_frames * sizeof(*encoded_frames)); + vpx_codec_pts_t *encoded_frames_timestamps = + unifex_alloc(max_frames * sizeof(*encoded_frames_timestamps)); + + do { + if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, + state->encoding_quality) != VPX_CODEC_OK) { + if (flushing) { + return error(env, "Encoding frame failed", flush_result_error, state); + } else { + return error(env, "Encoding frame failed", encode_frame_result_error, + state); + } } - - if (packet->kind == VPX_CODEC_CX_FRAME_PKT) { - encoded_frames[frames_cnt] = unifex_alloc(sizeof(UnifexPayload)); - unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, - encoded_frames[frames_cnt]); + got_packets = 0; + + while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != + NULL) { + got_packets = 1; + if (packet->kind != VPX_CODEC_CX_FRAME_PKT) + continue; + + if (frames_cnt >= max_frames) { + max_frames *= 2; + encoded_frames = unifex_realloc(encoded_frames, + max_frames * sizeof(*encoded_frames)); + + encoded_frames_timestamps = + unifex_realloc(encoded_frames_timestamps, + max_frames * sizeof(*encoded_frames_timestamps)); + } + alloc_output_frame(env, packet, &encoded_frames[frames_cnt]); memcpy(encoded_frames[frames_cnt]->data, packet->data.frame.buf, packet->data.frame.sz); + encoded_frames_timestamps[frames_cnt] = packet->data.frame.pts; frames_cnt++; } - } - - UNIFEX_TERM result = encode_frame_result_ok(env, encoded_frames, frames_cnt); + } while (got_packets && flushing); + UNIFEX_TERM result; + if (flushing) { + result = flush_result_ok(env, encoded_frames, frames_cnt, + encoded_frames_timestamps, frames_cnt); + } else { + result = encode_frame_result_ok(env, encoded_frames, frames_cnt, + encoded_frames_timestamps, frames_cnt); + } free_payloads(encoded_frames, frames_cnt); return result; } -UNIFEX_TERM flush(UnifexEnv *env, State *state) {} \ No newline at end of file +UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, + vpx_codec_pts_t pts, State *state) { + get_image_from_raw_frame(&state->img, raw_frame); + return encode(env, &state->img, pts, state); +} + +UNIFEX_TERM flush(UnifexEnv *env, State *state) { + return encode(env, NULL, 0, state); +} \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.h b/c_src/membrane_vpx_plugin/vpx_encoder.h index df1190c..79739b3 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.h +++ b/c_src/membrane_vpx_plugin/vpx_encoder.h @@ -8,6 +8,7 @@ typedef struct State { vpx_codec_ctx_t codec_context; vpx_codec_iface_t *codec_interface; vpx_image_t img; + int encoding_quality; } State; #include "_generated/vpx_encoder.h" diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs index 0278a35..73796db 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs +++ b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs @@ -6,13 +6,17 @@ type codec :: :vp8 | :vp9 type pixel_format :: :I420 | :I422 | :I444 | :NV12 | :YV12 -spec create(codec, width :: unsigned, height :: unsigned, pixel_format) :: +type encoding_quality :: :best | :good | :realtime + +spec create(codec, width :: unsigned, height :: unsigned, pixel_format, encoding_quality) :: {:ok :: label, state} | {:error :: label, reason :: atom} spec encode_frame(payload, pts :: int64, state) :: - {:ok :: label, frames :: [payload]} | {:error :: label, reason :: atom} + {:ok :: label, frames :: [payload], timestamps :: [int64]} + | {:error :: label, reason :: atom} spec flush(state) :: - {:ok :: label, frames :: [payload]} | {:error :: label, reason :: atom} + {:ok :: label, frames :: [payload], timestamps :: [int64]} + | {:error :: label, reason :: atom} dirty :cpu, encode_frame: 3 diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index f437964..774d812 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -27,4 +27,7 @@ defmodule Membrane.VP8.Encoder do @impl true defdelegate handle_buffer(pad, buffer, ctx, state), to: VPx.Encoder + + @impl true + defdelegate handle_end_of_stream(pad, ctx, state), to: VPx.Encoder end diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index 1ff2eb8..3025766 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -27,4 +27,7 @@ defmodule Membrane.VP9.Encoder do @impl true defdelegate handle_buffer(pad, buffer, ctx, state), to: VPx.Encoder + + @impl true + defdelegate handle_end_of_stream(pad, ctx, state), to: VPx.Encoder end diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index e09cec4..17bed0f 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -56,7 +56,7 @@ defmodule Membrane.VPx.Encoder do output_stream_format = struct(codec_module, width: width, height: height, framerate: framerate) - native = Native.create!(state.codec, width, height, pixel_format) + native = Native.create!(state.codec, width, height, pixel_format, :best) {[stream_format: {:output, output_stream_format}], %{state | encoder_ref: native}} end @@ -64,8 +64,23 @@ defmodule Membrane.VPx.Encoder do @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: callback_return() def handle_buffer(:input, %Buffer{payload: payload, pts: pts}, _ctx, state) do - {:ok, encoded_frames} = Native.encode_frame(payload, pts, state.encoder_ref) - buffers = Enum.map(encoded_frames, &%Buffer{payload: &1, pts: pts}) + {:ok, encoded_frames, timestamps} = Native.encode_frame(payload, pts, state.encoder_ref) + + buffers = + Enum.zip(encoded_frames, timestamps) + |> Enum.map(fn {frame, frame_pts} -> %Buffer{payload: frame, pts: frame_pts} end) + {[buffer: {:output, buffers}], state} end + + @spec handle_end_of_stream(:input, CallbackContext.t(), State.t()) :: callback_return() + def handle_end_of_stream(:input, _ctx, state) do + {:ok, encoded_frames, timestamps} = Native.flush(state.encoder_ref) + + buffers = + Enum.zip(encoded_frames, timestamps) + |> Enum.map(fn {frame, frame_pts} -> %Buffer{payload: frame, pts: frame_pts} end) + + {[buffer: {:output, buffers}, end_of_stream: :output], state} + end end diff --git a/lib/membrane_vpx/encoder/vpx_encoder_native.ex b/lib/membrane_vpx/encoder/vpx_encoder_native.ex index e57e611..59412b1 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder_native.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder_native.ex @@ -4,10 +4,16 @@ defmodule Membrane.VPx.Encoder.Native do alias Membrane.RawVideo - @spec create!(:vp8 | :vp9, non_neg_integer(), non_neg_integer(), RawVideo.pixel_format()) :: + @spec create!( + :vp8 | :vp9, + non_neg_integer(), + non_neg_integer(), + RawVideo.pixel_format(), + :best | :good | :realtime + ) :: reference() - def create!(codec, width, height, pixel_format) do - case create(codec, width, height, pixel_format) do + def create!(codec, width, height, pixel_format, encoding_quality) do + case create(codec, width, height, pixel_format, encoding_quality) do {:ok, decoder_ref} -> decoder_ref {:error, reason} -> raise "Failed to create native encoder: #{inspect(reason)}" end diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 69b9ece..28a624e 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -45,8 +45,9 @@ defmodule Membrane.VPx.EncoderTest do height: 720, framerate: {30, 1} }) - |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts")}) + # |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts1")}) |> child(:decoder, decoder_struct) + # |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts2")}) |> child(:serializer, %Membrane.IVF.Serializer{ width: 1080, height: 720, From ee554d0723ae8f754457afa5ee693a855d3b4d5c Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 19 Jun 2024 17:14:49 +0200 Subject: [PATCH 05/24] Allow for specifying encoding deadline --- c_src/membrane_vpx_plugin/vpx_encoder.c | 36 +++++++------------ c_src/membrane_vpx_plugin/vpx_encoder.h | 2 +- .../membrane_vpx_plugin/vpx_encoder.spec.exs | 10 ++++-- lib/membrane_vpx/encoder/vp8_encoder.ex | 12 +++++-- lib/membrane_vpx/encoder/vp9_encoder.ex | 12 +++++-- lib/membrane_vpx/encoder/vpx_encoder.ex | 8 +++-- .../encoder/vpx_encoder_native.ex | 11 +++--- test/membrane_vpx_plugin/vpx_encoder_test.exs | 4 +-- 8 files changed, 51 insertions(+), 44 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 56ad1e1..497d8d5 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -9,10 +9,10 @@ void handle_destroy_state(UnifexEnv *env, State *state) { vpx_codec_destroy(&state->codec_context); } -UNIFEX_TERM error(UnifexEnv *env, const char *reason, +UNIFEX_TERM error(UnifexEnv *env, const char *reason, int codec_initialized, UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), State *state) { - if (&state->codec_context) { + if (codec_initialized) { const char *detail = vpx_codec_error_detail(&state->codec_context); fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(&state->codec_context)); if (detail) { @@ -39,20 +39,9 @@ vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { } } -int translate_encoding_quality(EncodingQuality encoding_quality) { - switch (encoding_quality) { - case ENCODING_QUALITY_BEST: - return 0; - case ENCODING_QUALITY_GOOD: - return 1000000; - case ENCODING_QUALITY_REALTIME: - return 1; - } -} - UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, unsigned int height, PixelFormat pixel_format, - EncodingQuality encoding_quality) { + unsigned int encoding_deadline) { UNIFEX_TERM result; State *state = unifex_alloc_state(env); vpx_codec_enc_cfg_t config; @@ -65,29 +54,29 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, state->codec_interface = vpx_codec_vp9_cx(); break; } - state->encoding_quality = translate_encoding_quality(encoding_quality); + state->encoding_deadline = encoding_deadline; // return error(env, "Failed to get default codec config", state); if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { - return error(env, "Failed to get default codec config", create_result_error, - state); + return error(env, "Failed to get default codec config", 0, + create_result_error, state); } config.g_h = height; config.g_w = width; config.g_timebase.num = 1; config.g_timebase.den = 1000000000; // 1e9 - config.rc_target_bitrate = 200; config.g_error_resilient = 1; if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, 0)) { - return error(env, "Failed to initialize encoder", create_result_error, + return error(env, "Failed to initialize encoder", 0, create_result_error, state); } if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, height, 1)) { - return error(env, "Failed to allocate image", create_result_error, state); + return error(env, "Failed to allocate image", 1, create_result_error, + state); } result = create_result_ok(env, state); unifex_release_state(env, state); @@ -118,11 +107,12 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, do { if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, - state->encoding_quality) != VPX_CODEC_OK) { + state->encoding_deadline) != VPX_CODEC_OK) { if (flushing) { - return error(env, "Encoding frame failed", flush_result_error, state); + return error(env, "Encoding frame failed", 1, flush_result_error, + state); } else { - return error(env, "Encoding frame failed", encode_frame_result_error, + return error(env, "Encoding frame failed", 1, encode_frame_result_error, state); } } diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.h b/c_src/membrane_vpx_plugin/vpx_encoder.h index 79739b3..7618beb 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.h +++ b/c_src/membrane_vpx_plugin/vpx_encoder.h @@ -8,7 +8,7 @@ typedef struct State { vpx_codec_ctx_t codec_context; vpx_codec_iface_t *codec_interface; vpx_image_t img; - int encoding_quality; + unsigned int encoding_deadline; } State; #include "_generated/vpx_encoder.h" diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs index 73796db..97aa370 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs +++ b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs @@ -6,9 +6,13 @@ type codec :: :vp8 | :vp9 type pixel_format :: :I420 | :I422 | :I444 | :NV12 | :YV12 -type encoding_quality :: :best | :good | :realtime - -spec create(codec, width :: unsigned, height :: unsigned, pixel_format, encoding_quality) :: +spec create( + codec, + width :: unsigned, + height :: unsigned, + pixel_format, + encoding_deadline :: unsigned + ) :: {:ok :: label, state} | {:error :: label, reason :: atom} spec encode_frame(payload, pts :: int64, state) :: diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index 774d812..12e1630 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -6,9 +6,15 @@ defmodule Membrane.VP8.Encoder do alias Membrane.{VP8, VPx} - def_options real_time: [ - spec: boolean(), - default: true + def_options encoding_deadline: [ + spec: non_neg_integer(), + default: 1, + description: """ + Determines how long should it take the encoder to encode a frame (in microseconds). + The longer the encoding takes the better the quality will be. If set to 0 the + encoder will take as long as it needs to produce the best frame possible. Note that + this is a soft limit, there is no guarantee that te encoding will never exceed it. + """ ] def_input_pad :input, diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index 3025766..7c46ca7 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -6,9 +6,15 @@ defmodule Membrane.VP9.Encoder do alias Membrane.{VP9, VPx} - def_options real_time: [ - spec: boolean(), - default: true + def_options encoding_deadline: [ + spec: non_neg_integer(), + default: 1, + description: """ + Determines how long should it take the encoder to encode a frame (in microseconds). + The longer the encoding takes the better the quality will be. If set to 0 the + encoder will take as long as it needs to produce the best frame possible. Note that + this is a soft limit, there is no guarantee that te encoding will never exceed it. + """ ] def_input_pad :input, diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index 17bed0f..40a8d01 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -11,10 +11,11 @@ defmodule Membrane.VPx.Encoder do @type t :: %__MODULE__{ codec: :vp8 | :vp9, codec_module: VP8 | VP9, + encoding_deadline: non_neg_integer(), encoder_ref: reference() | nil } - @enforce_keys [:codec, :codec_module] + @enforce_keys [:codec, :codec_module, :encoding_deadline] defstruct @enforce_keys ++ [ encoder_ref: nil @@ -32,7 +33,8 @@ defmodule Membrane.VPx.Encoder do case codec do :vp8 -> VP8 :vp9 -> VP9 - end + end, + encoding_deadline: opts.encoding_deadline } {[], state} @@ -56,7 +58,7 @@ defmodule Membrane.VPx.Encoder do output_stream_format = struct(codec_module, width: width, height: height, framerate: framerate) - native = Native.create!(state.codec, width, height, pixel_format, :best) + native = Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) {[stream_format: {:output, output_stream_format}], %{state | encoder_ref: native}} end diff --git a/lib/membrane_vpx/encoder/vpx_encoder_native.ex b/lib/membrane_vpx/encoder/vpx_encoder_native.ex index 59412b1..19eac3d 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder_native.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder_native.ex @@ -2,18 +2,17 @@ defmodule Membrane.VPx.Encoder.Native do @moduledoc false use Unifex.Loader - alias Membrane.RawVideo + alias Membrane.{RawVideo, VPx} @spec create!( :vp8 | :vp9, non_neg_integer(), non_neg_integer(), RawVideo.pixel_format(), - :best | :good | :realtime - ) :: - reference() - def create!(codec, width, height, pixel_format, encoding_quality) do - case create(codec, width, height, pixel_format, encoding_quality) do + non_neg_integer() + ) :: reference() + def create!(codec, width, height, pixel_format, encoding_deadline) do + case create(codec, width, height, pixel_format, encoding_deadline) do {:ok, decoder_ref} -> decoder_ref {:error, reason} -> raise "Failed to create native encoder: #{inspect(reason)}" end diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 28a624e..0d420f9 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -14,7 +14,7 @@ defmodule Membrane.VPx.EncoderTest do "ref_vp8.raw", "output_vp8.ivf", "ref_vp8.ivf", - %Membrane.VP8.Encoder{} + %Membrane.VP8.Encoder{encoding_deadline: 0} ) end @@ -24,7 +24,7 @@ defmodule Membrane.VPx.EncoderTest do "ref_vp9.ivf", "output_vp9.ivf", "ref_vp9.ivf", - %Membrane.VP9.Encoder{} + %Membrane.VP9.Encoder{encoding_deadline: 0} ) end end From cfcb2f0925a2e8bee0b207400a486fb3d414ad7b Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 19 Jun 2024 17:25:52 +0200 Subject: [PATCH 06/24] Minor refactor to satisfy the CI --- c_src/membrane_vpx_plugin/vpx_encoder.c | 2 ++ lib/membrane_vpx/encoder/vpx_encoder_native.ex | 4 +--- test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 497d8d5..3646c7d 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -36,6 +36,8 @@ vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { return VPX_IMG_FMT_YV12; case PIXEL_FORMAT_NV12: return VPX_IMG_FMT_NV12; + default: + return VPX_IMG_FMT_I420; } } diff --git a/lib/membrane_vpx/encoder/vpx_encoder_native.ex b/lib/membrane_vpx/encoder/vpx_encoder_native.ex index 19eac3d..376d3b0 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder_native.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder_native.ex @@ -2,13 +2,11 @@ defmodule Membrane.VPx.Encoder.Native do @moduledoc false use Unifex.Loader - alias Membrane.{RawVideo, VPx} - @spec create!( :vp8 | :vp9, non_neg_integer(), non_neg_integer(), - RawVideo.pixel_format(), + Membrane.RawVideo.pixel_format(), non_neg_integer() ) :: reference() def create!(codec, width, height, pixel_format, encoding_deadline) do diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 0d420f9..169f699 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -31,7 +31,7 @@ defmodule Membrane.VPx.EncoderTest do defp perform_decoder_test(tmp_dir, input_file, output_file, ref_file, decoder_struct) do output_path = Path.join(tmp_dir, output_file) - ref_path = Path.join(@fixtures_dir, ref_file) + # ref_path = Path.join(@fixtures_dir, ref_file) pid = Membrane.Testing.Pipeline.start_link_supervised!( From 5107acfb9297555d75e88469e914549d10eb0049 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 19 Jun 2024 17:59:28 +0200 Subject: [PATCH 07/24] Fix warnings --- lib/membrane_vpx/encoder/vpx_encoder.ex | 2 +- test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index 40a8d01..f8365ec 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -24,7 +24,7 @@ defmodule Membrane.VPx.Encoder do @type callback_return :: {[Membrane.Element.Action.t()], State.t()} - @spec handle_init(CallbackContext.t(), VP8.Decoder.t() | VP9.Decoder.t(), :vp8 | :vp9) :: + @spec handle_init(CallbackContext.t(), VP8.Encoder.t() | VP9.Encoder.t(), :vp8 | :vp9) :: callback_return() def handle_init(_ctx, opts, codec) do state = %State{ diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 169f699..0cc8bf9 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -29,7 +29,7 @@ defmodule Membrane.VPx.EncoderTest do end end - defp perform_decoder_test(tmp_dir, input_file, output_file, ref_file, decoder_struct) do + defp perform_decoder_test(tmp_dir, input_file, output_file, _ref_file, decoder_struct) do output_path = Path.join(tmp_dir, output_file) # ref_path = Path.join(@fixtures_dir, ref_file) From 3e923b62a289db5e584327510e2dc36a7ee6e306 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Thu, 20 Jun 2024 01:05:56 +0200 Subject: [PATCH 08/24] Change formatting --- c_src/membrane_vpx_plugin/vpx_common.c | 16 ++--- c_src/membrane_vpx_plugin/vpx_common.h | 11 ++- c_src/membrane_vpx_plugin/vpx_encoder.c | 92 ++++++++++++------------- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c index 75ea2f1..7e6f925 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.c +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -1,13 +1,11 @@ #include "vpx_common.h" Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { - const int height = (plane > 0 && img->y_chroma_shift > 0) - ? (img->d_h + 1) >> img->y_chroma_shift - : img->d_h; + const int height = + (plane > 0 && img->y_chroma_shift > 0) ? (img->d_h + 1) >> img->y_chroma_shift : img->d_h; - int width = (plane > 0 && img->x_chroma_shift > 0) - ? (img->d_w + 1) >> img->x_chroma_shift - : img->d_w; + int width = + (plane > 0 && img->x_chroma_shift > 0) ? (img->d_w + 1) >> img->x_chroma_shift : img->d_w; // Fixing NV12 chroma width if it is odd if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) @@ -16,9 +14,9 @@ Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { return (Dimensions){width, height}; } -void convert_between_image_and_raw_frame(vpx_image_t *img, - UnifexPayload *raw_frame, - ConversionType conversion_type) { +void convert_between_image_and_raw_frame( + vpx_image_t *img, UnifexPayload *raw_frame, ConversionType conversion_type +) { const int bytes_per_pixel = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1; // Assuming that for nv12 we write all chroma data at once diff --git a/c_src/membrane_vpx_plugin/vpx_common.h b/c_src/membrane_vpx_plugin/vpx_common.h index 72bc5f6..b2e861d 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.h +++ b/c_src/membrane_vpx_plugin/vpx_common.h @@ -8,15 +8,12 @@ typedef struct Dimensions { unsigned int height; } Dimensions; -typedef enum ConversionType { - IMAGE_TO_RAW_FRAME, - RAW_FRAME_TO_IMAGE -} ConversionType; +typedef enum ConversionType { IMAGE_TO_RAW_FRAME, RAW_FRAME_TO_IMAGE } ConversionType; Dimensions get_plane_dimensions(const vpx_image_t *img, int plane); void free_payloads(UnifexPayload **payloads, unsigned int payloads_cnt); -void convert_between_image_and_raw_frame(vpx_image_t *img, - UnifexPayload *raw_frame, - ConversionType conversion_type); \ No newline at end of file +void convert_between_image_and_raw_frame( + vpx_image_t *img, UnifexPayload *raw_frame, ConversionType conversion_type +); \ No newline at end of file diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 3646c7d..f4be7a9 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -9,9 +9,13 @@ void handle_destroy_state(UnifexEnv *env, State *state) { vpx_codec_destroy(&state->codec_context); } -UNIFEX_TERM error(UnifexEnv *env, const char *reason, int codec_initialized, - UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), - State *state) { +UNIFEX_TERM error( + UnifexEnv *env, + const char *reason, + int codec_initialized, + UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), + State *state +) { if (codec_initialized) { const char *detail = vpx_codec_error_detail(&state->codec_context); fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(&state->codec_context)); @@ -41,9 +45,14 @@ vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { } } -UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, - unsigned int height, PixelFormat pixel_format, - unsigned int encoding_deadline) { +UNIFEX_TERM create( + UnifexEnv *env, + Codec codec, + unsigned int width, + unsigned int height, + PixelFormat pixel_format, + unsigned int encoding_deadline +) { UNIFEX_TERM result; State *state = unifex_alloc_state(env); vpx_codec_enc_cfg_t config; @@ -60,8 +69,7 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, // return error(env, "Failed to get default codec config", state); if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { - return error(env, "Failed to get default codec config", 0, - create_result_error, state); + return error(env, "Failed to get default codec config", 0, create_result_error, state); } config.g_h = height; @@ -70,15 +78,11 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec, unsigned int width, config.g_timebase.den = 1000000000; // 1e9 config.g_error_resilient = 1; - if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, - 0)) { - return error(env, "Failed to initialize encoder", 0, create_result_error, - state); + if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, 0)) { + return error(env, "Failed to initialize encoder", 0, create_result_error, state); } - if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, - height, 1)) { - return error(env, "Failed to allocate image", 1, create_result_error, - state); + if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, height, 1)) { + return error(env, "Failed to allocate image", 1, create_result_error, state); } result = create_result_ok(env, state); unifex_release_state(env, state); @@ -89,55 +93,48 @@ void get_image_from_raw_frame(vpx_image_t *img, UnifexPayload *raw_frame) { convert_between_image_and_raw_frame(img, raw_frame, RAW_FRAME_TO_IMAGE); } -void alloc_output_frame(UnifexEnv *env, const vpx_codec_cx_pkt_t *packet, - UnifexPayload **output_frame) { +void alloc_output_frame( + UnifexEnv *env, const vpx_codec_cx_pkt_t *packet, UnifexPayload **output_frame +) { *output_frame = unifex_alloc(sizeof(UnifexPayload)); - unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, - *output_frame); + unifex_payload_alloc(env, UNIFEX_PAYLOAD_BINARY, packet->data.frame.sz, *output_frame); } -UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, - State *state) { +UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State *state) { vpx_codec_iter_t iter = NULL; int flushing = (img == NULL), got_packets = 0; const vpx_codec_cx_pkt_t *packet = NULL; unsigned int frames_cnt = 0, max_frames = 2; - UnifexPayload **encoded_frames = - unifex_alloc(max_frames * sizeof(*encoded_frames)); + UnifexPayload **encoded_frames = unifex_alloc(max_frames * sizeof(*encoded_frames)); vpx_codec_pts_t *encoded_frames_timestamps = unifex_alloc(max_frames * sizeof(*encoded_frames_timestamps)); do { - if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, - state->encoding_deadline) != VPX_CODEC_OK) { + if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, state->encoding_deadline) != + VPX_CODEC_OK) { if (flushing) { - return error(env, "Encoding frame failed", 1, flush_result_error, - state); + return error(env, "Encoding frame failed", 1, flush_result_error, state); } else { - return error(env, "Encoding frame failed", 1, encode_frame_result_error, - state); + return error(env, "Encoding frame failed", 1, encode_frame_result_error, state); } } got_packets = 0; - while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != - NULL) { + while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != NULL) { got_packets = 1; if (packet->kind != VPX_CODEC_CX_FRAME_PKT) continue; if (frames_cnt >= max_frames) { max_frames *= 2; - encoded_frames = unifex_realloc(encoded_frames, - max_frames * sizeof(*encoded_frames)); + encoded_frames = unifex_realloc(encoded_frames, max_frames * sizeof(*encoded_frames)); - encoded_frames_timestamps = - unifex_realloc(encoded_frames_timestamps, - max_frames * sizeof(*encoded_frames_timestamps)); + encoded_frames_timestamps = unifex_realloc( + encoded_frames_timestamps, max_frames * sizeof(*encoded_frames_timestamps) + ); } alloc_output_frame(env, packet, &encoded_frames[frames_cnt]); - memcpy(encoded_frames[frames_cnt]->data, packet->data.frame.buf, - packet->data.frame.sz); + memcpy(encoded_frames[frames_cnt]->data, packet->data.frame.buf, packet->data.frame.sz); encoded_frames_timestamps[frames_cnt] = packet->data.frame.pts; frames_cnt++; } @@ -145,23 +142,22 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, UNIFEX_TERM result; if (flushing) { - result = flush_result_ok(env, encoded_frames, frames_cnt, - encoded_frames_timestamps, frames_cnt); + result = + flush_result_ok(env, encoded_frames, frames_cnt, encoded_frames_timestamps, frames_cnt); } else { - result = encode_frame_result_ok(env, encoded_frames, frames_cnt, - encoded_frames_timestamps, frames_cnt); + result = encode_frame_result_ok( + env, encoded_frames, frames_cnt, encoded_frames_timestamps, frames_cnt + ); } free_payloads(encoded_frames, frames_cnt); return result; } -UNIFEX_TERM encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, - vpx_codec_pts_t pts, State *state) { +UNIFEX_TERM +encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, vpx_codec_pts_t pts, State *state) { get_image_from_raw_frame(&state->img, raw_frame); return encode(env, &state->img, pts, state); } -UNIFEX_TERM flush(UnifexEnv *env, State *state) { - return encode(env, NULL, 0, state); -} \ No newline at end of file +UNIFEX_TERM flush(UnifexEnv *env, State *state) { return encode(env, NULL, 0, state); } \ No newline at end of file From 1d43fe7bde70ffd0732e6d5391b186810ce269bc Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 13:42:14 +0200 Subject: [PATCH 09/24] Make tests pass --- c_src/membrane_vpx_plugin/vpx_decoder.c | 2 +- c_src/membrane_vpx_plugin/vpx_encoder.c | 16 ++++++++-------- mix.lock | 2 +- test/fixtures/input_vp8.ivf | Bin 0 -> 81742 bytes test/fixtures/input_vp9.ivf | Bin 0 -> 16614 bytes test/fixtures/ref_vp8.ivf | Bin 81742 -> 32634 bytes test/fixtures/ref_vp9.ivf | Bin 16614 -> 22218 bytes test/membrane_vpx_plugin/vpx_decoder_test.exs | 4 ++-- test/membrane_vpx_plugin/vpx_encoder_test.exs | 18 ++++++++---------- 9 files changed, 20 insertions(+), 22 deletions(-) create mode 100644 test/fixtures/input_vp8.ivf create mode 100644 test/fixtures/input_vp9.ivf diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index d814ba3..513aa47 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -75,7 +75,7 @@ UNIFEX_TERM decode_frame(UnifexEnv *env, UnifexPayload *frame, State *state) { vpx_codec_iter_t iter = NULL; vpx_image_t *img = NULL; PixelFormat pixel_format = PIXEL_FORMAT_I420; - unsigned int frames_cnt = 0, allocated_frames = 2; + unsigned int frames_cnt = 0, allocated_frames = 1; UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(*output_frames)); if (vpx_codec_decode(&state->codec_context, frame->data, frame->size, NULL, 0)) { diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index f4be7a9..11cb3b3 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -67,7 +67,6 @@ UNIFEX_TERM create( } state->encoding_deadline = encoding_deadline; - // return error(env, "Failed to get default codec config", state); if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { return error(env, "Failed to get default codec config", 0, create_result_error, state); } @@ -104,10 +103,11 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State vpx_codec_iter_t iter = NULL; int flushing = (img == NULL), got_packets = 0; const vpx_codec_cx_pkt_t *packet = NULL; - unsigned int frames_cnt = 0, max_frames = 2; - UnifexPayload **encoded_frames = unifex_alloc(max_frames * sizeof(*encoded_frames)); + + unsigned int frames_cnt = 0, allocated_frames = 1; + UnifexPayload **encoded_frames = unifex_alloc(allocated_frames * sizeof(*encoded_frames)); vpx_codec_pts_t *encoded_frames_timestamps = - unifex_alloc(max_frames * sizeof(*encoded_frames_timestamps)); + unifex_alloc(allocated_frames * sizeof(*encoded_frames_timestamps)); do { if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, state->encoding_deadline) != @@ -125,12 +125,12 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State if (packet->kind != VPX_CODEC_CX_FRAME_PKT) continue; - if (frames_cnt >= max_frames) { - max_frames *= 2; - encoded_frames = unifex_realloc(encoded_frames, max_frames * sizeof(*encoded_frames)); + if (frames_cnt >= allocated_frames) { + allocated_frames *= 2; + encoded_frames = unifex_realloc(encoded_frames, allocated_frames * sizeof(*encoded_frames)); encoded_frames_timestamps = unifex_realloc( - encoded_frames_timestamps, max_frames * sizeof(*encoded_frames_timestamps) + encoded_frames_timestamps, allocated_frames * sizeof(*encoded_frames_timestamps) ); } alloc_output_frame(env, packet, &encoded_frames[frames_cnt]); diff --git a/mix.lock b/mix.lock index b132b3c..54e90a6 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,7 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "membrane_core": {:hex, :membrane_core, "1.1.0", "c3bbaa5af7c26a7c3748e573efe343c2104801e3463b9e491a607e82860334a4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3209d7f7e86d736cb7caffbba16b075c571cebb9439ab939ed6119c50fb59a5"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.0", "e855a848e84eaed537b41fd4436712038fc5518059eadc8609c83cd2d819653a", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "9c3653ca9f13bb409b36257d6094798d4625c739ab7a4035c12308622eb16e0b"}, - "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "7f261b0328980eb851f8c33ff83fa149a573991d", [branch: "fix-plugin"]}, + "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "e439515ffc0e72b46a28f8f3b09fc3f869605553", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.0", "99fdf3a5cd5f64118e550b80d79ffed0d4be7becb3878a657f8726550c8c756f", [:mix], [], "hexpm", "1768c8137e4cfc6470117f71318a1dd9726c3b305a78bd56d90f796be716bd99"}, "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, diff --git a/test/fixtures/input_vp8.ivf b/test/fixtures/input_vp8.ivf new file mode 100644 index 0000000000000000000000000000000000000000..326238a62bf25130c5c8f4b42107f8a20e1135fb GIT binary patch literal 81742 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/ref_vp8.ivf b/test/fixtures/ref_vp8.ivf index 326238a62bf25130c5c8f4b42107f8a20e1135fb..4b2e416300c1de393ee08de21e3c1f77381e82d6 100644 GIT binary patch literal 32634 zcmb@sWmsLyvMxMvcbDMq9yC~R3j_}^ad$#+clY2P+#$HTySux)Ge5HSTIcM2?|JUM zKfd`4tFEe^qpRPl>K@&nWu(Lb01|-e7j71A$O&*F0002?e*U@t`Dro#>;C5&tp=73 zM)yaGM-BiWq(Ed~_{=~dgo?Bt6=h!jnH%Z+xz}ds&3oVb>G~^LU_0}Rz==zwJHdnC z8E9Rw_O040=rLh;o%n@useJ}`rj4=}w2+zA#`$3PM!dfMa(*pXNGt;S1X{d@euaNy zfAV=nc#hh)iLf~Z-U-Nql-|jo|tx5C}(5%d5}Jd%)X3*+4n;l0+$LZ_51mR`4|c?DFb&4znDz3GDQk z^#t=Gcq4r|zhZvwe^YqLRAmiaL0NExcf-40KVBd9D)O>_Tfd^e1WgN~zHU85y%C%^ zoWMMIU1uJFrrK58bG?q=YplPs`2b4rYQ6(|!+LRhCcbB$XFe4KeUcN@eJgqzd@TUk zbPM*kCwSGpIlP6vf^MTgAn*2W!ALLNx5PJ-SC~8IH_&n0l!v%y{xj5DjF;@o@GA%e zBAOaq`5_9)Q;ojuL<>%cuVssQ>KM5aDuW>uO)3nWhz(jS;YUBcO|^HXH80Wd(~GyQ zYn-qe&Z?3Ut|wly8DKmUcczoG-cbFO_`~waJ1y<`a3Wh+1QOrG_t)GRA7O2U9xIwu z{p`;(J0YI5A3G+#iNqq_sh7_+KAK#f!fp*It_V!~J2=fD1zA;Te(tK!R#i5E(6qJz ztpnrO5~pz9uZ_OUO{~Pw0*czZrcF!H)IU{`@_x&gldpdRX4J?5Q?;&8rDe?+}@BLTyU@5Zv@a!mF3025La6orI{>nl{=nod@o`6jd2Hs`<0nq#h!B0^Q<{BM@8KKr{XaP08rSH?GF6jcAnwz&FN z4HO}`Whi@B$ztGqXxx=V!0cqTTh0(2AD|A#f^QKpZw?-ZVnXqRnJn=8cccH)3)v#R zeRd|`^?Z>l;TKA>zIwkDhKz-60*T-CmJG2U^qw2SU-RD!-n%^L(!8)F?Rf|LuCY>i zTbVu$Wa4%n?K7JJAD2sO8~j0IqlmLPa^fPpIq;^gm;x5fm1lG;=TpEv1 zops0ctM33<_3`!gD#dG1-`-1-kwEt=@mbJaS;`TYdNSSBV+3es% zKsp?GnQ%0!dR`Ysg+t(dk>fiEb##FqyqGkMMte~A)9>w>6US0GOV-5}R}Xa-^L4=B zox>XQxPqHvj%#Y17CA_gsH=iX8$DaLebsUOCR_n)p0tv%c z$liA_!$pa(Nxr$&Kh$LCMxtH!)46|%-Fo+WzxBOZgq5^+7yk!s|K;=8Su30;-YMFC zYc7vat$ltNHF^)f7|&dkzZ5s8=tV^4`nkqF+TC+f zZ?!O|r5`o(@}>Qm7#06VHOvO{V*Gl;9;x}1HIyB0O$O%jh0(_|o?y-2sXkib3@40V zs^{|q@5}C>oxC3LpE#e{Qtt`J5Kx^8+X&>XvCF77)X>8C5R}8YULDV~K`|A%z#1B& zS=zF36@CyY#zWrU0;+Mg^ufL9&JrBfDQ@VKm{hKGzkgU{>t~DpEJbKC^2=#M_*Mg{ z={0VHEW#(vTv>gHLMiyVSp3dqTQ&J)GF^1g zB+*PnEMHKgoIL+QVMLtf65z);=dky#I;#5mO~Rmy|MuJMzv=x|>%UmthckEP^+!v5 ze^sE;t~$AJ@#H9w@+(6ww`%j|$c^*=S<^ZCz}k12?4*3#P=M4}#914^!8~awUsVy= z8ZMh0LR>cr{qh<$nm`}^T=A!W)zoD<*r_(imJ#;r@S11>k-7hPZ%NNGJ6+& zYjHTn>%j&h>W9Alve>-3Rnt(BNetgRQzD`Zuy2xDXlnIL`QPQX=8$JgVcAE|iY$N` zFslZ)bdT8#-%D+A-;Yj%e1s1y>L)Pv>{VdY)i6Vd2NXJ-s~=!g(wwS_TqclglHyBHsGZ-rGjmome;{>k13&`xxO&{09m3r>kkg zFYaGI#wMx%oyq@B?G~TFT%Y)~=+7K3s+`c$Z2@o3!aJBq{%Z4)72O(SCaP)O4?(lz zdq=7CW#Pcxjr3|(l8p^PR$I!`RV^*%&6-?QF5w4)eiQS1#G|6Uor$c7s@4ZrF~8fy zb)jAD5+>fqutqzA>^C0d!MN3|UsBVb>?+=n#L5JmCoT4~?+5b-L4%}seDz6ZYhO*i z%&9G$QGM&&$B8iYBw{D)5vZvV9k%unJ(@si3HN_C23|2;U5>U&U6T;@QK`Qiqbo z$^h{&5(rxPMhA~?p&K>`TBl4iSAamu*cfb5+S;(N%o`>6$2?xF=`GV1lEa~IklKLR zZytt%fS!tnz2CeRFEIIdLlInLq}@fn)rEFUrWXN2i*5y;m7Au~zEbUw51|&3dkG@YO1w_X zGl^#m7M_Y^0dtW5CYde*{&BwZ$~A$)-oz>=M)gU9?_T!1@O}@b+>Ln~8QxJxHz-@pwSl^06;ePH|O`h5h0Cg@&#~i^3*6^z4T_aZYT zEQ#kEMoW`FVXQ=E)#=p!y48ff9pLfe z;SlZbNWJZ%Dj?s_is<*NR*GpmP-!Iql9v=i-c!yJi4e|6t8+9&==;7sM8N>6Dxkd zYQjsRw`3ws*oBX$JU^E=W=6-Ll17IR0mYvt(0hiZkNRq4_uzoFufd6Ed*4 zs7Qf}aK;_Ni#lAZM~jMOAc>+nsxZaR@dXgbSZ-pNxI^3ZkRiIc%h4+SnJPTxZd2Cu zXdIr(Uvw{g`W+toEEavwu&(3;3YF_-!@rPEeb>4@q6gehF;Vh~-Wp$YTZS`#ir<%M zd2(t9+`3%^3&a_F$`N#({NgD#m{i6Ie6*M(oV{;!TxG>n1kY(W!m~ zWxfrL`Tck677>=t3U5!D)O_)@CDDg!h$W8pz-9%xkd3$T$xwyxuZKg!gm@Vn76;1R z@X{)h^sKk%OwwCNbiRoG$05)B(1VOWcZq|B0;HPsoEBM}5!>pPac4!}Hu#wk z(!o_c_Mn>dsf-&h0Tw!J=axQ)CJ|CRfv>* zx@G+mEYVM$ffi6m%qDdEyB<}8>S9J*FbvXEmV64hO+Nwk)F;IVFN4#VGVid-?+Uq1 zxa}b5l3OhWd#fuOu3k5eM~OHfZ5DP}x{|*khXfRuw&wkaJW*l;hrNI=G1X|6r)GYZ zht-6QPH3FYfi6OCa9&gAPxLjKEe8J6z*w(CLhkt`MA3X!u6q^Q)&$48>ND2F$2 zXfyAM!Ir@?<`t4e`ySf?Lrm61N*WzOk5jdUI8TqfEH$11MUPc7TE2tJv~{J zoYvY7W45Y4+Q5Yrbo`eu@sr{+4_zjD0>rUGZQ1tJJSk>)w)kZd!+t<1a`o_EfE~v7 zz1Rcw$m=&EAi#C)882kj!H42T-H@pZ5PtVQac?qDSP&)4$efru{4Bv{f%ECQk2uRm zip^}qhiL`Vd$ubq<*XLu9BmbD-cDG%3R?L&N8)s+EjFbb(S+@BqjUrWpKAL4dC&`H zu1CPDTlOI8fwu&_ z5XBN_d+M9gpdus*I~&ZrnJUL&spS9zpLVq`5m`dK81m@?BJPA$;>W9)K#|G2*0i7i zE2J#Fl1t%gwT7IW4?Y$|J(^`W!+py4Pg5)Kza&!n7~kdUUP!MzcIaoK+1&(?93zk& zTVg7A3g>BO`nlQon^do#r$r4#$Mk08n+d{fNw!qMZ|Xr;Zd%00r-<%3q&z#-BFsuJ zwV8C-wX~XXiX|_ztQ@37n0>LyCi_f{Cbhv4h|~|QTBl#+0!E^+SB%}paK3hO3PX3k zDAj~Fv@M>35cyYgRfIkfD_-)K+$E$#biD-5E9*)(lM)GD=`Ty3yq>~JvJS7cK_?v3 z^&&J7kG+Nqb8oFop%h!$>dKap4}QG~mBDSkxW#yctw5A{NkFyGFP3z9<-S=SsF{l_ zCr5;7ek`$(nM3_1D>WH%btr z6~Fb}^a(d}x?^j^R0LA^+E4Pc%J zE`ISdI1Me3tkSZ@sW{343wX;h!iD$s1`<7PK<;)|^P(!QSpCc}ytP9!{`e;MwsGOp zD?>X9K|t$4BUcM5M%6cBJmC)fCBjfp|C`6P33uzGF6U3X!j{9Z)9cTCWe2KFqSG`5 zIM83Pwb#FA{&-E@H)GEMw9De1i$zxZ^P}C|JCquQ;zK?|)}-uDGJBjbXh4ySorqqC zmoWEnB*uR1E z2`)J9yIpJqlCPX_pPX@*;9?NBlV7F_1xyhd8*1T9s(pp=Yq3>?9{!;Muv#Q(Qu9k1 zUkwGX2l|ZovHrvlQdKUFDIL<&PMp7n6(K!C8l@8uk5ZUA~#-z`a))VdEP6{5e@RJkHMt;%?1vY=I?xa@moZR@V z#vPANmCiE6au%rf(vHq*c$r(r(VVjcU2zYL+W!)^m-YY{XFwzF7Rn-JbRDo~tK7Cf zpw%yc89a!0EFW);Yjl?tvAhh|cVV^GnYe?O>a|{C1z#rnLBc@`(xWvCPIBckEV_U- z;Bsw#rjc`2I7+@O%PAJUa(lh|QE(~Ah<3Y-ycO(FLszW8!w2&`=)k}-Dc(4L^EW`7 z!kc=^N0IV-#v<>zYxxQ~K|(R&?K4J}F?Hq-f?D`dGY!+epV(_p{LLY(VgLw%@0j|1 zZssUCD>vMO&dxR`>5R8wei$xiqFlv|S3q=8KeVDo$JcS0bq2RBflouT&mKgdR_cR~ zBQ7gaDrB5v3|$G&f*|d`2&^u124LuVccvY|+S0*y2LGDv2dNoIF44@EIysU*jx0BL za@t2E2M#$Srd-2t2}wC%UeVWwf(a$ zM@251V6Ckjw;NQng)ae8v>H5}QNmrSAaP0+LQT=cZ9n2ENGjLAiqD)*CYgn}(Puu5q0Rii{R-C8r z>t}0Ko$<%CxXa-&wUi^E(9C%0_V!!dsJCzXiP(WWa*X_l*+KUE%|8gni$ zSeTU$Mj%S!9_y@hb|86n-FKd0FOUkO$}(fI%f=IVy}sO=XF za3N&e{ya308cY@-$1?P;^8jC_-$d*~*~%e|C}e@ZJa{f`;SPC^dBSxP<96-JE&q-BUzkG@Q zWU38Q?JKxA%Ggj_e;p$2I{eA67+E2t%w14C_>6&NE>z;HGzFsAETltuQPlX-iJEx0 zRa%1y29e$B2kBQ5LvNOPtK!Q`44)FM=q}#5S+_1eW!lNzA9hU`$Ek1@eOOc+oWBt} zdvd-pl%JsEZha>C$+EYY9jyYnMG!4hs8J3bl8d1U#{=xZiJEMSdz)Pc!9VUYMeRys zwAzV+wRBgrK+HokE6R>oHUitCN-8hk#J43lqKqsH-87A9VXY|kUs>SIqbH=14OhDVvaQ{vj;ktY&sUqGEg;=#GX5y1PNW)=S}mE4Jqu{0LR z)-8McDM`_W`SUf6yttgwCmuMhhdWn|RtLX$s|BraAxcwZsh1dWM9Q6^AU5zDh#NQ! zOQ)jjy)YNH{_Qso6yw3_$%MyGY=ZYD+dN>xvR({H=?IhL*i_fV z<}K0Ws84TK0|fqtj_d~2$E?$8vO%iX2J0UU^;A*eR4@BWUlLjRBQpkI@Nd1xLp)-t zKKX4T04OD#g8TSdPc7)lI95M`x1|d;Fa~T}8hL(n=m_)JwyO2(FAbyfV*Wmvbg~lN zTIClLf=OU#7JwiWj0}wr!N}Cbx!QEdcDg(^IhwUdF^94pt!D+n;z$=vX}DF+$@oan ze|*hABDgJd=ex{^UAICoqwuDw*fZo&s499(Wcx&+gI&1Xhx@}z+^>H&b>(Vxoug(0phHS6=d`-QbsW=hHnxj@fzmh?w zlK>T1cVG_<1 zTe5A9c0j`umnvpdqBJ@F+LaTs<61hthVjL_K!UnmF*H9f5}HJL28qErC-*uY8TxbW z4F`*9ldvY^^|N)1FWQG>8%F{~X2(L_CMKRcbUZM^0A^fQP9FUhj%xU%tuKE4r1c-I z)SBS7;HwbT6C(VJryy#b_x*3Y+2TLq^DUTk$uUJgnZl*5w%A!}n-y-QKB209b_T(b zRt^s*e6pA-MrgsL)zu_<4T*WkVg9wmYhRJhD#|Jtr?)VOHk#9c;Ven79UF{3{i;TU zeo~ju_h_2|%XMTQwJ8Zd$=TzKSZJ47@>!)_^9aZaXEG>wN<}7ysmP`GQVI@68D;2H z9t=66C^56_#uP7#{=|JiWd*Wa261iH5{HVsBqk5$(@p3PQ+&6S%r;DZ4=;b zQ9R$b(+FFMl)UF-N{3&>nxM_Hub21ePD#>{jdh_QXXj~F>pKr6|zFOvLNo3yo1?oFUu;_s$WsxFkIW(kcu^r zJKxT`nZ9$i93IiRJ+5Bv zd~?FP`uMD%quh?4V#X=g*GK~ydETtWt{E!>Kd3_;7|{x9L95=*^=Bdaz-0-)S&S*- zoC>h-IZ+FNcB!d-fvp>uBh){XX#-)d65B!!2od=_D(PP7VuX?I>V>^#oM_cSYh+Q( znE{${wHserzX{BWdu+EJgVrz!p|Mz}`t%^ z>ueaS6t3SVp9xN0TA zih`xKK2*SSGWm3aoo@{qm;wS1m~)@wy%yF$Cor8~5jd2HhWS%Ur@GVVZ1a1tS%24D zENSciRA-QW#dZ5&blT-!G)Hej<1dm=e$H;7ZMshB#QdsGy%6^mx2-+)U@tihA_UH) zz4W8Kxl=PevY zJNTk7YAOHj?@ww%`_&R`RbpEFu-dYK%mT4MqD5jQv{)bF>B_~&Ctc* z7xZ4jXujt2nWP;yQAstxGUtS*23CBMABOx%qOC&^?31+q*nj*SX@cs zT`PbqXZ>9X({i1h1?LS5V;F)d8E?Dhk_%qfE-(-_^qT@G<+&&>-KzaVQ4B!G?`OXZbg_0!O~*%Ht()@`8J zL?EMwOj(X3!4Ce^2>On+PKwfdGO`s%UvnICvqMJlLrj^M3XDI|@!6g{oY| zSsy0(EK7$r9%y`ab%0@8DJiAp_6Pw+o!+OX5ePe$qc!ZIs(D5R7I}CSqdic5{6_{9 zNsW)2Hr9$>&vgbN)ZAPFTz&B7ZPPzTbZq$>xl{!9rH7bRD9PfinG%b~G?G_(Ns19xaiBk*i*@OqYSQI9PthY-{<@Pg&ohx67 zs97W0jEAiV78z&k4F)J%k?2`;9&R^GAU zx?VMESOb5JnbJVkZtmK&$dCqViKeP4r#FjL$%H1|UHzMeo5q)!fg;q-i7N1$Cy&2Nvt$ThbPOR3Tl(#Z20DjfZJIWlAA^RI#Gc zklsUu4`QXUlM^{=Yh8-KZZijBRTv`cOHm$Z$>|*JDA$tWlDRoMsf;h! zQ|JZqG2G(cxH-$B_8x{i)9snlJL5{!TtDPG#ajMNcI?$nb5@Hf|&T_8Og{``SnEa!iwxVm;vd;W8Yy|VRB^<;zefii`JH(3d;=TjlU6}?1&3; z&!E&S>>&z{xk}2kX(e-Tm#;bF1qIhf!zz;KB4R?>PVEorgnJrfYw~`G_Jch76fy3k zlg)y(uqWM!!Sai(egZC^RxAR=(9XgsOEqxVfOaIT>vs~QMT{MFnG}51 z-8+KaO0Ls~DSj_eur#0dXQDwcdGJQE%z8oi`7W6kFwHjSiCIzZ7Q2i0`+%q<(2m?A z5K_7MTH7&7$Mr`(xS1j2j+oTriosKdGl7nXg!rjvN@md_ciP%ch0ajt`G~*6-IvZV z?bM>$`brl^qKQ}Xk54}g?{?S&VLWGro1i9H#oV=EGOtyWl0AVn@gP+7gV^Ghd;bW^ zt1*+-l4L9ut3;NF`=}(d$qc%9E3cOIZZ(p=yl|PS-jr)oqAr^6YG*IHVAwIN1%t4r zB#G2Kz+dV<0W2rnP_Qge4Su$QY%U@X3GEpz=a+GoYV!G^&35m=qm4iX=$?m8hBMOk! zL=q40s4~JJi@;WjM>d7+fxzU4a2X32!ZyLEaaZ`CC*?_^<>nDB7RPhd_z|Fp2XT5z17Tmu`Jm|vx&gfj=hj>rty zGlu&*{#;K@{QN>e2;2!c60Qxv)U*UgM*{cV;3A<|!@zOUa0hF3)xJ`P*Z@xK1*^vj zXKMHB_ElwfF=Bs5jLFb=pxpk*>C7BA><)R-@VzMlM zq_wYte~+5f?4wg;1@L8jT$8#Im0bX%b!7sk?EpI6fWNF-%wv62PPyuctLhG_l6xmG z0KB6a;FrmlQ(_QYao3caPNRl7UR@J=za;Hme4X2m8pjdF>JP~kk6Pv*_9@G$|e zT5kOeW>g9oL2WgQa5h&IKg8ZE>;!8?Ny9Ah08wJ^6YAP?#FP2lm|6b`YqWTKizpX6 zbdc?N&@5XNd4R^R13W#})*ntIMgVg#FhwO-KJNQd{)(lM{u(?UrfErV-6tCWfqFBp!;&~rGe#CJwF5Bm`{~Lkhw-A&nA`ASD#CA*0(f49M zK}F-;6b^C}G(y91ow85RmRPqxu(~CX(ps}B&Mv#OlCE7R4>_+1?$g3O<<`UGA%-Vy zP+)`lHMflzS$(O+A)d{G?POIt?z!CdWmfl(CIllDnSQ*jwTb3?`meAZby+c2t;nfm-pkp0QOwiC6#U0(BHoa;QoQvPmmrGf~z~ z!MNZf$luziaeAVjc!E1NSyfnyX(KHIdvn;|T{wELXlY&| ztX|N1V1a66lnn?a^O@rg)Vis&BB8>AX^Ws~H$;JoU_VT_O;@ID&XBR($xuwn{m zo>H=xZi3$ypx25sAAhnsWaCLzo=JFULPCurfHQr*?fc!UQ&VYBL5`T#Wf2=Q6N4C| zz<2yZnK$AawM*ChdYVWNZBsuU1RavrfOk#S7Gf`V2pIJzhPKCG#4ole9Fo(mO_9b` z3KZdw7U^}dA#Yq9amO|-!x`bX$|3mPh-muA|YHL-Pn9RtZ&P@c20 zg8o@bX5&Tc?){_C^;%q5LetIOcFu?0nRVNPX!ebS{-9Qk5jvQp^kR?>Vv*Nj8@j)v zbMi7PKD!<@RXWs>2l2W@h|L7E?JG5*Gn!RgV3;a5nceRF5Rkp0P7!vtNZ!v}{Z(W- zuci0~XK~N$MVWxnM#IE{eAK046>87_Q)tD^i%u`4j=~=C%?lNt_Sx^2EQ6*bG=H}_ zHf*`lP=;~qBZz=R#H%k@-)<0-Tq;)DYHu>^>f z1>8>1i$VTa@FgFD8L|2>gL>z<&Ld1OL8GiCe6Oq?>y5p6t+Q+IRotb%le|d1_`b*- zUKyDScC5?|N1H9{9brIU_nRsQ7pB+@jct@ca-k{FHL@q@r>|JU2t0X_X71wo_il+Y zO+p%(RmQtl660`IyF;6l-=w_sYzgMK;2p^6MUDv7=r}Hlti==|1fbpZ_Z9MpFL!T^ z@$^eg=P8{to7T!G2$U}Oa>VZQPV!I71yJm*^erf!bl#&F7-?yxF}+1E8q^OAF8bX7 z^!vjO{ab6BxrPS!3(#82OIcDzvP@D6ivGy;c1c2C2J@+1m^lDT%LcmXuIbC8iCpbb z$o_EgU{?47 z-0&Xa1cq0ss4pAhSAD7ve!=gXez?*Rg<9WmkQMMGu!97guFTAgl9McIxpqRNr&6rh zqKi>Qte;_U(xTc;qxmvIr|V*G9sD+PMqh+0+K~E-pPBla0usme^@oWZEJ4XS^B2rs?{d(riF)l7b*?VFgBU_BsaJefh`=G_ev;toCC_6b1CfmFBwZXfIa!zF39BLnh+ z+J_%*Ljl|w<9{Sp%Nzd1Fate;-uI1Xg4RK|pcl{^2s92l2R(t_yg*){eb7DV)e9sD zY6o3|o zAD=n{n;{D^@B7~MSK#;rb|T%a&7Lf?hqTqfCre4{~!d8 z;<_^kFZP4@c{az9ogMyXqQd)s(QALg>6Z%QiG4U^HDLpOpkmmQ+5Xj=Ht7C6sV12@ zG7j5;@^VUl%&?7DR%0ab|1Jsye_z#u6(PeA`b-M@ew6C^zLTV|S+=CG{_lt6hVY-_ z&myLOk)&ARsDBA!B14j*`jeuD5W@Z?7%T_&hx8|_3zG5=iJdrPiDA?HK!r{v*)ip4 zYm82sRERrF8J%2dTwV!`+B^>-AEoGrx?~jjK~z&JboXv#h`;R|^BJ&9L)_ub{I{Lg zJpg>_hMQ0ajSSsy{*U#~J-v=m+S~=dAq~?;&f1&)BA!E=WJd;Vf`g=zlNm2j582r%+sHmp85`F3i2fQG`S}q1 zJvljc9Su2-9pj2_vu2CWow7u}o-o6XMyb164J)h!5e`Ae^^h??&__f!dFY%wgyRx&Q=PBclq2 zMy3LkkFV8YJ$@7qC=7Tua^-4Vy>xdeu)0sS-f1i&PBMie+*ifr-l?=x=NY2W#BaPB zEDLsSkgTD|F-y!Sp^~>uv5Efvnp?ye{-4=QgM;Zmq9J;R*jn2$imD0=Fg)N{!`i7` zNF8$SQWdkUAA`->FAcaN+f)S;%U^xpP;46h%?oPx`=xr&JTkt3$bYAYW$V_X+;S@u z7|1^ysS<~D!<(Uq?3;Pc%nf+S49Y`2pnbc#aL4{dp#PDtUha_%l?#l9lDYQ!&gSWyz z9$-a6hD9B%@8Qi&Z3nuLwu+(XTL-;|I?(G=pj_+exs99K&1=9lyy)?fa)?RJ*EgtD zx(w*g!tyjY-@85M?;6<|?8rl~4S-8;^LOpFuN9|W7SI11L1h$4VMjLdKXCp{XJ@3U zCz2%G0DCHa_LlB*%_J=d{47D?-%jbaJc+eeY0^AIA;plAJ68DMc)cM-A6l_t+^+bX zTct1oX{tP2{di!gq7^NN<8gx}?#RvVENHMS*J#Cm1}Mg58X7+{2P0JK7Ui7g9zHO{TY~K9%6p z)jIbx%UAk&UE4gm5`d0zXC)UrhQP=eMn6-@^Tpp=Qlor)pJb2Q3_ab)7320f2@Vi5 z$5$tpAhe*4$gz(5z({7@z^4LGCKc|Wpwe*uvKB^H2(7dQ365Bp42ic9%(rx(w*?hB z2R6Qi*9n{IjV8Hz{$N`sI;;)X=%%wmhSI{3Gtu_z;u*Kn2e%L~Sgb9a`+Cbb$v~KD zQa6!WKx&Uo?NTC5=0Q7vKGtLj`)~~(&Gtjoq_j+r7EgR4iiowD8dCU7n9DDtgsiD| ziTEQvd$enn>DFZ(O-ya|Z@t2@E;`9u}XFi}A1+#YbStLoF-A`{as z)5qYTdv8QnG{nOAFOgwg*>TpE^8opGbVB`&&Pz_fcAP>(u;4%FMB3m}H@4hK{I37i zv*hXP>^d z(bPZ`|8K(J;J=?E5alCu%&*l_T4whuI9*Cc|0YWQewO=Fash()moCc|nh|Wr57^cj zT~`*dQgU!bUrHaGgK$(GSO#-6!}E=>ad8*}NDUE|55CNfxJ2P-7O)-hx*@t^Pvp3$ zpA(P@D`9>N(8rM+>w5Jqpo&5EU*~*9zcRo>h8lv48YrKxdCYMXw}DjMTUyY0LYg7x z|H3ZddM z$1P`QIApc5F7cHPW{5mqf^tUJ>#-sUMS-$mb0F1ER3oh!`wipwEytj!5Fvktj)P!- zLL0MCs8B-xV3tFAi~Rw%PP&;uRDd&lY`dd1ABz;X5G6pw6(>Sj(Xsz1L9T>-@5mb4 z_VbA)G%WmCE_lsAC&0UfYRorZYudd~F@Rb~HuQ%O6aM#xU)RySjrgpE=FEo2-wcka zRzfk9-o-C*p)5!^2*2OnK|Bv$t;)O10hO#We6=Nf1u1l z6i=8%V)Lw2Mk6OB!cdx@c5i_=`RnO@+nHTW z*ausb|Kj~s@<-4Z#+mq!@ZSX%$KD=)tN$Z19)IKib>{!C^Val*|2xD!U)60+n`}r_ zi`d45}1t4a|fXR%E2L8?MnPYDh@`x!+=3SXk566fu0x( z%~ckt8`Ig@Pw7~R1-F3a1!GgSpYemA%TumU#t5oYk>r<$(smc3Cn7zRfR?ZPDD4Ry zo?!-Nro%mwgT|ca%^za zQW`?n4w8ae-3_k;QUhH`EUGO1cp37R#q{{IK z25?^|H2RDPA0Ug;m7OGfFx}+2J%LP8V$*2l*?UJFmV@w09 zEz-@SbeM=2WiZ}utPP85o(7+2Pb55dh$sd^ObGg_J_? z$=v@DpR@ga?s_Wlu%|Tl3uWT;ZG%5OTM`fx&D7(#?}Z6x8c0knZ-ehpccM&B74t%B z+;vg%zBy#$cEW(HL&2Qf~`ypm?O-%vFj=@h$*1L1TYTdMf8)H?6A+}=J4I4Il5TafTLlHN7 zStIg#s&c&-Vz6K8P6X3Ow1XqU8Rd{YM9=E~)UcL7zHhVsjmphMJ(@^%If z*C+*7VFg||-Oh&h`z#3dpJAZr|AsE6OwoarQj)ILZwLX%lJc~^~Qy^Fwv+{WePbwh(JkUdJsnKqvsW^PtJI(EHOuiu4veG5vf6mm$OihEa*ezMRXTZ(Q#2Q|Uf(e<5wwAa8g6hFE(|}QM zOC{UFCRP;MFVC`V=$u2`@~DKk5#`k>uIJ}<&E0N^u=_}ZY2ijZSwav<1~F}_iIyt z$|Q?YfUKWfOVTlSAL*wy#M|0JBf%qvazZ^a#%#KP>1IJOu@ESih~(I za+5=N|9_Qz1yoyG*KL5{7Th7Y6ezAmg1b9~qD4w66!+j#+={zHad$6n1&X_sLXiqy zUV7X6f4%a*@4W#T?0s@LgfZsYd#*L-I)~lzFOz3YZRrvJ( zWKMrwoqx}%HoqhNJr#qk_ojW{dx+E`F~P5HfF^_-k^vk7@DJ|-*mH?VAtd0$@iYtZ zm(M26@v6e6L~NU}=cRAs^LT|Qu28|Kp$Lc6ruSw$n-qa=bKTO`oZxPvQa4}zR1N>5 zKuSc`mDCU|c>{U~A_0?IL~TY2XLw%3>V@_!Zgu7A6*h?S+4c*JS5@0su@B%>ka;94B+>ETd}Ca4jCXlQ zT3#fNh+L)nAub9dW#y=13msm6L@074UTG^lz=;cebckNHop`}Mv{=Il8CT7{8Vo|I z21Vi~lKP*5>gURppFG7LchC%A4G5Dyj*JBLhZEfaOp(Ke5&0!{U#W1Xe0oNr;90x= zCGk2(j7mMVSU(2}2@Y$r8Y@Zz8XR$#BK=BxCvn~qmwK5G-c zNuMJ5%6!B$yhNm-)L)**TG9g>zbn>Fa8UZoD-H?}3PRa7huDQKjf7keaiiZ$EW4{3 zHgvbQW;?8ukz>VXBh9W{_}X;HZuqc0=n)C9q>n!9%vEC`iQSWs$0`ruTVI-wjh*1g zHjg;rEo{9vp)YXUBEX~!?bELWYs10-{Z|;M3;rVvXmDu|knWYeb3Oi8KmXrYa#>l7 zw&uzG$FkCx`*UqAzx!`gU*v3IT}OYRBV$MZwXi;k_y1pG5LT4&0NZOc6F@2c)s zwKe0*63R+V0A5z@lSK%xq2iy26c%5m`B_|qC$_LKU=*OGK5nQyz0J5weVuxPXIB@s zlc5f2W(qF!+Fmqcx|P-5Ahd43Y0}5YwVs5=q_= zwz`9vvaLJ@#I)%!jdu(vl%2O4F$A&f(3y;{vNZhJWlp@2^O(ZK72&WvglC$ZlzTia z>xC7@U%b<%s}45jH6c9CgcxlIZ`HnpHteRI_W0W+Kk1j1#&+b;-BMWj+LJxJ3D~NN zr3~6B_I2`tT9AY0D#Gl$PoCuUGU^iNhf&)=g>6t#^u7VQdB|-)`Jo-Q-ozCKY?g)c zrrn>VYs(X8Ljdi}Y}SHb0*rHb= zR=*lTTc3ZmlsGi`9I+NCzxs|vBc;{;d5To2qG)s>w8MWI>0R#Dre>ExV}E39h8}k) z1~n12t7XxB>C{}I%!fP>8EAg_%sQgSxMs&XhN?x=dbv3MAO+GCEZv;_A2I0j9 zW-2HTL3Rom>%mt*a8M9ADDW&c;3deZRK5k}QDVWjAS!SWC^j$vOQu{#%yz7dVa^TbcY9o#%%F)am0<-j)YKlR*FVK`z^+kXsEbqmdqNzsbs&#hDr{ zO|!Sf6z=jVZGFFU0P=3(tU1>`4h?{QPiIc`sLy5gy;xvlN4T0c*@~Je*_1Jup$sIo ziQND=s%x6+n@+PYa2!j7w^7f6O*RV!Z~%)a#KTQ0v7?(r7`0s*1TBSjxPu@aO&~HF z;k4-p&p!M&KgGl{^A|HfNo%Z5+HH=$9#xw%!4^~yuD`4mFeJr1an4X~1j$zg_0(5) znKnu038$R(^Xa&RAUN%b7YcG^j}GV;F}^xKpc0>m7ktEn3c}vlyp9iljt&TdN9K*Z_gJr=1+AO3^XLhoT|36PL{%n{jItm} zN|WgIxS|)hSohXOy@)5kXd{y$d(eEBg)9|Z$XZU?VguiOB5l$;-5zhi{r!45@>7s4zBnVT6FG@lv z3Xk*^zAfpj4ZnIOtvYEGgoS7IkjvSYVAlHUtU<}Uu99)}5-;__C>iglPxATd&iSg9 z`9d;zyrcnIwxNGb3je2)%T-p%dS>~e)4c~ zCh$!1RG^SgDY_jf*kS42>gEgf{*UlhGajw!lO1Q@X!D>BUOJmJe;g}s!<^{y{;-C*(G+F4FDCcj`!SkU!Z{hFL z9!QjZ3TKixUx5Hi98LW#$vqiOBs#Ukli6n1^YHW@p#!9(K|v)zS~|q|3}d_w0cno+ zXfD9)51yV6KU^$DQlsUI3)`}Z3l2!#d@nkCse@6eDJmTU@2A9{w@)JLlVVP_pS`#> z=@rMKWbL@!dN-+5s5tjh2JB%wrUgLPLRtKz_wlqR##G~A6+$8WCgfovkD0jYTh6tZ z;KYg+x)-A(3zMYcW+F5~?zaz*jh6j8wQYI&GM|0g4)h1Sq0t%wq`;7d^$Tg7KX>#0 zNC0d(G(_Y%JE2W)0QaYN*w!L7_)qTd&mK{G9>2G5b6KujGx=}H&_8n~3DzABSVy}*)*o%VCf0YdWgkb;1|B^%fo?V@OyZx85#*Bb_`D`}o{T4tir7Fa? zrQaC>rjS1Yz3E`6Yhb+--xXO1MM8&0v`9>$q11k)4t~SI7!!EbB*r_iK+ogaNuIH3 zfwVbxx>38GW8?|mR4_q17$gne^dSY>NMH$+Y{_ehKUXEwfZEP!0T>MWZr=Lm4UiyC zpRJ$f=*lSKTYs6yfR_Q}Bi{8%)YHC+KW~WDzq)bZepv}dC01}3D6nuwt;5j1DNo&3 zWS@R!VCZPciQ$0(KsbW{+R|#4u+JH15Q}c94aWv0o?+I2B`|SHZU{<-_epJWVgS@d zGk~T{oRPLFt9ssiiZpPjzrmzi?bV5wPWjqMkAdNUc5?Y~C^WEjVt$2vtJ-DTdOYwt z9%MYF16Ktt16+$ZP@a4Fb{(gT@xt3wHODUvpZ6%ys?3H`i(v#QX%DI`evE z6~Y>mt+K6P^#lj$S}=I)pzcJv$ugu55b1*8@#cATdn~zkx(;WqzcYat{;HVyO#?|Z z_Gz8{au977&(JyROO6^1gbHF;nSPt0j7kbsAH~Sr_ot1j8*zEFgk^XLCB&pkV)_E` zgw{In!`vy*r#SoO3!()~LVode)wqSk8r=Y*@dOedp#!jqFu@j_9mOT4It77TW(^|h zxkgN;!5&~ep>(=foL-YuUS2dX!(ZO{OSVTE4MFQ_{Hle@BKXQDqL^<`r=6H2 zSZ%p?Y#F9YWYzP`)y<+L4WbwhxeIJ*)l)YPCe!C;_43u`^W|G3Kahsa+M@pT8l!n! z3m-g8f?n1L+J;_;oY7r=f*%gH^>*g~gHI`<`ZCw#yu@puoapD`~!p~|CQSQ^sJ+fE=(v_2k1 zIvX!~%GhhIcWy@q4pX{y*Zq#A`MR=5W8Ri3fSt}GM}&54f{C2FZsm?+*-AB}+yEKL zo(=(Y-S1nVNI>*yztMp^yU|;~hBBc0#4|ju=UXrE1&TEe8xb_v1aUC|4zYNB#sBF6 zHSr#KFfjsk)V|RXR;`@kQb|i;3?v*5WeD0Mybw1N(yUoMg%Ehqa%#JA%^p6r)TzMk zjn?1a^MlI#D}6ap8He$lhtd82jpuY)M{l~27~{xz1Db?`YxcpJ5b0N_AeA|GEkfbj z=fr0k(ilo>d!n`YCOZu-awpo^rRJTl_?cKRr@7F zB%L^Sz5Ftc!x(fbNI{X@;}Sp*h=L{Xjt(<;H>vW|Mh1;x#)Zo=dH0d9o?}&j4kxe0 z>!}`FHRb>+mbYk*4N`1z4MdXJf{C*aH7e@Ux(+Pf)T&?9Fb$cAxz@iH{gN1a&G*s0 zUY^l;DjVw?8$oAoSq4Rh4CNj|O=<1fvLPbR{gGYnO3=}ml`)pw9K${_3T>4-6I9Nz zH}H${O58##{3TF9%n8% z6Gdp%#{40@P&yUFZwAio1*(ToK3I(4{ECrjnLo&FknDe!b74JeAaBQgI;^Qz!r7;L zqU-*i?fanzE{#P>mx^4BU;wIGhZu4$gMlx$gi`%2Wl*Zkr3m(P#6@LD zUMId@!YICAFhjHyQ&lh_-T45PskA#&4OKFeDZcJ8AtTKo+Fn51Yn~sIG$)9(@3h%V zR&`Fa2bw{^q^?3{U-mXs4~|Mi1)fC}qToFnmC(6LnwU5bOzvEH_VBbP48IUXEvXIEeu@{=G~_UKiXSl5 zj|IWZ;BeJ@l!-*GzIsY;s;;E-%cTS zP0q+9u6BMzlHAGM@t&w3>RIzH&vP@3cuNH!-0#dXSh6$VL+=R_655SBfeAeq5N! zz>@jx-rXPdsD9Re&Eo+s&_>M(eVOwcl4LrQks%x+KnfN?xW6K(M&d^VwR`^-K{U9} zS$4kiN;eAY-}hp+eL`~ic7N~peg2DsYn+o?d3wj=)x*6LU4GA&wz=`OO8c*{gYl(A zuz3T5LEKAinjdfaVtR^D#>nP(y+$yG{g^)Z0-Mv#3qOx9jSn&jw;yUu6m| zL1}KX(ZGfFigO`d4(SyUP(5HpxB}h2C;+lD1lHHze1MZjlLM%Y@ld8;>fwYx%?!sz z?{C}saG9~YN@Kti0-rZNNNj=kO>g`D3!9cK0(=b`TM_JK}a?(fov<4MlH_7dD@Kd4>tx zshtpOt*-(3s)aBcVI=T6cc>f=(u{}8l^8%|vNe_pka8AV!1lJ&FGiddufSN9; z>uRcn(y*djHGyfdQ)DeRjs0@i6(!Z;8;XVr+D7eGyc$PB$m}fK*r4qwRgzQn)*|HC zWgP9p1X{hu&`T-zBk#4i%0&Z%w@~Jo9X=6xY#3RA-(KMb`EE{GTu_X>^!|ofoo7+6zVy4T zysdvFa8DQOw>|~sOlhgw@l+S=aT;V++cr7NH5wQa@$e` zAMs~SR?m7y5X*F}insAm0#kluzF*}Q-dSYSs5IZk7IKy!V1+WQLItP9?p(a6na(uR zP;v_y!7m?4a{kHT@J0m#)qc}!&s+MWNkl{okYgpcO*0Ln`Ay4 zj?vU=s{8x(@9%8MncJjpVWv0ie-+uC{wQ0kjvD0a zrWKO>KMDKtlos)n^0XH55L7z7c-ho{|0S{YkUH~|qeZ#(MTF~&o882(v|mW8Ca$W< z2wC#eZHca&GL7GB!SS#N#4XhbWi?9INeqei`P1+?7v4zX;YHXwdgW7tT=)9I2sqhr z`Kb~=bu$beQ6isEzc6`fJCxAr7;5|aGMdFu94@guZ?!eNr?am(kcaz#NtIyk+jA1%Bd>f<7x-92~Y`G&^_(swI zJDaqLpQEi+aOt{|!kpSUsZ`b?WfjX&NT*QmkG6K6F-`2)Tb0sBtKKKOF-7B%DDgqIQf+Oe3j)}=J%~h=L=v;O*1uJsEysXLoes#`C zel`u=tdI`_6P|**Q*clZLEqG3Oao8G664QT^QvDnLERp(T>$?VYU8B;;8C&v^j(Pa1YxJ?r%=THTTNq42oc{;s6lTS_0lWWOw1; z?3=FuKP!2`F))Bz(f%-#R7^kN^~me|>Eqn)_`}9|Y#DI{6D6rO%q2-~G4x=%W%UpF zkyNZ6d|wYaR!44-qMQSHY5mI(PdYL}NXbHd#+)XMH3D4A6fl&B{?B2A?jUu1ig;|! zV&4=R@R3pmUkc?3SBxq^&~<}`EDeo!bPb!AG}7KA1$)iOROww3)~l-MFO%#dPeJc{)eLV&4i&{wRRwnat`~w=!{*9r4%bzv(q<`) z!o>Xvs9Mjtp)T0<^zZ4Y*fZJ4G^}FjkU!yx)N?}G0jU|nGx6l6Uh&qf9U;UD^jKet z;q6@yX4X{tiwfHl0_21sjHh(+Qx7k)>IR|Wpdg;MS@;J@f}W`*u>Xq1vJpm$6XYO! z-a$ktAitbJ9PmgXRq)vPEteQ^JU4BDIv0#rhFKix5NY3B+c(iQLuvC#YO5dD_G%Rd zxR^^aG~2e2VssjcGE^&f3AhjjyabTD)Lm`= zPB@GZOmqqS`#Q_i4e3`=+CU8)CZ3;VqA0<*Yr3cRxybVw35!9Io7>3=mC zlvP~M8$x)T4Ni@l&Ha8zUnA}>Q>JxTa8cMsn4zsB0ln5QJpX?WS!Oh z(kmxI_|ek$?&^+Mul+BFpW(5n>F)*~v_peoTL;0fAXFFoM-b8gaRGck+$4t`ez36f zfxYfeHJ2+_T|YJ%wVu+htz&!Mn&TjMuVxaV z*q;V#WJXwFULqZ4Gx5^@+bN;~zdYDhr_`OCVS#JYUaK`HFG4$f|2pA&#Oajte>byy zZNrvgUx2&o3{+S=Vu$;_Ri5kk(%lu}Vvt6?_e`wC5U(v_N=8s50OTw-Ch zfS9uj@W>-2#%F5BbHxPY%*4s}RPorSMi6?`1@ttg^(})C;0>p*GIsZ!qYP|H-YgR_ z8K-iZ!a>&FqX*{4ugzOwY&FPP^;k@n9jt=HOxj{T54;iAf-gm$5*;^A&$`RaT=bFv zH*>1CY8W23}o8Noh>mfQN&R1b{@|oeQ8w&qR6nJ7Uh$KDZ+l?(EoNAPpuFz=k z4zZj#J#Oq9nMo^BX9Vx%&}Y@xBGDhunmW)fupL~#aonAnqe9CDv>Q4>)}J%NuqhVF zuvMT-{p{?L!(`*#ALJl0TvuFXdDmH0VWmSn&jzT6zDzs=6;nU?%C{K^kFr=YSy1Wu zTu{3U4Mm(r=_{6M>w8wG)pdlH;(86?3ud9maDncQ#u`F>L2uR7cE;|WSXRsWakx6W zHxaOXp5-=rsyWTAu+2PE7T^syh5?fB&s_oX2O$4a(8m?BD)g|tbual4Q2>MFI!vXH zGjwfw_T#&=C-za0Jhn!l`Z~y{(O7`AbyNq}k14N`=yp&Rata zrHUNH2rY=vFmxCZ{rkS@g8QCZOcy*hPB-td{WR-HKY=uSk;;P%Y?o4X|=a3tj{^43CRRAV=L$yQu7ebYP1KSfLOILenb zYLiWus**=G746Ic_ICi#vzD&Qciph6<2;UNc%LA3YWODvIwV@H_LMcboS@EHTaRyY zzQsl(K)zLU63xetG{iwOS?{2cXa0Q_eF=SMH);+}CgLGM2M>Jg!qB-QR-f=aeoQ8X z-^RWANzRj9Dy~tIpXR;$juRl+TE6yBeuz^05i8)RXIeCniLRuq?6%nLnD5}K`&eMq zVG^Iyy_BNZ&DgUwC?rKT!%|WT)48gt2SD*G-y&D=?#}HI;tbrrEUPyQaoZy&8`KwR z@T*0OOLJeyQXfC8YWKTW!Zx}vk4pZR9UFP+ten(oOf<-7A5exHNYDjVRnm}o@z&Xxt zdU=vVgn+5UJafIM8jZllG)NqREecM*XvAZb7trRMX&CT{{sjI%J+{Jn=D3TcCWEPV zE$Q6eUH6wJ6?-M@SG^DZ+kFQ8jl&@TcT&A z@{@8Oa?CpeYeb?M8s0dK>U!UbcAX=0Ag>PQD0XkjoDFXp)EW|hMVks>2!rAeyav;a zpd%#;DocnJx>se@gr$z?MR@ogWZHz%viX5j9?s!~p$v`HfS9^rx+vf}nDPrTB`uwUQ8=k!huQ6E!V7p$ z-Bl({fq2k3yXe23WKEd`XQGN=|ZyP%pkum;{fwl$}Wydbq80M_J6P4ew~FUpPG)Z2`DJb$;g=8{S~`nS^}lmV0~=TRnl-~xt8LyNT+ zSF0cs783UKt9jdtCXZVoa;QAI=j+usU{{`Q%jb)nU&uc9S|79VKA%#r2noHFVG8BJ zxIMX2Wv!V<&`oxa7jj6^pLp(XXRyMi_xjoPoY|�GEWS(VOw+>oM{b&H|_*jO!Es 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 literal 81742 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 I@!o*{13O;`c>n+a literal 16614 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 diff --git a/test/membrane_vpx_plugin/vpx_decoder_test.exs b/test/membrane_vpx_plugin/vpx_decoder_test.exs index 00fbf91..77d21c6 100644 --- a/test/membrane_vpx_plugin/vpx_decoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_decoder_test.exs @@ -11,7 +11,7 @@ defmodule Membrane.VPx.DecoderTest do test "VP8 codec", %{tmp_dir: tmp_dir} do perform_decoder_test( tmp_dir, - "ref_vp8.ivf", + "input_vp8.ivf", "output_vp8.raw", "ref_vp8.raw", %Membrane.VP8.Decoder{framerate: {30, 1}} @@ -21,7 +21,7 @@ defmodule Membrane.VPx.DecoderTest do test "VP9 codec", %{tmp_dir: tmp_dir} do perform_decoder_test( tmp_dir, - "ref_vp9.ivf", + "input_vp9.ivf", "output_vp9.raw", "ref_vp9.raw", %Membrane.VP9.Decoder{framerate: {30, 1}} diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 0cc8bf9..9df9f15 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -9,7 +9,7 @@ defmodule Membrane.VPx.EncoderTest do describe "Encoder encodes correctly for" do @describetag :tmp_dir test "VP8 codec", %{tmp_dir: tmp_dir} do - perform_decoder_test( + perform_encoder_test( tmp_dir, "ref_vp8.raw", "output_vp8.ivf", @@ -19,9 +19,9 @@ defmodule Membrane.VPx.EncoderTest do end test "VP9 codec", %{tmp_dir: tmp_dir} do - perform_decoder_test( + perform_encoder_test( tmp_dir, - "ref_vp9.ivf", + "ref_vp9.raw", "output_vp9.ivf", "ref_vp9.ivf", %Membrane.VP9.Encoder{encoding_deadline: 0} @@ -29,9 +29,9 @@ defmodule Membrane.VPx.EncoderTest do end end - defp perform_decoder_test(tmp_dir, input_file, output_file, _ref_file, decoder_struct) do + defp perform_encoder_test(tmp_dir, input_file, output_file, ref_file, encoder_struct) do output_path = Path.join(tmp_dir, output_file) - # ref_path = Path.join(@fixtures_dir, ref_file) + ref_path = Path.join(@fixtures_dir, ref_file) pid = Membrane.Testing.Pipeline.start_link_supervised!( @@ -45,19 +45,17 @@ defmodule Membrane.VPx.EncoderTest do height: 720, framerate: {30, 1} }) - # |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts1")}) - |> child(:decoder, decoder_struct) - # |> child(%Membrane.Debug.Filter{handle_buffer: &IO.inspect(&1.pts, label: "pts2")}) + |> child(:encoder, encoder_struct) |> child(:serializer, %Membrane.IVF.Serializer{ width: 1080, height: 720, - rate: 1_000_000_000 + timebase: {1, 30} }) |> child(:sink, %Membrane.File.Sink{location: output_path}) ) assert_end_of_stream(pid, :sink, :input, 2000) - # assert File.read(ref_path) == File.read(output_path) + assert File.read!(ref_path) == File.read!(output_path) end end From 651e299e05925cc5cb2e3ad62cfc420d04c92ff2 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 13:45:25 +0200 Subject: [PATCH 10/24] Increase timeout --- test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index 9df9f15..b63a662 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -54,7 +54,7 @@ defmodule Membrane.VPx.EncoderTest do |> child(:sink, %Membrane.File.Sink{location: output_path}) ) - assert_end_of_stream(pid, :sink, :input, 2000) + assert_end_of_stream(pid, :sink, :input, 5000) assert File.read!(ref_path) == File.read!(output_path) end From 48f7dc6208c58734c7f1197301e3ff1ee4e27d9b Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 14:42:12 +0200 Subject: [PATCH 11/24] Abstract away the error function --- c_src/membrane_vpx_plugin/vpx_common.c | 23 ++++++++- c_src/membrane_vpx_plugin/vpx_common.h | 9 ++++ c_src/membrane_vpx_plugin/vpx_decoder.c | 8 +-- c_src/membrane_vpx_plugin/vpx_encoder.c | 51 +++++++++---------- .../membrane_vpx_plugin/vpx_encoder.spec.exs | 2 +- 5 files changed, 58 insertions(+), 35 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c index 7e6f925..ab56418 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.c +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -1,5 +1,25 @@ #include "vpx_common.h" +UNIFEX_TERM result_error( + UnifexEnv *env, + const char *reason, + UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), + vpx_codec_ctx_t *codec_context, + void *state +) { + if (codec_context) { + const char *detail = vpx_codec_error_detail(codec_context); + fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(codec_context)); + if (detail) { + fprintf(stderr, " %s\n", detail); + } + } + + if (state) unifex_release_resource(state); + + return result_error_fun(env, reason); +} + Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { const int height = (plane > 0 && img->y_chroma_shift > 0) ? (img->d_h + 1) >> img->y_chroma_shift : img->d_h; @@ -8,8 +28,7 @@ Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { (plane > 0 && img->x_chroma_shift > 0) ? (img->d_w + 1) >> img->x_chroma_shift : img->d_w; // Fixing NV12 chroma width if it is odd - if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) - width = (width + 1) & ~1; + if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) width = (width + 1) & ~1; return (Dimensions){width, height}; } diff --git a/c_src/membrane_vpx_plugin/vpx_common.h b/c_src/membrane_vpx_plugin/vpx_common.h index b2e861d..0d492ea 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.h +++ b/c_src/membrane_vpx_plugin/vpx_common.h @@ -1,4 +1,5 @@ #pragma once +#include "vpx/vpx_codec.h" #include "vpx/vpx_image.h" #include #include @@ -8,6 +9,14 @@ typedef struct Dimensions { unsigned int height; } Dimensions; +UNIFEX_TERM result_error( + UnifexEnv *env, + const char *reason, + UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), + vpx_codec_ctx_t *codec_context, + void *state +); + typedef enum ConversionType { IMAGE_TO_RAW_FRAME, RAW_FRAME_TO_IMAGE } ConversionType; Dimensions get_plane_dimensions(const vpx_image_t *img, int plane); diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index 513aa47..842e0b1 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -23,9 +23,7 @@ UNIFEX_TERM create(UnifexEnv *env, Codec codec) { } if (vpx_codec_dec_init(&state->codec_context, state->codec_interface, NULL, 0)) { - result = create_result_error(env, "Failed to initialize decoder"); - unifex_release_state(env, state); - return result; + return result_error(env, "Failed to initialize decoder", create_result_error, NULL, state); } result = create_result_ok(env, state); unifex_release_state(env, state); @@ -79,7 +77,9 @@ UNIFEX_TERM decode_frame(UnifexEnv *env, UnifexPayload *frame, State *state) { UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(*output_frames)); if (vpx_codec_decode(&state->codec_context, frame->data, frame->size, NULL, 0)) { - return decode_frame_result_error(env, "Decoding frame failed"); + return result_error( + env, "Decoding frame failed", decode_frame_result_error, &state->codec_context, NULL + ); } while ((img = vpx_codec_get_frame(&state->codec_context, &iter)) != NULL) { diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index 11cb3b3..f868733 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -9,25 +9,6 @@ void handle_destroy_state(UnifexEnv *env, State *state) { vpx_codec_destroy(&state->codec_context); } -UNIFEX_TERM error( - UnifexEnv *env, - const char *reason, - int codec_initialized, - UNIFEX_TERM (*result_error_fun)(UnifexEnv *, const char *), - State *state -) { - if (codec_initialized) { - const char *detail = vpx_codec_error_detail(&state->codec_context); - fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(&state->codec_context)); - if (detail) { - fprintf(stderr, " %s\n", detail); - } - } - UNIFEX_TERM result = result_error_fun(env, reason); - unifex_release_state(env, state); - return result; -} - vpx_img_fmt_t translate_pixel_format(PixelFormat pixel_format) { switch (pixel_format) { case PIXEL_FORMAT_I420: @@ -68,7 +49,9 @@ UNIFEX_TERM create( state->encoding_deadline = encoding_deadline; if (vpx_codec_enc_config_default(state->codec_interface, &config, 0)) { - return error(env, "Failed to get default codec config", 0, create_result_error, state); + return result_error( + env, "Failed to get default codec config", create_result_error, NULL, state + ); } config.g_h = height; @@ -78,10 +61,12 @@ UNIFEX_TERM create( config.g_error_resilient = 1; if (vpx_codec_enc_init(&state->codec_context, state->codec_interface, &config, 0)) { - return error(env, "Failed to initialize encoder", 0, create_result_error, state); + return result_error(env, "Failed to initialize encoder", create_result_error, NULL, state); } if (!vpx_img_alloc(&state->img, translate_pixel_format(pixel_format), width, height, 1)) { - return error(env, "Failed to allocate image", 1, create_result_error, state); + return result_error( + env, "Failed to allocate image", create_result_error, &state->codec_context, state + ); } result = create_result_ok(env, state); unifex_release_state(env, state); @@ -110,20 +95,29 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State unifex_alloc(allocated_frames * sizeof(*encoded_frames_timestamps)); do { + // Reasoning for the do-while and while loops comes from the description of vpx_codec_encode: + // + // When the last frame has been passed to the encoder, this function should continue to be + // called, with the img parameter set to NULL. This will signal the end-of-stream condition to + // the encoder and allow it to encode any held buffers. Encoding is complete when + // vpx_codec_encode() is called and vpx_codec_get_cx_data() returns no data. if (vpx_codec_encode(&state->codec_context, img, pts, 1, 0, state->encoding_deadline) != VPX_CODEC_OK) { if (flushing) { - return error(env, "Encoding frame failed", 1, flush_result_error, state); + return result_error( + env, "Encoding frame failed", flush_result_error, &state->codec_context, NULL + ); } else { - return error(env, "Encoding frame failed", 1, encode_frame_result_error, state); + return result_error( + env, "Encoding frame failed", encode_frame_result_error, &state->codec_context, NULL + ); } } got_packets = 0; while ((packet = vpx_codec_get_cx_data(&state->codec_context, &iter)) != NULL) { got_packets = 1; - if (packet->kind != VPX_CODEC_CX_FRAME_PKT) - continue; + if (packet->kind != VPX_CODEC_CX_FRAME_PKT) continue; if (frames_cnt >= allocated_frames) { allocated_frames *= 2; @@ -154,8 +148,9 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State return result; } -UNIFEX_TERM -encode_frame(UnifexEnv *env, UnifexPayload *raw_frame, vpx_codec_pts_t pts, State *state) { +UNIFEX_TERM encode_frame( + UnifexEnv *env, UnifexPayload *raw_frame, vpx_codec_pts_t pts, State *state +) { get_image_from_raw_frame(&state->img, raw_frame); return encode(env, &state->img, pts, state); } diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs index 97aa370..a0ee455 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs +++ b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs @@ -23,4 +23,4 @@ spec flush(state) :: {:ok :: label, frames :: [payload], timestamps :: [int64]} | {:error :: label, reason :: atom} -dirty :cpu, encode_frame: 3 +dirty :cpu, create: 5, encode_frame: 3, flush: 1 From 974f69311ecdcb7a150d8fff1d5cab6ab9a081b2 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 21 Jun 2024 17:30:08 +0200 Subject: [PATCH 12/24] Improve error logging --- c_src/membrane_vpx_plugin/vpx_common.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_common.c b/c_src/membrane_vpx_plugin/vpx_common.c index ab56418..d9efffe 100644 --- a/c_src/membrane_vpx_plugin/vpx_common.c +++ b/c_src/membrane_vpx_plugin/vpx_common.c @@ -7,17 +7,27 @@ UNIFEX_TERM result_error( vpx_codec_ctx_t *codec_context, void *state ) { + char *full_reason; if (codec_context) { + const char *error = vpx_codec_error(codec_context); const char *detail = vpx_codec_error_detail(codec_context); - fprintf(stderr, "%s: %s\n", reason, vpx_codec_error(codec_context)); if (detail) { - fprintf(stderr, " %s\n", detail); + full_reason = unifex_alloc(strlen(reason) + strlen(error) + strlen(detail) + 5); + sprintf(full_reason, "%s: %s: %s", reason, error, detail); + } else { + full_reason = unifex_alloc(strlen(reason) + strlen(error) + 3); + sprintf(full_reason, "%s: %s", reason, error); } + } else { + full_reason = unifex_alloc(strlen(reason) + 1); + sprintf(full_reason, "%s", reason); } if (state) unifex_release_resource(state); - return result_error_fun(env, reason); + UNIFEX_TERM result = result_error_fun(env, full_reason); + unifex_free(full_reason); + return result; } Dimensions get_plane_dimensions(const vpx_image_t *img, int plane) { From ad2e3096de99e56a3d45bdd93d0c7a85ef6945fb Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 26 Jun 2024 11:32:03 +0200 Subject: [PATCH 13/24] Increase timeout --- test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index b63a662..e80928a 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -54,7 +54,7 @@ defmodule Membrane.VPx.EncoderTest do |> child(:sink, %Membrane.File.Sink{location: output_path}) ) - assert_end_of_stream(pid, :sink, :input, 5000) + assert_end_of_stream(pid, :sink, :input, 10_000) assert File.read!(ref_path) == File.read!(output_path) end From 58a239c63e3cfc018b24a8664ebba02008580250 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 26 Jun 2024 15:05:29 +0200 Subject: [PATCH 14/24] Allow for nil framerate in RawVideo --- lib/membrane_vpx/decoder/vpx_decoder.ex | 4 ++-- mix.lock | 12 ++++++------ test/membrane_vpx_plugin/vpx_decoder_test.exs | 4 ++-- test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 -- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/membrane_vpx/decoder/vpx_decoder.ex b/lib/membrane_vpx/decoder/vpx_decoder.ex index b2f1058..dd383cd 100644 --- a/lib/membrane_vpx/decoder/vpx_decoder.ex +++ b/lib/membrane_vpx/decoder/vpx_decoder.ex @@ -79,14 +79,14 @@ defmodule Membrane.VPx.Decoder do { state.width || raise("Width not provided"), state.height || raise("Height not provided"), - state.framerate || raise("Framerate not provided") + state.framerate } %{width: width, height: height, framerate: framerate} -> { width, height, - framerate || state.framerate || raise("Framerate not provided") + framerate || state.framerate } end diff --git a/mix.lock b/mix.lock index 54e90a6..66f1d53 100644 --- a/mix.lock +++ b/mix.lock @@ -9,8 +9,8 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, @@ -21,12 +21,12 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "membrane_core": {:hex, :membrane_core, "1.1.0", "c3bbaa5af7c26a7c3748e573efe343c2104801e3463b9e491a607e82860334a4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3209d7f7e86d736cb7caffbba16b075c571cebb9439ab939ed6119c50fb59a5"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.0", "e855a848e84eaed537b41fd4436712038fc5518059eadc8609c83cd2d819653a", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "9c3653ca9f13bb409b36257d6094798d4625c739ab7a4035c12308622eb16e0b"}, - "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "e439515ffc0e72b46a28f8f3b09fc3f869605553", [branch: "fix-plugin"]}, + "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "51a7a6a94c5f4b69d877915e1035a7b0f20fd72b", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, - "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.0", "99fdf3a5cd5f64118e550b80d79ffed0d4be7becb3878a657f8726550c8c756f", [:mix], [], "hexpm", "1768c8137e4cfc6470117f71318a1dd9726c3b305a78bd56d90f796be716bd99"}, + "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, - "membrane_vp8_format": {:git, "https://github.com/membraneframework/membrane_vp8_format.git", "b9d9e4d56d7f25c6ac341b2d4581445d22c1d190", [branch: "add-fields"]}, - "membrane_vp9_format": {:git, "https://github.com/membraneframework/membrane_vp9_format.git", "93b2d68f4a3c9b06a1a3d7246bfd9374b58935ea", [branch: "add-fields"]}, + "membrane_vp8_format": {:git, "https://github.com/membraneframework/membrane_vp8_format.git", "ea00c96059042c63bafed1472da1f6b631152960", [branch: "add-fields"]}, + "membrane_vp9_format": {:git, "https://github.com/membraneframework/membrane_vp9_format.git", "a0fe6a87e87b6637b04959f85dc2b2f0f3e10c0b", [branch: "add-fields"]}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, diff --git a/test/membrane_vpx_plugin/vpx_decoder_test.exs b/test/membrane_vpx_plugin/vpx_decoder_test.exs index 77d21c6..dd413f9 100644 --- a/test/membrane_vpx_plugin/vpx_decoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_decoder_test.exs @@ -14,7 +14,7 @@ defmodule Membrane.VPx.DecoderTest do "input_vp8.ivf", "output_vp8.raw", "ref_vp8.raw", - %Membrane.VP8.Decoder{framerate: {30, 1}} + %Membrane.VP8.Decoder{} ) end @@ -24,7 +24,7 @@ defmodule Membrane.VPx.DecoderTest do "input_vp9.ivf", "output_vp9.raw", "ref_vp9.raw", - %Membrane.VP9.Decoder{framerate: {30, 1}} + %Membrane.VP9.Decoder{} ) end end diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index e80928a..aa5c002 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -47,8 +47,6 @@ defmodule Membrane.VPx.EncoderTest do }) |> child(:encoder, encoder_struct) |> child(:serializer, %Membrane.IVF.Serializer{ - width: 1080, - height: 720, timebase: {1, 30} }) |> child(:sink, %Membrane.File.Sink{location: output_path}) From 66c9a88c909fc2889cff46c7fdb285a14a233d63 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 26 Jun 2024 15:15:26 +0200 Subject: [PATCH 15/24] Fix test --- lib/membrane_vpx/decoder/vpx_decoder.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/membrane_vpx/decoder/vpx_decoder.ex b/lib/membrane_vpx/decoder/vpx_decoder.ex index dd383cd..01d329a 100644 --- a/lib/membrane_vpx/decoder/vpx_decoder.ex +++ b/lib/membrane_vpx/decoder/vpx_decoder.ex @@ -82,11 +82,11 @@ defmodule Membrane.VPx.Decoder do state.framerate } - %{width: width, height: height, framerate: framerate} -> + %{width: width, height: height} -> { width, height, - framerate || state.framerate + state.framerate } end From 7114749d37ff57a5d8fce91bb854eaa2388555ad Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 26 Jun 2024 15:17:48 +0200 Subject: [PATCH 16/24] Improve descriptions --- lib/membrane_vpx/decoder/vp8_decoder.ex | 2 +- lib/membrane_vpx/decoder/vp9_decoder.ex | 2 +- lib/membrane_vpx/encoder/vp8_encoder.ex | 2 +- lib/membrane_vpx/encoder/vp9_encoder.ex | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/membrane_vpx/decoder/vp8_decoder.ex b/lib/membrane_vpx/decoder/vp8_decoder.ex index 897beb5..4ea89d6 100644 --- a/lib/membrane_vpx/decoder/vp8_decoder.ex +++ b/lib/membrane_vpx/decoder/vp8_decoder.ex @@ -24,7 +24,7 @@ defmodule Membrane.VP8.Decoder do spec: {non_neg_integer(), pos_integer()} | nil, default: nil, description: """ - Framerate, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. + Framerate of the stream. """ ] diff --git a/lib/membrane_vpx/decoder/vp9_decoder.ex b/lib/membrane_vpx/decoder/vp9_decoder.ex index 08eae14..a89a737 100644 --- a/lib/membrane_vpx/decoder/vp9_decoder.ex +++ b/lib/membrane_vpx/decoder/vp9_decoder.ex @@ -24,7 +24,7 @@ defmodule Membrane.VP9.Decoder do spec: {non_neg_integer(), pos_integer()} | nil, default: nil, description: """ - Framerate, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. + Framerate of the stream. """ ] diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index 12e1630..ba749d7 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -13,7 +13,7 @@ defmodule Membrane.VP8.Encoder do Determines how long should it take the encoder to encode a frame (in microseconds). The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that - this is a soft limit, there is no guarantee that te encoding will never exceed it. + this is a soft limit, there is no guarantee that the encoding process will never exceed it. """ ] diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index 7c46ca7..20b3536 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -13,7 +13,7 @@ defmodule Membrane.VP9.Encoder do Determines how long should it take the encoder to encode a frame (in microseconds). The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that - this is a soft limit, there is no guarantee that te encoding will never exceed it. + this is a soft limit, there is no guarantee that the encoding process will never exceed it. """ ] From 37b7db8c0a4dcdbb55b02ccd343d5482f3008f25 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Thu, 27 Jun 2024 12:11:20 +0200 Subject: [PATCH 17/24] Update deps --- mix.exs | 8 ++------ mix.lock | 6 +++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/mix.exs b/mix.exs index fbe4c52..1cf903a 100644 --- a/mix.exs +++ b/mix.exs @@ -41,12 +41,8 @@ defmodule Membrane.VPx.Plugin.Mixfile do {:membrane_core, "~> 1.0"}, {:unifex, "~> 1.2"}, {:membrane_raw_video_format, "~> 0.4.0", override: true}, - # {:membrane_vp8_format, "~> 0.4.0"}, - {:membrane_vp8_format, - github: "membraneframework/membrane_vp8_format", branch: "add-fields", override: true}, - # {:membrane_vp9_format, "~> 0.4.0"}, - {:membrane_vp9_format, - github: "membraneframework/membrane_vp9_format", branch: "add-fields", override: true}, + {:membrane_vp8_format, "~> 0.5.0"}, + {:membrane_vp9_format, "~> 0.5.0"}, {:membrane_precompiled_dependency_provider, "~> 0.1.0"}, # {:membrane_ivf_plugin, "~> 0.7.0", only: :test}, {:membrane_ivf_plugin, diff --git a/mix.lock b/mix.lock index 66f1d53..3db59f3 100644 --- a/mix.lock +++ b/mix.lock @@ -21,12 +21,12 @@ "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "membrane_core": {:hex, :membrane_core, "1.1.0", "c3bbaa5af7c26a7c3748e573efe343c2104801e3463b9e491a607e82860334a4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3209d7f7e86d736cb7caffbba16b075c571cebb9439ab939ed6119c50fb59a5"}, "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.0", "e855a848e84eaed537b41fd4436712038fc5518059eadc8609c83cd2d819653a", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "9c3653ca9f13bb409b36257d6094798d4625c739ab7a4035c12308622eb16e0b"}, - "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "51a7a6a94c5f4b69d877915e1035a7b0f20fd72b", [branch: "fix-plugin"]}, + "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "e112040f22fe87dbe6142ee85c551abf202b426f", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, - "membrane_vp8_format": {:git, "https://github.com/membraneframework/membrane_vp8_format.git", "ea00c96059042c63bafed1472da1f6b631152960", [branch: "add-fields"]}, - "membrane_vp9_format": {:git, "https://github.com/membraneframework/membrane_vp9_format.git", "a0fe6a87e87b6637b04959f85dc2b2f0f3e10c0b", [branch: "add-fields"]}, + "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, + "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, From d1b0e36c7ef1673fc58b0623d7c9b192486c875c Mon Sep 17 00:00:00 2001 From: noarkhh Date: Thu, 27 Jun 2024 12:31:17 +0200 Subject: [PATCH 18/24] Refactor --- lib/membrane_vpx/decoder/vp8_decoder.ex | 4 ++-- lib/membrane_vpx/decoder/vp9_decoder.ex | 4 ++-- lib/membrane_vpx/decoder/vpx_decoder.ex | 12 ++++++------ lib/membrane_vpx/encoder/vpx_encoder_native.ex | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/membrane_vpx/decoder/vp8_decoder.ex b/lib/membrane_vpx/decoder/vp8_decoder.ex index 4ea89d6..8736234 100644 --- a/lib/membrane_vpx/decoder/vp8_decoder.ex +++ b/lib/membrane_vpx/decoder/vp8_decoder.ex @@ -7,14 +7,14 @@ defmodule Membrane.VP8.Decoder do alias Membrane.{VP8, VPx} def_options width: [ - spec: non_neg_integer() | nil, + spec: pos_integer() | nil, default: nil, description: """ Width of a frame, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. """ ], height: [ - spec: non_neg_integer() | nil, + spec: pos_integer() | nil, default: nil, description: """ Height of a frame, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. diff --git a/lib/membrane_vpx/decoder/vp9_decoder.ex b/lib/membrane_vpx/decoder/vp9_decoder.ex index a89a737..19663da 100644 --- a/lib/membrane_vpx/decoder/vp9_decoder.ex +++ b/lib/membrane_vpx/decoder/vp9_decoder.ex @@ -7,14 +7,14 @@ defmodule Membrane.VP9.Decoder do alias Membrane.{VP9, VPx} def_options width: [ - spec: non_neg_integer() | nil, + spec: pos_integer() | nil, default: nil, description: """ Width of a frame, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. """ ], height: [ - spec: non_neg_integer() | nil, + spec: pos_integer() | nil, default: nil, description: """ Height of a frame, needed if not provided with stream format. If it's not specified either in this option or the stream format, the element will crash. diff --git a/lib/membrane_vpx/decoder/vpx_decoder.ex b/lib/membrane_vpx/decoder/vpx_decoder.ex index 01d329a..c037c55 100644 --- a/lib/membrane_vpx/decoder/vpx_decoder.ex +++ b/lib/membrane_vpx/decoder/vpx_decoder.ex @@ -10,9 +10,9 @@ defmodule Membrane.VPx.Decoder do @type t :: %__MODULE__{ codec: :vp8 | :vp9, - width: non_neg_integer() | nil, - height: non_neg_integer() | nil, - framerate: {non_neg_integer(), pos_integer()} | nil, + width: pos_integer() | nil, + height: pos_integer() | nil, + framerate: {pos_integer(), pos_integer()} | nil, decoder_ref: reference() | nil } @@ -51,7 +51,7 @@ defmodule Membrane.VPx.Decoder do @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: callback_return() def handle_buffer(:input, %Buffer{payload: payload, pts: pts}, ctx, state) do - {:ok, decoded_frames, pixel_format} = Native.decode_frame(payload, state.decoder_ref) + {:ok, [decoded_frame], pixel_format} = Native.decode_frame(payload, state.decoder_ref) stream_format_action = if ctx.pads.output.stream_format == nil do @@ -63,8 +63,8 @@ defmodule Membrane.VPx.Decoder do [] end - buffers = Enum.map(decoded_frames, &%Buffer{payload: &1, pts: pts}) - {stream_format_action ++ [buffer: {:output, buffers}], state} + {stream_format_action ++ [buffer: {:output, %Buffer{payload: decoded_frame, pts: pts}}], + state} end @spec get_output_stream_format( diff --git a/lib/membrane_vpx/encoder/vpx_encoder_native.ex b/lib/membrane_vpx/encoder/vpx_encoder_native.ex index 376d3b0..6025da7 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder_native.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder_native.ex @@ -4,8 +4,8 @@ defmodule Membrane.VPx.Encoder.Native do @spec create!( :vp8 | :vp9, - non_neg_integer(), - non_neg_integer(), + pos_integer(), + pos_integer(), Membrane.RawVideo.pixel_format(), non_neg_integer() ) :: reference() From af6f06b412f652dfc70225ee72c160b6d805b75f Mon Sep 17 00:00:00 2001 From: noarkhh Date: Tue, 2 Jul 2024 12:31:45 +0200 Subject: [PATCH 19/24] Apply reviewers suggestion --- c_src/membrane_vpx_plugin/vpx_decoder.c | 2 +- .../membrane_vpx_plugin/vpx_decoder.spec.exs | 2 +- c_src/membrane_vpx_plugin/vpx_encoder.c | 6 +-- .../membrane_vpx_plugin/vpx_encoder.spec.exs | 2 +- lib/membrane_vpx/encoder/vp8_encoder.ex | 7 ++-- lib/membrane_vpx/encoder/vp9_encoder.ex | 7 ++-- lib/membrane_vpx/encoder/vpx_encoder.ex | 38 +++++++++++++++---- test/membrane_vpx_plugin/vpx_decoder_test.exs | 2 + test/membrane_vpx_plugin/vpx_encoder_test.exs | 2 + 9 files changed, 48 insertions(+), 20 deletions(-) diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.c b/c_src/membrane_vpx_plugin/vpx_decoder.c index 842e0b1..06db615 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.c +++ b/c_src/membrane_vpx_plugin/vpx_decoder.c @@ -74,7 +74,7 @@ UNIFEX_TERM decode_frame(UnifexEnv *env, UnifexPayload *frame, State *state) { vpx_image_t *img = NULL; PixelFormat pixel_format = PIXEL_FORMAT_I420; unsigned int frames_cnt = 0, allocated_frames = 1; - UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(*output_frames)); + UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(UnifexPayload*)); if (vpx_codec_decode(&state->codec_context, frame->data, frame->size, NULL, 0)) { return result_error( diff --git a/c_src/membrane_vpx_plugin/vpx_decoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_decoder.spec.exs index c1a02f3..0e1ec06 100644 --- a/c_src/membrane_vpx_plugin/vpx_decoder.spec.exs +++ b/c_src/membrane_vpx_plugin/vpx_decoder.spec.exs @@ -12,4 +12,4 @@ spec decode_frame(payload, state) :: {:ok :: label, frames :: [payload], pixel_format :: pixel_format} | {:error :: label, reason :: atom} -dirty :cpu, create: 1, decode_frame: 2 +dirty :cpu, [:create, :decode_frame] diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.c b/c_src/membrane_vpx_plugin/vpx_encoder.c index f868733..942c0b8 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.c +++ b/c_src/membrane_vpx_plugin/vpx_encoder.c @@ -90,9 +90,9 @@ UNIFEX_TERM encode(UnifexEnv *env, vpx_image_t *img, vpx_codec_pts_t pts, State const vpx_codec_cx_pkt_t *packet = NULL; unsigned int frames_cnt = 0, allocated_frames = 1; - UnifexPayload **encoded_frames = unifex_alloc(allocated_frames * sizeof(*encoded_frames)); + UnifexPayload **encoded_frames = unifex_alloc(allocated_frames * sizeof(UnifexPayload*)); vpx_codec_pts_t *encoded_frames_timestamps = - unifex_alloc(allocated_frames * sizeof(*encoded_frames_timestamps)); + unifex_alloc(allocated_frames * sizeof(vpx_codec_pts_t)); do { // Reasoning for the do-while and while loops comes from the description of vpx_codec_encode: @@ -155,4 +155,4 @@ UNIFEX_TERM encode_frame( return encode(env, &state->img, pts, state); } -UNIFEX_TERM flush(UnifexEnv *env, State *state) { return encode(env, NULL, 0, state); } \ No newline at end of file +UNIFEX_TERM flush(UnifexEnv *env, State *state) { return encode(env, NULL, 0, state); } diff --git a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs index a0ee455..4283c92 100644 --- a/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs +++ b/c_src/membrane_vpx_plugin/vpx_encoder.spec.exs @@ -23,4 +23,4 @@ spec flush(state) :: {:ok :: label, frames :: [payload], timestamps :: [int64]} | {:error :: label, reason :: atom} -dirty :cpu, create: 5, encode_frame: 3, flush: 1 +dirty :cpu, [:create, :encode_frame, :flush] diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index ba749d7..6d354df 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -7,10 +7,11 @@ defmodule Membrane.VP8.Encoder do alias Membrane.{VP8, VPx} def_options encoding_deadline: [ - spec: non_neg_integer(), - default: 1, + spec: Membrane.Time.t(), + default: Membrane.Time.microsecond(), + inspector: &Membrane.Time.inspect/1, description: """ - Determines how long should it take the encoder to encode a frame (in microseconds). + Determines how long should it take the encoder to encode a frame. The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index 20b3536..d71dcc0 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -7,10 +7,11 @@ defmodule Membrane.VP9.Encoder do alias Membrane.{VP9, VPx} def_options encoding_deadline: [ - spec: non_neg_integer(), - default: 1, + spec: Membrane.Time.t(), + default: Membrane.Time.microsecond(), + inspector: &Membrane.Time.inspect/1, description: """ - Determines how long should it take the encoder to encode a frame (in microseconds). + Determines how long should it take the encoder to encode a frame. The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index f8365ec..9bb7283 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -45,7 +45,7 @@ defmodule Membrane.VPx.Encoder do def handle_stream_format( :input, raw_video_format, - _ctx, + ctx, %State{codec_module: codec_module} = state ) do %RawVideo{ @@ -58,9 +58,28 @@ defmodule Membrane.VPx.Encoder do output_stream_format = struct(codec_module, width: width, height: height, framerate: framerate) - native = Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) + {buffers, native} = + case ctx.pads.input.stream_format do + nil -> + native = + Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) - {[stream_format: {:output, output_stream_format}], %{state | encoder_ref: native}} + {[], native} + + %RawVideo{width: ^width, height: ^height, pixel_format: ^pixel_format} -> + {[], state.encoder_ref} + + _other_format -> + buffers = flush(state.encoder_ref) + + native = + Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) + + {buffers, native} + end + + {[buffer: {:output, buffers}, stream_format: {:output, output_stream_format}], + %{state | encoder_ref: native}} end @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: @@ -77,12 +96,15 @@ defmodule Membrane.VPx.Encoder do @spec handle_end_of_stream(:input, CallbackContext.t(), State.t()) :: callback_return() def handle_end_of_stream(:input, _ctx, state) do - {:ok, encoded_frames, timestamps} = Native.flush(state.encoder_ref) + buffers = flush(state.encoder_ref) + {[buffer: {:output, buffers}, end_of_stream: :output], state} + end - buffers = - Enum.zip(encoded_frames, timestamps) - |> Enum.map(fn {frame, frame_pts} -> %Buffer{payload: frame, pts: frame_pts} end) + @spec flush(reference()) :: [Membrane.Buffer.t()] + defp flush(encoder_ref) do + {:ok, encoded_frames, timestamps} = Native.flush(encoder_ref) - {[buffer: {:output, buffers}, end_of_stream: :output], state} + Enum.zip(encoded_frames, timestamps) + |> Enum.map(fn {frame, frame_pts} -> %Buffer{payload: frame, pts: frame_pts} end) end end diff --git a/test/membrane_vpx_plugin/vpx_decoder_test.exs b/test/membrane_vpx_plugin/vpx_decoder_test.exs index dd413f9..6a96526 100644 --- a/test/membrane_vpx_plugin/vpx_decoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_decoder_test.exs @@ -47,5 +47,7 @@ defmodule Membrane.VPx.DecoderTest do assert_end_of_stream(pid, :sink, :input, 2000) assert File.read!(ref_path) == File.read!(output_path) + + Membrane.Testing.Pipeline.terminate(pid) end end diff --git a/test/membrane_vpx_plugin/vpx_encoder_test.exs b/test/membrane_vpx_plugin/vpx_encoder_test.exs index aa5c002..1843eb1 100644 --- a/test/membrane_vpx_plugin/vpx_encoder_test.exs +++ b/test/membrane_vpx_plugin/vpx_encoder_test.exs @@ -55,5 +55,7 @@ defmodule Membrane.VPx.EncoderTest do assert_end_of_stream(pid, :sink, :input, 10_000) assert File.read!(ref_path) == File.read!(output_path) + + Membrane.Testing.Pipeline.terminate(pid) end end From ad7ef142cbd3cef9dc078b9a90b67e7006015015 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Tue, 2 Jul 2024 12:36:22 +0200 Subject: [PATCH 20/24] Bump version --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 1cf903a..88fe515 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,7 @@ defmodule Membrane.VPx.Plugin.Mixfile do [ {:membrane_core, "~> 1.0"}, {:unifex, "~> 1.2"}, - {:membrane_raw_video_format, "~> 0.4.0", override: true}, + {:membrane_raw_video_format, "~> 0.4.0"}, {:membrane_vp8_format, "~> 0.5.0"}, {:membrane_vp9_format, "~> 0.5.0"}, {:membrane_precompiled_dependency_provider, "~> 0.1.0"}, diff --git a/mix.lock b/mix.lock index 3db59f3..33e7098 100644 --- a/mix.lock +++ b/mix.lock @@ -20,11 +20,11 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "membrane_core": {:hex, :membrane_core, "1.1.0", "c3bbaa5af7c26a7c3748e573efe343c2104801e3463b9e491a607e82860334a4", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3209d7f7e86d736cb7caffbba16b075c571cebb9439ab939ed6119c50fb59a5"}, - "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.0", "e855a848e84eaed537b41fd4436712038fc5518059eadc8609c83cd2d819653a", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "9c3653ca9f13bb409b36257d6094798d4625c739ab7a4035c12308622eb16e0b"}, + "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.1", "055a904823506e806e1e1a43643de2dfbe9baf3c1fe2f6f055d2e9b3710767dd", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0b209e17a7bafb8e281fe5d15b3760d9c6f8b3af628ed4589267ba01b7774d8f"}, "membrane_ivf_plugin": {:git, "https://github.com/membraneframework/membrane_ivf_plugin.git", "e112040f22fe87dbe6142ee85c551abf202b426f", [branch: "fix-plugin"]}, "membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"}, "membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"}, - "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.1", "fc0ac1f995411c3e3ccd93ac7ff8fe30930f8ff76d404b2f2a585d7efed6636f", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.3.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "bdc7859c9d576f59dd221cfa2a29940b4c58637b321279c23cb7c9e413436b65"}, + "membrane_raw_video_parser_plugin": {:hex, :membrane_raw_video_parser_plugin, "0.12.2", "7a1f11e122dfc1481654fd5a9ac43db80f7851ad569662cfca2e8a818403101c", [:mix], [{:bunch, "~> 1.3", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.0", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}], "hexpm", "c9254cc52c96ba0b575a65e4ab41f9218cef91ee5953cf6c1180835a21873907"}, "membrane_vp8_format": {:hex, :membrane_vp8_format, "0.5.0", "a589c20bb9d97ddc9b717684d00cefc84e2500ce63a0c33c4b9618d9b2f9b2ea", [:mix], [], "hexpm", "d29e0dae4bebc6838e82e031c181fe626d168c687e4bc617c1d0772bdeed19d5"}, "membrane_vp9_format": {:hex, :membrane_vp9_format, "0.5.0", "c6a4f2cbfc39dba5d80ad8287162c52b5cf6488676bd64435c1ac957bd16e66f", [:mix], [], "hexpm", "68752d8cbe7270ec222fc84a7d1553499f0d8ff86ef9d9e89f8955d49e20278e"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, From 5778c96185cb31d60998151d225611da815d0ce4 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 3 Jul 2024 16:28:49 +0200 Subject: [PATCH 21/24] Add encoding deadline calculation --- lib/membrane_vpx/encoder/vp8_encoder.ex | 7 +++--- lib/membrane_vpx/encoder/vp9_encoder.ex | 7 +++--- lib/membrane_vpx/encoder/vpx_encoder.ex | 33 ++++++++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index 6d354df..f4b1661 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -7,14 +7,15 @@ defmodule Membrane.VP8.Encoder do alias Membrane.{VP8, VPx} def_options encoding_deadline: [ - spec: Membrane.Time.t(), - default: Membrane.Time.microsecond(), - inspector: &Membrane.Time.inspect/1, + spec: Membrane.Time.t() | :auto, + default: :auto, description: """ Determines how long should it take the encoder to encode a frame. The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. + If set to `:auto` the deadline will be calculated based on the framerate provided by + incoming stream format. If it's `nil` a fixed deadline of 10ms will be set. """ ] diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index d71dcc0..0763c22 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -7,14 +7,15 @@ defmodule Membrane.VP9.Encoder do alias Membrane.{VP9, VPx} def_options encoding_deadline: [ - spec: Membrane.Time.t(), - default: Membrane.Time.microsecond(), - inspector: &Membrane.Time.inspect/1, + spec: Membrane.Time.t() | :auto, + default: :auto, description: """ Determines how long should it take the encoder to encode a frame. The longer the encoding takes the better the quality will be. If set to 0 the encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. + If set to `:auto` the deadline will be calculated based on the framerate provided by + incoming stream format. If it's `nil` a fixed deadline of 10ms will be set. """ ] diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index 9bb7283..5b12123 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -5,6 +5,8 @@ defmodule Membrane.VPx.Encoder do alias Membrane.Element.CallbackContext alias Membrane.VPx.Encoder.Native + @default_encoding_deadline Membrane.Time.milliseconds(10) + defmodule State do @moduledoc false @@ -58,28 +60,35 @@ defmodule Membrane.VPx.Encoder do output_stream_format = struct(codec_module, width: width, height: height, framerate: framerate) - {buffers, native} = + encoding_deadline = + case {state.encoding_deadline, framerate} do + {:auto, nil} -> @default_encoding_deadline |> Membrane.Time.as_microseconds(:round) + {:auto, {num, denom}} -> div(denom * 1_000_000, num) + {fixed_deadline, _framerate} -> fixed_deadline |> Membrane.Time.as_microseconds(:round) + end + + {buffers, encoder_ref} = case ctx.pads.input.stream_format do - nil -> - native = - Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) + ^raw_video_format -> + {[], state.encoder_ref} - {[], native} + nil -> + encoder_ref = + Native.create!(state.codec, width, height, pixel_format, encoding_deadline) - %RawVideo{width: ^width, height: ^height, pixel_format: ^pixel_format} -> - {[], state.encoder_ref} + {[], encoder_ref} - _other_format -> + _changed_format -> buffers = flush(state.encoder_ref) - native = - Native.create!(state.codec, width, height, pixel_format, state.encoding_deadline) + encoder_ref = + Native.create!(state.codec, width, height, pixel_format, encoding_deadline) - {buffers, native} + {buffers, encoder_ref} end {[buffer: {:output, buffers}, stream_format: {:output, output_stream_format}], - %{state | encoder_ref: native}} + %{state | encoder_ref: encoder_ref}} end @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: From 0c45d3c8e8dbd349b7f767d534eb36206be9fc29 Mon Sep 17 00:00:00 2001 From: noarkhh Date: Wed, 3 Jul 2024 16:48:21 +0200 Subject: [PATCH 22/24] Refactor --- lib/membrane_vpx/encoder/vpx_encoder.ex | 81 +++++++++++++------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/membrane_vpx/encoder/vpx_encoder.ex b/lib/membrane_vpx/encoder/vpx_encoder.ex index 5b12123..8eb96c5 100644 --- a/lib/membrane_vpx/encoder/vpx_encoder.ex +++ b/lib/membrane_vpx/encoder/vpx_encoder.ex @@ -44,51 +44,23 @@ defmodule Membrane.VPx.Encoder do @spec handle_stream_format(:input, RawVideo.t(), CallbackContext.t(), State.t()) :: callback_return() - def handle_stream_format( - :input, - raw_video_format, - ctx, - %State{codec_module: codec_module} = state - ) do + def handle_stream_format(:input, stream_format, ctx, state) do %RawVideo{ width: width, height: height, - framerate: framerate, - pixel_format: pixel_format - } = raw_video_format + framerate: framerate + } = stream_format output_stream_format = - struct(codec_module, width: width, height: height, framerate: framerate) - - encoding_deadline = - case {state.encoding_deadline, framerate} do - {:auto, nil} -> @default_encoding_deadline |> Membrane.Time.as_microseconds(:round) - {:auto, {num, denom}} -> div(denom * 1_000_000, num) - {fixed_deadline, _framerate} -> fixed_deadline |> Membrane.Time.as_microseconds(:round) - end + struct(state.codec_module, width: width, height: height, framerate: framerate) - {buffers, encoder_ref} = - case ctx.pads.input.stream_format do - ^raw_video_format -> - {[], state.encoder_ref} + {flushed_buffers, encoder_ref} = + maybe_recreate_encoder(ctx.pads.input.stream_format, stream_format, state) - nil -> - encoder_ref = - Native.create!(state.codec, width, height, pixel_format, encoding_deadline) - - {[], encoder_ref} - - _changed_format -> - buffers = flush(state.encoder_ref) - - encoder_ref = - Native.create!(state.codec, width, height, pixel_format, encoding_deadline) - - {buffers, encoder_ref} - end - - {[buffer: {:output, buffers}, stream_format: {:output, output_stream_format}], - %{state | encoder_ref: encoder_ref}} + { + [buffer: {:output, flushed_buffers}, stream_format: {:output, output_stream_format}], + %{state | encoder_ref: encoder_ref} + } end @spec handle_buffer(:input, Membrane.Buffer.t(), CallbackContext.t(), State.t()) :: @@ -109,6 +81,39 @@ defmodule Membrane.VPx.Encoder do {[buffer: {:output, buffers}, end_of_stream: :output], state} end + @spec maybe_recreate_encoder( + previous_stream_format :: RawVideo.t(), + new_stream_format :: RawVideo.t(), + State.t() + ) :: {flushed_buffers :: [Buffer.t()], encoder_ref :: reference()} + defp maybe_recreate_encoder(unchanged_stream_format, unchanged_stream_format, state) do + {[], state.encoder_ref} + end + + defp maybe_recreate_encoder(_previous_stream_format, new_stream_format, state) do + %RawVideo{ + width: width, + height: height, + framerate: framerate, + pixel_format: pixel_format + } = new_stream_format + + encoding_deadline = + case {state.encoding_deadline, framerate} do + {:auto, nil} -> @default_encoding_deadline |> Membrane.Time.as_microseconds(:round) + {:auto, {num, denom}} -> div(denom * 1_000_000, num) + {fixed_deadline, _framerate} -> fixed_deadline |> Membrane.Time.as_microseconds(:round) + end + + new_encoder_ref = + Native.create!(state.codec, width, height, pixel_format, encoding_deadline) + + case state.encoder_ref do + nil -> {[], new_encoder_ref} + old_encoder_ref -> {flush(old_encoder_ref), new_encoder_ref} + end + end + @spec flush(reference()) :: [Membrane.Buffer.t()] defp flush(encoder_ref) do {:ok, encoded_frames, timestamps} = Native.flush(encoder_ref) From c4ac5586661a3c0a74569c3f97e572fae6ca3028 Mon Sep 17 00:00:00 2001 From: Jakub Pryc <94321002+Noarkhh@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:36:26 +0200 Subject: [PATCH 23/24] Update lib/membrane_vpx/encoder/vp8_encoder.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Kita --- lib/membrane_vpx/encoder/vp8_encoder.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index f4b1661..14cdd31 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -15,7 +15,7 @@ defmodule Membrane.VP8.Encoder do encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. If set to `:auto` the deadline will be calculated based on the framerate provided by - incoming stream format. If it's `nil` a fixed deadline of 10ms will be set. + incoming stream format. If the framerate is `nil`, a fixed deadline of 10ms will be set. """ ] From 33386fb81b6842c6ac76bb11240a825bdc8c062b Mon Sep 17 00:00:00 2001 From: noarkhh Date: Fri, 5 Jul 2024 13:14:10 +0200 Subject: [PATCH 24/24] Improve descriptions --- lib/membrane_vpx/encoder/vp8_encoder.ex | 2 +- lib/membrane_vpx/encoder/vp9_encoder.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/membrane_vpx/encoder/vp8_encoder.ex b/lib/membrane_vpx/encoder/vp8_encoder.ex index 14cdd31..aa0c27b 100644 --- a/lib/membrane_vpx/encoder/vp8_encoder.ex +++ b/lib/membrane_vpx/encoder/vp8_encoder.ex @@ -15,7 +15,7 @@ defmodule Membrane.VP8.Encoder do encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. If set to `:auto` the deadline will be calculated based on the framerate provided by - incoming stream format. If the framerate is `nil`, a fixed deadline of 10ms will be set. + incoming stream format. If the framerate is `nil` a fixed deadline of 10ms will be set. """ ] diff --git a/lib/membrane_vpx/encoder/vp9_encoder.ex b/lib/membrane_vpx/encoder/vp9_encoder.ex index 0763c22..5e1c85d 100644 --- a/lib/membrane_vpx/encoder/vp9_encoder.ex +++ b/lib/membrane_vpx/encoder/vp9_encoder.ex @@ -15,7 +15,7 @@ defmodule Membrane.VP9.Encoder do encoder will take as long as it needs to produce the best frame possible. Note that this is a soft limit, there is no guarantee that the encoding process will never exceed it. If set to `:auto` the deadline will be calculated based on the framerate provided by - incoming stream format. If it's `nil` a fixed deadline of 10ms will be set. + incoming stream format. If the framerate is `nil` a fixed deadline of 10ms will be set. """ ]