@@ -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: | |||