Update audio APIs to use audio.Reader instead

This commit is contained in:
Lukas Herman
2020-01-21 21:18:56 -08:00
parent d1129816ac
commit 9466cf89fc
9 changed files with 289 additions and 80 deletions

3
go.mod
View File

@@ -4,8 +4,11 @@ go 1.13
require ( require (
github.com/blackjack/webcam v0.0.0-20191123110216-08fa32efcb67 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/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140
github.com/pion/webrtc/v2 v2.1.19-0.20200106051345-726a16faa60d github.com/pion/webrtc/v2 v2.1.19-0.20200106051345-726a16faa60d
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6 gopkg.in/hraban/opus.v2 v2.0.0-20191117073431-57179dff69a6
) )
replace github.com/faiface/beep => ../../faiface/beep

25
go.sum
View File

@@ -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/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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 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 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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 h1:z8Rh8GKvXMJ3sQfXYWCF5NMLnVshzqdPtjvcfyPMrgQ=
github.com/jfreymuth/pulse v0.0.0-20200107133239-fe42f62ea140/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/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 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= 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 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= 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.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 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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-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 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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 h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -1,6 +1,10 @@
package codec package codec
import "image" import (
"github.com/pion/mediadevices/pkg/io/audio"
"image"
"io"
)
type VideoEncoder interface { type VideoEncoder interface {
Encode(img image.Image) ([]byte, error) Encode(img image.Image) ([]byte, error)
@@ -21,13 +25,10 @@ type VideoSetting struct {
type VideoEncoderBuilder func(s VideoSetting) (VideoEncoder, error) type VideoEncoderBuilder func(s VideoSetting) (VideoEncoder, error)
type VideoDecoderBuilder func(s VideoSetting) (VideoDecoder, error) type VideoDecoderBuilder func(s VideoSetting) (VideoDecoder, error)
type AudioEncoder interface {
Encode([]int16) ([]byte, error)
Close() error
}
type AudioSetting struct { 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)

View File

@@ -2,52 +2,111 @@ package opus
import ( import (
"fmt" "fmt"
"io"
"math"
"reflect"
"unsafe"
"github.com/faiface/beep"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/webrtc/v2" "github.com/pion/webrtc/v2"
"gopkg.in/hraban/opus.v2" "gopkg.in/hraban/opus.v2"
) )
type encoder struct { type encoder struct {
engine *opus.Encoder 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) var _ codec.AudioEncoderBuilder = codec.AudioEncoderBuilder(NewEncoder)
func init() { func init() {
codec.Register(webrtc.Opus, codec.AudioEncoderBuilder(NewEncoder)) codec.Register(webrtc.Opus, codec.AudioEncoderBuilder(NewEncoder))
} }
func NewEncoder(s codec.AudioSetting) (codec.AudioEncoder, error) { func NewEncoder(r audio.Reader, s codec.AudioSetting) (io.ReadCloser, error) {
channels := 1 // mono if s.InSampleRate == 0 {
engine, err := opus.NewEncoder(48000, channels, opus.AppVoIP) 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 { if err != nil {
return nil, err return nil, err
} }
buffSize := 1024 inBuffSize := targetLatency * float64(s.OutSampleRate) / 1000
buff := make([]byte, buffSize) inBuff := make([][2]float32, int(inBuffSize))
return &encoder{engine, buff}, nil 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) { func flatten(samples [][2]float32) []float32 {
frameSize := len(b) // must be interleaved if stereo if len(samples) == 0 {
frameSizeMs := float32(frameSize) * 1000 / 48000 return nil
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)
} }
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 0, err
}
curN += n
}
if err != nil { if err != nil {
return nil, err return 0, err
} }
return e.buff[:n], nil n, err = e.engine.EncodeFloat32(flatten(e.inBuff), p)
if err != nil {
return n, err
}
return n, nil
} }
func (e *encoder) Close() error { func (e *encoder) Close() error {

View File

@@ -2,6 +2,8 @@ package codec
import ( import (
"fmt" "fmt"
"github.com/pion/mediadevices/pkg/io/audio"
"io"
) )
var ( var (
@@ -39,11 +41,11 @@ func BuildVideoDecoder(name string, s VideoSetting) (VideoDecoder, error) {
return b(s) 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] b, ok := audioEncoders[name]
if !ok { if !ok {
return nil, fmt.Errorf("codec: can't find %s audio encoder", name) return nil, fmt.Errorf("codec: can't find %s audio encoder", name)
} }
return b(s) return b(r, s)
} }

View File

@@ -1,6 +1,7 @@
package driver package driver
import ( import (
"github.com/pion/mediadevices/pkg/io/audio"
"github.com/pion/mediadevices/pkg/frame" "github.com/pion/mediadevices/pkg/frame"
) )
@@ -14,7 +15,6 @@ const (
) )
type DataCb func(b []byte) type DataCb func(b []byte)
type AudioDataCb func(b []int16)
type OpenCloser interface { type OpenCloser interface {
Open() error Open() error
@@ -42,13 +42,14 @@ type VideoSetting struct {
} }
type AudioCapable interface { type AudioCapable interface {
Start(setting AudioSetting, cb AudioDataCb) error Start(setting AudioSetting) (audio.Reader, error)
Stop() error Stop() error
Settings() []AudioSetting Settings() []AudioSetting
} }
type AudioSetting struct { type AudioSetting struct {
SampleRate int SampleRate int
Mono bool
} }
type Adapter interface { type Adapter interface {

View File

@@ -1,12 +1,16 @@
package driver package driver
import ( import (
"io"
"github.com/jfreymuth/pulse" "github.com/jfreymuth/pulse"
"github.com/pion/mediadevices/pkg/io/audio"
) )
type microphone struct { type microphone struct {
c *pulse.Client c *pulse.Client
s *pulse.RecordStream s *pulse.RecordStream
samplesChan chan<- []float32
} }
var _ AudioAdapter = &microphone{} var _ AudioAdapter = &microphone{}
@@ -34,31 +38,60 @@ func (m *microphone) Close() error {
return nil return nil
} }
func (m *microphone) Start(setting AudioSetting, cb AudioDataCb) error { func (m *microphone) Start(setting AudioSetting) (audio.Reader, error) {
buff := make([]int16, 960) var options []pulse.RecordOption
n := 0 if setting.Mono {
handler := func(b []int16) { options = append(options, pulse.RecordMono)
for n+len(b) >= 960 { } else {
nCopied := copy(buff[n:], b) options = append(options, pulse.RecordStereo)
cb(buff) }
n = 0 options = append(options, pulse.RecordSampleRate(48000), pulse.RecordBufferFragmentSize(512))
b = b[nCopied:]
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
}
samples[i][0] = buff[bi]
if !setting.Mono {
samples[i][1] = buff[bi+1]
bi++
}
bi++
} }
nCopied := copy(buff[n:], b)
n += nCopied return len(samples), nil
})
handler := func(b []float32) {
samplesChan <- b
} }
stream, err := m.c.NewRecord(handler, pulse.RecordSampleRate(48000), pulse.RecordLatency(0.005)) stream, err := m.c.NewRecord(handler, options...)
if err != nil { if err != nil {
return err return nil, err
} }
stream.Start() stream.Start()
m.s = stream m.s = stream
return nil m.samplesChan = samplesChan
return reader, nil
} }
func (m *microphone) Stop() error { func (m *microphone) Stop() error {
close(m.samplesChan)
m.s.Stop() m.s.Stop()
return nil return nil
} }
@@ -71,10 +104,8 @@ func (m *microphone) Info() Info {
} }
func (m *microphone) Settings() []AudioSetting { func (m *microphone) Settings() []AudioSetting {
src, err := m.c.DefaultSource() return []AudioSetting{AudioSetting{
if err != nil { SampleRate: 48000,
return nil Mono: false,
} }}
return []AudioSetting{AudioSetting{src.SampleRate()}}
} }

87
pkg/io/audio/beep.go Normal file
View 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
}

View File

@@ -2,6 +2,7 @@ package mediadevices
import ( import (
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"github.com/pion/mediadevices/pkg/codec" "github.com/pion/mediadevices/pkg/codec"
@@ -137,7 +138,7 @@ type audioTrack struct {
*track *track
d driver.AudioDriver d driver.AudioDriver
setting driver.AudioSetting setting driver.AudioSetting
encoder codec.AudioEncoder encoder io.ReadCloser
} }
var _ Tracker = &audioTrack{} var _ Tracker = &audioTrack{}
@@ -148,44 +149,43 @@ func newAudioTrack(pc *webrtc.PeerConnection, d driver.AudioDriver, setting driv
return nil, err return nil, err
} }
encoder, err := codec.BuildAudioEncoder(codecName, codec.AudioSetting{ reader, err := d.Start(setting)
SampleRate: setting.SampleRate,
})
if err != nil { if err != nil {
return nil, err 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, track: t,
d: d, d: d,
setting: setting, setting: setting,
encoder: encoder, encoder: encoder,
} }
go at.start()
err = d.Start(setting, vt.dataCb) return &at, nil
if err != nil {
encoder.Close()
return nil, err
}
return &vt, nil
} }
func (vt *audioTrack) dataCb(b []int16) { func (t *audioTrack) start() {
encoded, err := vt.encoder.Encode(b) buff := make([]byte, 1024)
if err != nil { for {
// TODO: probably do some logging here n, err := t.encoder.Read(buff)
return if err != nil {
} // TODO: better error handling
panic(err)
err = vt.s.sample(encoded) }
if err != nil { t.s.sample(buff[:n])
// TODO: probably do some logging here
return
} }
} }
func (vt *audioTrack) Stop() { func (t *audioTrack) Stop() {
vt.d.Stop() t.d.Stop()
vt.encoder.Close() t.encoder.Close()
} }