Skip to content

Commit

Permalink
added hostapi for audio devices
Browse files Browse the repository at this point in the history
  • Loading branch information
dh1tw committed Jul 15, 2019
1 parent 9dbb92d commit c623381
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 38 deletions.
2 changes: 2 additions & 0 deletions .remoteAudio.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ device-name = "default"
samplerate = 48000
latency = "5ms"
channels = 1
hostapi = "default"

[output-device]
device-name = "default"
samplerate = 48000
latency = "5ms"
channels = 2
hostapi = "default"

[opus]
application = "restricted_lowdelay"
Expand Down
9 changes: 9 additions & 0 deletions audio/sinks/scWriter/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Option func(*Options)

// Options contains the parameters for initializing a sound card writer.
type Options struct {
HostAPI string
DeviceName string
Channels int
Samplerate float64
Expand All @@ -15,6 +16,14 @@ type Options struct {
RingBufferSize int
}

// HostAPI is a functional option to enforce the usage of a particular
// audio host API
func HostAPI(hostAPI string) Option {
return func(args *Options) {
args.HostAPI = hostAPI
}
}

// DeviceName is a functional option to specify the name of the
// Audio device
func DeviceName(name string) Option {
Expand Down
110 changes: 95 additions & 15 deletions audio/sinks/scWriter/scWriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package scWriter
import (
"fmt"
"log"
"runtime"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -41,21 +43,17 @@ func NewScWriter(opts ...Option) (*ScWriter, error) {
return nil, err
}

info, err := pa.DefaultOutputDevice()
if err != nil {
return nil, err
}

w := &ScWriter{
options: Options{
DeviceName: "default",
HostAPI: "default",
Channels: 2,
Samplerate: 48000,
FramesPerBuffer: 480,
RingBufferSize: 10,
Latency: time.Millisecond * 10,
},
deviceInfo: info,
deviceInfo: nil,
ring: ringBuffer.Ring{},
volume: 0.7,
}
Expand All @@ -76,13 +74,47 @@ func NewScWriter(opts ...Option) (*ScWriter, error) {
ratio: 1,
}

// select Playback Audio Device
if w.options.DeviceName != "default" {
device, err := getPaDevice(w.options.DeviceName)
var hostAPI *pa.HostApiInfo

if w.options.HostAPI == "default" {
switch runtime.GOOS {
case "windows":
// try to use WASAPI since it provides lower latency than the
// other windows audio apis
ha, err := pa.HostApi(pa.WASAPI)
if err != nil {
// try to fallback to the default API
ha, err = pa.DefaultHostApi()
if err != nil {
return nil, fmt.Errorf("unable to determine the default host api - please provide a specific host api")
}
}
hostAPI = ha
default:
// all other OS
ha, err := pa.DefaultHostApi()
if err != nil {
return nil, fmt.Errorf("unable to determine the default host api - please provide a specific host api")
}
hostAPI = ha
}
} else {
// non-default HostAPI
ha, err := getHostAPI(w.options.HostAPI)
if err != nil {
return nil, err
}
hostAPI = ha
}

if w.options.DeviceName == "default" {
w.deviceInfo = hostAPI.DefaultOutputDevice
} else {
dev, err := getPaDevice(w.options.DeviceName, hostAPI)
if err != nil {
return nil, err
}
w.deviceInfo = device
w.deviceInfo = dev
}

// setup Audio Stream
Expand All @@ -109,6 +141,7 @@ func NewScWriter(opts ...Option) (*ScWriter, error) {
}

w.stream = stream
log.Printf("output sound device: %s, HostAPI: %s\n", w.deviceInfo.Name, w.deviceInfo.HostApi.Name)

return w, nil
}
Expand Down Expand Up @@ -323,16 +356,63 @@ func (p *ScWriter) Flush() {
p.ring.SetCapacity(p.options.RingBufferSize)
}

// getHostAPI takes the name of a supported portaudio host api and returns
// the corresponding portaudio hostApiInfo object
func getHostAPI(name string) (*pa.HostApiInfo, error) {

var hostAPIType pa.HostApiType

switch strings.ToLower(name) {
case "indevelopment":
hostAPIType = pa.InDevelopment
case "directsound":
hostAPIType = pa.DirectSound
case "mme":
hostAPIType = pa.MME
case "asio":
hostAPIType = pa.ASIO
case "soundmanager":
hostAPIType = pa.SoundManager
case "coreaudio":
hostAPIType = pa.CoreAudio
case "oss":
hostAPIType = pa.OSS
case "alsa":
hostAPIType = pa.ALSA
case "al":
hostAPIType = pa.AL
case "beos":
hostAPIType = pa.BeOS
case "wdmks":
hostAPIType = pa.WDMkS
case "jack":
hostAPIType = pa.JACK
case "wasapi":
hostAPIType = pa.WASAPI
case "audiosciencehpi":
hostAPIType = pa.AudioScienceHPI
default:
return nil, fmt.Errorf("unknown host api type: %s", name)
}

hostAPIInfo, err := pa.HostApi(hostAPIType)
if err != nil {
return nil, fmt.Errorf("unable to load host api %s: %s", name, err.Error())
}

return hostAPIInfo, nil

}

// getPaDevice checks if the Audio Devices actually exist and
// then returns it
func getPaDevice(name string) (*pa.DeviceInfo, error) {
devices, _ := pa.Devices()
for _, device := range devices {
if device.Name == name {
func getPaDevice(name string, hostAPI *pa.HostApiInfo) (*pa.DeviceInfo, error) {
for _, device := range hostAPI.Devices {
if strings.ToLower(device.Name) == strings.ToLower(name) {
return device, nil
}
}
return nil, fmt.Errorf("unknown audio device %s", name)
return nil, fmt.Errorf("unknown audio device '%s'", name)
}

// Write converts the frames in the audio buffer into the right format
Expand Down
9 changes: 9 additions & 0 deletions audio/sources/scReader/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Option func(*Options)

// Options contains the parameters for initializing a soundcard reader.
type Options struct {
HostAPI string
DeviceName string
Channels int
Samplerate float64
Expand All @@ -19,6 +20,14 @@ type Options struct {
Callback audio.OnDataCb
}

// HostAPI is a functional option to enforce the usage of a particular
// audio host API
func HostAPI(hostAPI string) Option {
return func(args *Options) {
args.HostAPI = hostAPI
}
}

// DeviceName is a functional option to specify the name of the
// Audio device
func DeviceName(name string) Option {
Expand Down
108 changes: 94 additions & 14 deletions audio/sources/scReader/scReader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package scReader
import (
"fmt"
"log"
"runtime"
"strings"
"sync"
"time"

Expand All @@ -28,32 +30,62 @@ func NewScReader(opts ...Option) (*ScReader, error) {
return nil, err
}

info, err := pa.DefaultInputDevice()
if err != nil {
return nil, err
}

r := &ScReader{
options: Options{
HostAPI: "default",
DeviceName: "default",
Channels: 1,
Samplerate: 48000,
FramesPerBuffer: 480,
Latency: time.Millisecond * 10,
},
deviceInfo: info,
deviceInfo: nil,
}

for _, option := range opts {
option(&r.options)
}

if r.options.DeviceName != "default" {
device, err := getPaDevice(r.options.DeviceName)
var hostAPI *pa.HostApiInfo

if r.options.HostAPI == "default" {
switch runtime.GOOS {
case "windows":
// try to use WASAPI since it provides lower latency than the
// other windows audio apis
ha, err := pa.HostApi(pa.WASAPI)
if err != nil {
// try to fallback to the default API
ha, err = pa.DefaultHostApi()
if err != nil {
return nil, fmt.Errorf("unable to determine the default host api - please provide a specific host api")
}
}
hostAPI = ha
default:
// all other OS
ha, err := pa.DefaultHostApi()
if err != nil {
return nil, fmt.Errorf("unable to determine the default host api - please provide a specific host api")
}
hostAPI = ha
}
} else {
ha, err := getHostAPI(r.options.HostAPI)
if err != nil {
return nil, err
}
hostAPI = ha
}

if r.options.DeviceName == "default" {
r.deviceInfo = hostAPI.DefaultInputDevice
} else {
dev, err := getPaDevice(r.options.DeviceName, hostAPI)
if err != nil {
return nil, err
}
r.deviceInfo = device
r.deviceInfo = dev
}

// setup Audio Stream
Expand All @@ -77,6 +109,7 @@ func NewScReader(opts ...Option) (*ScReader, error) {
}
r.stream = stream

log.Printf("input sound device: %s, HostAPI: %s\n", r.deviceInfo.Name, r.deviceInfo.HostApi.Name)
return r, nil
}

Expand Down Expand Up @@ -144,14 +177,61 @@ func (r *ScReader) Close() error {
return nil
}

// getHostAPI takes the name of a supported portaudio host api and returns
// the corresponding portaudio hostApiInfo object
func getHostAPI(name string) (*pa.HostApiInfo, error) {

var hostAPIType pa.HostApiType

switch strings.ToLower(name) {
case "indevelopment":
hostAPIType = pa.InDevelopment
case "directsound":
hostAPIType = pa.DirectSound
case "mme":
hostAPIType = pa.MME
case "asio":
hostAPIType = pa.ASIO
case "soundmanager":
hostAPIType = pa.SoundManager
case "coreaudio":
hostAPIType = pa.CoreAudio
case "oss":
hostAPIType = pa.OSS
case "alsa":
hostAPIType = pa.ALSA
case "al":
hostAPIType = pa.AL
case "beos":
hostAPIType = pa.BeOS
case "wdmks":
hostAPIType = pa.WDMkS
case "jack":
hostAPIType = pa.JACK
case "wasapi":
hostAPIType = pa.WASAPI
case "audiosciencehpi":
hostAPIType = pa.AudioScienceHPI
default:
return nil, fmt.Errorf("unknown host api type: %s", name)
}

hostAPIInfo, err := pa.HostApi(hostAPIType)
if err != nil {
return nil, fmt.Errorf("unable to load host api %s: %s", name, err.Error())
}

return hostAPIInfo, nil

}

// getPaDevice checks if the Audio Devices actually exist and
// then returns it
func getPaDevice(name string) (*pa.DeviceInfo, error) {
devices, _ := pa.Devices()
for _, device := range devices {
if device.Name == name {
func getPaDevice(name string, hostAPI *pa.HostApiInfo) (*pa.DeviceInfo, error) {
for _, device := range hostAPI.Devices {
if strings.ToLower(device.Name) == strings.ToLower(name) {
return device, nil
}
}
return nil, fmt.Errorf("unknown audio device %s", name)
return nil, fmt.Errorf("unknown audio device '%s'", name)
}
Loading

0 comments on commit c623381

Please sign in to comment.