Small improvements

pull/559/head
Mattia Iavarone 6 years ago
parent 802b6915a8
commit 29ee5cc0fe
  1. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  2. 20
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  3. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  4. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererFrameCallback.java
  5. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  6. 153
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java

@ -67,6 +67,8 @@ public class EglViewport {
}
public void setFilter(@NonNull Filter filter) {
// TODO see if this is needed. If setFilter is always called from the correct GL thread,
// we don't need to wait for a new draw call (which might not even happen).
mPendingFilter = filter;
}

@ -95,6 +95,12 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
SnapshotGlPictureRecorder.this.onRendererTextureCreated(textureId);
}
@RendererThread
@Override
public void onRendererFilterChanged(@NonNull Filter filter) {
SnapshotGlPictureRecorder.this.onRendererFilterChanged(filter);
}
@RendererThread
@Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
@ -102,10 +108,6 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY);
}
@Override
public void onFilterChanged(@NonNull Filter filter) {
mViewport.setFilter(filter.copy());
}
});
}
@ -125,6 +127,12 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
}
}
@RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT)
private void onRendererFilterChanged(@NonNull Filter filter) {
mViewport.setFilter(filter.copy());
}
/**
* The tricky part here is the EGL surface creation.
*
@ -150,7 +158,9 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
*/
@RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT)
private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
private void onRendererFrame(@SuppressWarnings("unused") @NonNull SurfaceTexture surfaceTexture,
final float scaleX,
final float scaleY) {
// Get egl context from the RendererThread, which is the one in which we have created
// the textureId and the overlayTextureId, managed by the GlSurfaceView.
// Next operations can then be performed on different threads using this handle.

@ -141,7 +141,9 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
@RendererThread
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
if (mCurrentFilter == null) mCurrentFilter = new NoFilter();
if (mCurrentFilter == null) {
mCurrentFilter = new NoFilter();
}
mOutputViewport = new EglViewport(mCurrentFilter);
mOutputTextureId = mOutputViewport.createTexture();
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId);
@ -282,7 +284,7 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
public void run() {
mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
callback.onFilterChanged(mCurrentFilter);
callback.onRendererFilterChanged(mCurrentFilter);
}
});
}
@ -325,20 +327,26 @@ public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceT
}
@Override
public void setFilter(@NonNull Filter filter) {
public void setFilter(final @NonNull Filter filter) {
mCurrentFilter = filter;
if (hasSurface()) {
filter.setSize(mOutputSurfaceWidth, mOutputSurfaceHeight);
}
mCurrentFilter = filter;
if (mOutputViewport != null) {
mOutputViewport.setFilter(filter);
}
// Need to synchronize when iterating the Collections.synchronizedSet
synchronized (mRendererFrameCallbacks) {
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onFilterChanged(filter);
getView().queueEvent(new Runnable() {
@Override
public void run() {
if (mOutputViewport != null) {
mOutputViewport.setFilter(filter);
}
// Need to synchronize when iterating the Collections.synchronizedSet
synchronized (mRendererFrameCallbacks) {
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFilterChanged(filter);
}
}
}
}
});
}
}

@ -33,10 +33,11 @@ public interface RendererFrameCallback {
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY);
/**
* Called on the change of shader filter
* We should update the EglViewPort once you receives this event
* Called when the renderer filter changes. This is guaranteed to be called at least once
* before the first {@link #onRendererFrame(SurfaceTexture, float, float)}.
*
* @param filter the new filter applied
* @param filter the new filter
*/
void onFilterChanged(@NonNull Filter filter);
@RendererThread
void onRendererFilterChanged(@NonNull Filter filter);
}

@ -104,7 +104,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
}
@Override
public void onFilterChanged(@NonNull Filter filter) {
public void onRendererFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter.copy();
if (mEncoderEngine != null) {
mCurrentFilter.setSize(mResult.size.getWidth(), mResult.size.getHeight());

@ -134,85 +134,92 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
@EncoderThread
@Override
protected void onEvent(@NonNull String event, @Nullable Object data) {
switch (event) {
case FILTER_EVENT:
//noinspection ConstantConditions
onFilter((Filter) data);
break;
case FRAME_EVENT:
//noinspection ConstantConditions
onFrame((Frame) data);
break;
}
}
if (event.equals(FILTER_EVENT)) {
Filter filter = (Filter) data;
mViewport.setFilter(filter);
} else if (event.equals(FRAME_EVENT)) {
Frame frame = (Frame) data;
if (frame == null) {
throw new IllegalArgumentException("Got null frame for FRAME_EVENT.");
}
if (!shouldRenderFrame(frame.timestampUs())) {
mFramePool.recycle(frame);
return;
}
private void onFilter(@NonNull Filter filter) {
mViewport.setFilter(filter);
}
// Notify we're got the first frame and its absolute time.
if (mFrameNumber == 1) {
notifyFirstFrameMillis(frame.timestampMillis);
}
private void onFrame(@NonNull Frame frame) {
if (!shouldRenderFrame(frame.timestampUs())) {
mFramePool.recycle(frame);
return;
}
// Notify we have reached the max length value.
if (mFirstTimeUs == Long.MIN_VALUE) mFirstTimeUs = frame.timestampUs();
if (!hasReachedMaxLength()) {
boolean didReachMaxLength = (frame.timestampUs() - mFirstTimeUs) > getMaxLengthUs();
if (didReachMaxLength) {
LOG.w("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"firstTimeUs:", mFirstTimeUs,
"- reached max length! deltaUs:", frame.timestampUs() - mFirstTimeUs);
notifyMaxLengthReached();
}
}
// Notify we're got the first frame and its absolute time.
if (mFrameNumber == 1) {
notifyFirstFrameMillis(frame.timestampMillis);
}
// First, drain any previous data.
LOG.i("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"- draining.");
drainOutput(false);
// Then draw on the surface.
LOG.i("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"- rendering.");
// 1. 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.
float[] transform = frame.transform;
float scaleX = mConfig.scaleX;
float scaleY = mConfig.scaleY;
float scaleTranslX = (1F - scaleX) / 2F;
float scaleTranslY = (1F - scaleY) / 2F;
Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0);
Matrix.scaleM(transform, 0, scaleX, scaleY, 1);
// 2. 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
// translate to origin, rotate, then back to where we were.
Matrix.translateM(transform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(transform, 0, mTransformRotation, 0, 0, 1);
Matrix.translateM(transform, 0, -0.5F, -0.5F, 0);
// 3. Do the same for overlays with their own rotation.
if (mConfig.hasOverlay()) {
mConfig.overlayDrawer.draw(mConfig.overlayTarget);
Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, 0.5F, 0.5F, 0);
Matrix.rotateM(mConfig.overlayDrawer.getTransform(), 0, mConfig.overlayRotation, 0, 0, 1);
Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, -0.5F, -0.5F, 0);
// Notify we have reached the max length value.
if (mFirstTimeUs == Long.MIN_VALUE) mFirstTimeUs = frame.timestampUs();
if (!hasReachedMaxLength()) {
boolean didReachMaxLength = (frame.timestampUs() - mFirstTimeUs) > getMaxLengthUs();
if (didReachMaxLength) {
LOG.w("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"firstTimeUs:", mFirstTimeUs,
"- reached max length! deltaUs:", frame.timestampUs() - mFirstTimeUs);
notifyMaxLengthReached();
}
mViewport.drawFrame(mConfig.textureId, transform);
if (mConfig.hasOverlay()) {
mConfig.overlayDrawer.render();
}
mWindow.setPresentationTime(frame.timestampNanos);
mWindow.swapBuffers();
mFramePool.recycle(frame);
}
// First, drain any previous data.
LOG.i("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"- draining.");
drainOutput(false);
// Then draw on the surface.
LOG.i("onEvent -",
"frameNumber:", mFrameNumber,
"timestampUs:", frame.timestampUs(),
"- rendering.");
// 1. 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.
float[] transform = frame.transform;
float scaleX = mConfig.scaleX;
float scaleY = mConfig.scaleY;
float scaleTranslX = (1F - scaleX) / 2F;
float scaleTranslY = (1F - scaleY) / 2F;
Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0);
Matrix.scaleM(transform, 0, scaleX, scaleY, 1);
// 2. 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
// translate to origin, rotate, then back to where we were.
Matrix.translateM(transform, 0, 0.5F, 0.5F, 0);
Matrix.rotateM(transform, 0, mTransformRotation, 0, 0, 1);
Matrix.translateM(transform, 0, -0.5F, -0.5F, 0);
// 3. Do the same for overlays with their own rotation.
if (mConfig.hasOverlay()) {
mConfig.overlayDrawer.draw(mConfig.overlayTarget);
Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, 0.5F, 0.5F, 0);
Matrix.rotateM(mConfig.overlayDrawer.getTransform(), 0, mConfig.overlayRotation, 0, 0, 1);
Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, -0.5F, -0.5F, 0);
}
mViewport.drawFrame(mConfig.textureId, transform);
if (mConfig.hasOverlay()) {
mConfig.overlayDrawer.render();
}
mWindow.setPresentationTime(frame.timestampNanos);
mWindow.swapBuffers();
mFramePool.recycle(frame);
}
@Override

Loading…
Cancel
Save