mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 18:50:00 +02:00
Merge pull request #1881 from birarda/audio-fixes
repairs to audio so buffers don't climb
This commit is contained in:
commit
7d5366a1b8
3 changed files with 48 additions and 61 deletions
|
@ -56,6 +56,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
|
||||||
_numOutputCallbackBytes(0),
|
_numOutputCallbackBytes(0),
|
||||||
_loopbackAudioOutput(NULL),
|
_loopbackAudioOutput(NULL),
|
||||||
_loopbackOutputDevice(NULL),
|
_loopbackOutputDevice(NULL),
|
||||||
|
_proceduralAudioOutput(NULL),
|
||||||
|
_proceduralOutputDevice(NULL),
|
||||||
_inputRingBuffer(0),
|
_inputRingBuffer(0),
|
||||||
_ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL),
|
_ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL),
|
||||||
_scope(scope),
|
_scope(scope),
|
||||||
|
@ -75,7 +77,7 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p
|
||||||
_muted(false)
|
_muted(false)
|
||||||
{
|
{
|
||||||
// clear the array of locally injected samples
|
// clear the array of locally injected samples
|
||||||
memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::init(QGLWidget *parent) {
|
void Audio::init(QGLWidget *parent) {
|
||||||
|
@ -272,6 +274,9 @@ void Audio::start() {
|
||||||
|
|
||||||
// setup a loopback audio output device
|
// setup a loopback audio output device
|
||||||
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
|
_loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
|
||||||
|
|
||||||
|
// setup a procedural audio output device
|
||||||
|
_proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
|
||||||
|
|
||||||
gettimeofday(&_lastReceiveTime, NULL);
|
gettimeofday(&_lastReceiveTime, NULL);
|
||||||
}
|
}
|
||||||
|
@ -332,7 +337,7 @@ void Audio::handleAudioInput() {
|
||||||
memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||||
|
|
||||||
// zero out the locally injected audio in preparation for audio procedural sounds
|
// zero out the locally injected audio in preparation for audio procedural sounds
|
||||||
memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||||
|
|
||||||
if (!_muted) {
|
if (!_muted) {
|
||||||
// we aren't muted, downsample the input audio
|
// we aren't muted, downsample the input audio
|
||||||
|
@ -363,6 +368,22 @@ void Audio::handleAudioInput() {
|
||||||
// add procedural effects to the appropriate input samples
|
// add procedural effects to the appropriate input samples
|
||||||
addProceduralSounds(monoAudioSamples,
|
addProceduralSounds(monoAudioSamples,
|
||||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||||
|
|
||||||
|
if (!_proceduralOutputDevice) {
|
||||||
|
_proceduralOutputDevice = _proceduralAudioOutput->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice
|
||||||
|
QByteArray proceduralOutput;
|
||||||
|
proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4 * sizeof(int16_t));
|
||||||
|
|
||||||
|
linearResampling(_localProceduralSamples,
|
||||||
|
reinterpret_cast<int16_t*>(proceduralOutput.data()),
|
||||||
|
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
|
||||||
|
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4,
|
||||||
|
_desiredInputFormat, _outputFormat);
|
||||||
|
|
||||||
|
_proceduralOutputDevice->write(proceduralOutput);
|
||||||
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
NodeList* nodeList = NodeList::getInstance();
|
||||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||||
|
@ -431,12 +452,6 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
||||||
|
|
||||||
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
|
||||||
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
|
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
|
||||||
|
|
||||||
static int numRequiredOutputSamples = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / networkOutputToOutputRatio;
|
|
||||||
|
|
||||||
QByteArray outputBuffer;
|
|
||||||
outputBuffer.resize(numRequiredOutputSamples * sizeof(int16_t));
|
|
||||||
|
|
||||||
|
|
||||||
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
|
if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) {
|
||||||
// we don't have any audio data left in the output buffer
|
// we don't have any audio data left in the output buffer
|
||||||
|
@ -448,6 +463,14 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
||||||
|
|
||||||
// if there is anything in the ring buffer, decide what to do
|
// if there is anything in the ring buffer, decide what to do
|
||||||
if (_ringBuffer.samplesAvailable() > 0) {
|
if (_ringBuffer.samplesAvailable() > 0) {
|
||||||
|
|
||||||
|
|
||||||
|
int numNetworkOutputSamples = _ringBuffer.samplesAvailable();
|
||||||
|
int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio;
|
||||||
|
|
||||||
|
QByteArray outputBuffer;
|
||||||
|
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
|
||||||
|
|
||||||
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
|
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
|
||||||
+ (_jitterBufferSamples * 2))) {
|
+ (_jitterBufferSamples * 2))) {
|
||||||
// starved and we don't have enough to start, keep waiting
|
// starved and we don't have enough to start, keep waiting
|
||||||
|
@ -458,62 +481,25 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
|
||||||
|
|
||||||
// copy the samples we'll resample from the ring buffer - this also
|
// copy the samples we'll resample from the ring buffer - this also
|
||||||
// pushes the read pointer of the ring buffer forwards
|
// pushes the read pointer of the ring buffer forwards
|
||||||
int16_t ringBufferSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO];
|
int16_t ringBufferSamples[numNetworkOutputSamples];
|
||||||
_ringBuffer.readSamples(ringBufferSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
|
_ringBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples);
|
||||||
|
|
||||||
// add the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL from each QByteArray
|
// add the next numNetworkOutputSamples from each QByteArray
|
||||||
// in our _localInjectionByteArrays QVector to the _localInjectedSamples
|
// in our _localInjectionByteArrays QVector to the localInjectedSamples
|
||||||
|
|
||||||
// add to the output samples whatever is in the _localAudioOutput byte array
|
|
||||||
// that lets this user hear sound effects and loopback (if enabled)
|
|
||||||
|
|
||||||
for (int b = 0; b < _localInjectionByteArrays.size(); b++) {
|
|
||||||
QByteArray audioByteArray = _localInjectionByteArrays.at(b);
|
|
||||||
|
|
||||||
int16_t* byteArraySamples = (int16_t*) audioByteArray.data();
|
|
||||||
|
|
||||||
int samplesToRead = qMin((int)(audioByteArray.size() / sizeof(int16_t)),
|
|
||||||
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
|
||||||
|
|
||||||
for (int i = 0; i < samplesToRead; i++) {
|
|
||||||
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + byteArraySamples[i],
|
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samplesToRead < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
|
|
||||||
// there isn't anything left to inject from this byte array, remove it from the vector
|
|
||||||
_localInjectionByteArrays.remove(b);
|
|
||||||
} else {
|
|
||||||
// pull out the bytes we just read for outputs
|
|
||||||
audioByteArray.remove(0, samplesToRead * sizeof(int16_t));
|
|
||||||
|
|
||||||
// still data left to read - replace the byte array in the QVector with the smaller one
|
|
||||||
_localInjectionByteArrays.replace(b, audioByteArray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
|
|
||||||
ringBufferSamples[i * 2] = glm::clamp(ringBufferSamples[i * 2] + _localInjectedSamples[i],
|
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
|
||||||
ringBufferSamples[(i * 2) + 1] = glm::clamp(ringBufferSamples[(i * 2) + 1] + _localInjectedSamples[i],
|
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the packet from the RB to the output
|
// copy the packet from the RB to the output
|
||||||
linearResampling(ringBufferSamples,
|
linearResampling(ringBufferSamples,
|
||||||
(int16_t*) outputBuffer.data(),
|
(int16_t*) outputBuffer.data(),
|
||||||
NETWORK_BUFFER_LENGTH_SAMPLES_STEREO,
|
numNetworkOutputSamples,
|
||||||
numRequiredOutputSamples,
|
numDeviceOutputSamples,
|
||||||
_desiredOutputFormat, _outputFormat);
|
_desiredOutputFormat, _outputFormat);
|
||||||
|
|
||||||
if (_outputDevice) {
|
if (_outputDevice) {
|
||||||
|
|
||||||
_outputDevice->write(outputBuffer);
|
_outputDevice->write(outputBuffer);
|
||||||
|
|
||||||
// add output (@speakers) data just written to the scope
|
// add output (@speakers) data just written to the scope
|
||||||
QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection,
|
||||||
Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples,
|
Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples, numNetworkOutputSamples)),
|
||||||
NETWORK_BUFFER_LENGTH_BYTES_STEREO)),
|
|
||||||
Q_ARG(bool, true), Q_ARG(bool, false));
|
Q_ARG(bool, true), Q_ARG(bool, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -672,7 +658,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
|
||||||
int16_t collisionSample = (int16_t) sample;
|
int16_t collisionSample = (int16_t) sample;
|
||||||
|
|
||||||
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample,
|
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_collisionSoundMagnitude *= _collisionSoundDuration;
|
_collisionSoundMagnitude *= _collisionSoundDuration;
|
||||||
|
@ -696,7 +682,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) {
|
||||||
int16_t collisionSample = (int16_t) sample;
|
int16_t collisionSample = (int16_t) sample;
|
||||||
|
|
||||||
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
_localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample,
|
_localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample,
|
||||||
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE);
|
||||||
|
|
||||||
_drumSoundVolume *= (1.f - _drumSoundDecay);
|
_drumSoundVolume *= (1.f - _drumSoundDecay);
|
||||||
|
@ -727,8 +713,8 @@ void Audio::startDrumSound(float volume, float frequency, float duration, float
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::handleAudioByteArray(const QByteArray& audioByteArray) {
|
void Audio::handleAudioByteArray(const QByteArray& audioByteArray) {
|
||||||
// add this byte array to our QVector
|
// TODO: either create a new audio device (up to the limit of the sound card or a hard limit)
|
||||||
_localInjectionByteArrays.append(audioByteArray);
|
// or send to the mixer and use delayed loopback
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::renderToolIcon(int screenHeight) {
|
void Audio::renderToolIcon(int screenHeight) {
|
||||||
|
|
|
@ -86,8 +86,7 @@ private:
|
||||||
QAudioFormat _inputFormat;
|
QAudioFormat _inputFormat;
|
||||||
QIODevice* _inputDevice;
|
QIODevice* _inputDevice;
|
||||||
int _numInputCallbackBytes;
|
int _numInputCallbackBytes;
|
||||||
int16_t _localInjectedSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL];
|
int16_t _localProceduralSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL];
|
||||||
QVector<QByteArray> _localInjectionByteArrays;
|
|
||||||
QAudioOutput* _audioOutput;
|
QAudioOutput* _audioOutput;
|
||||||
QAudioFormat _desiredOutputFormat;
|
QAudioFormat _desiredOutputFormat;
|
||||||
QAudioFormat _outputFormat;
|
QAudioFormat _outputFormat;
|
||||||
|
@ -95,6 +94,8 @@ private:
|
||||||
int _numOutputCallbackBytes;
|
int _numOutputCallbackBytes;
|
||||||
QAudioOutput* _loopbackAudioOutput;
|
QAudioOutput* _loopbackAudioOutput;
|
||||||
QIODevice* _loopbackOutputDevice;
|
QIODevice* _loopbackOutputDevice;
|
||||||
|
QAudioOutput* _proceduralAudioOutput;
|
||||||
|
QIODevice* _proceduralOutputDevice;
|
||||||
AudioRingBuffer _inputRingBuffer;
|
AudioRingBuffer _inputRingBuffer;
|
||||||
AudioRingBuffer _ringBuffer;
|
AudioRingBuffer _ringBuffer;
|
||||||
|
|
||||||
|
|
|
@ -70,9 +70,9 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
|
||||||
// read to the end of the buffer
|
// read to the end of the buffer
|
||||||
int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput;
|
int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput;
|
||||||
memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t));
|
memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t));
|
||||||
|
|
||||||
// read the rest from the beginning of the buffer
|
// read the rest from the beginning of the buffer
|
||||||
memcpy(data + numSamplesToEnd, _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t));
|
memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t));
|
||||||
} else {
|
} else {
|
||||||
// read the data
|
// read the data
|
||||||
memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t));
|
memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t));
|
||||||
|
|
Loading…
Reference in a new issue