Skip to content

Commit

Permalink
Create encoder (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Noarkhh authored Jul 8, 2024
1 parent a7f0baa commit f738736
Show file tree
Hide file tree
Showing 22 changed files with 655 additions and 97 deletions.
21 changes: 12 additions & 9 deletions bundlex.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ 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)},
{:pkg_config, "vpx"}
]
],
preprocessor: Unifex
],
vpx_encoder: [
interface: :nif,
sources: ["vpx_encoder.c", "vpx_common.c"],
os_deps: [
libvpx: [
{:precompiled, Membrane.PrecompiledDependencyProvider.get_dependency_url(:libvpx)},
Expand All @@ -20,14 +31,6 @@ defmodule Membrane.VPx.BundlexProject do
],
preprocessor: Unifex
]
# vpx_encoder: [
# interface: :nif,
# sources: ["vpx_encoder.c"],
# os_deps: [
# libvpx: [{:pkg_config, "vpx"}]
# ],
# preprocessor: Unifex
# ]
]
end
end
85 changes: 85 additions & 0 deletions c_src/membrane_vpx_plugin/vpx_common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#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
) {
char *full_reason;
if (codec_context) {
const char *error = vpx_codec_error(codec_context);
const char *detail = vpx_codec_error_detail(codec_context);
if (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);

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) {
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 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) {
unifex_payload_release(payloads[i]);
unifex_free(payloads[i]);
}
}
unifex_free(payloads);
}
28 changes: 28 additions & 0 deletions c_src/membrane_vpx_plugin/vpx_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include "vpx/vpx_codec.h"
#include "vpx/vpx_image.h"
#include <unifex/payload.h>
#include <unifex/unifex.h>

typedef struct Dimensions {
unsigned int width;
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);

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
);
65 changes: 14 additions & 51 deletions c_src/membrane_vpx_plugin/vpx_decoder.c
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -20,28 +23,13 @@ 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);
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;
Expand All @@ -55,25 +43,8 @@ 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) {
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;

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) {
Expand All @@ -85,19 +56,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;
}
Expand All @@ -107,11 +73,13 @@ 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;
UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(*output_frames));
unsigned int frames_cnt = 0, allocated_frames = 1;
UnifexPayload **output_frames = unifex_alloc(allocated_frames * sizeof(UnifexPayload*));

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) {
Expand All @@ -121,19 +89,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(output_frames, frames_cnt);

return result;
}
6 changes: 1 addition & 5 deletions c_src/membrane_vpx_plugin/vpx_decoder.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
#pragma once
#include "vpx/vp8dx.h"
#include "vpx/vpx_decoder.h"
#include "vpx_common.h"
#include <erl_nif.h>

typedef struct State {
vpx_codec_ctx_t codec_context;
vpx_codec_iface_t *codec_interface;
} State;

typedef struct Dimensions {
unsigned int width;
unsigned int height;
} Dimensions;

#include "_generated/vpx_decoder.h"
2 changes: 1 addition & 1 deletion c_src/membrane_vpx_plugin/vpx_decoder.spec.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading

0 comments on commit f738736

Please sign in to comment.