@@ -4,33 +4,6 @@ JUCE breaking changes | |||||
Develop | 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 | Change | ||||
------ | ------ | ||||
JUCE no longer supports OS X deployment targets earlier than 10.7. | JUCE no longer supports OS X deployment targets earlier than 10.7. | ||||
@@ -180,6 +180,18 @@ void CameraDevice::stopRecording() | |||||
pimpl->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() | StringArray CameraDevice::getAvailableDevices() | ||||
{ | { | ||||
@@ -174,6 +174,48 @@ public: | |||||
and reopen the device to be able to use it further. */ | and reopen the device to be able to use it further. */ | ||||
std::function<void (const String& /*error*/)> onErrorOccurred; | 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: | private: | ||||
String name; | String name; | ||||
@@ -534,6 +534,9 @@ struct CameraDevice::Pimpl | |||||
void startRecordingToFile (const File&, int) {} | void startRecordingToFile (const File&, int) {} | ||||
void stopRecording() {} | void stopRecording() {} | ||||
void addListener (CameraDevice::Listener*) {} | |||||
void removeListener (CameraDevice::Listener*) {} | |||||
String getCameraId() const noexcept { return {}; } | String getCameraId() const noexcept { return {}; } | ||||
bool openedOk() const noexcept { return false; } | bool openedOk() const noexcept { return false; } | ||||
Time getTimeOfFirstRecordedFrame() const { return {}; } | Time getTimeOfFirstRecordedFrame() const { return {}; } | ||||
@@ -668,6 +671,21 @@ struct CameraDevice::Pimpl | |||||
return results; | 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: | private: | ||||
enum | enum | ||||
{ | { | ||||
@@ -1197,9 +1215,11 @@ private: | |||||
WeakReference<ImageReader> safeThis (this); | WeakReference<ImageReader> safeThis (this); | ||||
owner.callListeners (image); | |||||
// Android may take multiple pictures before it handles a request to stop. | // Android may take multiple pictures before it handles a request to stop. | ||||
if (hasNotifiedListeners.compareAndSetBool (1, 0)) | 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 | struct ImageBuffer | ||||
@@ -1901,7 +1921,9 @@ private: | |||||
// Delay still picture capture for devices that can't handle it right after | // Delay still picture capture for devices that can't handle it right after | ||||
// stopRepeating/abortCaptures calls. | // 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); | env->CallBooleanMethod (handler, AndroidHandler.postDelayed, delayedCaptureRunnable.get(), (jlong) 200); | ||||
} | } | ||||
@@ -1951,8 +1973,6 @@ private: | |||||
if (Pimpl::checkHasExceptionOccurred()) | if (Pimpl::checkHasExceptionOccurred()) | ||||
return; | return; | ||||
delayedCaptureRunnable.clear(); | |||||
// NB: for preview, using preview capture request again | // NB: for preview, using preview capture request again | ||||
env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest, previewCaptureRequest.get(), | env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest, previewCaptureRequest.get(), | ||||
nullptr, handler.get()); | nullptr, handler.get()); | ||||
@@ -2773,6 +2793,9 @@ private: | |||||
std::unique_ptr<ScopedCameraDevice> scopedCameraDevice; | std::unique_ptr<ScopedCameraDevice> scopedCameraDevice; | ||||
CriticalSection listenerLock; | |||||
ListenerList<Listener> listeners; | |||||
std::function<void (const Image&)> pictureTakenCallback; | std::function<void (const Image&)> pictureTakenCallback; | ||||
Time firstRecordedFrameTimeMs; | Time firstRecordedFrameTimeMs; | ||||
@@ -2888,9 +2911,16 @@ private: | |||||
cameraOpenCallback (cameraId, error); | 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) | if (pictureTakenCallback != nullptr) | ||||
pictureTakenCallback (image); | pictureTakenCallback (image); | ||||
@@ -123,6 +123,21 @@ struct CameraDevice::Pimpl | |||||
return results; | 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: | private: | ||||
static NSArray<AVCaptureDevice*>* getDevices() | static NSArray<AVCaptureDevice*>* getDevices() | ||||
{ | { | ||||
@@ -512,11 +527,11 @@ private: | |||||
{ | { | ||||
#pragma clang diagnostic push | #pragma clang diagnostic push | ||||
#pragma clang diagnostic ignored "-Wundeclared-selector" | #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 | #pragma clang diagnostic pop | ||||
addIvar<CaptureSession*> ("owner"); | addIvar<CaptureSession*> ("owner"); | ||||
@@ -641,7 +656,10 @@ private: | |||||
NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: imageSampleBuffer]; | NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: imageSampleBuffer]; | ||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | 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 | else | ||||
@@ -815,7 +833,10 @@ private: | |||||
auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | ||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | 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, | static UIImage* getImageWithCorrectOrientation (CGImagePropertyOrientation imageOrientation, | ||||
@@ -912,7 +933,10 @@ private: | |||||
auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | auto* imageData = UIImageJPEGRepresentation (uiImage, 0.f); | ||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | 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) | 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; | takingPicture = false; | ||||
captureSession.notifyImageReceived (image); | |||||
captureSession.notifyPictureTaken (image); | |||||
} | } | ||||
CaptureSession& captureSession; | CaptureSession& captureSession; | ||||
@@ -1105,9 +1134,14 @@ private: | |||||
owner.cameraSessionRuntimeError (error); | 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; | 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) | if (pictureTakenCallback != nullptr) | ||||
pictureTakenCallback (image); | pictureTakenCallback (image); | ||||
@@ -1171,6 +1211,9 @@ private: | |||||
String cameraId; | String cameraId; | ||||
InternalOpenCameraResultCallback cameraOpenCallback; | InternalOpenCameraResultCallback cameraOpenCallback; | ||||
CriticalSection listenerLock; | |||||
ListenerList<Listener> listeners; | |||||
std::function<void (const Image&)> pictureTakenCallback; | std::function<void (const Image&)> pictureTakenCallback; | ||||
CaptureSession captureSession; | CaptureSession captureSession; | ||||
@@ -175,8 +175,8 @@ struct CameraDevice::Pimpl | |||||
void handleImageCapture (const Image& image) | void handleImageCapture (const Image& image) | ||||
{ | { | ||||
if (pictureTakenCallback != nullptr) | |||||
pictureTakenCallback (image); | |||||
const ScopedLock sl (listenerLock); | |||||
listeners.call ([=] (Listener& l) { l.imageReceived (image); }); | |||||
} | } | ||||
void triggerImageCapture() | void triggerImageCapture() | ||||
@@ -199,12 +199,33 @@ struct CameraDevice::Pimpl | |||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length); | ||||
handleImageCapture (image); | |||||
WeakReference<Pimpl> weakRef (this); | 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() | static StringArray getAvailableDevices() | ||||
{ | { | ||||
StringArray results; | StringArray results; | ||||
@@ -231,6 +252,9 @@ struct CameraDevice::Pimpl | |||||
Time firstPresentationTime; | Time firstPresentationTime; | ||||
bool isRecording = false; | bool isRecording = false; | ||||
CriticalSection listenerLock; | |||||
ListenerList<Listener> listeners; | |||||
std::function<void (const Image&)> pictureTakenCallback; | std::function<void (const Image&)> pictureTakenCallback; | ||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | ||||
@@ -195,7 +195,7 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse) | void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse) | ||||
{ | { | ||||
{ | { | ||||
const ScopedLock sl (callbackLock); | |||||
const ScopedLock sl (pictureTakenCallbackLock); | |||||
jassert (pictureTakenCallbackToUse != nullptr); | jassert (pictureTakenCallbackToUse != nullptr); | ||||
@@ -229,10 +229,35 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
return firstRecordedTime; | 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) | if (pictureTakenCallback == nullptr) | ||||
return; | return; | ||||
@@ -305,7 +330,10 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
imageNeedsFlipping = true; | imageNeedsFlipping = true; | ||||
} | } | ||||
notifyImageReceivedIfNeeded (loadingImage); | |||||
if (listeners.size() > 0) | |||||
callListeners (loadingImage); | |||||
notifyPictureTakenIfNeeded (loadingImage); | |||||
sendChangeMessage(); | sendChangeMessage(); | ||||
} | } | ||||
@@ -534,6 +562,12 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
ComSmartPtr<GrabberCallback> callback; | ComSmartPtr<GrabberCallback> callback; | ||||
CriticalSection listenerLock; | |||||
ListenerList<Listener> listeners; | |||||
CriticalSection pictureTakenCallbackLock; | |||||
std::function<void (const Image&)> pictureTakenCallback; | |||||
bool isRecording, openedSuccessfully; | bool isRecording, openedSuccessfully; | ||||
int width, height; | int width, height; | ||||
Time firstRecordedTime; | Time firstRecordedTime; | ||||
@@ -557,9 +591,6 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
bool recordNextFrameTime; | bool recordNextFrameTime; | ||||
int previewMaxFPS; | int previewMaxFPS; | ||||
CriticalSection callbackLock; | |||||
std::function<void (const Image&)> pictureTakenCallback; | |||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) | ||||
private: | private: | ||||