mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +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()
|
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 {
|
type hook interface {
|
||||||
OnSuspendAudio(f func() error)
|
OnSuspendAudio(f func() error)
|
||||||
OnResumeAudio(f func() error)
|
OnResumeAudio(f func() error)
|
||||||
|
@ -101,6 +101,9 @@ func (p *dummyPlayer) Err() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *dummyPlayer) SetBufferSize(bufferSize int) {
|
||||||
|
}
|
||||||
|
|
||||||
func (p *dummyPlayer) Close() error {
|
func (p *dummyPlayer) Close() error {
|
||||||
p.m.Lock()
|
p.m.Lock()
|
||||||
defer p.m.Unlock()
|
defer p.m.Unlock()
|
||||||
|
@ -125,14 +125,15 @@ type Player struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type playerImpl struct {
|
type playerImpl struct {
|
||||||
context *Context
|
context *Context
|
||||||
src io.Reader
|
src io.Reader
|
||||||
volume float64
|
volume float64
|
||||||
err atomicError
|
err atomicError
|
||||||
state playerState
|
state playerState
|
||||||
tmpbuf []byte
|
tmpbuf []byte
|
||||||
buf []byte
|
buf []byte
|
||||||
eof bool
|
eof bool
|
||||||
|
bufferSize int
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
@ -140,9 +141,10 @@ type playerImpl struct {
|
|||||||
func newPlayer(context *Context, src io.Reader) *Player {
|
func newPlayer(context *Context, src io.Reader) *Player {
|
||||||
p := &Player{
|
p := &Player{
|
||||||
p: &playerImpl{
|
p: &playerImpl{
|
||||||
context: context,
|
context: context,
|
||||||
src: src,
|
src: src,
|
||||||
volume: 1,
|
volume: 1,
|
||||||
|
bufferSize: context.defaultBufferSize(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(p, (*Player).Close)
|
runtime.SetFinalizer(p, (*Player).Close)
|
||||||
@ -176,9 +178,27 @@ func (p *playerImpl) Play() {
|
|||||||
<-ch
|
<-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 {
|
func (p *playerImpl) ensureTmpBuf() []byte {
|
||||||
if p.tmpbuf == nil {
|
if p.tmpbuf == nil {
|
||||||
p.tmpbuf = make([]byte, p.context.defaultBufferSize())
|
p.tmpbuf = make([]byte, p.bufferSize)
|
||||||
}
|
}
|
||||||
return p.tmpbuf
|
return p.tmpbuf
|
||||||
}
|
}
|
||||||
@ -193,7 +213,7 @@ func (p *playerImpl) playImpl() {
|
|||||||
|
|
||||||
if !p.eof {
|
if !p.eof {
|
||||||
buf := p.ensureTmpBuf()
|
buf := p.ensureTmpBuf()
|
||||||
for len(p.buf) < p.context.defaultBufferSize() {
|
for len(p.buf) < p.bufferSize {
|
||||||
n, err := p.src.Read(buf)
|
n, err := p.src.Read(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
p.setErrorImpl(err)
|
p.setErrorImpl(err)
|
||||||
@ -360,7 +380,7 @@ func (p *playerImpl) canReadSourceToBuffer() bool {
|
|||||||
if p.eof {
|
if p.eof {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return len(p.buf) < p.context.defaultBufferSize()
|
return len(p.buf) < p.bufferSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) readSourceToBuffer() {
|
func (p *playerImpl) readSourceToBuffer() {
|
||||||
@ -374,7 +394,7 @@ func (p *playerImpl) readSourceToBuffer() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.buf) >= p.context.defaultBufferSize() {
|
if len(p.buf) >= p.bufferSize {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ type contextProxy struct {
|
|||||||
|
|
||||||
// NewPlayer implements context.
|
// NewPlayer implements context.
|
||||||
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
||||||
return c.otoContext.NewPlayer(r)
|
return c.otoContext.NewPlayer(r).(player)
|
||||||
}
|
}
|
||||||
|
|
||||||
func otoContextToContext(ctx otoContext) context {
|
func otoContextToContext(ctx otoContext) context {
|
||||||
|
@ -32,6 +32,7 @@ type player interface {
|
|||||||
SetVolume(volume float64)
|
SetVolume(volume float64)
|
||||||
UnplayedBufferSize() int
|
UnplayedBufferSize() int
|
||||||
Err() error
|
Err() error
|
||||||
|
SetBufferSize(bufferSize int)
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,12 +64,13 @@ func newPlayerFactory(sampleRate int) *playerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type playerImpl struct {
|
type playerImpl struct {
|
||||||
context *Context
|
context *Context
|
||||||
player player
|
player player
|
||||||
src io.Reader
|
src io.Reader
|
||||||
stream *timeStream
|
stream *timeStream
|
||||||
factory *playerFactory
|
factory *playerFactory
|
||||||
m sync.Mutex
|
initBufferSize int
|
||||||
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl, error) {
|
func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl, error) {
|
||||||
@ -156,6 +158,10 @@ func (p *playerImpl) ensurePlayer() error {
|
|||||||
}
|
}
|
||||||
if p.player == nil {
|
if p.player == nil {
|
||||||
p.player = p.factory.context.NewPlayer(p.stream)
|
p.player = p.factory.context.NewPlayer(p.stream)
|
||||||
|
if p.initBufferSize != 0 {
|
||||||
|
p.player.SetBufferSize(p.initBufferSize)
|
||||||
|
p.initBufferSize = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -292,6 +298,17 @@ func (p *playerImpl) UnplayedBufferSize() time.Duration {
|
|||||||
return time.Duration(samples) * time.Second / time.Duration(p.factory.sampleRate)
|
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 {
|
func (p *playerImpl) source() io.Reader {
|
||||||
return p.src
|
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