mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-05 08:36:55 +08:00
Update audio APIs to use audio.Reader instead
This commit is contained in:
3
go.mod
3
go.mod
@@ -4,8 +4,11 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/blackjack/webcam v0.0.0-20191123110216-08fa32efcb67
|
||||
github.com/faiface/beep v1.0.2
|
||||
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140
|
||||
github.com/pion/webrtc/v2 v2.1.19-0.20200106051345-726a16faa60d
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6
|
||||
)
|
||||
|
||||
replace github.com/faiface/beep => ../../faiface/beep
|
||||
|
25
go.sum
25
go.sum
@@ -7,14 +7,28 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
|
||||
github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
|
||||
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
|
||||
github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
|
||||
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
|
||||
github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk=
|
||||
github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
|
||||
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140 h1:z8Rh8GKvXMJ3sQfXYWCF5NMLnVshzqdPtjvcfyPMrgQ=
|
||||
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -22,8 +36,11 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -78,6 +95,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
|
||||
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo=
|
||||
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -86,6 +109,7 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -93,6 +117,7 @@ golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX
|
||||
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@@ -1,6 +1,10 @@
|
||||
package codec
|
||||
|
||||
import "image"
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"image"
|
||||
"io"
|
||||
)
|
||||
|
||||
type VideoEncoder interface {
|
||||
Encode(img image.Image) ([]byte, error)
|
||||
@@ -21,13 +25,10 @@ type VideoSetting struct {
|
||||
type VideoEncoderBuilder func(s VideoSetting) (VideoEncoder, error)
|
||||
type VideoDecoderBuilder func(s VideoSetting) (VideoDecoder, error)
|
||||
|
||||
type AudioEncoder interface {
|
||||
Encode([]int16) ([]byte, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type AudioSetting struct {
|
||||
SampleRate int
|
||||
InSampleRate, OutSampleRate int
|
||||
// Latency in ms
|
||||
Latency float64
|
||||
}
|
||||
|
||||
type AudioEncoderBuilder func(s AudioSetting) (AudioEncoder, error)
|
||||
type AudioEncoderBuilder func(r audio.Reader, s AudioSetting) (io.ReadCloser, error)
|
||||
|
@@ -2,52 +2,111 @@ package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/webrtc/v2"
|
||||
"gopkg.in/hraban/opus.v2"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
engine *opus.Encoder
|
||||
buff []byte
|
||||
inBuff [][2]float32
|
||||
reader audio.Reader
|
||||
}
|
||||
|
||||
var _ codec.AudioEncoder = &encoder{}
|
||||
var latencies = []float64{5, 10, 20, 40, 60}
|
||||
|
||||
var _ io.ReadCloser = &encoder{}
|
||||
var _ codec.AudioEncoderBuilder = codec.AudioEncoderBuilder(NewEncoder)
|
||||
|
||||
func init() {
|
||||
codec.Register(webrtc.Opus, codec.AudioEncoderBuilder(NewEncoder))
|
||||
}
|
||||
|
||||
func NewEncoder(s codec.AudioSetting) (codec.AudioEncoder, error) {
|
||||
channels := 1 // mono
|
||||
engine, err := opus.NewEncoder(48000, channels, opus.AppVoIP)
|
||||
func NewEncoder(r audio.Reader, s codec.AudioSetting) (io.ReadCloser, error) {
|
||||
if s.InSampleRate == 0 {
|
||||
return nil, fmt.Errorf("opus: InSampleRate is required")
|
||||
}
|
||||
|
||||
if s.OutSampleRate == 0 {
|
||||
s.OutSampleRate = 48000
|
||||
}
|
||||
|
||||
if s.Latency == 0 {
|
||||
s.Latency = 20
|
||||
}
|
||||
|
||||
// Select the nearest supported latency
|
||||
var targetLatency float64
|
||||
nearestDist := math.Inf(+1)
|
||||
for _, latency := range latencies {
|
||||
dist := math.Abs(latency - s.Latency)
|
||||
if dist >= nearestDist {
|
||||
break
|
||||
}
|
||||
|
||||
nearestDist = dist
|
||||
targetLatency = latency
|
||||
}
|
||||
|
||||
// Since audio.Reader only supports stereo mode, channels is always 2
|
||||
channels := 2
|
||||
|
||||
engine, err := opus.NewEncoder(s.OutSampleRate, channels, opus.AppVoIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffSize := 1024
|
||||
buff := make([]byte, buffSize)
|
||||
return &encoder{engine, buff}, nil
|
||||
inBuffSize := targetLatency * float64(s.OutSampleRate) / 1000
|
||||
inBuff := make([][2]float32, int(inBuffSize))
|
||||
streamer := audio.ToBeep(r)
|
||||
newSampleRate := beep.SampleRate(s.OutSampleRate)
|
||||
oldSampleRate := beep.SampleRate(s.InSampleRate)
|
||||
streamer = beep.Resample(3, oldSampleRate, newSampleRate, streamer)
|
||||
|
||||
reader := audio.FromBeep(streamer)
|
||||
e := encoder{engine, inBuff, reader}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (e *encoder) Encode(b []int16) ([]byte, error) {
|
||||
frameSize := len(b) // must be interleaved if stereo
|
||||
frameSizeMs := float32(frameSize) * 1000 / 48000
|
||||
switch frameSizeMs {
|
||||
case 2.5, 5, 10, 20, 40, 60:
|
||||
// Good.
|
||||
default:
|
||||
return nil, fmt.Errorf("Illegal frame size: %d bytes (%f ms)", frameSize, frameSizeMs)
|
||||
func flatten(samples [][2]float32) []float32 {
|
||||
if len(samples) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := e.engine.Encode(b, e.buff)
|
||||
data := uintptr(unsafe.Pointer(&samples[0]))
|
||||
l := len(samples) * 2
|
||||
return *(*[]float32)(unsafe.Pointer(&reflect.SliceHeader{Data: data, Len: l, Cap: l}))
|
||||
}
|
||||
|
||||
func (e *encoder) Read(p []byte) (n int, err error) {
|
||||
var curN int
|
||||
|
||||
// While the buffer is not full, keep reading so that we meet the latency requirement
|
||||
for curN < len(e.inBuff) {
|
||||
n, err := e.reader.Read(e.inBuff[curN:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return e.buff[:n], nil
|
||||
curN += n
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err = e.engine.EncodeFloat32(flatten(e.inBuff), p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (e *encoder) Close() error {
|
||||
|
@@ -2,6 +2,8 @@ package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -39,11 +41,11 @@ func BuildVideoDecoder(name string, s VideoSetting) (VideoDecoder, error) {
|
||||
return b(s)
|
||||
}
|
||||
|
||||
func BuildAudioEncoder(name string, s AudioSetting) (AudioEncoder, error) {
|
||||
func BuildAudioEncoder(name string, r audio.Reader, s AudioSetting) (io.ReadCloser, error) {
|
||||
b, ok := audioEncoders[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("codec: can't find %s audio encoder", name)
|
||||
}
|
||||
|
||||
return b(s)
|
||||
return b(r, s)
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
)
|
||||
|
||||
@@ -14,7 +15,6 @@ const (
|
||||
)
|
||||
|
||||
type DataCb func(b []byte)
|
||||
type AudioDataCb func(b []int16)
|
||||
|
||||
type OpenCloser interface {
|
||||
Open() error
|
||||
@@ -42,13 +42,14 @@ type VideoSetting struct {
|
||||
}
|
||||
|
||||
type AudioCapable interface {
|
||||
Start(setting AudioSetting, cb AudioDataCb) error
|
||||
Start(setting AudioSetting) (audio.Reader, error)
|
||||
Stop() error
|
||||
Settings() []AudioSetting
|
||||
}
|
||||
|
||||
type AudioSetting struct {
|
||||
SampleRate int
|
||||
Mono bool
|
||||
}
|
||||
|
||||
type Adapter interface {
|
||||
|
@@ -1,12 +1,16 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/jfreymuth/pulse"
|
||||
"github.com/pion/mediadevices/pkg/io/audio"
|
||||
)
|
||||
|
||||
type microphone struct {
|
||||
c *pulse.Client
|
||||
s *pulse.RecordStream
|
||||
samplesChan chan<- []float32
|
||||
}
|
||||
|
||||
var _ AudioAdapter = µphone{}
|
||||
@@ -34,31 +38,60 @@ func (m *microphone) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *microphone) Start(setting AudioSetting, cb AudioDataCb) error {
|
||||
buff := make([]int16, 960)
|
||||
n := 0
|
||||
handler := func(b []int16) {
|
||||
for n+len(b) >= 960 {
|
||||
nCopied := copy(buff[n:], b)
|
||||
cb(buff)
|
||||
n = 0
|
||||
b = b[nCopied:]
|
||||
func (m *microphone) Start(setting AudioSetting) (audio.Reader, error) {
|
||||
var options []pulse.RecordOption
|
||||
if setting.Mono {
|
||||
options = append(options, pulse.RecordMono)
|
||||
} else {
|
||||
options = append(options, pulse.RecordStereo)
|
||||
}
|
||||
nCopied := copy(buff[n:], b)
|
||||
n += nCopied
|
||||
options = append(options, pulse.RecordSampleRate(48000), pulse.RecordBufferFragmentSize(512))
|
||||
|
||||
samplesChan := make(chan []float32, 1)
|
||||
var buff []float32
|
||||
var bi int
|
||||
var more bool
|
||||
|
||||
reader := audio.ReaderFunc(func(samples [][2]float32) (n int, err error) {
|
||||
for i := range samples {
|
||||
// if we don't have anything left in buff, we'll wait until we receive
|
||||
// more samples
|
||||
if bi == len(buff) {
|
||||
buff, more = <-samplesChan
|
||||
if !more {
|
||||
return i, io.EOF
|
||||
}
|
||||
bi = 0
|
||||
}
|
||||
|
||||
stream, err := m.c.NewRecord(handler, pulse.RecordSampleRate(48000), pulse.RecordLatency(0.005))
|
||||
samples[i][0] = buff[bi]
|
||||
if !setting.Mono {
|
||||
samples[i][1] = buff[bi+1]
|
||||
bi++
|
||||
}
|
||||
bi++
|
||||
}
|
||||
|
||||
return len(samples), nil
|
||||
})
|
||||
|
||||
handler := func(b []float32) {
|
||||
samplesChan <- b
|
||||
}
|
||||
|
||||
stream, err := m.c.NewRecord(handler, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream.Start()
|
||||
m.s = stream
|
||||
return nil
|
||||
m.samplesChan = samplesChan
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (m *microphone) Stop() error {
|
||||
close(m.samplesChan)
|
||||
m.s.Stop()
|
||||
return nil
|
||||
}
|
||||
@@ -71,10 +104,8 @@ func (m *microphone) Info() Info {
|
||||
}
|
||||
|
||||
func (m *microphone) Settings() []AudioSetting {
|
||||
src, err := m.c.DefaultSource()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []AudioSetting{AudioSetting{src.SampleRate()}}
|
||||
return []AudioSetting{AudioSetting{
|
||||
SampleRate: 48000,
|
||||
Mono: false,
|
||||
}}
|
||||
}
|
||||
|
87
pkg/io/audio/beep.go
Normal file
87
pkg/io/audio/beep.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
)
|
||||
|
||||
type beepStreamer struct {
|
||||
err error
|
||||
r Reader
|
||||
buff [][2]float32
|
||||
}
|
||||
|
||||
func ToBeep(r Reader) beep.Streamer {
|
||||
if r == nil {
|
||||
panic("FromReader requires a non-nil Reader")
|
||||
}
|
||||
|
||||
return &beepStreamer{r: r}
|
||||
}
|
||||
|
||||
func (b *beepStreamer) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
// Since there was an error, the stream has to be drained
|
||||
if b.err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if len(b.buff) < len(samples) {
|
||||
b.buff = append(b.buff, make([][2]float32, len(samples)-len(b.buff))...)
|
||||
}
|
||||
|
||||
n, err := b.r.Read(b.buff[:len(samples)])
|
||||
if err != nil {
|
||||
b.err = err
|
||||
if err != io.EOF {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
samples[i][0] = float64(b.buff[i][0])
|
||||
samples[i][1] = float64(b.buff[i][1])
|
||||
}
|
||||
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (b *beepStreamer) Err() error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
type beepReader struct {
|
||||
s beep.Streamer
|
||||
buff [][2]float64
|
||||
}
|
||||
|
||||
func FromBeep(s beep.Streamer) Reader {
|
||||
if s == nil {
|
||||
panic("FromStreamer requires a non-nil beep.Streamer")
|
||||
}
|
||||
|
||||
return &beepReader{s: s}
|
||||
}
|
||||
|
||||
func (r *beepReader) Read(samples [][2]float32) (n int, err error) {
|
||||
if len(r.buff) < len(samples) {
|
||||
r.buff = append(r.buff, make([][2]float64, len(samples)-len(r.buff))...)
|
||||
}
|
||||
|
||||
n, ok := r.s.Stream(r.buff[:len(samples)])
|
||||
if !ok {
|
||||
err := r.s.Err()
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
samples[i][0] = float32(r.buff[i][0])
|
||||
samples[i][1] = float32(r.buff[i][1])
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
52
track.go
52
track.go
@@ -2,6 +2,7 @@ package mediadevices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/codec"
|
||||
@@ -137,7 +138,7 @@ type audioTrack struct {
|
||||
*track
|
||||
d driver.AudioDriver
|
||||
setting driver.AudioSetting
|
||||
encoder codec.AudioEncoder
|
||||
encoder io.ReadCloser
|
||||
}
|
||||
|
||||
var _ Tracker = &audioTrack{}
|
||||
@@ -148,44 +149,43 @@ func newAudioTrack(pc *webrtc.PeerConnection, d driver.AudioDriver, setting driv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encoder, err := codec.BuildAudioEncoder(codecName, codec.AudioSetting{
|
||||
SampleRate: setting.SampleRate,
|
||||
})
|
||||
reader, err := d.Start(setting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vt := audioTrack{
|
||||
codecSetting := codec.AudioSetting{
|
||||
InSampleRate: setting.SampleRate,
|
||||
}
|
||||
|
||||
encoder, err := codec.BuildAudioEncoder(codecName, reader, codecSetting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
at := audioTrack{
|
||||
track: t,
|
||||
d: d,
|
||||
setting: setting,
|
||||
encoder: encoder,
|
||||
}
|
||||
go at.start()
|
||||
return &at, nil
|
||||
}
|
||||
|
||||
err = d.Start(setting, vt.dataCb)
|
||||
func (t *audioTrack) start() {
|
||||
buff := make([]byte, 1024)
|
||||
for {
|
||||
n, err := t.encoder.Read(buff)
|
||||
if err != nil {
|
||||
encoder.Close()
|
||||
return nil, err
|
||||
// TODO: better error handling
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &vt, nil
|
||||
}
|
||||
|
||||
func (vt *audioTrack) dataCb(b []int16) {
|
||||
encoded, err := vt.encoder.Encode(b)
|
||||
if err != nil {
|
||||
// TODO: probably do some logging here
|
||||
return
|
||||
}
|
||||
|
||||
err = vt.s.sample(encoded)
|
||||
if err != nil {
|
||||
// TODO: probably do some logging here
|
||||
return
|
||||
t.s.sample(buff[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func (vt *audioTrack) Stop() {
|
||||
vt.d.Stop()
|
||||
vt.encoder.Close()
|
||||
func (t *audioTrack) Stop() {
|
||||
t.d.Stop()
|
||||
t.encoder.Close()
|
||||
}
|
||||
|
Reference in New Issue
Block a user