渲染到纹理的java Android相机预览被拉伸
我正在使用纹理将安卓摄像头预览发送到Unity3d,并将其拉伸。预览不会渲染到安卓中的任何视图,而是直接渲染到具有以下特性的纹理:
setPreviewTexture(texture);
然后,字节被发送到Unity3d,并在屏幕上绘制每个onPreviewFrame
相机预览大小和纹理大小设置为1024x768,Unity3d中的纹理容器大小相同。该设备的分辨率是960x540,所以它的比例不同。我正在为相机选择一个支持的分辨率,原始尺寸的比例为4:3,所以应该没有拉伸。 安卓似乎只渲染了可以渲染到屏幕上的部分的纹理——它将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文件
# 1 楼答案
抱歉耽搁了。我终于有时间测试你的项目了。我觉得不错。下面是你在Unity中可以做的事情,以获得一个好的结果(尽管只针对横向进行了测试,因为你似乎还没有在android中实现旋转处理):转到播放器设置,并将默认方向设置为横向左。将画布缩放器设置为与相机图像相同的参考分辨率(1024x768)。最后将RawImage的Z旋转设置为0。由于4:3的比例,顶部和底部将被切割,但这是正常的。如果需要完整图像,请将cavas缩放器设置为匹配高度而不是宽度