| @@ -4,33 +4,6 @@ JUCE breaking changes | |||
| Develop | |||
| ======= | |||
| Change | |||
| ------ | |||
| CameraDevice::Listener::imageReceived() has been replaced by a new function | |||
| CameraDevice::takeStillPicture(). The callback passed in takeStillPicture() | |||
| will always be triggered on the message thread. | |||
| Possible Issues | |||
| --------------- | |||
| The code handling image capture needs to be adjusted to work well on a message | |||
| thread. This means that you should not perform any lengthy operations in your | |||
| callback, as this will stall your UI. | |||
| Workaround | |||
| ---------- | |||
| Use CameraDevice::takeStillPicture() instead of old listener callback. Pass | |||
| your lambda or other std::function compliant object to takeStillPicture which | |||
| will be called for you when the capture has finished. If you want to perform | |||
| any time consuming operation upon receiving the picture, schedule it on a | |||
| separate worker thread. | |||
| Rationale | |||
| --------- | |||
| The old Listener interface was not working in a typical listener pattern. It | |||
| feels more natural to request still picture capture with a dedicated function. | |||
| This is also more compliant with async mobile APIs. | |||
| Change | |||
| ------ | |||
| JUCE no longer supports OS X deployment targets earlier than 10.7. | |||
| @@ -180,6 +180,18 @@ void CameraDevice::stopRecording() | |||
| pimpl->stopRecording(); | |||
| } | |||
| void CameraDevice::addListener (Listener* listenerToAdd) | |||
| { | |||
| if (listenerToAdd != nullptr) | |||
| pimpl->addListener (listenerToAdd); | |||
| } | |||
| void CameraDevice::removeListener (Listener* listenerToRemove) | |||
| { | |||
| if (listenerToRemove != nullptr) | |||
| pimpl->removeListener (listenerToRemove); | |||
| } | |||
| //============================================================================== | |||
| StringArray CameraDevice::getAvailableDevices() | |||
| { | |||
| @@ -174,6 +174,48 @@ public: | |||
| and reopen the device to be able to use it further. */ | |||
| std::function<void (const String& /*error*/)> onErrorOccurred; | |||
| //============================================================================== | |||
| /** | |||
| Receives callbacks with individual frames from a CameraDevice. It is mainly | |||
| useful for processing multiple frames that has to be done as quickly as | |||
| possible. The callbacks can be called from any thread. | |||
| If you just need to take one picture, you should use takeStillPicture() instead. | |||
| @see CameraDevice::addListener | |||
| */ | |||
| class JUCE_API Listener | |||
| { | |||
| public: | |||
| Listener() {} | |||
| virtual ~Listener() {} | |||
| /** This method is called when a new image arrives. | |||
| This may be called by any thread, so be careful about thread-safety, | |||
| and make sure that you process the data as quickly as possible to | |||
| avoid glitching! | |||
| Simply add a listener to be continuously notified about new frames becoming | |||
| available and remove the listener when you no longer need new frames. | |||
| If you just need to take one picture, use takeStillPicture() instead. | |||
| @see CameraDevice::takeStillPicture | |||
| */ | |||
| virtual void imageReceived (const Image& image) = 0; | |||
| }; | |||
| /** Adds a listener to receive images from the camera. | |||
| Be very careful not to delete the listener without first removing it by calling | |||
| removeListener(). | |||
| */ | |||
| void addListener (Listener* listenerToAdd); | |||
| /** Removes a listener that was previously added with addListener(). */ | |||
| void removeListener (Listener* listenerToRemove); | |||
| private: | |||
| String name; | |||
| @@ -534,6 +534,9 @@ struct CameraDevice::Pimpl | |||
| void startRecordingToFile (const File&, int) {} | |||
| void stopRecording() {} | |||
| void addListener (CameraDevice::Listener*) {} | |||
| void removeListener (CameraDevice::Listener*) {} | |||
| String getCameraId() const noexcept { return {}; } | |||
| bool openedOk() const noexcept { return false; } | |||
| Time getTimeOfFirstRecordedFrame() const { return {}; } | |||
| @@ -668,6 +671,21 @@ struct CameraDevice::Pimpl | |||
| return results; | |||
| } | |||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.add (listenerToAdd); | |||
| if (listeners.size() == 1) | |||
| triggerStillPictureCapture(); | |||
| } | |||
| void removeListener (CameraDevice::Listener* listenerToRemove) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.remove (listenerToRemove); | |||
| } | |||
| private: | |||
| enum | |||
| { | |||
| @@ -1197,9 +1215,11 @@ private: | |||
| WeakReference<ImageReader> safeThis (this); | |||
| owner.callListeners (image); | |||
| // Android may take multiple pictures before it handles a request to stop. | |||
| if (hasNotifiedListeners.compareAndSetBool (1, 0)) | |||
| MessageManager::callAsync ([safeThis, image]() mutable { if (safeThis != nullptr) safeThis->owner.notifyImageReceived (image); }); | |||
| MessageManager::callAsync ([safeThis, image]() mutable { if (safeThis != nullptr) safeThis->owner.notifyPictureTaken (image); }); | |||
| } | |||
| struct ImageBuffer | |||
| @@ -1901,7 +1921,9 @@ private: | |||
| // Delay still picture capture for devices that can't handle it right after | |||
| // stopRepeating/abortCaptures calls. | |||
| delayedCaptureRunnable = GlobalRef (CreateJavaInterface (&runnable, "java/lang/Runnable").get()); | |||
| if (delayedCaptureRunnable.get() == nullptr) | |||
| delayedCaptureRunnable = GlobalRef (CreateJavaInterface (&runnable, "java/lang/Runnable").get()); | |||
| env->CallBooleanMethod (handler, AndroidHandler.postDelayed, delayedCaptureRunnable.get(), (jlong) 200); | |||
| } | |||
| @@ -1951,8 +1973,6 @@ private: | |||
| if (Pimpl::checkHasExceptionOccurred()) | |||
| return; | |||
| delayedCaptureRunnable.clear(); | |||
| // NB: for preview, using preview capture request again | |||
| env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest, previewCaptureRequest.get(), | |||
| nullptr, handler.get()); | |||
| @@ -2773,6 +2793,9 @@ private: | |||
| std::unique_ptr<ScopedCameraDevice> scopedCameraDevice; | |||
| CriticalSection listenerLock; | |||
| ListenerList<Listener> listeners; | |||
| std::function<void (const Image&)> pictureTakenCallback; | |||
| Time firstRecordedFrameTimeMs; | |||
| @@ -2888,9 +2911,16 @@ private: | |||
| cameraOpenCallback (cameraId, error); | |||
| } | |||
| void notifyImageReceived (const Image& image) | |||
| //============================================================================== | |||
| void callListeners (const Image& image) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.call ([=] (Listener& l) { l.imageReceived (image); }); | |||
| } | |||
| void notifyPictureTaken (const Image& image) | |||
| { | |||
| JUCE_CAMERA_LOG ("notifyImageReceived()"); | |||
| JUCE_CAMERA_LOG ("notifyPictureTaken()"); | |||
| if (pictureTakenCallback != nullptr) | |||
| pictureTakenCallback (image); | |||
| @@ -123,6 +123,21 @@ struct CameraDevice::Pimpl | |||
| return results; | |||
| } | |||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.add (listenerToAdd); | |||
| if (listeners.size() == 1) | |||
| triggerStillPictureCapture(); | |||
| } | |||
| void removeListener (CameraDevice::Listener* listenerToRemove) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.remove (listenerToRemove); | |||
| } | |||
| private: | |||
| static NSArray<AVCaptureDevice*>* getDevices() | |||
| { | |||
| @@ -512,11 +527,11 @@ private: | |||
| { | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wundeclared-selector" | |||
| addMethod (@selector (sessionDidStartRunning:), started, "v@:@"); | |||
| addMethod (@selector (sessionDidStopRunning:), stopped, "v@:@"); | |||
| addMethod (@selector (sessionRuntimeError:), runtimeError, "v@:@"); | |||
| addMethod (@selector (sessionWasInterrupted:), interrupted, "v@:@"); | |||
| addMethod (@selector (sessionDidStartRunning:), interruptionEnded, "v@:@"); | |||
| addMethod (@selector (sessionDidStartRunning:), started, "v@:@"); | |||
| addMethod (@selector (sessionDidStopRunning:), stopped, "v@:@"); | |||
| addMethod (@selector (sessionRuntimeError:), runtimeError, "v@:@"); | |||
| addMethod (@selector (sessionWasInterrupted:), interrupted, "v@:@"); | |||
| addMethod (@selector (sessionInterruptionEnded:), interruptionEnded, "v@:@"); | |||
| #pragma clang diagnostic pop | |||
| addIvar<CaptureSession*> ("owner"); | |||
| @@ -641,7 +656,10 @@ private: | |||
| NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: imageSampleBuffer]; | |||
| auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | |||
| MessageManager::callAsync ([this, image]() { imageTaken (image); }); | |||
| callListeners (image); | |||
| MessageManager::callAsync ([this, image]() { notifyPictureTaken (image); }); | |||
| }]; | |||
| } | |||
| else | |||
| @@ -815,7 +833,10 @@ private: | |||
| auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | |||
| auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | |||
| MessageManager::callAsync ([self, image]() { getOwner (self).imageTaken (image); }); | |||
| getOwner (self).callListeners (image); | |||
| MessageManager::callAsync ([self, image]() { getOwner (self).notifyPictureTaken (image); }); | |||
| } | |||
| static UIImage* getImageWithCorrectOrientation (CGImagePropertyOrientation imageOrientation, | |||
| @@ -912,7 +933,10 @@ private: | |||
| auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | |||
| auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | |||
| MessageManager::callAsync ([self, image]() { getOwner (self).imageTaken (image); }); | |||
| getOwner (self).callListeners (image); | |||
| MessageManager::callAsync ([self, image]() { getOwner (self).notifyPictureTaken (image); }); | |||
| } | |||
| static CGImagePropertyOrientation uiImageOrientationToCGImageOrientation (UIImageOrientation orientation) | |||
| @@ -932,11 +956,16 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| void imageTaken (const Image& image) | |||
| void callListeners (const Image& image) | |||
| { | |||
| captureSession.callListeners (image); | |||
| } | |||
| void notifyPictureTaken (const Image& image) | |||
| { | |||
| takingPicture = false; | |||
| captureSession.notifyImageReceived (image); | |||
| captureSession.notifyPictureTaken (image); | |||
| } | |||
| CaptureSession& captureSession; | |||
| @@ -1105,9 +1134,14 @@ private: | |||
| owner.cameraSessionRuntimeError (error); | |||
| } | |||
| void notifyImageReceived (const Image& image) | |||
| void callListeners (const Image& image) | |||
| { | |||
| owner.notifyImageReceived (image); | |||
| owner.callListeners (image); | |||
| } | |||
| void notifyPictureTaken (const Image& image) | |||
| { | |||
| owner.notifyPictureTaken (image); | |||
| } | |||
| Pimpl& owner; | |||
| @@ -1152,9 +1186,15 @@ private: | |||
| } | |||
| } | |||
| void notifyImageReceived (const Image& image) | |||
| void callListeners (const Image& image) | |||
| { | |||
| JUCE_CAMERA_LOG ("notifyImageReceived()"); | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.call ([=] (Listener& l) { l.imageReceived (image); }); | |||
| } | |||
| void notifyPictureTaken (const Image& image) | |||
| { | |||
| JUCE_CAMERA_LOG ("notifyPictureTaken()"); | |||
| if (pictureTakenCallback != nullptr) | |||
| pictureTakenCallback (image); | |||
| @@ -1171,6 +1211,9 @@ private: | |||
| String cameraId; | |||
| InternalOpenCameraResultCallback cameraOpenCallback; | |||
| CriticalSection listenerLock; | |||
| ListenerList<Listener> listeners; | |||
| std::function<void (const Image&)> pictureTakenCallback; | |||
| CaptureSession captureSession; | |||
| @@ -175,8 +175,8 @@ struct CameraDevice::Pimpl | |||
| void handleImageCapture (const Image& image) | |||
| { | |||
| if (pictureTakenCallback != nullptr) | |||
| pictureTakenCallback (image); | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.call ([=] (Listener& l) { l.imageReceived (image); }); | |||
| } | |||
| void triggerImageCapture() | |||
| @@ -199,12 +199,33 @@ struct CameraDevice::Pimpl | |||
| auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | |||
| handleImageCapture (image); | |||
| WeakReference<Pimpl> weakRef (this); | |||
| MessageManager::callAsync ([weakRef, image]() mutable { if (weakRef != nullptr) weakRef->handleImageCapture (image); }); | |||
| MessageManager::callAsync ([weakRef, image]() mutable | |||
| { | |||
| if (weakRef != nullptr && weakRef->pictureTakenCallback != nullptr) | |||
| weakRef->pictureTakenCallback (image); | |||
| }); | |||
| }]; | |||
| } | |||
| } | |||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.add (listenerToAdd); | |||
| if (listeners.size() == 1) | |||
| triggerImageCapture(); | |||
| } | |||
| void removeListener (CameraDevice::Listener* listenerToRemove) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.remove (listenerToRemove); | |||
| } | |||
| static StringArray getAvailableDevices() | |||
| { | |||
| StringArray results; | |||
| @@ -231,6 +252,9 @@ struct CameraDevice::Pimpl | |||
| Time firstPresentationTime; | |||
| bool isRecording = false; | |||
| CriticalSection listenerLock; | |||
| ListenerList<Listener> listeners; | |||
| std::function<void (const Image&)> pictureTakenCallback; | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | |||
| @@ -195,7 +195,7 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||
| void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse) | |||
| { | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| const ScopedLock sl (pictureTakenCallbackLock); | |||
| jassert (pictureTakenCallbackToUse != nullptr); | |||
| @@ -229,10 +229,35 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||
| return firstRecordedTime; | |||
| } | |||
| void notifyImageReceivedIfNeeded (const Image& image) | |||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| if (listeners.size() == 0) | |||
| addUser(); | |||
| listeners.add (listenerToAdd); | |||
| } | |||
| void removeListener (CameraDevice::Listener* listenerToRemove) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.remove (listenerToRemove); | |||
| if (listeners.size() == 0) | |||
| removeUser(); | |||
| } | |||
| void callListeners (const Image& image) | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| listeners.call ([=] (Listener& l) { l.imageReceived (image); }); | |||
| } | |||
| void notifyPictureTakenIfNeeded (const Image& image) | |||
| { | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| const ScopedLock sl (pictureTakenCallbackLock); | |||
| if (pictureTakenCallback == nullptr) | |||
| return; | |||
| @@ -305,7 +330,10 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||
| imageNeedsFlipping = true; | |||
| } | |||
| notifyImageReceivedIfNeeded (loadingImage); | |||
| if (listeners.size() > 0) | |||
| callListeners (loadingImage); | |||
| notifyPictureTakenIfNeeded (loadingImage); | |||
| sendChangeMessage(); | |||
| } | |||
| @@ -534,6 +562,12 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||
| ComSmartPtr<GrabberCallback> callback; | |||
| CriticalSection listenerLock; | |||
| ListenerList<Listener> listeners; | |||
| CriticalSection pictureTakenCallbackLock; | |||
| std::function<void (const Image&)> pictureTakenCallback; | |||
| bool isRecording, openedSuccessfully; | |||
| int width, height; | |||
| Time firstRecordedTime; | |||
| @@ -557,9 +591,6 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||
| bool recordNextFrameTime; | |||
| int previewMaxFPS; | |||
| CriticalSection callbackLock; | |||
| std::function<void (const Image&)> pictureTakenCallback; | |||
| JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | |||
| private: | |||