|
|
@ -72,16 +72,17 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
private EglCore mEglCore; |
|
|
|
private EglCore mEglCore; |
|
|
|
private EglViewport mFullScreen; |
|
|
|
private EglViewport mFullScreen; |
|
|
|
private int mTextureId; |
|
|
|
private int mTextureId; |
|
|
|
private int mFrameNum; |
|
|
|
private int mFrameNum = -1; // Important
|
|
|
|
private VideoCoreEncoder mVideoEncoder; |
|
|
|
private VideoCoreEncoder mVideoEncoder; |
|
|
|
private float mTransformationScaleX = 1F; |
|
|
|
private float mTransformationScaleX = 1F; |
|
|
|
private float mTransformationScaleY = 1F; |
|
|
|
private float mTransformationScaleY = 1F; |
|
|
|
|
|
|
|
private int mTransformationRotation = 0; |
|
|
|
|
|
|
|
|
|
|
|
// ----- accessed by multiple threads -----
|
|
|
|
// ----- accessed by multiple threads -----
|
|
|
|
private volatile EncoderHandler mHandler; |
|
|
|
private volatile EncoderHandler mHandler; |
|
|
|
|
|
|
|
|
|
|
|
private final Object mReadyFence = new Object(); // guards ready/running
|
|
|
|
private final Object mLooperReadyLock = new Object(); // guards ready/running
|
|
|
|
private boolean mReady; |
|
|
|
private boolean mLooperReady; |
|
|
|
private boolean mRunning; |
|
|
|
private boolean mRunning; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -102,11 +103,13 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
final float mScaleX; |
|
|
|
final float mScaleX; |
|
|
|
final float mScaleY; |
|
|
|
final float mScaleY; |
|
|
|
final EGLContext mEglContext; |
|
|
|
final EGLContext mEglContext; |
|
|
|
|
|
|
|
final String mMimeType; |
|
|
|
|
|
|
|
|
|
|
|
Config(File outputFile, int width, int height, |
|
|
|
Config(File outputFile, int width, int height, |
|
|
|
int bitRate, int frameRate, |
|
|
|
int bitRate, int frameRate, |
|
|
|
int rotation, |
|
|
|
int rotation, |
|
|
|
float scaleX, float scaleY, |
|
|
|
float scaleX, float scaleY, |
|
|
|
|
|
|
|
String mimeType, |
|
|
|
EGLContext sharedEglContext) { |
|
|
|
EGLContext sharedEglContext) { |
|
|
|
mOutputFile = outputFile; |
|
|
|
mOutputFile = outputFile; |
|
|
|
mWidth = width; |
|
|
|
mWidth = width; |
|
|
@ -117,6 +120,7 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
mScaleX = scaleX; |
|
|
|
mScaleX = scaleX; |
|
|
|
mScaleY = scaleY; |
|
|
|
mScaleY = scaleY; |
|
|
|
mRotation = rotation; |
|
|
|
mRotation = rotation; |
|
|
|
|
|
|
|
mMimeType = mimeType; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
@ -130,8 +134,9 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
try { |
|
|
|
try { |
|
|
|
mVideoEncoder = new VideoCoreEncoder(config.mWidth, config.mHeight, |
|
|
|
mVideoEncoder = new VideoCoreEncoder(config.mWidth, config.mHeight, |
|
|
|
config.mBitRate, config.mFrameRate, |
|
|
|
config.mBitRate, config.mFrameRate, |
|
|
|
config.mRotation, |
|
|
|
0, // The video encoder rotation does not work, so we apply it here using Matrix.rotateM().
|
|
|
|
config.mOutputFile); |
|
|
|
config.mOutputFile, |
|
|
|
|
|
|
|
config.mMimeType); |
|
|
|
} catch (IOException ioe) { |
|
|
|
} catch (IOException ioe) { |
|
|
|
throw new RuntimeException(ioe); |
|
|
|
throw new RuntimeException(ioe); |
|
|
|
} |
|
|
|
} |
|
|
@ -141,6 +146,7 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
mFullScreen = new EglViewport(); |
|
|
|
mFullScreen = new EglViewport(); |
|
|
|
mTransformationScaleX = config.mScaleX; |
|
|
|
mTransformationScaleX = config.mScaleX; |
|
|
|
mTransformationScaleY = config.mScaleY; |
|
|
|
mTransformationScaleY = config.mScaleY; |
|
|
|
|
|
|
|
mTransformationRotation = config.mRotation; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void releaseEncoder() { |
|
|
|
private void releaseEncoder() { |
|
|
@ -169,16 +175,16 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void startRecording(Config config) { |
|
|
|
public void startRecording(Config config) { |
|
|
|
Log.d(TAG, "Encoder: startRecording()"); |
|
|
|
Log.d(TAG, "Encoder: startRecording()"); |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
if (mRunning) { |
|
|
|
if (mRunning) { |
|
|
|
Log.w(TAG, "Encoder thread already running"); |
|
|
|
Log.w(TAG, "Encoder thread already running"); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
mRunning = true; |
|
|
|
mRunning = true; |
|
|
|
new Thread(this, "TextureMovieEncoder").start(); |
|
|
|
new Thread(this, "TextureMovieEncoder").start(); |
|
|
|
while (!mReady) { |
|
|
|
while (!mLooperReady) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
mReadyFence.wait(); |
|
|
|
mLooperReadyLock.wait(); |
|
|
|
} catch (InterruptedException ie) { |
|
|
|
} catch (InterruptedException ie) { |
|
|
|
// ignore
|
|
|
|
// ignore
|
|
|
|
} |
|
|
|
} |
|
|
@ -205,7 +211,7 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
* Returns true if recording has been started. |
|
|
|
* Returns true if recording has been started. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public boolean isRecording() { |
|
|
|
public boolean isRecording() { |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
return mRunning; |
|
|
|
return mRunning; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -224,18 +230,14 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
* stall the caller while this thread does work. |
|
|
|
* stall the caller while this thread does work. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void frameAvailable(SurfaceTexture st) { |
|
|
|
public void frameAvailable(SurfaceTexture st) { |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
if (!mReady) { |
|
|
|
if (!mLooperReady) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
float[] transform = new float[16]; // TODO - avoid alloc every frame. Not easy, need a pool
|
|
|
|
float[] transform = new float[16]; // TODO - avoid alloc every frame. Not easy, need a pool
|
|
|
|
st.getTransformMatrix(transform); |
|
|
|
st.getTransformMatrix(transform); |
|
|
|
float translX = (1F - mTransformationScaleX) / 2F; |
|
|
|
|
|
|
|
float translY = (1F - mTransformationScaleY) / 2F; |
|
|
|
|
|
|
|
Matrix.translateM(transform, 0, translX, translY, 0); |
|
|
|
|
|
|
|
Matrix.scaleM(transform, 0, mTransformationScaleX, mTransformationScaleY, 1); |
|
|
|
|
|
|
|
long timestamp = st.getTimestamp(); |
|
|
|
long timestamp = st.getTimestamp(); |
|
|
|
if (timestamp == 0) { |
|
|
|
if (timestamp == 0) { |
|
|
|
// Seeing this after device is toggled off/on with power button. The
|
|
|
|
// Seeing this after device is toggled off/on with power button. The
|
|
|
@ -257,8 +259,8 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
* TODO: do something less clumsy |
|
|
|
* TODO: do something less clumsy |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setTextureId(int id) { |
|
|
|
public void setTextureId(int id) { |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
if (!mReady) return; |
|
|
|
if (!mLooperReady) return; |
|
|
|
} |
|
|
|
} |
|
|
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null)); |
|
|
|
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_TEXTURE_ID, id, 0, null)); |
|
|
|
} |
|
|
|
} |
|
|
@ -272,16 +274,15 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
// Establish a Looper for this thread, and define a Handler for it.
|
|
|
|
// Establish a Looper for this thread, and define a Handler for it.
|
|
|
|
Looper.prepare(); |
|
|
|
Looper.prepare(); |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
mHandler = new EncoderHandler(this); |
|
|
|
mHandler = new EncoderHandler(this); |
|
|
|
mReady = true; |
|
|
|
mLooperReady = true; |
|
|
|
mReadyFence.notify(); |
|
|
|
mLooperReadyLock.notify(); |
|
|
|
} |
|
|
|
} |
|
|
|
Looper.loop(); |
|
|
|
Looper.loop(); |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "Encoder thread exiting"); |
|
|
|
Log.d(TAG, "Encoder thread exiting"); |
|
|
|
synchronized (mReadyFence) { |
|
|
|
synchronized (mLooperReadyLock) { |
|
|
|
mReady = mRunning = false; |
|
|
|
mLooperReady = mRunning = false; |
|
|
|
mHandler = null; |
|
|
|
mHandler = null; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -314,14 +315,63 @@ class VideoTextureEncoder implements Runnable { |
|
|
|
encoder.prepareEncoder(config); |
|
|
|
encoder.prepareEncoder(config); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case MSG_STOP_RECORDING: |
|
|
|
case MSG_STOP_RECORDING: |
|
|
|
|
|
|
|
encoder.mFrameNum = -1; |
|
|
|
encoder.mVideoEncoder.drainEncoder(true); |
|
|
|
encoder.mVideoEncoder.drainEncoder(true); |
|
|
|
encoder.releaseEncoder(); |
|
|
|
encoder.releaseEncoder(); |
|
|
|
((Runnable) obj).run(); |
|
|
|
((Runnable) obj).run(); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case MSG_FRAME_AVAILABLE: |
|
|
|
case MSG_FRAME_AVAILABLE: |
|
|
|
|
|
|
|
if (encoder.mFrameNum < 0) break; |
|
|
|
encoder.mFrameNum++; |
|
|
|
encoder.mFrameNum++; |
|
|
|
long timestamp = (((long) inputMessage.arg1) << 32) | (((long) inputMessage.arg2) & 0xffffffffL); |
|
|
|
long timestamp = (((long) inputMessage.arg1) << 32) | (((long) inputMessage.arg2) & 0xffffffffL); |
|
|
|
float[] transform = (float[]) obj; |
|
|
|
float[] transform = (float[]) obj; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We must scale this matrix like GLCameraPreview does, because it might have some cropping.
|
|
|
|
|
|
|
|
// Scaling takes place with respect to the (0, 0, 0) point, so we must apply a Translation to compensate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We also must rotate this matrix. In GLCameraPreview it is not needed because it is a live
|
|
|
|
|
|
|
|
// stream, but the output video, must be correctly rotated based on the device rotation at the moment.
|
|
|
|
|
|
|
|
// Rotation also takes place with respect to the origin (the Z axis), so we must apply another Translation to compensate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The order of operations must be translate, rotate & scale.
|
|
|
|
|
|
|
|
float scaleX = encoder.mTransformationScaleX; |
|
|
|
|
|
|
|
float scaleY = encoder.mTransformationScaleY; |
|
|
|
|
|
|
|
int rotation = encoder.mTransformationRotation; |
|
|
|
|
|
|
|
float W = scaleX; |
|
|
|
|
|
|
|
float H = scaleY; |
|
|
|
|
|
|
|
float scaleTranslX = (1F - scaleX) / 2F; |
|
|
|
|
|
|
|
float scaleTranslY = (1F - scaleY) / 2F; |
|
|
|
|
|
|
|
float rotationTranslX = 0F; |
|
|
|
|
|
|
|
float rotationTranslY = 0F; |
|
|
|
|
|
|
|
boolean flip = false;// rotation % 180 != 0;
|
|
|
|
|
|
|
|
Log.e("VideoTextureEncoder", "Rotation is " + rotation); |
|
|
|
|
|
|
|
if (rotation == 90) { |
|
|
|
|
|
|
|
rotationTranslX = W / 2F; |
|
|
|
|
|
|
|
rotationTranslY = W / 2F; |
|
|
|
|
|
|
|
} else if (rotation == 180) { |
|
|
|
|
|
|
|
rotationTranslX = W / 2F; |
|
|
|
|
|
|
|
rotationTranslY = H / 2F; |
|
|
|
|
|
|
|
} else if (rotation == 270) { |
|
|
|
|
|
|
|
rotationTranslX = H / 2F; |
|
|
|
|
|
|
|
rotationTranslY = H / 2F; |
|
|
|
|
|
|
|
Log.e("VideoTextureEncoder", "Rotation translY is" + rotationTranslY + ", h is " + H); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, 0, 0, 0); // vedo il lato destro e alto
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, 0, 0.5F, 0); // same
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, 0, -0.5F, 0); // peggio
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, 0.5F, 0, 0); // peggio: vedo il pixel in alto a dx
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, -0.5F, 0, 0); // ho aggiustato la VERTICALE
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, -0.5F, -1, 0); // no changes
|
|
|
|
|
|
|
|
// Matrix.translateM(transform, 0, -0.5F, 1, 0); // ci siamo quasi
|
|
|
|
|
|
|
|
Matrix.translateM(transform, 0, -0.5F, 1.75F, 0); // ottimo! ma è scalato male!!
|
|
|
|
|
|
|
|
Matrix.rotateM(transform, 0, rotation, 0, 0, 1); |
|
|
|
|
|
|
|
Matrix.translateM(transform, 0, rotationTranslX, rotationTranslY, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0); |
|
|
|
|
|
|
|
Matrix.scaleM(transform, 0, scaleX, scaleY, 1); |
|
|
|
|
|
|
|
|
|
|
|
encoder.mVideoEncoder.drainEncoder(false); |
|
|
|
encoder.mVideoEncoder.drainEncoder(false); |
|
|
|
encoder.mFullScreen.drawFrame(encoder.mTextureId, transform); |
|
|
|
encoder.mFullScreen.drawFrame(encoder.mTextureId, transform); |
|
|
|
encoder.mInputWindowSurface.setPresentationTime(timestamp); |
|
|
|
encoder.mInputWindowSurface.setPresentationTime(timestamp); |
|
|
|