You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

NDK下C代码直接创建Bitmap优化I420视频帧转换性能求助

直接在NDK中处理I420帧并渲染/创建Bitmap的优化方案

针对你在视频通话场景中遇到的「I420转NV21再转Bitmap带来的延迟和画质损失」问题,这里有两个更高效的原生层解决方案,完全跳过Java层的中间转换步骤:

方案一:直接通过ANativeWindow关联Surface渲染(推荐用于实时视频)

这是延迟最低的方案,因为我们直接把I420帧数据渲染到Android的Surface上,不需要创建Bitmap,完全绕开Java层的转换开销。

核心步骤:

  1. 在Java层传递Surface到JNI:把视频渲染用的Surface(比如TextureView的Surface、SurfaceView的Surface)通过JNI传递到C代码中。
  2. 获取ANativeWindow:用ANativeWindow_fromSurface(env, surface)把Java的Surface转换成原生层的ANativeWindow对象。
  3. 配置窗口格式:检查Surface支持的YUV格式(优先选I420,如果不支持再转NV21),用ANativeWindow_setBuffersGeometry设置宽高和像素格式。
  4. 锁定缓冲区并拷贝数据
    • 调用ANativeWindow_lock获取窗口的像素缓冲区。
    • 在C层直接完成I420到目标格式(比如NV21)的转换,把数据拷贝到缓冲区的对应区域。这里可以用NEON指令集优化转换逻辑,比Java层快很多。
    • 调用ANativeWindow_unlockAndPost提交缓冲区完成渲染。

简化代码示例:

#include <android/native_window_jni.h>

JNIEXPORT void JNICALL
Java_com_your_package_VideoRenderer_renderI420Frame(JNIEnv *env, jobject thiz, jobject surface,
                                                    jbyteArray i420_data, jint width, jint height) {
    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
    if (!window) {
        return;
    }

    // 设置缓冲区格式为NV21(如果Surface支持I420可以直接用WINDOW_FORMAT_YV12)
    ANativeWindow_setBuffersGeometry(window, width, height, WINDOW_FORMAT_NV21);

    ANativeWindow_Buffer buffer;
    if (ANativeWindow_lock(window, &buffer, NULL) < 0) {
        ANativeWindow_release(window);
        return;
    }

    // 获取I420数据指针
    jbyte* i420_ptr = env->GetByteArrayElements(i420_data, NULL);
    if (!i420_ptr) {
        ANativeWindow_unlockAndPost(window);
        ANativeWindow_release(window);
        return;
    }

    // 这里实现I420到NV21的转换,直接写入buffer.bits
    // 建议用NEON优化转换函数,比如手写汇编或用编译器自动优化
    convertI420ToNV21(i420_ptr, (uint8_t*)buffer.bits, width, height);

    // 释放资源
    env->ReleaseByteArrayElements(i420_data, i420_ptr, 0);
    ANativeWindow_unlockAndPost(window);
    ANativeWindow_release(window);
}

方案二:在C层直接创建Bitmap(适合需要Bitmap做后续处理的场景)

如果你的业务逻辑必须要Bitmap对象(比如截图、保存到相册),可以直接在JNI层创建Bitmap并填充像素数据,避免Java层的转换开销。

核心步骤:

  1. 创建Bitmap对象:用AndroidBitmap_createBitmap在JNI层直接创建ARGB格式的Bitmap。
  2. 锁定Bitmap像素缓冲区:调用AndroidBitmap_lockPixels获取像素指针。
  3. I420转ARGB并填充:在C层完成I420到ARGB的颜色空间转换,直接把数据写入Bitmap的像素缓冲区。同样可以用NEON优化转换逻辑。
  4. 解锁并返回Bitmap:调用AndroidBitmap_unlockPixels,然后把Bitmap对象返回给Java层。

简化代码示例:

#include <android/bitmap.h>

JNIEXPORT jobject JNICALL
Java_com_your_package_VideoConverter_i420ToBitmap(JNIEnv *env, jobject thiz, jbyteArray i420_data,
                                                  jint width, jint height) {
    jobject bitmap = NULL;
    // 创建ARGB_8888格式的Bitmap
    int result = AndroidBitmap_createBitmap(env, width, height, ANDROID_BITMAP_FORMAT_RGBA_8888, &bitmap);
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        return NULL;
    }

    AndroidBitmapInfo info;
    void* pixels;
    result = AndroidBitmap_getInfo(env, bitmap, &info);
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        env->DeleteLocalRef(bitmap);
        return NULL;
    }

    result = AndroidBitmap_lockPixels(env, bitmap, &pixels);
    if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
        env->DeleteLocalRef(bitmap);
        return NULL;
    }

    // 获取I420数据指针
    jbyte* i420_ptr = env->GetByteArrayElements(i420_data, NULL);
    if (i420_ptr) {
        // 实现I420到ARGB_8888的转换,写入pixels
        convertI420ToARGB(i420_ptr, (uint8_t*)pixels, width, height);
        env->ReleaseByteArrayElements(i420_data, i420_ptr, 0);
    }

    AndroidBitmap_unlockPixels(env, bitmap);
    return bitmap;
}

关键优化建议

  • 优先用ANativeWindow渲染:实时视频场景下,Surface渲染的延迟远低于Bitmap方案,因为它直接利用硬件合成,没有Bitmap的内存拷贝和颜色转换开销。
  • 优化YUV转换逻辑:用NEON指令集优化I420到NV21/ARGB的转换函数,能大幅提升转换速度,减少CPU占用。
  • 复用缓冲区:尽量避免频繁创建ANativeWindow或Bitmap,复用已有的缓冲区对象,减少内存分配和回收的开销。
  • 匹配格式减少转换:提前检查Surface支持的YUV格式,如果支持I420(WINDOW_FORMAT_YV12),可以直接渲染I420数据,完全跳过格式转换步骤。

内容的提问来源于stack exchange,提问作者rosu alin

火山引擎 最新活动