使用CameraX时BitmapFactory.decodeByteArray返回null的问题
问题:CameraX捕获图像经JNI传递后BitmapFactory.decodeByteArray返回null
我正在开发基于Native C的简易Android应用,目标是通过CameraX捕获图像,经JNI传递给C处理后将结果显示在ImageView中。调试发现BitmapFactory.decodeByteArray(processedImageData, 0, processedImageData.length, options)始终返回null,processedImageData不为空且和原始imageData内容一致,直接用imageData调用该方法也返回null。
相关代码
CameraActivity.java
public class CameraActivity extends AppCompatActivity { // Used to load the 'ar' library on application startup. static { System.loadLibrary("ar"); } private ListenableFuture<ProcessCameraProvider> cameraProviderFuture; ProcessCameraProvider cameraProvider; CameraSelector cameraSelector; ImageView imageView; private void startImageAnalysis() { // Create an ImageAnalysis use case ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build(); // Create a HandlerThread to handle the image analysis on a separate thread HandlerThread analyzerThread = new HandlerThread("ImageAnalyzer"); analyzerThread.start(); Handler analyzerHandler = new Handler(analyzerThread.getLooper()); // Create an Executor using the Handler from the HandlerThread Executor analyzerExecutor = new Executor() { @Override public void execute(Runnable runnable) { analyzerHandler.post(runnable); } }; imageAnalysis.setAnalyzer(analyzerExecutor, image -> { // Get the image buffer as a ByteBuffer ByteBuffer buffer = image.getPlanes()[0].getBuffer(); int width = image.getWidth(); int height = image.getHeight(); // Convert the ByteBuffer to a byte[] array byte[] imageData = new byte[buffer.remaining()]; buffer.get(imageData); Log.d("MyApp", "Processing image"); // Call the JNI wrapper function with the buffer and its dimensions processImageWrapper(imageData, width, height); // Close the image to release the resources image.close(); }); // Bind the ImageAnalysis use case to the camera cameraProvider.bindToLifecycle(this, cameraSelector, imageAnalysis); } private void stopImageAnalysis() { // TODO: Unbind the ImageAnalysis use case from the camera } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); com.example.ar.databinding.ActivityCameraBinding binding = ActivityCameraBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setContentView(R.layout.activity_camera); imageView = findViewById(R.id.image_view); // Initialize the camera provider future cameraProviderFuture = ProcessCameraProvider.getInstance(this); // Wait for the camera provider to be available cameraProviderFuture.addListener(() -> { try { // Get the camera provider cameraProvider = cameraProviderFuture.get(); // Bind the camera to the preview bindCamera(cameraProvider); } catch (ExecutionException | InterruptedException e) { Log.e("CameraXActivity", "Error binding camera", e); } }, ContextCompat.getMainExecutor(this)); } private void bindCamera(@NonNull ProcessCameraProvider cameraProvider) { // Create an image capture use case and set it up ImageCapture imageCapture = new ImageCapture.Builder() .build(); // Select the back camera as the default cameraSelector = new CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build(); startImageAnalysis(); } // Declare the native function public native byte[] processImage(byte[] imageData, int width, int height); // Define the JNI wrapper function private void processImageWrapper(byte[] imageData, int width, int height) { // Call the native function with the buffer and its dimensions and receive the processed image byte[] processedImageData = processImage(imageData, width, height); // Create a BitmapFactory.Options object to specify the image format BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; // Set the format to ARGB_8888 // Convert the processed image byte array to a Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(processedImageData, 0, processedImageData.length, options); // Set the Bitmap to the ImageView to render it on the screen imageView.setImageBitmap(bitmap); } }
C++处理函数
extern "C" JNIEXPORT jbyteArray JNICALL Java_com_example_ar_CameraActivity_processImage( JNIEnv* env, jobject /* this */, jbyteArray image_data, jint width, jint height) { // Get a pointer to the input image data jbyte* input_image_data = env->GetByteArrayElements(image_data, NULL); // Check if the input image data is null if (input_image_data == NULL) { return NULL; } // Create a new byte array to hold the output image data jbyteArray output_image_data = env->NewByteArray(width * height * 4); // Check if the output image data is null if (output_image_data == NULL) { // Release the input image data env->ReleaseByteArrayElements(image_data, input_image_data, JNI_ABORT); return NULL; } // Copy the input image data to the output image data env->SetByteArrayRegion(output_image_data, 0, width * height * 4, input_image_data); // Release the input image data env->ReleaseByteArrayElements(image_data, input_image_data, JNI_ABORT); // Return the output image data return output_image_data; }
核心原因
- CameraX的ImageAnalysis默认输出YUV_420_888格式图像,
image.getPlanes()[0]仅获取到Y分量的原始字节数据,并非ARGB/RGB等Bitmap可识别的像素格式。 BitmapFactory.decodeByteArray只支持JPEG/PNG等标准压缩图像格式,或已转换为ARGB/RGB的原始像素数组,无法直接解码YUV字节流。
解决方案
方案1:先将YUV转换为ARGB像素数据
在Java端把CameraX输出的YUV图像转为ARGB格式的字节数组,再传给JNI处理:
imageAnalysis.setAnalyzer(analyzerExecutor, image -> { // 创建ARGB格式Bitmap Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); // 转换YUV到ARGB convertYuvToArgb(image, bitmap); // 将Bitmap转为字节数组 int byteCount = bitmap.getByteCount(); ByteBuffer buffer = ByteBuffer.allocate(byteCount); bitmap.copyPixelsToBuffer(buffer); byte[] imageData = buffer.array(); // 调用JNI处理 byte[] processedData = processImage(imageData, bitmap.getWidth(), bitmap.getHeight()); // 将处理后的字节数组转回Bitmap ByteBuffer processedBuffer = ByteBuffer.wrap(processedData); Bitmap processedBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); processedBitmap.copyPixelsFromBuffer(processedBuffer); imageView.setImageBitmap(processedBitmap); image.close(); }); // YUV转ARGB工具方法 private void convertYuvToArgb(Image image, Bitmap bitmap) { Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuffer = planes[0].getBuffer(); ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); int width = image.getWidth(); int height = image.getHeight(); int yRowStride = planes[0].getRowStride(); int uvRowStride = planes[1].getRowStride(); int uvPixelStride = planes[1].getPixelStride(); int[] pixels = new int[width * height]; int pixelIndex = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int yIndex = y * yRowStride + x; int uvIndex = uvRowStride * (y / 2) + uvPixelStride * (x / 2); int yValue = yBuffer.get(yIndex) & 0xFF; int uValue = uBuffer.get(uvIndex) & 0xFF; int vValue = vBuffer.get(uvIndex) & 0xFF; // YUV转RGB公式 int r = (int) (yValue + 1.370705 * (vValue - 128)); int g = (int) (yValue - 0.698001 * (vValue - 128) - 0.337633 * (uValue - 128)); int b = (int) (yValue + 1.732446 * (uValue - 128)); // 裁剪到0-255范围 r = Math.max(0, Math.min(255, r)); g = Math.max(0, Math.min(255, g)); b = Math.max(0, Math.min(255, b)); pixels[pixelIndex++] = 0xFF000000 | (r << 16) | (g << 8) | b; } } bitmap.setPixels(pixels, 0, width, 0, 0, width, height); }
方案2:直接操作Bitmap像素缓冲区(性能更优)
避免字节数组拷贝,直接将Bitmap的内存地址传给C++处理:
- 修改Java中的Native方法和调用逻辑:
// 声明Native方法 public native void processBitmap(Bitmap bitmap); private void processImageWrapper(Image image) { Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); convertYuvToArgb(image, bitmap); // 调用Native方法处理Bitmap processBitmap(bitmap); imageView.setImageBitmap(bitmap); image.close(); }
- 修改C++代码:
#include <android/bitmap.h> extern "C" JNIEXPORT void JNICALL Java_com_example_ar_CameraActivity_processBitmap( JNIEnv* env, jobject /* this */, jobject bitmap) { AndroidBitmapInfo bitmapInfo; void* pixelBuffer; // 获取Bitmap信息 if (AndroidBitmap_getInfo(env, bitmap, &bitmapInfo) != ANDROID_BITMAP_RESULT_SUCCESS) { return; } // 锁定Bitmap像素缓冲区 if (AndroidBitmap_lockPixels(env, bitmap, &pixelBuffer) != ANDROID_BITMAP_RESULT_SUCCESS) { return; } // 处理ARGB像素数据(示例:反转颜色) uint32_t* argbPixels = static_cast<uint32_t*>(pixelBuffer); int width = bitmapInfo.width; int height = bitmapInfo.height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int index = y * width + x; uint32_t pixel = argbPixels[index]; // 反转RGB通道 argbPixels[index] = 0xFF000000 | ((pixel & 0x000000FF) << 16) | (pixel & 0x0000FF00) | ((pixel & 0x00FF0000) >> 16); } } // 解锁像素缓冲区 AndroidBitmap_unlockPixels(env, bitmap); }
方案3:将YUV转为JPEG字节流
如果不需要复杂的像素级处理,可先把YUV转为JPEG格式,再传给JNI,处理后直接解码:
imageAnalysis.setAnalyzer(analyzerExecutor, image -> { // 将YUV转为JPEG字节流 YuvImage yuvImage = new YuvImage( getYuvByteArray(image), ImageFormat.NV21, image.getWidth(), image.getHeight(), null); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 90, outputStream); byte[] jpegData = outputStream.toByteArray(); // 调用JNI处理 byte[] processedJpeg = processImage(jpegData, image.getWidth(), image.getHeight()); // 解码JPEG为Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(processedJpeg, 0, processedJpeg.length); imageView.setImageBitmap(bitmap); image.close(); }); private byte[] getYuvByteArray(Image image) { Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuffer = planes[0].getBuffer(); ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); int ySize = yBuffer.remaining(); int uSize = uBuffer.remaining(); int vSize = vBuffer.remaining(); byte[] yuvBytes = new byte[ySize + uSize + vSize]; yBuffer.get(yuvBytes, 0, ySize); vBuffer.get(yuvBytes, ySize, vSize); uBuffer.get(yuvBytes, ySize + vSize, uSize); return yuvBytes; }
内容的提问来源于stack exchange,提问作者Ghiles_1010
相关产品推荐
相关产品推荐

