diff options
| author | Serge A. Zaitsev <zaitsev.serge@gmail.com> | 2017-10-03 11:53:01 +0300 |
|---|---|---|
| committer | Serge A. Zaitsev <zaitsev.serge@gmail.com> | 2017-10-03 11:53:01 +0300 |
| commit | c8fd4f11425835f0c57e1429e1674da886048b1e (patch) | |
| tree | 2d0be407cc17af4e9c695b06cbc1199d20c64f6b | |
| parent | 5c69780f4bf67e26ed370cea39418b2d82df119e (diff) | |
add go bindings
| -rw-r--r-- | rtaudio.go | 551 | ||||
| -rw-r--r-- | rtaudio_test.go | 71 |
2 files changed, 622 insertions, 0 deletions
diff --git a/rtaudio.go b/rtaudio.go new file mode 100644 index 0000000..0488daf --- /dev/null +++ b/rtaudio.go @@ -0,0 +1,551 @@ +package rtaudio + +/* + +#cgo CXXFLAGS: -g +#cgo LDFLAGS: -lstdc++ -g + +#cgo linux CXXFLAGS: -D__LINUX_ALSA__ +#cgo linux LDFLAGS: -lm -lasound -pthread + +#cgo linux,pulseaudio CXXFLAGS: -D__LINUX_PULSE__ +#cgo linux,pulseaudio LDFLAGS: -lpulse -lpulse-simple + +#cgo jack CXXFLAGS: -D__UNIX_JACK__ +#cgo jack LDFLAGS: -ljack + +#cgo windows CXXFLAGS: -D__WINDOWS_WASAPI__ +#cgo windows LDFLAGS: -lm -luuid -lksuser -lwinmm -lole32 + +#cgo darwin CXXFLAGS: -D__MACOSX_CORE__ +#cgo darwin LDFLAGS: -framework CoreAudio -framework CoreFoundation + +#include "rtaudio_c.h" + +extern int goCallback(void *out, void *in, unsigned int nFrames, + double stream_time, rtaudio_stream_status_t status, + void *userdata); + +*/ +import "C" +import ( + "errors" + "sync" + "time" + "unsafe" +) + +// API is an enumeration of available compiled APIs. Supported API include +// Alsa/PulseAudio/OSS, Jack, CoreAudio, WASAPI/ASIO/DS and dummy API. +type API C.rtaudio_api_t + +const ( + // APIUnspecified looks for a working compiled API. + APIUnspecified API = C.RTAUDIO_API_UNSPECIFIED + // APILinuxALSA uses the Advanced Linux Sound Architecture API. + APILinuxALSA = C.RTAUDIO_API_LINUX_ALSA + // APILinuxPulse uses the Linux PulseAudio API. + APILinuxPulse = C.RTAUDIO_API_LINUX_PULSE + // APILinuxOSS uses the Linux Open Sound System API. + APILinuxOSS = C.RTAUDIO_API_LINUX_OSS + // APIUnixJack uses the Jack Low-Latency Audio Server API. + APIUnixJack = C.RTAUDIO_API_UNIX_JACK + // APIMacOSXCore uses Macintosh OS-X Core Audio API. + APIMacOSXCore = C.RTAUDIO_API_MACOSX_CORE + // APIWindowsWASAPI uses the Microsoft WASAPI API. + APIWindowsWASAPI = C.RTAUDIO_API_WINDOWS_WASAPI + // APIWindowsASIO uses the Steinberg Audio Stream I/O API. + APIWindowsASIO = C.RTAUDIO_API_WINDOWS_ASIO + // APIWindowsDS uses the Microsoft Direct Sound API. + APIWindowsDS = C.RTAUDIO_API_WINDOWS_DS + // APIDummy is a compilable but non-functional API. + APIDummy = C.RTAUDIO_API_DUMMY +) + +func (api API) String() string { + switch api { + case APIUnspecified: + return "unspecified" + case APILinuxALSA: + return "alsa" + case APILinuxPulse: + return "pulse" + case APILinuxOSS: + return "oss" + case APIUnixJack: + return "jack" + case APIMacOSXCore: + return "coreaudio" + case APIWindowsWASAPI: + return "wasapi" + case APIWindowsASIO: + return "asio" + case APIWindowsDS: + return "directsound" + case APIDummy: + return "dummy" + } + return "?" +} + +// StreamStatus defines over- or underflow flags in the audio callback. +type StreamStatus C.rtaudio_stream_status_t + +const ( + // StatusInputOverflow indicates that data was discarded because of an + // overflow condition at the driver. + StatusInputOverflow StreamStatus = C.RTAUDIO_STATUS_INPUT_OVERFLOW + // StatusOutputUnderflow indicates that the output buffer ran low, likely + // producing a break in the output sound. + StatusOutputUnderflow StreamStatus = C.RTAUDIO_STATUS_OUTPUT_UNDERFLOW +) + +// Version returns current RtAudio library version string. +func Version() string { + return C.GoString(C.rtaudio_version()) +} + +// CompiledAPI determines the available compiled audio APIs. +func CompiledAPI() (apis []API) { + capis := (*[1 << 30]C.rtaudio_api_t)(unsafe.Pointer(C.rtaudio_compiled_api())) + for i := 0; ; i++ { + api := capis[i] + if api == C.RTAUDIO_API_UNSPECIFIED { + break + } + apis = append(apis, API(api)) + } + return apis +} + +// DeviceInfo is the public device information structure for returning queried values. +type DeviceInfo struct { + Name string + Probed bool + NumOutputChannels int + NumInputChannels int + NumDuplexChannels int + IsDefaultOutput bool + IsDefaultInput bool + + //rtaudio_format_t native_formats; + + PreferredSampleRate uint + SampleRates []int +} + +// StreamParams is the structure for specifying input or ouput stream parameters. +type StreamParams struct { + DeviceID uint + NumChannels uint + FirstChannel uint +} + +// StreamFlags is a set of RtAudio stream option flags. +type StreamFlags C.rtaudio_stream_flags_t + +const ( + // FlagsNoninterleaved is set to use non-interleaved buffers (default = interleaved). + FlagsNoninterleaved = C.RTAUDIO_FLAGS_NONINTERLEAVED + // FlagsMinimizeLatency when set attempts to configure stream parameters for lowest possible latency. + FlagsMinimizeLatency = C.RTAUDIO_FLAGS_MINIMIZE_LATENCY + // FlagsHogDevice when set attempts to grab device for exclusive use. + FlagsHogDevice = C.RTAUDIO_FLAGS_HOG_DEVICE + // FlagsScheduleRealtime is set in attempt to select realtime scheduling (round-robin) for the callback thread. + FlagsScheduleRealtime = C.RTAUDIO_FLAGS_SCHEDULE_REALTIME + // FlagsAlsaUseDefault is set to use the "default" PCM device (ALSA only). + FlagsAlsaUseDefault = C.RTAUDIO_FLAGS_ALSA_USE_DEFAULT +) + +// StreamOptions is the structure for specifying stream options. +type StreamOptions struct { + Flags StreamFlags + NumBuffers uint + Priotity int + Name string +} + +// RtAudio is a "controller" used to select an available audio i/o interface. +type RtAudio interface { + Destroy() + CurrentAPI() API + Devices() ([]DeviceInfo, error) + DefaultOutputDevice() int + DefaultInputDevice() int + + Open(out, in *StreamParams, format Format, sampleRate uint, frames uint, cb Callback, opts *StreamOptions) error + Close() + Start() error + Stop() error + Abort() error + + IsOpen() bool + IsRunning() bool + + Latency() (int, error) + SampleRate() (uint, error) + Time() (time.Duration, error) + SetTime(time.Duration) error + + ShowWarnings(bool) +} + +type rtaudio struct { + audio C.rtaudio_t + cb Callback + inputChannels int + outputChannels int + format Format +} + +var _ RtAudio = &rtaudio{} + +// Create a new RtAudio instance using the given API. +func Create(api API) (RtAudio, error) { + audio := C.rtaudio_create(C.rtaudio_api_t(api)) + if C.rtaudio_error(audio) != nil { + return nil, errors.New(C.GoString(C.rtaudio_error(audio))) + } + return &rtaudio{audio: audio}, nil +} + +func (audio *rtaudio) Destroy() { + C.rtaudio_destroy(audio.audio) +} + +func (audio *rtaudio) CurrentAPI() API { + return API(C.rtaudio_current_api(audio.audio)) +} + +func (audio *rtaudio) DefaultInputDevice() int { + return int(C.rtaudio_get_default_input_device(audio.audio)) +} + +func (audio *rtaudio) DefaultOutputDevice() int { + return int(C.rtaudio_get_default_output_device(audio.audio)) +} + +func (audio *rtaudio) Devices() ([]DeviceInfo, error) { + n := C.rtaudio_device_count(audio.audio) + devices := []DeviceInfo{} + for i := C.int(0); i < n; i++ { + cinfo := C.rtaudio_get_device_info(audio.audio, i) + if C.rtaudio_error(audio.audio) != nil { + return nil, errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + sr := []int{} + for _, r := range cinfo.sample_rates { + if r == 0 { + break + } + sr = append(sr, int(r)) + } + devices = append(devices, DeviceInfo{ + Name: C.GoString(&cinfo.name[0]), + Probed: cinfo.probed != 0, + NumInputChannels: int(cinfo.input_channels), + NumOutputChannels: int(cinfo.output_channels), + NumDuplexChannels: int(cinfo.duplex_channels), + IsDefaultOutput: cinfo.is_default_output != 0, + IsDefaultInput: cinfo.is_default_input != 0, + PreferredSampleRate: uint(cinfo.preferred_sample_rate), + SampleRates: sr, + }) + // TODO: formats + } + return devices, nil +} + +// Format defines RtAudio data format type. +type Format int + +const ( + // FormatInt8 uses 8-bit signed integer. + FormatInt8 Format = C.RTAUDIO_FORMAT_SINT8 + // FormatInt16 uses 16-bit signed integer. + FormatInt16 = C.RTAUDIO_FORMAT_SINT16 + // FormatInt24 uses 24-bit signed integer. + FormatInt24 = C.RTAUDIO_FORMAT_SINT24 + // FormatInt32 uses 32-bit signed integer. + FormatInt32 = C.RTAUDIO_FORMAT_SINT32 + // FormatFloat32 uses 32-bit floating point values normalized between (-1..1). + FormatFloat32 = C.RTAUDIO_FORMAT_FLOAT32 + // FormatFloat64 uses 64-bit floating point values normalized between (-1..1). + FormatFloat64 = C.RTAUDIO_FORMAT_FLOAT64 +) + +// Buffer is a common interface for audio buffers of various data format types. +type Buffer interface { + Len() int + Int8() []int8 + Int16() []int16 + Int24() []Int24 + Int32() []int32 + Float32() []float32 + Float64() []float64 +} + +// Int24 is a helper type to convert int32 values to int24 and back. +type Int24 [3]byte + +// Set Int24 value using the least significant bytes of the given number n. +func (i *Int24) Set(n int32) { + (*i)[0], (*i)[1], (*i)[2] = byte(n&0xff), byte((n&0xff00)>>8), byte((n&0xff0000)>>16) +} + +// Get Int24 value as int32. +func (i Int24) Get() int32 { + n := int32(i[0]) | int32(i[1])<<8 | int32(i[2])<<16 + if n&0x800000 != 0 { + n |= ^0xffffff + } + return n +} + +type buffer struct { + format Format + length int + numChannels int + ptr unsafe.Pointer +} + +func (b *buffer) Len() int { + if b.ptr == nil { + return 0 + } + return b.length +} + +func (b *buffer) Int8() []int8 { + if b.format != FormatInt8 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]int8)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +func (b *buffer) Int16() []int16 { + if b.format != FormatInt16 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]int16)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +func (b *buffer) Int24() []Int24 { + if b.format != FormatInt24 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]Int24)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +func (b *buffer) Int32() []int32 { + if b.format != FormatInt32 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]int32)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +func (b *buffer) Float32() []float32 { + if b.format != FormatFloat32 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]float32)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +func (b *buffer) Float64() []float64 { + if b.format != FormatFloat64 { + return nil + } + if b.ptr == nil { + return nil + } + return (*[1 << 30]float64)(b.ptr)[:b.length*b.numChannels : b.length*b.numChannels] +} + +// Callback is a client-defined function that will be invoked when input data +// is available and/or output data is needed. +type Callback func(out Buffer, in Buffer, dur time.Duration, status StreamStatus) int + +var ( + mu sync.Mutex + audios = map[int]*rtaudio{} +) + +func registerAudio(a *rtaudio) int { + mu.Lock() + defer mu.Unlock() + for i := 0; ; i++ { + if _, ok := audios[i]; !ok { + audios[i] = a + return i + } + } +} + +func unregisterAudio(a *rtaudio) { + mu.Lock() + defer mu.Unlock() + for i := 0; i < len(audios); i++ { + if audios[i] == a { + delete(audios, i) + return + } + } +} + +func findAudio(k int) *rtaudio { + mu.Lock() + defer mu.Unlock() + return audios[k] +} + +//export goCallback +func goCallback(out, in unsafe.Pointer, frames C.uint, sec C.double, + status C.rtaudio_stream_status_t, userdata unsafe.Pointer) C.int { + + k := int(uintptr(userdata)) + audio := findAudio(k) + dur := time.Duration(time.Microsecond * time.Duration(sec*1000000.0)) + inbuf := &buffer{audio.format, int(frames), audio.inputChannels, in} + outbuf := &buffer{audio.format, int(frames), audio.outputChannels, out} + return C.int(audio.cb(outbuf, inbuf, dur, StreamStatus(status))) +} + +func (audio *rtaudio) Open(out, in *StreamParams, format Format, sampleRate uint, + frames uint, cb Callback, opts *StreamOptions) error { + var ( + cInPtr *C.rtaudio_stream_parameters_t + cOutPtr *C.rtaudio_stream_parameters_t + cOptsPtr *C.rtaudio_stream_options_t + cIn C.rtaudio_stream_parameters_t + cOut C.rtaudio_stream_parameters_t + cOpts C.rtaudio_stream_options_t + ) + + audio.inputChannels = 0 + audio.outputChannels = 0 + if out != nil { + audio.outputChannels = int(out.NumChannels) + cOut.device_id = C.uint(out.DeviceID) + cOut.num_channels = C.uint(out.NumChannels) + cOut.first_channel = C.uint(out.FirstChannel) + cOutPtr = &cOut + } + if in != nil { + audio.inputChannels = int(in.NumChannels) + cIn.device_id = C.uint(in.DeviceID) + cIn.num_channels = C.uint(in.NumChannels) + cIn.first_channel = C.uint(in.FirstChannel) + cInPtr = &cIn + } + if opts != nil { + cOpts.flags = C.rtaudio_stream_flags_t(opts.Flags) + cOpts.num_buffers = C.uint(opts.NumBuffers) + cOpts.priority = C.int(opts.Priotity) + cOptsPtr = &cOpts + } + framesCount := C.uint(frames) + audio.format = format + audio.cb = cb + + k := registerAudio(audio) + C.rtaudio_open_stream(audio.audio, cOutPtr, cInPtr, + C.rtaudio_format_t(format), C.uint(sampleRate), &framesCount, + C.rtaudio_cb_t(C.goCallback), unsafe.Pointer(uintptr(k)), cOptsPtr, nil) + if C.rtaudio_error(audio.audio) != nil { + return errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return nil +} + +func (audio *rtaudio) Close() { + unregisterAudio(audio) + C.rtaudio_close_stream(audio.audio) +} + +func (audio *rtaudio) Start() error { + C.rtaudio_start_stream(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return nil +} + +func (audio *rtaudio) Stop() error { + C.rtaudio_stop_stream(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return nil +} + +func (audio *rtaudio) Abort() error { + C.rtaudio_abort_stream(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return nil +} + +func (audio *rtaudio) IsOpen() bool { + return C.rtaudio_is_stream_open(audio.audio) != 0 +} + +func (audio *rtaudio) IsRunning() bool { + return C.rtaudio_is_stream_running(audio.audio) != 0 +} + +func (audio *rtaudio) Latency() (int, error) { + latency := C.rtaudio_get_stream_latency(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return int(latency), nil +} + +func (audio *rtaudio) SampleRate() (uint, error) { + sampleRate := C.rtaudio_get_stream_sample_rate(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return uint(sampleRate), nil +} + +func (audio *rtaudio) Time() (time.Duration, error) { + sec := C.rtaudio_get_stream_time(audio.audio) + if C.rtaudio_error(audio.audio) != nil { + return 0, errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return time.Duration(time.Microsecond * time.Duration(sec*1000000.0)), nil +} + +func (audio *rtaudio) SetTime(t time.Duration) error { + sec := float64(t) * 1000000.0 / float64(time.Microsecond) + C.rtaudio_set_stream_time(audio.audio, C.double(sec)) + if C.rtaudio_error(audio.audio) != nil { + return errors.New(C.GoString(C.rtaudio_error(audio.audio))) + } + return nil +} + +func (audio *rtaudio) ShowWarnings(show bool) { + if show { + C.rtaudio_show_warnings(audio.audio, 1) + } else { + C.rtaudio_show_warnings(audio.audio, 0) + } +} diff --git a/rtaudio_test.go b/rtaudio_test.go new file mode 100644 index 0000000..591ed9a --- /dev/null +++ b/rtaudio_test.go @@ -0,0 +1,71 @@ +package rtaudio + +import ( + "log" + "math" + "time" +) + +func ExampleCompiledAPI() { + log.Println("RtAudio version: ", Version()) + for _, api := range CompiledAPI() { + log.Println("Compiled API: ", api) + } +} + +func ExampleListDevices() { + audio, err := Create(APIUnspecified) + if err != nil { + log.Fatal(err) + } + defer audio.Destroy() + devices, err := audio.Devices() + if err != nil { + log.Fatal(err) + } + for _, d := range devices { + log.Printf("Audio device: %#v\n", d) + } +} + +func ExamplePlaySine() { + const ( + sampleRate = 44100 + bufSz = 512 + freq = 440.0 + ) + phase := 0.0 + audio, err := Create(APIUnspecified) + if err != nil { + log.Fatal(err) + } + defer audio.Destroy() + + params := StreamParams{ + DeviceID: uint(audio.DefaultOutputDevice()), + NumChannels: 2, + FirstChannel: 0, + } + options := StreamOptions{ + Flags: FlagsAlsaUseDefault, + } + cb := func(out, in Buffer, dur time.Duration, status StreamStatus) int { + samples := out.Float32() + for i := 0; i < len(samples)/2; i++ { + sample := float32(math.Sin(2 * math.Pi * phase)) + phase += freq / sampleRate + + samples[i*2] = sample + samples[i*2+1] = sample + } + return 0 + } + err = audio.Open(¶ms, nil, FormatFloat32, sampleRate, bufSz, cb, &options) + if err != nil { + log.Fatal(err) + } + defer audio.Close() + audio.Start() + defer audio.Stop() + time.Sleep(3 * time.Second) +} |
