mirror of
git://f0xx.org/android_cast
synced 2026-07-29 04:18:09 +03:00
libvpx integration
This commit is contained in:
@@ -46,4 +46,5 @@ else()
|
||||
endif()
|
||||
|
||||
find_library(log-lib log)
|
||||
target_link_libraries(androidcast_codecs ${log-lib})
|
||||
find_library(android-lib android)
|
||||
target_link_libraries(androidcast_codecs ${log-lib} ${android-lib})
|
||||
|
||||
@@ -8,13 +8,16 @@ The app loads `libandroidcast_codecs.so` for availability probes and future soft
|
||||
|-----------|------|--------|-------|
|
||||
| FEC 3/4 + RS | Yes | — | v2 **per-shard** on wire (`FecShardWire`); v1 monolithic decode still accepted |
|
||||
| NACK + retransmit cache | Yes | — | UDP `MSG_NACK`, combined with FEC when enabled |
|
||||
| libvpx VP8/VP9 | Stub | Probe only | HW passthrough until `ANDROIDCAST_LIBVPX_LIB` is linked |
|
||||
| libvpx VP8/VP9 | Native | Encode + decode | Screen cast via ImageReader; receiver libvpx or MediaCodec fallback |
|
||||
| Opus / Speex | Stub | Probe only | Submodules under `third-party/` |
|
||||
|
||||
## Enable libvpx (developer)
|
||||
|
||||
1. `git submodule update --init third-party/libvpx`
|
||||
2. Build `libvpx.a` for your ABI: `./scripts/build-native-codecs.sh arm64-v8a`
|
||||
2. Build `libvpx.a` per ABI (NDK is auto-detected; see `scripts/android-ndk.sh`):
|
||||
|
||||
`./scripts/build-native-codecs.sh arm64-v8a`
|
||||
|
||||
(If the repo path has **spaces**, the script uses `/tmp/androidcast-libvpx-$USER` automatically.)
|
||||
3. Build **each ABI** you ship (Gradle links `build/native/<abi>/libvpx.a` automatically):
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <jni.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -14,6 +16,7 @@
|
||||
#endif
|
||||
|
||||
#define VPX_ENC_MAGIC 0x56505845L /* VPXE */
|
||||
#define VPX_DEC_MAGIC 0x56505844L /* VPXD */
|
||||
|
||||
#if ANDROIDCAST_HAVE_LIBVPX
|
||||
|
||||
@@ -34,6 +37,13 @@ static vpx_codec_iface_t *pick_encoder_iface(const char *mime) {
|
||||
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;
|
||||
}
|
||||
@@ -63,6 +73,75 @@ static VpxEncoderCtx *enc_from_handle(jlong handle) {
|
||||
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
|
||||
@@ -132,8 +211,8 @@ Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncoderCrea
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuv(
|
||||
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
|
||||
@@ -167,7 +246,7 @@ Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuv(
|
||||
}
|
||||
|
||||
if (vpx_codec_encode(&ctx->codec, &ctx->img, (vpx_codec_pts_t) ptsUs, 1, flags,
|
||||
VPX_DL_REALTIME) != VPX_CODEC_OK) {
|
||||
VPX_DL_REALTIME) != VPX_CODEC_OK) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -176,18 +255,47 @@ Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxEncodeYuv(
|
||||
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) {
|
||||
(*env)->SetByteArrayRegion(env, out, 0, (jsize) pkt->data.frame.sz,
|
||||
(const jbyte *) pkt->data.frame.buf);
|
||||
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 out;
|
||||
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) {
|
||||
@@ -217,3 +325,95 @@ Java_com_foxx_androidcast_media_codec_jni_NativeCodecBridge_nativeVpxRequestKeyf
|
||||
}
|
||||
#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_dec_cfg_t cfg = {0};
|
||||
cfg.threads = 1;
|
||||
if (vpx_codec_dec_init(&ctx->codec, pick_decoder_iface(mime_utf), &cfg, 0) != 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);
|
||||
if (err != VPX_CODEC_OK) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user