无需FFmpeg和OpenCV,NDK是否有可提取视频帧的原生API?
NDK原生视频帧提取及色彩调整实现方案
API支持说明
NDK原生提供了无第三方依赖的视频处理能力,你的需求完全可以实现:
- 帧提取可使用两类原生API:
AMediaMetadataRetriever:API 24及以上支持,对应Java层MediaMetadataRetriever的原生实现,适合按时间戳取指定帧、简单逐帧提取场景AMediaExtractor+AMediaCodec组合:API 21及以上支持,通过硬解码实现全量逐帧提取,性能更高、可控性更强
- 色彩调整可直接操作提取到的帧内存数据完成,无需额外依赖库
编译配置
需要在CMakeLists.txt中链接NDK原生媒体库:
find_library(media-lib mediandk) find_library(bitmap-lib jnigraphics) target_link_libraries( 你的原生模块名 ${media-lib} ${bitmap-lib} )
代码示例
1. 按时间戳提取视频帧
#include <jni.h> #include <media/NdkMediaMetadataRetriever.h> #include <android/bitmap.h> #include <algorithm> extern "C" JNIEXPORT jobject JNICALL Java_com_your_package_VideoUtils_getFrameAtTime(JNIEnv *env, jobject thiz, jstring video_path, jlong time_us) { const char *path = env->GetStringUTFChars(video_path, nullptr); AMediaMetadataRetriever *retriever = AMediaMetadataRetriever_create(); // 设置数据源 media_status_t status = AMediaMetadataRetriever_setDataSource(retriever, path, nullptr); if (status != AMEDIA_OK) { AMediaMetadataRetriever_delete(retriever); env->ReleaseStringUTFChars(video_path, path); return nullptr; } AImage *image = nullptr; // 取帧模式:1=获取最近的帧(含非关键帧),0=最近关键帧,2=下一关键帧 status = AMediaMetadataRetriever_getFrameAtTime(retriever, time_us, 1, &image); if (status != AMEDIA_OK || !image) { AMediaMetadataRetriever_delete(retriever); env->ReleaseStringUTFChars(video_path, path); return nullptr; } // 读取图像参数 int32_t width, height; AImage_getWidth(image, &width); AImage_getHeight(image, &height); // 创建RGBA格式Bitmap AndroidBitmapInfo bitmap_info{}; bitmap_info.width = width; bitmap_info.height = height; bitmap_info.stride = width * 4; bitmap_info.format = ANDROID_BITMAP_FORMAT_RGBA_8888; jobject bitmap = nullptr; if (AndroidBitmap_createBitmap(env, &bitmap_info, nullptr, &bitmap) != ANDROID_BITMAP_RESULT_SUCCESS) { AImage_delete(image); AMediaMetadataRetriever_delete(retriever); env->ReleaseStringUTFChars(video_path, path); return nullptr; } // 拷贝帧数据到Bitmap void *bitmap_buf = nullptr; AndroidBitmap_lockPixels(env, bitmap, &bitmap_buf); uint8_t *image_buf = nullptr; int32_t row_stride = 0; AImage_lockImageBuffer(image, &image_buf, &row_stride); memcpy(bitmap_buf, image_buf, width * height * 4); AImage_unlockImageBuffer(image); AndroidBitmap_unlockPixels(env, bitmap); // 释放资源 AImage_delete(image); AMediaMetadataRetriever_delete(retriever); env->ReleaseStringUTFChars(video_path, path); return bitmap; }
2. 帧色彩调整示例
以下为亮度调整的示例代码,可直接扩展实现对比度、饱和度、色调等调整逻辑:
extern "C" JNIEXPORT void JNICALL Java_com_your_package_VideoUtils_adjustFrameBrightness(JNIEnv *env, jobject thiz, jobject bitmap, jint brightness) { AndroidBitmapInfo info{}; if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS || info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { return; } void *pixels = nullptr; AndroidBitmap_lockPixels(env, bitmap, &pixels); auto *buf = static_cast<uint8_t*>(pixels); int pixel_count = info.width * info.height; for (int i = 0; i < pixel_count; ++i) { int r = buf[i * 4]; int g = buf[i * 4 + 1]; int b = buf[i * 4 + 2]; // 限制数值范围在0-255之间 r = std::clamp(r + brightness, 0, 255); g = std::clamp(g + brightness, 0, 255); b = std::clamp(b + brightness, 0, 255); buf[i * 4] = r; buf[i * 4 + 1] = g; buf[i * 4 + 2] = b; } AndroidBitmap_unlockPixels(env, bitmap); }
额外说明
如果需要全量逐帧提取整个视频的所有帧,建议使用AMediaExtractor解封装+AMediaCodec硬解码的方案,性能远高于循环调用AMediaMetadataRetriever取帧,同样无第三方依赖。
内容的提问来源于stack exchange,提问作者Justin Wood
相关产品推荐
相关产品推荐

