diff --git a/audio/internal/oboe/binding_android.cpp b/audio/internal/oboe/binding_android.cpp index fbebf2aa5..a5cc002e9 100644 --- a/audio/internal/oboe/binding_android.cpp +++ b/audio/internal/oboe/binding_android.cpp @@ -17,12 +17,9 @@ #include "_cgo_export.h" #include "oboe_oboe_Oboe_android.h" -#include -#include +#include #include -#include - namespace { class Player; @@ -39,9 +36,6 @@ public: const char *Resume(); const char *Close(); - void AddPlayer(Player *player); - void RemovePlayer(Player *player); - oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboe_stream, void *audio_data, int32_t num_frames) override; @@ -52,73 +46,9 @@ private: int channel_num_ = 0; int bit_depth_in_bytes_ = 0; - std::mutex mutex_; - std::set players_; std::shared_ptr stream_; }; -class Player { -public: - Player(double volume, uintptr_t go_player) : go_player_{go_player} { - std::atomic_store(&volume_, volume); - Stream::GetInstance().AddPlayer(this); - } - - ~Player() { Stream::GetInstance().RemovePlayer(this); } - - void SetVolume(double volume) { std::atomic_store(&volume_, volume); } - - void Play() { std::atomic_store(&playing_, true); } - - void Pause() { std::atomic_store(&playing_, false); } - - bool IsPlaying() { return std::atomic_load(&playing_); } - - void AppendBuffer(uint8_t *data, int length) { - std::lock_guard lock(mutex_); - buf_.insert(buf_.end(), data, data + length); - } - - size_t GetUnplayedBufferSize() { - std::lock_guard lock(mutex_); - return buf_.size(); - } - - size_t Read(std::vector &buf) { - if (!std::atomic_load(&playing_)) { - return 0; - } - - const double volume = std::atomic_load(&volume_); - size_t copy_num = 0; - { - // 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_); - copy_num = std::min(buf.size(), buf_.size() / 2); - for (size_t i = 0; i < copy_num; i++) { - int16_t v = static_cast(buf_[2 * i]) | - (static_cast(buf_[2 * i + 1]) << 8); - buf[i] = static_cast(v) / (1 << 15) * volume; - } - buf_.erase(buf_.begin(), buf_.begin() + copy_num * 2); - } - - if (copy_num) { - ebiten_oboe_onWrittenCallback(go_player_); - } - return copy_num; - } - -private: - const uintptr_t go_player_; - - std::atomic volume_{1.0}; - std::atomic playing_; - std::vector buf_; - std::mutex mutex_; -}; - Stream &Stream::GetInstance() { static Stream *stream = new Stream(); return *stream; @@ -144,6 +74,7 @@ const char *Stream::Play(int sample_rate, int channel_num, ->setFormat(oboe::AudioFormat::Float) ->setChannelCount(channel_num_) ->setSampleRate(sample_rate_) + ->setBufferCapacityInFrames(1024) ->setDataCallback(this) ->openStream(stream_); if (result != oboe::Result::OK) { @@ -195,41 +126,14 @@ const char *Stream::Close() { return nullptr; } -void Stream::AddPlayer(Player *player) { - std::lock_guard lock(mutex_); - players_.insert(player); -} - -void Stream::RemovePlayer(Player *player) { - std::lock_guard lock(mutex_); - players_.erase(player); -} - oboe::DataCallbackResult Stream::onAudioReady(oboe::AudioStream *oboe_stream, void *audio_data, int32_t num_frames) { size_t num = num_frames * channel_num_; - std::vector> bufs; - { - // 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_); - bufs.resize(players_.size()); - size_t i = 0; - for (Player *player : players_) { - bufs[i].resize(num); - player->Read(bufs[i]); - i++; - } - } - float *dst = reinterpret_cast(audio_data); - for (int i = 0; i < num; i++) { - dst[i] = 0; - for (const std::vector buf : bufs) { - dst[i] += buf[i]; - } - } + // TODO: Do not use a lock in onAudioReady. + // https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_data_callback.html#ad8a3a9f609df5fd3a5d885cbe1b2204d + ebiten_oboe_read(dst, num); return oboe::DataCallbackResult::Continue; } @@ -249,45 +153,4 @@ const char *ebiten_oboe_Suspend() { return Stream::GetInstance().Pause(); } const char *ebiten_oboe_Resume() { return Stream::GetInstance().Resume(); } -PlayerID ebiten_oboe_Player_Create(double volume, uintptr_t go_player) { - Player *p = new Player(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); -} - -void ebiten_oboe_Player_Play(PlayerID audio_player) { - Player *p = reinterpret_cast(audio_player); - p->Play(); -} - -void 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); -} - -void ebiten_oboe_Player_Close(PlayerID audio_player) { - Player *p = reinterpret_cast(audio_player); - delete p; -} - -int ebiten_oboe_Player_UnplayedBufferSize(PlayerID audio_player) { - Player *p = reinterpret_cast(audio_player); - return p->GetUnplayedBufferSize(); -} - } // extern "C" diff --git a/audio/internal/oboe/binding_android.go b/audio/internal/oboe/binding_android.go index 0fcf997a4..c93706f0e 100644 --- a/audio/internal/oboe/binding_android.go +++ b/audio/internal/oboe/binding_android.go @@ -26,12 +26,15 @@ import "C" import ( "fmt" - "runtime" - "sync" + "reflect" "unsafe" ) -func Play(sampleRate, channelNum, bitDepthInBytes int) error { +var theReadFunc func(buf []float32) + +func Play(sampleRate, channelNum, bitDepthInBytes int, readFunc func(buf []float32)) error { + // Play can invoke the callback. Set the callback before Play. + theReadFunc = readFunc if msg := C.ebiten_oboe_Play(C.int(sampleRate), C.int(channelNum), C.int(bitDepthInBytes)); msg != nil { return fmt.Errorf("oboe: Play failed: %s", C.GoString(msg)) } @@ -52,89 +55,15 @@ func Resume() error { return nil } -type Player struct { - player C.PlayerID - onWritten func() - - // m is the mutex for this player. - // This is necessary as Close can be invoked from the finalizer goroutine. - m sync.Mutex -} - -func NewPlayer(volume float64, onWritten func()) *Player { - p := &Player{ - onWritten: onWritten, +//export ebiten_oboe_read +func ebiten_oboe_read(buf *C.float, len C.size_t) { + s := []float32{} + h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) + h.Data = uintptr(unsafe.Pointer(buf)) + h.Len = int(len) + h.Cap = int(len) + for i := range s { + s[i] = 0 } - p.player = C.ebiten_oboe_Player_Create(C.double(volume), C.uintptr_t(uintptr(unsafe.Pointer(p)))) - runtime.SetFinalizer(p, (*Player).Close) - return p -} - -//export ebiten_oboe_onWrittenCallback -func ebiten_oboe_onWrittenCallback(player C.uintptr_t) { - p := (*Player)(unsafe.Pointer(uintptr(player))) - p.onWritten() -} - -func (p *Player) IsPlaying() bool { - p.m.Lock() - defer p.m.Unlock() - return bool(C.ebiten_oboe_Player_IsPlaying(p.player)) -} - -func (p *Player) AppendBuffer(buf []byte) { - p.m.Lock() - defer p.m.Unlock() - - ptr := C.CBytes(buf) - defer C.free(ptr) - - C.ebiten_oboe_Player_AppendBuffer(p.player, (*C.uint8_t)(ptr), C.int(len(buf))) -} - -func (p *Player) Play() error { - p.m.Lock() - defer p.m.Unlock() - - if p.player == 0 { - return fmt.Errorf("oboe: player is already closed at Play") - } - C.ebiten_oboe_Player_Play(p.player) - return nil -} - -func (p *Player) Pause() error { - p.m.Lock() - defer p.m.Unlock() - - if p.player == 0 { - return fmt.Errorf("oboe: player is already closed at Pause") - } - C.ebiten_oboe_Player_Pause(p.player) - return nil -} - -func (p *Player) SetVolume(volume float64) { - p.m.Lock() - defer p.m.Unlock() - C.ebiten_oboe_Player_SetVolume(p.player, C.double(volume)) -} - -func (p *Player) Close() error { - p.m.Lock() - defer p.m.Unlock() - - runtime.SetFinalizer(p, nil) - if p.player == 0 { - return fmt.Errorf("oboe: player is already closed at Close") - } - C.ebiten_oboe_Player_Close(p.player) - p.player = 0 - return nil -} - -func (p *Player) UnplayedBufferSize() int { - p.m.Lock() - defer p.m.Unlock() - return int(C.ebiten_oboe_Player_UnplayedBufferSize(p.player)) + theReadFunc(s) } diff --git a/audio/internal/oboe/binding_android.h b/audio/internal/oboe/binding_android.h index e850d40ac..1bfea9c22 100644 --- a/audio/internal/oboe/binding_android.h +++ b/audio/internal/oboe/binding_android.h @@ -29,15 +29,6 @@ const char *ebiten_oboe_Play(int sample_rate, int channel_num, int bit_depth_in_bytes); const char *ebiten_oboe_Suspend(); const char *ebiten_oboe_Resume(); -PlayerID ebiten_oboe_Player_Create(double volume, uintptr_t go_player); -bool ebiten_oboe_Player_IsPlaying(PlayerID audio_player); -void ebiten_oboe_Player_AppendBuffer(PlayerID audio_player, uint8_t *data, - int length); -void ebiten_oboe_Player_Play(PlayerID audio_player); -void ebiten_oboe_Player_Pause(PlayerID audio_player); -void ebiten_oboe_Player_Close(PlayerID audio_player); -void ebiten_oboe_Player_SetVolume(PlayerID audio_player, double volume); -int ebiten_oboe_Player_UnplayedBufferSize(PlayerID audio_player); #ifdef __cplusplus } diff --git a/audio/internal/readerdriver/driver_android.go b/audio/internal/readerdriver/driver_android.go index 705ccee93..2895c1ecc 100644 --- a/audio/internal/readerdriver/driver_android.go +++ b/audio/internal/readerdriver/driver_android.go @@ -15,10 +15,6 @@ package readerdriver import ( - "io" - "runtime" - "sync" - "github.com/hajimehoshi/ebiten/v2/audio/internal/oboe" ) @@ -30,6 +26,8 @@ type context struct { sampleRate int channelNum int bitDepthInBytes int + + players *players } func NewContext(sampleRate int, channelNum int, bitDepthInBytes int) (Context, chan struct{}, error) { @@ -40,24 +38,15 @@ func NewContext(sampleRate int, channelNum int, bitDepthInBytes int) (Context, c sampleRate: sampleRate, channelNum: channelNum, bitDepthInBytes: bitDepthInBytes, + players: newPlayers(), } - if err := oboe.Play(sampleRate, channelNum, bitDepthInBytes); err != nil { + if err := oboe.Play(sampleRate, channelNum, bitDepthInBytes, c.players.read); err != nil { return nil, nil, err } + go c.players.loop() return c, ready, nil } -func (c *context) NewPlayer(src io.Reader) Player { - p := &player{ - context: c, - src: src, - cond: sync.NewCond(&sync.Mutex{}), - volume: 1, - } - runtime.SetFinalizer(p, (*player).Close) - return p -} - func (c *context) Suspend() error { return oboe.Suspend() } @@ -65,247 +54,3 @@ func (c *context) Suspend() error { func (c *context) Resume() error { return oboe.Resume() } - -type player struct { - context *context - p *oboe.Player - src io.Reader - err error - cond *sync.Cond - closed bool - volume float64 - eof bool -} - -func (p *player) Pause() { - p.cond.L.Lock() - defer p.cond.L.Unlock() - - if p.err != nil { - return - } - if p.closed { - return - } - if p.p == nil { - return - } - if err := p.p.Pause(); err != nil { - p.setErrorImpl(err) - return - } - p.cond.Signal() -} - -func (p *player) Play() { - // Call Play asynchronously since Oboe's Play might take long. - ch := make(chan struct{}) - go func() { - p.cond.L.Lock() - defer p.cond.L.Unlock() - close(ch) - p.playImpl() - }() - - // Wait until the mutex is locked in the above goroutine. - <-ch -} - -func (p *player) playImpl() { - if p.err != nil { - return - } - if p.p != nil && p.p.IsPlaying() { - return - } - defer p.cond.Signal() - var runLoop bool - if p.p == nil { - p.p = oboe.NewPlayer(p.volume, func() { - p.cond.Signal() - }) - runLoop = true - } - - buf := make([]byte, p.context.maxBufferSize()) - for p.p.UnplayedBufferSize() < p.context.maxBufferSize() { - n, err := p.src.Read(buf) - if err != nil && err != io.EOF { - p.setErrorImpl(err) - return - } - p.p.AppendBuffer(buf[:n]) - if err == io.EOF { - p.eof = true - break - } - } - - if err := p.p.Play(); err != nil { - p.setErrorImpl(err) - return - } - if runLoop { - go p.loop() - } -} - -func (p *player) IsPlaying() bool { - p.cond.L.Lock() - defer p.cond.L.Unlock() - if p.p == nil { - return false - } - return p.p.IsPlaying() -} - -func (p *player) Reset() { - p.cond.L.Lock() - defer p.cond.L.Unlock() - p.resetImpl() -} - -func (p *player) resetImpl() { - if p.err != nil { - return - } - if p.closed { - return - } - if p.p == nil { - return - } - if err := p.p.Close(); err != nil { - p.setErrorImpl(err) - return - } - p.p = nil - p.eof = false - p.cond.Signal() -} - -func (p *player) Volume() float64 { - p.cond.L.Lock() - defer p.cond.L.Unlock() - return p.volume -} - -func (p *player) SetVolume(volume float64) { - p.cond.L.Lock() - defer p.cond.L.Unlock() - p.volume = volume - if p.p == nil { - return - } - p.p.SetVolume(volume) -} - -func (p *player) UnplayedBufferSize() int { - p.cond.L.Lock() - defer p.cond.L.Unlock() - - if p.p == nil { - return 0 - } - return p.p.UnplayedBufferSize() -} - -func (p *player) Err() error { - p.cond.L.Lock() - defer p.cond.L.Unlock() - return p.err -} - -func (p *player) Close() error { - p.cond.L.Lock() - defer p.cond.L.Unlock() - return p.closeImpl() -} - -func (p *player) closeImpl() error { - defer p.cond.Signal() - - runtime.SetFinalizer(p, nil) - p.closed = true - if p.p == nil { - return p.err - } - if err := p.p.Close(); err != nil && p.err == nil { - // Do not call setErrorImpl, or this can cause infinite recursive. - p.err = err - return p.err - } - p.p = nil - return p.err -} - -func (p *player) setError(err error) { - p.cond.L.Lock() - defer p.cond.L.Unlock() - p.setErrorImpl(err) -} - -func (p *player) setErrorImpl(err error) { - p.err = err - p.closeImpl() -} - -func (p *player) shouldWait() bool { - if p.closed { - return false - } - if p.p == nil { - return false - } - - // Wait when the player is paused. - if !p.p.IsPlaying() { - return true - } - - // When the source reaches EOF, wait until all the data is consumed. - if p.eof { - return p.p.UnplayedBufferSize() > 0 - } - - return p.p.UnplayedBufferSize() >= p.context.maxBufferSize() -} - -func (p *player) wait() bool { - p.cond.L.Lock() - defer p.cond.L.Unlock() - - for p.shouldWait() { - p.cond.Wait() - } - return p.p != nil && p.p.IsPlaying() -} - -func (p *player) loop() { - buf := make([]byte, 4096) - for { - if !p.wait() { - return - } - - n, err := p.src.Read(buf) - if err != nil && err != io.EOF { - p.setError(err) - return - } - - p.cond.L.Lock() - p.p.AppendBuffer(buf[:n]) - if err == io.EOF { - p.eof = true - } - - // Now p.resetImpl() doesn't close the stream gracefully. Then buffer size check is necessary here. - if p.eof && p.p.UnplayedBufferSize() == 0 { - p.resetImpl() - p.cond.L.Unlock() - return - } - p.cond.L.Unlock() - } -} diff --git a/audio/internal/readerdriver/player_unix.go b/audio/internal/readerdriver/player_unix.go index f1cb34622..b02668dbc 100644 --- a/audio/internal/readerdriver/player_unix.go +++ b/audio/internal/readerdriver/player_unix.go @@ -13,7 +13,6 @@ // limitations under the License. // +build aix dragonfly freebsd hurd illumos linux netbsd openbsd solaris -// +build !android package readerdriver @@ -153,9 +152,15 @@ func (p *player) Play() { } func (p *playerImpl) Play() { - p.m.Lock() - defer p.m.Unlock() - p.playImpl() + ch := make(chan struct{}) + go func() { + p.m.Lock() + defer p.m.Unlock() + + close(ch) + p.playImpl() + }() + <-ch } func (p *playerImpl) playImpl() { @@ -193,10 +198,7 @@ func (p *player) Pause() { func (p *playerImpl) Pause() { p.m.Lock() defer p.m.Unlock() - p.pauseImpl() -} -func (p *playerImpl) pauseImpl() { if p.state != playerPlay { return } @@ -335,15 +337,22 @@ func (p *playerImpl) readSourceToBuffer() { return } - if len(p.buf) >= p.context.maxBufferSize() { + maxBufferSize := p.context.maxBufferSize() + if len(p.buf) >= maxBufferSize { return } - buf := make([]byte, p.context.maxBufferSize()) - n, err := p.src.Read(buf) + + src := p.src + p.m.Unlock() + buf := make([]byte, maxBufferSize) + n, err := src.Read(buf) + p.m.Lock() + if err != nil && err != io.EOF { p.setErrorImpl(err) return } + p.buf = append(p.buf, buf[:n]...) if err == io.EOF && len(p.buf) == 0 { p.resetImpl()