-
-
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; | |
} | |
} | |
} | |
} |
What is Progress.GetFrameCallback =?????
Has anyone successfully used this code to convert camera frame from yuv to rgb?? Pls share.
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.
Progress.GetFrameCallback... doesn't work