mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Add camera support for iOS and Android.
This commit is contained in:
parent
bac6996d98
commit
772185f2b0
75 changed files with 6619 additions and 421 deletions
|
|
@ -27,22 +27,129 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
#if JUCE_MAC
|
||||
#include "../native/juce_mac_CameraDevice.h"
|
||||
#elif JUCE_WINDOWS
|
||||
#include "../native/juce_win32_CameraDevice.h"
|
||||
#elif JUCE_IOS
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
|
||||
#endif
|
||||
|
||||
#include "../native/juce_ios_CameraDevice.h"
|
||||
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#elif JUCE_ANDROID
|
||||
#include "../native/juce_android_CameraDevice.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
//==============================================================================
|
||||
class CameraDevice::CameraFactory
|
||||
{
|
||||
public:
|
||||
static CameraFactory& getInstance()
|
||||
{
|
||||
static CameraFactory factory;
|
||||
return factory;
|
||||
}
|
||||
|
||||
void openCamera (int index, OpenCameraResultCallback resultCallback,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
{
|
||||
auto cameraId = getAvailableDevices()[index];
|
||||
|
||||
if (getCameraIndex (cameraId) != -1)
|
||||
{
|
||||
// You are trying to open the same camera twice.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<CameraDevice> device (new CameraDevice (cameraId, index,
|
||||
minWidth, minHeight, maxWidth,
|
||||
maxHeight, useHighQuality));
|
||||
|
||||
camerasToOpen.add ({ nextRequestId++,
|
||||
std::unique_ptr<CameraDevice> (device.release()),
|
||||
resultCallback });
|
||||
|
||||
auto& pendingOpen = camerasToOpen.getReference (camerasToOpen.size() - 1);
|
||||
|
||||
pendingOpen.device->pimpl->open ([this](const String& deviceId, const String& error)
|
||||
{
|
||||
int index = getCameraIndex (deviceId);
|
||||
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
auto& pendingOpen = camerasToOpen.getReference (index);
|
||||
|
||||
if (error.isEmpty())
|
||||
pendingOpen.resultCallback (pendingOpen.device.release(), error);
|
||||
else
|
||||
pendingOpen.resultCallback (nullptr, error);
|
||||
|
||||
int id = pendingOpen.requestId;
|
||||
|
||||
MessageManager::callAsync ([this, id]() { removeRequestWithId (id); });
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
int getCameraIndex (const String& cameraId) const
|
||||
{
|
||||
for (int i = 0; i < camerasToOpen.size(); ++i)
|
||||
{
|
||||
auto& pendingOpen = camerasToOpen.getReference (i);
|
||||
|
||||
if (pendingOpen.device->pimpl->getCameraId() == cameraId)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void removeRequestWithId (int id)
|
||||
{
|
||||
for (int i = camerasToOpen.size(); --i >= 0;)
|
||||
{
|
||||
if (camerasToOpen.getReference (i).requestId == id)
|
||||
{
|
||||
camerasToOpen.remove (i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingCameraOpen
|
||||
{
|
||||
int requestId;
|
||||
std::unique_ptr<CameraDevice> device;
|
||||
OpenCameraResultCallback resultCallback;
|
||||
};
|
||||
|
||||
Array<PendingCameraOpen> camerasToOpen;
|
||||
static int nextRequestId;
|
||||
};
|
||||
|
||||
int CameraDevice::CameraFactory::nextRequestId = 0;
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
CameraDevice::CameraDevice (const String& nm, int index, int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
: name (nm), pimpl (new Pimpl (name, index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality))
|
||||
: name (nm), pimpl (new Pimpl (*this, name, index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality))
|
||||
{
|
||||
}
|
||||
|
||||
CameraDevice::~CameraDevice()
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
stopRecording();
|
||||
pimpl.reset();
|
||||
}
|
||||
|
|
@ -52,6 +159,11 @@ Component* CameraDevice::createViewerComponent()
|
|||
return new ViewerComponent (*this);
|
||||
}
|
||||
|
||||
void CameraDevice::takeStillPicture (std::function<void (const Image&)> pictureTakenCallback)
|
||||
{
|
||||
pimpl->takeStillPicture (pictureTakenCallback);
|
||||
}
|
||||
|
||||
void CameraDevice::startRecordingToFile (const File& file, int quality)
|
||||
{
|
||||
stopRecording();
|
||||
|
|
@ -68,18 +180,6 @@ 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()
|
||||
{
|
||||
|
|
@ -94,12 +194,44 @@ CameraDevice* CameraDevice::openDevice (int index,
|
|||
int maxWidth, int maxHeight,
|
||||
bool useHighQuality)
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
#if ! JUCE_ANDROID && ! JUCE_IOS
|
||||
std::unique_ptr<CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index,
|
||||
minWidth, minHeight, maxWidth, maxHeight, useHighQuality));
|
||||
if (d != nullptr && d->pimpl->openedOk())
|
||||
return d.release();
|
||||
#else
|
||||
ignoreUnused (index, minWidth, minHeight);
|
||||
ignoreUnused (maxWidth, maxHeight, useHighQuality);
|
||||
|
||||
// Use openDeviceAsync to open a camera device on iOS or Android.
|
||||
jassertfalse;
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CameraDevice::openDeviceAsync (int index, OpenCameraResultCallback resultCallback,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool useHighQuality)
|
||||
{
|
||||
jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
if (resultCallback == nullptr)
|
||||
{
|
||||
// A valid callback must be passed.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
CameraFactory::getInstance().openCamera (index, static_cast<OpenCameraResultCallback&&> (resultCallback),
|
||||
minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
|
||||
#else
|
||||
auto* device = openDevice (index, minWidth, minHeight, maxWidth, maxHeight, useHighQuality);
|
||||
|
||||
resultCallback (device, device != nullptr ? String() : "Could not open camera device");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ namespace juce
|
|||
Controls any video capture devices that might be available.
|
||||
|
||||
Use getAvailableDevices() to list the devices that are attached to the
|
||||
system, then call openDevice to open one for use. Once you have a CameraDevice
|
||||
object, you can get a viewer component from it, and use its methods to
|
||||
stream to a file or capture still-frames.
|
||||
system, then call openDevice() or openDeviceAsync() to open one for use.
|
||||
Once you have a CameraDevice object, you can get a viewer component from it,
|
||||
and use its methods to stream to a file or capture still-frames.
|
||||
|
||||
@tags{Video}
|
||||
*/
|
||||
|
|
@ -50,17 +50,18 @@ public:
|
|||
//==============================================================================
|
||||
/** Returns a list of the available cameras on this machine.
|
||||
|
||||
You can open one of these devices by calling openDevice().
|
||||
You can open one of these devices by calling openDevice() or openDeviceAsync().
|
||||
*/
|
||||
static StringArray getAvailableDevices();
|
||||
|
||||
/** Opens a camera device.
|
||||
/** Synchronously opens a camera device. This function should not be used on iOS or
|
||||
Android, use openDeviceAsync() instead.
|
||||
|
||||
The index parameter indicates which of the items returned by getAvailableDevices()
|
||||
to open.
|
||||
|
||||
The size constraints allow the method to choose between different resolutions if
|
||||
the camera supports this. If the resolution cam't be specified (e.g. on the Mac)
|
||||
the camera supports this. If the resolution can't be specified (e.g. on the Mac)
|
||||
then these will be ignored.
|
||||
|
||||
On Mac, if highQuality is false, then the camera will be opened in preview mode
|
||||
|
|
@ -72,16 +73,62 @@ public:
|
|||
int maxWidth = 1024, int maxHeight = 768,
|
||||
bool highQuality = true);
|
||||
|
||||
using OpenCameraResultCallback = std::function<void (CameraDevice*, const String& /*error*/)>;
|
||||
|
||||
/** Asynchronously opens a camera device on iOS (iOS 7+) or Android (API 21+).
|
||||
On other platforms, the function will simply call openDevice(). Upon completion,
|
||||
resultCallback will be invoked with valid CameraDevice* and an empty error
|
||||
String on success, or nullptr CameraDevice and a non-empty error String on failure.
|
||||
|
||||
This is the preferred method of opening a camera device, because it works on all
|
||||
platforms, whereas synchronous openDevice() does not work on iOS & Android.
|
||||
|
||||
The index parameter indicates which of the items returned by getAvailableDevices()
|
||||
to open.
|
||||
|
||||
The size constraints allow the method to choose between different resolutions if
|
||||
the camera supports this. If the resolution can't be specified then these will be
|
||||
ignored.
|
||||
|
||||
On iOS, if you want to switch a device, it is more efficient to open a new device
|
||||
before closing the older one, because this way both devices can share the same
|
||||
underlying camera session. Otherwise, the session needs to be close first, and this
|
||||
is a lengthy process that can take several seconds.
|
||||
|
||||
The Android implementation currently supports a maximum recording resolution of
|
||||
1080p. Choosing a larger size will result in larger pictures taken, but the video
|
||||
will be capped at 1080p.
|
||||
*/
|
||||
static void openDeviceAsync (int deviceIndex,
|
||||
OpenCameraResultCallback resultCallback,
|
||||
int minWidth = 128, int minHeight = 64,
|
||||
int maxWidth = 1024, int maxHeight = 768,
|
||||
bool highQuality = true);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the name of this device */
|
||||
const String& getName() const noexcept { return name; }
|
||||
|
||||
/** Creates a component that can be used to display a preview of the
|
||||
video from this camera.
|
||||
|
||||
Note: while you can change the size of the preview component, the actual
|
||||
preview display may be smaller than the size requested, because the correct
|
||||
aspect ratio is maintained automatically.
|
||||
*/
|
||||
Component* createViewerComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Triggers a still picture capture. Upon completion, pictureTakenCallback will
|
||||
be invoked on a message thread.
|
||||
|
||||
On Android, before calling takeStillPicture(), you need to create a preview with
|
||||
createViewerComponent() and you need to make it visible on screen.
|
||||
|
||||
Android does not support simultaneous video recording and still picture capture.
|
||||
*/
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallback);
|
||||
|
||||
/** Starts recording video to the specified file.
|
||||
|
||||
You should use getFileExtension() to find out the correct extension to
|
||||
|
|
@ -95,6 +142,16 @@ public:
|
|||
|
||||
The quality parameter can be 0, 1, or 2, to indicate low, medium, or high. It may
|
||||
or may not be used, depending on the driver.
|
||||
|
||||
On Android, before calling startRecordingToFile(), you need to create a preview with
|
||||
createViewerComponent() and you need to make it visible on screen.
|
||||
|
||||
The Android camera also requires exclusive access to the audio device, so make sure
|
||||
you close any open audio devices with AudioDeviceManager::closeAudioDevice() first.
|
||||
|
||||
Android does not support simultaneous video recording and still picture capture.
|
||||
|
||||
@see AudioDeviceManager::closeAudioDevice, AudioDeviceManager::restartLastAudioDevice
|
||||
*/
|
||||
void startRecordingToFile (const File& file, int quality = 2);
|
||||
|
||||
|
|
@ -113,36 +170,9 @@ public:
|
|||
*/
|
||||
Time getTimeOfFirstRecordedFrame() const;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives callbacks with images from a CameraDevice.
|
||||
|
||||
@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!
|
||||
*/
|
||||
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);
|
||||
/** Set this callback to be notified whenever an error occurs. You may need to close
|
||||
and reopen the device to be able to use it further. */
|
||||
std::function<void (const String& /*error*/)> onErrorOccurred;
|
||||
|
||||
private:
|
||||
String name;
|
||||
|
|
@ -158,6 +188,32 @@ private:
|
|||
CameraDevice (const String& name, int index,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight, bool highQuality);
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
class CameraFactory;
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void juce_cameraDeviceStateClosed (int64);
|
||||
friend void juce_cameraDeviceStateDisconnected (int64);
|
||||
friend void juce_cameraDeviceStateError (int64, int);
|
||||
friend void juce_cameraDeviceStateOpened (int64, void*);
|
||||
|
||||
friend void juce_cameraCaptureSessionActive (int64, void*);
|
||||
friend void juce_cameraCaptureSessionClosed (int64, void*);
|
||||
friend void juce_cameraCaptureSessionConfigureFailed (int64, void*);
|
||||
friend void juce_cameraCaptureSessionConfigured (int64, void*);
|
||||
friend void juce_cameraCaptureSessionReady (int64, void*);
|
||||
|
||||
friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*);
|
||||
friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int);
|
||||
friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64);
|
||||
friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64);
|
||||
|
||||
friend void juce_deviceOrientationChanged (int64, int);
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CameraDevice)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,13 +59,23 @@
|
|||
|
||||
//=============================================================================
|
||||
/** Config: JUCE_USE_CAMERA
|
||||
Enables web-cam support using the CameraDevice class (Mac and Windows).
|
||||
Enables camera support using the CameraDevice class (Mac, Windows, iOS, Android).
|
||||
*/
|
||||
#ifndef JUCE_USE_CAMERA
|
||||
#define JUCE_USE_CAMERA 0
|
||||
#endif
|
||||
|
||||
#if ! (JUCE_MAC || JUCE_WINDOWS)
|
||||
#ifndef JUCE_CAMERA_LOG_ENABLED
|
||||
#define JUCE_CAMERA_LOG_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if JUCE_CAMERA_LOG_ENABLED
|
||||
#define JUCE_CAMERA_LOG(x) DBG(x)
|
||||
#else
|
||||
#define JUCE_CAMERA_LOG(x) {}
|
||||
#endif
|
||||
|
||||
#if ! (JUCE_MAC || JUCE_WINDOWS || JUCE_IOS || JUCE_ANDROID)
|
||||
#undef JUCE_USE_CAMERA
|
||||
#endif
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1288
modules/juce_video/native/juce_ios_CameraDevice.h
Normal file
1288
modules/juce_video/native/juce_ios_CameraDevice.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -26,8 +26,9 @@
|
|||
|
||||
struct CameraDevice::Pimpl
|
||||
{
|
||||
Pimpl (const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/,
|
||||
Pimpl (CameraDevice& ownerToUse, const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/,
|
||||
int /*maxWidth*/, int /*maxHeight*/, bool useHighQuality)
|
||||
: owner (ownerToUse)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
|
|
@ -42,11 +43,20 @@ struct CameraDevice::Pimpl
|
|||
static DelegateClass cls;
|
||||
callbackDelegate = (id<AVCaptureFileOutputRecordingDelegate>) [cls.createInstance() init];
|
||||
DelegateClass::setOwner (callbackDelegate, this);
|
||||
|
||||
SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: callbackDelegate
|
||||
selector: runtimeErrorSel
|
||||
name: AVCaptureSessionRuntimeErrorNotification
|
||||
object: session];
|
||||
}
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: callbackDelegate];
|
||||
|
||||
[session stopRunning];
|
||||
removeImageCapture();
|
||||
removeMovieCapture();
|
||||
|
|
@ -113,6 +123,19 @@ struct CameraDevice::Pimpl
|
|||
refreshConnections();
|
||||
}
|
||||
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
|
||||
{
|
||||
if (pictureTakenCallbackToUse == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
pictureTakenCallback = static_cast<std::function<void (const Image&)>&&> (pictureTakenCallbackToUse);
|
||||
|
||||
triggerImageCapture();
|
||||
}
|
||||
|
||||
void startRecordingToFile (const File& file, int /*quality*/)
|
||||
{
|
||||
stopRecording();
|
||||
|
|
@ -150,21 +173,10 @@ struct CameraDevice::Pimpl
|
|||
return nil;
|
||||
}
|
||||
|
||||
void handleImageCapture (const void* data, size_t size)
|
||||
void handleImageCapture (const Image& image)
|
||||
{
|
||||
auto image = ImageFileFormat::loadFrom (data, size);
|
||||
|
||||
const ScopedLock sl (listenerLock);
|
||||
|
||||
if (! listeners.isEmpty())
|
||||
{
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
if (auto* l = listeners[i])
|
||||
l->imageReceived (image);
|
||||
|
||||
if (! listeners.isEmpty())
|
||||
triggerImageCapture();
|
||||
}
|
||||
if (pictureTakenCallback != nullptr)
|
||||
pictureTakenCallback (image);
|
||||
}
|
||||
|
||||
void triggerImageCapture()
|
||||
|
|
@ -174,33 +186,25 @@ struct CameraDevice::Pimpl
|
|||
if (auto* videoConnection = getVideoConnection())
|
||||
{
|
||||
[imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
|
||||
completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError*)
|
||||
completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
|
||||
{
|
||||
auto buffer = CMSampleBufferGetDataBuffer (sampleBuffer);
|
||||
size_t size = CMBlockBufferGetDataLength (buffer);
|
||||
jassert (CMBlockBufferIsRangeContiguous (buffer, 0, size)); // TODO: need to add code to handle this if it happens
|
||||
char* data = nullptr;
|
||||
CMBlockBufferGetDataPointer (buffer, 0, &size, nullptr, &data);
|
||||
handleImageCapture (data, size);
|
||||
if (error != nil)
|
||||
{
|
||||
JUCE_CAMERA_LOG ("Still picture capture failed, error: " + nsStringToJuce (error.localizedDescription));
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
NSData* imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation: sampleBuffer];
|
||||
|
||||
auto image = ImageFileFormat::loadFrom (imageData.bytes, (size_t) imageData.length);
|
||||
|
||||
WeakReference<Pimpl> weakRef (this);
|
||||
MessageManager::callAsync ([weakRef, image]() mutable { if (weakRef != nullptr) weakRef->handleImageCapture (image); });
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
void addListener (CameraDevice::Listener* listenerToAdd)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.addIfNotAlreadyThere (listenerToAdd);
|
||||
|
||||
if (listeners.size() == 1)
|
||||
triggerImageCapture();
|
||||
}
|
||||
|
||||
void removeListener (CameraDevice::Listener* listenerToRemove)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.removeFirstMatchingValue (listenerToRemove);
|
||||
}
|
||||
|
||||
static StringArray getAvailableDevices()
|
||||
{
|
||||
StringArray results;
|
||||
|
|
@ -208,6 +212,15 @@ struct CameraDevice::Pimpl
|
|||
return results;
|
||||
}
|
||||
|
||||
void cameraSessionRuntimeError (const String& error)
|
||||
{
|
||||
JUCE_CAMERA_LOG ("cameraSessionRuntimeError(), error = " + error);
|
||||
|
||||
if (owner.onErrorOccurred != nullptr)
|
||||
owner.onErrorOccurred (error);
|
||||
}
|
||||
|
||||
CameraDevice& owner;
|
||||
AVCaptureView* captureView = nil;
|
||||
AVCaptureSession* session = nil;
|
||||
AVCaptureMovieFileOutput* fileOutput = nil;
|
||||
|
|
@ -218,8 +231,9 @@ struct CameraDevice::Pimpl
|
|||
Time firstPresentationTime;
|
||||
bool isRecording = false;
|
||||
|
||||
Array<CameraDevice::Listener*> listeners;
|
||||
CriticalSection listenerLock;
|
||||
std::function<void (const Image&)> pictureTakenCallback;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
@ -235,17 +249,29 @@ private:
|
|||
addMethod (@selector (captureOutput:didResumeRecordingToOutputFileAtURL: fromConnections:), didResumeRecordingToOutputFileAtURL, "v@:@@@");
|
||||
addMethod (@selector (captureOutput:willFinishRecordingToOutputFileAtURL:fromConnections:error:), willFinishRecordingToOutputFileAtURL, "v@:@@@@");
|
||||
|
||||
SEL runtimeErrorSel = NSSelectorFromString (nsStringLiteral ("captureSessionRuntimeError:"));
|
||||
addMethod (runtimeErrorSel, sessionRuntimeError, "v@:@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
|
||||
static Pimpl& getOwner (id self) { return *getIvar<Pimpl*> (self, "owner"); }
|
||||
|
||||
private:
|
||||
static void didStartRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void didPauseRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void didResumeRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*) {}
|
||||
static void willFinishRecordingToOutputFileAtURL (id, SEL, AVCaptureFileOutput*, NSURL*, NSArray*, NSError*) {}
|
||||
|
||||
static void sessionRuntimeError (id self, SEL, NSNotification* notification)
|
||||
{
|
||||
JUCE_CAMERA_LOG (nsStringToJuce ([notification description]));
|
||||
|
||||
NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
|
||||
auto errorString = error != nil ? nsStringToJuce (error.localizedDescription) : String();
|
||||
getOwner (self).cameraSessionRuntimeError (errorString);
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (Pimpl)
|
||||
|
|
|
|||
|
|
@ -49,10 +49,11 @@ static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0
|
|||
|
||||
struct CameraDevice::Pimpl : public ChangeBroadcaster
|
||||
{
|
||||
Pimpl (const String&, int index,
|
||||
int minWidth, int minHeight,
|
||||
int maxWidth, int maxHeight, bool /*highQuality*/)
|
||||
: isRecording (false),
|
||||
Pimpl (CameraDevice& ownerToUse, const String&, int index,
|
||||
int minWidth, int minHeight, int maxWidth, int maxHeight,
|
||||
bool /*highQuality*/)
|
||||
: owner (ownerToUse),
|
||||
isRecording (false),
|
||||
openedSuccessfully (false),
|
||||
imageNeedsFlipping (false),
|
||||
width (0), height (0),
|
||||
|
|
@ -191,6 +192,22 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster
|
|||
|
||||
bool openedOk() const noexcept { return openedSuccessfully; }
|
||||
|
||||
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (callbackLock);
|
||||
|
||||
jassert (pictureTakenCallbackToUse != nullptr);
|
||||
|
||||
if (pictureTakenCallbackToUse == nullptr)
|
||||
return;
|
||||
|
||||
pictureTakenCallback = static_cast<std::function<void (const Image&)>&&> (pictureTakenCallbackToUse);
|
||||
}
|
||||
|
||||
addUser();
|
||||
}
|
||||
|
||||
void startRecordingToFile (const File& file, int quality)
|
||||
{
|
||||
addUser();
|
||||
|
|
@ -212,32 +229,26 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster
|
|||
return firstRecordedTime;
|
||||
}
|
||||
|
||||
void addListener (CameraDevice::Listener* listenerToAdd)
|
||||
void notifyImageReceivedIfNeeded (const Image& image)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
{
|
||||
const ScopedLock sl (callbackLock);
|
||||
|
||||
if (listeners.size() == 0)
|
||||
addUser();
|
||||
if (pictureTakenCallback == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.addIfNotAlreadyThere (listenerToAdd);
|
||||
}
|
||||
WeakReference<Pimpl> weakRef (this);
|
||||
MessageManager::callAsync ([weakRef, image]() mutable
|
||||
{
|
||||
if (weakRef == nullptr)
|
||||
return;
|
||||
|
||||
void removeListener (CameraDevice::Listener* listenerToRemove)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
listeners.removeAllInstancesOf (listenerToRemove);
|
||||
if (weakRef->pictureTakenCallback != nullptr)
|
||||
weakRef->pictureTakenCallback (image);
|
||||
|
||||
if (listeners.size() == 0)
|
||||
removeUser();
|
||||
}
|
||||
|
||||
void callListeners (const Image& image)
|
||||
{
|
||||
const ScopedLock sl (listenerLock);
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
if (CameraDevice::Listener* const l = listeners[i])
|
||||
l->imageReceived (image);
|
||||
weakRef->pictureTakenCallback = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void addUser()
|
||||
|
|
@ -294,8 +305,7 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster
|
|||
imageNeedsFlipping = true;
|
||||
}
|
||||
|
||||
if (listeners.size() > 0)
|
||||
callListeners (loadingImage);
|
||||
notifyImageReceivedIfNeeded (loadingImage);
|
||||
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
|
@ -520,9 +530,9 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster
|
|||
JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
|
||||
};
|
||||
|
||||
CameraDevice& owner;
|
||||
|
||||
ComSmartPtr<GrabberCallback> callback;
|
||||
Array<CameraDevice::Listener*> listeners;
|
||||
CriticalSection listenerLock;
|
||||
|
||||
bool isRecording, openedSuccessfully;
|
||||
int width, height;
|
||||
|
|
@ -547,6 +557,11 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster
|
|||
bool recordNextFrameTime;
|
||||
int previewMaxFPS;
|
||||
|
||||
CriticalSection callbackLock;
|
||||
std::function<void (const Image&)> pictureTakenCallback;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
|
||||
|
||||
private:
|
||||
void getVideoSizes (IAMStreamConfig* const streamConfig)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ public:
|
|||
|
||||
//==============================================================================
|
||||
/** Tries to load a video from a local file.
|
||||
@returns am error if the file failed to be loaded correctly
|
||||
@returns an error if the file failed to be loaded correctly
|
||||
*/
|
||||
Result load (const File& file);
|
||||
|
||||
/** Tries to load a video from a URL.
|
||||
@returns am error if the file failed to be loaded correctly
|
||||
@returns an error if the file failed to be loaded correctly
|
||||
*/
|
||||
Result load (const URL& url);
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ public:
|
|||
File getCurrentVideoFile() const;
|
||||
|
||||
/** Returns the last URL that was loaded.
|
||||
If nothing is open, or if it was a file rather than a URL, this will return File().
|
||||
If nothing is open, or if it was a file rather than a URL, this will return URL().
|
||||
*/
|
||||
URL getCurrentVideoURL() const;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue