Add examples/playback
Using faiface/beep play a Ogg file directly from disk
This commit is contained in:
parent
733188ab17
commit
dbc1ce8df2
25
examples/playback/README.md
Normal file
25
examples/playback/README.md
Normal 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
19
examples/playback/go.mod
Normal 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
37
examples/playback/go.sum
Normal 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
133
examples/playback/main.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue