parent
2b43b242b1
commit
2cc2f217b6
@ -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