-
-
Save wernerd/b933deb3187f582fec2c to your computer and use it in GitHub Desktop.
/* | |
* Copyright 2014 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.example.android.camera2video; | |
import android.Manifest; | |
import android.app.Activity; | |
import android.app.AlertDialog; | |
import android.app.Dialog; | |
import android.app.DialogFragment; | |
import android.app.Fragment; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.pm.PackageManager; | |
import android.content.res.Configuration; | |
import android.graphics.Bitmap; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.ImageFormat; | |
import android.graphics.Matrix; | |
import android.graphics.Paint; | |
import android.graphics.PixelFormat; | |
import android.graphics.Rect; | |
import android.graphics.RectF; | |
import android.graphics.SurfaceTexture; | |
import android.hardware.camera2.CameraAccessException; | |
import android.hardware.camera2.CameraCaptureSession; | |
import android.hardware.camera2.CameraCharacteristics; | |
import android.hardware.camera2.CameraDevice; | |
import android.hardware.camera2.CameraManager; | |
import android.hardware.camera2.CameraMetadata; | |
import android.hardware.camera2.CaptureRequest; | |
import android.hardware.camera2.params.StreamConfigurationMap; | |
import android.media.ImageReader; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.HandlerThread; | |
import android.renderscript.Allocation; | |
import android.renderscript.Element; | |
import android.renderscript.RenderScript; | |
import android.renderscript.Type; | |
import android.support.annotation.NonNull; | |
import android.support.v13.app.FragmentCompat; | |
import android.support.v4.app.ActivityCompat; | |
import android.util.Log; | |
import android.util.Size; | |
import android.util.SparseIntArray; | |
import android.view.LayoutInflater; | |
import android.view.Surface; | |
import android.view.TextureView; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.Toast; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.List; | |
import java.util.concurrent.Semaphore; | |
import java.util.concurrent.TimeUnit; | |
public class Camera2VideoFragment extends Fragment | |
implements FragmentCompat.OnRequestPermissionsResultCallback, Progress.GetFrameCallback | |
{ | |
private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); | |
private static final String TAG = "Camera2VideoFragment"; | |
private static final int REQUEST_VIDEO_PERMISSIONS = 1; | |
private static final String FRAGMENT_DIALOG = "dialog"; | |
private static final String[] VIDEO_PERMISSIONS = { | |
Manifest.permission.CAMERA, | |
Manifest.permission.RECORD_AUDIO, | |
}; | |
static { | |
ORIENTATIONS.append(Surface.ROTATION_0, 90); | |
ORIENTATIONS.append(Surface.ROTATION_90, 0); | |
ORIENTATIONS.append(Surface.ROTATION_180, 270); | |
ORIENTATIONS.append(Surface.ROTATION_270, 180); | |
} | |
private static final int WIDTH = 352; // 720; // 640; | |
private static final int HEIGHT = 288; // 480; | |
/** | |
* An {@link AutoFitTextureView} for camera preview. | |
*/ | |
private AutoFitTextureView mTextureView; | |
/** | |
* A reference to the opened {@link android.hardware.camera2.CameraDevice}. | |
*/ | |
private CameraDevice mCameraDevice; | |
/** | |
* A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for | |
* preview. | |
*/ | |
private CameraCaptureSession mPreviewSession; | |
private RenderScript mRS; | |
private RgbConversion mConversion; | |
/** | |
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a | |
* {@link TextureView}. | |
*/ | |
private Rect mRectSrc; | |
private TextureView.SurfaceTextureListener mSurfaceTextureListener | |
= new TextureView.SurfaceTextureListener() { | |
@Override | |
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
openCamera(width, height); | |
} | |
@Override | |
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
configureTransform(width, height); | |
} | |
@Override | |
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { | |
return true; | |
} | |
@Override | |
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { | |
} | |
}; | |
private Rect mRectDest; | |
boolean mVideoTexture; | |
private TextureView.SurfaceTextureListener mVideoTextureListener | |
= new TextureView.SurfaceTextureListener() { | |
@Override | |
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
configureTransformBitmap(width, height); | |
mVideoTexture = true; | |
} | |
@Override | |
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, | |
int width, int height) { | |
configureTransformBitmap(width, height); | |
} | |
@Override | |
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { | |
mVideoTexture = false; | |
return true; | |
} | |
@Override | |
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { | |
} | |
}; | |
/** | |
* The {@link android.util.Size} of camera preview. | |
*/ | |
private Size mPreviewSize; | |
/** | |
* The {@link android.util.Size} of video recording. | |
*/ | |
private Size mVideoSize; | |
/** | |
* Camera preview. | |
*/ | |
private CaptureRequest.Builder mPreviewBuilder; | |
/** | |
* An additional thread for running tasks that shouldn't block the UI. | |
*/ | |
private HandlerThread mBackgroundThread; | |
/** | |
* A {@link Handler} for running tasks in the background. | |
*/ | |
private Handler mBackgroundHandler; | |
/** | |
* A {@link Semaphore} to prevent the app from exiting before closing the camera. | |
*/ | |
private Semaphore mCameraOpenCloseLock = new Semaphore(1); | |
private ImageReader mImageReader; | |
private TextureView mVideoView; | |
/** | |
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status. | |
*/ | |
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { | |
@Override | |
public void onOpened(@NonNull CameraDevice cameraDevice) { | |
mCameraDevice = cameraDevice; | |
startPreview(); | |
mCameraOpenCloseLock.release(); | |
if (null != mTextureView) { | |
configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); | |
} | |
} | |
@Override | |
public void onDisconnected(@NonNull CameraDevice cameraDevice) { | |
mCameraOpenCloseLock.release(); | |
cameraDevice.close(); | |
mCameraDevice = null; | |
} | |
@Override | |
public void onError(@NonNull CameraDevice cameraDevice, int error) { | |
mCameraOpenCloseLock.release(); | |
cameraDevice.close(); | |
mCameraDevice = null; | |
Activity activity = getActivity(); | |
if (null != activity) { | |
activity.finish(); | |
} | |
} | |
}; | |
public static Camera2VideoFragment newInstance() { | |
return new Camera2VideoFragment(); | |
} | |
/** | |
* Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose | |
* width and height are at least as large as the respective requested values, and whose aspect | |
* ratio matches with the specified value. | |
* | |
* @param choices The list of sizes that the camera supports for the intended output class | |
* @param width The minimum desired width | |
* @param height The minimum desired height | |
* @return The optimal {@code Size}, or an arbitrary one if none were big enough | |
*/ | |
private static Size chooseOptimalSize(Size[] choices, int width, int height) { | |
// Collect the supported resolutions that are at least as big as the preview Surface | |
List<Size> bigEnough = new ArrayList<>(); | |
for (Size option : choices) { | |
Log.d(TAG, String.format("Available sizes: W: %d, h: %d", option.getWidth(), option.getHeight())); | |
if (option.getWidth() >= width && option.getHeight() >= height) { | |
bigEnough.add(option); | |
} | |
} | |
// Pick the smallest of those, assuming we found any | |
if (bigEnough.size() > 0) { | |
return Collections.min(bigEnough, new CompareSizesByArea()); | |
} else { | |
Log.e(TAG, "Couldn't find any suitable preview size, requested width: " + width + ", height: " + height); | |
return choices[0]; | |
} | |
} | |
@Override | |
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |
mRS = RenderScript.create(getActivity()); | |
return inflater.inflate(R.layout.fragment_camera2_video, container, false); | |
} | |
@Override | |
public void onViewCreated(final View view, Bundle savedInstanceState) { | |
mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); | |
mVideoView = (TextureView) view.findViewById(R.id.VideoSurfacePreview); | |
mVideoView.setSurfaceTextureListener(mVideoTextureListener); | |
mRectDest = new Rect(0, 0,WIDTH, HEIGHT); | |
} | |
@Override | |
public void onResume() { | |
super.onResume(); | |
startBackgroundThread(); | |
if (mTextureView.isAvailable()) { | |
openCamera(mTextureView.getWidth(), mTextureView.getHeight()); | |
} else { | |
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); | |
} | |
} | |
@Override | |
public void onPause() { | |
closeCamera(); | |
stopBackgroundThread(); | |
super.onPause(); | |
} | |
private static Paint paL = new Paint(Paint.ANTI_ALIAS_FLAG); | |
/** | |
* Starts a background thread and its {@link Handler}. | |
*/ | |
private void startBackgroundThread() { | |
mBackgroundThread = new HandlerThread("CameraBackground"); | |
mBackgroundThread.start(); | |
mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); | |
} | |
/** | |
* Stops the background thread and its {@link Handler}. | |
*/ | |
private void stopBackgroundThread() { | |
mConversion.setOutputSurface(null); | |
mBackgroundThread.quitSafely(); | |
try { | |
mBackgroundThread.join(); | |
mBackgroundThread = null; | |
mBackgroundHandler = null; | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Gets whether you should show UI with rationale for requesting permissions. | |
* | |
* @param permissions The permissions your app wants to request. | |
* @return Whether you can show permission rationale UI. | |
*/ | |
private boolean shouldShowRequestPermissionRationale(String[] permissions) { | |
for (String permission : permissions) { | |
if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Requests permissions needed for recording video. | |
*/ | |
private void requestVideoPermissions() { | |
if (shouldShowRequestPermissionRationale(VIDEO_PERMISSIONS)) { | |
new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG); | |
} else { | |
FragmentCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS); | |
} | |
} | |
@Override | |
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | |
if (requestCode == REQUEST_VIDEO_PERMISSIONS) { | |
if (grantResults.length == VIDEO_PERMISSIONS.length) { | |
for (int result : grantResults) { | |
if (result != PackageManager.PERMISSION_GRANTED) { | |
ErrorDialog.newInstance(getString(R.string.permission_request)) | |
.show(getChildFragmentManager(), FRAGMENT_DIALOG); | |
break; | |
} | |
} | |
} else { | |
ErrorDialog.newInstance(getString(R.string.permission_request)) | |
.show(getChildFragmentManager(), FRAGMENT_DIALOG); | |
} | |
} else { | |
super.onRequestPermissionsResult(requestCode, permissions, grantResults); | |
} | |
} | |
private boolean hasPermissionsGranted(String[] permissions) { | |
for (String permission : permissions) { | |
if (ActivityCompat.checkSelfPermission(getActivity(), permission) | |
!= PackageManager.PERMISSION_GRANTED) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`. | |
*/ | |
String mFrontCameraId; | |
String mBackCameraId; | |
private void openCamera(int width, int height) { | |
if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) { | |
requestVideoPermissions(); | |
return; | |
} | |
final Activity activity = getActivity(); | |
if (null == activity || activity.isFinishing()) { | |
return; | |
} | |
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); | |
try { | |
Log.d(TAG, "tryAcquire"); | |
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { | |
throw new RuntimeException("Time out waiting to lock camera opening."); | |
} | |
// Get camera ids to check/test front and back facing camera | |
String[] cameraIds = manager.getCameraIdList(); | |
for (String id : cameraIds) { | |
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); | |
int i = characteristics.get(CameraCharacteristics.LENS_FACING); | |
if (i == CameraCharacteristics.LENS_FACING_FRONT) | |
mFrontCameraId = id; | |
else if (i== CameraCharacteristics.LENS_FACING_BACK) | |
mBackCameraId = id; | |
else { | |
Log.d(TAG, "No appropriate Camera found."); | |
} | |
} | |
String cameraId = mBackCameraId; | |
// Choose the sizes for camera preview and video recording | |
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); | |
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
mVideoSize = chooseOptimalSize(map.getOutputSizes(Allocation.class), WIDTH, HEIGHT); | |
mImageReader = ImageReader.newInstance(mVideoSize.getWidth(), mVideoSize.getHeight(), PixelFormat.RGBA_8888, 1); | |
mConversion = new RgbConversion(mRS, mVideoSize, this, 120); | |
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height); | |
mRectSrc = new Rect(0, 0, WIDTH, HEIGHT); | |
int orientation = getResources().getConfiguration().orientation; | |
if (orientation == Configuration.ORIENTATION_LANDSCAPE) { | |
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); | |
} else { | |
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); | |
} | |
configureTransform(width, height); | |
manager.openCamera(cameraId, mStateCallback, null); | |
} catch (CameraAccessException e) { | |
Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show(); | |
activity.finish(); | |
} catch (NullPointerException e) { | |
// Currently an NPE is thrown if the Camera2API is used but not supported on the device. | |
ErrorDialog.newInstance(getString(R.string.camera_error)) | |
.show(getChildFragmentManager(), FRAGMENT_DIALOG); | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Interrupted while trying to lock camera opening."); | |
} catch (SecurityException e) { | |
throw new SecurityException("No permission to access camera."); | |
} | |
} | |
private void closeCamera() { | |
try { | |
mCameraOpenCloseLock.acquire(); | |
if (null != mCameraDevice) { | |
mCameraDevice.close(); | |
mCameraDevice = null; | |
} | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Interrupted while trying to lock camera closing."); | |
} finally { | |
mCameraOpenCloseLock.release(); | |
} | |
} | |
/** | |
* Start the camera preview. | |
*/ | |
private void startPreview() { | |
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize || !mVideoTexture) { | |
return; | |
} | |
try { | |
SurfaceTexture texture = mTextureView.getSurfaceTexture(); | |
assert texture != null; | |
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); | |
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
List<Surface> surfaces = new ArrayList<>(); | |
Surface previewSurface = new Surface(texture); | |
surfaces.add(previewSurface); | |
mPreviewBuilder.addTarget(previewSurface); | |
Surface readerSurface = mImageReader.getSurface(); | |
assert readerSurface != null; | |
mConversion.setOutputSurface(readerSurface); | |
Surface inputSurface = mConversion.getInputSurface(); | |
surfaces.add(inputSurface); | |
mPreviewBuilder.addTarget(inputSurface); | |
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { | |
@Override | |
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { | |
mPreviewSession = cameraCaptureSession; | |
updatePreview(); | |
} | |
@Override | |
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { | |
Activity activity = getActivity(); | |
if (null != activity) { | |
Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); | |
} | |
} | |
}, mBackgroundHandler); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Update the camera preview. {@link #startPreview()} needs to be called in advance. | |
*/ | |
private void updatePreview() { | |
if (null == mCameraDevice) { | |
return; | |
} | |
try { | |
setUpCaptureRequestBuilder(mPreviewBuilder); | |
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); | |
} catch (CameraAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) { | |
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); | |
} | |
private Matrix mBitMatrix; | |
/** | |
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. | |
* This method should not to be called until the camera preview size is determined in | |
* openCamera, or until the size of `mTextureView` is fixed. | |
* | |
* @param viewWidth The width of `mTextureView` | |
* @param viewHeight The height of `mTextureView` | |
*/ | |
private void configureTransformBitmap(int viewWidth, int viewHeight) { | |
Activity activity = getActivity(); | |
if (null == activity) { | |
return; | |
} | |
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); | |
rotation = ORIENTATIONS.get(rotation); | |
Matrix matrix = new Matrix(); | |
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); | |
RectF bufferRect = new RectF(0, 0, mVideoSize.getHeight(), mVideoSize.getWidth()); | |
float centerX = viewRect.centerX(); | |
float centerY = viewRect.centerY(); | |
if (rotation != 0) { | |
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); | |
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); | |
float scale = Math.max( | |
(float) viewHeight / mVideoSize.getHeight(), | |
(float) viewWidth / mVideoSize.getWidth()); | |
matrix.postScale(scale, scale, centerX, centerY); | |
matrix.postRotate(rotation, centerX, centerY); | |
} | |
mBitMatrix = matrix; | |
} | |
private void configureTransform(int viewWidth, int viewHeight) { | |
Activity activity = getActivity(); | |
if (null == mTextureView || null == mPreviewSize || null == activity) { | |
return; | |
} | |
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); | |
Matrix matrix = new Matrix(); | |
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); | |
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); | |
float centerX = viewRect.centerX(); | |
float centerY = viewRect.centerY(); | |
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { | |
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); | |
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); | |
float scale = Math.max( | |
(float) viewHeight / mPreviewSize.getHeight(), | |
(float) viewWidth / mPreviewSize.getWidth()); | |
matrix.postScale(scale, scale, centerX, centerY); | |
matrix.postRotate(90 * (rotation - 2), centerX, centerY); | |
} | |
mTextureView.setTransform(matrix); | |
} | |
/** | |
* Compares two {@code Size}s based on their areas. | |
*/ | |
static class CompareSizesByArea implements Comparator<Size> { | |
@Override | |
public int compare(Size lhs, Size rhs) { | |
// We cast here to ensure the multiplications won't overflow | |
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - | |
(long) rhs.getWidth() * rhs.getHeight()); | |
} | |
} | |
public static class ErrorDialog extends DialogFragment { | |
private static final String ARG_MESSAGE = "message"; | |
public static ErrorDialog newInstance(String message) { | |
ErrorDialog dialog = new ErrorDialog(); | |
Bundle args = new Bundle(); | |
args.putString(ARG_MESSAGE, message); | |
dialog.setArguments(args); | |
return dialog; | |
} | |
@Override | |
public Dialog onCreateDialog(Bundle savedInstanceState) { | |
final Activity activity = getActivity(); | |
return new AlertDialog.Builder(activity) | |
.setMessage(getArguments().getString(ARG_MESSAGE)) | |
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialogInterface, int i) { | |
activity.finish(); | |
} | |
}) | |
.create(); | |
} | |
} | |
public static class ConfirmationDialog extends DialogFragment { | |
@Override | |
public Dialog onCreateDialog(Bundle savedInstanceState) { | |
final Fragment parent = getParentFragment(); | |
return new AlertDialog.Builder(getActivity()) | |
.setMessage(R.string.permission_request) | |
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialog, int which) { | |
FragmentCompat.requestPermissions(parent, VIDEO_PERMISSIONS, | |
REQUEST_VIDEO_PERMISSIONS); | |
} | |
}) | |
.setNegativeButton(android.R.string.cancel, | |
new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialog, int which) { | |
parent.getActivity().finish(); | |
} | |
}) | |
.create(); | |
} | |
} | |
/** | |
* Example how to use an integer ARGB array with a bitmap. | |
* | |
* @param frameDataRgb the integer ARGB array from renderscript. | |
*/ | |
@Override | |
public void onFrameArrayInt(int[] frameDataRgb) { | |
Bitmap bitmap = Bitmap.createBitmap(frameDataRgb, mVideoSize.getWidth(), mVideoSize.getHeight(), Bitmap.Config.ARGB_8888); | |
Canvas c = mVideoView.lockCanvas(); | |
if (c != null) { | |
c.drawColor(Color.BLACK); | |
c.drawBitmap(Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mBitMatrix, true), mRectSrc, mRectDest, paL); | |
mVideoView.unlockCanvasAndPost(c); | |
} | |
} | |
private static class RgbConversion implements Allocation.OnBufferAvailableListener { | |
private Allocation mInputAllocation; | |
private Allocation mOutputAllocation; | |
private Allocation mOutputAllocationInt; | |
private Allocation mScriptAllocation; | |
private Size mSizeVideoCall; | |
private ScriptC_yuv2rgb mScriptC; | |
private int[] mOutBufferInt; | |
private long mLastProcessed; | |
private Camera2VideoFragment mFrameCallback; | |
private final int mFrameEveryMs; | |
RgbConversion(RenderScript rs, Size dimensions, Camera2VideoFragment frameCallback, int frameMs) { | |
mSizeVideoCall = dimensions; | |
mFrameCallback = frameCallback; | |
mFrameEveryMs = frameMs; | |
createAllocations(rs); | |
mInputAllocation.setOnBufferAvailableListener(this); | |
mScriptC = new ScriptC_yuv2rgb(rs); | |
mScriptC.set_gCurrentFrame(mInputAllocation); | |
mScriptC.set_gIntFrame(mOutputAllocationInt); | |
} | |
private void createAllocations(RenderScript rs) { | |
mOutBufferInt = | |
new int[mSizeVideoCall.getWidth() * mSizeVideoCall.getHeight()]; | |
final int width = mSizeVideoCall.getWidth(); | |
final int height = mSizeVideoCall.getHeight(); | |
Type.Builder yuvTypeBuilder = new Type.Builder(rs, Element.YUV(rs)); | |
yuvTypeBuilder.setX(width); | |
yuvTypeBuilder.setY(height); | |
yuvTypeBuilder.setYuvFormat(ImageFormat.YUV_420_888); | |
mInputAllocation = Allocation.createTyped(rs, yuvTypeBuilder.create(), | |
Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT); | |
Type rgbType = Type.createXY(rs, Element.RGBA_8888(rs), width, height); | |
Type intType = Type.createXY(rs, Element.U32(rs), width, height); | |
mScriptAllocation = Allocation.createTyped(rs, rgbType, Allocation.USAGE_SCRIPT); | |
mOutputAllocation = Allocation.createTyped(rs, rgbType, Allocation.USAGE_IO_OUTPUT | Allocation.USAGE_SCRIPT); | |
mOutputAllocationInt = Allocation.createTyped(rs, intType, Allocation.USAGE_SCRIPT); | |
} | |
Surface getInputSurface() { | |
return mInputAllocation.getSurface(); | |
} | |
void setOutputSurface(Surface output) { | |
mOutputAllocation.setSurface(output); | |
} | |
@Override | |
public void onBufferAvailable(Allocation a) { | |
// Get the new frame into the input allocation | |
mInputAllocation.ioReceive(); | |
// Run processing pass if it's time to process a frame - just to limit the output frequency | |
final long current = System.currentTimeMillis(); | |
if ((current - mLastProcessed) >= mFrameEveryMs) { | |
mScriptC.forEach_yuv2rgbFrames(mScriptAllocation, mOutputAllocation); | |
if (mFrameCallback != null) { | |
mOutputAllocationInt.copyTo(mOutBufferInt); | |
mFrameCallback.onFrameArrayInt(mOutBufferInt); | |
} | |
mLastProcessed = current; | |
} | |
} | |
} | |
} |
Has anyone successfully used this code to convert camera frame from yuv to rgb?? Pls share.
To whoever has problems with this: I don't know what exactly your problems are, but I've found a way to implement all this functionality. Take a look at https://github.com/noncom/CameraCaptureNative, it's intended to be used with Unity3D and C++, but if you strip all that away and simply leave the Java/Kotlin part, then that's what you can use in your Java/Kotlin app.
Notice that the ScriptC_yuv2rgb
class is crucial for this and it's auto-generated by Android Studio from a render script (in the "rs" folder of the CameraCaptureNative project). You need to run the build once for it to be auto-generated, even though it's shown as a missing class at the beginning.
Has anyone successfully used this code to convert camera frame from yuv to rgb?? Pls share.