// Copyright 2021 The Ebiten Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "binding_android.h" #include "_cgo_export.h" #include "oboe_oboe_Oboe_android.h" #include #include #include namespace { class Player : public oboe::AudioStreamDataCallback { public: static const char *Suspend() { std::lock_guard lock(PlayersMutex()); for (Player *player : GetPlayers()) { if (!player->IsPlaying()) { continue; } // Close should be called rather than Pause for onPause. // https://github.com/google/oboe/blob/master/docs/GettingStarted.md if (const char *msg = player->Close(); msg) { return msg; } GetPlayersToResume().insert(player); } return nullptr; } static const char *Resume() { std::lock_guard lock(PlayersMutex()); for (Player *player : GetPlayersToResume()) { if (const char *msg = player->Play(); msg) { return msg; } } GetPlayersToResume().clear(); return nullptr; } Player(int sample_rate, int channel_num, int bit_depth_in_bytes, double volume, uintptr_t go_player) : sample_rate_{sample_rate}, channel_num_{channel_num}, bit_depth_in_bytes_{bit_depth_in_bytes}, go_player_{go_player} { std::atomic_store(&volume_, volume); { std::lock_guard lock(PlayersMutex()); GetPlayers().insert(this); } } void SetVolume(double volume) { std::atomic_store(&volume_, volume); } bool IsPlaying() { return stream_->getState() == oboe::StreamState::Started; } void AppendBuffer(uint8_t *data, int length) { // Sync this constants with internal/readerdriver/driver.go const size_t bytes_per_sample = channel_num_ * bit_depth_in_bytes_; const size_t one_buffer_size = sample_rate_ * channel_num_ * bit_depth_in_bytes_ / 4 / bytes_per_sample * bytes_per_sample; const size_t max_buffer_size = one_buffer_size * 2; std::lock_guard lock(mutex_); buf_.insert(buf_.end(), data, data + length); } const char *Play() { if (bit_depth_in_bytes_ != 2) { return "bit_depth_in_bytes_ must be 2 but not"; } if (!stream_) { oboe::AudioStreamBuilder builder; oboe::Result result = builder.setDirection(oboe::Direction::Output) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) ->setSharingMode(oboe::SharingMode::Shared) ->setFormat(oboe::AudioFormat::I16) ->setChannelCount(channel_num_) ->setSampleRate(sample_rate_) ->setDataCallback(this) ->openStream(stream_); if (result != oboe::Result::OK) { return oboe::convertToText(result); } } if (stream_->getSharingMode() != oboe::SharingMode::Shared) { return "oboe::SharingMode::Shared is not available"; } // What if the buffer size is not enough? if (oboe::Result result = stream_->start(); result != oboe::Result::OK) { return oboe::convertToText(result); } return nullptr; } const char *Pause() { if (!stream_) { return nullptr; } if (oboe::Result result = stream_->pause(); result != oboe::Result::OK) { return oboe::convertToText(result); } return nullptr; } const char *Close() { if (!stream_) { return nullptr; } if (oboe::Result result = stream_->stop(); result != oboe::Result::OK) { return oboe::convertToText(result); } if (oboe::Result result = stream_->close(); result != oboe::Result::OK) { return oboe::convertToText(result); } stream_.reset(); return nullptr; } const char *CloseAndRemove() { // Close and remove self from the players atomically. // Otherwise, a removed player might be resumed at Resume unexpectedly. std::lock_guard lock(PlayersMutex()); const char *msg = Close(); GetPlayers().erase(this); GetPlayersToResume().erase(this); return msg; } int GetUnplayedBufferSize() { std::lock_guard lock(mutex_); return buf_.size(); } oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboe_stream, void *audio_data, int32_t num_frames) override { size_t num_bytes = num_frames * channel_num_ * bit_depth_in_bytes_; std::vector buf(num_bytes); { // TODO: Do not use a lock in onAudioReady. // https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_data_callback.html#ad8a3a9f609df5fd3a5d885cbe1b2204d std::lock_guard lock(mutex_); size_t copy_bytes = std::min(num_bytes, buf_.size()); std::copy(buf_.begin(), buf_.begin() + copy_bytes, buf.begin()); buf_.erase(buf_.begin(), buf_.begin() + copy_bytes); onWrittenCallback(go_player_); } if (const double volume = std::atomic_load(&volume_); volume < 1) { for (int i = 0; i < buf.size() / 2; i++) { int16_t v = static_cast(buf[2 * i]) | (static_cast(buf[2 * i + 1]) << 8); v = static_cast(static_cast(v) * volume); buf[2 * i] = static_cast(v); buf[2 * i + 1] = static_cast(v >> 8); } } std::copy(buf.begin(), buf.end(), reinterpret_cast(audio_data)); return oboe::DataCallbackResult::Continue; } private: static std::set &GetPlayers() { static std::set players; return players; } static std::set &GetPlayersToResume() { static std::set players_to_resume; return players_to_resume; } static std::mutex &PlayersMutex() { static std::mutex mutex; return mutex; } const int sample_rate_; const int channel_num_; const int bit_depth_in_bytes_; const uintptr_t go_player_; std::atomic volume_{1.0}; std::vector buf_; std::mutex mutex_; std::shared_ptr stream_; }; } // namespace extern "C" { const char *ebiten_oboe_Suspend() { return Player::Suspend(); } const char *ebiten_oboe_Resume() { return Player::Resume(); } PlayerID ebiten_oboe_Player_Create(int sample_rate, int channel_num, int bit_depth_in_bytes, double volume, uintptr_t go_player) { Player *p = new Player(sample_rate, channel_num, bit_depth_in_bytes, volume, go_player); return reinterpret_cast(p); } bool ebiten_oboe_Player_IsPlaying(PlayerID audio_player) { Player *p = reinterpret_cast(audio_player); return p->IsPlaying(); } void ebiten_oboe_Player_AppendBuffer(PlayerID audio_player, uint8_t *data, int length) { Player *p = reinterpret_cast(audio_player); p->AppendBuffer(data, length); } const char *ebiten_oboe_Player_Play(PlayerID audio_player) { Player *p = reinterpret_cast(audio_player); return p->Play(); } const char *ebiten_oboe_Player_Pause(PlayerID audio_player) { Player *p = reinterpret_cast(audio_player); return p->Pause(); } void ebiten_oboe_Player_SetVolume(PlayerID audio_player, double volume) { Player *p = reinterpret_cast(audio_player); p->SetVolume(volume); } const char *ebiten_oboe_Player_Close(PlayerID audio_player) { Player *p = reinterpret_cast(audio_player); const char *msg = p->CloseAndRemove(); delete p; return msg; } int ebiten_oboe_Player_UnplayedBufferSize(PlayerID audio_player) { Player *p = reinterpret_cast(audio_player); return p->GetUnplayedBufferSize(); } } // extern "C"