1
0
mirror of git://f0xx.org/ac/ac-mobile-android synced 2026-07-29 02:58:06 +03:00
This commit is contained in:
Anton Afanasyeu
2026-06-23 12:30:10 +02:00
commit 8675c2df3a
2972 changed files with 176371 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
/* package ndk/jni/androidcast_codecs.c */
/*********************************************************************
* androidcast_codecs.c
* Created at: Sun 17 May 2026 22:49:49 +0200
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
* Contributors:
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 19 lines)
* - Cursor Agent (project assistant)
* Digest: SHA256 a6e0a5cdb3c06c23f932e9927494a53786a587533964767fab09fce1da5028a8
**********************************************************************/
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "androidcast_codecs"
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
(void)vm;
(void)reserved;
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "androidcast codecs native library loaded");
return JNI_VERSION_1_6;
}

134
ndk/jni/crash_hook.c Normal file
View File

@@ -0,0 +1,134 @@
/* package ndk/jni/crash_hook.c */
/*********************************************************************
* crash_hook.c
* Created at: Wed 20 May 2026 14:31:55 +0200
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
* Contributors:
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 130 lines)
* - Cursor Agent (project assistant)
* Digest: SHA256 3ae2b4ff82863a890f6b5a592d9dd3d93bd28afbf93817a4ecae9e5c2cf03811
**********************************************************************/
#include <jni.h>
#include <android/log.h>
#include <dlfcn.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <unwind.h>
#define LOG_TAG "androidcast_crash"
#define REPORT_DIR_MAX 512
#define MAX_FRAMES 64
static char g_report_dir[REPORT_DIR_MAX];
static volatile sig_atomic_t g_handling;
typedef struct {
void **frames;
int max;
int count;
} backtrace_state_t;
static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) {
backtrace_state_t *state = (backtrace_state_t *) arg;
uintptr_t pc = _Unwind_GetIP(context);
if (pc == 0) {
return _URC_NO_REASON;
}
if (state->count < state->max) {
state->frames[state->count++] = (void *) pc;
}
return _URC_NO_REASON;
}
static int capture_backtrace(void **frames, int max) {
backtrace_state_t state = {frames, max, 0};
_Unwind_Backtrace(unwind_callback, &state);
return state.count;
}
static void write_native_report(const char *signal_name) {
if (g_report_dir[0] == '\0') {
return;
}
char path[REPORT_DIR_MAX + 64];
snprintf(path, sizeof(path), "%s/native_%d_%ld.txt",
g_report_dir, (int) getpid(), (long) time(NULL));
FILE *f = fopen(path, "w");
if (!f) {
return;
}
fprintf(f, "signal=%s\n", signal_name);
fprintf(f, "pid=%d\n", (int) getpid());
void *frames[MAX_FRAMES];
int n = capture_backtrace(frames, MAX_FRAMES);
for (int i = 0; i < n; i++) {
Dl_info info;
memset(&info, 0, sizeof(info));
if (dladdr(frames[i], &info) && info.dli_fname) {
uintptr_t offset = (uintptr_t) frames[i] - (uintptr_t) info.dli_fbase;
fprintf(f, "#%02d %p %s", i, frames[i], info.dli_fname);
if (info.dli_sname) {
fprintf(f, " %s+0x%lx", info.dli_sname,
(unsigned long) ((uintptr_t) frames[i] - (uintptr_t) info.dli_saddr));
} else {
fprintf(f, "+0x%lx", (unsigned long) offset);
}
fprintf(f, "\n");
} else {
fprintf(f, "#%02d %p\n", i, frames[i]);
}
}
fclose(f);
}
static void crash_signal_handler(int sig) {
if (g_handling) {
_exit(128 + sig);
}
g_handling = 1;
const char *name = "UNKNOWN";
switch (sig) {
case SIGSEGV: name = "SIGSEGV"; break;
case SIGABRT: name = "SIGABRT"; break;
case SIGBUS: name = "SIGBUS"; break;
case SIGFPE: name = "SIGFPE"; break;
case SIGILL: name = "SIGILL"; break;
}
write_native_report(name);
signal(sig, SIG_DFL);
raise(sig);
}
static void install_handlers(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = crash_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
sigaction(SIGILL, &sa, NULL);
}
JNIEXPORT void JNICALL
Java_com_foxx_androidcast_crash_CrashNativeBridge_nativeInstallCrashHook(
JNIEnv *env, jclass clazz, jstring reportDir) {
(void) clazz;
const char *dir = (*env)->GetStringUTFChars(env, reportDir, NULL);
if (!dir) {
return;
}
strncpy(g_report_dir, dir, REPORT_DIR_MAX - 1);
g_report_dir[REPORT_DIR_MAX - 1] = '\0';
(*env)->ReleaseStringUTFChars(env, reportDir, dir);
install_handlers();
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "native crash hook installed: %s", g_report_dir);
}

103
ndk/jni/entitlements_jni.c Normal file
View File

@@ -0,0 +1,103 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include "obfuscate.h"
#include "obfuscate_file.h"
static jint throw_illegal_state(JNIEnv *env, const char *msg)
{
jclass cls = (*env)->FindClass(env, "java/lang/IllegalStateException");
if (cls != NULL)
(*env)->ThrowNew(env, cls, msg);
return OBF_ERR_NULL;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeReadFrom(
JNIEnv *env, jclass clazz, jstring path, jlong key, jlongArray out_val)
{
const char *cpath;
uint64_t decoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (path == NULL || out_val == NULL)
return throw_illegal_state(env, "path or out_val is null");
cpath = (*env)->GetStringUTFChars(env, path, NULL);
if (cpath == NULL)
return OBF_ERR_IO;
rv = read_from(cpath, (uint64_t)key, &decoded);
(*env)->ReleaseStringUTFChars(env, path, cpath);
if (rv != OBF_OK)
return rv;
slot = (jlong)decoded;
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
return OBF_OK;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeWriteTo(
JNIEnv *env, jclass clazz, jstring path, jlong val, jlong key)
{
const char *cpath;
int32_t rv;
(void)clazz;
if (path == NULL)
return throw_illegal_state(env, "path is null");
cpath = (*env)->GetStringUTFChars(env, path, NULL);
if (cpath == NULL)
return OBF_ERR_IO;
rv = write_to(cpath, (uint64_t)val, (uint64_t)key);
(*env)->ReleaseStringUTFChars(env, path, cpath);
return rv;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeDecode(
JNIEnv *env, jclass clazz, jlong encoded_val, jlong key, jlongArray out_val)
{
uint64_t decoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (out_val == NULL)
return throw_illegal_state(env, "out_val is null");
rv = decode((uint64_t)encoded_val, (uint64_t)key, &decoded);
if (rv != OBF_OK)
return rv;
slot = (jlong)decoded;
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
return OBF_OK;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeEncode(
JNIEnv *env, jclass clazz, jlong val, jlong key, jlongArray out_encoded)
{
uint64_t encoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (out_encoded == NULL)
return throw_illegal_state(env, "out_encoded is null");
rv = encode((uint64_t)val, (uint64_t)key, &encoded);
if (rv != OBF_OK)
return rv;
slot = (jlong)encoded;
(*env)->SetLongArrayRegion(env, out_encoded, 0, 1, &slot);
return OBF_OK;
}

436
ndk/jni/libvpx_bridge.c Normal file
View File

@@ -0,0 +1,436 @@
/* package ndk/jni/libvpx_bridge.c */
/*********************************************************************
* libvpx_bridge.c
* Created at: Sun 17 May 2026 22:49:49 +0200
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
* Contributors:
* - Anton Afanasyeu <a.afanasieff@gmail.com> (6 commits, 432 lines)
* - Cursor Agent (project assistant)
* Digest: SHA256 3bdffcead822ea7b00582f416ffe18d3f885f8b91ddab3e87752c23dcf326a02
**********************************************************************/
#include <jni.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <stdlib.h>
#include <string.h>
#ifndef ANDROIDCAST_HAVE_LIBVPX
#define ANDROIDCAST_HAVE_LIBVPX 0
#endif
#if ANDROIDCAST_HAVE_LIBVPX
#include <vpx/vpx_encoder.h>
#include <vpx/vp8cx.h>
#include <vpx/vpx_decoder.h>
#include <vpx/vp8dx.h>
#endif
#define VPX_ENC_MAGIC 0x56505845L /* VPXE */
#define VPX_DEC_MAGIC 0x56505844L /* VPXD */
#if ANDROIDCAST_HAVE_LIBVPX
typedef struct {
unsigned long magic;
vpx_codec_ctx_t codec;
vpx_image_t img;
int width;
int height;
int frame_count;
int force_keyframe;
} VpxEncoderCtx;
static vpx_codec_iface_t *pick_encoder_iface(const char *mime) {
if (mime != NULL && strstr(mime, "VP9") != NULL) {
return vpx_codec_vp9_cx();
}
return vpx_codec_vp8_cx();
}
static vpx_codec_iface_t *pick_decoder_iface(const char *mime) {
if (mime != NULL && strstr(mime, "VP9") != NULL) {
return vpx_codec_vp9_dx();
}
return vpx_codec_vp8_dx();
}
static vpx_img_fmt_t img_fmt(void) {
return VPX_IMG_FMT_I420;
}
static void copy_i420_to_image(const uint8_t *yuv, vpx_image_t *img, int width, int height) {
int y_stride = img->stride[VPX_PLANE_Y];
int uv_stride = img->stride[VPX_PLANE_U];
const uint8_t *src_y = yuv;
const uint8_t *src_u = yuv + width * height;
const uint8_t *src_v = src_u + (width / 2) * (height / 2);
for (int row = 0; row < height; row++) {
memcpy(img->planes[VPX_PLANE_Y] + row * y_stride, src_y + row * width, (size_t) width);
}
int chroma_h = height / 2;
int chroma_w = width / 2;
for (int row = 0; row < chroma_h; row++) {
memcpy(img->planes[VPX_PLANE_U] + row * uv_stride, src_u + row * chroma_w, (size_t) chroma_w);
memcpy(img->planes[VPX_PLANE_V] + row * uv_stride, src_v + row * chroma_w, (size_t) chroma_w);
}
}
static VpxEncoderCtx *enc_from_handle(jlong handle) {
VpxEncoderCtx *ctx = (VpxEncoderCtx *) (intptr_t) handle;
if (ctx == NULL || ctx->magic != VPX_ENC_MAGIC) {
return NULL;
}
return ctx;
}
typedef struct {
unsigned long magic;
vpx_codec_ctx_t codec;
} VpxDecoderCtx;
static VpxDecoderCtx *dec_from_handle(jlong handle) {
VpxDecoderCtx *ctx = (VpxDecoderCtx *) (intptr_t) handle;
if (ctx == NULL || ctx->magic != VPX_DEC_MAGIC) {
return NULL;
}
return ctx;
}
static void render_i420_to_window(ANativeWindow *window, const vpx_image_t *img) {
if (window == NULL || img == NULL) {
return;
}
const int width = (int) img->d_w;
const int height = (int) img->d_h;
if (width <= 0 || height <= 0) {
return;
}
ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_RGBA_8888);
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, NULL) != 0) {
return;
}
uint8_t *dst = (uint8_t *) buffer.bits;
const int dst_stride = buffer.stride * 4;
const uint8_t *y_plane = img->planes[VPX_PLANE_Y];
const uint8_t *u_plane = img->planes[VPX_PLANE_U];
const uint8_t *v_plane = img->planes[VPX_PLANE_V];
const int y_stride = img->stride[VPX_PLANE_Y];
const int u_stride = img->stride[VPX_PLANE_U];
const int v_stride = img->stride[VPX_PLANE_V];
for (int row = 0; row < height; row++) {
uint8_t *row_dst = dst + row * dst_stride;
for (int col = 0; col < width; col++) {
int y = y_plane[row * y_stride + col];
int u = u_plane[(row / 2) * u_stride + (col / 2)] - 128;
int v = v_plane[(row / 2) * v_stride + (col / 2)] - 128;
int r = y + ((351 * v) >> 8);
int g = y - ((179 * v + 86 * u) >> 8);
int b = y + ((443 * u) >> 8);
if (r < 0) {
r = 0;
} else if (r > 255) {
r = 255;
}
if (g < 0) {
g = 0;
} else if (g > 255) {
g = 255;
}
if (b < 0) {
b = 0;
} else if (b > 255) {
b = 255;
}
row_dst[col * 4] = (uint8_t) r;
row_dst[col * 4 + 1] = (uint8_t) g;
row_dst[col * 4 + 2] = (uint8_t) b;
row_dst[col * 4 + 3] = 255;
}
}
ANativeWindow_unlockAndPost(window);
}
#endif /* ANDROIDCAST_HAVE_LIBVPX */
JNIEXPORT jboolean JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeIsLibvpxAvailable(
JNIEnv *env, jclass clazz) {
(void) env;
(void) clazz;
return ANDROIDCAST_HAVE_LIBVPX ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jlong JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncoderCreate(
JNIEnv *env, jclass clazz, jstring mime, jint width, jint height, jint bitrateKbps) {
(void) clazz;
#if !ANDROIDCAST_HAVE_LIBVPX
(void) env;
(void) mime;
(void) width;
(void) height;
(void) bitrateKbps;
return 0L;
#else
if (width <= 0 || height <= 0 || bitrateKbps <= 0) {
return 0L;
}
const char *mime_utf = (*env)->GetStringUTFChars(env, mime, NULL);
VpxEncoderCtx *ctx = (VpxEncoderCtx *) calloc(1, sizeof(VpxEncoderCtx));
if (ctx == NULL) {
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
ctx->magic = VPX_ENC_MAGIC;
ctx->width = width;
ctx->height = height;
vpx_codec_enc_cfg_t cfg;
vpx_codec_iface_t *iface = pick_encoder_iface(mime_utf);
if (vpx_codec_enc_config_default(iface, &cfg, 0) != VPX_CODEC_OK) {
free(ctx);
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
cfg.g_w = (unsigned int) width;
cfg.g_h = (unsigned int) height;
cfg.rc_target_bitrate = bitrateKbps;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = 30;
cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
cfg.g_lag_in_frames = 0;
if (vpx_codec_enc_init(&ctx->codec, iface, &cfg, 0) != VPX_CODEC_OK) {
free(ctx);
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
/* VP8 and VP9 both use VP8E_SET_CPUUSED (see libvpx tests / vp9_cx_iface.c). */
vpx_codec_control(&ctx->codec, VP8E_SET_CPUUSED, 8);
if (!vpx_img_alloc(&ctx->img, img_fmt(), width, height, 1)) {
vpx_codec_destroy(&ctx->codec);
free(ctx);
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return (jlong) (intptr_t) ctx;
#endif
}
JNIEXPORT jobject JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuvFrame(
JNIEnv *env, jclass clazz, jlong handle, jbyteArray yuv, jlong ptsUs, jboolean forceKeyframe) {
(void) clazz;
#if !ANDROIDCAST_HAVE_LIBVPX
(void) env;
(void) handle;
(void) yuv;
(void) ptsUs;
(void) forceKeyframe;
return NULL;
#else
VpxEncoderCtx *ctx = enc_from_handle(handle);
if (ctx == NULL || yuv == NULL) {
return NULL;
}
jsize yuv_len = (*env)->GetArrayLength(env, yuv);
int need = ctx->width * ctx->height * 3 / 2;
if (yuv_len < need) {
return NULL;
}
jbyte *yuv_bytes = (*env)->GetByteArrayElements(env, yuv, NULL);
if (yuv_bytes == NULL) {
return NULL;
}
copy_i420_to_image((const uint8_t *) yuv_bytes, &ctx->img, ctx->width, ctx->height);
(*env)->ReleaseByteArrayElements(env, yuv, yuv_bytes, JNI_ABORT);
vpx_enc_frame_flags_t flags = 0;
if (forceKeyframe || ctx->force_keyframe) {
flags |= VPX_EFLAG_FORCE_KF;
ctx->force_keyframe = 0;
}
if (vpx_codec_encode(&ctx->codec, &ctx->img, (vpx_codec_pts_t) ptsUs, 1, flags,
VPX_DL_REALTIME) != VPX_CODEC_OK) {
return NULL;
}
const vpx_codec_cx_pkt_t *pkt;
vpx_codec_iter_t iter = NULL;
while ((pkt = vpx_codec_get_cx_data(&ctx->codec, &iter)) != NULL) {
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
jbyteArray out = (*env)->NewByteArray(env, (jsize) pkt->data.frame.sz);
if (out == NULL) {
return NULL;
}
(*env)->SetByteArrayRegion(env, out, 0, (jsize) pkt->data.frame.sz,
(const jbyte *) pkt->data.frame.buf);
jboolean key = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? JNI_TRUE : JNI_FALSE;
jclass frame_cls = (*env)->FindClass(
env, "com/foxx/androidcast/media/codec/jni/VpxEncodedFrame");
jmethodID ctor = (*env)->GetMethodID(env, frame_cls, "<init>", "([BZ)V");
jobject frame = (*env)->NewObject(env, frame_cls, ctor, out, key);
ctx->frame_count++;
return frame;
}
}
return NULL;
#endif
}
JNIEXPORT jbyteArray JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuv(
JNIEnv *env, jclass clazz, jlong handle, jbyteArray yuv, jlong ptsUs, jboolean forceKeyframe) {
#if !ANDROIDCAST_HAVE_LIBVPX
(void) env;
(void) clazz;
(void) handle;
(void) yuv;
(void) ptsUs;
(void) forceKeyframe;
return NULL;
#else
jobject frame = Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuvFrame(
env, clazz, handle, yuv, ptsUs, forceKeyframe);
if (frame == NULL) {
return NULL;
}
jclass frame_cls = (*env)->GetObjectClass(env, frame);
jfieldID data_f = (*env)->GetFieldID(env, frame_cls, "data", "[B");
return (jbyteArray) (*env)->GetObjectField(env, frame, data_f);
#endif
}
JNIEXPORT void JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncoderRelease(
JNIEnv *env, jclass clazz, jlong handle) {
(void) env;
(void) clazz;
#if ANDROIDCAST_HAVE_LIBVPX
VpxEncoderCtx *ctx = enc_from_handle(handle);
if (ctx == NULL) {
return;
}
ctx->magic = 0;
vpx_img_free(&ctx->img);
vpx_codec_destroy(&ctx->codec);
free(ctx);
#endif
}
JNIEXPORT void JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxRequestKeyframe(
JNIEnv *env, jclass clazz, jlong handle) {
(void) env;
(void) clazz;
#if ANDROIDCAST_HAVE_LIBVPX
VpxEncoderCtx *ctx = enc_from_handle(handle);
if (ctx != NULL) {
ctx->force_keyframe = 1;
}
#endif
}
JNIEXPORT jlong JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxDecoderCreate(
JNIEnv *env, jclass clazz, jstring mime) {
(void) clazz;
#if !ANDROIDCAST_HAVE_LIBVPX
(void) env;
(void) mime;
return 0L;
#else
const char *mime_utf = (*env)->GetStringUTFChars(env, mime, NULL);
VpxDecoderCtx *ctx = (VpxDecoderCtx *) calloc(1, sizeof(VpxDecoderCtx));
if (ctx == NULL) {
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
ctx->magic = VPX_DEC_MAGIC;
vpx_codec_iface_t *iface = pick_decoder_iface(mime_utf);
vpx_codec_dec_cfg_t cfg = {0};
cfg.threads = 1;
/* vpxdec.c: dec_flags |= VPX_CODEC_USE_ERROR_CONCEALMENT when --error-concealment */
int dec_flags = 0;
if (vpx_codec_get_caps(iface) & VPX_CODEC_CAP_ERROR_CONCEALMENT) {
dec_flags |= VPX_CODEC_USE_ERROR_CONCEALMENT;
}
if (vpx_codec_dec_init(&ctx->codec, iface, &cfg, dec_flags) != VPX_CODEC_OK) {
free(ctx);
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return 0L;
}
(*env)->ReleaseStringUTFChars(env, mime, mime_utf);
return (jlong) (intptr_t) ctx;
#endif
}
JNIEXPORT jboolean JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxDecodeFrame(
JNIEnv *env, jclass clazz, jlong handle, jbyteArray data, jlong ptsUs, jobject surface) {
(void) clazz;
(void) ptsUs;
#if !ANDROIDCAST_HAVE_LIBVPX
(void) env;
(void) handle;
(void) data;
(void) surface;
return JNI_FALSE;
#else
VpxDecoderCtx *ctx = dec_from_handle(handle);
if (ctx == NULL || data == NULL || surface == NULL) {
return JNI_FALSE;
}
jsize len = (*env)->GetArrayLength(env, data);
if (len <= 0) {
return JNI_FALSE;
}
jbyte *bytes = (*env)->GetByteArrayElements(env, data, NULL);
if (bytes == NULL) {
return JNI_FALSE;
}
vpx_codec_err_t err = vpx_codec_decode(&ctx->codec, (const uint8_t *) bytes, (unsigned int) len,
NULL, 0);
(*env)->ReleaseByteArrayElements(env, data, bytes, JNI_ABORT);
/* With error concealment, vpx_codec_decode may warn yet still output a frame (see vpxdec.c). */
(void) err;
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
if (window == NULL) {
return JNI_FALSE;
}
vpx_codec_iter_t iter = NULL;
vpx_image_t *img;
jboolean rendered = JNI_FALSE;
while ((img = vpx_codec_get_frame(&ctx->codec, &iter)) != NULL) {
render_i420_to_window(window, img);
rendered = JNI_TRUE;
}
ANativeWindow_release(window);
return rendered;
#endif
}
JNIEXPORT void JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxDecoderRelease(
JNIEnv *env, jclass clazz, jlong handle) {
(void) env;
(void) clazz;
#if ANDROIDCAST_HAVE_LIBVPX
VpxDecoderCtx *ctx = dec_from_handle(handle);
if (ctx == NULL) {
return;
}
ctx->magic = 0;
vpx_codec_destroy(&ctx->codec);
free(ctx);
#endif
}

25
ndk/jni/opus_bridge.c Normal file
View File

@@ -0,0 +1,25 @@
/* package ndk/jni/opus_bridge.c */
/*********************************************************************
* opus_bridge.c
* Created at: Sun 17 May 2026 22:49:49 +0200
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
* Contributors:
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 21 lines)
* - Cursor Agent (project assistant)
* Digest: SHA256 401e8483138f40becb1bcd3bfb46693c651c44dbc6f5d85cbcc645df93b51bfb
**********************************************************************/
#include <jni.h>
#ifndef ANDROIDCAST_HAVE_OPUS
#define ANDROIDCAST_HAVE_OPUS 0
#endif
JNIEXPORT jboolean JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeIsOpusAvailable(
JNIEnv *env, jclass clazz) {
(void)env;
(void)clazz;
return ANDROIDCAST_HAVE_OPUS ? JNI_TRUE : JNI_FALSE;
}

25
ndk/jni/speex_bridge.c Normal file
View File

@@ -0,0 +1,25 @@
/* package ndk/jni/speex_bridge.c */
/*********************************************************************
* speex_bridge.c
* Created at: Sun 17 May 2026 22:49:49 +0200
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
* Contributors:
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 21 lines)
* - Cursor Agent (project assistant)
* Digest: SHA256 5085e4a255dcad08ac2eb746ad1d1f64408a191f035d9a6ca83dab896b3e9a09
**********************************************************************/
#include <jni.h>
#ifndef ANDROIDCAST_HAVE_SPEEX
#define ANDROIDCAST_HAVE_SPEEX 0
#endif
JNIEXPORT jboolean JNICALL
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeIsSpeexAvailable(
JNIEnv *env, jclass clazz) {
(void)env;
(void)clazz;
return ANDROIDCAST_HAVE_SPEEX ? JNI_TRUE : JNI_FALSE;
}