| @@ -142,6 +142,68 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| class DrawableComponent : public Component, | |||
| public ValueTree::Listener | |||
| { | |||
| public: | |||
| DrawableComponent (const ValueTree& drawable_) | |||
| { | |||
| setDrawable (drawable_); | |||
| } | |||
| ~DrawableComponent() | |||
| { | |||
| } | |||
| void setDrawable (const ValueTree& newDrawable) | |||
| { | |||
| drawable.removeListener (this); | |||
| drawable = newDrawable; | |||
| drawable.addListener (this); | |||
| drawableObject = Drawable::createFromValueTree (drawable, 0); // xxx image provider missing | |||
| resized(); | |||
| repaint(); | |||
| } | |||
| void paint (Graphics& g) | |||
| { | |||
| if (drawableObject != 0) | |||
| drawableObject->drawAt (g, 0, 0, 1.0f); | |||
| } | |||
| void resized() | |||
| { | |||
| DrawableComposite* dc = dynamic_cast <DrawableComposite*> (static_cast <Drawable*> (drawableObject)); | |||
| if (dc != 0) | |||
| { | |||
| dc->setMarker (DrawableComposite::contentLeftMarkerName, true, RelativeCoordinate (0)); | |||
| dc->setMarker (DrawableComposite::contentTopMarkerName, false, RelativeCoordinate (0)); | |||
| dc->setMarker (DrawableComposite::contentRightMarkerName, true, RelativeCoordinate (getWidth())); | |||
| dc->setMarker (DrawableComposite::contentBottomMarkerName, false, RelativeCoordinate (getHeight())); | |||
| } | |||
| } | |||
| void valueTreePropertyChanged (ValueTree&, const Identifier&) { updateGraphics(); } | |||
| void valueTreeChildrenChanged (ValueTree&) { updateGraphics(); } | |||
| void valueTreeParentChanged (ValueTree&) { updateGraphics(); } | |||
| private: | |||
| ValueTree drawable; | |||
| ScopedPointer<Drawable> drawableObject; | |||
| void updateGraphics() | |||
| { | |||
| if (drawableObject != 0) | |||
| { | |||
| const Rectangle<float> dirtyArea (drawableObject->refreshFromValueTree (drawable, 0)); | |||
| repaint (dirtyArea.getSmallestIntegerContainer()); | |||
| } | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| */ | |||
| @@ -1110,8 +1110,6 @@ public: | |||
| #endif | |||
| //============================================================================== | |||
| const int numGroups = 4; | |||
| class WidgetsDemo : public Component, | |||
| public ButtonListener | |||
| { | |||
| @@ -18349,6 +18349,28 @@ void ValueTree::SharedObject::moveChild (int currentIndex, int newIndex, UndoMan | |||
| } | |||
| } | |||
| void ValueTree::SharedObject::reorderChildren (const ReferenceCountedArray <SharedObject>& newOrder, UndoManager* undoManager) | |||
| { | |||
| jassert (newOrder.size() == children.size()); | |||
| if (undoManager == 0) | |||
| { | |||
| children = newOrder; | |||
| sendChildChangeMessage(); | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < children.size(); ++i) | |||
| { | |||
| if (children.getUnchecked(i) != newOrder.getUnchecked(i)) | |||
| { | |||
| jassert (children.contains (newOrder.getUnchecked(i))); | |||
| moveChild (children.indexOf (newOrder.getUnchecked(i)), i, undoManager); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| bool ValueTree::SharedObject::isEquivalentTo (const SharedObject& other) const | |||
| { | |||
| if (type != other.type | |||
| @@ -44257,19 +44279,9 @@ void DrawableButton::paintButton (Graphics& g, | |||
| if (imageToDraw != 0) | |||
| { | |||
| if (style == ImageRaw) | |||
| { | |||
| imageToDraw->draw (g, 1.0f); | |||
| } | |||
| else | |||
| { | |||
| imageToDraw->drawWithin (g, | |||
| imageSpace.getX(), | |||
| imageSpace.getY(), | |||
| imageSpace.getWidth(), | |||
| imageSpace.getHeight(), | |||
| RectanglePlacement::centred, | |||
| 1.0f); | |||
| } | |||
| imageToDraw->drawWithin (g, imageSpace.toFloat(), RectanglePlacement::centred, 1.0f); | |||
| } | |||
| } | |||
| @@ -44831,13 +44843,15 @@ void ToolbarButton::paintButtonArea (Graphics& g, | |||
| if (getToggleState() && toggledOnImage != 0) | |||
| d = toggledOnImage; | |||
| const Rectangle<float> area (0.0f, 0.0f, (float) width, (float) height); | |||
| if (! isEnabled()) | |||
| { | |||
| Image im (Image::ARGB, width, height, true); | |||
| { | |||
| Graphics g2 (im); | |||
| d->drawWithin (g2, 0, 0, width, height, RectanglePlacement::centred, 1.0f); | |||
| d->drawWithin (g2, area, RectanglePlacement::centred, 1.0f); | |||
| } | |||
| im.desaturate(); | |||
| @@ -44845,7 +44859,7 @@ void ToolbarButton::paintButtonArea (Graphics& g, | |||
| } | |||
| else | |||
| { | |||
| d->drawWithin (g, 0, 0, width, height, RectanglePlacement::centred, 1.0f); | |||
| d->drawWithin (g, area, RectanglePlacement::centred, 1.0f); | |||
| } | |||
| } | |||
| @@ -71698,7 +71712,7 @@ public: | |||
| if (current != 0) | |||
| { | |||
| registerMouseDown (screenPos, time, current); | |||
| registerMouseDown (screenPos, time, current, buttonState); | |||
| sendMouseDown (current, screenPos, time); | |||
| } | |||
| } | |||
| @@ -71845,16 +71859,10 @@ public: | |||
| for (int i = 1; i < numElementsInArray (mouseDowns); ++i) | |||
| { | |||
| if (mouseDowns[0].time - mouseDowns[i].time < (int) (MouseEvent::getDoubleClickTimeout() * (1.0 + 0.25 * (i - 1))) | |||
| && abs (mouseDowns[0].position.getX() - mouseDowns[i].position.getX()) < 8 | |||
| && abs (mouseDowns[0].position.getY() - mouseDowns[i].position.getY()) < 8) | |||
| { | |||
| if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[1], (int) (MouseEvent::getDoubleClickTimeout() * (1.0 + 0.25 * (i - 1))))) | |||
| ++numClicks; | |||
| } | |||
| else | |||
| { | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| @@ -71970,13 +71978,23 @@ private: | |||
| Point<int> position; | |||
| int64 time; | |||
| Component* component; | |||
| ModifierKeys buttons; | |||
| bool canBePartOfMultipleClickWith (const RecentMouseDown& other, int maxTimeBetween) const | |||
| { | |||
| return time - other.time < maxTimeBetween | |||
| && abs (position.getX() - other.position.getX()) < 8 | |||
| && abs (position.getY() - other.position.getY()) < 8 | |||
| && buttons == other.buttons;; | |||
| } | |||
| }; | |||
| RecentMouseDown mouseDowns[4]; | |||
| bool mouseMovedSignificantlySincePressed; | |||
| int64 lastTime; | |||
| void registerMouseDown (const Point<int>& screenPos, const int64 time, Component* const component) throw() | |||
| void registerMouseDown (const Point<int>& screenPos, const int64 time, | |||
| Component* const component, const ModifierKeys& modifiers) throw() | |||
| { | |||
| for (int i = numElementsInArray (mouseDowns); --i > 0;) | |||
| mouseDowns[i] = mouseDowns[i - 1]; | |||
| @@ -71984,6 +72002,7 @@ private: | |||
| mouseDowns[0].position = screenPos; | |||
| mouseDowns[0].time = time; | |||
| mouseDowns[0].component = component; | |||
| mouseDowns[0].buttons = modifiers.withOnlyMouseButtons(); | |||
| mouseMovedSignificantlySincePressed = false; | |||
| } | |||
| @@ -85932,21 +85951,21 @@ void RectanglePlacement::applyTo (double& x, double& y, | |||
| } | |||
| } | |||
| const AffineTransform RectanglePlacement::getTransformToFit (float x, float y, | |||
| float w, float h, | |||
| const float dx, const float dy, | |||
| const float dw, const float dh) const throw() | |||
| const AffineTransform RectanglePlacement::getTransformToFit (const Rectangle<float>& source, const Rectangle<float>& destination) const throw() | |||
| { | |||
| if (w == 0 || h == 0) | |||
| if (source.isEmpty()) | |||
| return AffineTransform::identity; | |||
| const float scaleX = dw / w; | |||
| const float scaleY = dh / h; | |||
| float w = source.getWidth(); | |||
| float h = source.getHeight(); | |||
| const float scaleX = destination.getWidth() / w; | |||
| const float scaleY = destination.getHeight() / h; | |||
| if ((flags & stretchToFit) != 0) | |||
| return AffineTransform::translation (-x, -y) | |||
| return AffineTransform::translation (-source.getX(), -source.getY()) | |||
| .scaled (scaleX, scaleY) | |||
| .translated (dx, dy); | |||
| .translated (destination.getX(), destination.getY()); | |||
| float scale = (flags & fillDestination) != 0 ? jmax (scaleX, scaleY) | |||
| : jmin (scaleX, scaleY); | |||
| @@ -85960,21 +85979,20 @@ const AffineTransform RectanglePlacement::getTransformToFit (float x, float y, | |||
| w *= scale; | |||
| h *= scale; | |||
| float newX = dx; | |||
| float newX = destination.getX(); | |||
| float newY = destination.getY(); | |||
| if ((flags & xRight) != 0) | |||
| newX += dw - w; // right | |||
| newX += destination.getWidth() - w; // right | |||
| else if ((flags & xLeft) == 0) | |||
| newX += (dw - w) / 2.0f; // centre | |||
| float newY = dy; | |||
| newX += (destination.getWidth() - w) / 2.0f; // centre | |||
| if ((flags & yBottom) != 0) | |||
| newY += dh - h; // bottom | |||
| newY += destination.getHeight() - h; // bottom | |||
| else if ((flags & yTop) == 0) | |||
| newY += (dh - h) / 2.0f; // centre | |||
| newY += (destination.getHeight() - h) / 2.0f; // centre | |||
| return AffineTransform::translation (-x, -y) | |||
| return AffineTransform::translation (-source.getX(), -source.getY()) | |||
| .scaled (scale, scale) | |||
| .translated (newX, newY); | |||
| } | |||
| @@ -86015,22 +86033,12 @@ void Drawable::drawAt (Graphics& g, const float x, const float y, const float op | |||
| } | |||
| void Drawable::drawWithin (Graphics& g, | |||
| const int destX, | |||
| const int destY, | |||
| const int destW, | |||
| const int destH, | |||
| const Rectangle<float>& destArea, | |||
| const RectanglePlacement& placement, | |||
| const float opacity) const | |||
| { | |||
| if (destW > 0 && destH > 0) | |||
| { | |||
| Rectangle<float> bounds (getBounds()); | |||
| draw (g, opacity, | |||
| placement.getTransformToFit (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), | |||
| (float) destX, (float) destY, | |||
| (float) destW, (float) destH)); | |||
| } | |||
| if (! destArea.isEmpty()) | |||
| draw (g, opacity, placement.getTransformToFit (getBounds(), destArea)); | |||
| } | |||
| Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes) | |||
| @@ -88190,8 +88198,8 @@ public: | |||
| const RectanglePlacement placement (placementFlags); | |||
| newState.transform | |||
| = placement.getTransformToFit (vx, vy, vw, vh, | |||
| 0.0f, 0.0f, newState.width, newState.height) | |||
| = placement.getTransformToFit (Rectangle<float> (vx, vy, vw, vh), | |||
| Rectangle<float> (0.0f, 0.0f, newState.width, newState.height)) | |||
| .followedBy (newState.transform); | |||
| } | |||
| } | |||
| @@ -249243,8 +249251,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| #define log(a) {} | |||
| #endif | |||
| #define JUCE_ASIOCALLBACK // should probably use this to define the callback type, but | |||
| // the asio header doesn't actually specify a calling convention for the functions.. | |||
| #define JUCE_ASIOCALLBACK __cdecl | |||
| #if ASIO_DEBUGGING | |||
| static void logError (const String& context, long error) | |||
| @@ -249347,40 +249354,15 @@ public: | |||
| } | |||
| } | |||
| const StringArray getOutputChannelNames() | |||
| { | |||
| return outputChannelNames; | |||
| } | |||
| const StringArray getInputChannelNames() | |||
| { | |||
| return inputChannelNames; | |||
| } | |||
| int getNumSampleRates() | |||
| { | |||
| return sampleRates.size(); | |||
| } | |||
| double getSampleRate (int index) | |||
| { | |||
| return sampleRates [index]; | |||
| } | |||
| int getNumBufferSizesAvailable() | |||
| { | |||
| return bufferSizes.size(); | |||
| } | |||
| const StringArray getOutputChannelNames() { return outputChannelNames; } | |||
| const StringArray getInputChannelNames() { return inputChannelNames; } | |||
| int getBufferSizeSamples (int index) | |||
| { | |||
| return bufferSizes [index]; | |||
| } | |||
| int getNumSampleRates() { return sampleRates.size(); } | |||
| double getSampleRate (int index) { return sampleRates [index]; } | |||
| int getDefaultBufferSize() | |||
| { | |||
| return preferredSize; | |||
| } | |||
| int getNumBufferSizesAvailable() { return bufferSizes.size(); } | |||
| int getBufferSizeSamples (int index) { return bufferSizes [index]; } | |||
| int getDefaultBufferSize() { return preferredSize; } | |||
| const String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| @@ -249834,45 +249816,18 @@ public: | |||
| } | |||
| } | |||
| bool isOpen() | |||
| { | |||
| return isOpen_ || insideControlPanelModalLoop; | |||
| } | |||
| bool isOpen() { return isOpen_ || insideControlPanelModalLoop; } | |||
| bool isPlaying() { return isASIOOpen && (currentCallback != 0); } | |||
| int getCurrentBufferSizeSamples() | |||
| { | |||
| return currentBlockSizeSamples; | |||
| } | |||
| int getCurrentBufferSizeSamples() { return currentBlockSizeSamples; } | |||
| double getCurrentSampleRate() { return currentSampleRate; } | |||
| int getCurrentBitDepth() { return currentBitDepth; } | |||
| double getCurrentSampleRate() | |||
| { | |||
| return currentSampleRate; | |||
| } | |||
| const BigInteger getActiveOutputChannels() const { return currentChansOut; } | |||
| const BigInteger getActiveInputChannels() const { return currentChansIn; } | |||
| const BigInteger getActiveOutputChannels() const | |||
| { | |||
| return currentChansOut; | |||
| } | |||
| const BigInteger getActiveInputChannels() const | |||
| { | |||
| return currentChansIn; | |||
| } | |||
| int getCurrentBitDepth() | |||
| { | |||
| return currentBitDepth; | |||
| } | |||
| int getOutputLatencyInSamples() | |||
| { | |||
| return outputLatency + currentBlockSizeSamples / 4; | |||
| } | |||
| int getInputLatencyInSamples() | |||
| { | |||
| return inputLatency + currentBlockSizeSamples / 4; | |||
| } | |||
| int getOutputLatencyInSamples() { return outputLatency + currentBlockSizeSamples / 4; } | |||
| int getInputLatencyInSamples() { return inputLatency + currentBlockSizeSamples / 4; } | |||
| void start (AudioIODeviceCallback* callback) | |||
| { | |||
| @@ -249898,20 +249853,8 @@ public: | |||
| lastCallback->audioDeviceStopped(); | |||
| } | |||
| bool isPlaying() | |||
| { | |||
| return isASIOOpen && (currentCallback != 0); | |||
| } | |||
| const String getLastError() | |||
| { | |||
| return error; | |||
| } | |||
| bool hasControlPanel() const | |||
| { | |||
| return true; | |||
| } | |||
| const String getLastError() { return error; } | |||
| bool hasControlPanel() const { return true; } | |||
| bool showControlPanel() | |||
| { | |||
| @@ -250438,7 +250381,6 @@ private: | |||
| for (i = 0; i < numActiveInputChans; ++i) | |||
| { | |||
| float* const dst = inBuffers[i]; | |||
| jassert (dst != 0); | |||
| const char* const src = (const char*) (infos[i].buffers[bi]); | |||
| @@ -250475,16 +250417,12 @@ private: | |||
| } | |||
| } | |||
| currentCallback->audioDeviceIOCallback ((const float**) inBuffers, | |||
| numActiveInputChans, | |||
| outBuffers, | |||
| numActiveOutputChans, | |||
| samps); | |||
| currentCallback->audioDeviceIOCallback ((const float**) inBuffers, numActiveInputChans, | |||
| outBuffers, numActiveOutputChans, samps); | |||
| for (i = 0; i < numActiveOutputChans; ++i) | |||
| { | |||
| float* const src = outBuffers[i]; | |||
| jassert (src != 0); | |||
| char* const dst = (char*) (infos [numActiveInputChans + i].buffers[bi]); | |||
| @@ -253757,7 +253695,8 @@ public: | |||
| width (0), | |||
| height (0), | |||
| activeUsers (0), | |||
| recordNextFrameTime (false) | |||
| recordNextFrameTime (false), | |||
| previewMaxFPS (60) | |||
| { | |||
| HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph); | |||
| if (FAILED (hr)) | |||
| @@ -253888,6 +253827,11 @@ public: | |||
| mediaControl->Stop(); | |||
| } | |||
| int getPreviewMaxFPS() const | |||
| { | |||
| return previewMaxFPS; | |||
| } | |||
| void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/) | |||
| { | |||
| if (recordNextFrameTime) | |||
| @@ -253958,13 +253902,14 @@ public: | |||
| g.drawImage (activeImage, rx, ry, rw, rh, 0, 0, width, height); | |||
| } | |||
| bool createFileCaptureFilter (const File& file) | |||
| bool createFileCaptureFilter (const File& file, int quality) | |||
| { | |||
| removeFileCaptureFilter(); | |||
| file.deleteFile(); | |||
| mediaControl->Stop(); | |||
| firstRecordedTime = Time(); | |||
| recordNextFrameTime = true; | |||
| previewMaxFPS = 60; | |||
| HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter); | |||
| @@ -253990,17 +253935,30 @@ public: | |||
| hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress()); | |||
| // This gibberish is the DirectShow profile for a video-only wmv file. | |||
| String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\"><streamconfig " | |||
| "majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" " | |||
| "bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\"><videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>" | |||
| "<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" btemporalcompression=\"1\" lsamplesize=\"0\"> <videoinfoheader " | |||
| "dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"100000\"><rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/> <rctarget " | |||
| "left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/> <bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" " | |||
| "bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" biclrused=\"0\" biclrimportant=\"0\"/> " | |||
| "</videoinfoheader></wmmediatype></streamconfig></profile>"); | |||
| String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">" | |||
| " <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\"" | |||
| " streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\"" | |||
| " bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">" | |||
| " <videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>" | |||
| " <wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\"" | |||
| " btemporalcompression=\"1\" lsamplesize=\"0\">" | |||
| " <videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">" | |||
| " <rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>" | |||
| " <rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>" | |||
| " <bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\"" | |||
| " bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\"" | |||
| " biclrused=\"0\" biclrimportant=\"0\"/>" | |||
| " </videoinfoheader>" | |||
| " </wmmediatype>" | |||
| " </streamconfig>" | |||
| "</profile>"); | |||
| const int fps[] = { 10, 15, 30 }; | |||
| const int maxFramesPerSecond = fps [quality % numElementsInArray (fps)]; | |||
| prof = prof.replace ("$WIDTH", String (width)) | |||
| .replace ("$HEIGHT", String (height)); | |||
| .replace ("$HEIGHT", String (height)) | |||
| .replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond)); | |||
| ComSmartPtr <IWMProfile> currentProfile; | |||
| hr = profileManager->LoadProfileByData ((const WCHAR*) prof, currentProfile.resetAndGetPointerAddress()); | |||
| @@ -254018,6 +253976,7 @@ public: | |||
| && ok && activeUsers > 0 | |||
| && SUCCEEDED (mediaControl->Run())) | |||
| { | |||
| previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding | |||
| return true; | |||
| } | |||
| } | |||
| @@ -254047,6 +254006,8 @@ public: | |||
| if (ok && activeUsers > 0) | |||
| mediaControl->Run(); | |||
| previewMaxFPS = 60; | |||
| } | |||
| void addListener (CameraDevice::Listener* listenerToAdd) | |||
| @@ -254086,7 +254047,7 @@ public: | |||
| { | |||
| public: | |||
| DShowCaptureViewerComp (DShowCameraDeviceInteral* const owner_) | |||
| : owner (owner_) | |||
| : owner (owner_), maxFPS (15), lastRepaintTime (0) | |||
| { | |||
| setOpaque (true); | |||
| owner->addChangeListener (this); | |||
| @@ -254123,11 +254084,22 @@ public: | |||
| void changeListenerCallback (void*) | |||
| { | |||
| repaint(); | |||
| const int64 now = Time::currentTimeMillis(); | |||
| if (now >= lastRepaintTime + (1000 / maxFPS)) | |||
| { | |||
| lastRepaintTime = now; | |||
| repaint(); | |||
| if (owner != 0) | |||
| maxFPS = owner->getPreviewMaxFPS(); | |||
| } | |||
| } | |||
| private: | |||
| DShowCameraDeviceInteral* owner; | |||
| int maxFPS; | |||
| int64 lastRepaintTime; | |||
| }; | |||
| bool ok; | |||
| @@ -254157,6 +254129,7 @@ private: | |||
| Image activeImage; | |||
| bool recordNextFrameTime; | |||
| int previewMaxFPS; | |||
| void getVideoSizes (IAMStreamConfig* const streamConfig) | |||
| { | |||
| @@ -254384,11 +254357,12 @@ const String CameraDevice::getFileExtension() | |||
| void CameraDevice::startRecordingToFile (const File& file, int quality) | |||
| { | |||
| jassert (quality >= 0 && quality <= 2); | |||
| stopRecording(); | |||
| DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal; | |||
| d->addUser(); | |||
| isRecording = d->createFileCaptureFilter (file); | |||
| isRecording = d->createFileCaptureFilter (file, quality); | |||
| } | |||
| const Time CameraDevice::getTimeOfFirstRecordedFrame() const | |||
| @@ -265301,7 +265275,7 @@ bool File::isHidden() const | |||
| static const String getIOSSystemLocation (NSSearchPathDirectory type) | |||
| { | |||
| return nsStringToJuce ([NSSearchPathForDirectoriesInDomains (type, NSUserDomainMask, YES) | |||
| objectAtIndex:0]); | |||
| objectAtIndex: 0]); | |||
| } | |||
| #endif | |||
| @@ -276241,17 +276215,12 @@ static NSArray* findDiskBurnerDevices() | |||
| NSMutableArray* results = [NSMutableArray array]; | |||
| NSArray* devs = [DRDevice devices]; | |||
| if (devs != 0) | |||
| for (int i = 0; i < [devs count]; ++i) | |||
| { | |||
| int num = [devs count]; | |||
| int i; | |||
| for (i = 0; i < num; ++i) | |||
| { | |||
| NSDictionary* dic = [[devs objectAtIndex: i] info]; | |||
| NSString* name = [dic valueForKey: DRDeviceProductNameKey]; | |||
| if (name != nil) | |||
| [results addObject: name]; | |||
| } | |||
| NSDictionary* dic = [[devs objectAtIndex: i] info]; | |||
| NSString* name = [dic valueForKey: DRDeviceProductNameKey]; | |||
| if (name != nil) | |||
| [results addObject: name]; | |||
| } | |||
| return results; | |||
| @@ -64,7 +64,7 @@ | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 1 | |||
| #define JUCE_MINOR_VERSION 52 | |||
| #define JUCE_BUILDNUMBER 77 | |||
| #define JUCE_BUILDNUMBER 78 | |||
| /** Current Juce version number. | |||
| @@ -1123,18 +1123,19 @@ inline void swapVariables (Type& variable1, Type& variable2) | |||
| int numElements = numElementsInArray (myArray) // returns 3 | |||
| @endcode | |||
| */ | |||
| template <typename Type> | |||
| inline int numElementsInArray (Type& array) | |||
| template <typename Type, int N> | |||
| inline int numElementsInArray (Type (&array)[N]) | |||
| { | |||
| (void) array; // (required to avoid a spurious warning in MS compilers) | |||
| return static_cast<int> (sizeof (array) / sizeof (0[array])); | |||
| sizeof (0[array]); // This line should cause an error if you pass an object with a user-defined subscript operator | |||
| return N; | |||
| } | |||
| // Some useful maths functions that aren't always present with all compilers and build settings. | |||
| /** Using juce_hypot and juce_hypotf is easier than dealing with all the different | |||
| versions of these functions of various platforms and compilers. */ | |||
| inline double juce_hypot (double a, double b) | |||
| inline double juce_hypot (double a, double b) throw() | |||
| { | |||
| #if JUCE_WINDOWS | |||
| return _hypot (a, b); | |||
| @@ -3665,9 +3666,7 @@ static void sortArray (ElementComparator& comparator, | |||
| { | |||
| if (comparator.compareElements (array[i], array [i + 1]) > 0) | |||
| { | |||
| const ElementType temp = array [i]; | |||
| array [i] = array[i + 1]; | |||
| array [i + 1] = temp; | |||
| swapVariables (array[i], array[i + 1]); | |||
| if (i > firstElement) | |||
| i -= 2; | |||
| @@ -3695,19 +3694,14 @@ static void sortArray (ElementComparator& comparator, | |||
| if (comparator.compareElements (array[k], array [maxIndex]) > 0) | |||
| maxIndex = k; | |||
| const ElementType temp = array [maxIndex]; | |||
| array [maxIndex] = array[j]; | |||
| array [j] = temp; | |||
| swapVariables (array[j], array[maxIndex]); | |||
| --j; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| const int mid = firstElement + (size >> 1); | |||
| ElementType temp = array [mid]; | |||
| array [mid] = array [firstElement]; | |||
| array [firstElement] = temp; | |||
| swapVariables (array[mid], array[firstElement]); | |||
| int i = firstElement; | |||
| int j = lastElement + 1; | |||
| @@ -3725,14 +3719,10 @@ static void sortArray (ElementComparator& comparator, | |||
| if (j < i) | |||
| break; | |||
| temp = array[i]; | |||
| array[i] = array[j]; | |||
| array[j] = temp; | |||
| swapVariables (array[i], array[j]); | |||
| } | |||
| temp = array [firstElement]; | |||
| array [firstElement] = array[j]; | |||
| array [j] = temp; | |||
| swapVariables (array[j], array[firstElement]); | |||
| if (j - 1 - firstElement >= lastElement - i) | |||
| { | |||
| @@ -13897,22 +13887,21 @@ public: | |||
| To improve performance, the compareElements() method can be declared as static or const. | |||
| @param comparator the comparator to use for comparing elements. | |||
| @param retainOrderOfEquivalentItems if this is true, then items | |||
| which the comparator says are equivalent will be | |||
| kept in the order in which they currently appear | |||
| in the array. This is slower to perform, but may | |||
| be important in some cases. If it's false, a faster | |||
| algorithm is used, but equivalent elements may be | |||
| rearranged. | |||
| @param undoManager optional UndoManager for storing the changes | |||
| @param retainOrderOfEquivalentItems if this is true, then items which the comparator says are | |||
| equivalent will be kept in the order in which they currently appear in the array. | |||
| This is slower to perform, but may be important in some cases. If it's false, a | |||
| faster algorithm is used, but equivalent elements may be rearranged. | |||
| */ | |||
| template <typename ElementComparator> | |||
| void sort (ElementComparator& comparator, const bool retainOrderOfEquivalentItems = false) | |||
| void sort (ElementComparator& comparator, UndoManager* undoManager, bool retainOrderOfEquivalentItems) | |||
| { | |||
| if (object != 0) | |||
| { | |||
| ReferenceCountedArray <SharedObject> sortedList (object->children); | |||
| ComparatorAdapter <ElementComparator> adapter (comparator); | |||
| object->children.sort (adapter, retainOrderOfEquivalentItems); | |||
| object->sendChildChangeMessage(); | |||
| sortedList.sort (adapter, retainOrderOfEquivalentItems); | |||
| object->reorderChildren (sortedList, undoManager); | |||
| } | |||
| } | |||
| @@ -13964,6 +13953,7 @@ private: | |||
| void removeChild (int childIndex, UndoManager*); | |||
| void removeAllChildren (UndoManager*); | |||
| void moveChild (int currentIndex, int newIndex, UndoManager*); | |||
| void reorderChildren (const ReferenceCountedArray <SharedObject>& newOrder, UndoManager*); | |||
| bool isEquivalentTo (const SharedObject& other) const; | |||
| XmlElement* createXml() const; | |||
| @@ -24392,14 +24382,8 @@ public: | |||
| /** Returns the transform that should be applied to these source co-ordinates to fit them | |||
| into the destination rectangle using the current flags. | |||
| */ | |||
| const AffineTransform getTransformToFit (float sourceX, | |||
| float sourceY, | |||
| float sourceW, | |||
| float sourceH, | |||
| float destinationX, | |||
| float destinationY, | |||
| float destinationW, | |||
| float destinationH) const throw(); | |||
| const AffineTransform getTransformToFit (const Rectangle<float>& source, | |||
| const Rectangle<float>& destination) const throw(); | |||
| private: | |||
| @@ -43224,6 +43208,9 @@ private: | |||
| just call the post() method to send them, and when they arrive, your | |||
| messageCallback() method will automatically be invoked. | |||
| Always create an instance of a CallbackMessage on the heap, as it will be | |||
| deleted automatically after the message has been delivered. | |||
| @see MessageListener, MessageManager, ActionListener, ChangeListener | |||
| */ | |||
| class JUCE_API CallbackMessage : public Message | |||
| @@ -44527,19 +44514,13 @@ public: | |||
| and can either be made as big as possible, or just reduced to fit. | |||
| @param g the graphics context to render onto | |||
| @param destX top-left of the target rectangle to fit it into | |||
| @param destY top-left of the target rectangle to fit it into | |||
| @param destWidth size of the target rectangle to fit the image into | |||
| @param destHeight size of the target rectangle to fit the image into | |||
| @param destArea the target rectangle to fit the drawable into | |||
| @param placement defines the alignment and rescaling to use to fit | |||
| this object within the target rectangle. | |||
| @param opacity the opacity to use, in the range 0 to 1.0 | |||
| */ | |||
| void drawWithin (Graphics& g, | |||
| int destX, | |||
| int destY, | |||
| int destWidth, | |||
| int destHeight, | |||
| const Rectangle<float>& destArea, | |||
| const RectanglePlacement& placement, | |||
| float opacity) const; | |||
| @@ -172,11 +172,12 @@ inline void swapVariables (Type& variable1, Type& variable2) | |||
| int numElements = numElementsInArray (myArray) // returns 3 | |||
| @endcode | |||
| */ | |||
| template <typename Type> | |||
| inline int numElementsInArray (Type& array) | |||
| template <typename Type, int N> | |||
| inline int numElementsInArray (Type (&array)[N]) | |||
| { | |||
| (void) array; // (required to avoid a spurious warning in MS compilers) | |||
| return static_cast<int> (sizeof (array) / sizeof (0[array])); | |||
| sizeof (0[array]); // This line should cause an error if you pass an object with a user-defined subscript operator | |||
| return N; | |||
| } | |||
| //============================================================================== | |||
| @@ -184,7 +185,7 @@ inline int numElementsInArray (Type& array) | |||
| /** Using juce_hypot and juce_hypotf is easier than dealing with all the different | |||
| versions of these functions of various platforms and compilers. */ | |||
| inline double juce_hypot (double a, double b) | |||
| inline double juce_hypot (double a, double b) throw() | |||
| { | |||
| #if JUCE_WINDOWS | |||
| return _hypot (a, b); | |||
| @@ -69,22 +69,12 @@ void Drawable::drawAt (Graphics& g, const float x, const float y, const float op | |||
| } | |||
| void Drawable::drawWithin (Graphics& g, | |||
| const int destX, | |||
| const int destY, | |||
| const int destW, | |||
| const int destH, | |||
| const Rectangle<float>& destArea, | |||
| const RectanglePlacement& placement, | |||
| const float opacity) const | |||
| { | |||
| if (destW > 0 && destH > 0) | |||
| { | |||
| Rectangle<float> bounds (getBounds()); | |||
| draw (g, opacity, | |||
| placement.getTransformToFit (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), | |||
| (float) destX, (float) destY, | |||
| (float) destW, (float) destH)); | |||
| } | |||
| if (! destArea.isEmpty()) | |||
| draw (g, opacity, placement.getTransformToFit (getBounds(), destArea)); | |||
| } | |||
| //============================================================================== | |||
| @@ -38,8 +38,7 @@ | |||
| #define log(a) {} | |||
| #endif | |||
| #define JUCE_ASIOCALLBACK // should probably use this to define the callback type, but | |||
| // the asio header doesn't actually specify a calling convention for the functions.. | |||
| #define JUCE_ASIOCALLBACK __cdecl | |||
| //============================================================================== | |||
| #if ASIO_DEBUGGING | |||
| @@ -146,40 +145,15 @@ public: | |||
| } | |||
| } | |||
| const StringArray getOutputChannelNames() | |||
| { | |||
| return outputChannelNames; | |||
| } | |||
| const StringArray getOutputChannelNames() { return outputChannelNames; } | |||
| const StringArray getInputChannelNames() { return inputChannelNames; } | |||
| const StringArray getInputChannelNames() | |||
| { | |||
| return inputChannelNames; | |||
| } | |||
| int getNumSampleRates() { return sampleRates.size(); } | |||
| double getSampleRate (int index) { return sampleRates [index]; } | |||
| int getNumSampleRates() | |||
| { | |||
| return sampleRates.size(); | |||
| } | |||
| double getSampleRate (int index) | |||
| { | |||
| return sampleRates [index]; | |||
| } | |||
| int getNumBufferSizesAvailable() | |||
| { | |||
| return bufferSizes.size(); | |||
| } | |||
| int getBufferSizeSamples (int index) | |||
| { | |||
| return bufferSizes [index]; | |||
| } | |||
| int getDefaultBufferSize() | |||
| { | |||
| return preferredSize; | |||
| } | |||
| int getNumBufferSizesAvailable() { return bufferSizes.size(); } | |||
| int getBufferSizeSamples (int index) { return bufferSizes [index]; } | |||
| int getDefaultBufferSize() { return preferredSize; } | |||
| const String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| @@ -633,45 +607,18 @@ public: | |||
| } | |||
| } | |||
| bool isOpen() | |||
| { | |||
| return isOpen_ || insideControlPanelModalLoop; | |||
| } | |||
| int getCurrentBufferSizeSamples() | |||
| { | |||
| return currentBlockSizeSamples; | |||
| } | |||
| double getCurrentSampleRate() | |||
| { | |||
| return currentSampleRate; | |||
| } | |||
| const BigInteger getActiveOutputChannels() const | |||
| { | |||
| return currentChansOut; | |||
| } | |||
| bool isOpen() { return isOpen_ || insideControlPanelModalLoop; } | |||
| bool isPlaying() { return isASIOOpen && (currentCallback != 0); } | |||
| const BigInteger getActiveInputChannels() const | |||
| { | |||
| return currentChansIn; | |||
| } | |||
| int getCurrentBufferSizeSamples() { return currentBlockSizeSamples; } | |||
| double getCurrentSampleRate() { return currentSampleRate; } | |||
| int getCurrentBitDepth() { return currentBitDepth; } | |||
| int getCurrentBitDepth() | |||
| { | |||
| return currentBitDepth; | |||
| } | |||
| int getOutputLatencyInSamples() | |||
| { | |||
| return outputLatency + currentBlockSizeSamples / 4; | |||
| } | |||
| const BigInteger getActiveOutputChannels() const { return currentChansOut; } | |||
| const BigInteger getActiveInputChannels() const { return currentChansIn; } | |||
| int getInputLatencyInSamples() | |||
| { | |||
| return inputLatency + currentBlockSizeSamples / 4; | |||
| } | |||
| int getOutputLatencyInSamples() { return outputLatency + currentBlockSizeSamples / 4; } | |||
| int getInputLatencyInSamples() { return inputLatency + currentBlockSizeSamples / 4; } | |||
| void start (AudioIODeviceCallback* callback) | |||
| { | |||
| @@ -697,20 +644,8 @@ public: | |||
| lastCallback->audioDeviceStopped(); | |||
| } | |||
| bool isPlaying() | |||
| { | |||
| return isASIOOpen && (currentCallback != 0); | |||
| } | |||
| const String getLastError() | |||
| { | |||
| return error; | |||
| } | |||
| bool hasControlPanel() const | |||
| { | |||
| return true; | |||
| } | |||
| const String getLastError() { return error; } | |||
| bool hasControlPanel() const { return true; } | |||
| bool showControlPanel() | |||
| { | |||
| @@ -840,7 +775,6 @@ private: | |||
| bool volatile insideControlPanelModalLoop; | |||
| bool volatile shouldUsePreferredSize; | |||
| //============================================================================== | |||
| void removeCurrentDriver() | |||
| { | |||
| @@ -1242,7 +1176,6 @@ private: | |||
| for (i = 0; i < numActiveInputChans; ++i) | |||
| { | |||
| float* const dst = inBuffers[i]; | |||
| jassert (dst != 0); | |||
| const char* const src = (const char*) (infos[i].buffers[bi]); | |||
| @@ -1279,16 +1212,12 @@ private: | |||
| } | |||
| } | |||
| currentCallback->audioDeviceIOCallback ((const float**) inBuffers, | |||
| numActiveInputChans, | |||
| outBuffers, | |||
| numActiveOutputChans, | |||
| samps); | |||
| currentCallback->audioDeviceIOCallback ((const float**) inBuffers, numActiveInputChans, | |||
| outBuffers, numActiveOutputChans, samps); | |||
| for (i = 0; i < numActiveOutputChans; ++i) | |||
| { | |||
| float* const src = outBuffers[i]; | |||
| jassert (src != 0); | |||
| char* const dst = (char*) (infos [numActiveInputChans + i].buffers[bi]); | |||
| @@ -45,7 +45,8 @@ public: | |||
| width (0), | |||
| height (0), | |||
| activeUsers (0), | |||
| recordNextFrameTime (false) | |||
| recordNextFrameTime (false), | |||
| previewMaxFPS (60) | |||
| { | |||
| HRESULT hr = graphBuilder.CoCreateInstance (CLSID_FilterGraph); | |||
| if (FAILED (hr)) | |||
| @@ -176,6 +177,11 @@ public: | |||
| mediaControl->Stop(); | |||
| } | |||
| int getPreviewMaxFPS() const | |||
| { | |||
| return previewMaxFPS; | |||
| } | |||
| void handleFrame (double /*time*/, BYTE* buffer, long /*bufferSize*/) | |||
| { | |||
| if (recordNextFrameTime) | |||
| @@ -246,13 +252,14 @@ public: | |||
| g.drawImage (activeImage, rx, ry, rw, rh, 0, 0, width, height); | |||
| } | |||
| bool createFileCaptureFilter (const File& file) | |||
| bool createFileCaptureFilter (const File& file, int quality) | |||
| { | |||
| removeFileCaptureFilter(); | |||
| file.deleteFile(); | |||
| mediaControl->Stop(); | |||
| firstRecordedTime = Time(); | |||
| recordNextFrameTime = true; | |||
| previewMaxFPS = 60; | |||
| HRESULT hr = asfWriter.CoCreateInstance (CLSID_WMAsfWriter); | |||
| @@ -278,17 +285,30 @@ public: | |||
| hr = WMCreateProfileManager (profileManager.resetAndGetPointerAddress()); | |||
| // This gibberish is the DirectShow profile for a video-only wmv file. | |||
| String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\"><streamconfig " | |||
| "majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\" streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\" " | |||
| "bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\"><videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>" | |||
| "<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\" btemporalcompression=\"1\" lsamplesize=\"0\"> <videoinfoheader " | |||
| "dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"100000\"><rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/> <rctarget " | |||
| "left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/> <bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\" " | |||
| "bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\" biclrused=\"0\" biclrimportant=\"0\"/> " | |||
| "</videoinfoheader></wmmediatype></streamconfig></profile>"); | |||
| String prof ("<profile version=\"589824\" storageformat=\"1\" name=\"Quality\" description=\"Quality type for output.\">" | |||
| " <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" streamnumber=\"1\"" | |||
| " streamname=\"Video Stream\" inputname=\"Video409\" bitrate=\"894960\"" | |||
| " bufferwindow=\"0\" reliabletransport=\"1\" decodercomplexity=\"AU\" rfc1766langid=\"en-us\">" | |||
| " <videomediaprops maxkeyframespacing=\"50000000\" quality=\"90\"/>" | |||
| " <wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" bfixedsizesamples=\"0\"" | |||
| " btemporalcompression=\"1\" lsamplesize=\"0\">" | |||
| " <videoinfoheader dwbitrate=\"894960\" dwbiterrorrate=\"0\" avgtimeperframe=\"$AVGTIMEPERFRAME\">" | |||
| " <rcsource left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>" | |||
| " <rctarget left=\"0\" top=\"0\" right=\"$WIDTH\" bottom=\"$HEIGHT\"/>" | |||
| " <bitmapinfoheader biwidth=\"$WIDTH\" biheight=\"$HEIGHT\" biplanes=\"1\" bibitcount=\"24\"" | |||
| " bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" biypelspermeter=\"0\"" | |||
| " biclrused=\"0\" biclrimportant=\"0\"/>" | |||
| " </videoinfoheader>" | |||
| " </wmmediatype>" | |||
| " </streamconfig>" | |||
| "</profile>"); | |||
| const int fps[] = { 10, 15, 30 }; | |||
| const int maxFramesPerSecond = fps [quality % numElementsInArray (fps)]; | |||
| prof = prof.replace ("$WIDTH", String (width)) | |||
| .replace ("$HEIGHT", String (height)); | |||
| .replace ("$HEIGHT", String (height)) | |||
| .replace ("$AVGTIMEPERFRAME", String (10000000 / maxFramesPerSecond)); | |||
| ComSmartPtr <IWMProfile> currentProfile; | |||
| hr = profileManager->LoadProfileByData ((const WCHAR*) prof, currentProfile.resetAndGetPointerAddress()); | |||
| @@ -306,6 +326,7 @@ public: | |||
| && ok && activeUsers > 0 | |||
| && SUCCEEDED (mediaControl->Run())) | |||
| { | |||
| previewMaxFPS = (quality < 2) ? 15 : 25; // throttle back the preview comps to try to leave the cpu free for encoding | |||
| return true; | |||
| } | |||
| } | |||
| @@ -335,6 +356,8 @@ public: | |||
| if (ok && activeUsers > 0) | |||
| mediaControl->Run(); | |||
| previewMaxFPS = 60; | |||
| } | |||
| //============================================================================== | |||
| @@ -377,7 +400,7 @@ public: | |||
| { | |||
| public: | |||
| DShowCaptureViewerComp (DShowCameraDeviceInteral* const owner_) | |||
| : owner (owner_) | |||
| : owner (owner_), maxFPS (15), lastRepaintTime (0) | |||
| { | |||
| setOpaque (true); | |||
| owner->addChangeListener (this); | |||
| @@ -414,11 +437,22 @@ public: | |||
| void changeListenerCallback (void*) | |||
| { | |||
| repaint(); | |||
| const int64 now = Time::currentTimeMillis(); | |||
| if (now >= lastRepaintTime + (1000 / maxFPS)) | |||
| { | |||
| lastRepaintTime = now; | |||
| repaint(); | |||
| if (owner != 0) | |||
| maxFPS = owner->getPreviewMaxFPS(); | |||
| } | |||
| } | |||
| private: | |||
| DShowCameraDeviceInteral* owner; | |||
| int maxFPS; | |||
| int64 lastRepaintTime; | |||
| }; | |||
| //============================================================================== | |||
| @@ -449,6 +483,7 @@ private: | |||
| Image activeImage; | |||
| bool recordNextFrameTime; | |||
| int previewMaxFPS; | |||
| void getVideoSizes (IAMStreamConfig* const streamConfig) | |||
| { | |||
| @@ -681,11 +716,12 @@ const String CameraDevice::getFileExtension() | |||
| void CameraDevice::startRecordingToFile (const File& file, int quality) | |||
| { | |||
| jassert (quality >= 0 && quality <= 2); | |||
| stopRecording(); | |||
| DShowCameraDeviceInteral* const d = (DShowCameraDeviceInteral*) internal; | |||
| d->addUser(); | |||
| isRecording = d->createFileCaptureFilter (file); | |||
| isRecording = d->createFileCaptureFilter (file, quality); | |||
| } | |||
| const Time CameraDevice::getTimeOfFirstRecordedFrame() const | |||