parent
481062a08b
commit
419ff34736
@ -0,0 +1,32 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
|
||||
import android.support.test.filters.SmallTest; |
||||
import android.support.test.runner.AndroidJUnit4; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mockito; |
||||
|
||||
import static org.junit.Assert.assertNull; |
||||
|
||||
|
||||
@RunWith(AndroidJUnit4.class) |
||||
@SmallTest |
||||
public class PictureRecorderTest extends BaseTest { |
||||
|
||||
@Test |
||||
public void testRecorder() { |
||||
PictureResult result = new PictureResult(); |
||||
PictureRecorder.PictureResultListener listener = Mockito.mock(PictureRecorder.PictureResultListener.class); |
||||
PictureRecorder recorder = new PictureRecorder(result, listener) { |
||||
void take() { |
||||
dispatchResult(); |
||||
} |
||||
}; |
||||
recorder.take(); |
||||
Mockito.verify(listener, Mockito.times(1)).onPictureResult(result); |
||||
assertNull(recorder.mListener); |
||||
assertNull(recorder.mResult); |
||||
} |
||||
} |
@ -0,0 +1,70 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
import android.hardware.Camera; |
||||
import android.media.CamcorderProfile; |
||||
import android.media.MediaRecorder; |
||||
import android.support.media.ExifInterface; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* A {@link PictureResult} that uses standard APIs. |
||||
*/ |
||||
class FullPictureRecorder extends PictureRecorder { |
||||
|
||||
private static final String TAG = FullPictureRecorder.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
private Camera mCamera; |
||||
|
||||
FullPictureRecorder(PictureResult stub, PictureResultListener listener, Camera camera) { |
||||
super(stub, listener); |
||||
mCamera = camera; |
||||
|
||||
// We set the rotation to the camera parameters, but we don't know if the result will be
|
||||
// already rotated with 0 exif, or original with non zero exif. we will have to read EXIF.
|
||||
Camera.Parameters params = mCamera.getParameters(); |
||||
params.setRotation(mResult.rotation); |
||||
mCamera.setParameters(params); |
||||
} |
||||
|
||||
// Camera2 constructor here...
|
||||
|
||||
@Override |
||||
void take() { |
||||
mCamera.takePicture( |
||||
new Camera.ShutterCallback() { |
||||
@Override |
||||
public void onShutter() { |
||||
dispatchOnShutter(true); |
||||
} |
||||
}, |
||||
null, |
||||
null, |
||||
new Camera.PictureCallback() { |
||||
@Override |
||||
public void onPictureTaken(byte[] data, final Camera camera) { |
||||
int exifRotation; |
||||
try { |
||||
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data)); |
||||
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); |
||||
exifRotation = CameraUtils.decodeExifOrientation(exifOrientation); |
||||
} catch (IOException e) { |
||||
exifRotation = 0; |
||||
} |
||||
mResult.jpeg = data; |
||||
mResult.rotation = exifRotation; |
||||
camera.startPreview(); // This is needed, read somewhere in the docs.
|
||||
dispatchResult(); |
||||
} |
||||
} |
||||
); |
||||
} |
||||
|
||||
@Override |
||||
protected void dispatchResult() { |
||||
mCamera = null; |
||||
super.dispatchResult(); |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
import android.support.annotation.Nullable; |
||||
|
||||
/** |
||||
* Interface for picture capturing. |
||||
* Don't call start if already started. Don't call stop if already stopped. |
||||
* Don't reuse. |
||||
*/ |
||||
abstract class PictureRecorder { |
||||
|
||||
/* tests */ PictureResult mResult; |
||||
/* tests */ PictureResultListener mListener; |
||||
|
||||
PictureRecorder(PictureResult stub, PictureResultListener listener) { |
||||
mResult = stub; |
||||
mListener = listener; |
||||
} |
||||
|
||||
abstract void take(); |
||||
|
||||
protected void dispatchOnShutter(boolean didPlaySound) { |
||||
if (mListener != null) mListener.onPictureShutter(didPlaySound); |
||||
} |
||||
|
||||
protected void dispatchResult() { |
||||
if (mListener != null) { |
||||
mListener.onPictureResult(mResult); |
||||
mListener = null; |
||||
mResult = null; |
||||
} |
||||
} |
||||
|
||||
interface PictureResultListener { |
||||
void onPictureShutter(boolean didPlaySound); |
||||
void onPictureResult(@Nullable PictureResult result); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
package com.otaliastudios.cameraview; |
||||
|
||||
import android.graphics.ImageFormat; |
||||
import android.graphics.Rect; |
||||
import android.graphics.YuvImage; |
||||
import android.hardware.Camera; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
|
||||
/** |
||||
* A {@link PictureResult} that uses standard APIs. |
||||
*/ |
||||
class SnapshotPictureRecorder extends PictureRecorder { |
||||
|
||||
private static final String TAG = SnapshotPictureRecorder.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
private Camera1 mController; |
||||
private Camera mCamera; |
||||
private AspectRatio mOutputRatio; |
||||
private Size mSensorPreviewSize; |
||||
private int mFormat; |
||||
|
||||
SnapshotPictureRecorder(PictureResult stub, Camera1 controller, Camera camera, AspectRatio viewRatio) { |
||||
super(stub, controller); |
||||
mController = controller; |
||||
mCamera = camera; |
||||
mOutputRatio = viewRatio; |
||||
mFormat = mController.mPreviewFormat; |
||||
mSensorPreviewSize = mController.mPreviewSize; |
||||
} |
||||
|
||||
@Override |
||||
void take() { |
||||
mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { |
||||
@Override |
||||
public void onPreviewFrame(final byte[] yuv, Camera camera) { |
||||
dispatchOnShutter(false); |
||||
|
||||
// Got to rotate the preview frame, since byte[] data here does not include
|
||||
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
|
||||
// Adding EXIF to a byte array, unfortunately, is hard.
|
||||
final int sensorToOutput = mResult.rotation; |
||||
final Size outputSize = mResult.size; |
||||
WorkerHandler.run(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
// Rotate the picture, because no one will write EXIF data,
|
||||
// then crop if needed. In both cases, transform yuv to jpeg.
|
||||
byte[] data = RotationHelper.rotate(yuv, mSensorPreviewSize, sensorToOutput); |
||||
YuvImage yuv = new YuvImage(data, mFormat, outputSize.getWidth(), outputSize.getHeight(), null); |
||||
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
||||
Rect outputRect = CropHelper.computeCrop(outputSize, mOutputRatio); |
||||
yuv.compressToJpeg(outputRect, 90, stream); |
||||
data = stream.toByteArray(); |
||||
|
||||
mResult.jpeg = data; |
||||
mResult.size = new Size(outputRect.width(), outputRect.height()); |
||||
mResult.rotation = 0; |
||||
dispatchResult(); |
||||
} |
||||
}); |
||||
|
||||
// It seems that the buffers are already cleared here, so we need to allocate again.
|
||||
camera.setPreviewCallbackWithBuffer(null); // Release anything left
|
||||
camera.setPreviewCallbackWithBuffer(mController); // Add ourselves
|
||||
mController.mFrameManager.allocate(ImageFormat.getBitsPerPixel(mFormat), mController.mPreviewSize); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
protected void dispatchResult() { |
||||
mController = null; |
||||
mCamera = null; |
||||
mOutputRatio = null; |
||||
mFormat = 0; |
||||
mSensorPreviewSize = null; |
||||
super.dispatchResult(); |
||||
} |
||||
} |
Loading…
Reference in new issue