Add examples/playback

Using faiface/beep play a Ogg file directly from disk
This commit is contained in:
Sean DuBois 2022-10-01 23:09:51 -04:00
parent 733188ab17
commit dbc1ce8df2
4 changed files with 214 additions and 0 deletions

View file

@ -0,0 +1,25 @@
# playback
playback demonstrates decoding a ogg file and then playing using the [faiface/beep](github.com/faiface/beep) library
## Instructions
### Install playback
Download and install playback
```
go install github.com/pion/opus/examples/playback@latest
```
### Create a ogg file to decode
Encode Opus into an ogg file, or use one that you already have. This implementation doesn't
support most Opus features yet, so encoding will be constrained.
```
ffmpeg -i $INPUT_FILE -c:a libopus -ac 1 -b:a 10K output.ogg
```
### Decode
Demux and decode the provided `ogg` file. The output audio samples will be played.
```
playback `pwd`/output.ogg
```

19
examples/playback/go.mod Normal file
View file

@ -0,0 +1,19 @@
module github.com/pion/opus/examples/playback
go 1.18
require (
github.com/faiface/beep v1.1.0
github.com/pion/opus v0.0.0
)
require (
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
)
replace github.com/pion/opus => ./../..

37
examples/playback/go.sum Normal file
View file

@ -0,0 +1,37 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

133
examples/playback/main.go Normal file
View file

@ -0,0 +1,133 @@
package main
import (
"bytes"
"io"
"os"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/speaker"
"github.com/pion/opus"
"github.com/pion/opus/pkg/oggreader"
)
type opusReader struct {
oggFile *oggreader.OggReader
opusDecoder opus.Decoder
decodeBuffer []byte
decodeBufferOffset int
segmentBuffer [][]byte
}
func (o *opusReader) Read(p []byte) (n int, err error) {
if o.decodeBufferOffset == 0 || o.decodeBufferOffset >= len(o.decodeBuffer) {
if len(o.segmentBuffer) == 0 {
for {
o.segmentBuffer, _, err = o.oggFile.ParseNextPage()
if err != nil {
return 0, err
} else if bytes.HasPrefix(o.segmentBuffer[0], []byte("OpusTags")) {
continue
}
break
}
}
var segment []byte
segment, o.segmentBuffer = o.segmentBuffer[0], o.segmentBuffer[1:]
o.decodeBufferOffset = 0
if _, _, err = o.opusDecoder.Decode(segment, o.decodeBuffer); err != nil {
return 0, err
}
}
n = copy(p, o.decodeBuffer[o.decodeBufferOffset:])
o.decodeBufferOffset += n
return n, nil
}
func main() {
if len(os.Args) != 2 {
panic("Usage: <in-file>")
}
file, err := os.Open(os.Args[1])
if err != nil {
panic(err)
}
oggFile, _, err := oggreader.NewWith(file)
if err != nil {
panic(err)
}
r := &opusReader{
decodeBuffer: make([]byte, 1920),
oggFile: oggFile,
opusDecoder: opus.NewDecoder(),
}
format := beep.Format{
SampleRate: beep.SampleRate(48000),
NumChannels: 1,
Precision: 2,
}
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan struct{})
speaker.Play(beep.Seq(&pcmStream{
r: r,
f: format,
buf: make([]byte, 512*format.Width()),
}, beep.Callback(func() {
close(done)
})))
<-done
}
// pcmStream allows faiface to play PCM directly
type pcmStream struct {
r io.Reader
f beep.Format
buf []byte
len int
pos int
err error
}
func (s *pcmStream) Err() error { return s.err }
func (s *pcmStream) Stream(samples [][2]float64) (n int, ok bool) {
width := s.f.Width()
// if there's not enough data for a full sample, get more
if size := s.len - s.pos; size < width {
// if there's a partial sample, move it to the beginning of the buffer
if size != 0 {
copy(s.buf, s.buf[s.pos:s.len])
}
s.len = size
s.pos = 0
// refill the buffer
nbytes, err := s.r.Read(s.buf[s.len:])
if err != nil {
if err != io.EOF {
s.err = err
}
return n, false
}
s.len += nbytes
}
// decode as many samples as we can
for n < len(samples) && s.len-s.pos >= width {
samples[n], _ = s.f.DecodeSigned(s.buf[s.pos:])
n++
s.pos += width
}
return n, true
}