From 71aca694103f5cd28c8a9b3f85f52f53b8ad41e1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 5 Dec 2013 11:36:45 -0800 Subject: [PATCH] fix rendered scope for new Qt Audio --- assignment-client/src/AssignmentClient.cpp | 4 +- interface/src/Application.cpp | 2 +- interface/src/Audio.cpp | 54 ++++++++++------ interface/src/Audio.h | 2 +- interface/src/Oscilloscope.cpp | 74 +++++++++++++--------- interface/src/Oscilloscope.h | 10 +-- 6 files changed, 90 insertions(+), 56 deletions(-) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index fff24a51a3..17b1b6099e 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -78,8 +78,8 @@ void AssignmentClient::readPendingDatagrams() { if (_currentAssignment) { // have the threaded current assignment handle this datagram QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection, - Q_ARG(const QByteArray&, QByteArray((char*) packetData, receivedBytes)), - Q_ARG(const HifiSockAddr&, senderSockAddr)); + Q_ARG(QByteArray, QByteArray((char*) packetData, receivedBytes)), + Q_ARG(HifiSockAddr, senderSockAddr)); } else if (packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT || packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) { if (_currentAssignment) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1a61f5b08f..89369cb93b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,7 +133,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _lookatIndicatorScale(1.0f), _perfStatsOn(false), _chatEntryOn(false), - _audio(STARTUP_JITTER_SAMPLES), + _audio(&_audioScope, STARTUP_JITTER_SAMPLES), _stopNetworkReceiveThread(false), _voxelProcessor(), _voxelHideShowThread(&_voxels), diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index df8e2d253f..1fd91bfd80 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -41,12 +41,16 @@ static const int ICON_SIZE = 24; static const int ICON_LEFT = 20; static const int BOTTOM_PADDING = 110; -Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : +Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent) : QObject(parent), + _audioInput(NULL), _inputDevice(NULL), + _audioOutput(NULL), + _outputDevice(NULL), _isBufferSendCallback(false), _nextOutputSamples(NULL), _ringBuffer(true), + _scope(scope), _averagedLatency(0.0), _measuredJitter(0), _jitterBufferSamples(initialJitterBufferSamples), @@ -124,6 +128,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { const int QT_SAMPLE_RATE = 44100; const int SAMPLE_RATE_RATIO = QT_SAMPLE_RATE / SAMPLE_RATE; const int CALLBACK_ACCELERATOR_RATIO = 2; +const int CALLBACK_IO_BUFFER_SIZE = BUFFER_LENGTH_BYTES_STEREO * SAMPLE_RATE_RATIO / CALLBACK_ACCELERATOR_RATIO; void Audio::start() { @@ -147,7 +152,7 @@ void Audio::start() { } _audioInput = new QAudioInput(inputAudioDevice, audioFormat, this); - _audioInput->setBufferSize(BUFFER_LENGTH_BYTES_STEREO * SAMPLE_RATE_RATIO / CALLBACK_ACCELERATOR_RATIO); + _audioInput->setBufferSize(CALLBACK_IO_BUFFER_SIZE); _inputDevice = _audioInput->start(); connect(_inputDevice, SIGNAL(readyRead()), SLOT(handleAudioInput())); @@ -164,15 +169,14 @@ void Audio::start() { } _audioOutput = new QAudioOutput(outputDeviceInfo, audioFormat, this); - _audioOutput->setBufferSize(BUFFER_LENGTH_BYTES_STEREO * SAMPLE_RATE_RATIO / CALLBACK_ACCELERATOR_RATIO); + _audioOutput->setBufferSize(CALLBACK_IO_BUFFER_SIZE); _outputDevice = _audioOutput->start(); gettimeofday(&_lastReceiveTime, NULL); } void Audio::handleAudioInput() { - static int16_t stereoInputBuffer[BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 2 * SAMPLE_RATE_RATIO]; - static int16_t stereoOutputBuffer[BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 2 * SAMPLE_RATE_RATIO / CALLBACK_ACCELERATOR_RATIO]; + static int16_t stereoInputBuffer[CALLBACK_IO_BUFFER_SIZE * 2]; static char monoAudioDataPacket[MAX_PACKET_SIZE]; static int bufferSizeSamples = _audioInput->bufferSize() / sizeof(int16_t); @@ -180,36 +184,45 @@ void Audio::handleAudioInput() { static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + NUM_BYTES_RFC4122_UUID; static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes); + QByteArray inputByteArray = _inputDevice->read(CALLBACK_IO_BUFFER_SIZE); + if (_isBufferSendCallback) { // this is the second half of a full buffer of data // zero out the monoAudioSamples array memset(monoAudioSamples, 0, BUFFER_LENGTH_BYTES_PER_CHANNEL); - // read out the current samples from the _inputDevice - _inputDevice->read((char*) (stereoInputBuffer + bufferSizeSamples), sizeof(stereoInputBuffer) / 2); + // copy samples from the inputByteArray to the stereoInputBuffer + memcpy((char*) (stereoInputBuffer + bufferSizeSamples), inputByteArray.data(), inputByteArray.size()); + } else { // take samples we have in this callback and store them in the first half of the static buffer // to send off in the next callback - _inputDevice->read((char*) stereoInputBuffer, sizeof(stereoInputBuffer) / 2); + memcpy((char*) stereoInputBuffer, inputByteArray.data(), inputByteArray.size()); } + // add input data just written to the scope + QMetaObject::invokeMethod(_scope, "addStereoSamples", Qt::QueuedConnection, + Q_ARG(QByteArray, inputByteArray), Q_ARG(bool, true)); + + QByteArray stereoOutputBuffer; + if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted) { // if local loopback enabled, copy input to output if (_isBufferSendCallback) { - memcpy(stereoOutputBuffer, stereoInputBuffer + bufferSizeSamples, sizeof(stereoOutputBuffer)); + stereoOutputBuffer.append((char*) (stereoInputBuffer + bufferSizeSamples), CALLBACK_IO_BUFFER_SIZE); } else { - memcpy(stereoOutputBuffer, stereoInputBuffer, sizeof(stereoOutputBuffer)); + stereoOutputBuffer.append((char*) stereoInputBuffer, CALLBACK_IO_BUFFER_SIZE); } } else { // zero out the stereoOutputBuffer - memset(stereoOutputBuffer, 0, sizeof(stereoOutputBuffer)); + stereoOutputBuffer = QByteArray(CALLBACK_IO_BUFFER_SIZE, 0); } // add procedural effects to the appropriate input samples addProceduralSounds(monoAudioSamples + (_isBufferSendCallback ? BUFFER_LENGTH_SAMPLES_PER_CHANNEL / CALLBACK_ACCELERATOR_RATIO : 0), - stereoOutputBuffer, + (int16_t*) stereoOutputBuffer.data(), BUFFER_LENGTH_SAMPLES_PER_CHANNEL / CALLBACK_ACCELERATOR_RATIO); if (_isBufferSendCallback) { @@ -300,16 +313,19 @@ void Audio::handleAudioInput() { } if (_nextOutputSamples) { + + int16_t* stereoOutputBufferSamples = (int16_t*) stereoOutputBuffer.data(); + // play whatever we have in the audio buffer for (int s = 0; s < PACKET_LENGTH_SAMPLES_PER_CHANNEL / CALLBACK_ACCELERATOR_RATIO; s++) { int16_t leftSample = _nextOutputSamples[s]; int16_t rightSample = _nextOutputSamples[s + PACKET_LENGTH_SAMPLES_PER_CHANNEL]; - stereoOutputBuffer[(s * 4)] += leftSample; - stereoOutputBuffer[(s * 4) + 2] += leftSample; + stereoOutputBufferSamples[(s * 4)] += leftSample; + stereoOutputBufferSamples[(s * 4) + 2] += leftSample; - stereoOutputBuffer[(s * 4) + 1] += rightSample; - stereoOutputBuffer[(s * 4) + 3] += rightSample; + stereoOutputBufferSamples[(s * 4) + 1] += rightSample; + stereoOutputBufferSamples[(s * 4) + 3] += rightSample; } if (_isBufferSendCallback) { @@ -325,11 +341,11 @@ void Audio::handleAudioInput() { } } - _outputDevice->write((char*) stereoOutputBuffer, sizeof(stereoOutputBuffer)); + _outputDevice->write(stereoOutputBuffer); // add output (@speakers) data just written to the scope -// _scope->addSamples(1, outputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); -// _scope->addSamples(2, outputRight, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + QMetaObject::invokeMethod(_scope, "addStereoSamples", Qt::QueuedConnection, + Q_ARG(QByteArray, stereoOutputBuffer), Q_ARG(bool, false)); _isBufferSendCallback = !_isBufferSendCallback; diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 41b6c21f8f..7730a937c3 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -38,7 +38,7 @@ class Audio : public QObject { Q_OBJECT public: // setup for audio I/O - Audio(int16_t initialJitterBufferSamples, QObject* parent = 0); + Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent = 0); void render(int screenWidth, int screenHeight); diff --git a/interface/src/Oscilloscope.cpp b/interface/src/Oscilloscope.cpp index ebaddbbb04..2c65f2a07e 100644 --- a/interface/src/Oscilloscope.cpp +++ b/interface/src/Oscilloscope.cpp @@ -6,13 +6,16 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include "Oscilloscope.h" - -#include "InterfaceConfig.h" #include #include #include +#include + +#include "InterfaceConfig.h" + +#include "Oscilloscope.h" + // Reimplemented 4/26/13 (tosh) - don't blame Philip for bugs using namespace std; @@ -65,37 +68,50 @@ Oscilloscope::~Oscilloscope() { delete[] _samples; } -void Oscilloscope::addSamples(unsigned ch, short const* data, unsigned n) { +void Oscilloscope::addStereoSamples(const QByteArray& audioByteArray, bool isInput) { if (! enabled || inputPaused) { return; } - - // determine start/end offset of this channel's region - unsigned baseOffs = MAX_SAMPLES_PER_CHANNEL * ch; - unsigned endOffs = baseOffs + MAX_SAMPLES_PER_CHANNEL; - - // fetch write position for this channel - unsigned writePos = _writePos[ch]; - - // determine write position after adding the samples - unsigned newWritePos = writePos + n; - unsigned n2 = 0; - if (newWritePos >= endOffs) { - // passed boundary of the circular buffer? -> we need to copy two blocks - n2 = newWritePos - endOffs; - newWritePos = baseOffs + n2; - n -= n2; + + unsigned int numSamplesPerChannel = audioByteArray.size() / (sizeof(int16_t) * 2); + int16_t samples[numSamplesPerChannel]; + const int16_t* stereoSamples = (int16_t*) audioByteArray.constData(); + + for (int channel = 0; channel < (isInput ? 1 : 2); channel++) { + // add samples for each of the channels + + // enumerate the interleaved stereoSamples array and pull out the samples for this channel + for (int i = 0; i < audioByteArray.size() / sizeof(int16_t); i += 2) { + samples[i / 2] = stereoSamples[i + channel]; + } + + // determine start/end offset of this channel's region + unsigned baseOffs = MAX_SAMPLES_PER_CHANNEL * (channel + !isInput); + unsigned endOffs = baseOffs + MAX_SAMPLES_PER_CHANNEL; + + // fetch write position for this channel + unsigned writePos = _writePos[channel + !isInput]; + + // determine write position after adding the samples + unsigned newWritePos = writePos + numSamplesPerChannel; + unsigned n2 = 0; + if (newWritePos >= endOffs) { + // passed boundary of the circular buffer? -> we need to copy two blocks + n2 = newWritePos - endOffs; + newWritePos = baseOffs + n2; + numSamplesPerChannel -= n2; + } + + // copy data + memcpy(_samples + writePos, samples, numSamplesPerChannel * sizeof(int16_t)); + if (n2 > 0) { + memcpy(_samples + baseOffs, samples + numSamplesPerChannel, n2 * sizeof(int16_t)); + } + + // set new write position for this channel + _writePos[channel + !isInput] = newWritePos; } - - // copy data - memcpy(_samples + writePos, data, n * sizeof(short)); - if (n2 > 0) { - memcpy(_samples + baseOffs, data + n, n2 * sizeof(short)); - } - - // set new write position for this channel - _writePos[ch] = newWritePos; } void Oscilloscope::render(int x, int y) { diff --git a/interface/src/Oscilloscope.h b/interface/src/Oscilloscope.h index d81fc11358..f17976d4e4 100644 --- a/interface/src/Oscilloscope.h +++ b/interface/src/Oscilloscope.h @@ -11,13 +11,14 @@ #include -class Oscilloscope { +#include + +class Oscilloscope : public QObject { + Q_OBJECT public: Oscilloscope(int width, int height, bool isEnabled); ~Oscilloscope(); - void addSamples(unsigned ch, short const* data, unsigned n); - void render(int x, int y); // Switches: On/Off, Stop Time @@ -57,7 +58,8 @@ public: // Sets the number of input samples per output sample. Without filtering // just uses every nTh sample. void setDownsampleRatio(unsigned n) { assert(n > 0); _downsampleRatio = n; } - +public slots: + void addStereoSamples(const QByteArray& audioByteArray, bool isInput); private: // don't copy/assign Oscilloscope(Oscilloscope const&); // = delete;