fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,329 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
struct CameraDevice::Pimpl
{
Pimpl (CameraDevice& ownerToUse, const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/,
int /*maxWidth*/, int /*maxHeight*/, bool useHighQuality)
: owner (ownerToUse)
{
JUCE_AUTORELEASEPOOL
{
captureView = [[AVCaptureView alloc] init];
session = captureView.session;
session.sessionPreset = useHighQuality ? AVCaptureSessionPresetHigh
: AVCaptureSessionPresetMedium;
refreshConnections();
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();
[session release];
[callbackDelegate release];
}
bool openedOk() const noexcept { return openingError.isEmpty(); }
void addImageCapture()
{
if (imageOutput == nil)
{
imageOutput = [[AVCaptureStillImageOutput alloc] init];
auto* imageSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil];
[imageOutput setOutputSettings: imageSettings];
[imageSettings release];
[session addOutput: imageOutput];
}
}
void addMovieCapture()
{
if (fileOutput == nil)
{
fileOutput = [[AVCaptureMovieFileOutput alloc] init];
[session addOutput: fileOutput];
}
}
void removeImageCapture()
{
if (imageOutput != nil)
{
[session removeOutput: imageOutput];
[imageOutput release];
imageOutput = nil;
}
}
void removeMovieCapture()
{
if (fileOutput != nil)
{
[session removeOutput: fileOutput];
[fileOutput release];
fileOutput = nil;
}
}
void refreshConnections()
{
[session beginConfiguration];
removeImageCapture();
removeMovieCapture();
addImageCapture();
addMovieCapture();
[session commitConfiguration];
}
void refreshIfNeeded()
{
if (getVideoConnection() == nullptr)
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();
refreshIfNeeded();
firstPresentationTime = Time::getCurrentTime();
file.deleteFile();
[fileOutput startRecordingToOutputFileURL: createNSURLFromFile (file)
recordingDelegate: callbackDelegate];
}
void stopRecording()
{
if (isRecording)
{
[fileOutput stopRecording];
isRecording = false;
}
}
Time getTimeOfFirstRecordedFrame() const
{
return firstPresentationTime;
}
AVCaptureConnection* getVideoConnection() const
{
if (imageOutput != nil)
for (AVCaptureConnection* connection in imageOutput.connections)
if ([connection isActive] && [connection isEnabled])
for (AVCaptureInputPort* port in [connection inputPorts])
if ([[port mediaType] isEqual: AVMediaTypeVideo])
return connection;
return nil;
}
void handleImageCapture (const Image& image)
{
const ScopedLock sl (listenerLock);
listeners.call ([=] (Listener& l) { l.imageReceived (image); });
if (! listeners.isEmpty())
triggerImageCapture();
}
void triggerImageCapture()
{
refreshIfNeeded();
if (auto* videoConnection = getVideoConnection())
{
[imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection
completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError* error)
{
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);
handleImageCapture (image);
WeakReference<Pimpl> weakRef (this);
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;
results.add ("default");
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;
AVCaptureStillImageOutput* imageOutput = nil;
id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil;
String openingError;
Time firstPresentationTime;
bool isRecording = false;
CriticalSection listenerLock;
ListenerList<Listener> listeners;
std::function<void (const Image&)> pictureTakenCallback;
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
private:
//==============================================================================
struct DelegateClass : public ObjCClass<NSObject>
{
DelegateClass() : ObjCClass<NSObject> ("JUCECameraDelegate_")
{
addIvar<Pimpl*> ("owner");
addProtocol (@protocol (AVCaptureFileOutputRecordingDelegate));
addMethod (@selector (captureOutput:didStartRecordingToOutputFileAtURL: fromConnections:), didStartRecordingToOutputFileAtURL, "v@:@@@");
addMethod (@selector (captureOutput:didPauseRecordingToOutputFileAtURL: fromConnections:), didPauseRecordingToOutputFileAtURL, "v@:@@@");
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"); }
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)
};
struct CameraDevice::ViewerComponent : public NSViewComponent
{
ViewerComponent (CameraDevice& d)
{
JUCE_AUTORELEASEPOOL
{
setSize (640, 480);
setView (d.pimpl->captureView);
}
}
~ViewerComponent()
{
setView (nil);
}
JUCE_DECLARE_NON_COPYABLE (ViewerComponent)
};
String CameraDevice::getFileExtension()
{
return ".mov";
}

View File

@ -0,0 +1,199 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if JUCE_IOS
using BaseClass = UIViewComponent;
#else
using BaseClass = NSViewComponent;
#endif
struct VideoComponent::Pimpl : public BaseClass
{
Pimpl()
{
setVisible (true);
#if JUCE_MAC && JUCE_32BIT
auto view = [[NSView alloc] init]; // 32-bit builds don't have AVPlayerView, so need to use a layer
controller = [[AVPlayerLayer alloc] init];
setView (view);
[view setNextResponder: [view superview]];
[view setWantsLayer: YES];
[view setLayer: controller];
[view release];
#elif JUCE_MAC
controller = [[AVPlayerView alloc] init];
setView (controller);
[controller setNextResponder: [controller superview]];
[controller setWantsLayer: YES];
#else
controller = [[AVPlayerViewController alloc] init];
setView ([controller view]);
#endif
}
~Pimpl()
{
close();
setView (nil);
[controller release];
}
Result load (const File& file)
{
auto r = load (createNSURLFromFile (file));
if (r.wasOk())
currentFile = file;
return r;
}
Result load (const URL& url)
{
auto r = load ([NSURL URLWithString: juceStringToNS (url.toString (true))]);
if (r.wasOk())
currentURL = url;
return r;
}
Result load (NSURL* url)
{
if (url != nil)
{
close();
if (auto* player = [AVPlayer playerWithURL: url])
{
[controller setPlayer: player];
return Result::ok();
}
}
return Result::fail ("Couldn't open movie");
}
void close()
{
stop();
[controller setPlayer: nil];
currentFile = File();
currentURL = {};
}
bool isOpen() const noexcept { return getAVPlayer() != nil; }
bool isPlaying() const noexcept { return getSpeed() != 0; }
void play() noexcept { [getAVPlayer() play]; }
void stop() noexcept { [getAVPlayer() pause]; }
void setPosition (double newPosition)
{
if (auto* p = getAVPlayer())
{
CMTime t = { (CMTimeValue) (100000.0 * newPosition),
(CMTimeScale) 100000, kCMTimeFlags_Valid };
[p seekToTime: t
toleranceBefore: kCMTimeZero
toleranceAfter: kCMTimeZero];
}
}
double getPosition() const
{
if (auto* p = getAVPlayer())
return toSeconds ([p currentTime]);
return 0.0;
}
void setSpeed (double newSpeed)
{
[getAVPlayer() setRate: (float) newSpeed];
}
double getSpeed() const
{
if (auto* p = getAVPlayer())
return [p rate];
return 0.0;
}
Rectangle<int> getNativeSize() const
{
if (auto* player = getAVPlayer())
{
auto s = [[player currentItem] presentationSize];
return { (int) s.width, (int) s.height };
}
return {};
}
double getDuration() const
{
if (auto* player = getAVPlayer())
return toSeconds ([[player currentItem] duration]);
return 0.0;
}
void setVolume (float newVolume)
{
[getAVPlayer() setVolume: newVolume];
}
float getVolume() const
{
if (auto* p = getAVPlayer())
return [p volume];
return 0.0f;
}
File currentFile;
URL currentURL;
private:
#if JUCE_IOS
AVPlayerViewController* controller = nil;
#elif JUCE_32BIT
AVPlayerLayer* controller = nil;
#else
AVPlayerView* controller = nil;
#endif
AVPlayer* getAVPlayer() const noexcept { return [controller player]; }
static double toSeconds (const CMTime& t) noexcept
{
return t.timescale != 0 ? (t.value / (double) t.timescale) : 0.0;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};

View File

@ -0,0 +1,838 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
interface ISampleGrabberCB : public IUnknown
{
virtual STDMETHODIMP SampleCB (double, IMediaSample*) = 0;
virtual STDMETHODIMP BufferCB (double, BYTE*, long) = 0;
};
interface ISampleGrabber : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE SetOneShot (BOOL) = 0;
virtual HRESULT STDMETHODCALLTYPE SetMediaType (const AM_MEDIA_TYPE*) = 0;
virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType (AM_MEDIA_TYPE*) = 0;
virtual HRESULT STDMETHODCALLTYPE SetBufferSamples (BOOL) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer (long*, long*) = 0;
virtual HRESULT STDMETHODCALLTYPE GetCurrentSample (IMediaSample**) = 0;
virtual HRESULT STDMETHODCALLTYPE SetCallback (ISampleGrabberCB*, long) = 0;
};
static const IID IID_ISampleGrabberCB = { 0x0579154A, 0x2B53, 0x4994, { 0xB0, 0xD0, 0xE7, 0x73, 0x14, 0x8E, 0xFF, 0x85 } };
static const IID IID_ISampleGrabber = { 0x6B652FFF, 0x11FE, 0x4fce, { 0x92, 0xAD, 0x02, 0x66, 0xB5, 0xD7, 0xC7, 0x8F } };
static const CLSID CLSID_SampleGrabber = { 0xC1F400A0, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
static const CLSID CLSID_NullRenderer = { 0xC1F400A4, 0x3F08, 0x11d3, { 0x9F, 0x0B, 0x00, 0x60, 0x08, 0x03, 0x9E, 0x37 } };
struct CameraDevice::Pimpl : public ChangeBroadcaster
{
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),
activeUsers (0),
recordNextFrameTime (false),
previewMaxFPS (60)
{
HRESULT hr = captureGraphBuilder.CoCreateInstance (CLSID_CaptureGraphBuilder2);
if (FAILED (hr))
return;
filter = enumerateCameras (nullptr, index);
if (filter == nullptr)
return;
hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
if (FAILED (hr))
return;
hr = captureGraphBuilder->SetFiltergraph (graphBuilder);
if (FAILED (hr))
return;
hr = graphBuilder.QueryInterface (mediaControl);
if (FAILED (hr))
return;
{
ComSmartPtr<IAMStreamConfig> streamConfig;
hr = captureGraphBuilder->FindInterface (&PIN_CATEGORY_CAPTURE, 0, filter,
IID_IAMStreamConfig, (void**) streamConfig.resetAndGetPointerAddress());
if (streamConfig != nullptr)
{
getVideoSizes (streamConfig);
if (! selectVideoSize (streamConfig, minWidth, minHeight, maxWidth, maxHeight))
return;
}
}
hr = graphBuilder->AddFilter (filter, _T("Video Capture"));
if (FAILED (hr))
return;
hr = smartTee.CoCreateInstance (CLSID_SmartTee);
if (FAILED (hr))
return;
hr = graphBuilder->AddFilter (smartTee, _T("Smart Tee"));
if (FAILED (hr))
return;
if (! connectFilters (filter, smartTee))
return;
ComSmartPtr<IBaseFilter> sampleGrabberBase;
hr = sampleGrabberBase.CoCreateInstance (CLSID_SampleGrabber);
if (FAILED (hr))
return;
hr = sampleGrabberBase.QueryInterface (IID_ISampleGrabber, sampleGrabber);
if (FAILED (hr))
return;
{
AM_MEDIA_TYPE mt = { 0 };
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
mt.formattype = FORMAT_VideoInfo;
sampleGrabber->SetMediaType (&mt);
}
callback = new GrabberCallback (*this);
hr = sampleGrabber->SetCallback (callback, 1);
hr = graphBuilder->AddFilter (sampleGrabberBase, _T("Sample Grabber"));
if (FAILED (hr))
return;
ComSmartPtr<IPin> grabberInputPin;
if (! (getPin (smartTee, PINDIR_OUTPUT, smartTeeCaptureOutputPin, "capture")
&& getPin (smartTee, PINDIR_OUTPUT, smartTeePreviewOutputPin, "preview")
&& getPin (sampleGrabberBase, PINDIR_INPUT, grabberInputPin)))
return;
hr = graphBuilder->Connect (smartTeePreviewOutputPin, grabberInputPin);
if (FAILED (hr))
return;
AM_MEDIA_TYPE mt = { 0 };
hr = sampleGrabber->GetConnectedMediaType (&mt);
VIDEOINFOHEADER* pVih = (VIDEOINFOHEADER*) (mt.pbFormat);
width = pVih->bmiHeader.biWidth;
height = pVih->bmiHeader.biHeight;
ComSmartPtr<IBaseFilter> nullFilter;
hr = nullFilter.CoCreateInstance (CLSID_NullRenderer);
hr = graphBuilder->AddFilter (nullFilter, _T("Null Renderer"));
if (connectFilters (sampleGrabberBase, nullFilter)
&& addGraphToRot())
{
activeImage = Image (Image::RGB, width, height, true);
loadingImage = Image (Image::RGB, width, height, true);
openedSuccessfully = true;
}
}
~Pimpl()
{
if (mediaControl != nullptr)
mediaControl->Stop();
removeGraphFromRot();
disconnectAnyViewers();
if (sampleGrabber != nullptr)
{
sampleGrabber->SetCallback (nullptr, 0);
sampleGrabber = nullptr;
}
callback = nullptr;
graphBuilder = nullptr;
mediaControl = nullptr;
filter = nullptr;
captureGraphBuilder = nullptr;
smartTee = nullptr;
smartTeePreviewOutputPin = nullptr;
smartTeeCaptureOutputPin = nullptr;
asfWriter = nullptr;
}
bool openedOk() const noexcept { return openedSuccessfully; }
void takeStillPicture (std::function<void (const Image&)> pictureTakenCallbackToUse)
{
{
const ScopedLock sl (pictureTakenCallbackLock);
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();
isRecording = createFileCaptureFilter (file, quality);
}
void stopRecording()
{
if (isRecording)
{
removeFileCaptureFilter();
removeUser();
isRecording = false;
}
}
Time getTimeOfFirstRecordedFrame() const
{
return firstRecordedTime;
}
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 (pictureTakenCallbackLock);
if (pictureTakenCallback == nullptr)
return;
}
WeakReference<Pimpl> weakRef (this);
MessageManager::callAsync ([weakRef, image]() mutable
{
if (weakRef == nullptr)
return;
if (weakRef->pictureTakenCallback != nullptr)
weakRef->pictureTakenCallback (image);
weakRef->pictureTakenCallback = nullptr;
});
}
void addUser()
{
if (openedSuccessfully && activeUsers++ == 0)
mediaControl->Run();
}
void removeUser()
{
if (openedSuccessfully && --activeUsers == 0)
mediaControl->Stop();
}
void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/)
{
if (recordNextFrameTime)
{
const double defaultCameraLatency = 0.1;
firstRecordedTime = Time::getCurrentTime() - RelativeTime (defaultCameraLatency);
recordNextFrameTime = false;
ComSmartPtr<IPin> pin;
if (getPin (filter, PINDIR_OUTPUT, pin))
{
ComSmartPtr<IAMPushSource> pushSource;
HRESULT hr = pin.QueryInterface (pushSource);
if (pushSource != nullptr)
{
REFERENCE_TIME latency = 0;
hr = pushSource->GetLatency (&latency);
firstRecordedTime = firstRecordedTime - RelativeTime ((double) latency);
}
}
}
{
const int lineStride = width * 3;
const ScopedLock sl (imageSwapLock);
{
loadingImage.duplicateIfShared();
const Image::BitmapData destData (loadingImage, 0, 0, width, height, Image::BitmapData::writeOnly);
for (int i = 0; i < height; ++i)
memcpy (destData.getLinePointer ((height - 1) - i),
buffer + lineStride * i,
lineStride);
}
imageNeedsFlipping = true;
}
if (listeners.size() > 0)
callListeners (loadingImage);
notifyPictureTakenIfNeeded (loadingImage);
sendChangeMessage();
}
void drawCurrentImage (Graphics& g, Rectangle<int> area)
{
if (imageNeedsFlipping)
{
const ScopedLock sl (imageSwapLock);
std::swap (loadingImage, activeImage);
imageNeedsFlipping = false;
}
Rectangle<int> centred (RectanglePlacement (RectanglePlacement::centred)
.appliedTo (Rectangle<int> (width, height), area));
RectangleList<int> borders (area);
borders.subtract (centred);
g.setColour (Colours::black);
g.fillRectList (borders);
g.drawImage (activeImage, centred.getX(), centred.getY(),
centred.getWidth(), centred.getHeight(), 0, 0, width, height);
}
bool createFileCaptureFilter (const File& file, int quality)
{
removeFileCaptureFilter();
file.deleteFile();
mediaControl->Stop();
firstRecordedTime = Time();
recordNextFrameTime = true;
previewMaxFPS = 60;
HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter);
if (SUCCEEDED (hr))
{
ComSmartPtr<IFileSinkFilter> fileSink;
hr = asfWriter.QueryInterface (fileSink);
if (SUCCEEDED (hr))
{
hr = fileSink->SetFileName (file.getFullPathName().toWideCharPointer(), 0);
if (SUCCEEDED (hr))
{
hr = graphBuilder->AddFilter (asfWriter, _T("AsfWriter"));
if (SUCCEEDED (hr))
{
ComSmartPtr<IConfigAsfWriter> asfConfig;
hr = asfWriter.QueryInterface (asfConfig);
asfConfig->SetIndexMode (true);
ComSmartPtr<IWMProfileManager> profileManager;
hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress());
// This gibberish is the DirectShow profile for a video-only wmv file.
String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">"
"<streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" "
"streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" "
"bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">"
"<videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>"
"<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" "
"btemporalcompression=\"1\" lsamplesize=\"0\">"
"<videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">"
"<rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
"<rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>"
"<bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" "
"bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" "
"biclrused=\"0\" biclrimportant=\"0\"/>"
"</videoinfoheader>"
"</wmmediatype>"
"</streamconfig>"
"</profile>");
const int fps[] = { 10, 15, 30 };
int maxFramesPerSecond = fps [jlimit (0, numElementsInArray (fps) - 1, quality & 0xff)];
if ((quality & 0xff000000) != 0) // (internal hacky way to pass explicit frame rates for testing)
maxFramesPerSecond = (quality >> 24) & 0xff;
prof = prof.replace ("$WIDTH", String (width))
.replace ("$HEIGHT", String (height))
.replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond));
ComSmartPtr<IWMProfile> currentProfile;
hr = profileManager->LoadProfileByData (prof.toWideCharPointer(), currentProfile.resetAndGetPointerAddress());
hr = asfConfig->ConfigureFilterUsingProfile (currentProfile);
if (SUCCEEDED (hr))
{
ComSmartPtr<IPin> asfWriterInputPin;
if (getPin (asfWriter, PINDIR_INPUT, asfWriterInputPin, "Video Input 01"))
{
hr = graphBuilder->Connect (smartTeeCaptureOutputPin, asfWriterInputPin);
if (SUCCEEDED (hr) && openedSuccessfully && activeUsers > 0
&& SUCCEEDED (mediaControl->Run()))
{
previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding
if ((quality & 0x00ff0000) != 0) // (internal hacky way to pass explicit frame rates for testing)
previewMaxFPS = (quality >> 16) & 0xff;
return true;
}
}
}
}
}
}
}
removeFileCaptureFilter();
if (openedSuccessfully && activeUsers > 0)
mediaControl->Run();
return false;
}
void removeFileCaptureFilter()
{
mediaControl->Stop();
if (asfWriter != nullptr)
{
graphBuilder->RemoveFilter (asfWriter);
asfWriter = nullptr;
}
if (openedSuccessfully && activeUsers > 0)
mediaControl->Run();
previewMaxFPS = 60;
}
static ComSmartPtr<IBaseFilter> enumerateCameras (StringArray* names, const int deviceIndexToOpen)
{
int index = 0;
ComSmartPtr<ICreateDevEnum> pDevEnum;
if (SUCCEEDED (pDevEnum.CoCreateInstance (CLSID_SystemDeviceEnum)))
{
ComSmartPtr<IEnumMoniker> enumerator;
HRESULT hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, enumerator.resetAndGetPointerAddress(), 0);
if (SUCCEEDED (hr) && enumerator != nullptr)
{
ComSmartPtr<IMoniker> moniker;
ULONG fetched;
while (enumerator->Next (1, moniker.resetAndGetPointerAddress(), &fetched) == S_OK)
{
ComSmartPtr<IBaseFilter> captureFilter;
hr = moniker->BindToObject (0, 0, IID_IBaseFilter, (void**) captureFilter.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
{
ComSmartPtr<IPropertyBag> propertyBag;
hr = moniker->BindToStorage (0, 0, IID_IPropertyBag, (void**) propertyBag.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
{
VARIANT var;
var.vt = VT_BSTR;
hr = propertyBag->Read (_T("FriendlyName"), &var, 0);
propertyBag = nullptr;
if (SUCCEEDED (hr))
{
if (names != nullptr)
names->add (var.bstrVal);
if (index == deviceIndexToOpen)
return captureFilter;
++index;
}
}
}
}
}
}
return nullptr;
}
static StringArray getAvailableDevices()
{
StringArray devs;
enumerateCameras (&devs, -1);
return devs;
}
struct GrabberCallback : public ComBaseClassHelperBase<ISampleGrabberCB>
{
GrabberCallback (Pimpl& p)
: ComBaseClassHelperBase<ISampleGrabberCB> (0), owner (p) {}
JUCE_COMRESULT QueryInterface (REFIID refId, void** result)
{
if (refId == IID_ISampleGrabberCB)
return castToType<ISampleGrabberCB> (result);
return ComBaseClassHelperBase<ISampleGrabberCB>::QueryInterface (refId, result);
}
STDMETHODIMP SampleCB (double, IMediaSample*) { return E_FAIL; }
STDMETHODIMP BufferCB (double time, BYTE* buffer, long bufferSize)
{
owner.handleFrame (time, buffer, bufferSize);
return S_OK;
}
Pimpl& owner;
JUCE_DECLARE_NON_COPYABLE (GrabberCallback)
};
CameraDevice& owner;
ComSmartPtr<GrabberCallback> callback;
CriticalSection listenerLock;
ListenerList<Listener> listeners;
CriticalSection pictureTakenCallbackLock;
std::function<void (const Image&)> pictureTakenCallback;
bool isRecording, openedSuccessfully;
int width, height;
Time firstRecordedTime;
Array<ViewerComponent*> viewerComps;
ComSmartPtr<ICaptureGraphBuilder2> captureGraphBuilder;
ComSmartPtr<IBaseFilter> filter, smartTee, asfWriter;
ComSmartPtr<IGraphBuilder> graphBuilder;
ComSmartPtr<ISampleGrabber> sampleGrabber;
ComSmartPtr<IMediaControl> mediaControl;
ComSmartPtr<IPin> smartTeePreviewOutputPin, smartTeeCaptureOutputPin;
int activeUsers;
Array<int> widths, heights;
DWORD graphRegistrationID;
CriticalSection imageSwapLock;
bool imageNeedsFlipping;
Image loadingImage, activeImage;
bool recordNextFrameTime;
int previewMaxFPS;
JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl)
private:
void getVideoSizes (IAMStreamConfig* const streamConfig)
{
widths.clear();
heights.clear();
int count = 0, size = 0;
streamConfig->GetNumberOfCapabilities (&count, &size);
if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
{
for (int i = 0; i < count; ++i)
{
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE* config;
HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
if (SUCCEEDED (hr))
{
const int w = scc.InputSize.cx;
const int h = scc.InputSize.cy;
bool duplicate = false;
for (int j = widths.size(); --j >= 0;)
{
if (w == widths.getUnchecked (j) && h == heights.getUnchecked (j))
{
duplicate = true;
break;
}
}
if (! duplicate)
{
DBG ("Camera capture size: " + String (w) + ", " + String (h));
widths.add (w);
heights.add (h);
}
deleteMediaType (config);
}
}
}
}
bool selectVideoSize (IAMStreamConfig* const streamConfig,
const int minWidth, const int minHeight,
const int maxWidth, const int maxHeight)
{
int count = 0, size = 0, bestArea = 0, bestIndex = -1;
streamConfig->GetNumberOfCapabilities (&count, &size);
if (size == sizeof (VIDEO_STREAM_CONFIG_CAPS))
{
AM_MEDIA_TYPE* config;
VIDEO_STREAM_CONFIG_CAPS scc;
for (int i = 0; i < count; ++i)
{
HRESULT hr = streamConfig->GetStreamCaps (i, &config, (BYTE*) &scc);
if (SUCCEEDED (hr))
{
if (scc.InputSize.cx >= minWidth
&& scc.InputSize.cy >= minHeight
&& scc.InputSize.cx <= maxWidth
&& scc.InputSize.cy <= maxHeight)
{
int area = scc.InputSize.cx * scc.InputSize.cy;
if (area > bestArea)
{
bestIndex = i;
bestArea = area;
}
}
deleteMediaType (config);
}
}
if (bestIndex >= 0)
{
HRESULT hr = streamConfig->GetStreamCaps (bestIndex, &config, (BYTE*) &scc);
hr = streamConfig->SetFormat (config);
deleteMediaType (config);
return SUCCEEDED (hr);
}
}
return false;
}
static bool getPin (IBaseFilter* filter, const PIN_DIRECTION wantedDirection,
ComSmartPtr<IPin>& result, const char* pinName = nullptr)
{
ComSmartPtr<IEnumPins> enumerator;
ComSmartPtr<IPin> pin;
filter->EnumPins (enumerator.resetAndGetPointerAddress());
while (enumerator->Next (1, pin.resetAndGetPointerAddress(), 0) == S_OK)
{
PIN_DIRECTION dir;
pin->QueryDirection (&dir);
if (wantedDirection == dir)
{
PIN_INFO info = { 0 };
pin->QueryPinInfo (&info);
if (pinName == nullptr || String (pinName).equalsIgnoreCase (String (info.achName)))
{
result = pin;
return true;
}
}
}
return false;
}
bool connectFilters (IBaseFilter* const first, IBaseFilter* const second) const
{
ComSmartPtr<IPin> in, out;
return getPin (first, PINDIR_OUTPUT, out)
&& getPin (second, PINDIR_INPUT, in)
&& SUCCEEDED (graphBuilder->Connect (out, in));
}
bool addGraphToRot()
{
ComSmartPtr<IRunningObjectTable> rot;
if (FAILED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
return false;
ComSmartPtr<IMoniker> moniker;
WCHAR buffer[128];
HRESULT hr = CreateItemMoniker (_T("!"), buffer, moniker.resetAndGetPointerAddress());
if (FAILED (hr))
return false;
graphRegistrationID = 0;
return SUCCEEDED (rot->Register (0, graphBuilder, moniker, &graphRegistrationID));
}
void removeGraphFromRot()
{
ComSmartPtr<IRunningObjectTable> rot;
if (SUCCEEDED (GetRunningObjectTable (0, rot.resetAndGetPointerAddress())))
rot->Revoke (graphRegistrationID);
}
void disconnectAnyViewers();
static void deleteMediaType (AM_MEDIA_TYPE* const pmt)
{
if (pmt->cbFormat != 0)
CoTaskMemFree ((PVOID) pmt->pbFormat);
if (pmt->pUnk != nullptr)
pmt->pUnk->Release();
CoTaskMemFree (pmt);
}
JUCE_DECLARE_NON_COPYABLE (Pimpl)
};
//==============================================================================
struct CameraDevice::ViewerComponent : public Component,
public ChangeListener
{
ViewerComponent (CameraDevice& d)
: owner (d.pimpl.get()), maxFPS (15), lastRepaintTime (0)
{
setOpaque (true);
owner->addChangeListener (this);
owner->addUser();
owner->viewerComps.add (this);
setSize (owner->width, owner->height);
}
~ViewerComponent()
{
if (owner != nullptr)
{
owner->viewerComps.removeFirstMatchingValue (this);
owner->removeUser();
owner->removeChangeListener (this);
}
}
void ownerDeleted()
{
owner = nullptr;
}
void paint (Graphics& g) override
{
g.setColour (Colours::black);
g.setImageResamplingQuality (Graphics::lowResamplingQuality);
if (owner != nullptr)
owner->drawCurrentImage (g, getLocalBounds());
else
g.fillAll();
}
void changeListenerCallback (ChangeBroadcaster*) override
{
const int64 now = Time::currentTimeMillis();
if (now >= lastRepaintTime + (1000 / maxFPS))
{
lastRepaintTime = now;
repaint();
if (owner != nullptr)
maxFPS = owner->previewMaxFPS;
}
}
private:
Pimpl* owner;
int maxFPS;
int64 lastRepaintTime;
};
void CameraDevice::Pimpl::disconnectAnyViewers()
{
for (int i = viewerComps.size(); --i >= 0;)
viewerComps.getUnchecked(i)->ownerDeleted();
}
String CameraDevice::getFileExtension()
{
return ".wmv";
}

View File

@ -0,0 +1,892 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
namespace VideoRenderers
{
//======================================================================
struct Base
{
virtual ~Base() {}
virtual HRESULT create (ComSmartPtr<IGraphBuilder>&, ComSmartPtr<IBaseFilter>&, HWND) = 0;
virtual void setVideoWindow (HWND) = 0;
virtual void setVideoPosition (HWND) = 0;
virtual void repaintVideo (HWND, HDC) = 0;
virtual void displayModeChanged() = 0;
virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0;
};
//======================================================================
struct VMR7 : public Base
{
VMR7() {}
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
{
ComSmartPtr<IVMRFilterConfig> filterConfig;
HRESULT hr = baseFilter.CoCreateInstance (CLSID_VideoMixingRenderer);
if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"VMR-7");
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (filterConfig);
if (SUCCEEDED (hr)) hr = filterConfig->SetRenderingMode (VMRMode_Windowless);
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (windowlessControl);
if (SUCCEEDED (hr)) hr = windowlessControl->SetVideoClippingWindow (hwnd);
if (SUCCEEDED (hr)) hr = windowlessControl->SetAspectRatioMode (VMR_ARMODE_LETTER_BOX);
return hr;
}
void setVideoWindow (HWND hwnd) override
{
windowlessControl->SetVideoClippingWindow (hwnd);
}
void setVideoPosition (HWND hwnd) override
{
long videoWidth = 0, videoHeight = 0;
windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
RECT src, dest;
SetRect (&src, 0, 0, videoWidth, videoHeight);
GetClientRect (hwnd, &dest);
windowlessControl->SetVideoPosition (&src, &dest);
}
void repaintVideo (HWND hwnd, HDC hdc) override
{
windowlessControl->RepaintVideo (hwnd, hdc);
}
void displayModeChanged() override
{
windowlessControl->DisplayModeChanged();
}
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
{
return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr);
}
ComSmartPtr<IVMRWindowlessControl> windowlessControl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7)
};
//======================================================================
struct EVR : public Base
{
EVR() {}
HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder,
ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) override
{
ComSmartPtr<IMFGetService> getService;
HRESULT hr = baseFilter.CoCreateInstance (CLSID_EnhancedVideoRenderer);
if (SUCCEEDED (hr)) hr = graphBuilder->AddFilter (baseFilter, L"EVR");
if (SUCCEEDED (hr)) hr = baseFilter.QueryInterface (getService);
if (SUCCEEDED (hr)) hr = getService->GetService (MR_VIDEO_RENDER_SERVICE, IID_IMFVideoDisplayControl,
(void**) videoDisplayControl.resetAndGetPointerAddress());
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetVideoWindow (hwnd);
if (SUCCEEDED (hr)) hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture);
return hr;
}
void setVideoWindow (HWND hwnd) override
{
videoDisplayControl->SetVideoWindow (hwnd);
}
void setVideoPosition (HWND hwnd) override
{
const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f };
RECT dest;
GetClientRect (hwnd, &dest);
videoDisplayControl->SetVideoPosition (&src, &dest);
}
void repaintVideo (HWND, HDC) override
{
videoDisplayControl->RepaintVideo();
}
void displayModeChanged() override {}
HRESULT getVideoSize (long& videoWidth, long& videoHeight) override
{
SIZE sz = { 0, 0 };
HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr);
videoWidth = sz.cx;
videoHeight = sz.cy;
return hr;
}
ComSmartPtr<IMFVideoDisplayControl> videoDisplayControl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR)
};
};
//==============================================================================
struct VideoComponent::Pimpl : public Component
{
Pimpl() : videoLoaded (false)
{
setOpaque (true);
context.reset (new DirectShowContext (*this));
componentWatcher.reset (new ComponentWatcher (*this));
}
~Pimpl()
{
close();
context = nullptr;
componentWatcher = nullptr;
}
Result loadFromString (const String& fileOrURLPath)
{
close();
auto r = context->loadFile (fileOrURLPath);
if (r.wasOk())
{
videoLoaded = true;
context->updateVideoPosition();
}
return r;
}
Result load (const File& file)
{
auto r = loadFromString (file.getFullPathName());
if (r.wasOk())
currentFile = file;
return r;
}
Result load (const URL& url)
{
auto r = loadFromString (url.toString (true));
if (r.wasOk())
currentURL = url;
return r;
}
void close()
{
stop();
context->release();
videoLoaded = false;
currentFile = File();
currentURL = {};
}
bool isOpen() const
{
return videoLoaded;
}
bool isPlaying() const
{
return context->state == DirectShowContext::runningState;
}
void play()
{
if (videoLoaded)
context->play();
}
void stop()
{
if (videoLoaded)
context->pause();
}
void setPosition (double newPosition)
{
if (videoLoaded)
context->setPosition (newPosition);
}
double getPosition() const
{
return videoLoaded ? context->getPosition() : 0.0;
}
void setSpeed (double newSpeed)
{
if (videoLoaded)
context->setSpeed (newSpeed);
}
Rectangle<int> getNativeSize() const
{
return videoLoaded ? context->getVideoSize()
: Rectangle<int>();
}
double getDuration() const
{
return videoLoaded ? context->getDuration() : 0.0;
}
void setVolume (float newVolume)
{
if (videoLoaded)
context->setVolume (newVolume);
}
float getVolume() const
{
return videoLoaded ? context->getVolume() : 0.0f;
}
void paint (Graphics& g) override
{
if (videoLoaded)
context->handleUpdateNowIfNeeded();
else
g.fillAll (Colours::grey);
}
void updateContextPosition()
{
context->updateContextPosition();
if (getWidth() > 0 && getHeight() > 0)
if (auto* peer = getTopLevelComponent()->getPeer())
context->updateWindowPosition (peer->getAreaCoveredBy (*this));
}
void updateContextVisibility()
{
context->showWindow (isShowing());
}
void recreateNativeWindowAsync()
{
context->recreateNativeWindowAsync();
repaint();
}
File currentFile;
URL currentURL;
private:
bool videoLoaded;
//==============================================================================
struct ComponentWatcher : public ComponentMovementWatcher
{
ComponentWatcher (Pimpl& c) : ComponentMovementWatcher (&c), owner (c)
{
}
void componentMovedOrResized (bool, bool) override
{
if (owner.videoLoaded)
owner.updateContextPosition();
}
void componentPeerChanged() override
{
if (owner.videoLoaded)
owner.recreateNativeWindowAsync();
}
void componentVisibilityChanged() override
{
if (owner.videoLoaded)
owner.updateContextVisibility();
}
Pimpl& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentWatcher)
};
std::unique_ptr<ComponentWatcher> componentWatcher;
//======================================================================
struct DirectShowContext : public AsyncUpdater
{
DirectShowContext (Pimpl& c) : component (c)
{
CoInitialize (0);
}
~DirectShowContext()
{
release();
CoUninitialize();
}
//======================================================================
void updateWindowPosition (const Rectangle<int>& newBounds)
{
nativeWindow->setWindowPosition (newBounds);
}
void showWindow (bool shouldBeVisible)
{
nativeWindow->showWindow (shouldBeVisible);
}
//======================================================================
void repaint()
{
if (hasVideo)
videoRenderer->repaintVideo (nativeWindow->hwnd, nativeWindow->hdc);
}
void updateVideoPosition()
{
if (hasVideo)
videoRenderer->setVideoPosition (nativeWindow->hwnd);
}
void displayResolutionChanged()
{
if (hasVideo)
videoRenderer->displayModeChanged();
}
//======================================================================
void peerChanged()
{
deleteNativeWindow();
mediaEvent->SetNotifyWindow (0, 0, 0);
if (videoRenderer != nullptr)
videoRenderer->setVideoWindow (nullptr);
createNativeWindow();
mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
if (videoRenderer != nullptr)
videoRenderer->setVideoWindow (hwnd);
}
void handleAsyncUpdate() override
{
if (hwnd != 0)
{
if (needToRecreateNativeWindow)
{
peerChanged();
needToRecreateNativeWindow = false;
}
if (needToUpdateViewport)
{
updateVideoPosition();
needToUpdateViewport = false;
}
repaint();
}
else
{
triggerAsyncUpdate();
}
}
void recreateNativeWindowAsync()
{
needToRecreateNativeWindow = true;
triggerAsyncUpdate();
}
void updateContextPosition()
{
needToUpdateViewport = true;
triggerAsyncUpdate();
}
//======================================================================
Result loadFile (const String& fileOrURLPath)
{
jassert (state == uninitializedState);
if (! createNativeWindow())
return Result::fail ("Can't create window");
HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph);
// basic playback interfaces
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaControl);
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaPosition);
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (mediaEvent);
if (SUCCEEDED (hr)) hr = graphBuilder.QueryInterface (basicAudio);
// video renderer interface
if (SUCCEEDED (hr))
{
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
{
videoRenderer.reset (new VideoRenderers::EVR());
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
if (FAILED (hr))
videoRenderer = nullptr;
}
if (videoRenderer == nullptr)
{
videoRenderer.reset (new VideoRenderers::VMR7());
hr = videoRenderer->create (graphBuilder, baseFilter, hwnd);
}
}
// build filter graph
if (SUCCEEDED (hr))
{
hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr);
if (FAILED (hr))
{
#if JUCE_MODAL_LOOPS_PERMITTED
// Annoyingly, if we don't run the msg loop between failing and deleting the window, the
// whole OS message-dispatch system gets itself into a state, and refuses to deliver any
// more messages for the whole app. (That's what happens in Win7, anyway)
MessageManager::getInstance()->runDispatchLoopUntil (200);
#endif
}
}
// remove video renderer if not connected (no video)
if (SUCCEEDED (hr))
{
if (isRendererConnected())
{
hasVideo = true;
}
else
{
hasVideo = false;
graphBuilder->RemoveFilter (baseFilter);
videoRenderer = nullptr;
baseFilter = nullptr;
}
}
// set window to receive events
if (SUCCEEDED (hr))
hr = mediaEvent->SetNotifyWindow ((OAHWND) hwnd, graphEventID, 0);
if (SUCCEEDED (hr))
{
state = stoppedState;
pause();
return Result::ok();
}
// Note that if you're trying to open a file and this method fails, you may
// just need to install a suitable codec. It seems that by default DirectShow
// doesn't support a very good range of formats.
release();
return getErrorMessageFromResult (hr);
}
static Result getErrorMessageFromResult (HRESULT hr)
{
switch (hr)
{
case VFW_E_INVALID_FILE_FORMAT: return Result::fail ("Invalid file format");
case VFW_E_NOT_FOUND: return Result::fail ("File not found");
case VFW_E_UNKNOWN_FILE_TYPE: return Result::fail ("Unknown file type");
case VFW_E_UNSUPPORTED_STREAM: return Result::fail ("Unsupported stream");
case VFW_E_CANNOT_CONNECT: return Result::fail ("Cannot connect");
case VFW_E_CANNOT_LOAD_SOURCE_FILTER: return Result::fail ("Cannot load source filter");
}
TCHAR messageBuffer[512] = { 0 };
FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
return Result::fail (String (messageBuffer));
}
void release()
{
if (mediaControl != nullptr)
mediaControl->Stop();
if (mediaEvent != nullptr)
mediaEvent->SetNotifyWindow (0, 0, 0);
if (videoRenderer != nullptr)
videoRenderer->setVideoWindow (0);
hasVideo = false;
videoRenderer = nullptr;
baseFilter = nullptr;
basicAudio = nullptr;
mediaEvent = nullptr;
mediaPosition = nullptr;
mediaControl = nullptr;
graphBuilder = nullptr;
state = uninitializedState;
if (nativeWindow != nullptr)
deleteNativeWindow();
}
void graphEventProc()
{
LONG ec = 0;
LONG_PTR p1 = {}, p2 = {};
jassert (mediaEvent != nullptr);
while (SUCCEEDED (mediaEvent->GetEvent (&ec, &p1, &p2, 0)))
{
mediaEvent->FreeEventParams (ec, p1, p2);
switch (ec)
{
case EC_REPAINT:
component.repaint();
break;
case EC_COMPLETE:
component.stop();
break;
case EC_USERABORT:
case EC_ERRORABORT:
case EC_ERRORABORTEX:
component.close();
break;
default:
break;
}
}
}
//======================================================================
void play()
{
mediaControl->Run();
state = runningState;
}
void stop()
{
mediaControl->Stop();
state = stoppedState;
}
void pause()
{
mediaControl->Pause();
state = pausedState;
}
//======================================================================
Rectangle<int> getVideoSize() const noexcept
{
long width = 0, height = 0;
if (hasVideo)
videoRenderer->getVideoSize (width, height);
return { (int) width, (int) height };
}
//======================================================================
double getDuration() const
{
REFTIME duration;
mediaPosition->get_Duration (&duration);
return duration;
}
double getPosition() const
{
REFTIME seconds;
mediaPosition->get_CurrentPosition (&seconds);
return seconds;
}
void setSpeed (double newSpeed) { mediaPosition->put_Rate (newSpeed); }
void setPosition (double seconds) { mediaPosition->put_CurrentPosition (seconds); }
void setVolume (float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); }
// in DirectShow, full volume is 0, silence is -10000
static long convertToDShowVolume (float vol) noexcept
{
if (vol >= 1.0f) return 0;
if (vol <= 0.0f) return -10000;
return roundToInt ((vol * 10000.0f) - 10000.0f);
}
float getVolume() const
{
long volume;
basicAudio->get_Volume (&volume);
return (volume + 10000) / 10000.0f;
}
enum State { uninitializedState, runningState, pausedState, stoppedState };
State state = uninitializedState;
private:
//======================================================================
enum { graphEventID = WM_APP + 0x43f0 };
Pimpl& component;
HWND hwnd = {};
HDC hdc = {};
ComSmartPtr<IGraphBuilder> graphBuilder;
ComSmartPtr<IMediaControl> mediaControl;
ComSmartPtr<IMediaPosition> mediaPosition;
ComSmartPtr<IMediaEventEx> mediaEvent;
ComSmartPtr<IBasicAudio> basicAudio;
ComSmartPtr<IBaseFilter> baseFilter;
std::unique_ptr<VideoRenderers::Base> videoRenderer;
bool hasVideo = false, needToUpdateViewport = true, needToRecreateNativeWindow = false;
//======================================================================
bool createNativeWindow()
{
jassert (nativeWindow == nullptr);
if (auto* topLevelPeer = component.getTopLevelComponent()->getPeer())
{
nativeWindow.reset (new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this));
hwnd = nativeWindow->hwnd;
if (hwnd != 0)
{
hdc = GetDC (hwnd);
component.updateContextPosition();
component.updateContextVisibility();
return true;
}
nativeWindow = nullptr;
}
else
{
jassertfalse;
}
return false;
}
void deleteNativeWindow()
{
jassert (nativeWindow != nullptr);
ReleaseDC (hwnd, hdc);
hwnd = {};
hdc = {};
nativeWindow = nullptr;
}
bool isRendererConnected()
{
ComSmartPtr<IEnumPins> enumPins;
HRESULT hr = baseFilter->EnumPins (enumPins.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
hr = enumPins->Reset();
ComSmartPtr<IPin> pin;
while (SUCCEEDED (hr)
&& enumPins->Next (1, pin.resetAndGetPointerAddress(), nullptr) == S_OK)
{
ComSmartPtr<IPin> otherPin;
hr = pin->ConnectedTo (otherPin.resetAndGetPointerAddress());
if (SUCCEEDED (hr))
{
PIN_DIRECTION direction;
hr = pin->QueryDirection (&direction);
if (SUCCEEDED (hr) && direction == PINDIR_INPUT)
return true;
}
else if (hr == VFW_E_NOT_CONNECTED)
{
hr = S_OK;
}
}
return false;
}
//======================================================================
struct NativeWindowClass : private DeletedAtShutdown
{
bool isRegistered() const noexcept { return atom != 0; }
LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) MAKELONG (atom, 0); }
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (NativeWindowClass)
private:
NativeWindowClass()
{
String windowClassName ("JUCE_DIRECTSHOW_");
windowClassName << (int) (Time::currentTimeMillis() & 0x7fffffff);
HINSTANCE moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
TCHAR moduleFile [1024] = { 0 };
GetModuleFileName (moduleHandle, moduleFile, 1024);
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof (wcex);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = (WNDPROC) wndProc;
wcex.lpszClassName = windowClassName.toWideCharPointer();
wcex.hInstance = moduleHandle;
atom = RegisterClassEx (&wcex);
jassert (atom != 0);
}
~NativeWindowClass()
{
if (atom != 0)
UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
clearSingletonInstance();
}
static LRESULT CALLBACK wndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (auto* c = (DirectShowContext*) GetWindowLongPtr (hwnd, GWLP_USERDATA))
{
switch (msg)
{
case WM_NCHITTEST: return HTTRANSPARENT;
case WM_ERASEBKGND: return 1;
case WM_DISPLAYCHANGE: c->displayResolutionChanged(); break;
case graphEventID: c->graphEventProc(); return 0;
default: break;
}
}
return DefWindowProc (hwnd, msg, wParam, lParam);
}
ATOM atom = {};
JUCE_DECLARE_NON_COPYABLE (NativeWindowClass)
};
//======================================================================
struct NativeWindow
{
NativeWindow (HWND parentToAddTo, void* userData)
{
auto* wc = NativeWindowClass::getInstance();
if (wc->isRegistered())
{
DWORD exstyle = 0;
DWORD type = WS_CHILD;
hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(),
L"", type, 0, 0, 0, 0, parentToAddTo, 0,
(HINSTANCE) Process::getCurrentModuleInstanceHandle(), 0);
if (hwnd != 0)
{
hdc = GetDC (hwnd);
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) userData);
}
}
jassert (hwnd != 0);
}
~NativeWindow()
{
if (hwnd != 0)
{
SetWindowLongPtr (hwnd, GWLP_USERDATA, (LONG_PTR) 0);
DestroyWindow (hwnd);
}
}
void setWindowPosition (Rectangle<int> newBounds)
{
SetWindowPos (hwnd, 0, newBounds.getX(), newBounds.getY(),
newBounds.getWidth(), newBounds.getHeight(),
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
}
void showWindow (bool shouldBeVisible)
{
ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
}
HWND hwnd = {};
HDC hdc = {};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow)
};
std::unique_ptr<NativeWindow> nativeWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext)
};
std::unique_ptr<DirectShowContext> context;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
JUCE_IMPLEMENT_SINGLETON (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass)