mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +01:00
audio: add Player.SetBufferSize
This change also adds examples/realtimepcm. Closes #2026
This commit is contained in:
parent
798b60b67c
commit
08783542eb
@ -404,6 +404,14 @@ func (p *Player) UnplayedBufferSize() time.Duration {
|
||||
return p.p.UnplayedBufferSize()
|
||||
}
|
||||
|
||||
// SetBufferSize adjusts the buffer size of the player.
|
||||
// If 0 is specified, the default buffer size is used.
|
||||
// A small buffer size is useful if you want to play a real-time PCM for example.
|
||||
// Note that the audio quality might be affected if you modify the buffer size.
|
||||
func (p *Player) SetBufferSize(bufferSize int) {
|
||||
p.p.SetBufferSize(bufferSize)
|
||||
}
|
||||
|
||||
type hook interface {
|
||||
OnSuspendAudio(f func() error)
|
||||
OnResumeAudio(f func() error)
|
||||
|
@ -101,6 +101,9 @@ func (p *dummyPlayer) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) SetBufferSize(bufferSize int) {
|
||||
}
|
||||
|
||||
func (p *dummyPlayer) Close() error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
@ -125,14 +125,15 @@ type Player struct {
|
||||
}
|
||||
|
||||
type playerImpl struct {
|
||||
context *Context
|
||||
src io.Reader
|
||||
volume float64
|
||||
err atomicError
|
||||
state playerState
|
||||
tmpbuf []byte
|
||||
buf []byte
|
||||
eof bool
|
||||
context *Context
|
||||
src io.Reader
|
||||
volume float64
|
||||
err atomicError
|
||||
state playerState
|
||||
tmpbuf []byte
|
||||
buf []byte
|
||||
eof bool
|
||||
bufferSize int
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
@ -140,9 +141,10 @@ type playerImpl struct {
|
||||
func newPlayer(context *Context, src io.Reader) *Player {
|
||||
p := &Player{
|
||||
p: &playerImpl{
|
||||
context: context,
|
||||
src: src,
|
||||
volume: 1,
|
||||
context: context,
|
||||
src: src,
|
||||
volume: 1,
|
||||
bufferSize: context.defaultBufferSize(),
|
||||
},
|
||||
}
|
||||
runtime.SetFinalizer(p, (*Player).Close)
|
||||
@ -176,9 +178,27 @@ func (p *playerImpl) Play() {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func (p *Player) SetBufferSize(bufferSize int) {
|
||||
p.p.setBufferSize(bufferSize)
|
||||
}
|
||||
|
||||
func (p *playerImpl) setBufferSize(bufferSize int) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
orig := p.bufferSize
|
||||
p.bufferSize = bufferSize
|
||||
if bufferSize == 0 {
|
||||
p.bufferSize = p.context.defaultBufferSize()
|
||||
}
|
||||
if orig != p.bufferSize {
|
||||
p.tmpbuf = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *playerImpl) ensureTmpBuf() []byte {
|
||||
if p.tmpbuf == nil {
|
||||
p.tmpbuf = make([]byte, p.context.defaultBufferSize())
|
||||
p.tmpbuf = make([]byte, p.bufferSize)
|
||||
}
|
||||
return p.tmpbuf
|
||||
}
|
||||
@ -193,7 +213,7 @@ func (p *playerImpl) playImpl() {
|
||||
|
||||
if !p.eof {
|
||||
buf := p.ensureTmpBuf()
|
||||
for len(p.buf) < p.context.defaultBufferSize() {
|
||||
for len(p.buf) < p.bufferSize {
|
||||
n, err := p.src.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
p.setErrorImpl(err)
|
||||
@ -360,7 +380,7 @@ func (p *playerImpl) canReadSourceToBuffer() bool {
|
||||
if p.eof {
|
||||
return false
|
||||
}
|
||||
return len(p.buf) < p.context.defaultBufferSize()
|
||||
return len(p.buf) < p.bufferSize
|
||||
}
|
||||
|
||||
func (p *playerImpl) readSourceToBuffer() {
|
||||
@ -374,7 +394,7 @@ func (p *playerImpl) readSourceToBuffer() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(p.buf) >= p.context.defaultBufferSize() {
|
||||
if len(p.buf) >= p.bufferSize {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ type contextProxy struct {
|
||||
|
||||
// NewPlayer implements context.
|
||||
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
||||
return c.otoContext.NewPlayer(r)
|
||||
return c.otoContext.NewPlayer(r).(player)
|
||||
}
|
||||
|
||||
func otoContextToContext(ctx otoContext) context {
|
||||
|
@ -32,6 +32,7 @@ type player interface {
|
||||
SetVolume(volume float64)
|
||||
UnplayedBufferSize() int
|
||||
Err() error
|
||||
SetBufferSize(bufferSize int)
|
||||
io.Closer
|
||||
}
|
||||
|
||||
@ -63,12 +64,13 @@ func newPlayerFactory(sampleRate int) *playerFactory {
|
||||
}
|
||||
|
||||
type playerImpl struct {
|
||||
context *Context
|
||||
player player
|
||||
src io.Reader
|
||||
stream *timeStream
|
||||
factory *playerFactory
|
||||
m sync.Mutex
|
||||
context *Context
|
||||
player player
|
||||
src io.Reader
|
||||
stream *timeStream
|
||||
factory *playerFactory
|
||||
initBufferSize int
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl, error) {
|
||||
@ -156,6 +158,10 @@ func (p *playerImpl) ensurePlayer() error {
|
||||
}
|
||||
if p.player == nil {
|
||||
p.player = p.factory.context.NewPlayer(p.stream)
|
||||
if p.initBufferSize != 0 {
|
||||
p.player.SetBufferSize(p.initBufferSize)
|
||||
p.initBufferSize = 0
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -292,6 +298,17 @@ func (p *playerImpl) UnplayedBufferSize() time.Duration {
|
||||
return time.Duration(samples) * time.Second / time.Duration(p.factory.sampleRate)
|
||||
}
|
||||
|
||||
func (p *playerImpl) SetBufferSize(bufferSize int) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.player == nil {
|
||||
p.initBufferSize = bufferSize
|
||||
return
|
||||
}
|
||||
p.player.SetBufferSize(bufferSize)
|
||||
}
|
||||
|
||||
func (p *playerImpl) source() io.Reader {
|
||||
return p.src
|
||||
}
|
||||
|
159
examples/realtimepcm/main.go
Normal file
159
examples/realtimepcm/main.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
//go:build example
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/audio"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
sampleRate = 48000
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
audioContext *audio.Context
|
||||
player *audio.Player
|
||||
sineWave *SineWave
|
||||
}
|
||||
|
||||
type SineWave struct {
|
||||
frequency int
|
||||
minFrequency int
|
||||
maxFrequency int
|
||||
|
||||
// position is the position in the wave length in the range of [0, 1).
|
||||
position float64
|
||||
|
||||
remaining []byte
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func NewSineWave() *SineWave {
|
||||
return &SineWave{
|
||||
frequency: 440,
|
||||
minFrequency: 440,
|
||||
maxFrequency: 880,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SineWave) Update(raisePitch bool) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if raisePitch {
|
||||
if s.frequency < s.maxFrequency {
|
||||
s.frequency += 10
|
||||
}
|
||||
} else {
|
||||
if s.frequency > s.minFrequency {
|
||||
s.frequency -= 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SineWave) Read(buf []byte) (int, error) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
if len(s.remaining) > 0 {
|
||||
n := copy(buf, s.remaining)
|
||||
s.remaining = s.remaining[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var origBuf []byte
|
||||
if len(buf)%4 > 0 {
|
||||
origBuf = buf
|
||||
buf = make([]byte, len(origBuf)+4-len(origBuf)%4)
|
||||
}
|
||||
|
||||
length := sampleRate / float64(s.frequency)
|
||||
p := int64(length * s.position)
|
||||
for i := 0; i < len(buf)/4; i++ {
|
||||
const max = 32767
|
||||
b := int16(math.Sin(2*math.Pi*float64(p)/float64(length)) * max)
|
||||
buf[4*i] = byte(b)
|
||||
buf[4*i+1] = byte(b >> 8)
|
||||
buf[4*i+2] = byte(b)
|
||||
buf[4*i+3] = byte(b >> 8)
|
||||
p++
|
||||
}
|
||||
|
||||
s.position = float64(p) / float64(length)
|
||||
s.position = s.position - math.Floor(s.position)
|
||||
|
||||
if origBuf != nil {
|
||||
n := copy(origBuf, buf)
|
||||
s.remaining = buf[n:]
|
||||
return n, nil
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
return &Game{
|
||||
audioContext: audio.NewContext(sampleRate),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
if g.audioContext == nil {
|
||||
g.audioContext = audio.NewContext(sampleRate)
|
||||
}
|
||||
if g.player == nil {
|
||||
g.sineWave = NewSineWave()
|
||||
p, err := g.audioContext.NewPlayer(g.sineWave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.player = p
|
||||
g.player.Play()
|
||||
|
||||
// Adjust the buffer size to reflect the audio source changes in real time.
|
||||
// Note that Ebiten doesn't guarantee the audio quality when the buffer size is modified.
|
||||
// 8192 should work in most cases, but this might cause glitches in some environments.
|
||||
g.player.SetBufferSize(8192)
|
||||
}
|
||||
g.sineWave.Update(ebiten.IsKeyPressed(ebiten.KeyA))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
ebitenutil.DebugPrint(screen, "This is an example of a real time PCM.\nPress and hold the A key.")
|
||||
}
|
||||
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
return screenWidth, screenHeight
|
||||
}
|
||||
|
||||
func main() {
|
||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||
ebiten.SetWindowTitle("Real Time PCM (Ebiten Demo)")
|
||||
if err := ebiten.RunGame(NewGame()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user