渲染到纹理的Android相机预览被拉伸

时间:2017-11-02 10:22:34

标签: java android unity3d camera

我正在使用纹理向Unity3d发送Android相机预览,并且它已被拉伸。预览不会渲染到android中的任何视图,而是直接渲染到纹理:

setPreviewTexture(texture);

然后,将字节发送到Unity3d并在每个onPreviewFrame的屏幕上绘制。

相机预览尺寸和纹理尺寸设置为1024x768,Unity3d中的纹理容器尺寸相同。该设备的分辨率为960x540,因此它的比例不同。我正在为相机选择支持的分辨率,原始尺寸的比例为4:3,因此不应该拉伸。 似乎android正在渲染纹理只能实际渲染到屏幕的部分 - 它需要16:9的图像并将其渲染为4:3纹理。 如果我错了,请纠正我,但在这个例子中它似乎如何工作。 这是一些代码:

public int startCamera(int idx, int width, int height) {
    nativeTexturePointer = createExternalTexture();
    texture = new SurfaceTexture(nativeTexturePointer);

    mCamera = Camera.open(idx);
    setupCamera(width, height);

    try {
        mCamera.setPreviewTexture(texture);
        mCamera.setPreviewCallback(this);
        mCamera.startPreview();
        Log.i("Unity", "JAVA: camera started");
    } catch (IOException ioe) {
        Log.w("Unity", "JAVA: CAM LAUNCH FAILED");
    }
    return nativeTexturePointer;
}

public void stopCamera() {
    mCamera.setPreviewCallback(null);
    mCamera.stopPreview();
    mCamera.release();
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
    Log.i("Unity", "JAVA: Camera stopped");
}

private int createExternalTexture() {
    int[] textureIdContainer = new int[1];
    GLES20.glGenTextures(1, textureIdContainer, 0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIdContainer[0]);
    return textureIdContainer[0];
}

@SuppressLint("NewApi")
private void setupCamera(int width, int height) {
    Camera.Parameters params = mCamera.getParameters();
    params.setRecordingHint(true);
    params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
    params.setPreviewFormat(17);
    params.setZoom(0);
        // 16 ~ NV16 ~ YCbCr
        // 17 ~ NV21 ~ YCbCr ~ DEFAULT *
        // 4  ~ RGB_565
        // 256~ JPEG
        // 20 ~ YUY2 ~ YcbCr ...
        // 842094169 ~ YV12 ~ 4:2:0 YCrCb comprised of WXH Y plane, W/2xH/2 Cr & Cb. see documentation *
    params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
    Camera.Size previewSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPreviewSizes());
    Camera.Size picSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPictureSizes());

    params.setPictureSize(picSize.width, picSize.height);
    params.setPreviewSize(previewSize.width, previewSize.height);
    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
    params.setExposureCompensation(0);

    try{
        mCamera.setParameters(params);
    } catch (Exception e){
        Log.i("Unity", "ERROR: " + e.getMessage());
    }

    Camera.Size mCameraPreviewSize = params.getPreviewSize();
    prevWidth = mCameraPreviewSize.width;
    prevHeight = mCameraPreviewSize.height;

    int[] fpsRange = new int[2];
    params.getPreviewFpsRange(fpsRange);
}

private Camera.Size getOptimalSize(int width, int height, List<Camera.Size> sizes) {
    if(mCamera == null)
        return null;

    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio=(double)width / height;

    if (sizes == null)
        return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;
    int targetWidth = width;

    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        Log.i("Unity", "RES: size=" + size.width + "/" + size.height + "/ Aspect Ratio: " + ratio + "target width: " + width + "target height: " + height);

        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
        if (Math.abs(size.width - targetWidth) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.width - targetWidth);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.width - targetWidth) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.width - targetWidth);
            }
        }
    }
    Log.i("Unity", "optimal size=" + optimalSize.width + "/" + optimalSize.height + "/ Aspect Ratio: " + (double) optimalSize.width / optimalSize.height);
    return optimalSize;
}

public int getPreviewSizeWidth() {
    return prevWidth;
}

public int getPreviewSizeHeight() { return prevHeight; }

public byte[] bytes;
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    bytes = data;

    //Log.i("Unity", "JAVA: " + Integer.toString(data.length));
    UnityPlayer.UnitySendMessage(gameObjectTargetName, "GetBuffer", "");
}

要求发送到Unity3d的纹理是1024x768而不进行裁剪。有没有人对此有任何解决方法?

更新(添加C#代码)

此代码从java获取预览:

void CreateCameraTexture() {
    _texWidth = nativeCameraObject.Call<int>("getPreviewSizeWidth");
    _texHeight = nativeCameraObject.Call<int>("getPreviewSizeHeight");
    _cameraPreview = new Texture2D(_texWidth, _texHeight, TextureFormat.Alpha8, false);
    _converter = new YUVDecode(_texWidth, _texHeight);
    shaderMat.SetFloat("_Width", _texWidth);

    if (OnCameraTextureCreated != null)
        OnCameraTextureCreated(_cameraPreview);
}

private byte[] _bytes;
public void GetBuffer(string str) {
    _bytes = nativeCameraObject.Get<byte[]>("bytes");
    SetColor(_bytes);
    _cameraPreview.LoadRawTextureData(_bytes);
    _cameraPreview.Apply();

    UpdateLibFrame();
}

void SetColor(byte[] bytes) {
    _converter.SetBytes(bytes);
    shaderMat.SetTexture("_YUVTex", _converter.yuvtexture);
}

void UpdateLibFrame() {
    if (OnFrameUpdate != null) OnFrameUpdate(_cameraPreview.GetRawTextureData());
}

另一个类中的代码显示数据:

private RectTransform previewRect {
    get {
        if (!_previewRect)
            _previewRect = cameraPreview.GetComponent<RectTransform>();
        return _previewRect;
    }
}

private void GetPreviewFromCamera() {
    NativeCamera.OnCameraTextureCreated += (Texture2D cameraTex) => {
        if (cameraPreview != null && cameraPreview.texture != null)
            DestroyImmediate(cameraPreview.texture);

        previewRect.sizeDelta = new Vector2(1024, 768);

        cameraPreview.texture = cameraTex;
        cameraPreview.enabled = true;
    };
}

更新:添加了示例项目的链接

项目在这里: https://www.dropbox.com/s/wid1qa9cmq3ck6w/CameraPreview.zip?dl=0

CameraJava是一个gradle项目。输出是一个.AAR文件,您必须将其复制到Assets / Plugins。

1 个答案:

答案 0 :(得分:0)

抱歉抱歉。我终于有时间测试你的项目了。它对我来说很好看。这是你在Unity中可以做的事情,以获得一个好的结果(虽然只测试了风景,因为你似乎没有在Android中实现旋转处理):转到播放器设置并将默认方向设置为左侧风景。将Canvas Scaler设置为与摄像机图像相同的参考分辨率(1024x768)。最后将RawImage的Z旋转设置为0.由于4:3的比例,顶部和底部将被切割,但这是正常的。如果你想要完整的图像,设置cavas缩放器以匹配高度而不是宽度。