Browse Source

Camera: bring back CameraDevice::Listener for users wanting to process individual frames. Fix a typo too.

tags/2021-05-28
Lukasz Kozakiewicz 7 years ago
parent
commit
2dd1a80469
7 changed files with 212 additions and 57 deletions
  1. +0
    -27
      BREAKING-CHANGES.txt
  2. +12
    -0
      modules/juce_video/capture/juce_CameraDevice.cpp
  3. +42
    -0
      modules/juce_video/capture/juce_CameraDevice.h
  4. +36
    -6
      modules/juce_video/native/juce_android_CameraDevice.h
  5. +57
    -14
      modules/juce_video/native/juce_ios_CameraDevice.h
  6. +27
    -3
      modules/juce_video/native/juce_mac_CameraDevice.h
  7. +38
    -7
      modules/juce_video/native/juce_win32_CameraDevice.h

+ 0
- 27
BREAKING-CHANGES.txt View File

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


+ 12
- 0
modules/juce_video/capture/juce_CameraDevice.cpp View File

@@ -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()
{


+ 42
- 0
modules/juce_video/capture/juce_CameraDevice.h View File

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


+ 36
- 6
modules/juce_video/native/juce_android_CameraDevice.h View File

@@ -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);


+ 57
- 14
modules/juce_video/native/juce_ios_CameraDevice.h View File

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


+ 27
- 3
modules/juce_video/native/juce_mac_CameraDevice.h View File

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


+ 38
- 7
modules/juce_video/native/juce_win32_CameraDevice.h View File

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


Loading…
Cancel
Save