HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Native library AV1Lib by barry
expand copy to clipboardexpand
//Written by Barry Porter, 2025

#include "dana_lib_defs.h"
#include "nli_util.h"
#include "vmi_util.h"

#include 
#include 
#include 

#ifdef WINDOWS
#include 
#endif

#ifdef LINUX
#include 
#include 
#include 
#include 
#endif

#include 

#include "aom/aom_encoder.h"
#include "aom/aomcx.h"
#include "aom/aom_decoder.h"
#include "common/tools_common.h"
#include "common/video_reader.h"
#include "common/video_common.h"
#include "aom/aomdx.h"
#include "aom_ports/mem_ops.h"

#define AV1_FOURCC 0x31305641

void AV1Lib_setInterfaceFunction(char* name, void* ptr);
Interface* AV1Lib_getPublicInterface();
const DanaType* AV1Lib_getTypeDefinition(char* name);

static CoreAPI *api;

static GlobalTypeLink *charArrayGT = NULL;
static GlobalTypeLink *frameDataGT = NULL;
static GlobalTypeLink *frameDataArrayGT = NULL;
static GlobalTypeLink *encodedFrameDataGT = NULL;
static GlobalTypeLink *encodedFrameDataArrayGT = NULL;

typedef struct {
    AvxVideoInfo        info;
    aom_codec_iface_t   *intf;
    aom_codec_ctx_t     codec;
    aom_codec_iter_t    iterator;
    aom_image_t         *image;
	DanaEl* buffer[128];
} Instance;

typedef struct {
    AvxVideoInfo        info;
    aom_codec_iface_t   *intf;
    aom_codec_ctx_t     codec;
    aom_image_t         image;
	size_t width;
	size_t height;
	size_t frameCount;
	DanaEl* buffer[128];
} InstanceEncoder;

INSTRUCTION_DEF op_make_instance(FrameData *cframe)
	{
	Instance* n = malloc(sizeof(Instance));
	memset(n, 0, sizeof(Instance));

	// get the av1 decoder interface
    n->intf = aom_codec_av1_dx();
    // init the codec from our chosen interface
    if (aom_codec_dec_init(&n->codec, n->intf, NULL, 0)) {
		api -> throwException(cframe, "decoder init failed");
        return RETURN_OK;
    }

	api -> returnRaw(cframe, (unsigned char*) &n, sizeof(void*));

	return RETURN_OK;
	}

INSTRUCTION_DEF op_decode(FrameData *cframe)
	{
	Instance* handle = NULL;
	memcpy(&handle, api -> getParamRaw(cframe, 0), sizeof(void*));

	//printf("::decode\n");

	DanaEl* array = api -> getParamEl(cframe, 1);
	unsigned char* content = api -> getArrayContent(array);
	size_t contentLen = api -> getArrayLength(array);

	//size_t width = api -> getParamInt(cframe, 2);
	//size_t height = api -> getParamInt(cframe, 3);

	aom_codec_err_t err = 0;

	// decode the frame
	if ((err = aom_codec_decode(&handle->codec, content, contentLen, NULL)) != 0)
		{
		api -> throwException(cframe, aom_codec_err_to_string(err));
		return RETURN_OK;
		}
	
	//we should keep calling get_frame until it returns "null", since a single decode operation can return multiple frames
	int frameCount = 0;
	memset(&handle->iterator, 0, sizeof(handle->iterator));
	while ((handle -> image = aom_codec_get_frame(&handle->codec, &handle->iterator)) != NULL)
		{
		if (handle -> image == NULL)
			{
			api -> throwException(cframe, "image frame get failed");
			return RETURN_OK;
			}
		
		DanaEl* cell = api -> makeData(frameDataGT);
		api -> setDataFieldEl(cell, 0, api -> getParamEl(cframe, 2));

		unsigned char* rawPix = NULL;
		
		aom_image_t* img = handle->image;
		// copy the decoded data into our buffer
		for (size_t plane = 0; plane < 3; ++plane) {
			const unsigned char *buf = img->planes[plane];
			const int stride = img->stride[plane];
			const int w = aom_img_plane_width(img, plane) * ((img->fmt & AOM_IMG_FMT_HIGHBITDEPTH) ? 2 : 1);
			const int h = aom_img_plane_height(img, plane);

			//printf("plane-%u::stride %u w %u h %u format %u AS %u\n", plane, stride, w, h, img -> fmt, stride * h);

			DanaEl* pixels = api -> makeArray(charArrayGT, stride * h, &rawPix);
			memcpy(rawPix, buf, stride * h);

			api -> setDataFieldEl(cell, plane + 1, pixels);
			api -> setDataFieldInt(cell, plane + 4, stride);
			}
		
		handle -> buffer[frameCount] = cell;
		frameCount ++;
		}

	if (frameCount != 0)
		{
		DanaEl* result = api -> makeArray(frameDataArrayGT, frameCount, NULL);

		for (int i = 0; i < frameCount; i++)
			{
			api -> setArrayCellEl(result, i, handle -> buffer[i]);
			}

		api -> returnEl(cframe, result);
		}
	
	return RETURN_OK;
	}

INSTRUCTION_DEF op_destroy(FrameData *cframe)
	{
	Instance* handle = NULL;
	memcpy(&handle, api -> getParamRaw(cframe, 0), sizeof(void*));

	aom_codec_destroy(&handle -> codec);

	free(handle);

	return RETURN_OK;
	}

static int getOptionInt(DanaEl* options, char* name, int fallback, bool* set)
	{
	if (options != NULL)
		{
		int i;
		for (i = 0; i < api -> getArrayLength(options); i++)
			{
			DanaEl* cell = api -> getArrayCellEl(options, i);
			DanaEl* strKey = api -> getDataFieldEl(cell, 0);

			if ((api -> getArrayLength(strKey) == strlen(name)) && memcmp(api -> getArrayContent(strKey), name, strlen(name)) == 0)
				{
				char tmp[256];
				memset(tmp, 0, sizeof(tmp));
				DanaEl* strVal = api -> getDataFieldEl(cell, 1);
				if (strVal != NULL && api -> getArrayLength(strVal) != 0)
					{
					memcpy(tmp, api -> getArrayContent(strVal), api -> getArrayLength(strVal));
					*set = true;
					return atoi(tmp);
					}
				}
			}
		}
	
	return fallback;
	}

INSTRUCTION_DEF op_make_instance_encoder(FrameData *cframe)
	{
	#ifndef WASM
	InstanceEncoder* n = malloc(sizeof(InstanceEncoder));
	memset(n, 0, sizeof(InstanceEncoder));

	aom_codec_enc_cfg_t cfg;

	int usage = 0;
	int speed = 2;

	n -> intf = aom_codec_av1_cx();
	n -> info.codec_fourcc = AV1_FOURCC;
	n -> info.frame_width = api -> getParamInt(cframe, 0);
	n -> info.frame_height = api -> getParamInt(cframe, 1);
	n -> info.time_base.numerator = 1;
	n -> info.time_base.denominator = api -> getParamInt(cframe, 2);

	DanaEl* options = api -> getParamEl(cframe, 3);
	bool bitrate_set = false;
	int bitrate = getOptionInt(options, "bitrate", 4096, &bitrate_set);

	if (!aom_img_alloc(&n -> image, AOM_IMG_FMT_I420, n -> info.frame_width, n -> info.frame_height, 1))
		{
		api -> throwException(cframe, "failed to allocate encoder memory");
		return RETURN_OK;
		}

	//printf("encoder format: %u\n", n -> image.fmt);

	if (aom_codec_enc_config_default(n -> intf, &cfg, usage))
		{
		api -> throwException(cframe, "failed to configure encoder");
		return RETURN_OK;
		}

	cfg.g_w = n -> info.frame_width;
	cfg.g_h = n -> info.frame_height;
	cfg.g_timebase.num = n -> info.time_base.numerator;
	cfg.g_timebase.den = n -> info.time_base.denominator;
	cfg.rc_target_bitrate = bitrate;
	cfg.g_error_resilient = AOM_ERROR_RESILIENT_DEFAULT;

	if (aom_codec_enc_init(&n -> codec, n -> intf, &cfg, 0))
		{
		api -> throwException(cframe, "failed to start encoder");
		return RETURN_OK;
		}

	if (aom_codec_control(&n -> codec, AOME_SET_CPUUSED, speed))
		{
		api -> throwException(cframe, "failed to configure CPU for encoder");
		return RETURN_OK;
		}

	n -> width = api -> getParamInt(cframe, 0);
	n -> height = api -> getParamInt(cframe, 1);

	api -> returnRaw(cframe, (unsigned char*) &n, sizeof(void*));
	#endif

	return RETURN_OK;
	}

#ifndef WASM
static DanaEl** encode_frame(InstanceEncoder* n, aom_codec_ctx_t *codec, aom_image_t *img, int frame_index, int flags, int *encodeCount)
	{
	int index = 0;
	aom_codec_iter_t iter = NULL;
	const aom_codec_cx_pkt_t *pkt = NULL;
	const aom_codec_err_t res =
	aom_codec_encode(codec, img, frame_index, 1, flags);
	if (res != AOM_CODEC_OK)
		{
		//printf("ENCODE FAILED\n");
		return NULL;//die_codec(codec, "Failed to encode frame");
		}

	while ((pkt = aom_codec_get_cx_data(codec, &iter)) != NULL)
		{
		DanaEl* ef = api -> makeData(encodedFrameDataGT);
		unsigned char* cnt = NULL;
		DanaEl* bytes = api -> makeArray(charArrayGT, pkt->data.frame.sz, &cnt);
		memcpy(cnt, pkt->data.frame.buf, pkt->data.frame.sz);
		api -> setDataFieldEl(ef, 0, bytes);
		api -> setDataFieldInt(ef, 1, pkt->data.frame.pts);

		n -> buffer[index] = ef;

		index ++;
		}
	
	DanaEl** result = NULL;
	if (index != 0)
		{
		result = malloc(sizeof(DanaEl*) * index);
		int i = 0;
		for (i = 0; i < index; i++)
			{
			result[i] = n -> buffer[i];
			}
		
		*encodeCount = index;
		}

	return result;
	}
#endif

INSTRUCTION_DEF op_encode(FrameData *cframe)
	{
	#ifndef WASM
	InstanceEncoder* handle = NULL;
	memcpy(&handle, api -> getParamRaw(cframe, 0), sizeof(void*));

	DanaEl* img = api -> getParamEl(cframe, 1);
	unsigned char keyFrame = api -> getParamRaw(cframe, 2)[0];

	for (size_t plane = 0; plane < 3; ++plane) {
		unsigned char *buf = handle -> image.planes[plane];
		//calculate our copy-volume as the lower of the two stride values (we already know the w/h are the same between input and output)
		int inputStride = api -> getDataFieldInt(img, plane + 4);
		int outputStride = handle -> image.stride[plane];
		int stride = inputStride;
		if (outputStride < stride) stride = outputStride;
		int w = aom_img_plane_width(&handle -> image, plane) * ((handle -> image.fmt & AOM_IMG_FMT_HIGHBITDEPTH) ? 2 : 1);
		int h = aom_img_plane_height(&handle -> image, plane);
		
		DanaEl* pixels = api -> getDataFieldEl(img, plane + 1);
		unsigned char* rawPix = api -> getArrayContent(pixels);

		for (int i = 0; i < h; i++)
			{
			memcpy(buf, rawPix, stride);
			buf += outputStride;
			rawPix += inputStride;
			}
		}
	
	int flags = 0;
	if (keyFrame) flags |= AOM_EFLAG_FORCE_KF;

	int ec = 0;
	DanaEl** result = encode_frame(handle, &handle -> codec, &handle -> image, handle -> frameCount, flags, &ec);
	if (result != NULL)
		{
		DanaEl* array = api -> makeArray(encodedFrameDataArrayGT, ec, NULL);
		int i = 0;
		for (i = 0; i < ec; i++)
			{
			api -> setArrayCellEl(array, i, result[i]);
			}
		free(result);

		api -> returnEl(cframe, array);
		}
	
	handle -> frameCount ++;

	#endif

	return RETURN_OK;
	}

INSTRUCTION_DEF op_encode_finish(FrameData *cframe)
	{
	#ifndef WASM
	
	InstanceEncoder* handle = NULL;
	memcpy(&handle, api -> getParamRaw(cframe, 0), sizeof(void*));

	// flush the encoder
	DanaEl** buffer[128];
	int countBuffer[128];
	DanaEl** result = NULL;
	int index = 0;
	int totalLength = 0;
	int ec = 0;
	while ((result = encode_frame(handle, &handle -> codec, NULL, -1, 0, &ec)) != NULL)
		{
		buffer[index] = result;
		countBuffer[index] = ec;
		totalLength += ec;
		index ++;
		}
	
	if (totalLength != 0)
		{
		DanaEl* value = api -> makeArray(encodedFrameDataArrayGT, totalLength, NULL);
		int i = 0;
		int ndx = 0;
		for (i = 0; i < index; i++)
			{
			int j = 0;
			for (j = 0; j < countBuffer[i]; j++)
				{
				api -> setArrayCellEl(value, ndx, buffer[i][j]);
				ndx ++;
				}
			
			free(buffer[i]);
			}
		free(result);

		api -> returnEl(cframe, value);
		}
	
		#endif

	return RETURN_OK;
	}

INSTRUCTION_DEF op_destroy_encoder(FrameData *cframe)
	{
	#ifndef WASM
	InstanceEncoder* handle = NULL;
	memcpy(&handle, api -> getParamRaw(cframe, 0), sizeof(void*));

	aom_img_free(&handle -> image);
	aom_codec_destroy(&handle -> codec);

	free(handle);

	#endif

	return RETURN_OK;
	}

#ifdef STATIC_NATIVE_LIBRARIES
Interface* AV1Lib_load(CoreAPI *capi)
#else
Interface* load(CoreAPI *capi)
#endif
	{
	api = capi;
	
	AV1Lib_setInterfaceFunction("newDecoder", op_make_instance);
	AV1Lib_setInterfaceFunction("decode", op_decode);
	AV1Lib_setInterfaceFunction("destroyDecoder", op_destroy);

	AV1Lib_setInterfaceFunction("newEncoder", op_make_instance_encoder);
	AV1Lib_setInterfaceFunction("encode", op_encode);
	AV1Lib_setInterfaceFunction("finishEncode", op_encode_finish);
	AV1Lib_setInterfaceFunction("destroyEncoder", op_destroy_encoder);
	
	charArrayGT = api -> resolveGlobalTypeMapping(AV1Lib_getTypeDefinition("byte[]"));
	frameDataGT = api -> resolveGlobalTypeMapping(AV1Lib_getTypeDefinition("PixelMapYUV"));
	frameDataArrayGT = api -> resolveGlobalTypeMapping(AV1Lib_getTypeDefinition("PixelMapYUV[]"));
	encodedFrameDataGT = api -> resolveGlobalTypeMapping(AV1Lib_getTypeDefinition("EncodedFrame"));
	encodedFrameDataArrayGT = api -> resolveGlobalTypeMapping(AV1Lib_getTypeDefinition("EncodedFrame[]"));
	
	return AV1Lib_getPublicInterface();
	}

#ifdef STATIC_NATIVE_LIBRARIES
void AV1Lib_unload()
#else
void unload()
#endif
	{
	api -> decrementGTRefCount(charArrayGT);
	api -> decrementGTRefCount(frameDataGT);
	api -> decrementGTRefCount(frameDataArrayGT);
	api -> decrementGTRefCount(encodedFrameDataGT);
	api -> decrementGTRefCount(encodedFrameDataArrayGT);
	}
Revision history
To propose a new revision to this entity, use dana source put -uls your/new/version.c -n AV1Lib -gni media.video.Decoder:av1 -apiv 17 -m "reason for update" -u yourUsername
Version 4 (this version) by barry
Notes for this version: Adds a method to pass in optional encoder parameters
Version 3 by barry
Version 2 by barry
Version 1 by barry