You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

横屏模式下YUV转Bitmap异常问题排查求助

问题分析与解决方案

核心问题定位

从你的场景和错误日志来看,横屏(含反向横屏)时出现的java.lang.IllegalStateException: buffer is inaccessible异常,本质是YUV数据转换时的内存访问越界,而且这个问题和Camera2图像输出与Activity方向不匹配直接相关。

竖屏时相机输出的图像尺寸和Activity显示方向一致,转换逻辑能正常工作;但横屏时,相机传感器的原生输出方向和Activity方向存在90度旋转,导致你计算的图像宽高、buffer尺寸和实际数据不匹配,最终触发了不可访问的buffer异常。

至于你遇到的“重启/加日志后临时正常”的现象,这只是内存分配的偶然侥幸——重启后系统内存充足,日志输出改变了线程执行时机,刚好让buffer分配尺寸覆盖了越界访问,但这不是真正的修复,一旦内存状态变化问题就会复发。


具体修复步骤

1. 正确处理图像方向与宽高匹配

横屏时,相机输出的图像方向和Activity显示方向存在旋转差,不能直接用image.getWidth()/image.getHeight()来创建Bitmap,需要根据设备旋转角度和相机传感器方向计算正确的宽高:

// 在onImageAvailable中获取正确的宽高
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
    case Surface.ROTATION_0: degrees = 0; break;
    case Surface.ROTATION_90: degrees = 90; break;
    case Surface.ROTATION_180: degrees = 180; break;
    case Surface.ROTATION_270: degrees = 270; break;
}

// 拿到相机传感器的原生方向(大部分竖屏手机为90度)
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
int totalRotation = (sensorOrientation - degrees + 360) % 360;

// 根据旋转角度调整宽高
int bitmapWidth = image.getWidth();
int bitmapHeight = image.getHeight();
if (totalRotation == 90 || totalRotation == 270) {
    bitmapWidth = image.getHeight();
    bitmapHeight = image.getWidth();
}

2. 修复YUV转ByteBuffer的内存访问问题

原代码直接操作DirectByteBuffer.array()是不安全的,部分设备或内存策略下会导致不可访问异常。改成用byte数组存储YUV数据,避免直接操作DirectBuffer的内部数组:

修改imageToByteBuffer方法:

private byte[] imageToByteBuffer(final Image image) {
    final Rect crop = image.getCropRect();
    final int width = crop.width();
    final int height = crop.height();
    final Image.Plane[] planes = image.getPlanes();
    final byte[] rowData = new byte[planes[0].getRowStride()];
    final int bufferSize = width * height * 3 / 2; // YUV_420_888的固定总尺寸
    final byte[] output = new byte[bufferSize];
    int channelOffset = 0;
    int outputStride = 0;

    for (int planeIndex = 0; planeIndex < 3; planeIndex++) {
        if (planeIndex == 0) {
            channelOffset = 0;
            outputStride = 1;
        } else if (planeIndex == 1) {
            channelOffset = width * height + 1;
            outputStride = 2;
        } else if (planeIndex == 2) {
            channelOffset = width * height;
            outputStride = 2;
        }

        final ByteBuffer buffer = planes[planeIndex].getBuffer();
        final int rowStride = planes[planeIndex].getRowStride();
        final int pixelStride = planes[planeIndex].getPixelStride();
        final int shift = (planeIndex == 0)? 0 : 1;
        final int widthShifted = width >> shift;
        final int heightShifted = height >> shift;

        buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));

        for (int row = 0; row < heightShifted; row++) {
            final int length;
            if (pixelStride == 1 && outputStride == 1) {
                length = widthShifted;
                buffer.get(output, channelOffset, length);
                channelOffset += length;
            } else {
                length = (widthShifted - 1) * pixelStride + 1;
                buffer.get(rowData, 0, length);
                for (int col = 0; col < widthShifted; col++) {
                    output[channelOffset] = rowData[col * pixelStride];
                    channelOffset += outputStride;
                }
            }

            if (row < heightShifted - 1) {
                buffer.position(buffer.position() + rowStride - length);
            }
        }
    }
    return output;
}

同步修改mediaImageToBitmap方法,传入正确的宽高并及时释放资源:

public Bitmap mediaImageToBitmap(Image image, Context context, int bitmapWidth, int bitmapHeight) {
    final byte[] yuvBytes = imageToByteBuffer(image);
    rs = RenderScript.create(context);
    Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
    allocationRgb = Allocation.createFromBitmap(rs, bitmap);
    allocationYuv = Allocation.createSized(rs, Element.U8(rs), yuvBytes.length);
    allocationYuv.copyFrom(yuvBytes);
    
    ScriptIntrinsicYuvToRGB scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
    scriptYuvToRgb.setInput(allocationYuv);
    scriptYuvToRgb.forEach(allocationRgb);
    allocationRgb.copyTo(bitmap);
    
    // 必须释放RenderScript资源,避免内存泄漏
    release();
    return bitmap;
}

3. 强制释放Camera Image资源

原代码中没有及时关闭Image对象,会导致Camera的buffer池耗尽,引发各种隐性异常。一定要在处理完图像后调用image.close():

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        final Image image = reader.acquireLatestImage();
        if (image == null) return;
        try {
            BitmapFunctions bitmapFunctions = new BitmapFunctions();
            // 这里插入前面计算宽高的代码...
            Bitmap bitmap = bitmapFunctions.mediaImageToBitmap(image, activity, bitmapWidth, bitmapHeight);
            // 处理bitmap(保存/识别等逻辑)
        } finally {
            // 必须关闭Image,释放Camera buffer
            image.close();
        }
    }
};

内容的提问来源于stack exchange,提问作者Chung Yau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:41:45