From c7b681809034fd0a3300de8571ea1b64d910183f Mon Sep 17 00:00:00 2001 From: Yuriy Levchenko Date: Thu, 18 Sep 2025 00:28:57 +0300 Subject: [PATCH] Set head mode when creating Android sound sources --- cmake/Android/Mengine/CMakeLists.txt | 4 +- gradle/app.gradle | 11 - gradle/libraries/Mengine/build.gradle | 7 - gradle/settings.gradle.kts | 1 - src/Bootstrapper/Bootstrapper.cpp | 8 + .../AndroidSoundPlugin/AndroidSoundPlugin.cpp | 42 ++ .../AndroidSoundPlugin/AndroidSoundPlugin.h | 21 + src/Plugins/AndroidSoundPlugin/CMakeLists.txt | 11 + src/Plugins/CMakeLists.txt | 4 + .../AndroidSoundSystem/AndroidSoundBuffer.cpp | 154 +++++ .../AndroidSoundSystem/AndroidSoundBuffer.h | 54 ++ .../AndroidSoundSystem/AndroidSoundSource.cpp | 624 ++++++++++++++++++ .../AndroidSoundSystem/AndroidSoundSource.h | 98 +++ .../AndroidSoundSystem/AndroidSoundSystem.cpp | 242 +++++++ .../AndroidSoundSystem/AndroidSoundSystem.h | 70 ++ src/Systems/AndroidSoundSystem/CMakeLists.txt | 16 + 16 files changed, 1347 insertions(+), 20 deletions(-) create mode 100644 src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.cpp create mode 100644 src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.h create mode 100644 src/Plugins/AndroidSoundPlugin/CMakeLists.txt create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundBuffer.cpp create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundBuffer.h create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundSource.cpp create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundSource.h create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundSystem.cpp create mode 100644 src/Systems/AndroidSoundSystem/AndroidSoundSystem.h create mode 100644 src/Systems/AndroidSoundSystem/CMakeLists.txt diff --git a/cmake/Android/Mengine/CMakeLists.txt b/cmake/Android/Mengine/CMakeLists.txt index 64ba8368cd..56df52ea4a 100644 --- a/cmake/Android/Mengine/CMakeLists.txt +++ b/cmake/Android/Mengine/CMakeLists.txt @@ -24,7 +24,7 @@ ADD_PLATFORM(AndroidPlatform "MENGINE_PLATFORM") # systems ADD_SYSTEM(MENGINE_SYSTEM_ALLOCATOR POSIXAllocatorSystem "MENGINE_SYSTEM_ALLOCATOR") -ADD_SYSTEM(MENGINE_SYSTEM_SOUND OpenALSoundSystem "MENGINE_SYSTEM_SOUND") +ADD_SYSTEM(MENGINE_SYSTEM_SOUND AndroidSoundSystem "MENGINE_SYSTEM_SOUND") ADD_SYSTEM(MENGINE_SYSTEM_FILE AndroidFileSystem "MENGINE_SYSTEM_FILE") ADD_SYSTEM(MENGINE_SYSTEM_DATETIME POSIXDateTimeSystem "MENGINE_SYSTEM_DATETIME") ADD_SYSTEM(MENGINE_SYSTEM_UNICODE NativeUnicodeSystem "MENGINE_SYSTEM_UNICODE") @@ -92,6 +92,8 @@ ADD_PLUGIN(MENGINE_PLUGIN_WIN32_CRITICALERRORSMONITOR OFF OFF "MENGINE_PLUGIN_WI ADD_PLUGIN(MENGINE_PLUGIN_ANDROID_NATIVE_PYTHON ON OFF "MENGINE_PLUGIN_ANDROID_NATIVE_PYTHON") +ADD_PLUGIN(MENGINE_PLUGIN_ANDROID_SOUND ON OFF "MENGINE_PLUGIN_ANDROID_SOUND") + MENGINE_ADD_DEFINITION(MENGINE_RENDER_TEXTURE_RGBA) MENGINE_ADD_DEFINITION(MENGINE_RENDER_COLOR_RGBA) #MENGINE_ADD_DEFINITION(MENGINE_SETJMP_UNSUPPORTED) diff --git a/gradle/app.gradle b/gradle/app.gradle index 583829da8c..d6ed6b33a7 100644 --- a/gradle/app.gradle +++ b/gradle/app.gradle @@ -332,9 +332,6 @@ android { jniLibs { pickFirsts += ['lib/**/libc++_shared.so'] - if(MENGINE_APP_LIBRARY_OPENAL32) { - pickFirsts += ['lib/**/libopenal.so'] - } } } @@ -360,14 +357,6 @@ if (ANDROID_APP_SPLIT_ENABLE == true) { } } -Utils.logAvailable("MENGINE_APP_LIBRARY_OPENAL32", MENGINE_APP_LIBRARY_OPENAL32) - -if (MENGINE_APP_LIBRARY_OPENAL32 == true) { - dependencies { - implementation project(':libraries:OpenAL32') - } -} - Utils.logAvailable("MENGINE_APP_LIBRARY_MENGINE", MENGINE_APP_LIBRARY_MENGINE) if (MENGINE_APP_LIBRARY_MENGINE == true) { diff --git a/gradle/libraries/Mengine/build.gradle b/gradle/libraries/Mengine/build.gradle index 31f6a7bd5a..ac78c3e2bf 100644 --- a/gradle/libraries/Mengine/build.gradle +++ b/gradle/libraries/Mengine/build.gradle @@ -132,17 +132,10 @@ android { } } - if (MENGINE_APP_LIBRARY_OPENAL32 == true) { - preBuild.dependsOn ":libraries:OpenAL32:build" - } - namespace "org.Mengine.Base" } dependencies { - if (MENGINE_APP_LIBRARY_OPENAL32 == true) { - implementation project(':libraries:OpenAL32') - } } android { diff --git a/gradle/settings.gradle.kts b/gradle/settings.gradle.kts index b7a335cc0e..1dc9ac678e 100644 --- a/gradle/settings.gradle.kts +++ b/gradle/settings.gradle.kts @@ -113,7 +113,6 @@ if (extra.has("ANDROID_APP_DELIVERY_PACKAGES") == true) { } includeLibrary("MENGINE_APP_LIBRARY_MENGINE", ":libraries:Mengine") -includeLibrary("MENGINE_APP_LIBRARY_OPENAL32", ":libraries:OpenAL32") /***************************************************************************** / * - MENGINE_APP_PLUGIN_FIREBASE [https://firebase.google.com] diff --git a/src/Bootstrapper/Bootstrapper.cpp b/src/Bootstrapper/Bootstrapper.cpp index 2cddb45c32..2d03963f92 100644 --- a/src/Bootstrapper/Bootstrapper.cpp +++ b/src/Bootstrapper/Bootstrapper.cpp @@ -390,6 +390,10 @@ PLUGIN_EXPORT( Win32AntifreezeMonitor ); PLUGIN_EXPORT( AndroidNativePython ); #endif ////////////////////////////////////////////////////////////////////////// +#if defined(MENGINE_PLUGIN_ANDROID_SOUND_STATIC) +PLUGIN_EXPORT( AndroidSound ); +#endif +////////////////////////////////////////////////////////////////////////// #if defined(MENGINE_PLUGIN_APPLE_NATIVE_PYTHON_STATIC) PLUGIN_EXPORT( AppleNativePython ); #endif @@ -1449,6 +1453,10 @@ namespace Mengine MENGINE_ADD_PLUGIN( AndroidNativePython, "plugin AndroidNativePython...", MENGINE_DOCUMENT_FACTORABLE ); #endif +#if defined(MENGINE_PLUGIN_ANDROID_SOUND_STATIC) + MENGINE_ADD_PLUGIN( AndroidSound, "plugin AndroidSound...", MENGINE_DOCUMENT_FACTORABLE ); +#endif + #if defined(MENGINE_PLUGIN_APPLE_NATIVE_PYTHON_STATIC) MENGINE_ADD_PLUGIN( AppleNativePython, "plugin AppleNativePython...", MENGINE_DOCUMENT_FACTORABLE ); #endif diff --git a/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.cpp b/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.cpp new file mode 100644 index 0000000000..c64d3f00be --- /dev/null +++ b/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.cpp @@ -0,0 +1,42 @@ +#include "AndroidSoundPlugin.h" + +#include "Interface/SoundSystemInterface.h" + +#include "Kernel/Logger.h" +#include "Kernel/PluginHelper.h" + +////////////////////////////////////////////////////////////////////////// +SERVICE_EXTERN( SoundSystem ); +////////////////////////////////////////////////////////////////////////// +PLUGIN_FACTORY( AndroidSound, Mengine::AndroidSoundPlugin ); +////////////////////////////////////////////////////////////////////////// +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + AndroidSoundPlugin::AndroidSoundPlugin() + { + } + ////////////////////////////////////////////////////////////////////////// + AndroidSoundPlugin::~AndroidSoundPlugin() + { + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundPlugin::_initializePlugin() + { + if( SERVICE_IS_EXIST( SoundSystemInterface ) == false ) + { + LOGGER_INFO( "android_sound", "AndroidSoundPlugin waiting for sound system service initialization" ); + } + + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundPlugin::_finalizePlugin() + { + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundPlugin::_destroyPlugin() + { + } + ////////////////////////////////////////////////////////////////////////// +} diff --git a/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.h b/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.h new file mode 100644 index 0000000000..267c90a66f --- /dev/null +++ b/src/Plugins/AndroidSoundPlugin/AndroidSoundPlugin.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Kernel/PluginBase.h" + +namespace Mengine +{ + class AndroidSoundPlugin + : public PluginBase + { + PLUGIN_DECLARE( "AndroidSound" ); + + public: + AndroidSoundPlugin(); + ~AndroidSoundPlugin() override; + + protected: + bool _initializePlugin() override; + void _finalizePlugin() override; + void _destroyPlugin() override; + }; +} diff --git a/src/Plugins/AndroidSoundPlugin/CMakeLists.txt b/src/Plugins/AndroidSoundPlugin/CMakeLists.txt new file mode 100644 index 0000000000..56a8e95cac --- /dev/null +++ b/src/Plugins/AndroidSoundPlugin/CMakeLists.txt @@ -0,0 +1,11 @@ +MENGINE_PROJECT(AndroidSoundPlugin) + +ADD_FILTER( +src + AndroidSoundPlugin.h + AndroidSoundPlugin.cpp +) + +ADD_MENGINE_PLUGIN(MENGINE_PLUGIN_ANDROID_SOUND) + +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Kernel) diff --git a/src/Plugins/CMakeLists.txt b/src/Plugins/CMakeLists.txt index 27ee4388be..5f5d43abad 100644 --- a/src/Plugins/CMakeLists.txt +++ b/src/Plugins/CMakeLists.txt @@ -98,6 +98,10 @@ if(MENGINE_PLUGIN_ANDROID_NATIVE_PYTHON) ADD_SUBDIRECTORY(AndroidNativePythonPlugin) endif() +if(MENGINE_PLUGIN_ANDROID_SOUND) + ADD_SUBDIRECTORY(AndroidSoundPlugin) +endif() + if(MENGINE_PLUGIN_DAZZLE) ADD_SUBDIRECTORY(DazzlePlugin) endif() diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.cpp b/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.cpp new file mode 100644 index 0000000000..ae05aea5a1 --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.cpp @@ -0,0 +1,154 @@ +#include "AndroidSoundBuffer.h" + +#include "Interface/SoundCodecInterface.h" + +#include "Kernel/Logger.h" +#include "Kernel/MemoryStreamHelper.h" + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + AndroidSoundBuffer::AndroidSoundBuffer() + : m_frequency( 0 ) + , m_channels( 0 ) + , m_bits( 0 ) + , m_duration( 0.f ) + , m_streamable( false ) + { + } + ////////////////////////////////////////////////////////////////////////// + AndroidSoundBuffer::~AndroidSoundBuffer() + { + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundBuffer::acquireSoundBuffer() + { + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundBuffer::releaseSoundBuffer() + { + //Empty + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundBuffer::updateSoundBuffer() + { + return false; + } + ////////////////////////////////////////////////////////////////////////// + const SoundDecoderInterfacePtr & AndroidSoundBuffer::getDecoder() const + { + return m_soundDecoder; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundBuffer::load( const SoundDecoderInterfacePtr & _soundDecoder, bool _streamable ) + { + m_soundDecoder = _soundDecoder; + + const SoundCodecDataInfo * dataInfo = m_soundDecoder->getCodecDataInfo(); + + if( dataInfo == nullptr ) + { + LOGGER_ERROR( "AndroidSoundBuffer invalid codec data info" ); + + return false; + } + + m_frequency = dataInfo->frequency; + m_channels = dataInfo->channels; + m_bits = dataInfo->bits; + m_duration = dataInfo->duration; + m_streamable = _streamable; + + if( m_channels == 0 || m_channels > 2 ) + { + LOGGER_ERROR( "AndroidSoundBuffer unsupported channel count %u", m_channels ); + + return false; + } + + if( m_bits != 8 && m_bits != 16 ) + { + LOGGER_ERROR( "AndroidSoundBuffer unsupported bits %u", m_bits ); + + return false; + } + + size_t pcmSize = dataInfo->size; + + if( pcmSize == 0 ) + { + LOGGER_ERROR( "AndroidSoundBuffer invalid data size" ); + + return false; + } + + MemoryInterfacePtr pcmMemory = Helper::createMemoryCacheBuffer( pcmSize, MENGINE_DOCUMENT_FACTORABLE ); + + if( pcmMemory == nullptr ) + { + LOGGER_ERROR( "AndroidSoundBuffer invalid memory allocate size %zu", pcmSize ); + + return false; + } + + SoundDecoderData data; + data.buffer = pcmMemory->getBuffer(); + data.size = pcmSize; + + size_t decodeSize = m_soundDecoder->decode( &data ); + + if( decodeSize == 0 ) + { + LOGGER_ERROR( "AndroidSoundBuffer invalid decode size" ); + + return false; + } + + m_pcmData.assign( (uint8_t *)data.buffer, (uint8_t *)data.buffer + decodeSize ); + + return true; + } + ////////////////////////////////////////////////////////////////////////// + const uint8_t * AndroidSoundBuffer::getData() const + { + if( m_pcmData.empty() == true ) + { + return nullptr; + } + + return m_pcmData.data(); + } + ////////////////////////////////////////////////////////////////////////// + size_t AndroidSoundBuffer::getDataSize() const + { + return m_pcmData.size(); + } + ////////////////////////////////////////////////////////////////////////// + uint32_t AndroidSoundBuffer::getFrequency() const + { + return m_frequency; + } + ////////////////////////////////////////////////////////////////////////// + uint32_t AndroidSoundBuffer::getChannels() const + { + return m_channels; + } + ////////////////////////////////////////////////////////////////////////// + uint32_t AndroidSoundBuffer::getBits() const + { + return m_bits; + } + ////////////////////////////////////////////////////////////////////////// + float AndroidSoundBuffer::getDuration() const + { + return m_duration; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundBuffer::isStreamable() const + { + return m_streamable; + } + ////////////////////////////////////////////////////////////////////////// +} + diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.h b/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.h new file mode 100644 index 0000000000..b53ece7a1c --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundBuffer.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Interface/SoundSystemInterface.h" + +#include "Kernel/Factorable.h" +#include "Kernel/Vector.h" + +namespace Mengine +{ + class AndroidSoundBuffer + : public SoundBufferInterface + { + DECLARE_FACTORABLE( AndroidSoundBuffer ); + + public: + AndroidSoundBuffer(); + ~AndroidSoundBuffer() override; + + public: + bool acquireSoundBuffer() override; + void releaseSoundBuffer() override; + + public: + bool updateSoundBuffer() override; + + public: + const SoundDecoderInterfacePtr & getDecoder() const override; + + public: + bool load( const SoundDecoderInterfacePtr & _soundDecoder, bool _streamable ); + + public: + const uint8_t * getData() const; + size_t getDataSize() const; + uint32_t getFrequency() const; + uint32_t getChannels() const; + uint32_t getBits() const; + float getDuration() const; + bool isStreamable() const; + + protected: + SoundDecoderInterfacePtr m_soundDecoder; + Vector m_pcmData; + + uint32_t m_frequency; + uint32_t m_channels; + uint32_t m_bits; + float m_duration; + bool m_streamable; + }; + + typedef IntrusivePtr AndroidSoundBufferPtr; +} + diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundSource.cpp b/src/Systems/AndroidSoundSystem/AndroidSoundSource.cpp new file mode 100644 index 0000000000..cbc2ffe91d --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundSource.cpp @@ -0,0 +1,624 @@ +#include "AndroidSoundSource.h" + +#include "AndroidSoundSystem.h" + +#include "Kernel/Assertion.h" +#include "Kernel/Logger.h" + +#include + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + AndroidSoundSource::AndroidSoundSource() + : m_soundSystem( nullptr ) + , m_volume( 1.f ) + , m_loop( false ) + , m_headMode( true ) + , m_playing( false ) + , m_pausing( false ) + , m_position( 0.f ) + , m_needRestart( false ) + , m_playerObject( nullptr ) + , m_playerPlay( nullptr ) + , m_playerBufferQueue( nullptr ) + , m_playerVolume( nullptr ) + { + } + ////////////////////////////////////////////////////////////////////////// + AndroidSoundSource::~AndroidSoundSource() + { + this->finalize(); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::initialize( AndroidSoundSystem * _soundSystem ) + { + m_soundSystem = _soundSystem; + + m_soundSystem->registerSoundSource( this ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::finalize() + { + this->destroyPlayer_(); + + if( m_soundSystem != nullptr ) + { + m_soundSystem->unregisterSoundSource( this ); + m_soundSystem = nullptr; + } + + m_soundBuffer = nullptr; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::play() + { + if( m_playing == true ) + { + return true; + } + + if( m_soundBuffer == nullptr ) + { + LOGGER_ASSERTION( "AndroidSoundSource invalid play without buffer" ); + + return false; + } + + if( m_pausing == false ) + { + if( this->createPlayer_() == false ) + { + return false; + } + + uint32_t bits = m_soundBuffer->getBits(); + uint32_t channels = m_soundBuffer->getChannels(); + + uint32_t bytesPerSample = (bits / 8) * channels; + + size_t offset = 0; + + if( m_position > 0.f && bytesPerSample > 0 ) + { + uint32_t frequency = m_soundBuffer->getFrequency(); + size_t frame = (size_t)(m_position * 0.001f * frequency); + offset = frame * bytesPerSample; + } + + if( this->enqueueBuffer_( offset ) == false ) + { + LOGGER_ERROR( "AndroidSoundSource invalid enqueue buffer" ); + + this->destroyPlayer_(); + + return false; + } + + this->applyVolume_(); + this->applyLooping_(); + + SLresult result_play = ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_PLAYING ); + + if( result_play != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid play state [%d]", result_play ); + + this->destroyPlayer_(); + + return false; + } + } + else + { + SLresult result_play = ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_PLAYING ); + + if( result_play != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid resume state [%d]", result_play ); + + return false; + } + } + + m_playing = true; + m_pausing = false; + + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::stop() + { + if( m_playing == false && m_pausing == false ) + { + return; + } + + if( m_playerPlay != nullptr ) + { + ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_STOPPED ); + } + + this->destroyPlayer_(); + + m_playing = false; + m_pausing = false; + m_position = 0.f; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::pause() + { + if( m_playing == false ) + { + return; + } + + if( m_pausing == true ) + { + return; + } + + if( m_playerPlay == nullptr ) + { + return; + } + + SLresult result_state = ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_PAUSED ); + + if( result_state != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid pause state [%d]", result_state ); + + return; + } + + SLmillisecond position = 0; + SLresult result_position = ( *m_playerPlay )->GetPosition( m_playerPlay, &position ); + + if( result_position == SL_RESULT_SUCCESS ) + { + m_position = (float)position; + } + + m_playing = false; + m_pausing = true; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::resume() + { + if( m_pausing == false ) + { + return false; + } + + if( m_playerPlay == nullptr ) + { + return false; + } + + SLresult result_state = ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_PLAYING ); + + if( result_state != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid resume state [%d]", result_state ); + + return false; + } + + m_playing = true; + m_pausing = false; + + return true; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::isPlay() const + { + return m_playing; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::isPause() const + { + return m_pausing; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::setVolume( float _volume ) + { + m_volume = _volume; + + this->applyVolume_(); + } + ////////////////////////////////////////////////////////////////////////// + float AndroidSoundSource::getVolume() const + { + return m_volume; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::setLoop( bool _loop ) + { + m_loop = _loop; + + this->applyLooping_(); + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::getLoop() const + { + return m_loop; + } + ////////////////////////////////////////////////////////////////////////// + float AndroidSoundSource::getDuration() const + { + if( m_soundBuffer == nullptr ) + { + LOGGER_ASSERTION( "AndroidSoundSource invalid get duration without buffer" ); + + return 0.f; + } + + return m_soundBuffer->getDuration(); + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::setPosition( float _position ) + { + if( m_playing == true ) + { + return false; + } + + m_position = _position; + + return true; + } + ////////////////////////////////////////////////////////////////////////// + float AndroidSoundSource::getPosition() const + { + if( m_playerPlay != nullptr && m_playing == true ) + { + SLmillisecond position = 0; + SLresult result = ( *m_playerPlay )->GetPosition( m_playerPlay, &position ); + + if( result == SL_RESULT_SUCCESS ) + { + return (float)position; + } + } + + return m_position; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::setSoundBuffer( const SoundBufferInterfacePtr & _soundBuffer ) + { + if( m_soundBuffer != nullptr ) + { + this->stop(); + } + + m_soundBuffer = AndroidSoundBufferPtr::from( _soundBuffer ); + } + ////////////////////////////////////////////////////////////////////////// + const SoundBufferInterfacePtr & AndroidSoundSource::getSoundBuffer() const + { + return m_soundBuffer; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::setHeadMode( bool _headMode ) + { + m_headMode = _headMode; + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::getHeadMode() const + { + return m_headMode; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::updateFromSystem() + { + if( m_playerPlay == nullptr ) + { + return; + } + + if( m_playing == false ) + { + if( m_needRestart == true ) + { + m_needRestart = false; + + this->destroyPlayer_(); + } + + return; + } + + SLuint32 playState = SL_PLAYSTATE_STOPPED; + SLresult result_state = ( *m_playerPlay )->GetPlayState( m_playerPlay, &playState ); + + if( result_state != SL_RESULT_SUCCESS ) + { + return; + } + + if( playState == SL_PLAYSTATE_STOPPED ) + { + m_playing = false; + m_pausing = false; + m_position = 0.f; + m_needRestart = true; + } + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::createPlayer_() + { + this->destroyPlayer_(); + + if( m_soundSystem == nullptr ) + { + return false; + } + + SLEngineItf engine = m_soundSystem->getSLEngine(); + SLObjectItf outputMix = m_soundSystem->getOutputMixObject(); + + if( engine == nullptr || outputMix == nullptr ) + { + LOGGER_ERROR( "AndroidSoundSource invalid engine interfaces" ); + + return false; + } + + SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 }; + + SLuint32 channels = m_soundBuffer->getChannels(); + + SLuint32 channelMask; + + if( channels == 1 ) + { + channelMask = SL_SPEAKER_FRONT_CENTER; + } + else if( channels == 2 ) + { + channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + } + else + { + LOGGER_ERROR( "AndroidSoundSource invalid channel count %u", channels ); + + return false; + } + + SLDataFormat_PCM formatPCM; + formatPCM.formatType = SL_DATAFORMAT_PCM; + formatPCM.numChannels = channels; + formatPCM.samplesPerSec = m_soundBuffer->getFrequency() * 1000; + formatPCM.bitsPerSample = m_soundBuffer->getBits(); + formatPCM.containerSize = m_soundBuffer->getBits(); + formatPCM.channelMask = channelMask; + formatPCM.endianness = SL_BYTEORDER_LITTLEENDIAN; + + SLDataSource audioSrc = { &locatorBufferQueue, &formatPCM }; + + SLDataLocator_OutputMix locatorOutputMix = { SL_DATALOCATOR_OUTPUTMIX, outputMix }; + SLDataSink audioSnk = { &locatorOutputMix, nullptr }; + + const SLInterfaceID ids[] = { SL_IID_BUFFERQUEUE, SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + + SLresult result_player = ( *engine )->CreateAudioPlayer( engine, &m_playerObject, &audioSrc, &audioSnk, 2, ids, req ); + + if( result_player != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid create audio player [%d]", result_player ); + + m_playerObject = nullptr; + + return false; + } + + SLresult result_realize = ( *m_playerObject )->Realize( m_playerObject, SL_BOOLEAN_FALSE ); + + if( result_realize != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid realize audio player [%d]", result_realize ); + + this->destroyPlayer_(); + + return false; + } + + SLresult result_play = ( *m_playerObject )->GetInterface( m_playerObject, SL_IID_PLAY, &m_playerPlay ); + + if( result_play != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid get play interface [%d]", result_play ); + + this->destroyPlayer_(); + + return false; + } + + SLresult result_buffer = ( *m_playerObject )->GetInterface( m_playerObject, SL_IID_BUFFERQUEUE, &m_playerBufferQueue ); + + if( result_buffer != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid get buffer queue interface [%d]", result_buffer ); + + this->destroyPlayer_(); + + return false; + } + + ( *m_playerBufferQueue )->RegisterCallback( m_playerBufferQueue, &AndroidSoundSource::s_callbackBufferQueue_, this ); + + SLresult result_volume = ( *m_playerObject )->GetInterface( m_playerObject, SL_IID_VOLUME, &m_playerVolume ); + + if( result_volume != SL_RESULT_SUCCESS ) + { + m_playerVolume = nullptr; + } + + m_needRestart = false; + + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::destroyPlayer_() + { + if( m_playerPlay != nullptr ) + { + ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_STOPPED ); + } + + if( m_playerBufferQueue != nullptr ) + { + ( *m_playerBufferQueue )->Clear( m_playerBufferQueue ); + m_playerBufferQueue = nullptr; + } + + m_playerVolume = nullptr; + m_playerPlay = nullptr; + + if( m_playerObject != nullptr ) + { + ( *m_playerObject )->Destroy( m_playerObject ); + m_playerObject = nullptr; + } + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::applyVolume_() + { + if( m_playerVolume == nullptr ) + { + return; + } + + if( m_volume <= 0.f ) + { + ( *m_playerVolume )->SetVolumeLevel( m_playerVolume, SL_MILLIBEL_MIN ); + + return; + } + + float volume = m_volume; + + if( volume < 0.f ) + { + volume = 0.f; + } + else if( volume > 1.f ) + { + volume = 1.f; + } + + SLmillibel level = (SLmillibel)(2000.f * log10f( volume )); + + ( *m_playerVolume )->SetVolumeLevel( m_playerVolume, level ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::applyLooping_() + { + if( m_playerPlay == nullptr ) + { + return; + } + + SLuint32 playState = SL_PLAYSTATE_STOPPED; + ( *m_playerPlay )->GetPlayState( m_playerPlay, &playState ); + + if( playState == SL_PLAYSTATE_PLAYING && m_loop == false ) + { + // do nothing, callback will stop playback once buffer consumed + } + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSource::enqueueBuffer_( size_t _offsetBytes ) + { + if( m_playerBufferQueue == nullptr ) + { + return false; + } + + const uint8_t * data = m_soundBuffer->getData(); + size_t dataSize = m_soundBuffer->getDataSize(); + + if( data == nullptr || dataSize == 0 ) + { + return false; + } + + if( _offsetBytes >= dataSize ) + { + return false; + } + + const void * bufferData = data + _offsetBytes; + size_t bufferSize = dataSize - _offsetBytes; + + SLresult result_clear = ( *m_playerBufferQueue )->Clear( m_playerBufferQueue ); + + if( result_clear != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid clear buffer queue [%d]", result_clear ); + + return false; + } + + SLresult result_enqueue = ( *m_playerBufferQueue )->Enqueue( m_playerBufferQueue, bufferData, bufferSize ); + + if( result_enqueue != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid enqueue buffer [%d]", result_enqueue ); + + return false; + } + + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::onBufferQueueEnd_() + { + if( m_loop == true ) + { + if( this->enqueueBuffer_( 0 ) == false ) + { + LOGGER_ERROR( "AndroidSoundSource invalid re-enqueue loop buffer" ); + + m_playing = false; + m_pausing = false; + m_position = 0.f; + m_needRestart = true; + + return; + } + + SLresult result_play = ( *m_playerPlay )->SetPlayState( m_playerPlay, SL_PLAYSTATE_PLAYING ); + + if( result_play != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSource invalid restart loop [%d]", result_play ); + + m_playing = false; + m_pausing = false; + m_position = 0.f; + m_needRestart = true; + } + + return; + } + + m_playing = false; + m_pausing = false; + m_position = 0.f; + m_needRestart = true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSource::s_callbackBufferQueue_( SLAndroidSimpleBufferQueueItf _bufferQueue, void * _context ) + { + MENGINE_UNUSED( _bufferQueue ); + + AndroidSoundSource * soundSource = static_cast( _context ); + + soundSource->onBufferQueueEnd_(); + } + ////////////////////////////////////////////////////////////////////////// +} + diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundSource.h b/src/Systems/AndroidSoundSystem/AndroidSoundSource.h new file mode 100644 index 0000000000..8cdc26637c --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundSource.h @@ -0,0 +1,98 @@ +#pragma once + +#include "Interface/SoundSourceInterface.h" + +#include "AndroidSoundBuffer.h" + +#include "Kernel/Factorable.h" + +#include +#include + +namespace Mengine +{ + class AndroidSoundSystem; + + class AndroidSoundSource + : public SoundSourceInterface + { + DECLARE_FACTORABLE( AndroidSoundSource ); + + public: + AndroidSoundSource(); + ~AndroidSoundSource() override; + + public: + void initialize( AndroidSoundSystem * _soundSystem ); + void finalize(); + + public: + bool play() override; + void stop() override; + void pause() override; + bool resume() override; + + public: + bool isPlay() const override; + bool isPause() const override; + + public: + void setVolume( float _volume ) override; + float getVolume() const override; + + public: + void setLoop( bool _loop ) override; + bool getLoop() const override; + + public: + float getDuration() const override; + + public: + bool setPosition( float _position ) override; + float getPosition() const override; + + public: + void setSoundBuffer( const SoundBufferInterfacePtr & _soundBuffer ) override; + const SoundBufferInterfacePtr & getSoundBuffer() const override; + + public: + void setHeadMode( bool _headMode ); + bool getHeadMode() const; + + public: + void updateFromSystem(); + + protected: + bool createPlayer_(); + void destroyPlayer_(); + void applyVolume_(); + void applyLooping_(); + bool enqueueBuffer_( size_t _offsetBytes ); + + protected: + void onBufferQueueEnd_(); + static void s_callbackBufferQueue_( SLAndroidSimpleBufferQueueItf _bufferQueue, void * _context ); + + protected: + AndroidSoundSystem * m_soundSystem; + AndroidSoundBufferPtr m_soundBuffer; + + float m_volume; + bool m_loop; + bool m_headMode; + + bool m_playing; + bool m_pausing; + float m_position; + + bool m_needRestart; + + SLObjectItf m_playerObject; + SLPlayItf m_playerPlay; + SLAndroidSimpleBufferQueueItf m_playerBufferQueue; + SLVolumeItf m_playerVolume; + }; + + typedef IntrusivePtr AndroidSoundSourcePtr; +} + diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundSystem.cpp b/src/Systems/AndroidSoundSystem/AndroidSoundSystem.cpp new file mode 100644 index 0000000000..7a0b22d6a0 --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundSystem.cpp @@ -0,0 +1,242 @@ +#include "AndroidSoundSystem.h" + +#include "AndroidSoundBuffer.h" +#include "AndroidSoundSource.h" + +#include "Kernel/AssertionFactory.h" +#include "Kernel/FactoryPool.h" +#include "Kernel/Logger.h" +#include "Kernel/ThreadMutexHelper.h" +#include "Kernel/ThreadMutexScope.h" + +#include + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + SERVICE_FACTORY( SoundSystem, Mengine::AndroidSoundSystem ); + ////////////////////////////////////////////////////////////////////////// + AndroidSoundSystem::AndroidSoundSystem() + : m_engineObject( nullptr ) + , m_engineEngine( nullptr ) + , m_outputMixObject( nullptr ) + , m_soundEnabled( true ) + { + } + ////////////////////////////////////////////////////////////////////////// + AndroidSoundSystem::~AndroidSoundSystem() + { + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSystem::_initializeService() + { + m_factorySoundBuffer = Helper::makeFactoryPoolWithMutex( MENGINE_DOCUMENT_FACTORABLE ); + m_factorySoundSource = Helper::makeFactoryPoolWithMutex( MENGINE_DOCUMENT_FACTORABLE ); + + m_sourcesMutex = Helper::createThreadMutex( MENGINE_DOCUMENT_FACTORABLE ); + + if( m_sourcesMutex == nullptr ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid create sources mutex" ); + + return false; + } + + SLresult result_create = slCreateEngine( &m_engineObject, 0, nullptr, 0, nullptr, nullptr ); + + if( result_create != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid create engine [%d]", result_create ); + + return false; + } + + SLresult result_realize_engine = ( *m_engineObject )->Realize( m_engineObject, SL_BOOLEAN_FALSE ); + + if( result_realize_engine != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid realize engine [%d]", result_realize_engine ); + + ( *m_engineObject )->Destroy( m_engineObject ); + m_engineObject = nullptr; + + return false; + } + + SLresult result_engine_interface = ( *m_engineObject )->GetInterface( m_engineObject, SL_IID_ENGINE, &m_engineEngine ); + + if( result_engine_interface != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid get engine interface [%d]", result_engine_interface ); + + ( *m_engineObject )->Destroy( m_engineObject ); + m_engineObject = nullptr; + m_engineEngine = nullptr; + + return false; + } + + SLresult result_create_output_mix = ( *m_engineEngine )->CreateOutputMix( m_engineEngine, &m_outputMixObject, 0, nullptr, nullptr ); + + if( result_create_output_mix != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid create output mix [%d]", result_create_output_mix ); + + ( *m_engineObject )->Destroy( m_engineObject ); + m_engineObject = nullptr; + m_engineEngine = nullptr; + + return false; + } + + SLresult result_realize_output = ( *m_outputMixObject )->Realize( m_outputMixObject, SL_BOOLEAN_FALSE ); + + if( result_realize_output != SL_RESULT_SUCCESS ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid realize output mix [%d]", result_realize_output ); + + ( *m_outputMixObject )->Destroy( m_outputMixObject ); + m_outputMixObject = nullptr; + + ( *m_engineObject )->Destroy( m_engineObject ); + m_engineObject = nullptr; + m_engineEngine = nullptr; + + return false; + } + + LOGGER_INFO( "android_sound", "AndroidSoundSystem initialized" ); + + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::_finalizeService() + { + m_sources.clear(); + + MENGINE_ASSERTION_FACTORY_EMPTY( m_factorySoundSource ); + MENGINE_ASSERTION_FACTORY_EMPTY( m_factorySoundBuffer ); + + m_factorySoundSource = nullptr; + m_factorySoundBuffer = nullptr; + + m_sourcesMutex = nullptr; + + if( m_outputMixObject != nullptr ) + { + ( *m_outputMixObject )->Destroy( m_outputMixObject ); + m_outputMixObject = nullptr; + } + + if( m_engineObject != nullptr ) + { + ( *m_engineObject )->Destroy( m_engineObject ); + m_engineObject = nullptr; + m_engineEngine = nullptr; + } + + LOGGER_INFO( "android_sound", "AndroidSoundSystem finalized" ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::_stopService() + { + this->foreachSoundSource_( []( AndroidSoundSource * _source ) + { + _source->stop(); + } ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::update() + { + this->foreachSoundSource_( []( AndroidSoundSource * _source ) + { + _source->updateFromSystem(); + } ); + } + ////////////////////////////////////////////////////////////////////////// + bool AndroidSoundSystem::isSilent() const + { + return false; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::onTurnSound( bool _turn ) + { + m_soundEnabled = _turn; + + this->foreachSoundSource_( [this]( AndroidSoundSource * _source ) + { + if( m_soundEnabled == false ) + { + _source->pause(); + } + } ); + } + ////////////////////////////////////////////////////////////////////////// + SoundBufferInterfacePtr AndroidSoundSystem::createSoundBuffer( const SoundDecoderInterfacePtr & _decoder, bool _streamable, const DocumentInterfacePtr & _doc ) + { + AndroidSoundBufferPtr buffer = m_factorySoundBuffer->createObject( _doc ); + + if( buffer->load( _decoder, _streamable ) == false ) + { + LOGGER_ERROR( "AndroidSoundSystem invalid load sound buffer" ); + + return nullptr; + } + + return buffer; + } + ////////////////////////////////////////////////////////////////////////// + SoundSourceInterfacePtr AndroidSoundSystem::createSoundSource( bool _isHeadMode, const SoundBufferInterfacePtr & _sample, const DocumentInterfacePtr & _doc ) + { + AndroidSoundSourcePtr source = m_factorySoundSource->createObject( _doc ); + + source->initialize( this ); + source->setHeadMode( _isHeadMode ); + source->setSoundBuffer( _sample ); + + return source; + } + ////////////////////////////////////////////////////////////////////////// + SLEngineItf AndroidSoundSystem::getSLEngine() const + { + return m_engineEngine; + } + ////////////////////////////////////////////////////////////////////////// + SLObjectItf AndroidSoundSystem::getOutputMixObject() const + { + return m_outputMixObject; + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::registerSoundSource( AndroidSoundSource * _source ) + { + ThreadMutexScope scope( m_sourcesMutex ); + + m_sources.emplace_back( _source ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::unregisterSoundSource( AndroidSoundSource * _source ) + { + ThreadMutexScope scope( m_sourcesMutex ); + + VectorAndroidSoundSources::iterator it_found = std::find( m_sources.begin(), m_sources.end(), _source ); + + if( it_found == m_sources.end() ) + { + return; + } + + m_sources.erase( it_found ); + } + ////////////////////////////////////////////////////////////////////////// + void AndroidSoundSystem::foreachSoundSource_( const Lambda & _lambda ) + { + ThreadMutexScope scope( m_sourcesMutex ); + + for( AndroidSoundSource * source : m_sources ) + { + _lambda( source ); + } + } + ////////////////////////////////////////////////////////////////////////// +} + diff --git a/src/Systems/AndroidSoundSystem/AndroidSoundSystem.h b/src/Systems/AndroidSoundSystem/AndroidSoundSystem.h new file mode 100644 index 0000000000..04c5e333f5 --- /dev/null +++ b/src/Systems/AndroidSoundSystem/AndroidSoundSystem.h @@ -0,0 +1,70 @@ +#pragma once + +#include "Interface/SoundSystemInterface.h" + +#include "Kernel/ServiceBase.h" +#include "Kernel/FactoryWithMutex.h" +#include "Kernel/ThreadMutex.h" +#include "Kernel/Vector.h" + +#include "Config/Lambda.h" + +#include + +namespace Mengine +{ + class AndroidSoundBuffer; + class AndroidSoundSource; + + class AndroidSoundSystem + : public ServiceBase + { + public: + AndroidSoundSystem(); + ~AndroidSoundSystem() override; + + protected: + bool _initializeService() override; + void _finalizeService() override; + + protected: + void _stopService() override; + + public: + void update() override; + + public: + bool isSilent() const override; + void onTurnSound( bool _turn ) override; + + public: + SoundBufferInterfacePtr createSoundBuffer( const SoundDecoderInterfacePtr & _decoder, bool _streamable, const DocumentInterfacePtr & _doc ) override; + SoundSourceInterfacePtr createSoundSource( bool _isHeadMode, const SoundBufferInterfacePtr & _sample, const DocumentInterfacePtr & _doc ) override; + + public: + SLEngineItf getSLEngine() const; + SLObjectItf getOutputMixObject() const; + + public: + void registerSoundSource( AndroidSoundSource * _source ); + void unregisterSoundSource( AndroidSoundSource * _source ); + + protected: + void foreachSoundSource_( const Lambda & _lambda ); + + private: + FactoryInterfacePtr m_factorySoundBuffer; + FactoryInterfacePtr m_factorySoundSource; + + SLObjectItf m_engineObject; + SLEngineItf m_engineEngine; + SLObjectItf m_outputMixObject; + + bool m_soundEnabled; + + typedef Vector VectorAndroidSoundSources; + VectorAndroidSoundSources m_sources; + ThreadMutexInterfacePtr m_sourcesMutex; + }; +} + diff --git a/src/Systems/AndroidSoundSystem/CMakeLists.txt b/src/Systems/AndroidSoundSystem/CMakeLists.txt new file mode 100644 index 0000000000..dfd21f938f --- /dev/null +++ b/src/Systems/AndroidSoundSystem/CMakeLists.txt @@ -0,0 +1,16 @@ +MENGINE_PROJECT(AndroidSoundSystem) + +ADD_FILTER( +src + AndroidSoundSystem.h + AndroidSoundSystem.cpp + AndroidSoundBuffer.h + AndroidSoundBuffer.cpp + AndroidSoundSource.h + AndroidSoundSource.cpp +) + +ADD_MENGINE_LIBRARY(Systems) + +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Kernel) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} OpenSLES)