From 82d620b1d9b6cc9c97d200c4d75de92950e49de1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sun, 23 Mar 2014 11:26:27 -0700 Subject: [PATCH] add support for AudioDevice scripting interface, and switching audio input/output device on the fly --- interface/src/Application.cpp | 2 + interface/src/Audio.cpp | 123 ++++++++++++++---- interface/src/Audio.h | 13 +- .../AudioDeviceScriptingInterface.cpp | 44 +++++++ .../scripting/AudioDeviceScriptingInterface.h | 31 +++++ 5 files changed, 190 insertions(+), 23 deletions(-) create mode 100644 interface/src/scripting/AudioDeviceScriptingInterface.cpp create mode 100644 interface/src/scripting/AudioDeviceScriptingInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8a8a1961ec..966f4eb2ea 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -72,6 +72,7 @@ #include "devices/TV3DManager.h" #include "renderer/ProgramObject.h" +#include "scripting/AudioDeviceScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" @@ -3552,6 +3553,7 @@ void Application::loadScript(const QString& fileNameString) { scriptEngine->registerGlobalObject("Overlays", &_overlays); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); QThread* workerThread = new QThread(this); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index b684cec46e..44432fcc94 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -92,6 +92,16 @@ void Audio::reset() { _ringBuffer.reset(); } +QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { + QAudioDeviceInfo result; + foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) { + if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { + result = audioDevice; + } + } + return result; +} + QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #ifdef __APPLE__ if (QAudioDeviceInfo::availableDevices(mode).size() > 1) { @@ -249,27 +259,92 @@ void Audio::start() { _desiredOutputFormat.setChannelCount(2); QAudioDeviceInfo inputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioInput); - qDebug() << "The audio input device is" << inputDeviceInfo.deviceName(); + qDebug() << "The default audio input device is" << inputDeviceInfo.deviceName(); + bool inputFormatSupported = switchInputToAudioDevice(inputDeviceInfo.deviceName()); + + QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); + qDebug() << "The default audio output device is" << outputDeviceInfo.deviceName(); + bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo.deviceName()); - if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { - qDebug() << "The format to be used for audio input is" << _inputFormat; + if (!inputFormatSupported || !outputFormatSupported) { + qDebug() << "Unable to set up audio I/O because of a problem with input or output formats."; + } +} + +bool Audio::switchInputToAudioDevice(const QString& inputDeviceName) { + bool supportedFormat = false; + + // cleanup any previously initialized device + if (_audioInput) { + _audioInput->stop(); + disconnect(_inputDevice, 0, 0, 0); + _inputDevice = NULL; + + delete _audioInput; + _audioInput = NULL; + _numInputCallbackBytes = 0; + + _inputAudioDeviceName = ""; + } + + QAudioDeviceInfo inputDeviceInfo = getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName); + + if (!inputDeviceInfo.isNull()) { + qDebug() << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; + _inputAudioDeviceName = inputDeviceInfo.deviceName().trimmed(); + + if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { + qDebug() << "The format to be used for audio input is" << _inputFormat; - _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); - _numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount() - * (_inputFormat.sampleRate() / SAMPLE_RATE) - / CALLBACK_ACCELERATOR_RATIO; - _audioInput->setBufferSize(_numInputCallbackBytes); + _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); + _numInputCallbackBytes = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL * _inputFormat.channelCount() + * (_inputFormat.sampleRate() / SAMPLE_RATE) + / CALLBACK_ACCELERATOR_RATIO; + _audioInput->setBufferSize(_numInputCallbackBytes); - QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); - qDebug() << "The audio output device is" << outputDeviceInfo.deviceName(); - - if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { - qDebug() << "The format to be used for audio output is" << _outputFormat; - + // how do we want to handle input working, but output not working? _inputRingBuffer.resizeForFrameSize(_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / sizeof(int16_t)); _inputDevice = _audioInput->start(); connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput())); + supportedFormat = true; + } + } + return supportedFormat; +} + +bool Audio::switchOutputToAudioDevice(const QString& outputDeviceName) { + bool supportedFormat = false; + + // cleanup any previously initialized device + if (_audioOutput) { + _audioOutput->stop(); + disconnect(_outputDevice, 0, 0, 0); + _outputDevice = NULL; + + delete _audioOutput; + _audioOutput = NULL; + _numInputCallbackBytes = 0; + + _loopbackOutputDevice = NULL; + delete _loopbackAudioOutput; + _loopbackAudioOutput = NULL; + + _proceduralOutputDevice = NULL; + delete _proceduralAudioOutput; + _proceduralAudioOutput = NULL; + _outputAudioDeviceName = ""; + } + + QAudioDeviceInfo outputDeviceInfo = getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName); + + if (!outputDeviceInfo.isNull()) { + qDebug() << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; + _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); + + if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { + qDebug() << "The format to be used for audio output is" << _outputFormat; + // setup our general output device for audio-mixer audio _audioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); _audioOutput->setBufferSize(_ringBuffer.getSampleCapacity() * sizeof(int16_t)); @@ -278,17 +353,15 @@ void Audio::start() { // setup a loopback audio output device _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - + // setup a procedural audio output device _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); gettimeofday(&_lastReceiveTime, NULL); + supportedFormat = true; } - - return; } - - qDebug() << "Unable to set up audio I/O because of a problem with input or output formats."; + return supportedFormat; } void Audio::handleAudioInput() { @@ -315,7 +388,9 @@ void Audio::handleAudioInput() { } if (_inputFormat == _outputFormat) { - _loopbackOutputDevice->write(inputByteArray); + if (_loopbackOutputDevice) { + _loopbackOutputDevice->write(inputByteArray); + } } else { static float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) * (_outputFormat.channelCount() / _inputFormat.channelCount()); @@ -326,7 +401,9 @@ void Audio::handleAudioInput() { inputByteArray.size() / sizeof(int16_t), loopBackByteArray.size() / sizeof(int16_t), _inputFormat, _outputFormat); - _loopbackOutputDevice->write(loopBackByteArray); + if (_loopbackOutputDevice) { + _loopbackOutputDevice->write(loopBackByteArray); + } } } @@ -469,7 +546,9 @@ void Audio::handleAudioInput() { NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4, _desiredInputFormat, _outputFormat); - _proceduralOutputDevice->write(proceduralOutput); + if (_proceduralOutputDevice) { + _proceduralOutputDevice->write(proceduralOutput); + } NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 6c2fbab204..0b986cc935 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -72,7 +72,7 @@ public: int getNetworkSampleRate() { return SAMPLE_RATE; } int getNetworkBufferLengthSamplesPerChannel() { return NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } - + public slots: void start(); void addReceivedAudioToBuffer(const QByteArray& audioByteArray); @@ -83,10 +83,18 @@ public slots: virtual void handleAudioByteArray(const QByteArray& audioByteArray); + bool switchInputToAudioDevice(const QString& inputDeviceName); + bool switchOutputToAudioDevice(const QString& outputDeviceName); + + QString getInputAudioDeviceName() const { return _inputAudioDeviceName; } + QString getOutputAudioDeviceName() const { return _outputAudioDeviceName; } + + signals: bool muteToggled(); private: + QByteArray firstInputFrame; QAudioInput* _audioInput; QAudioFormat _desiredInputFormat; @@ -105,6 +113,9 @@ private: QIODevice* _proceduralOutputDevice; AudioRingBuffer _inputRingBuffer; AudioRingBuffer _ringBuffer; + + QString _inputAudioDeviceName; + QString _outputAudioDeviceName; Oscilloscope* _scope; StDev _stdev; diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp new file mode 100644 index 0000000000..1d147a4a0c --- /dev/null +++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp @@ -0,0 +1,44 @@ +// +// AudioDeviceScriptingInterface.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 3/23/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include "Application.h" +#include "AudioDeviceScriptingInterface.h" + + +AudioDeviceScriptingInterface* AudioDeviceScriptingInterface::getInstance() { + static AudioDeviceScriptingInterface sharedInstance; + return &sharedInstance; +} + +bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { + bool result; + QMetaObject::invokeMethod(Application::getInstance()->getAudio(), "switchInputToAudioDevice", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, deviceName)); + + return result; +} + +bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) { + bool result; + QMetaObject::invokeMethod(Application::getInstance()->getAudio(), "switchOutputToAudioDevice", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, deviceName)); + + return result; +} + +QString AudioDeviceScriptingInterface::getInputDevice() { + return Application::getInstance()->getAudio()->getInputAudioDeviceName(); +} + +QString AudioDeviceScriptingInterface::getOutputDevice() { + return Application::getInstance()->getAudio()->getOutputAudioDeviceName(); +} diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.h b/interface/src/scripting/AudioDeviceScriptingInterface.h new file mode 100644 index 0000000000..a4352d9758 --- /dev/null +++ b/interface/src/scripting/AudioDeviceScriptingInterface.h @@ -0,0 +1,31 @@ +// +// AudioDeviceScriptingInterface.h +// hifi +// +// Created by Brad Hefta-Gaub on 3/22/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AudioDeviceScriptingInterface__ +#define __hifi__AudioDeviceScriptingInterface__ + +#include +#include +#include + +#include "Application.h" + +class AudioDeviceScriptingInterface : public QObject { + Q_OBJECT + AudioDeviceScriptingInterface() { }; +public: + static AudioDeviceScriptingInterface* getInstance(); + +public slots: + bool setInputDevice(const QString& deviceName); + bool setOutputDevice(const QString& deviceName); + QString getInputDevice(); + QString getOutputDevice(); +}; + +#endif /* defined(__hifi__AudioDeviceScriptingInterface__) */