From caf6bf9cacce85dc3761acfd7aae87be8887f802 Mon Sep 17 00:00:00 2001 From: Julian Santander Date: Thu, 13 Jan 2022 12:50:53 +0000 Subject: [PATCH 1/2] Android: Use common CBuffer instead of custom RingBuffer and use int16_t samples --- Jamulus.pro | 3 +- android/ring_buffer.h | 198 ------------------------------------------ android/sound.cpp | 73 +++++++--------- android/sound.h | 10 +-- src/buffer.h | 3 + 5 files changed, 42 insertions(+), 245 deletions(-) delete mode 100644 android/ring_buffer.h diff --git a/Jamulus.pro b/Jamulus.pro index 28c3978b72..0eb80057ff 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -210,8 +210,7 @@ win32 { target.path = /tmp/your_executable # path on device INSTALLS += target - HEADERS += android/sound.h \ - android/ring_buffer.h + HEADERS += android/sound.h SOURCES += android/sound.cpp \ android/androiddebug.cpp diff --git a/android/ring_buffer.h b/android/ring_buffer.h deleted file mode 100644 index cbdac0a328..0000000000 --- a/android/ring_buffer.h +++ /dev/null @@ -1,198 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2020 - * - * Author(s): - * Julian Santander - * - ****************************************************************************** - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. - * - * This program 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. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#pragma once -#include - -/** - * Implementantion of a ring buffer. - * Data is contained in a vector dynamically allocated. - */ -template -class RingBuffer -{ -public: - /** - * @brief RingBuffer - * @param max maximum number of elements that can be contained in the ring buffer - */ - RingBuffer ( std::size_t max = 0 ) : mData ( max ), mRead ( 0 ), mWrite ( 0 ), mFull ( false ) {} - - /** - * @brief Resets the ring_buffer - * @param max maximum number of elements that can be contained in the ring buffer. - */ - void reset ( std::size_t max = 0 ) - { - mData = std::vector ( max ); - mRead = 0; - mWrite = 0; - mFull = false; - } - - /** - * @brief Current number of elements contained in the ring buffer - * @return - */ - std::size_t size() const - { - std::size_t size = capacity(); - if ( !mFull ) - { - if ( mWrite >= mRead ) - { - size = mWrite - mRead; - } - else - { - size = capacity() + mWrite - mRead; - } - } - return size; - } - - /** - * @brief whether the ring buffer is full - * @return - */ - bool isFull() const { return mFull; } - - /** - * @brief whether the ring buffer is empty. - * @return - */ - bool isEmpty() const { return !isFull() && ( mRead == mWrite ); } - - /** - * @brief Maximum number of elements in the ring buffer - * @return - */ - std::size_t capacity() const { return mData.size(); } - - /** - * @brief Adds a single value - * @param v the value to add - */ - void put ( const T& v ) - { - mData[mWrite] = v; - forward(); - } - - /** - * @brief Reads a single value - * @param v the value read - * @return true if the value was read - */ - bool get ( T& v ) - { - if ( !isEmpty() ) - { - v = mData[mRead]; - backward(); - return true; - } - else - { - return false; - } - } - - /** - * @brief Adds a multiple consecutive values - * @param v pointer to the consecutive values - * @param count number of consecutive values. - */ - void put ( const T* v, std::size_t count ) - { - std::size_t avail = mWrite - capacity(); - std::size_t to_copy = std::min ( count, avail ); - memcpy ( mData.data() + mWrite, v, to_copy * sizeof ( T ) ); - forward ( to_copy ); - if ( to_copy < count ) - { - put ( v + to_copy, count - to_copy ); - } - } - - /** - * @brief Reads multiple values - * @param v pointer to the memory wher ethe values will be written - * @param count Maximum available size in the memory area - * @return actual number of elements read. - */ - std::size_t get ( T* v, std::size_t count ) - { - std::size_t avail = 0; - if ( mRead < mWrite ) - { - avail = mWrite - mRead; - } - else - { - avail = mRead - capacity(); - } - std::size_t to_copy = std::min ( count, avail ); - memcpy ( v, mData.data() + mRead, to_copy * sizeof ( T ) ); - backward ( to_copy ); - if ( ( size() > 0 ) && ( count > to_copy ) ) - { - return to_copy + get ( v + to_copy, count - to_copy ); - } - else - { - return to_copy; - } - } - -private: - void forward() - { - if ( isFull() ) - { - mRead = ( mRead + 1 ) % capacity(); - } - mWrite = ( mWrite + 1 ) % capacity(); - mFull = ( mRead == mWrite ); - } - - void forward ( std::size_t count ) - { - for ( std::size_t i = 0; i < count; i++ ) - { - forward(); - } - } - - void backward ( std::size_t count ) - { - mFull = false; - mRead = ( mRead + count ) % capacity(); - } - - std::vector mData; - std::size_t mRead; /** offset to reading point */ - std::size_t mWrite; /** offset to writing point */ - bool mFull; -}; diff --git a/android/sound.cpp b/android/sound.cpp index 65e45b108c..6d5bde7626 100644 --- a/android/sound.cpp +++ b/android/sound.cpp @@ -45,7 +45,9 @@ void CSound::setupCommonStreamParams ( oboe::AudioStreamBuilder* builder ) { // We request EXCLUSIVE mode since this will give us the lowest possible // latency. If EXCLUSIVE mode isn't available the builder will fall back to SHARED mode - builder->setFormat ( oboe::AudioFormat::Float ) + + // Setting format to be PCM 16 bits (int16_t) + builder->setFormat ( oboe::AudioFormat::I16 ) ->setSharingMode ( oboe::SharingMode::Exclusive ) ->setChannelCount ( oboe::ChannelCount::Stereo ) ->setSampleRate ( SYSTEM_SAMPLE_RATE_HZ ) @@ -60,10 +62,15 @@ void CSound::closeStream ( oboe::ManagedStream& stream ) { if ( stream ) { - oboe::Result requestStopRes = stream->requestStop(); - oboe::Result result = stream->close(); + oboe::Result result; + result = stream->requestStop(); + if ( oboe::Result::OK != result ) + { + throw CGenErr ( tr ( "Error requesting stream stop: $s", oboe::convertToText ( result ) ) ); + } - if ( result != oboe::Result::OK ) + result = stream->close(); + if ( oboe::Result::OK != result ) { throw CGenErr ( tr ( "Error closing stream: $s", oboe::convertToText ( result ) ) ); } @@ -184,7 +191,7 @@ int CSound::Init ( const int iNewPrefMonoBufferSize ) // create memory for intermediate audio buffer vecsTmpInputAudioSndCrdStereo.Init ( iOboeBufferSizeStereo ); - mOutBuffer.reset ( iOboeBufferSizeStereo * RING_FACTOR ); + mOutBuffer.Init ( iOboeBufferSizeStereo * RING_FACTOR ); return iOboeBufferSizeMono; } @@ -236,17 +243,14 @@ oboe::DataCallbackResult CSound::onAudioInput ( oboe::AudioStream* oboeStream, v // We're good to start recording now // Take the data from the recording device output buffer and move // it to the vector ready to send up to the server - float* floatData = static_cast ( audioData ); + // + // According to the format that we've set on initialization, audioData + // is an array of int16_t + // + int16_t* intData = static_cast ( audioData ); // Copy recording data to internal vector - for ( int frmNum = 0; frmNum < numFrames; ++frmNum ) - { - for ( int channelNum = 0; channelNum < oboeStream->getChannelCount(); channelNum++ ) - { - vecsTmpInputAudioSndCrdStereo[frmNum * oboeStream->getChannelCount() + channelNum] = - static_cast ( floatData[frmNum * oboeStream->getChannelCount() + channelNum] * _MAXSHORT ); - } - } + memcpy ( vecsTmpInputAudioSndCrdStereo.data(), intData, sizeof ( int16_t ) * numFrames * oboeStream->getChannelCount() ); if ( numFrames != iOboeBufferSizeMono ) { @@ -269,33 +273,14 @@ void CSound::addOutputData ( int channel_count ) QMutexLocker locker ( &MutexAudioProcessCallback ); // Only copy data if we have data to copy, otherwise fill with silence - if ( !vecsTmpInputAudioSndCrdStereo.empty() ) - { - for ( int frmNum = 0; frmNum < iOboeBufferSizeMono; ++frmNum ) - { - for ( int channelNum = 0; channelNum < channel_count; channelNum++ ) - { - // copy sample received from server into output buffer - - // convert to 32 bit - const int32_t iCurSam = static_cast ( vecsTmpInputAudioSndCrdStereo[frmNum * channel_count + channelNum] ); - - mOutBuffer.put ( ( static_cast ( iCurSam ) ) / _MAXSHORT ); - } - } - } - else + if ( vecsTmpInputAudioSndCrdStereo.empty() ) { // prime output stream buffer with silence - for ( int frmNum = 0; frmNum < iOboeBufferSizeMono; ++frmNum ) - { - for ( int channelNum = 0; channelNum < channel_count; channelNum++ ) - { - mOutBuffer.put ( 0 ); - } - } + vecsTmpInputAudioSndCrdStereo.resize ( iOboeBufferSizeMono * channel_count, 0 ); } + mOutBuffer.Put ( vecsTmpInputAudioSndCrdStereo, iOboeBufferSizeMono * channel_count ); + if ( mOutBuffer.isFull() ) { mStats.ring_overrun++; @@ -309,10 +294,18 @@ oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, QMutexLocker locker ( &MutexAudioProcessCallback ); - std::size_t to_write = numFrames * oboeStream->getChannelCount(); - std::size_t count = std::min ( mOutBuffer.size(), to_write ); + std::size_t to_write = numFrames * oboeStream->getChannelCount(); + std::size_t count = std::min ( (std::size_t) mOutBuffer.GetAvailData(), to_write ); + CVector outBuffer ( count ); + + mOutBuffer.Get ( outBuffer, count ); + + // + // According to the format that we've set on initialization, audioData + // is an array of int16_t + // - mOutBuffer.get ( (float*) audioData, count ); + memcpy ( audioData, outBuffer.data(), count * sizeof ( int16_t ) ); if ( to_write > count ) { diff --git a/android/sound.h b/android/sound.h index f75c0ab881..2c602128bf 100644 --- a/android/sound.h +++ b/android/sound.h @@ -29,7 +29,7 @@ #include "global.h" #include #include -#include "ring_buffer.h" +#include "buffer.h" #include /* Classes ********************************************************************/ @@ -69,10 +69,10 @@ class CSound : public CSoundBase, public oboe::AudioStreamCallback }; protected: - CVector vecsTmpInputAudioSndCrdStereo; - RingBuffer mOutBuffer; - int iOboeBufferSizeMono; - int iOboeBufferSizeStereo; + CVector vecsTmpInputAudioSndCrdStereo; + CBuffer mOutBuffer; + int iOboeBufferSizeMono; + int iOboeBufferSizeStereo; private: void setupCommonStreamParams ( oboe::AudioStreamBuilder* builder ); diff --git a/src/buffer.h b/src/buffer.h index 910f0dfca5..1eea7a2c17 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -211,6 +211,9 @@ class CBuffer return iAvData; } + bool isFull() const { return eBufState == BS_FULL; } + bool isEmpty() const { return eBufState == BS_EMPTY; } + protected: enum EBufState { From b1afd3c005e42896934a4380b415179ce86029b0 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 13 Jan 2022 16:05:21 +0000 Subject: [PATCH 2/2] Fix some compiler warnings --- android/sound.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/android/sound.cpp b/android/sound.cpp index 6d5bde7626..d7861ad4b6 100644 --- a/android/sound.cpp +++ b/android/sound.cpp @@ -149,6 +149,8 @@ void CSound::warnIfNotLowLatency ( oboe::ManagedStream& stream, QString streamNa { QString latencyMode = ( stream->getPerformanceMode() == oboe::PerformanceMode::None ? "None" : "Power Saving" ); } + + Q_UNUSED ( streamName ); } void CSound::closeStreams() @@ -294,7 +296,7 @@ oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, QMutexLocker locker ( &MutexAudioProcessCallback ); - std::size_t to_write = numFrames * oboeStream->getChannelCount(); + std::size_t to_write = (std::size_t) numFrames * oboeStream->getChannelCount(); std::size_t count = std::min ( (std::size_t) mOutBuffer.GetAvailData(), to_write ); CVector outBuffer ( count ); @@ -317,10 +319,22 @@ oboe::DataCallbackResult CSound::onAudioOutput ( oboe::AudioStream* oboeStream, } // TODO better handling of stream closing errors -void CSound::onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result result ) { qDebug() << "CSound::onErrorAfterClose"; } +void CSound::onErrorAfterClose ( oboe::AudioStream* oboeStream, oboe::Result result ) +{ + qDebug() << "CSound::onErrorAfterClose"; + + Q_UNUSED ( oboeStream ); + Q_UNUSED ( result ); +} // TODO better handling of stream closing errors -void CSound::onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result result ) { qDebug() << "CSound::onErrorBeforeClose"; } +void CSound::onErrorBeforeClose ( oboe::AudioStream* oboeStream, oboe::Result result ) +{ + qDebug() << "CSound::onErrorBeforeClose"; + + Q_UNUSED ( oboeStream ); + Q_UNUSED ( result ); +} void CSound::Stats::reset() {