Index: talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java |
diff --git a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java |
index caeb389137361cf4fcc7faa8df7a321624be8f73..36f60edd5cf70183528862b97191795851d98fbd 100644 |
--- a/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java |
+++ b/talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java |
@@ -77,7 +77,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
private final Object cameraIdLock = new Object(); |
private int id; |
private android.hardware.Camera.CameraInfo info; |
- private final FramePool videoBuffers; |
private final CameraStatistics cameraStatistics; |
// Remember the requested format in case we want to switch cameras. |
private int requestedWidth; |
@@ -90,8 +89,13 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
private CapturerObserver frameObserver = null; |
private final CameraEventsHandler eventsHandler; |
private boolean firstFrameReported; |
+ // Arbitrary queue depth. Higher number means more memory allocated & held, |
+ // lower number means more sensitivity to processing time in the client (and |
+ // potentially stalling the capturer if it runs out of buffers to write to). |
+ private static final int NUMBER_OF_CAPTURE_BUFFERS = 3; |
+ private final Set<byte[]> queuedBuffers = new HashSet<byte[]>(); |
private final boolean isCapturingToTexture; |
- private final SurfaceTextureHelper surfaceHelper; |
+ final SurfaceTextureHelper surfaceHelper; // Package visible for testing purposes. |
// The camera API can output one old frame after the camera has been switched or the resolution |
// has been changed. This flag is used for dropping the first frame after camera restart. |
private boolean dropNextFrame = false; |
@@ -129,14 +133,14 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2) |
/ CAMERA_OBSERVER_PERIOD_MS; |
- Logging.d(TAG, "Camera fps: " + cameraFps + |
- ". Pending buffers: " + cameraStatistics.pendingFramesTimeStamps()); |
+ Logging.d(TAG, "Camera fps: " + cameraFps +"."); |
if (cameraFramesCount == 0) { |
++freezePeriodCount; |
if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount > CAMERA_FREEZE_REPORT_TIMOUT_MS |
&& eventsHandler != null) { |
Logging.e(TAG, "Camera freezed."); |
- if (cameraStatistics.pendingFramesCount() == cameraStatistics.maxPendingFrames) { |
+ if (surfaceHelper.isTextureInUse()) { |
+ // This can only happen if we are capturing to textures. |
eventsHandler.onCameraFreezed("Camera failure. Client must return video buffers."); |
} else { |
eventsHandler.onCameraFreezed("Camera failure."); |
@@ -153,27 +157,14 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
private static class CameraStatistics { |
private int frameCount = 0; |
private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker(); |
- private final Set<Long> timeStampsNs = new HashSet<Long>(); |
- public final int maxPendingFrames; |
- CameraStatistics(int maxPendingFrames) { |
- this.maxPendingFrames = maxPendingFrames; |
+ CameraStatistics() { |
threadChecker.detachThread(); |
} |
- public void addPendingFrame(long timestamp) { |
+ public void addFrame() { |
threadChecker.checkIsOnValidThread(); |
++frameCount; |
- timeStampsNs.add(timestamp); |
- } |
- |
- public void frameReturned(long timestamp) { |
- threadChecker.checkIsOnValidThread(); |
- if (!timeStampsNs.contains(timestamp)) { |
- throw new IllegalStateException( |
- "CameraStatistics.frameReturned called with unknown timestamp " + timestamp); |
- } |
- timeStampsNs.remove(timestamp); |
} |
public int getAndResetFrameCount() { |
@@ -182,21 +173,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
frameCount = 0; |
return count; |
} |
- |
- // Return number of pending frames that have not been returned. |
- public int pendingFramesCount() { |
- threadChecker.checkIsOnValidThread(); |
- return timeStampsNs.size(); |
- } |
- |
- public String pendingFramesTimeStamps() { |
- threadChecker.checkIsOnValidThread(); |
- List<Long> timeStampsMs = new ArrayList<Long>(); |
- for (long ts : timeStampsNs) { |
- timeStampsMs.add(TimeUnit.NANOSECONDS.toMillis(ts)); |
- } |
- return timeStampsMs.toString(); |
- } |
} |
public static interface CameraEventsHandler { |
@@ -350,20 +326,18 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
private VideoCapturerAndroid(int cameraId, CameraEventsHandler eventsHandler, |
EglBase.Context sharedContext) { |
- Logging.d(TAG, "VideoCapturerAndroid"); |
this.id = cameraId; |
this.eventsHandler = eventsHandler; |
cameraThread = new HandlerThread(TAG); |
cameraThread.start(); |
cameraThreadHandler = new Handler(cameraThread.getLooper()); |
- videoBuffers = new FramePool(cameraThread); |
isCapturingToTexture = (sharedContext != null); |
- cameraStatistics = |
- new CameraStatistics(isCapturingToTexture ? 1 : FramePool.NUMBER_OF_CAPTURE_BUFFERS); |
+ cameraStatistics = new CameraStatistics(); |
surfaceHelper = SurfaceTextureHelper.create(sharedContext, cameraThreadHandler); |
if (isCapturingToTexture) { |
surfaceHelper.setListener(this); |
} |
+ Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingToTexture); |
} |
private void checkIsOnCameraThread() { |
@@ -403,9 +377,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
if (camera != null) { |
throw new IllegalStateException("Release called while camera is running"); |
} |
- if (cameraStatistics.pendingFramesCount() != 0) { |
- throw new IllegalStateException("Release called with pending frames left"); |
- } |
} |
}); |
surfaceHelper.disconnect(cameraThreadHandler); |
@@ -582,7 +553,13 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
camera.setParameters(parameters); |
if (!isCapturingToTexture) { |
- videoBuffers.queueCameraBuffers(captureFormat.frameSize(), camera); |
+ queuedBuffers.clear(); |
+ final int frameSize = captureFormat.frameSize(); |
+ for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { |
+ final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); |
+ queuedBuffers.add(buffer.array()); |
+ camera.addCallbackBuffer(buffer.array()); |
+ } |
camera.setPreviewCallbackWithBuffer(this); |
} |
camera.startPreview(); |
@@ -619,13 +596,7 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
Logging.d(TAG, "Stop preview."); |
camera.stopPreview(); |
camera.setPreviewCallbackWithBuffer(null); |
- if (!isCapturingToTexture()) { |
- videoBuffers.stopReturnBuffersToCamera(); |
- Logging.d(TAG, "stopReturnBuffersToCamera called." |
- + (cameraStatistics.pendingFramesCount() == 0? |
- " All buffers have been returned." |
- : " Pending buffers: " + cameraStatistics.pendingFramesTimeStamps() + ".")); |
- } |
+ queuedBuffers.clear(); |
captureFormat = null; |
Logging.d(TAG, "Release camera."); |
@@ -665,19 +636,6 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
return cameraThreadHandler; |
} |
- public void returnBuffer(final long timeStamp) { |
- cameraThreadHandler.post(new Runnable() { |
- @Override public void run() { |
- cameraStatistics.frameReturned(timeStamp); |
- if (isCapturingToTexture) { |
- surfaceHelper.returnTextureFrame(); |
- } else { |
- videoBuffers.returnBuffer(timeStamp); |
- } |
- } |
- }); |
- } |
- |
private int getDeviceOrientation() { |
int orientation = 0; |
@@ -713,7 +671,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
@Override |
public void onPreviewFrame(byte[] data, android.hardware.Camera callbackCamera) { |
checkIsOnCameraThread(); |
- if (camera == null) { |
+ if (camera == null || !queuedBuffers.contains(data)) { |
+ // The camera has been stopped or |data| is an old invalid buffer. |
return; |
} |
if (camera != callbackCamera) { |
@@ -728,16 +687,10 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
firstFrameReported = true; |
} |
- // Mark the frame owning |data| as used. |
- // Note that since data is directBuffer, |
- // data.length >= videoBuffers.frameSize. |
- if (videoBuffers.reserveByteBuffer(data, captureTimeNs)) { |
- cameraStatistics.addPendingFrame(captureTimeNs); |
- frameObserver.onByteBufferFrameCaptured(data, videoBuffers.frameSize, captureFormat.width, |
- captureFormat.height, getFrameOrientation(), captureTimeNs); |
- } else { |
- Logging.w(TAG, "reserveByteBuffer failed - dropping frame."); |
- } |
+ cameraStatistics.addFrame(); |
+ frameObserver.onByteBufferFrameCaptured(data, captureFormat.width, captureFormat.height, |
+ getFrameOrientation(), captureTimeNs); |
+ camera.addCallbackBuffer(data); |
} |
@Override |
@@ -762,121 +715,11 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
transformMatrix = |
RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.horizontalFlipMatrix()); |
} |
- cameraStatistics.addPendingFrame(timestampNs); |
- |
+ cameraStatistics.addFrame(); |
frameObserver.onTextureFrameCaptured(captureFormat.width, captureFormat.height, oesTextureId, |
transformMatrix, rotation, timestampNs); |
} |
- // Class used for allocating and bookkeeping video frames. All buffers are |
- // direct allocated so that they can be directly used from native code. This class is |
- // not thread-safe, and enforces single thread use. |
- private static class FramePool { |
- // Thread that all calls should be made on. |
- private final Thread thread; |
- // Arbitrary queue depth. Higher number means more memory allocated & held, |
- // lower number means more sensitivity to processing time in the client (and |
- // potentially stalling the capturer if it runs out of buffers to write to). |
- public static final int NUMBER_OF_CAPTURE_BUFFERS = 3; |
- // This container tracks the buffers added as camera callback buffers. It is needed for finding |
- // the corresponding ByteBuffer given a byte[]. |
- private final Map<byte[], ByteBuffer> queuedBuffers = new IdentityHashMap<byte[], ByteBuffer>(); |
- // This container tracks the frames that have been sent but not returned. It is needed for |
- // keeping the buffers alive and for finding the corresponding ByteBuffer given a timestamp. |
- private final Map<Long, ByteBuffer> pendingBuffers = new HashMap<Long, ByteBuffer>(); |
- private int frameSize = 0; |
- private android.hardware.Camera camera; |
- |
- public FramePool(Thread thread) { |
- this.thread = thread; |
- } |
- |
- private void checkIsOnValidThread() { |
- if (Thread.currentThread() != thread) { |
- throw new IllegalStateException("Wrong thread"); |
- } |
- } |
- |
- // Discards previous queued buffers and adds new callback buffers to camera. |
- public void queueCameraBuffers(int frameSize, android.hardware.Camera camera) { |
- checkIsOnValidThread(); |
- this.camera = camera; |
- this.frameSize = frameSize; |
- |
- queuedBuffers.clear(); |
- for (int i = 0; i < NUMBER_OF_CAPTURE_BUFFERS; ++i) { |
- final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize); |
- camera.addCallbackBuffer(buffer.array()); |
- queuedBuffers.put(buffer.array(), buffer); |
- } |
- Logging.d(TAG, "queueCameraBuffers enqueued " + NUMBER_OF_CAPTURE_BUFFERS |
- + " buffers of size " + frameSize + "."); |
- } |
- |
- public void stopReturnBuffersToCamera() { |
- checkIsOnValidThread(); |
- this.camera = null; |
- queuedBuffers.clear(); |
- // Frames in |pendingBuffers| need to be kept alive until they are returned. |
- } |
- |
- public boolean reserveByteBuffer(byte[] data, long timeStamp) { |
- checkIsOnValidThread(); |
- final ByteBuffer buffer = queuedBuffers.remove(data); |
- if (buffer == null) { |
- // Frames might be posted to |onPreviewFrame| with the previous format while changing |
- // capture format in |startPreviewOnCameraThread|. Drop these old frames. |
- Logging.w(TAG, "Received callback buffer from previous configuration with length: " |
- + (data == null ? "null" : data.length)); |
- return false; |
- } |
- if (buffer.capacity() != frameSize) { |
- throw new IllegalStateException("Callback buffer has unexpected frame size"); |
- } |
- if (pendingBuffers.containsKey(timeStamp)) { |
- Logging.e(TAG, "Timestamp already present in pending buffers - they need to be unique"); |
- return false; |
- } |
- pendingBuffers.put(timeStamp, buffer); |
- if (queuedBuffers.isEmpty()) { |
- Logging.d(TAG, "Camera is running out of capture buffers."); |
- } |
- return true; |
- } |
- |
- public void returnBuffer(long timeStamp) { |
- checkIsOnValidThread(); |
- final ByteBuffer returnedFrame = pendingBuffers.remove(timeStamp); |
- if (returnedFrame == null) { |
- throw new RuntimeException("unknown data buffer with time stamp " |
- + timeStamp + "returned?!?"); |
- } |
- |
- if (camera != null && returnedFrame.capacity() == frameSize) { |
- camera.addCallbackBuffer(returnedFrame.array()); |
- if (queuedBuffers.isEmpty()) { |
- Logging.d(TAG, "Frame returned when camera is running out of capture" |
- + " buffers for TS " + TimeUnit.NANOSECONDS.toMillis(timeStamp)); |
- } |
- queuedBuffers.put(returnedFrame.array(), returnedFrame); |
- return; |
- } |
- |
- if (returnedFrame.capacity() != frameSize) { |
- Logging.d(TAG, "returnBuffer with time stamp " |
- + TimeUnit.NANOSECONDS.toMillis(timeStamp) |
- + " called with old frame size, " + returnedFrame.capacity() + "."); |
- // Since this frame has the wrong size, don't requeue it. Frames with the correct size are |
- // created in queueCameraBuffers so this must be an old buffer. |
- return; |
- } |
- |
- Logging.d(TAG, "returnBuffer with time stamp " |
- + TimeUnit.NANOSECONDS.toMillis(timeStamp) |
- + " called after camera has been stopped."); |
- } |
- } |
- |
// Interface used for providing callbacks to an observer. |
interface CapturerObserver { |
// Notify if the camera have been started successfully or not. |
@@ -885,8 +728,8 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
// Delivers a captured frame. Called on a Java thread owned by |
// VideoCapturerAndroid. |
- abstract void onByteBufferFrameCaptured(byte[] data, int length, int width, int height, |
- int rotation, long timeStamp); |
+ abstract void onByteBufferFrameCaptured(byte[] data, int width, int height, int rotation, |
+ long timeStamp); |
// Delivers a captured frame in a texture with id |oesTextureId|. Called on a Java thread |
// owned by VideoCapturerAndroid. |
@@ -915,9 +758,9 @@ public class VideoCapturerAndroid extends VideoCapturer implements |
} |
@Override |
- public void onByteBufferFrameCaptured(byte[] data, int length, int width, int height, |
+ public void onByteBufferFrameCaptured(byte[] data, int width, int height, |
int rotation, long timeStamp) { |
- nativeOnByteBufferFrameCaptured(nativeCapturer, data, length, width, height, rotation, |
+ nativeOnByteBufferFrameCaptured(nativeCapturer, data, data.length, width, height, rotation, |
timeStamp); |
} |