Fix rotation

pull/360/head
Mattia Iavarone 6 years ago
parent 926b03cf35
commit 8662d9ba2e
  1. 3
      cameraview/src/main/gles/com/otaliastudios/cameraview/VideoCoreEncoder.java
  2. 8
      cameraview/src/main/gles/com/otaliastudios/cameraview/VideoTextureEncoder.java
  3. 24
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  4. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/MediaCodecVideoRecorder.java

@ -58,7 +58,7 @@ class VideoCoreEncoder {
/** /**
* Configures encoder and muxer state, and prepares the input Surface. * Configures encoder and muxer state, and prepares the input Surface.
*/ */
public VideoCoreEncoder(int width, int height, int bitRate, int frameRate, File outputFile) public VideoCoreEncoder(int width, int height, int bitRate, int frameRate, int rotation, File outputFile)
throws IOException { throws IOException {
mBufferInfo = new MediaCodec.BufferInfo(); mBufferInfo = new MediaCodec.BufferInfo();
@ -71,6 +71,7 @@ class VideoCoreEncoder {
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
format.setInteger("rotation-degrees", rotation);
// Create a MediaCodec encoder, and configure it with our format. Get a Surface // Create a MediaCodec encoder, and configure it with our format. Get a Surface
// we can use for input and wrap it with a class that handles the EGL work. // we can use for input and wrap it with a class that handles the EGL work.

@ -98,12 +98,14 @@ class VideoTextureEncoder implements Runnable {
final int mHeight; final int mHeight;
final int mBitRate; final int mBitRate;
final int mFrameRate; final int mFrameRate;
final int mRotation;
final float mScaleX; final float mScaleX;
final float mScaleY; final float mScaleY;
final EGLContext mEglContext; final EGLContext mEglContext;
Config(File outputFile, int width, int height, Config(File outputFile, int width, int height,
int bitRate, int frameRate, int bitRate, int frameRate,
int rotation,
float scaleX, float scaleY, float scaleX, float scaleY,
EGLContext sharedEglContext) { EGLContext sharedEglContext) {
mOutputFile = outputFile; mOutputFile = outputFile;
@ -114,6 +116,7 @@ class VideoTextureEncoder implements Runnable {
mEglContext = sharedEglContext; mEglContext = sharedEglContext;
mScaleX = scaleX; mScaleX = scaleX;
mScaleY = scaleY; mScaleY = scaleY;
mRotation = rotation;
} }
@Override @Override
@ -125,7 +128,10 @@ class VideoTextureEncoder implements Runnable {
private void prepareEncoder(Config config) { private void prepareEncoder(Config config) {
try { try {
mVideoEncoder = new VideoCoreEncoder(config.mWidth, config.mHeight, config.mBitRate, config.mFrameRate, config.mOutputFile); mVideoEncoder = new VideoCoreEncoder(config.mWidth, config.mHeight,
config.mBitRate, config.mFrameRate,
config.mRotation,
config.mOutputFile);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new RuntimeException(ioe); throw new RuntimeException(ioe);
} }

@ -719,7 +719,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
videoResult.isSnapshot = true; videoResult.isSnapshot = true;
videoResult.codec = mVideoCodec; videoResult.codec = mVideoCodec;
videoResult.location = mLocation; videoResult.location = mLocation;
videoResult.rotation = offset(REF_SENSOR, REF_OUTPUT);
// What matters as size here is the preview size, which is passed to the GLCameraPreview // What matters as size here is the preview size, which is passed to the GLCameraPreview
// surface texture, which is then passed to the encoder. The view size has no influence. // surface texture, which is then passed to the encoder. The view size has no influence.
@ -730,15 +729,30 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
Size preview = getPreviewSize(REF_VIEW); // The preview stream size in REF_VIEW Size preview = getPreviewSize(REF_VIEW); // The preview stream size in REF_VIEW
Size view = mPreview.getOutputSurfaceSize(); // The view size in REF_VIEW Size view = mPreview.getOutputSurfaceSize(); // The view size in REF_VIEW
Rect crop = CropHelper.computeCrop(preview, AspectRatio.of(view.getWidth(), view.getHeight())); Rect crop = CropHelper.computeCrop(preview, AspectRatio.of(view.getWidth(), view.getHeight()));
Size cropSize = new Size(crop.width(), crop.height()); // The visible size in REF_VIEW Size finalSize = new Size(crop.width(), crop.height()); // The visible size in REF_VIEW
Size finalSize = flip(REF_VIEW, REF_OUTPUT) ? cropSize.flip() : cropSize; // Move the REF_VIEW size to REF_OUTPUT // ^ We could flip to REF_OUTPUT here and this is what we are doing, but since the video codec recorder
// does not account rotation, we must stay in REF_VIEW for this to work well.
// Without cropping (missing ATM), the actual size is the preview size with no crops. // Without cropping (missing ATM - Edit: DONE), the actual size is the preview size with no crops.
// Passing a cropped size while the cropping is not implemented at the encoder surface level, // Passing a cropped size while the cropping is not implemented at the encoder surface level,
// would cause distortions and crashes in the video encoder. // would cause distortions and crashes in the video encoder.
// Size finalSize2 = getPreviewSize(REF_OUTPUT); // Size finalSize2 = getPreviewSize(REF_VIEW);
// With phone landscape on the left:
// offset(REF_SENSOR, REF_VIEW) -> 270 (correct)
// offset(REF_VIEW, REF_OUTPUT) -> 270 (correct)
// offset(REF_SENSOR, REF_OUTPUT) -> 180 (not correct)
// With straight phone:
// offset(REF_SENSOR, REF_VIEW) -> 270 (not correct)
// offset(REF_VIEW, REF_OUTPUT) -> 0 (correct)
// offset(REF_SENSOR, REF_OUTPUT) -> 270 (not correct)
// So it looks like REF_VIEW REF_OUTPUT is the correct one, meaning that
// the input data in this case is not in the REF_SENSOR coordinates but rather
// in the REF_VIEW ones.
videoResult.size = finalSize; videoResult.size = finalSize;
videoResult.rotation = offset(REF_VIEW, REF_OUTPUT);
videoResult.audio = mAudio; videoResult.audio = mAudio;
videoResult.maxSize = mVideoMaxSize; videoResult.maxSize = mVideoMaxSize;
videoResult.maxDuration = mVideoMaxDuration; videoResult.maxDuration = mVideoMaxDuration;

@ -9,6 +9,10 @@ import android.support.annotation.RequiresApi;
/** /**
* A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs. * A {@link VideoRecorder} that uses {@link android.media.MediaCodec} APIs.
*
* TODO rotation support. Currently we pass the wrong size
* TODO audio support
* TODO when cropping is huge, the first frame of the video result, noticeably, has no transformation applied. Don't know why.
*/ */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.RendererFrameCallback { class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.RendererFrameCallback {
@ -19,7 +23,6 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
private static final int STATE_RECORDING = 0; private static final int STATE_RECORDING = 0;
private static final int STATE_NOT_RECORDING = 1; private static final int STATE_NOT_RECORDING = 1;
private CamcorderProfile mProfile;
private VideoTextureEncoder mEncoder; private VideoTextureEncoder mEncoder;
private GLCameraPreview mPreview; private GLCameraPreview mPreview;
@ -29,7 +32,6 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
MediaCodecVideoRecorder(VideoResult stub, VideoResultListener listener, GLCameraPreview preview, int cameraId) { MediaCodecVideoRecorder(VideoResult stub, VideoResultListener listener, GLCameraPreview preview, int cameraId) {
super(stub, listener); super(stub, listener);
mProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
mEncoder = new VideoTextureEncoder(); mEncoder = new VideoTextureEncoder();
mPreview = preview; mPreview = preview;
mPreview.setRendererFrameCallback(this); mPreview.setRendererFrameCallback(this);
@ -38,6 +40,8 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
@Override @Override
void start() { void start() {
mDesiredState = STATE_RECORDING; mDesiredState = STATE_RECORDING;
// TODO respect maxSize by doing inspecting frameRate, bitRate and frame size?
// TODO do this at the encoder level, not here with a handler.
if (mResult.maxDuration > 0) { if (mResult.maxDuration > 0) {
new Handler().postDelayed(new Runnable() { new Handler().postDelayed(new Runnable() {
@Override @Override
@ -61,17 +65,20 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
@Override @Override
public void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY) { public void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY) {
if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) { if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) {
// Size must not be flipped based on rotation, unlike MediaRecorderVideoRecorder
Size size = mResult.getSize();
// Ensure width and height are divisible by 2, as I have read somewhere. // Ensure width and height are divisible by 2, as I have read somewhere.
int width = mResult.size.getWidth(); int width = size.getWidth();
int height = mResult.size.getHeight(); int height = size.getHeight();
width = width % 2 == 0 ? width : width + 1; width = width % 2 == 0 ? width : width + 1;
height = height % 2 == 0 ? height : height + 1; height = height % 2 == 0 ? height : height + 1;
VideoTextureEncoder.Config configuration = new VideoTextureEncoder.Config( VideoTextureEncoder.Config configuration = new VideoTextureEncoder.Config(
mResult.file, mResult.getFile(),
width, width,
height, height,
1000000, 1000000,
30, 30,
mResult.getRotation(),
scaleX, scaleX,
scaleY, scaleY,
EGL14.eglGetCurrentContext() EGL14.eglGetCurrentContext()

Loading…
Cancel
Save