| @@ -63,9 +63,7 @@ public: | |||||
| void resized() override | void resized() override | ||||
| { | { | ||||
| videoComp.setBoundsWithCorrectAspectRatio (Rectangle<int> (0, 0, getWidth(), getHeight() - 30), | |||||
| Justification::centred); | |||||
| fileChooser.setBounds (0, getHeight() - 24, getWidth(), 24); | |||||
| videoComp.setBounds (getLocalBounds().reduced (10)); | |||||
| } | } | ||||
| bool isInterestedInDragSource (const SourceDetails&) override { return true; } | bool isInterestedInDragSource (const SourceDetails&) override { return true; } | ||||
| @@ -90,11 +88,7 @@ public: | |||||
| } | } | ||||
| private: | private: | ||||
| #if JUCE_MAC | |||||
| MovieComponent videoComp; | |||||
| #elif JUCE_DIRECTSHOW | |||||
| DirectShowComponent videoComp; | |||||
| #endif | |||||
| VideoComponent videoComp; | |||||
| bool isDragOver; | bool isDragOver; | ||||
| FilenameComponent fileChooser; | FilenameComponent fileChooser; | ||||
| @@ -102,7 +96,9 @@ private: | |||||
| void filenameComponentChanged (FilenameComponent*) override | void filenameComponentChanged (FilenameComponent*) override | ||||
| { | { | ||||
| // this is called when the user changes the filename in the file chooser box | // this is called when the user changes the filename in the file chooser box | ||||
| if (videoComp.loadMovie (fileChooser.getCurrentFile())) | |||||
| auto result = videoComp.load (fileChooser.getCurrentFile()); | |||||
| if (result.wasOk()) | |||||
| { | { | ||||
| // loaded the file ok, so let's start it playing.. | // loaded the file ok, so let's start it playing.. | ||||
| @@ -112,7 +108,8 @@ private: | |||||
| else | else | ||||
| { | { | ||||
| AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | ||||
| "Couldn't load the file!", String()); | |||||
| "Couldn't load the file!", | |||||
| result.getErrorMessage()); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,389 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if JUCE_QUICKTIME && ! (JUCE_64BIT || JUCE_IOS) | |||||
| } // (juce namespace) | |||||
| #if ! JUCE_WINDOWS | |||||
| #include <QuickTime/Movies.h> | |||||
| #include <QuickTime/QTML.h> | |||||
| #include <QuickTime/QuickTimeComponents.h> | |||||
| #include <QuickTime/MediaHandlers.h> | |||||
| #include <QuickTime/ImageCodec.h> | |||||
| #else | |||||
| #if JUCE_MSVC | |||||
| #pragma warning (push) | |||||
| #pragma warning (disable : 4100) | |||||
| #endif | |||||
| /* If you've got an include error here, you probably need to install the QuickTime SDK and | |||||
| add its header directory to your include path. | |||||
| Alternatively, if you don't need any QuickTime services, just set the JUCE_QUICKTIME flag to 0. | |||||
| */ | |||||
| #undef SIZE_MAX | |||||
| #include <Movies.h> | |||||
| #include <QTML.h> | |||||
| #include <QuickTimeComponents.h> | |||||
| #include <MediaHandlers.h> | |||||
| #include <ImageCodec.h> | |||||
| #undef SIZE_MAX | |||||
| #if JUCE_MSVC | |||||
| #pragma warning (pop) | |||||
| #endif | |||||
| #endif | |||||
| namespace juce | |||||
| { | |||||
| bool juce_OpenQuickTimeMovieFromStream (InputStream* input, Movie& movie, Handle& dataHandle); | |||||
| static const char* const quickTimeFormatName = "QuickTime file"; | |||||
| //============================================================================== | |||||
| class QTAudioReader : public AudioFormatReader | |||||
| { | |||||
| public: | |||||
| QTAudioReader (InputStream* const input_, const int trackNum_) | |||||
| : AudioFormatReader (input_, quickTimeFormatName), | |||||
| ok (false), | |||||
| movie (0), | |||||
| trackNum (trackNum_), | |||||
| lastSampleRead (0), | |||||
| lastThreadId (0), | |||||
| extractor (0), | |||||
| dataHandle (0) | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| bufferList.calloc (256, 1); | |||||
| #if JUCE_WINDOWS | |||||
| if (InitializeQTML (0) != noErr) | |||||
| return; | |||||
| #endif | |||||
| if (EnterMovies() != noErr) | |||||
| return; | |||||
| bool opened = juce_OpenQuickTimeMovieFromStream (input_, movie, dataHandle); | |||||
| if (! opened) | |||||
| return; | |||||
| { | |||||
| const int numTracks = GetMovieTrackCount (movie); | |||||
| int trackCount = 0; | |||||
| for (int i = 1; i <= numTracks; ++i) | |||||
| { | |||||
| track = GetMovieIndTrack (movie, i); | |||||
| media = GetTrackMedia (track); | |||||
| OSType mediaType; | |||||
| GetMediaHandlerDescription (media, &mediaType, 0, 0); | |||||
| if (mediaType == SoundMediaType | |||||
| && trackCount++ == trackNum_) | |||||
| { | |||||
| ok = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (! ok) | |||||
| return; | |||||
| ok = false; | |||||
| lengthInSamples = GetMediaDecodeDuration (media); | |||||
| usesFloatingPointData = false; | |||||
| samplesPerFrame = (int) (GetMediaDecodeDuration (media) / GetMediaSampleCount (media)); | |||||
| trackUnitsPerFrame = GetMovieTimeScale (movie) * samplesPerFrame | |||||
| / GetMediaTimeScale (media); | |||||
| MovieAudioExtractionBegin (movie, 0, &extractor); | |||||
| unsigned long output_layout_size; | |||||
| OSStatus err = MovieAudioExtractionGetPropertyInfo (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Audio, | |||||
| kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||||
| 0, &output_layout_size, 0); | |||||
| if (err != noErr) | |||||
| return; | |||||
| HeapBlock<AudioChannelLayout> qt_audio_channel_layout; | |||||
| qt_audio_channel_layout.calloc (output_layout_size, 1); | |||||
| MovieAudioExtractionGetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Audio, | |||||
| kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||||
| output_layout_size, qt_audio_channel_layout, 0); | |||||
| qt_audio_channel_layout[0].mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; | |||||
| MovieAudioExtractionSetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Audio, | |||||
| kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, | |||||
| output_layout_size, | |||||
| qt_audio_channel_layout); | |||||
| err = MovieAudioExtractionGetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Audio, | |||||
| kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |||||
| sizeof (inputStreamDesc), | |||||
| &inputStreamDesc, 0); | |||||
| if (err != noErr) | |||||
| return; | |||||
| inputStreamDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger | |||||
| | kAudioFormatFlagIsPacked | |||||
| | kAudioFormatFlagsNativeEndian; | |||||
| inputStreamDesc.mBitsPerChannel = sizeof (SInt16) * 8; | |||||
| inputStreamDesc.mChannelsPerFrame = jmin ((UInt32) 2, inputStreamDesc.mChannelsPerFrame); | |||||
| inputStreamDesc.mBytesPerFrame = sizeof (SInt16) * inputStreamDesc.mChannelsPerFrame; | |||||
| inputStreamDesc.mBytesPerPacket = inputStreamDesc.mBytesPerFrame; | |||||
| err = MovieAudioExtractionSetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Audio, | |||||
| kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, | |||||
| sizeof (inputStreamDesc), | |||||
| &inputStreamDesc); | |||||
| if (err != noErr) | |||||
| return; | |||||
| Boolean allChannelsDiscrete = false; | |||||
| err = MovieAudioExtractionSetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Movie, | |||||
| kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, | |||||
| sizeof (allChannelsDiscrete), | |||||
| &allChannelsDiscrete); | |||||
| if (err != noErr) | |||||
| return; | |||||
| bufferList->mNumberBuffers = 1; | |||||
| bufferList->mBuffers[0].mNumberChannels = inputStreamDesc.mChannelsPerFrame; | |||||
| bufferList->mBuffers[0].mDataByteSize = jmax ((UInt32) 4096, (UInt32) (samplesPerFrame * (int) inputStreamDesc.mBytesPerFrame) + 16); | |||||
| dataBuffer.malloc (bufferList->mBuffers[0].mDataByteSize); | |||||
| bufferList->mBuffers[0].mData = dataBuffer; | |||||
| sampleRate = inputStreamDesc.mSampleRate; | |||||
| bitsPerSample = 16; | |||||
| numChannels = inputStreamDesc.mChannelsPerFrame; | |||||
| detachThread(); | |||||
| ok = true; | |||||
| } | |||||
| } | |||||
| ~QTAudioReader() | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| checkThreadIsAttached(); | |||||
| if (dataHandle != nullptr) | |||||
| DisposeHandle (dataHandle); | |||||
| if (extractor != nullptr) | |||||
| { | |||||
| MovieAudioExtractionEnd (extractor); | |||||
| extractor = nullptr; | |||||
| } | |||||
| DisposeMovie (movie); | |||||
| #if JUCE_MAC | |||||
| ExitMoviesOnThread(); | |||||
| #endif | |||||
| } | |||||
| } | |||||
| bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, | |||||
| int64 startSampleInFile, int numSamples) | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| checkThreadIsAttached(); | |||||
| bool readOk = true; | |||||
| while (numSamples > 0) | |||||
| { | |||||
| if (lastSampleRead != startSampleInFile) | |||||
| { | |||||
| TimeRecord time; | |||||
| time.scale = (TimeScale) inputStreamDesc.mSampleRate; | |||||
| time.base = 0; | |||||
| time.value.hi = 0; | |||||
| time.value.lo = (UInt32) startSampleInFile; | |||||
| OSStatus err = MovieAudioExtractionSetProperty (extractor, | |||||
| kQTPropertyClass_MovieAudioExtraction_Movie, | |||||
| kQTMovieAudioExtractionMoviePropertyID_CurrentTime, | |||||
| sizeof (time), &time); | |||||
| if (err != noErr) | |||||
| { | |||||
| readOk = false; | |||||
| break; | |||||
| } | |||||
| } | |||||
| int framesToDo = jmin (numSamples, (int) (bufferList->mBuffers[0].mDataByteSize / inputStreamDesc.mBytesPerFrame)); | |||||
| bufferList->mBuffers[0].mDataByteSize = inputStreamDesc.mBytesPerFrame * (UInt32) framesToDo; | |||||
| UInt32 outFlags = 0; | |||||
| UInt32 actualNumFrames = (UInt32) framesToDo; | |||||
| OSStatus err = MovieAudioExtractionFillBuffer (extractor, &actualNumFrames, bufferList, &outFlags); | |||||
| if (err != noErr) | |||||
| { | |||||
| readOk = false; | |||||
| break; | |||||
| } | |||||
| lastSampleRead = startSampleInFile + actualNumFrames; | |||||
| const int samplesReceived = (int) actualNumFrames; | |||||
| for (int j = numDestChannels; --j >= 0;) | |||||
| { | |||||
| if (destSamples[j] != nullptr) | |||||
| { | |||||
| const short* src = ((const short*) bufferList->mBuffers[0].mData) + j; | |||||
| for (int i = 0; i < samplesReceived; ++i) | |||||
| { | |||||
| destSamples[j][startOffsetInDestBuffer + i] = (*src << 16); | |||||
| src += numChannels; | |||||
| } | |||||
| } | |||||
| } | |||||
| startOffsetInDestBuffer += samplesReceived; | |||||
| startSampleInFile += samplesReceived; | |||||
| numSamples -= samplesReceived; | |||||
| if (((outFlags & kQTMovieAudioExtractionComplete) != 0 || samplesReceived == 0) && numSamples > 0) | |||||
| { | |||||
| for (int j = numDestChannels; --j >= 0;) | |||||
| if (destSamples[j] != nullptr) | |||||
| zeromem (destSamples[j] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples); | |||||
| break; | |||||
| } | |||||
| } | |||||
| detachThread(); | |||||
| return readOk; | |||||
| } | |||||
| } | |||||
| bool ok; | |||||
| private: | |||||
| Movie movie; | |||||
| Media media; | |||||
| Track track; | |||||
| const int trackNum; | |||||
| double trackUnitsPerFrame; | |||||
| int samplesPerFrame; | |||||
| int64 lastSampleRead; | |||||
| Thread::ThreadID lastThreadId; | |||||
| MovieAudioExtractionRef extractor; | |||||
| AudioStreamBasicDescription inputStreamDesc; | |||||
| HeapBlock<AudioBufferList> bufferList; | |||||
| HeapBlock<char> dataBuffer; | |||||
| Handle dataHandle; | |||||
| //============================================================================== | |||||
| void checkThreadIsAttached() | |||||
| { | |||||
| #if JUCE_MAC | |||||
| if (Thread::getCurrentThreadId() != lastThreadId) | |||||
| EnterMoviesOnThread (0); | |||||
| AttachMovieToCurrentThread (movie); | |||||
| #endif | |||||
| } | |||||
| void detachThread() | |||||
| { | |||||
| #if JUCE_MAC | |||||
| DetachMovieFromCurrentThread (movie); | |||||
| #endif | |||||
| } | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QTAudioReader) | |||||
| }; | |||||
| //============================================================================== | |||||
| QuickTimeAudioFormat::QuickTimeAudioFormat() : AudioFormat (quickTimeFormatName, ".mov .mp3 .mp4 .m4a") | |||||
| { | |||||
| } | |||||
| QuickTimeAudioFormat::~QuickTimeAudioFormat() | |||||
| { | |||||
| } | |||||
| Array<int> QuickTimeAudioFormat::getPossibleSampleRates() { return Array<int>(); } | |||||
| Array<int> QuickTimeAudioFormat::getPossibleBitDepths() { return Array<int>(); } | |||||
| bool QuickTimeAudioFormat::canDoStereo() { return true; } | |||||
| bool QuickTimeAudioFormat::canDoMono() { return true; } | |||||
| //============================================================================== | |||||
| AudioFormatReader* QuickTimeAudioFormat::createReaderFor (InputStream* sourceStream, | |||||
| const bool deleteStreamIfOpeningFails) | |||||
| { | |||||
| ScopedPointer<QTAudioReader> r (new QTAudioReader (sourceStream, 0)); | |||||
| if (r->ok) | |||||
| return r.release(); | |||||
| if (! deleteStreamIfOpeningFails) | |||||
| r->input = 0; | |||||
| return nullptr; | |||||
| } | |||||
| AudioFormatWriter* QuickTimeAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, | |||||
| double /*sampleRateToUse*/, | |||||
| unsigned int /*numberOfChannels*/, | |||||
| int /*bitsPerSample*/, | |||||
| const StringPairArray& /*metadataValues*/, | |||||
| int /*qualityOptionIndex*/) | |||||
| { | |||||
| jassertfalse; // not yet implemented! | |||||
| return nullptr; | |||||
| } | |||||
| #endif | |||||
| @@ -1,71 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if JUCE_QUICKTIME | |||||
| //============================================================================== | |||||
| /** | |||||
| Uses QuickTime to read the audio track a movie or media file. | |||||
| As well as QuickTime movies, this should also manage to open other audio | |||||
| files that quicktime can understand, like mp3, m4a, etc. | |||||
| @see AudioFormat | |||||
| */ | |||||
| class JUCE_API QuickTimeAudioFormat : public AudioFormat | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates a format object. */ | |||||
| QuickTimeAudioFormat(); | |||||
| /** Destructor. */ | |||||
| ~QuickTimeAudioFormat(); | |||||
| //============================================================================== | |||||
| Array<int> getPossibleSampleRates(); | |||||
| Array<int> getPossibleBitDepths(); | |||||
| bool canDoStereo(); | |||||
| bool canDoMono(); | |||||
| //============================================================================== | |||||
| AudioFormatReader* createReaderFor (InputStream* sourceStream, | |||||
| bool deleteStreamIfOpeningFails); | |||||
| AudioFormatWriter* createWriterFor (OutputStream* streamToWriteTo, | |||||
| double sampleRateToUse, | |||||
| unsigned int numberOfChannels, | |||||
| int bitsPerSample, | |||||
| const StringPairArray& metadataValues, | |||||
| int qualityOptionIndex); | |||||
| private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (QuickTimeAudioFormat) | |||||
| }; | |||||
| #endif | |||||
| @@ -41,9 +41,6 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| #if JUCE_QUICKTIME | |||||
| #import <QTKit/QTKit.h> | |||||
| #endif | |||||
| #include <AudioToolbox/AudioToolbox.h> | #include <AudioToolbox/AudioToolbox.h> | ||||
| #elif JUCE_IOS | #elif JUCE_IOS | ||||
| @@ -51,44 +48,14 @@ | |||||
| #import <AVFoundation/AVFoundation.h> | #import <AVFoundation/AVFoundation.h> | ||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_WINDOWS | |||||
| #if JUCE_QUICKTIME | |||||
| /* If you've got an include error here, you probably need to install the QuickTime SDK and | |||||
| add its header directory to your include path. | |||||
| Alternatively, if you don't need any QuickTime services, just set the JUCE_QUICKTIME flag to 0. | |||||
| */ | |||||
| #include <Movies.h> | |||||
| #include <QTML.h> | |||||
| #include <QuickTimeComponents.h> | |||||
| #include <MediaHandlers.h> | |||||
| #include <ImageCodec.h> | |||||
| /* If you've got QuickTime 7 installed, then these COM objects should be found in | |||||
| the "\Program Files\Quicktime" directory. You'll need to add this directory to | |||||
| your include search path to make these import statements work. | |||||
| */ | |||||
| #import <QTOLibrary.dll> | |||||
| #import <QTOControl.dll> | |||||
| #if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
| #pragma comment (lib, "QTMLClient.lib") | |||||
| #endif | |||||
| #endif | |||||
| #if JUCE_USE_WINDOWS_MEDIA_FORMAT | |||||
| #include <wmsdk.h> | |||||
| #endif | |||||
| #elif JUCE_WINDOWS && JUCE_USE_WINDOWS_MEDIA_FORMAT | |||||
| #include <wmsdk.h> | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| #if JUCE_ANDROID | |||||
| #undef JUCE_QUICKTIME | |||||
| #endif | |||||
| #include "format/juce_AudioFormat.cpp" | #include "format/juce_AudioFormat.cpp" | ||||
| #include "format/juce_AudioFormatManager.cpp" | #include "format/juce_AudioFormatManager.cpp" | ||||
| #include "format/juce_AudioFormatReader.cpp" | #include "format/juce_AudioFormatReader.cpp" | ||||
| @@ -102,7 +69,6 @@ namespace juce | |||||
| #include "codecs/juce_FlacAudioFormat.cpp" | #include "codecs/juce_FlacAudioFormat.cpp" | ||||
| #include "codecs/juce_MP3AudioFormat.cpp" | #include "codecs/juce_MP3AudioFormat.cpp" | ||||
| #include "codecs/juce_OggVorbisAudioFormat.cpp" | #include "codecs/juce_OggVorbisAudioFormat.cpp" | ||||
| #include "codecs/juce_QuickTimeAudioFormat.cpp" | |||||
| #include "codecs/juce_WavAudioFormat.cpp" | #include "codecs/juce_WavAudioFormat.cpp" | ||||
| #include "codecs/juce_LAMEEncoderAudioFormat.cpp" | #include "codecs/juce_LAMEEncoderAudioFormat.cpp" | ||||
| @@ -128,7 +128,6 @@ class AudioFormat; | |||||
| #include "codecs/juce_LAMEEncoderAudioFormat.h" | #include "codecs/juce_LAMEEncoderAudioFormat.h" | ||||
| #include "codecs/juce_MP3AudioFormat.h" | #include "codecs/juce_MP3AudioFormat.h" | ||||
| #include "codecs/juce_OggVorbisAudioFormat.h" | #include "codecs/juce_OggVorbisAudioFormat.h" | ||||
| #include "codecs/juce_QuickTimeAudioFormat.h" | |||||
| #include "codecs/juce_WavAudioFormat.h" | #include "codecs/juce_WavAudioFormat.h" | ||||
| #include "codecs/juce_WindowsMediaAudioFormat.h" | #include "codecs/juce_WindowsMediaAudioFormat.h" | ||||
| #include "sampler/juce_Sampler.h" | #include "sampler/juce_Sampler.h" | ||||
| @@ -0,0 +1,778 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| // This macro can be set if you need to override this internal name for some reason.. | |||||
| #ifndef JUCE_STATE_DICTIONARY_KEY | |||||
| #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" | |||||
| #endif | |||||
| struct AudioUnitHelpers | |||||
| { | |||||
| // maps a channel index into an AU format to an index of a juce format | |||||
| struct AUChannelStreamOrder | |||||
| { | |||||
| AudioChannelLayoutTag auLayoutTag; | |||||
| AudioChannelLabel speakerOrder[8]; | |||||
| }; | |||||
| static AUChannelStreamOrder auChannelStreamOrder[]; | |||||
| static AudioChannelSet::ChannelType CoreAudioChannelLabelToJuceType (AudioChannelLabel label) noexcept | |||||
| { | |||||
| if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) | |||||
| { | |||||
| const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; | |||||
| return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum); | |||||
| } | |||||
| switch (label) | |||||
| { | |||||
| case kAudioChannelLabel_Center: | |||||
| case kAudioChannelLabel_Mono: return AudioChannelSet::centre; | |||||
| case kAudioChannelLabel_Left: | |||||
| case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; | |||||
| case kAudioChannelLabel_Right: | |||||
| case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; | |||||
| case kAudioChannelLabel_LFEScreen: return AudioChannelSet::subbass; | |||||
| case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; | |||||
| case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; | |||||
| case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; | |||||
| case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; | |||||
| case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; | |||||
| case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundDirect; | |||||
| case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundDirect; | |||||
| case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; | |||||
| case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; | |||||
| case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; | |||||
| case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; | |||||
| case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; | |||||
| case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftRearSurround; | |||||
| case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; | |||||
| case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightRearSurround; | |||||
| case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; | |||||
| case kAudioChannelLabel_LFE2: return AudioChannelSet::subbass2; | |||||
| case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; | |||||
| case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; | |||||
| case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; | |||||
| case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; | |||||
| case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; | |||||
| case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; | |||||
| default: return AudioChannelSet::unknown; | |||||
| } | |||||
| } | |||||
| static AudioChannelLabel JuceChannelTypeToCoreAudioLabel (const AudioChannelSet::ChannelType& label) noexcept | |||||
| { | |||||
| if (label >= AudioChannelSet::discreteChannel0) | |||||
| { | |||||
| const unsigned int discreteChannelNum = label - AudioChannelSet::discreteChannel0;; | |||||
| return static_cast<AudioChannelLabel> (kAudioChannelLabel_Discrete_0 + discreteChannelNum); | |||||
| } | |||||
| switch (label) | |||||
| { | |||||
| case AudioChannelSet::centre: return kAudioChannelLabel_Center; | |||||
| case AudioChannelSet::left: return kAudioChannelLabel_Left; | |||||
| case AudioChannelSet::right: return kAudioChannelLabel_Right; | |||||
| case AudioChannelSet::subbass: return kAudioChannelLabel_LFEScreen; | |||||
| case AudioChannelSet::leftRearSurround: return kAudioChannelLabel_RearSurroundLeft; | |||||
| case AudioChannelSet::rightRearSurround: return kAudioChannelLabel_RearSurroundRight; | |||||
| case AudioChannelSet::leftCentre: return kAudioChannelLabel_LeftCenter; | |||||
| case AudioChannelSet::rightCentre: return kAudioChannelLabel_RightCenter; | |||||
| case AudioChannelSet::surround: return kAudioChannelLabel_CenterSurround; | |||||
| case AudioChannelSet::leftSurround: return kAudioChannelLabel_LeftSurround; | |||||
| case AudioChannelSet::rightSurround: return kAudioChannelLabel_RightSurround; | |||||
| case AudioChannelSet::topMiddle: return kAudioChannelLabel_TopCenterSurround; | |||||
| case AudioChannelSet::topFrontLeft: return kAudioChannelLabel_VerticalHeightLeft; | |||||
| case AudioChannelSet::topFrontRight: return kAudioChannelLabel_VerticalHeightRight; | |||||
| case AudioChannelSet::topFrontCentre: return kAudioChannelLabel_VerticalHeightCenter; | |||||
| case AudioChannelSet::topRearLeft: return kAudioChannelLabel_TopBackLeft; | |||||
| case AudioChannelSet::topRearRight: return kAudioChannelLabel_TopBackRight; | |||||
| case AudioChannelSet::topRearCentre: return kAudioChannelLabel_TopBackCenter; | |||||
| case AudioChannelSet::subbass2: return kAudioChannelLabel_LFE2; | |||||
| case AudioChannelSet::wideLeft: return kAudioChannelLabel_LeftWide; | |||||
| case AudioChannelSet::wideRight: return kAudioChannelLabel_RightWide; | |||||
| case AudioChannelSet::ambisonicW: return kAudioChannelLabel_Ambisonic_W; | |||||
| case AudioChannelSet::ambisonicX: return kAudioChannelLabel_Ambisonic_X; | |||||
| case AudioChannelSet::ambisonicY: return kAudioChannelLabel_Ambisonic_Y; | |||||
| case AudioChannelSet::ambisonicZ: return kAudioChannelLabel_Ambisonic_Z; | |||||
| case AudioChannelSet::leftSurroundDirect: return kAudioChannelLabel_LeftSurroundDirect; | |||||
| case AudioChannelSet::rightSurroundDirect: return kAudioChannelLabel_RightSurroundDirect; | |||||
| case AudioChannelSet::unknown: return kAudioChannelLabel_Unknown; | |||||
| case AudioChannelSet::discreteChannel0: return kAudioChannelLabel_Discrete_0; | |||||
| } | |||||
| return kAudioChannelLabel_Unknown; | |||||
| } | |||||
| static AudioChannelSet CoreAudioChannelBitmapToJuceType (UInt32 bitmap) noexcept | |||||
| { | |||||
| AudioChannelSet set; | |||||
| if ((bitmap & kAudioChannelBit_Left) != 0) set.addChannel (AudioChannelSet::left); | |||||
| if ((bitmap & kAudioChannelBit_Right) != 0) set.addChannel (AudioChannelSet::right); | |||||
| if ((bitmap & kAudioChannelBit_Center) != 0) set.addChannel (AudioChannelSet::centre); | |||||
| if ((bitmap & kAudioChannelBit_LFEScreen) != 0) set.addChannel (AudioChannelSet::subbass); | |||||
| if ((bitmap & kAudioChannelBit_LeftSurroundDirect) != 0) set.addChannel (AudioChannelSet::leftSurroundDirect); | |||||
| if ((bitmap & kAudioChannelBit_RightSurroundDirect) != 0) set.addChannel (AudioChannelSet::rightSurroundDirect); | |||||
| if ((bitmap & kAudioChannelBit_LeftCenter) != 0) set.addChannel (AudioChannelSet::leftCentre); | |||||
| if ((bitmap & kAudioChannelBit_RightCenter) != 0) set.addChannel (AudioChannelSet::rightCentre); | |||||
| if ((bitmap & kAudioChannelBit_CenterSurround) != 0) set.addChannel (AudioChannelSet::surround); | |||||
| if ((bitmap & kAudioChannelBit_LeftSurround) != 0) set.addChannel (AudioChannelSet::leftSurround); | |||||
| if ((bitmap & kAudioChannelBit_RightSurround) != 0) set.addChannel (AudioChannelSet::rightSurround); | |||||
| if ((bitmap & kAudioChannelBit_TopCenterSurround) != 0) set.addChannel (AudioChannelSet::topMiddle); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightLeft) != 0) set.addChannel (AudioChannelSet::topFrontLeft); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightCenter) != 0) set.addChannel (AudioChannelSet::topFrontCentre); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightRight) != 0) set.addChannel (AudioChannelSet::topFrontRight); | |||||
| if ((bitmap & kAudioChannelBit_TopBackLeft) != 0) set.addChannel (AudioChannelSet::topRearLeft); | |||||
| if ((bitmap & kAudioChannelBit_TopBackCenter) != 0) set.addChannel (AudioChannelSet::topRearCentre); | |||||
| if ((bitmap & kAudioChannelBit_TopBackRight) != 0) set.addChannel (AudioChannelSet::topRearRight); | |||||
| return set; | |||||
| } | |||||
| static AudioChannelSet CoreAudioChannelLayoutToJuceType (const AudioChannelLayout& layout) noexcept | |||||
| { | |||||
| const AudioChannelLayoutTag tag = layout.mChannelLayoutTag; | |||||
| if (tag == kAudioChannelLayoutTag_UseChannelBitmap) return CoreAudioChannelBitmapToJuceType (layout.mChannelBitmap); | |||||
| if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) | |||||
| { | |||||
| if (layout.mNumberChannelDescriptions <= 8) | |||||
| { | |||||
| // first try to convert the layout via the auChannelStreamOrder array | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; auChannelStreamOrder[layoutIndex].auLayoutTag != 0; ++layoutIndex) | |||||
| { | |||||
| const AUChannelStreamOrder& streamOrder = auChannelStreamOrder[layoutIndex]; | |||||
| int numChannels; | |||||
| for (numChannels = 0; numChannels < 8 && streamOrder.speakerOrder[numChannels] != 0;) | |||||
| ++numChannels; | |||||
| if (numChannels != (int) layout.mNumberChannelDescriptions) | |||||
| continue; | |||||
| int ch; | |||||
| for (ch = 0; ch < numChannels; ++ch) | |||||
| if (streamOrder.speakerOrder[ch] != layout.mChannelDescriptions[ch].mChannelLabel) | |||||
| break; | |||||
| // match! | |||||
| if (ch == numChannels) | |||||
| break; | |||||
| } | |||||
| if (auChannelStreamOrder[layoutIndex].auLayoutTag != 0) | |||||
| return CALayoutTagToChannelSet (auChannelStreamOrder[layoutIndex].auLayoutTag); | |||||
| } | |||||
| AudioChannelSet set; | |||||
| for (unsigned int i = 0; i < layout.mNumberChannelDescriptions; ++i) | |||||
| set.addChannel (CoreAudioChannelLabelToJuceType (layout.mChannelDescriptions[i].mChannelLabel)); | |||||
| return set; | |||||
| } | |||||
| return CALayoutTagToChannelSet (tag); | |||||
| } | |||||
| static AudioChannelSet CALayoutTagToChannelSet (AudioChannelLayoutTag tag) noexcept | |||||
| { | |||||
| switch (tag) | |||||
| { | |||||
| case kAudioChannelLayoutTag_Unknown: return AudioChannelSet::disabled(); | |||||
| case kAudioChannelLayoutTag_Mono: return AudioChannelSet::mono(); | |||||
| case kAudioChannelLayoutTag_Stereo: | |||||
| case kAudioChannelLayoutTag_StereoHeadphones: | |||||
| case kAudioChannelLayoutTag_Binaural: return AudioChannelSet::stereo(); | |||||
| case kAudioChannelLayoutTag_Quadraphonic: return AudioChannelSet::quadraphonic(); | |||||
| case kAudioChannelLayoutTag_Pentagonal: return AudioChannelSet::pentagonal(); | |||||
| case kAudioChannelLayoutTag_Hexagonal: return AudioChannelSet::hexagonal(); | |||||
| case kAudioChannelLayoutTag_Octagonal: return AudioChannelSet::octagonal(); | |||||
| case kAudioChannelLayoutTag_Ambisonic_B_Format: return AudioChannelSet::ambisonic(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_6_0: return AudioChannelSet::create6point0(); | |||||
| case kAudioChannelLayoutTag_DTS_6_0_A: return AudioChannelSet::create6point0Music(); | |||||
| case kAudioChannelLayoutTag_MPEG_6_1_A: return AudioChannelSet::create6point1(); | |||||
| case kAudioChannelLayoutTag_MPEG_5_0_B: return AudioChannelSet::create5point0(); | |||||
| case kAudioChannelLayoutTag_MPEG_5_1_A: return AudioChannelSet::create5point1(); | |||||
| case kAudioChannelLayoutTag_DTS_7_1: | |||||
| case kAudioChannelLayoutTag_MPEG_7_1_C: return AudioChannelSet::create7point1(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_7_0: return AudioChannelSet::create7point0(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_7_0_Front: return AudioChannelSet::createFront7point0(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_7_1_Front: return AudioChannelSet::createFront7point1(); | |||||
| case kAudioChannelLayoutTag_MPEG_3_0_A: | |||||
| case kAudioChannelLayoutTag_MPEG_3_0_B: return AudioChannelSet::createLCR(); | |||||
| case kAudioChannelLayoutTag_MPEG_4_0_A: | |||||
| case kAudioChannelLayoutTag_MPEG_4_0_B: return AudioChannelSet::createLCRS(); | |||||
| case kAudioChannelLayoutTag_ITU_2_1: return AudioChannelSet::createLRS(); | |||||
| case kAudioChannelLayoutTag_EAC3_7_1_C: return AudioChannelSet::create7point1AC3(); | |||||
| } | |||||
| if (int numChannels = static_cast<int> (tag) & 0xffff) | |||||
| return AudioChannelSet::discreteChannels (numChannels); | |||||
| // Bitmap and channel description array layout tags are currently unsupported :-( | |||||
| jassertfalse; | |||||
| return AudioChannelSet(); | |||||
| } | |||||
| static AudioChannelLayoutTag ChannelSetToCALayoutTag (const AudioChannelSet& set) noexcept | |||||
| { | |||||
| if (set == AudioChannelSet::mono()) return kAudioChannelLayoutTag_Mono; | |||||
| if (set == AudioChannelSet::stereo()) return kAudioChannelLayoutTag_Stereo; | |||||
| if (set == AudioChannelSet::createLCR()) return kAudioChannelLayoutTag_MPEG_3_0_A; | |||||
| if (set == AudioChannelSet::createLRS()) return kAudioChannelLayoutTag_ITU_2_1; | |||||
| if (set == AudioChannelSet::createLCRS()) return kAudioChannelLayoutTag_MPEG_4_0_A; | |||||
| if (set == AudioChannelSet::quadraphonic()) return kAudioChannelLayoutTag_Quadraphonic; | |||||
| if (set == AudioChannelSet::pentagonal()) return kAudioChannelLayoutTag_Pentagonal; | |||||
| if (set == AudioChannelSet::hexagonal()) return kAudioChannelLayoutTag_Hexagonal; | |||||
| if (set == AudioChannelSet::octagonal()) return kAudioChannelLayoutTag_Octagonal; | |||||
| if (set == AudioChannelSet::ambisonic()) return kAudioChannelLayoutTag_Ambisonic_B_Format; | |||||
| if (set == AudioChannelSet::create5point0()) return kAudioChannelLayoutTag_MPEG_5_0_B; | |||||
| if (set == AudioChannelSet::create5point1()) return kAudioChannelLayoutTag_MPEG_5_1_A; | |||||
| if (set == AudioChannelSet::create6point0()) return kAudioChannelLayoutTag_AudioUnit_6_0; | |||||
| if (set == AudioChannelSet::create6point0Music()) return kAudioChannelLayoutTag_DTS_6_0_A; | |||||
| if (set == AudioChannelSet::create6point1()) return kAudioChannelLayoutTag_MPEG_6_1_A; | |||||
| if (set == AudioChannelSet::create7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0; | |||||
| if (set == AudioChannelSet::create7point1()) return kAudioChannelLayoutTag_MPEG_7_1_C; | |||||
| if (set == AudioChannelSet::createFront7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0_Front; | |||||
| if (set == AudioChannelSet::createFront7point1()) return kAudioChannelLayoutTag_AudioUnit_7_1_Front; | |||||
| if (set == AudioChannelSet::create7point1AC3()) return kAudioChannelLayoutTag_EAC3_7_1_C; | |||||
| if (set == AudioChannelSet::disabled()) return kAudioChannelLayoutTag_Unknown; | |||||
| return static_cast<AudioChannelLayoutTag> ((int) kAudioChannelLayoutTag_DiscreteInOrder | set.size()); | |||||
| } | |||||
| static int auChannelIndexToJuce (int auIndex, const AudioChannelSet& channelSet) | |||||
| { | |||||
| if (auIndex >= 8) return auIndex; | |||||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
| if (auChannelStreamOrder[layoutIndex].auLayoutTag == 0) return auIndex; | |||||
| AudioChannelSet::ChannelType channelType | |||||
| = CoreAudioChannelLabelToJuceType (auChannelStreamOrder[layoutIndex].speakerOrder[auIndex]); | |||||
| // We need to map surround channels to rear surround channels for petagonal and hexagonal | |||||
| if (channelSet == AudioChannelSet::pentagonal() || channelSet == AudioChannelSet::hexagonal()) | |||||
| { | |||||
| switch (channelType) | |||||
| { | |||||
| case AudioChannelSet::leftSurround: | |||||
| channelType = AudioChannelSet::leftRearSurround; | |||||
| break; | |||||
| case AudioChannelSet::rightSurround: | |||||
| channelType = AudioChannelSet::rightRearSurround; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| const int juceIndex = channelSet.getChannelTypes().indexOf (channelType); | |||||
| jassert (juceIndex >= 0); | |||||
| return juceIndex >= 0 ? juceIndex : auIndex; | |||||
| } | |||||
| static int juceChannelIndexToAu (int juceIndex, const AudioChannelSet& channelSet) | |||||
| { | |||||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
| { | |||||
| if (auChannelStreamOrder[layoutIndex].auLayoutTag == 0) | |||||
| { | |||||
| jassertfalse; | |||||
| return juceIndex; | |||||
| } | |||||
| } | |||||
| const AUChannelStreamOrder& channelOrder = auChannelStreamOrder[layoutIndex]; | |||||
| AudioChannelSet::ChannelType channelType = channelSet.getTypeOfChannel (juceIndex); | |||||
| // We need to map rear surround channels to surround channels for petagonal and hexagonal | |||||
| if (channelSet == AudioChannelSet::pentagonal() || channelSet == AudioChannelSet::hexagonal()) | |||||
| { | |||||
| switch (channelType) | |||||
| { | |||||
| case AudioChannelSet::leftRearSurround: | |||||
| channelType = AudioChannelSet::leftSurround; | |||||
| break; | |||||
| case AudioChannelSet::rightRearSurround: | |||||
| channelType = AudioChannelSet::rightSurround; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (int i = 0; i < 8 && channelOrder.speakerOrder[i] != 0; ++i) | |||||
| if (CoreAudioChannelLabelToJuceType (channelOrder.speakerOrder[i]) == channelType) | |||||
| return i; | |||||
| jassertfalse; | |||||
| return juceIndex; | |||||
| } | |||||
| class ChannelRemapper | |||||
| { | |||||
| public: | |||||
| ChannelRemapper (PluginBusUtilities& bUtils) : busUtils (bUtils), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} | |||||
| ~ChannelRemapper () {} | |||||
| void alloc() | |||||
| { | |||||
| const int numInputBuses = busUtils.getBusCount (true); | |||||
| const int numOutputBuses = busUtils.getBusCount (false); | |||||
| initializeChannelMapArray (true, numInputBuses); | |||||
| initializeChannelMapArray (false, numOutputBuses); | |||||
| for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) | |||||
| fillLayoutChannelMaps (true, busIdx); | |||||
| for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) | |||||
| fillLayoutChannelMaps (false, busIdx); | |||||
| } | |||||
| void release() | |||||
| { | |||||
| inputLayoutMap = outputLayoutMap = nullptr; | |||||
| inputLayoutMapPtrStorage.free(); | |||||
| outputLayoutMapPtrStorage.free(); | |||||
| inputLayoutMapStorage.free(); | |||||
| outputLayoutMapStorage.free(); | |||||
| } | |||||
| inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } | |||||
| private: | |||||
| //============================================================================== | |||||
| PluginBusUtilities& busUtils; | |||||
| HeapBlock<int*> inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; | |||||
| HeapBlock<int> inputLayoutMapStorage, outputLayoutMapStorage; | |||||
| int** inputLayoutMap; | |||||
| int** outputLayoutMap; | |||||
| //============================================================================== | |||||
| void initializeChannelMapArray (bool isInput, const int numBuses) | |||||
| { | |||||
| HeapBlock<int*>& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; | |||||
| HeapBlock<int>& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; | |||||
| int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; | |||||
| const int totalInChannels = busUtils.findTotalNumChannels (true); | |||||
| const int totalOutChannels = busUtils.findTotalNumChannels (false); | |||||
| layoutMapPtrStorage.calloc (static_cast<size_t> (numBuses)); | |||||
| layoutMapStorage.calloc (static_cast<size_t> (isInput ? totalInChannels : totalOutChannels)); | |||||
| layoutMap = layoutMapPtrStorage. getData(); | |||||
| int ch = 0; | |||||
| for (int busIdx = 0; busIdx < numBuses; ++busIdx) | |||||
| { | |||||
| layoutMap[busIdx] = layoutMapStorage.getData() + ch; | |||||
| ch += busUtils.getNumChannels (isInput, busIdx); | |||||
| } | |||||
| } | |||||
| void fillLayoutChannelMaps (bool isInput, int busNr) | |||||
| { | |||||
| int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; | |||||
| const AudioChannelSet& channelFormat = busUtils.getChannelSet (isInput, busNr); | |||||
| const int numChannels = channelFormat.size(); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| layoutMap[i] = AudioUnitHelpers::juceChannelIndexToAu (i, channelFormat); | |||||
| } | |||||
| }; | |||||
| //============================================================================== | |||||
| class CoreAudioBufferList | |||||
| { | |||||
| public: | |||||
| CoreAudioBufferList () { reset(); } | |||||
| //============================================================================== | |||||
| void prepare (int inChannels, int outChannels, int maxFrames) | |||||
| { | |||||
| const int numChannels = jmax (inChannels, outChannels); | |||||
| scratch.setSize (numChannels, maxFrames); | |||||
| channels.calloc (static_cast<size_t> (numChannels)); | |||||
| reset(); | |||||
| } | |||||
| void release() | |||||
| { | |||||
| scratch.setSize (0, 0); | |||||
| channels.free(); | |||||
| } | |||||
| void reset() noexcept | |||||
| { | |||||
| pushIdx = 0; | |||||
| popIdx = 0; | |||||
| zeromem (channels.getData(), sizeof(float*) * static_cast<size_t> (scratch.getNumChannels())); | |||||
| } | |||||
| //============================================================================== | |||||
| float* setBuffer (const int idx, float* ptr = nullptr) noexcept | |||||
| { | |||||
| jassert (idx < scratch.getNumChannels()); | |||||
| return (channels [idx] = uniqueBuffer (idx, ptr)); | |||||
| } | |||||
| //============================================================================== | |||||
| float* push () noexcept | |||||
| { | |||||
| jassert (pushIdx < scratch.getNumChannels()); | |||||
| return channels [pushIdx++]; | |||||
| } | |||||
| void push (AudioBufferList& bufferList, const int* channelMap) noexcept | |||||
| { | |||||
| jassert (pushIdx < scratch.getNumChannels()); | |||||
| if (bufferList.mNumberBuffers > 0) | |||||
| { | |||||
| const UInt32 n = bufferList.mBuffers [0].mDataByteSize / | |||||
| (bufferList.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
| const bool isInterleaved = isAudioBufferInterleaved (bufferList); | |||||
| const int numChannels = static_cast<int> (isInterleaved ? bufferList.mBuffers [0].mNumberChannels | |||||
| : bufferList.mNumberBuffers); | |||||
| for (int ch = 0; ch < numChannels; ++ch) | |||||
| { | |||||
| float* data = push(); | |||||
| int mappedChannel = channelMap [ch]; | |||||
| if (isInterleaved || static_cast<float*> (bufferList.mBuffers [mappedChannel].mData) != data) | |||||
| copyAudioBuffer (bufferList, mappedChannel, n, data); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| float* pop () noexcept | |||||
| { | |||||
| jassert (popIdx < scratch.getNumChannels()); | |||||
| return channels[popIdx++]; | |||||
| } | |||||
| void pop (AudioBufferList& buffer, const int* channelMap) noexcept | |||||
| { | |||||
| if (buffer.mNumberBuffers > 0) | |||||
| { | |||||
| const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
| const bool isInterleaved = isAudioBufferInterleaved (buffer); | |||||
| const int numChannels = static_cast<int> (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); | |||||
| for (int ch = 0; ch < numChannels; ++ch) | |||||
| { | |||||
| int mappedChannel = channelMap [ch]; | |||||
| float* nextBuffer = pop(); | |||||
| if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) | |||||
| continue; // no copying necessary | |||||
| if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) | |||||
| buffer.mBuffers [mappedChannel].mData = nextBuffer; | |||||
| else | |||||
| copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| AudioSampleBuffer& getBuffer (UInt32 frames) noexcept | |||||
| { | |||||
| jassert (pushIdx == scratch.getNumChannels()); | |||||
| #if JUCE_DEBUG | |||||
| for (int i = 0; i < pushIdx; ++i) | |||||
| jassert (channels [i] != nullptr); | |||||
| #endif | |||||
| mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast<int> (frames)); | |||||
| return mutableBuffer; | |||||
| } | |||||
| private: | |||||
| float* uniqueBuffer (int idx, float* buffer) noexcept | |||||
| { | |||||
| if (buffer == nullptr) | |||||
| return scratch.getWritePointer (idx); | |||||
| for (int ch = 0; ch < idx; ++ch) | |||||
| if (buffer == channels[ch]) | |||||
| return scratch.getWritePointer (idx); | |||||
| return buffer; | |||||
| } | |||||
| //============================================================================== | |||||
| AudioSampleBuffer scratch; | |||||
| AudioSampleBuffer mutableBuffer; | |||||
| HeapBlock<float*> channels; | |||||
| int pushIdx, popIdx; | |||||
| }; | |||||
| static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); | |||||
| } | |||||
| static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) | |||||
| zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); | |||||
| } | |||||
| static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept | |||||
| { | |||||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||||
| { | |||||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
| memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
| const float* src = static_cast<const float*> (audioBuffer.mBuffers[0].mData); | |||||
| jassert (channel < numChannels); | |||||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
| for (const float* inData = src; inData < (src + n); inData += numChannels) | |||||
| *dst++ = inData[channel]; | |||||
| } | |||||
| } | |||||
| static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||||
| { | |||||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
| memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
| float* dst = static_cast<float*> (audioBuffer.mBuffers[0].mData); | |||||
| jassert (channel < numChannels); | |||||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
| for (float* outData = dst; outData < (dst + n); outData += numChannels) | |||||
| outData[channel] = *src++; | |||||
| } | |||||
| } | |||||
| static Array<AUChannelInfo> getAUChannelInfo (PluginBusUtilities& busUtils) | |||||
| { | |||||
| Array<AUChannelInfo> channelInfo; | |||||
| AudioProcessor* juceFilter = &busUtils.processor; | |||||
| const AudioProcessor::AudioBusArrangement& arr = juceFilter->busArrangement; | |||||
| PluginBusUtilities::ScopedBusRestorer restorer (busUtils); | |||||
| const bool hasMainInputBus = (busUtils.getNumEnabledBuses (true) > 0); | |||||
| const bool hasMainOutputBus = (busUtils.getNumEnabledBuses (false) > 0); | |||||
| if ((! hasMainInputBus) && (! hasMainOutputBus)) | |||||
| { | |||||
| // midi effect plug-in: no audio | |||||
| AUChannelInfo info; | |||||
| info.inChannels = 0; | |||||
| info.outChannels = 0; | |||||
| channelInfo.add (info); | |||||
| return channelInfo; | |||||
| } | |||||
| else | |||||
| { | |||||
| const uint32_t maxNumChanToCheckFor = 9; | |||||
| uint32_t defaultInputs = static_cast<uint32_t> (busUtils.getNumChannels (true, 0)); | |||||
| uint32_t defaultOutputs = static_cast<uint32_t> (busUtils.getNumChannels (false, 0)); | |||||
| uint32_t lastInputs = defaultInputs; | |||||
| uint32_t lastOutputs = defaultOutputs; | |||||
| SortedSet<uint32_t> supportedChannels; | |||||
| // add the current configuration | |||||
| if (lastInputs != 0 || lastOutputs != 0) | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
| { | |||||
| const AudioChannelSet dfltInLayout = busUtils.getDefaultLayoutForChannelNumAndBus(true, 0, static_cast<int> (inChanNum)); | |||||
| if (inChanNum != 0 && dfltInLayout.isDisabled()) | |||||
| continue; | |||||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
| { | |||||
| const AudioChannelSet dfltOutLayout = busUtils.getDefaultLayoutForChannelNumAndBus(false, 0, static_cast<int> (outChanNum)); | |||||
| if (outChanNum != 0 && dfltOutLayout.isDisabled()) | |||||
| continue; | |||||
| // get the number of channels again. This is only needed for some processors that change their configuration | |||||
| // even when they indicate that setPreferredBusArrangement failed. | |||||
| lastInputs = hasMainInputBus ? static_cast<uint32_t> (arr.inputBuses. getReference (0). channels.size()) : 0; | |||||
| lastOutputs = hasMainOutputBus ? static_cast<uint32_t> (arr.outputBuses.getReference (0). channels.size()) : 0; | |||||
| uint32_t channelConfiguration = (inChanNum << 16) | outChanNum; | |||||
| // did we already try this configuration? | |||||
| if (supportedChannels.contains (channelConfiguration)) continue; | |||||
| if (lastInputs != inChanNum && (! dfltInLayout.isDisabled())) | |||||
| { | |||||
| if (! juceFilter->setPreferredBusArrangement (true, 0, dfltInLayout)) continue; | |||||
| lastInputs = inChanNum; | |||||
| lastOutputs = hasMainOutputBus ? static_cast<uint32_t> (arr.outputBuses.getReference (0). channels.size()) : 0; | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| } | |||||
| if (lastOutputs != outChanNum && (! dfltOutLayout.isDisabled())) | |||||
| { | |||||
| if (! juceFilter->setPreferredBusArrangement (false, 0, dfltOutLayout)) continue; | |||||
| lastInputs = hasMainInputBus ? static_cast<uint32_t> (arr.inputBuses.getReference (0).channels.size()) : 0; | |||||
| lastOutputs = outChanNum; | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| } | |||||
| } | |||||
| } | |||||
| bool hasInOutMismatch = false; | |||||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||||
| { | |||||
| const uint32_t numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
| const uint32_t numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
| if (numInputs != numOutputs) | |||||
| { | |||||
| hasInOutMismatch = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| bool hasUnsupportedInput = ! hasMainOutputBus, hasUnsupportedOutput = ! hasMainInputBus; | |||||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
| { | |||||
| uint32_t channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); | |||||
| if (! supportedChannels.contains (channelConfiguration)) | |||||
| { | |||||
| hasUnsupportedInput = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
| { | |||||
| uint32_t channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; | |||||
| if (! supportedChannels.contains (channelConfiguration)) | |||||
| { | |||||
| hasUnsupportedOutput = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||||
| { | |||||
| const int numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
| const int numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
| AUChannelInfo info; | |||||
| // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | |||||
| info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||||
| info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||||
| if (info.inChannels == -2 && info.outChannels == -2) | |||||
| info.inChannels = -1; | |||||
| int j; | |||||
| for (j = 0; j < channelInfo.size(); ++j) | |||||
| if (channelInfo[j].inChannels == info.inChannels && channelInfo[j].outChannels == info.outChannels) | |||||
| break; | |||||
| if (j >= channelInfo.size()) | |||||
| channelInfo.add (info); | |||||
| } | |||||
| } | |||||
| return channelInfo; | |||||
| } | |||||
| }; | |||||
| AudioUnitHelpers::AUChannelStreamOrder AudioUnitHelpers::auChannelStreamOrder[] = | |||||
| { | |||||
| {kAudioChannelLayoutTag_Mono, {kAudioChannelLabel_Center, 0, 0, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Stereo, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, 0, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_StereoHeadphones, {kAudioChannelLabel_HeadphonesLeft, kAudioChannelLabel_HeadphonesRight, 0, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Binaural, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, 0, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Quadraphonic, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Pentagonal, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Hexagonal, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, kAudioChannelLabel_CenterSurround, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_Octagonal, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, kAudioChannelLabel_CenterSurround, kAudioChannelLabel_LeftWide, kAudioChannelLabel_RightWide}}, | |||||
| {kAudioChannelLayoutTag_Ambisonic_B_Format, {kAudioChannelLabel_Ambisonic_W, kAudioChannelLabel_Ambisonic_X, kAudioChannelLabel_Ambisonic_Y, kAudioChannelLabel_Ambisonic_Z, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_5_0_B, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_5_1_A, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_6_0, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, kAudioChannelLabel_CenterSurround, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_DTS_6_0_A, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_RearSurroundLeft, kAudioChannelLabel_RearSurroundRight, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_6_1_A, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_CenterSurround, 0}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_7_0, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, kAudioChannelLabel_RearSurroundLeft, kAudioChannelLabel_RearSurroundRight, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_7_1_C, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_RearSurroundLeft, kAudioChannelLabel_RearSurroundRight}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_7_0_Front,{kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_Center, kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter, 0}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_7_1_Front,{kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_LeftCenter, kAudioChannelLabel_RightCenter}}, | |||||
| {kAudioChannelLayoutTag_MPEG_3_0_A, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_3_0_B, {kAudioChannelLabel_Center, kAudioChannelLabel_Left, kAudioChannelLabel_Right, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_4_0_A, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_CenterSurround, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_MPEG_4_0_B, {kAudioChannelLabel_Center, kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_CenterSurround, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_ITU_2_1, {kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_CenterSurround, 0, 0, 0, 0, 0}}, | |||||
| {kAudioChannelLayoutTag_EAC3_7_1_C, {kAudioChannelLabel_Left, kAudioChannelLabel_Center, kAudioChannelLabel_Right, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurroundDirect, kAudioChannelLabel_RightSurroundDirect}}, | |||||
| {0, {0,0,0,0,0,0,0,0}} | |||||
| }; | |||||
| @@ -1,27 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #include "utility/juce_PluginUtilities.cpp" | |||||
| @@ -78,9 +78,8 @@ namespace MacFileHelpers | |||||
| NSNumber* hidden = nil; | NSNumber* hidden = nil; | ||||
| NSError* err = nil; | NSError* err = nil; | ||||
| return [[NSURL fileURLWithPath: juceStringToNS (path)] | |||||
| getResourceValue: &hidden forKey: NSURLIsHiddenKey error: &err] | |||||
| && [hidden boolValue]; | |||||
| return [createNSURLFromFile (path) getResourceValue: &hidden forKey: NSURLIsHiddenKey error: &err] | |||||
| && [hidden boolValue]; | |||||
| } | } | ||||
| #elif JUCE_IOS | #elif JUCE_IOS | ||||
| return File (path).getFileName().startsWithChar ('.'); | return File (path).getFileName().startsWithChar ('.'); | ||||
| @@ -298,7 +297,7 @@ bool File::moveToTrash() const | |||||
| #else | #else | ||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| { | { | ||||
| NSURL* url = [NSURL fileURLWithPath: juceStringToNS (getFullPathName())]; | |||||
| NSURL* url = createNSURLFromFile (*this); | |||||
| [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject: url] | [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject: url] | ||||
| completionHandler: nil]; | completionHandler: nil]; | ||||
| @@ -503,7 +503,7 @@ struct BackgroundDownloadTask : public URL::DownloadTask | |||||
| { | { | ||||
| NSFileManager* fileManager = [[NSFileManager alloc] init]; | NSFileManager* fileManager = [[NSFileManager alloc] init]; | ||||
| error = ([fileManager moveItemAtURL: location | error = ([fileManager moveItemAtURL: location | ||||
| toURL: [NSURL fileURLWithPath:juceStringToNS (targetLocation.getFullPathName())] | |||||
| toURL: createNSURLFromFile (targetLocation) | |||||
| error: nil] == NO); | error: nil] == NO); | ||||
| httpCode = 200; | httpCode = 200; | ||||
| finished = true; | finished = true; | ||||
| @@ -49,6 +49,16 @@ namespace | |||||
| return [NSString string]; | return [NSString string]; | ||||
| } | } | ||||
| static inline NSURL* createNSURLFromFile (const String& f) | |||||
| { | |||||
| return [NSURL fileURLWithPath: juceStringToNS (f)]; | |||||
| } | |||||
| static inline NSURL* createNSURLFromFile (const File& f) | |||||
| { | |||||
| return createNSURLFromFile (f.getFullPathName()); | |||||
| } | |||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| template <typename RectangleType> | template <typename RectangleType> | ||||
| static NSRect makeNSRect (const RectangleType& r) noexcept | static NSRect makeNSRect (const RectangleType& r) noexcept | ||||
| @@ -76,10 +76,6 @@ | |||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| #if JUCE_QUICKTIME && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
| #pragma comment (lib, "QTMLClient.lib") | |||||
| #endif | |||||
| #if JUCE_DIRECT2D && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | #if JUCE_DIRECT2D && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | ||||
| #pragma comment (lib, "Dwrite.lib") | #pragma comment (lib, "Dwrite.lib") | ||||
| #pragma comment (lib, "D2d1.lib") | #pragma comment (lib, "D2d1.lib") | ||||
| @@ -38,7 +38,7 @@ | |||||
| It also includes a callback that lets you know when the top-level peer is changed. | It also includes a callback that lets you know when the top-level peer is changed. | ||||
| This class is used by specialised components like WebBrowserComponent or QuickTimeComponent | |||||
| This class is used by specialised components like WebBrowserComponent | |||||
| because they need to keep their custom windows in the right place and respond to | because they need to keep their custom windows in the right place and respond to | ||||
| changes in the peer. | changes in the peer. | ||||
| */ | */ | ||||
| @@ -213,7 +213,7 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||||
| } | } | ||||
| #if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) | #if defined (MAC_OS_X_VERSION_10_6) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) | ||||
| [panel setDirectoryURL: [NSURL fileURLWithPath: juceStringToNS (directory)]]; | |||||
| [panel setDirectoryURL: createNSURLFromFile (directory)]; | |||||
| [panel setNameFieldStringValue: juceStringToNS (filename)]; | [panel setNameFieldStringValue: juceStringToNS (filename)]; | ||||
| if ([panel runModal] == 1 /*NSModalResponseOK*/) | if ([panel runModal] == 1 /*NSModalResponseOK*/) | ||||
| @@ -139,8 +139,7 @@ void RecentlyOpenedFilesList::registerRecentFileNatively (const File& file) | |||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| { | { | ||||
| [[NSDocumentController sharedDocumentController] | |||||
| noteNewRecentDocumentURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]]; | |||||
| [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: createNSURLFromFile (file)]; | |||||
| } | } | ||||
| #else | #else | ||||
| ignoreUnused (file); | ignoreUnused (file); | ||||
| @@ -24,9 +24,18 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| CameraDevice::CameraDevice (const String& nm, int index, int minWidth, int minHeight, int maxWidth, int maxHeight, | |||||
| bool highQuality) | |||||
| : name (nm), pimpl (new Pimpl (name, index, minWidth, minHeight, maxWidth, maxHeight, highQuality)) | |||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #include "../native/juce_mac_CameraDevice.h" | |||||
| #elif JUCE_WINDOWS | |||||
| #include "../native/juce_win32_CameraDevice.h" | |||||
| #elif JUCE_ANDROID | |||||
| #include "../native/juce_android_CameraDevice.h" | |||||
| #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)) | |||||
| { | { | ||||
| } | } | ||||
| @@ -81,14 +90,12 @@ StringArray CameraDevice::getAvailableDevices() | |||||
| CameraDevice* CameraDevice::openDevice (int index, | CameraDevice* CameraDevice::openDevice (int index, | ||||
| int minWidth, int minHeight, | int minWidth, int minHeight, | ||||
| int maxWidth, int maxHeight, | int maxWidth, int maxHeight, | ||||
| bool highQuality) | |||||
| bool useHighQuality) | |||||
| { | { | ||||
| ScopedPointer<CameraDevice> d (new CameraDevice (getAvailableDevices() [index], index, | |||||
| minWidth, minHeight, maxWidth, maxHeight, | |||||
| highQuality)); | |||||
| if (d->pimpl->openedOk()) | |||||
| return d.release(); | |||||
| if (ScopedPointer<CameraDevice> d = new CameraDevice (getAvailableDevices() [index], index, | |||||
| minWidth, minHeight, maxWidth, maxHeight, useHighQuality)) | |||||
| if (d->pimpl->openedOk()) | |||||
| return d.release(); | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| @@ -34,50 +34,23 @@ | |||||
| #endif | #endif | ||||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | ||||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | ||||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | ||||
| #include "juce_video.h" | #include "juce_video.h" | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| #import <AVFoundation/AVFoundation.h> | |||||
| #import <AVFoundation/AVFoundation.h> | |||||
| #import <AVKit/AVKit.h> | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
| #if JUCE_QUICKTIME | |||||
| /* If you've got an include error here, you probably need to install the QuickTime SDK and | |||||
| add its header directory to your include path. | |||||
| Alternatively, if you don't need any QuickTime services, just set the JUCE_QUICKTIME flag to 0. | |||||
| */ | |||||
| #include <Movies.h> | |||||
| #include <QTML.h> | |||||
| #include <QuickTimeComponents.h> | |||||
| #include <MediaHandlers.h> | |||||
| #include <ImageCodec.h> | |||||
| /* If you've got QuickTime 7 installed, then these COM objects should be found in | |||||
| the "\Program Files\Quicktime" directory. You'll need to add this directory to | |||||
| your include search path to make these import statements work. | |||||
| */ | |||||
| #import <QTOLibrary.dll> | |||||
| #import <QTOControl.dll> | |||||
| #if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
| #pragma comment (lib, "QTMLClient.lib") | |||||
| #endif | |||||
| #endif | |||||
| #if JUCE_USE_CAMERA || JUCE_DIRECTSHOW | |||||
| /* If you're using the camera classes, you'll need access to a few DirectShow headers. | |||||
| /* If you're using the camera classes, you'll need access to a few DirectShow headers. | |||||
| These files are provided in the normal Windows SDK. */ | These files are provided in the normal Windows SDK. */ | ||||
| #include <dshow.h> | |||||
| #include <dshowasf.h> | |||||
| #endif | |||||
| #if JUCE_DIRECTSHOW && JUCE_MEDIAFOUNDATION | |||||
| #include <evr.h> | |||||
| #endif | |||||
| #include <dshow.h> | |||||
| #include <dshowasf.h> | |||||
| #include <evr.h> | |||||
| #if JUCE_USE_CAMERA && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | #if JUCE_USE_CAMERA && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | ||||
| #pragma comment (lib, "Strmiids.lib") | #pragma comment (lib, "Strmiids.lib") | ||||
| @@ -88,7 +61,7 @@ | |||||
| #pragma comment (lib, "mfuuid.lib") | #pragma comment (lib, "mfuuid.lib") | ||||
| #endif | #endif | ||||
| #if JUCE_DIRECTSHOW && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
| #if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||||
| #pragma comment (lib, "strmiids.lib") | #pragma comment (lib, "strmiids.lib") | ||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| @@ -99,32 +72,7 @@ using namespace juce; | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #if JUCE_USE_CAMERA | |||||
| #include "native/juce_mac_CameraDevice.mm" | |||||
| #endif | |||||
| #if JUCE_MAC | |||||
| #include "native/juce_mac_MovieComponent.mm" | |||||
| #endif | |||||
| #elif JUCE_WINDOWS | |||||
| #if JUCE_USE_CAMERA | |||||
| #include "native/juce_win32_CameraDevice.cpp" | |||||
| #endif | |||||
| #if JUCE_DIRECTSHOW | |||||
| #include "native/juce_win32_DirectShowComponent.cpp" | |||||
| #endif | |||||
| #elif JUCE_LINUX | |||||
| #elif JUCE_ANDROID | |||||
| #if JUCE_USE_CAMERA | |||||
| #include "native/juce_android_CameraDevice.cpp" | |||||
| #endif | |||||
| #endif | |||||
| #include "playback/juce_VideoComponent.cpp" | |||||
| #if JUCE_USE_CAMERA | #if JUCE_USE_CAMERA | ||||
| #include "capture/juce_CameraDevice.cpp" | #include "capture/juce_CameraDevice.cpp" | ||||
| @@ -43,7 +43,7 @@ | |||||
| license: GPL/Commercial | license: GPL/Commercial | ||||
| dependencies: juce_data_structures juce_cryptography | dependencies: juce_data_structures juce_cryptography | ||||
| OSXFrameworks: AVFoundation CoreMedia | |||||
| OSXFrameworks: AVKit AVFoundation CoreMedia | |||||
| END_JUCE_MODULE_DECLARATION | END_JUCE_MODULE_DECLARATION | ||||
| @@ -56,59 +56,26 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #include <juce_gui_extra/juce_gui_extra.h> | #include <juce_gui_extra/juce_gui_extra.h> | ||||
| //============================================================================== | |||||
| /** Config: JUCE_DIRECTSHOW | |||||
| Enables DirectShow media-streaming architecture (MS Windows only). | |||||
| */ | |||||
| #ifndef JUCE_DIRECTSHOW | |||||
| #define JUCE_DIRECTSHOW 0 | |||||
| #endif | |||||
| /** Config: JUCE_MEDIAFOUNDATION | |||||
| Enables Media Foundation multimedia platform (Windows Vista and above). | |||||
| */ | |||||
| #ifndef JUCE_MEDIAFOUNDATION | |||||
| #define JUCE_MEDIAFOUNDATION 0 | |||||
| #endif | |||||
| #if ! JUCE_WINDOWS | |||||
| #undef JUCE_DIRECTSHOW | |||||
| #undef JUCE_MEDIAFOUNDATION | |||||
| #endif | |||||
| /** Config: JUCE_QUICKTIME | |||||
| Enables the QuickTimeMovieComponent class (Mac and Windows). | |||||
| If you're building on Windows, you'll need to have the Apple QuickTime SDK | |||||
| installed, and its header files will need to be on your include path. | |||||
| */ | |||||
| #if ! (defined (JUCE_QUICKTIME) || JUCE_LINUX || JUCE_IOS || JUCE_ANDROID || (JUCE_WINDOWS && ! JUCE_MSVC)) | |||||
| #define JUCE_QUICKTIME 0 | |||||
| #endif | |||||
| //============================================================================= | |||||
| #include "../juce_gui_extra/juce_gui_extra.h" | |||||
| //============================================================================= | |||||
| /** Config: JUCE_USE_CAMERA | /** Config: JUCE_USE_CAMERA | ||||
| Enables web-cam support using the CameraDevice class (Mac and Windows). | Enables web-cam support using the CameraDevice class (Mac and Windows). | ||||
| */ | */ | ||||
| #if (JUCE_QUICKTIME || JUCE_WINDOWS) && ! defined (JUCE_USE_CAMERA) | |||||
| #ifndef JUCE_USE_CAMERA | |||||
| #define JUCE_USE_CAMERA 0 | #define JUCE_USE_CAMERA 0 | ||||
| #endif | #endif | ||||
| #if ! (JUCE_MAC || JUCE_WINDOWS) | #if ! (JUCE_MAC || JUCE_WINDOWS) | ||||
| #undef JUCE_QUICKTIME | |||||
| #undef JUCE_USE_CAMERA | #undef JUCE_USE_CAMERA | ||||
| #endif | #endif | ||||
| //============================================================================== | |||||
| //============================================================================= | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| #if JUCE_DIRECTSHOW || DOXYGEN | |||||
| #include "playback/juce_DirectShowComponent.h" | |||||
| #endif | |||||
| #if JUCE_MAC || DOXYGEN | |||||
| #include "playback/juce_MovieComponent.h" | |||||
| #endif | |||||
| #include "playback/juce_VideoComponent.h" | |||||
| #include "capture/juce_CameraDevice.h" | #include "capture/juce_CameraDevice.h" | ||||
| } | } | ||||
| @@ -0,0 +1,167 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| //============================================================================== | |||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
| METHOD (play, "play", "()V") \ | |||||
| METHOD (stop, "stop", "()V") \ | |||||
| METHOD (close, "close", "()V") \ | |||||
| METHOD (isPlaying, "isPlaying", "()Z") \ | |||||
| METHOD (loadFile, "loadFile", "(Ljava/lang/String;)Z") \ | |||||
| METHOD (loadURL, "loadURL", "(Ljava/lang/String;)Z") \ | |||||
| METHOD (setCurrentPosition, "setCurrentPosition", "(D)V") \ | |||||
| METHOD (getCurrentPosition, "getCurrentPosition", "()D") \ | |||||
| METHOD (setSpeed, "setSpeed", "(D)V") \ | |||||
| METHOD (getDuration, "getDuration", "()D") \ | |||||
| METHOD (getVideoWidth, "getVideoWidth", "()I") \ | |||||
| METHOD (getVideoHeight, "getVideoHeight", "()I") \ | |||||
| METHOD (setVolume, "setVolume", "(F)V") \ | |||||
| METHOD (getVolume, "getVolume", "()F") \ | |||||
| DECLARE_JNI_CLASS (VideoView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$VideoView") | |||||
| #undef JNI_CLASS_MEMBERS | |||||
| struct VideoComponent::Pimpl : public Component | |||||
| { | |||||
| Pimpl() | |||||
| { | |||||
| } | |||||
| ~Pimpl() | |||||
| { | |||||
| close(); | |||||
| } | |||||
| Result load (const File& file) | |||||
| { | |||||
| if (isOpen() && videoView.callBooleanMethod (VideoView.loadFile, javaString (file.getFullPathName()).get())) | |||||
| { | |||||
| currentFile = file; | |||||
| return Result::ok(); | |||||
| } | |||||
| return Result::fail ("Couldn't open file"); | |||||
| } | |||||
| Result load (const URL& url) | |||||
| { | |||||
| if (isOpen() && videoView.callBooleanMethod (VideoView.loadFile, javaString (url.toString (true)).get())) | |||||
| { | |||||
| currentURL = url; | |||||
| return Result::ok(); | |||||
| } | |||||
| return Result::fail ("Couldn't open file"); | |||||
| } | |||||
| void close() | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.close); | |||||
| } | |||||
| bool isOpen() const | |||||
| { | |||||
| return videoView != nullptr; | |||||
| } | |||||
| bool isPlaying() const | |||||
| { | |||||
| return isOpen() && videoView.callBooleanMethod (VideoView.isPlaying); | |||||
| } | |||||
| void play() | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.play); | |||||
| } | |||||
| void stop() | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.stop); | |||||
| } | |||||
| void setPosition (double newPosition) | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.setCurrentPosition, (jdouble) newPosition); | |||||
| } | |||||
| double getPosition() const | |||||
| { | |||||
| if (isOpen()) | |||||
| return videoView.callDoubleMethod (VideoView.getCurrentPosition); | |||||
| return 0.0; | |||||
| } | |||||
| void setSpeed (double newSpeed) | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.setSpeed, (jdouble) newSpeed); | |||||
| } | |||||
| Rectangle<int> getNativeSize() const | |||||
| { | |||||
| if (isOpen()) | |||||
| { | |||||
| jint w = videoView.callIntMethod (VideoView.getVideoWidth); | |||||
| jint h = videoView.callIntMethod (VideoView.getVideoHeight); | |||||
| return Rectangle<int> (w, h); | |||||
| } | |||||
| return Rectangle<int>(); | |||||
| } | |||||
| double getDuration() const | |||||
| { | |||||
| if (isOpen()) | |||||
| return videoView.callDoubleMethod (VideoView.getDuration); | |||||
| return 0.0; | |||||
| } | |||||
| void setVolume (float newVolume) | |||||
| { | |||||
| if (isOpen()) | |||||
| videoView.callVoidMethod (VideoView.setVolume, (jfloat) newVolume); | |||||
| } | |||||
| float getVolume() const | |||||
| { | |||||
| if (isOpen()) | |||||
| return videoView.callFloatMethod (VideoView.getVolume); | |||||
| return 0.0f; | |||||
| } | |||||
| File currentFile; | |||||
| URL currentURL; | |||||
| GlobalRef videoView; | |||||
| }; | |||||
| @@ -0,0 +1,276 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 (const String&, int /*index*/, int /*minWidth*/, int /*minHeight*/, | |||||
| int /*maxWidth*/, int /*maxHeight*/, bool useHighQuality) | |||||
| { | |||||
| 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); | |||||
| } | |||||
| } | |||||
| ~Pimpl() | |||||
| { | |||||
| [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 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 void* data, size_t size) | |||||
| { | |||||
| 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(); | |||||
| } | |||||
| } | |||||
| void triggerImageCapture() | |||||
| { | |||||
| refreshIfNeeded(); | |||||
| if (auto* videoConnection = getVideoConnection()) | |||||
| { | |||||
| [imageOutput captureStillImageAsynchronouslyFromConnection: videoConnection | |||||
| completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError*) | |||||
| { | |||||
| 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); | |||||
| }]; | |||||
| } | |||||
| } | |||||
| 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; | |||||
| results.add ("default"); | |||||
| return results; | |||||
| } | |||||
| AVCaptureView* captureView = nil; | |||||
| AVCaptureSession* session = nil; | |||||
| AVCaptureMovieFileOutput* fileOutput = nil; | |||||
| AVCaptureStillImageOutput* imageOutput = nil; | |||||
| id<AVCaptureFileOutputRecordingDelegate> callbackDelegate = nil; | |||||
| String openingError; | |||||
| Time firstPresentationTime; | |||||
| bool isRecording = false; | |||||
| Array<CameraDevice::Listener*> listeners; | |||||
| CriticalSection listenerLock; | |||||
| 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@:@@@@"); | |||||
| 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*) {} | |||||
| }; | |||||
| 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"; | |||||
| } | |||||
| @@ -1,353 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #if ! JUCE_QUICKTIME | |||||
| #error "To support cameras in OSX you'll need to enable the JUCE_QUICKTIME flag" | |||||
| #endif | |||||
| extern Image juce_createImageFromCIImage (CIImage*, int w, int h); | |||||
| struct CameraDevice::Pimpl | |||||
| { | |||||
| Pimpl (const String&, const int index, int /*minWidth*/, int /*minHeight*/, int /*maxWidth*/, int /*maxHeight*/, | |||||
| bool useHighQuality) | |||||
| : input (nil), | |||||
| audioDevice (nil), | |||||
| audioInput (nil), | |||||
| session (nil), | |||||
| fileOutput (nil), | |||||
| imageOutput (nil), | |||||
| firstPresentationTime (0), | |||||
| averageTimeOffset (0), | |||||
| isRecording (false) | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| session = [[QTCaptureSession alloc] init]; | |||||
| NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; | |||||
| device = (QTCaptureDevice*) [devs objectAtIndex: index]; | |||||
| static DelegateClass cls; | |||||
| callbackDelegate = [cls.createInstance() init]; | |||||
| DelegateClass::setOwner (callbackDelegate, this); | |||||
| NSError* err = nil; | |||||
| [device retain]; | |||||
| [device open: &err]; | |||||
| if (err == nil) | |||||
| { | |||||
| input = [[QTCaptureDeviceInput alloc] initWithDevice: device]; | |||||
| audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: device]; | |||||
| [session addInput: input error: &err]; | |||||
| if (err == nil) | |||||
| { | |||||
| resetFile(); | |||||
| imageOutput = useHighQuality ? [[QTCaptureDecompressedVideoOutput alloc] init] : | |||||
| [[QTCaptureVideoPreviewOutput alloc] init]; | |||||
| [imageOutput setDelegate: callbackDelegate]; | |||||
| if (err == nil) | |||||
| { | |||||
| [session startRunning]; | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| openingError = nsStringToJuce ([err description]); | |||||
| DBG (openingError); | |||||
| } | |||||
| } | |||||
| ~Pimpl() | |||||
| { | |||||
| [session stopRunning]; | |||||
| [session removeOutput: imageOutput]; | |||||
| [session release]; | |||||
| [input release]; | |||||
| [device release]; | |||||
| [audioDevice release]; | |||||
| [audioInput release]; | |||||
| [fileOutput release]; | |||||
| [imageOutput release]; | |||||
| [callbackDelegate release]; | |||||
| } | |||||
| bool openedOk() const noexcept { return openingError.isEmpty(); } | |||||
| void resetFile() | |||||
| { | |||||
| [fileOutput recordToOutputFileURL: nil]; | |||||
| [session removeOutput: fileOutput]; | |||||
| [fileOutput release]; | |||||
| fileOutput = [[QTCaptureMovieFileOutput alloc] init]; | |||||
| [session removeInput: audioInput]; | |||||
| [audioInput release]; | |||||
| audioInput = nil; | |||||
| [audioDevice release]; | |||||
| audioDevice = nil; | |||||
| [fileOutput setDelegate: callbackDelegate]; | |||||
| } | |||||
| void addDefaultAudioInput() | |||||
| { | |||||
| NSError* err = nil; | |||||
| audioDevice = [QTCaptureDevice defaultInputDeviceWithMediaType: QTMediaTypeSound]; | |||||
| if ([audioDevice open: &err]) | |||||
| [audioDevice retain]; | |||||
| else | |||||
| audioDevice = nil; | |||||
| if (audioDevice != nil) | |||||
| { | |||||
| audioInput = [[QTCaptureDeviceInput alloc] initWithDevice: audioDevice]; | |||||
| [session addInput: audioInput error: &err]; | |||||
| } | |||||
| } | |||||
| void startRecordingToFile (const File& file, int quality) | |||||
| { | |||||
| stopRecording(); | |||||
| firstPresentationTime = 0; | |||||
| file.deleteFile(); | |||||
| // In some versions of QT (e.g. on 10.5), if you record video without audio, the speed comes | |||||
| // out wrong, so we'll put some audio in there too.., | |||||
| addDefaultAudioInput(); | |||||
| [session addOutput: fileOutput error: nil]; | |||||
| NSEnumerator* connectionEnumerator = [[fileOutput connections] objectEnumerator]; | |||||
| for (;;) | |||||
| { | |||||
| QTCaptureConnection* connection = [connectionEnumerator nextObject]; | |||||
| if (connection == nil) | |||||
| break; | |||||
| QTCompressionOptions* options = nil; | |||||
| NSString* mediaType = [connection mediaType]; | |||||
| if ([mediaType isEqualToString: QTMediaTypeVideo]) | |||||
| options = [QTCompressionOptions compressionOptionsWithIdentifier: | |||||
| quality >= 1 ? nsStringLiteral ("QTCompressionOptionsSD480SizeH264Video") | |||||
| : nsStringLiteral ("QTCompressionOptions240SizeH264Video")]; | |||||
| else if ([mediaType isEqualToString: QTMediaTypeSound]) | |||||
| options = [QTCompressionOptions compressionOptionsWithIdentifier: nsStringLiteral ("QTCompressionOptionsHighQualityAACAudio")]; | |||||
| [fileOutput setCompressionOptions: options forConnection: connection]; | |||||
| } | |||||
| [fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]]; | |||||
| isRecording = true; | |||||
| } | |||||
| void stopRecording() | |||||
| { | |||||
| if (isRecording) | |||||
| { | |||||
| resetFile(); | |||||
| isRecording = false; | |||||
| } | |||||
| } | |||||
| Time getTimeOfFirstRecordedFrame() const | |||||
| { | |||||
| return firstPresentationTime != 0 ? Time (firstPresentationTime + averageTimeOffset) | |||||
| : Time(); | |||||
| } | |||||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||||
| { | |||||
| const ScopedLock sl (listenerLock); | |||||
| if (listeners.size() == 0) | |||||
| [session addOutput: imageOutput error: nil]; | |||||
| listeners.addIfNotAlreadyThere (listenerToAdd); | |||||
| } | |||||
| void removeListener (CameraDevice::Listener* listenerToRemove) | |||||
| { | |||||
| const ScopedLock sl (listenerLock); | |||||
| listeners.removeFirstMatchingValue (listenerToRemove); | |||||
| if (listeners.size() == 0) | |||||
| [session removeOutput: imageOutput]; | |||||
| } | |||||
| void callListeners (CIImage* frame, int w, int h) | |||||
| { | |||||
| Image image (juce_createImageFromCIImage (frame, w, h)); | |||||
| const ScopedLock sl (listenerLock); | |||||
| for (int i = listeners.size(); --i >= 0;) | |||||
| { | |||||
| CameraDevice::Listener* const l = listeners[i]; | |||||
| if (l != nullptr) | |||||
| l->imageReceived (image); | |||||
| } | |||||
| } | |||||
| void captureBuffer (QTSampleBuffer* sampleBuffer) | |||||
| { | |||||
| const Time now (Time::getCurrentTime()); | |||||
| NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; | |||||
| int64 presentationTime = (hosttime != nil) | |||||
| ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) | |||||
| : (([sampleBuffer presentationTime].timeValue * 1000) / [sampleBuffer presentationTime].timeScale + 50); | |||||
| const int64 timeDiff = now.toMilliseconds() - presentationTime; | |||||
| if (firstPresentationTime == 0) | |||||
| { | |||||
| firstPresentationTime = presentationTime; | |||||
| averageTimeOffset = timeDiff; | |||||
| } | |||||
| else | |||||
| { | |||||
| averageTimeOffset = (averageTimeOffset * 120 + timeDiff * 8) / 128; | |||||
| } | |||||
| } | |||||
| static StringArray getAvailableDevices() | |||||
| { | |||||
| StringArray results; | |||||
| NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; | |||||
| for (int i = 0; i < (int) [devs count]; ++i) | |||||
| { | |||||
| QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i]; | |||||
| results.add (nsStringToJuce ([dev localizedDisplayName])); | |||||
| } | |||||
| return results; | |||||
| } | |||||
| QTCaptureDevice* device; | |||||
| QTCaptureDevice* audioDevice; | |||||
| QTCaptureDeviceInput* input; | |||||
| QTCaptureDeviceInput* audioInput; | |||||
| QTCaptureSession* session; | |||||
| QTCaptureMovieFileOutput* fileOutput; | |||||
| QTCaptureOutput* imageOutput; | |||||
| NSObject* callbackDelegate; | |||||
| String openingError; | |||||
| int64 firstPresentationTime, averageTimeOffset; | |||||
| bool isRecording; | |||||
| Array<CameraDevice::Listener*> listeners; | |||||
| CriticalSection listenerLock; | |||||
| private: | |||||
| //============================================================================== | |||||
| struct DelegateClass : public ObjCClass<NSObject> | |||||
| { | |||||
| DelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||||
| { | |||||
| addIvar<Pimpl*> ("owner"); | |||||
| addMethod (@selector (captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection:), | |||||
| didOutputVideoFrame, "v@:@", @encode (CVImageBufferRef), "@@"); | |||||
| addMethod (@selector (captureOutput:didOutputSampleBuffer:fromConnection:), | |||||
| didOutputVideoFrame, "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 didOutputVideoFrame (id self, SEL, QTCaptureOutput*, CVImageBufferRef videoFrame, | |||||
| QTSampleBuffer*, QTCaptureConnection*) | |||||
| { | |||||
| Pimpl* const internal = getOwner (self); | |||||
| if (internal->listeners.size() > 0) | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], | |||||
| (int) CVPixelBufferGetWidth (videoFrame), | |||||
| (int) CVPixelBufferGetHeight (videoFrame)); | |||||
| } | |||||
| } | |||||
| } | |||||
| static void didOutputSampleBuffer (id self, SEL, QTCaptureFileOutput*, QTSampleBuffer* sampleBuffer, QTCaptureConnection*) | |||||
| { | |||||
| getOwner (self)->captureBuffer (sampleBuffer); | |||||
| } | |||||
| }; | |||||
| JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||||
| }; | |||||
| struct CameraDevice::ViewerComponent : public NSViewComponent | |||||
| { | |||||
| ViewerComponent (CameraDevice& d) | |||||
| { | |||||
| JUCE_AUTORELEASEPOOL | |||||
| { | |||||
| captureView = [[QTCaptureView alloc] init]; | |||||
| [captureView setCaptureSession: d.pimpl->session]; | |||||
| setSize (640, 480); | |||||
| setView (captureView); | |||||
| } | |||||
| } | |||||
| ~ViewerComponent() | |||||
| { | |||||
| setView (nil); | |||||
| [captureView setCaptureSession: nil]; | |||||
| [captureView release]; | |||||
| } | |||||
| QTCaptureView* captureView; | |||||
| JUCE_DECLARE_NON_COPYABLE (ViewerComponent) | |||||
| }; | |||||
| String CameraDevice::getFileExtension() | |||||
| { | |||||
| return ".mov"; | |||||
| } | |||||
| @@ -32,9 +32,7 @@ struct MovieComponent::Pimpl | |||||
| { | { | ||||
| close(); | close(); | ||||
| NSString* videoFile = [NSString stringWithUTF8String: newPath.toUTF8()]; | |||||
| NSURL* url = [NSURL fileURLWithPath: videoFile]; | |||||
| NSURL* url = createNSURLFromFile (newPath); | |||||
| AVAsset* asset = [AVAsset assetWithURL: url]; | AVAsset* asset = [AVAsset assetWithURL: url]; | ||||
| duration = CMTimeGetSeconds (asset.duration); | duration = CMTimeGetSeconds (asset.duration); | ||||
| nativeSize = [[[asset tracksWithMediaType: AVMediaTypeVideo] objectAtIndex: 0] naturalSize]; | nativeSize = [[[asset tracksWithMediaType: AVMediaTypeVideo] objectAtIndex: 0] naturalSize]; | ||||
| @@ -0,0 +1,185 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| struct VideoComponent::Pimpl : public NSViewComponent | |||||
| { | |||||
| Pimpl() | |||||
| { | |||||
| setVisible (true); | |||||
| AVPlayerView* view = [[AVPlayerView alloc] init]; | |||||
| setView (view); | |||||
| [view setNextResponder: [view superview]]; | |||||
| [view setWantsLayer: YES]; | |||||
| [view release]; | |||||
| } | |||||
| ~Pimpl() | |||||
| { | |||||
| close(); | |||||
| setView (nil); | |||||
| } | |||||
| Result load (const File& file) | |||||
| { | |||||
| auto r = load (createNSURLFromFile (file)); | |||||
| if (r.wasOk()) | |||||
| currentFile = file; | |||||
| return r; | |||||
| } | |||||
| Result load (const URL& url) | |||||
| { | |||||
| Result r = load ([NSURL URLWithString: juceStringToNS (url.toString (true))]); | |||||
| if (r.wasOk()) | |||||
| currentURL = url; | |||||
| return r; | |||||
| } | |||||
| Result load (NSURL* url) | |||||
| { | |||||
| if (url != nil) | |||||
| { | |||||
| close(); | |||||
| if (AVPlayer* player = [AVPlayer playerWithURL: url]) | |||||
| { | |||||
| [getAVPlayerView() setPlayer: player]; | |||||
| return Result::ok(); | |||||
| } | |||||
| } | |||||
| return Result::fail ("Couldn't open movie"); | |||||
| } | |||||
| void close() | |||||
| { | |||||
| stop(); | |||||
| [getAVPlayerView() setPlayer: nil]; | |||||
| currentFile = File(); | |||||
| currentURL = URL(); | |||||
| } | |||||
| bool isOpen() const | |||||
| { | |||||
| return getAVPlayer() != nil; | |||||
| } | |||||
| bool isPlaying() const | |||||
| { | |||||
| return getSpeed() != 0; | |||||
| } | |||||
| void play() | |||||
| { | |||||
| [getAVPlayer() play]; | |||||
| } | |||||
| void stop() | |||||
| { | |||||
| [getAVPlayer() pause]; | |||||
| } | |||||
| void setPosition (double newPosition) | |||||
| { | |||||
| if (AVPlayer* p = getAVPlayer()) | |||||
| { | |||||
| CMTime t = { (CMTimeValue) (100000.0 * newPosition), | |||||
| (CMTimeScale) 100000, kCMTimeFlags_Valid }; | |||||
| [p seekToTime: t]; | |||||
| } | |||||
| } | |||||
| double getPosition() const | |||||
| { | |||||
| if (AVPlayer* p = getAVPlayer()) | |||||
| return toSeconds ([p currentTime]); | |||||
| return 0.0; | |||||
| } | |||||
| void setSpeed (double newSpeed) | |||||
| { | |||||
| [getAVPlayer() setRate: newSpeed]; | |||||
| } | |||||
| double getSpeed() const | |||||
| { | |||||
| if (AVPlayer* p = getAVPlayer()) | |||||
| return [p rate]; | |||||
| return 0.0; | |||||
| } | |||||
| Rectangle<int> getNativeSize() const | |||||
| { | |||||
| if (AVPlayer* player = getAVPlayer()) | |||||
| { | |||||
| CGSize s = [[player currentItem] presentationSize]; | |||||
| return Rectangle<int> ((int) s.width, (int) s.height); | |||||
| } | |||||
| return Rectangle<int>(); | |||||
| } | |||||
| double getDuration() const | |||||
| { | |||||
| if (AVPlayer* player = getAVPlayer()) | |||||
| return toSeconds ([[player currentItem] duration]); | |||||
| return 0.0; | |||||
| } | |||||
| void setVolume (float newVolume) | |||||
| { | |||||
| [getAVPlayer() setVolume: newVolume]; | |||||
| } | |||||
| float getVolume() const | |||||
| { | |||||
| if (AVPlayer* p = getAVPlayer()) | |||||
| return [p volume]; | |||||
| return 0.0f; | |||||
| } | |||||
| File currentFile; | |||||
| URL currentURL; | |||||
| private: | |||||
| AVPlayerView* getAVPlayerView() const { return (AVPlayerView*) getView(); } | |||||
| AVPlayer* getAVPlayer() const { return [getAVPlayerView() 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) | |||||
| }; | |||||
| @@ -494,9 +494,8 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
| return devs; | return devs; | ||||
| } | } | ||||
| class GrabberCallback : public ComBaseClassHelperBase<ISampleGrabberCB> | |||||
| struct GrabberCallback : public ComBaseClassHelperBase<ISampleGrabberCB> | |||||
| { | { | ||||
| public: | |||||
| GrabberCallback (Pimpl& p) | GrabberCallback (Pimpl& p) | ||||
| : ComBaseClassHelperBase<ISampleGrabberCB> (0), owner (p) {} | : ComBaseClassHelperBase<ISampleGrabberCB> (0), owner (p) {} | ||||
| @@ -516,7 +515,6 @@ struct CameraDevice::Pimpl : public ChangeBroadcaster | |||||
| return S_OK; | return S_OK; | ||||
| } | } | ||||
| private: | |||||
| Pimpl& owner; | Pimpl& owner; | ||||
| JUCE_DECLARE_NON_COPYABLE (GrabberCallback) | JUCE_DECLARE_NON_COPYABLE (GrabberCallback) | ||||
| @@ -1,928 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace DirectShowHelpers | |||||
| { | |||||
| bool checkDShowAvailability() | |||||
| { | |||||
| ComSmartPtr<IGraphBuilder> graph; | |||||
| return SUCCEEDED (graph.CoCreateInstance (CLSID_FilterGraph)); | |||||
| } | |||||
| //============================================================================== | |||||
| class VideoRenderer | |||||
| { | |||||
| public: | |||||
| VideoRenderer() {} | |||||
| virtual ~VideoRenderer() {} | |||||
| virtual HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder, | |||||
| ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) = 0; | |||||
| virtual void setVideoWindow (HWND hwnd) = 0; | |||||
| virtual void setVideoPosition (HWND hwnd, long videoWidth, long videoHeight) = 0; | |||||
| virtual void repaintVideo (HWND hwnd, HDC hdc) = 0; | |||||
| virtual void displayModeChanged() = 0; | |||||
| virtual HRESULT getVideoSize (long& videoWidth, long& videoHeight) = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| class VMR7 : public VideoRenderer | |||||
| { | |||||
| public: | |||||
| VMR7() {} | |||||
| HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder, | |||||
| ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) | |||||
| { | |||||
| 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) | |||||
| { | |||||
| windowlessControl->SetVideoClippingWindow (hwnd); | |||||
| } | |||||
| void setVideoPosition (HWND hwnd, long videoWidth, long videoHeight) | |||||
| { | |||||
| RECT src, dest; | |||||
| SetRect (&src, 0, 0, videoWidth, videoHeight); | |||||
| GetClientRect (hwnd, &dest); | |||||
| windowlessControl->SetVideoPosition (&src, &dest); | |||||
| } | |||||
| void repaintVideo (HWND hwnd, HDC hdc) | |||||
| { | |||||
| windowlessControl->RepaintVideo (hwnd, hdc); | |||||
| } | |||||
| void displayModeChanged() | |||||
| { | |||||
| windowlessControl->DisplayModeChanged(); | |||||
| } | |||||
| HRESULT getVideoSize (long& videoWidth, long& videoHeight) | |||||
| { | |||||
| return windowlessControl->GetNativeVideoSize (&videoWidth, &videoHeight, nullptr, nullptr); | |||||
| } | |||||
| private: | |||||
| ComSmartPtr<IVMRWindowlessControl> windowlessControl; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VMR7) | |||||
| }; | |||||
| //============================================================================== | |||||
| #if JUCE_MEDIAFOUNDATION | |||||
| class EVR : public VideoRenderer | |||||
| { | |||||
| public: | |||||
| EVR() {} | |||||
| HRESULT create (ComSmartPtr<IGraphBuilder>& graphBuilder, | |||||
| ComSmartPtr<IBaseFilter>& baseFilter, HWND hwnd) | |||||
| { | |||||
| 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, | |||||
| (LPVOID*) videoDisplayControl.resetAndGetPointerAddress()); | |||||
| if (SUCCEEDED (hr)) hr = videoDisplayControl->SetVideoWindow (hwnd); | |||||
| if (SUCCEEDED (hr)) hr = videoDisplayControl->SetAspectRatioMode (MFVideoARMode_PreservePicture); | |||||
| return hr; | |||||
| } | |||||
| void setVideoWindow (HWND hwnd) | |||||
| { | |||||
| videoDisplayControl->SetVideoWindow (hwnd); | |||||
| } | |||||
| void setVideoPosition (HWND hwnd, long /*videoWidth*/, long /*videoHeight*/) | |||||
| { | |||||
| const MFVideoNormalizedRect src = { 0.0f, 0.0f, 1.0f, 1.0f }; | |||||
| RECT dest; | |||||
| GetClientRect (hwnd, &dest); | |||||
| videoDisplayControl->SetVideoPosition (&src, &dest); | |||||
| } | |||||
| void repaintVideo (HWND /*hwnd*/, HDC /*hdc*/) | |||||
| { | |||||
| videoDisplayControl->RepaintVideo(); | |||||
| } | |||||
| void displayModeChanged() {} | |||||
| HRESULT getVideoSize (long& videoWidth, long& videoHeight) | |||||
| { | |||||
| SIZE sz; | |||||
| HRESULT hr = videoDisplayControl->GetNativeVideoSize (&sz, nullptr); | |||||
| videoWidth = sz.cx; | |||||
| videoHeight = sz.cy; | |||||
| return hr; | |||||
| } | |||||
| private: | |||||
| ComSmartPtr<IMFVideoDisplayControl> videoDisplayControl; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR) | |||||
| }; | |||||
| #endif | |||||
| } | |||||
| //============================================================================== | |||||
| class DirectShowComponent::DirectShowContext : public AsyncUpdater | |||||
| { | |||||
| public: | |||||
| DirectShowContext (DirectShowComponent& c, VideoRendererType renderType) | |||||
| : component (c), | |||||
| hwnd (0), | |||||
| hdc (0), | |||||
| state (uninitializedState), | |||||
| hasVideo (false), | |||||
| videoWidth (0), | |||||
| videoHeight (0), | |||||
| type (renderType), | |||||
| needToUpdateViewport (true), | |||||
| needToRecreateNativeWindow (false) | |||||
| { | |||||
| CoInitialize (0); | |||||
| if (type == dshowDefault) | |||||
| { | |||||
| type = dshowVMR7; | |||||
| #if JUCE_MEDIAFOUNDATION | |||||
| if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) | |||||
| type = dshowEVR; | |||||
| #endif | |||||
| } | |||||
| } | |||||
| ~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->getHandle(), nativeWindow->getContext()); | |||||
| } | |||||
| void updateVideoPosition() | |||||
| { | |||||
| if (hasVideo) | |||||
| videoRenderer->setVideoPosition (nativeWindow->getHandle(), videoWidth, videoHeight); | |||||
| } | |||||
| 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(); | |||||
| } | |||||
| //============================================================================== | |||||
| bool loadFile (const String& fileOrURLPath) | |||||
| { | |||||
| jassert (state == uninitializedState); | |||||
| if (! createNativeWindow()) | |||||
| return false; | |||||
| 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 JUCE_MEDIAFOUNDATION | |||||
| if (type == dshowEVR) | |||||
| videoRenderer = new DirectShowHelpers::EVR(); | |||||
| else | |||||
| #endif | |||||
| videoRenderer = new DirectShowHelpers::VMR7(); | |||||
| hr = videoRenderer->create (graphBuilder, baseFilter, hwnd); | |||||
| } | |||||
| // build filter graph | |||||
| if (SUCCEEDED (hr)) | |||||
| { | |||||
| hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr); | |||||
| if (FAILED (hr)) | |||||
| { | |||||
| // 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); | |||||
| } | |||||
| } | |||||
| // remove video renderer if not connected (no video) | |||||
| if (SUCCEEDED (hr)) | |||||
| { | |||||
| if (isRendererConnected()) | |||||
| { | |||||
| hasVideo = true; | |||||
| hr = videoRenderer->getVideoSize (videoWidth, videoHeight); | |||||
| } | |||||
| 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 true; | |||||
| } | |||||
| // 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 false; | |||||
| } | |||||
| 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; | |||||
| videoWidth = 0; | |||||
| videoHeight = 0; | |||||
| if (nativeWindow != nullptr) | |||||
| deleteNativeWindow(); | |||||
| } | |||||
| void graphEventProc() | |||||
| { | |||||
| LONG ec; | |||||
| 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: | |||||
| if (component.isLooping()) | |||||
| component.goToStart(); | |||||
| else | |||||
| component.stop(); | |||||
| break; | |||||
| case EC_USERABORT: | |||||
| case EC_ERRORABORT: | |||||
| case EC_ERRORABORTEX: | |||||
| component.closeMovie(); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| void run() | |||||
| { | |||||
| mediaControl->Run(); | |||||
| state = runningState; | |||||
| } | |||||
| void stop() | |||||
| { | |||||
| mediaControl->Stop(); | |||||
| state = stoppedState; | |||||
| } | |||||
| void pause() | |||||
| { | |||||
| mediaControl->Pause(); | |||||
| state = pausedState; | |||||
| } | |||||
| //============================================================================== | |||||
| bool isInitialised() const noexcept { return state != uninitializedState; } | |||||
| bool isRunning() const noexcept { return state == runningState; } | |||||
| bool isPaused() const noexcept { return state == pausedState; } | |||||
| bool isStopped() const noexcept { return state == stoppedState; } | |||||
| bool containsVideo() const noexcept { return hasVideo; } | |||||
| int getVideoWidth() const noexcept { return (int) videoWidth; } | |||||
| int getVideoHeight() const noexcept { return (int) videoHeight; } | |||||
| //============================================================================== | |||||
| double getDuration() const | |||||
| { | |||||
| REFTIME duration; | |||||
| mediaPosition->get_Duration (&duration); | |||||
| return duration; | |||||
| } | |||||
| double getPosition() const | |||||
| { | |||||
| REFTIME seconds; | |||||
| mediaPosition->get_CurrentPosition (&seconds); | |||||
| return seconds; | |||||
| } | |||||
| //============================================================================== | |||||
| void setSpeed (const float newSpeed) { mediaPosition->put_Rate (newSpeed); } | |||||
| void setPosition (const double seconds) { mediaPosition->put_CurrentPosition (seconds); } | |||||
| void setVolume (const float newVolume) { basicAudio->put_Volume (convertToDShowVolume (newVolume)); } | |||||
| // in DirectShow, full volume is 0, silence is -10000 | |||||
| static long convertToDShowVolume (const 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; | |||||
| } | |||||
| private: | |||||
| //============================================================================== | |||||
| enum { graphEventID = WM_APP + 0x43f0 }; | |||||
| DirectShowComponent& component; | |||||
| HWND hwnd; | |||||
| HDC hdc; | |||||
| enum State { uninitializedState, runningState, pausedState, stoppedState }; | |||||
| State state; | |||||
| bool hasVideo; | |||||
| long videoWidth, videoHeight; | |||||
| VideoRendererType type; | |||||
| ComSmartPtr<IGraphBuilder> graphBuilder; | |||||
| ComSmartPtr<IMediaControl> mediaControl; | |||||
| ComSmartPtr<IMediaPosition> mediaPosition; | |||||
| ComSmartPtr<IMediaEventEx> mediaEvent; | |||||
| ComSmartPtr<IBasicAudio> basicAudio; | |||||
| ComSmartPtr<IBaseFilter> baseFilter; | |||||
| ScopedPointer<DirectShowHelpers::VideoRenderer> videoRenderer; | |||||
| bool needToUpdateViewport, needToRecreateNativeWindow; | |||||
| //============================================================================== | |||||
| class NativeWindowClass : private DeletedAtShutdown | |||||
| { | |||||
| public: | |||||
| bool isRegistered() const noexcept { return atom != 0; } | |||||
| LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) MAKELONG (atom, 0); } | |||||
| juce_DeclareSingleton_SingleThreaded_Minimal (NativeWindowClass) | |||||
| private: | |||||
| NativeWindowClass() | |||||
| : atom (0) | |||||
| { | |||||
| 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 (DirectShowContext* const 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) | |||||
| }; | |||||
| //============================================================================== | |||||
| class NativeWindow | |||||
| { | |||||
| public: | |||||
| NativeWindow (HWND parentToAddTo, void* const userData) | |||||
| : hwnd (0), hdc (0) | |||||
| { | |||||
| NativeWindowClass* const wc = NativeWindowClass::getInstance(); | |||||
| if (wc->isRegistered()) | |||||
| { | |||||
| DWORD exstyle = 0; | |||||
| DWORD windowType = WS_CHILD; | |||||
| hwnd = CreateWindowEx (exstyle, wc->getWindowClassName(), | |||||
| L"", windowType, 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); | |||||
| } | |||||
| } | |||||
| HWND getHandle() const noexcept { return hwnd; } | |||||
| HDC getContext() const noexcept { return hdc; } | |||||
| void setWindowPosition (const Rectangle<int>& newBounds) | |||||
| { | |||||
| SetWindowPos (hwnd, 0, newBounds.getX(), newBounds.getY(), | |||||
| newBounds.getWidth(), newBounds.getHeight(), | |||||
| SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); | |||||
| } | |||||
| void showWindow (const bool shouldBeVisible) | |||||
| { | |||||
| ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE); | |||||
| } | |||||
| private: | |||||
| HWND hwnd; | |||||
| HDC hdc; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow) | |||||
| }; | |||||
| ScopedPointer<NativeWindow> nativeWindow; | |||||
| //============================================================================== | |||||
| bool createNativeWindow() | |||||
| { | |||||
| jassert (nativeWindow == nullptr); | |||||
| if (ComponentPeer* const topLevelPeer = component.getTopLevelComponent()->getPeer()) | |||||
| { | |||||
| nativeWindow = new NativeWindow ((HWND) topLevelPeer->getNativeHandle(), this); | |||||
| hwnd = nativeWindow->getHandle(); | |||||
| if (hwnd != 0) | |||||
| { | |||||
| hdc = GetDC (hwnd); | |||||
| component.updateContextPosition(); | |||||
| component.showContext (component.isShowing()); | |||||
| return true; | |||||
| } | |||||
| else | |||||
| { | |||||
| nativeWindow = nullptr; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void deleteNativeWindow() | |||||
| { | |||||
| jassert (nativeWindow != nullptr); | |||||
| ReleaseDC (hwnd, hdc); | |||||
| hwnd = 0; | |||||
| hdc = 0; | |||||
| 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; | |||||
| } | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext) | |||||
| }; | |||||
| juce_ImplementSingleton_SingleThreaded (DirectShowComponent::DirectShowContext::NativeWindowClass) | |||||
| //============================================================================== | |||||
| class DirectShowComponent::DirectShowComponentWatcher : public ComponentMovementWatcher | |||||
| { | |||||
| public: | |||||
| DirectShowComponentWatcher (DirectShowComponent* const c) | |||||
| : ComponentMovementWatcher (c), | |||||
| owner (c) | |||||
| { | |||||
| } | |||||
| void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override | |||||
| { | |||||
| if (owner->videoLoaded) | |||||
| owner->updateContextPosition(); | |||||
| } | |||||
| void componentPeerChanged() override | |||||
| { | |||||
| if (owner->videoLoaded) | |||||
| owner->recreateNativeWindowAsync(); | |||||
| } | |||||
| void componentVisibilityChanged() override | |||||
| { | |||||
| if (owner->videoLoaded) | |||||
| owner->showContext (owner->isShowing()); | |||||
| } | |||||
| private: | |||||
| DirectShowComponent* const owner; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowComponentWatcher) | |||||
| }; | |||||
| //============================================================================== | |||||
| DirectShowComponent::DirectShowComponent (VideoRendererType type) | |||||
| : videoLoaded (false), | |||||
| looping (false) | |||||
| { | |||||
| setOpaque (true); | |||||
| context = new DirectShowContext (*this, type); | |||||
| componentWatcher = new DirectShowComponentWatcher (this); | |||||
| } | |||||
| DirectShowComponent::~DirectShowComponent() | |||||
| { | |||||
| componentWatcher = nullptr; | |||||
| } | |||||
| bool DirectShowComponent::isDirectShowAvailable() | |||||
| { | |||||
| static bool isDSAvailable = DirectShowHelpers::checkDShowAvailability(); | |||||
| return isDSAvailable; | |||||
| } | |||||
| void DirectShowComponent::recreateNativeWindowAsync() | |||||
| { | |||||
| context->recreateNativeWindowAsync(); | |||||
| repaint(); | |||||
| } | |||||
| void DirectShowComponent::updateContextPosition() | |||||
| { | |||||
| context->updateContextPosition(); | |||||
| if (getWidth() > 0 && getHeight() > 0) | |||||
| if (ComponentPeer* peer = getTopLevelComponent()->getPeer()) | |||||
| context->updateWindowPosition (peer->getAreaCoveredBy (*this)); | |||||
| } | |||||
| void DirectShowComponent::showContext (const bool shouldBeVisible) | |||||
| { | |||||
| context->showWindow (shouldBeVisible); | |||||
| } | |||||
| void DirectShowComponent::paint (Graphics& g) | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->handleUpdateNowIfNeeded(); | |||||
| else | |||||
| g.fillAll (Colours::grey); | |||||
| } | |||||
| //============================================================================== | |||||
| bool DirectShowComponent::loadMovie (const String& fileOrURLPath) | |||||
| { | |||||
| closeMovie(); | |||||
| videoLoaded = context->loadFile (fileOrURLPath); | |||||
| if (videoLoaded) | |||||
| { | |||||
| videoPath = fileOrURLPath; | |||||
| context->updateVideoPosition(); | |||||
| } | |||||
| return videoLoaded; | |||||
| } | |||||
| bool DirectShowComponent::loadMovie (const File& videoFile) | |||||
| { | |||||
| return loadMovie (videoFile.getFullPathName()); | |||||
| } | |||||
| bool DirectShowComponent::loadMovie (const URL& videoURL) | |||||
| { | |||||
| return loadMovie (videoURL.toString (false)); | |||||
| } | |||||
| void DirectShowComponent::closeMovie() | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->release(); | |||||
| videoLoaded = false; | |||||
| videoPath.clear(); | |||||
| } | |||||
| //============================================================================== | |||||
| File DirectShowComponent::getCurrentMoviePath() const { return videoPath; } | |||||
| bool DirectShowComponent::isMovieOpen() const { return videoLoaded; } | |||||
| double DirectShowComponent::getMovieDuration() const { return videoLoaded ? context->getDuration() : 0.0; } | |||||
| void DirectShowComponent::setLooping (const bool shouldLoop) { looping = shouldLoop; } | |||||
| bool DirectShowComponent::isLooping() const { return looping; } | |||||
| void DirectShowComponent::getMovieNormalSize (int &width, int &height) const | |||||
| { | |||||
| width = context->getVideoWidth(); | |||||
| height = context->getVideoHeight(); | |||||
| } | |||||
| //============================================================================== | |||||
| void DirectShowComponent::setBoundsWithCorrectAspectRatio (const Rectangle<int>& spaceToFitWithin, | |||||
| RectanglePlacement placement) | |||||
| { | |||||
| int normalWidth, normalHeight; | |||||
| getMovieNormalSize (normalWidth, normalHeight); | |||||
| const Rectangle<int> normalSize (0, 0, normalWidth, normalHeight); | |||||
| if (! (spaceToFitWithin.isEmpty() || normalSize.isEmpty())) | |||||
| setBounds (placement.appliedTo (normalSize, spaceToFitWithin)); | |||||
| else | |||||
| setBounds (spaceToFitWithin); | |||||
| } | |||||
| //============================================================================== | |||||
| void DirectShowComponent::play() | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->run(); | |||||
| } | |||||
| void DirectShowComponent::stop() | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->pause(); | |||||
| } | |||||
| bool DirectShowComponent::isPlaying() const | |||||
| { | |||||
| return context->isRunning(); | |||||
| } | |||||
| void DirectShowComponent::goToStart() | |||||
| { | |||||
| setPosition (0.0); | |||||
| } | |||||
| void DirectShowComponent::setPosition (const double seconds) | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->setPosition (seconds); | |||||
| } | |||||
| double DirectShowComponent::getPosition() const | |||||
| { | |||||
| return videoLoaded ? context->getPosition() : 0.0; | |||||
| } | |||||
| void DirectShowComponent::setSpeed (const float newSpeed) | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->setSpeed (newSpeed); | |||||
| } | |||||
| void DirectShowComponent::setMovieVolume (const float newVolume) | |||||
| { | |||||
| if (videoLoaded) | |||||
| context->setVolume (newVolume); | |||||
| } | |||||
| float DirectShowComponent::getMovieVolume() const | |||||
| { | |||||
| return videoLoaded ? context->getVolume() : 0.0f; | |||||
| } | |||||
| @@ -0,0 +1,896 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 = new DirectShowContext (*this); | |||||
| componentWatcher = new ComponentWatcher (*this); | |||||
| } | |||||
| ~Pimpl() | |||||
| { | |||||
| close(); | |||||
| context = nullptr; | |||||
| componentWatcher = nullptr; | |||||
| } | |||||
| Result loadFromString (const String& fileOrURLPath) | |||||
| { | |||||
| close(); | |||||
| Result r = context->loadFile (fileOrURLPath); | |||||
| if (r.wasOk()) | |||||
| { | |||||
| videoLoaded = true; | |||||
| context->updateVideoPosition(); | |||||
| } | |||||
| return r; | |||||
| } | |||||
| Result load (const File& file) | |||||
| { | |||||
| Result r = loadFromString (file.getFullPathName()); | |||||
| if (r.wasOk()) | |||||
| currentFile = file; | |||||
| return r; | |||||
| } | |||||
| Result load (const URL& url) | |||||
| { | |||||
| Result r = loadFromString (url.toString (true)); | |||||
| if (r.wasOk()) | |||||
| currentURL = url; | |||||
| return r; | |||||
| } | |||||
| void close() | |||||
| { | |||||
| stop(); | |||||
| context->release(); | |||||
| videoLoaded = false; | |||||
| currentFile = File(); | |||||
| currentURL = URL(); | |||||
| } | |||||
| 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 (ComponentPeer* 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) | |||||
| }; | |||||
| ScopedPointer<ComponentWatcher> componentWatcher; | |||||
| //====================================================================== | |||||
| struct DirectShowContext : public AsyncUpdater | |||||
| { | |||||
| DirectShowContext (Pimpl& c) | |||||
| : component (c), hwnd(), hdc(), | |||||
| state (uninitializedState), | |||||
| hasVideo (false), | |||||
| needToUpdateViewport (true), | |||||
| needToRecreateNativeWindow (false) | |||||
| { | |||||
| 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 = new VideoRenderers::EVR(); | |||||
| hr = videoRenderer->create (graphBuilder, baseFilter, hwnd); | |||||
| if (FAILED (hr)) | |||||
| videoRenderer = nullptr; | |||||
| } | |||||
| if (videoRenderer == nullptr) | |||||
| { | |||||
| videoRenderer = new VideoRenderers::VMR7(); | |||||
| hr = videoRenderer->create (graphBuilder, baseFilter, hwnd); | |||||
| } | |||||
| } | |||||
| // build filter graph | |||||
| if (SUCCEEDED (hr)) | |||||
| { | |||||
| hr = graphBuilder->RenderFile (fileOrURLPath.toWideCharPointer(), nullptr); | |||||
| if (FAILED (hr)) | |||||
| { | |||||
| // 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); | |||||
| } | |||||
| } | |||||
| // 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 = nullptr, p2 = nullptr; | |||||
| 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 Rectangle<int> ((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; | |||||
| 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; | |||||
| ScopedPointer<VideoRenderers::Base> videoRenderer; | |||||
| bool hasVideo, needToUpdateViewport, needToRecreateNativeWindow; | |||||
| //====================================================================== | |||||
| bool createNativeWindow() | |||||
| { | |||||
| jassert (nativeWindow == nullptr); | |||||
| if (ComponentPeer* const topLevelPeer = component.getTopLevelComponent()->getPeer()) | |||||
| { | |||||
| nativeWindow = 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 = 0; | |||||
| hdc = 0; | |||||
| 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) MAKELONG (atom, 0); } | |||||
| juce_DeclareSingleton_SingleThreaded_Minimal (NativeWindowClass) | |||||
| private: | |||||
| NativeWindowClass() : atom() | |||||
| { | |||||
| 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 (DirectShowContext* const 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) : hwnd(), hdc() | |||||
| { | |||||
| NativeWindowClass* 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 (const bool shouldBeVisible) | |||||
| { | |||||
| ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE); | |||||
| } | |||||
| HWND hwnd; | |||||
| HDC hdc; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeWindow) | |||||
| }; | |||||
| ScopedPointer<NativeWindow> nativeWindow; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowContext) | |||||
| }; | |||||
| ScopedPointer<DirectShowContext> context; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
| }; | |||||
| juce_ImplementSingleton_SingleThreaded (VideoComponent::Pimpl::DirectShowContext::NativeWindowClass) | |||||
| @@ -1,213 +0,0 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #pragma once | |||||
| //============================================================================== | |||||
| /** | |||||
| A window that can play back a DirectShow video. | |||||
| @note Controller is not implemented | |||||
| */ | |||||
| class JUCE_API DirectShowComponent : public Component | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** DirectShow video renderer type. | |||||
| See MSDN for advice about choosing the right renderer. | |||||
| */ | |||||
| enum VideoRendererType | |||||
| { | |||||
| dshowDefault, /**< VMR7 for Windows XP, EVR for Windows Vista and later */ | |||||
| dshowVMR7, /**< Video Mixing Renderer 7 */ | |||||
| dshowEVR /**< Enhanced Video Renderer */ | |||||
| }; | |||||
| /** Creates a DirectShowComponent, initially blank. | |||||
| Use the loadMovie() method to load a video once you've added the | |||||
| component to a window, (or put it on the desktop as a heavyweight window). | |||||
| Loading a video when the component isn't visible can cause problems, as | |||||
| DirectShow needs a window handle to initialise properly. | |||||
| @see VideoRendererType | |||||
| */ | |||||
| DirectShowComponent (VideoRendererType type = dshowDefault); | |||||
| /** Destructor. */ | |||||
| ~DirectShowComponent(); | |||||
| /** Returns true if DirectShow is installed and working on this machine. */ | |||||
| static bool isDirectShowAvailable(); | |||||
| //============================================================================== | |||||
| /** Tries to load a DirectShow video from a file or URL into the player. | |||||
| It's best to call this function once you've added the component to a window, | |||||
| (or put it on the desktop as a heavyweight window). Loading a video when the | |||||
| component isn't visible can cause problems, because DirectShow needs a window | |||||
| handle to do its stuff. | |||||
| @param fileOrURLPath the file or URL path to open | |||||
| @returns true if the video opens successfully | |||||
| */ | |||||
| bool loadMovie (const String& fileOrURLPath); | |||||
| /** Tries to load a DirectShow video from a file into the player. | |||||
| It's best to call this function once you've added the component to a window, | |||||
| (or put it on the desktop as a heavyweight window). Loading a video when the | |||||
| component isn't visible can cause problems, because DirectShow needs a window | |||||
| handle to do its stuff. | |||||
| @param videoFile the video file to open | |||||
| @returns true if the video opens successfully | |||||
| */ | |||||
| bool loadMovie (const File& videoFile); | |||||
| /** Tries to load a DirectShow video from a URL into the player. | |||||
| It's best to call this function once you've added the component to a window, | |||||
| (or put it on the desktop as a heavyweight window). Loading a video when the | |||||
| component isn't visible can cause problems, because DirectShow needs a window | |||||
| handle to do its stuff. | |||||
| @param videoURL the video URL to open | |||||
| @returns true if the video opens successfully | |||||
| */ | |||||
| bool loadMovie (const URL& videoURL); | |||||
| /** Closes the video, if one is open. */ | |||||
| void closeMovie(); | |||||
| /** Returns the file path or URL from which the video file was loaded. | |||||
| If there isn't one, this returns an empty string. | |||||
| */ | |||||
| File getCurrentMoviePath() const; | |||||
| /** Returns true if there's currently a video open. */ | |||||
| bool isMovieOpen() const; | |||||
| /** Returns the length of the video, in seconds. */ | |||||
| double getMovieDuration() const; | |||||
| /** Returns the video's natural size, in pixels. | |||||
| You can use this to resize the component to show the video at its preferred | |||||
| scale. | |||||
| If no video is loaded, the size returned will be 0 x 0. | |||||
| */ | |||||
| void getMovieNormalSize (int& width, int& height) const; | |||||
| /** This will position the component within a given area, keeping its aspect | |||||
| ratio correct according to the video's normal size. | |||||
| The component will be made as large as it can go within the space, and will | |||||
| be aligned according to the justification value if this means there are gaps at | |||||
| the top or sides. | |||||
| @note Not implemented | |||||
| */ | |||||
| void setBoundsWithCorrectAspectRatio (const Rectangle<int>& spaceToFitWithin, | |||||
| RectanglePlacement placement); | |||||
| /** Starts the video playing. */ | |||||
| void play(); | |||||
| /** Stops the video playing. */ | |||||
| void stop(); | |||||
| /** Returns true if the video is currently playing. */ | |||||
| bool isPlaying() const; | |||||
| /** Moves the video's position back to the start. */ | |||||
| void goToStart(); | |||||
| /** Sets the video's position to a given time. */ | |||||
| void setPosition (double seconds); | |||||
| /** Returns the current play position of the video. */ | |||||
| double getPosition() const; | |||||
| /** Changes the video playback rate. | |||||
| A value of 1 is normal speed, greater values play it proportionately faster, | |||||
| smaller values play it slower. | |||||
| */ | |||||
| void setSpeed (float newSpeed); | |||||
| /** Changes the video's playback volume. | |||||
| @param newVolume the volume in the range 0 (silent) to 1.0 (full) | |||||
| */ | |||||
| void setMovieVolume (float newVolume); | |||||
| /** Returns the video's playback volume. | |||||
| @returns the volume in the range 0 (silent) to 1.0 (full) | |||||
| */ | |||||
| float getMovieVolume() const; | |||||
| /** Tells the video whether it should loop. */ | |||||
| void setLooping (bool shouldLoop); | |||||
| /** Returns true if the video is currently looping. | |||||
| @see setLooping | |||||
| */ | |||||
| bool isLooping() const; | |||||
| //============================================================================== | |||||
| /** @internal */ | |||||
| void paint (Graphics&) override; | |||||
| private: | |||||
| //============================================================================== | |||||
| String videoPath; | |||||
| bool videoLoaded, looping; | |||||
| class DirectShowContext; | |||||
| friend class DirectShowContext; | |||||
| friend struct ContainerDeletePolicy<DirectShowContext>; | |||||
| ScopedPointer<DirectShowContext> context; | |||||
| class DirectShowComponentWatcher; | |||||
| friend class DirectShowComponentWatcher; | |||||
| friend struct ContainerDeletePolicy<DirectShowComponentWatcher>; | |||||
| ScopedPointer<DirectShowComponentWatcher> componentWatcher; | |||||
| //============================================================================== | |||||
| void updateContextPosition(); | |||||
| void showContext (bool shouldBeVisible); | |||||
| void recreateNativeWindowAsync(); | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectShowComponent) | |||||
| }; | |||||
| @@ -0,0 +1,116 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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_MAC | |||||
| #include "../native/juce_mac_Video.h" | |||||
| #elif JUCE_WINDOWS | |||||
| #include "../native/juce_win32_Video.h" | |||||
| #elif JUCE_ANDROID | |||||
| #include "../native/juce_android_Video.h" | |||||
| #endif | |||||
| //============================================================================== | |||||
| VideoComponent::VideoComponent() : pimpl (new Pimpl()) | |||||
| { | |||||
| addAndMakeVisible (pimpl); | |||||
| } | |||||
| VideoComponent::~VideoComponent() | |||||
| { | |||||
| pimpl = nullptr; | |||||
| } | |||||
| Result VideoComponent::load (const File& file) | |||||
| { | |||||
| Result r = pimpl->load (file); | |||||
| resized(); | |||||
| return r; | |||||
| } | |||||
| Result VideoComponent::load (const URL& url) | |||||
| { | |||||
| Result r = pimpl->load (url); | |||||
| resized(); | |||||
| return r; | |||||
| } | |||||
| void VideoComponent::closeVideo() | |||||
| { | |||||
| pimpl->close(); | |||||
| resized(); | |||||
| } | |||||
| bool VideoComponent::isVideoOpen() const { return pimpl->isOpen(); } | |||||
| File VideoComponent::getCurrentVideoFile() const { return pimpl->currentFile; } | |||||
| URL VideoComponent::getCurrentVideoURL() const { return pimpl->currentURL; } | |||||
| double VideoComponent::getVideoDuration() const { return pimpl->getDuration(); } | |||||
| Rectangle<int> VideoComponent::getVideoNativeSize() const { return pimpl->getNativeSize(); } | |||||
| void VideoComponent::play() { pimpl->play(); } | |||||
| void VideoComponent::stop() { pimpl->stop(); } | |||||
| bool VideoComponent::isPlaying() const { return pimpl->isPlaying(); } | |||||
| void VideoComponent::setPlayPosition (double newPos) { pimpl->setPosition (newPos); } | |||||
| double VideoComponent::getPlayPosition() const { return pimpl->getPosition(); } | |||||
| void VideoComponent::setPlaySpeed (double newSpeed) { pimpl->setSpeed (newSpeed); } | |||||
| void VideoComponent::setAudioVolume (float newVolume) { pimpl->setVolume (newVolume); } | |||||
| float VideoComponent::getAudioVolume() const { return pimpl->getVolume(); } | |||||
| void VideoComponent::resized() | |||||
| { | |||||
| Rectangle<int> r = getLocalBounds(); | |||||
| if (isVideoOpen() && ! r.isEmpty()) | |||||
| { | |||||
| Rectangle<int> nativeSize = getVideoNativeSize(); | |||||
| if (nativeSize.isEmpty()) | |||||
| { | |||||
| // if we've just opened the file and are still waiting for it to | |||||
| // figure out the size, start our timer.. | |||||
| if (! isTimerRunning()) | |||||
| startTimer (50); | |||||
| } | |||||
| else | |||||
| { | |||||
| r = RectanglePlacement (RectanglePlacement::centred).appliedTo (nativeSize, r); | |||||
| stopTimer(); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| stopTimer(); | |||||
| } | |||||
| pimpl->setBounds (r); | |||||
| } | |||||
| void VideoComponent::timerCallback() | |||||
| { | |||||
| resized(); | |||||
| } | |||||
| @@ -0,0 +1,132 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_VIDEOCOMPONENT_H_INCLUDED | |||||
| #define JUCE_VIDEOCOMPONENT_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| A component that can play a movie. | |||||
| Use the load() method to open a video once you've added this component to | |||||
| a parent (or put it on the desktop). | |||||
| */ | |||||
| class JUCE_API VideoComponent : public Component, | |||||
| private Timer | |||||
| { | |||||
| public: | |||||
| //============================================================================== | |||||
| /** Creates an empty VideoComponent. | |||||
| Use the load() method to open a video once you've added this component to | |||||
| a parent (or put it on the desktop). | |||||
| */ | |||||
| VideoComponent(); | |||||
| /** Destructor. */ | |||||
| ~VideoComponent(); | |||||
| //============================================================================== | |||||
| /** Tries to load a video from a local file. | |||||
| @returns am 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 | |||||
| */ | |||||
| Result load (const URL& url); | |||||
| /** Closes the video and resets the component. */ | |||||
| void closeVideo(); | |||||
| /** Returns true if a video is currently open. */ | |||||
| bool isVideoOpen() const; | |||||
| /** Returns the last file that was loaded. | |||||
| If nothing is open, or if it was a URL rather than a file, this will return File(). | |||||
| */ | |||||
| 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(). | |||||
| */ | |||||
| URL getCurrentVideoURL() const; | |||||
| //============================================================================== | |||||
| /** Returns the length of the video, in seconds. */ | |||||
| double getVideoDuration() const; | |||||
| /** Returns the video's natural size, in pixels. | |||||
| If no video is loaded, an empty rectangle will be returned. | |||||
| */ | |||||
| Rectangle<int> getVideoNativeSize() const; | |||||
| /** Starts the video playing. */ | |||||
| void play(); | |||||
| /** Stops the video playing. */ | |||||
| void stop(); | |||||
| /** Returns true if the video is currently playing. */ | |||||
| bool isPlaying() const; | |||||
| /** Sets the video's position to a given time. */ | |||||
| void setPlayPosition (double newPositionSeconds); | |||||
| /** Returns the current play position of the video. */ | |||||
| double getPlayPosition() const; | |||||
| /** Changes the video playback rate. | |||||
| A value of 1.0 is normal speed, greater values will play faster, smaller | |||||
| values play more slowly. | |||||
| */ | |||||
| void setPlaySpeed (double newSpeed); | |||||
| /** Changes the video's playback volume. | |||||
| @param newVolume the volume in the range 0 (silent) to 1.0 (full) | |||||
| */ | |||||
| void setAudioVolume (float newVolume); | |||||
| /** Returns the video's playback volume. | |||||
| @returns the volume in the range 0 (silent) to 1.0 (full) | |||||
| */ | |||||
| float getAudioVolume() const; | |||||
| private: | |||||
| //============================================================================== | |||||
| struct Pimpl; | |||||
| friend struct Pimpl; | |||||
| friend struct ContainerDeletePolicy<Pimpl>; | |||||
| ScopedPointer<Pimpl> pimpl; | |||||
| void resized() override; | |||||
| void timerCallback() override; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent) | |||||
| }; | |||||
| #endif | |||||