Fix rotation

v2
Mattia Iavarone 6 years ago
parent dc76ff1b45
commit dc2f65aebf
  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.
*/
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 {
mBufferInfo = new MediaCodec.BufferInfo();
@ -71,6 +71,7 @@ class VideoCoreEncoder {
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
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
// 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 mBitRate;
final int mFrameRate;
final int mRotation;
final float mScaleX;
final float mScaleY;
final EGLContext mEglContext;
Config(File outputFile, int width, int height,
int bitRate, int frameRate,
int rotation,
float scaleX, float scaleY,
EGLContext sharedEglContext) {
mOutputFile = outputFile;
@ -114,6 +116,7 @@ class VideoTextureEncoder implements Runnable {
mEglContext = sharedEglContext;
mScaleX = scaleX;
mScaleY = scaleY;
mRotation = rotation;
}
@Override
@ -125,7 +128,10 @@ class VideoTextureEncoder implements Runnable {
private void prepareEncoder(Config config) {
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) {
throw new RuntimeException(ioe);
}

@ -719,7 +719,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
videoResult.isSnapshot = true;
videoResult.codec = mVideoCodec;
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
// 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 view = mPreview.getOutputSurfaceSize(); // The view size in REF_VIEW
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 = flip(REF_VIEW, REF_OUTPUT) ? cropSize.flip() : cropSize; // Move the REF_VIEW size to REF_OUTPUT
Size finalSize = new Size(crop.width(), crop.height()); // The visible size in REF_VIEW
// ^ 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,
// 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.rotation = offset(REF_VIEW, REF_OUTPUT);
videoResult.audio = mAudio;
videoResult.maxSize = mVideoMaxSize;
videoResult.maxDuration = mVideoMaxDuration;

@ -9,6 +9,10 @@ import android.support.annotation.RequiresApi;
/**
* 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)
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_NOT_RECORDING = 1;
private CamcorderProfile mProfile;
private VideoTextureEncoder mEncoder;
private GLCameraPreview mPreview;
@ -29,7 +32,6 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
MediaCodecVideoRecorder(VideoResult stub, VideoResultListener listener, GLCameraPreview preview, int cameraId) {
super(stub, listener);
mProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
mEncoder = new VideoTextureEncoder();
mPreview = preview;
mPreview.setRendererFrameCallback(this);
@ -38,6 +40,8 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
@Override
void start() {
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) {
new Handler().postDelayed(new Runnable() {
@Override
@ -61,17 +65,20 @@ class MediaCodecVideoRecorder extends VideoRecorder implements GLCameraPreview.R
@Override
public void onRendererFrame(SurfaceTexture surfaceTexture, float scaleX, float scaleY) {
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.
int width = mResult.size.getWidth();
int height = mResult.size.getHeight();
int width = size.getWidth();
int height = size.getHeight();
width = width % 2 == 0 ? width : width + 1;
height = height % 2 == 0 ? height : height + 1;
VideoTextureEncoder.Config configuration = new VideoTextureEncoder.Config(
mResult.file,
mResult.getFile(),
width,
height,
1000000,
30,
mResult.getRotation(),
scaleX,
scaleY,
EGL14.eglGetCurrentContext()

Loading…
Cancel
Save