Updated everything for dependencies. All sub packages are now part of the project. This was a massive update, hopefully won't have to be reverted.
This commit is contained in:
parent
a5c0e7a71c
commit
2e337db3c5
@ -3,10 +3,10 @@ package main
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/config"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumbleopenal"
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/config"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumbleopenal"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
)
|
||||
|
||||
type Barnard struct {
|
||||
|
@ -5,9 +5,9 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumbleopenal"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumbleopenal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
)
|
||||
|
||||
func (b *Barnard) start() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
)
|
||||
|
||||
type Hotkeys struct {
|
||||
|
@ -2,9 +2,9 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
"gopkg.in/yaml.v2"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
|
2
doc.go
2
doc.go
@ -1,2 +1,2 @@
|
||||
// barnard is a terminal based client for the Mumble VoIP system.
|
||||
package main // import "git.2mb.codes/~cmb/barnard"
|
||||
package main // import "git.stormux.org/storm/barnard"
|
||||
|
6
go.mod
6
go.mod
@ -1,12 +1,10 @@
|
||||
module git.2mb.codes/~cmb/barnard
|
||||
module git.stormux.org/storm/barnard
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require (
|
||||
git.2mb.codes/~cmb/go-openal v0.0.0-20240129060821-44aa590ff5a5
|
||||
github.com/alessio/shellescape v1.4.2
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/hraban/opus v0.0.0-00010101000000-000000000000
|
||||
github.com/kennygrant/sanitize v1.2.4
|
||||
github.com/nsf/termbox-go v1.1.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@ -18,4 +16,4 @@ require (
|
||||
google.golang.org/protobuf v1.26.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/hraban/opus => git.2mb.codes/~cmb/opus v0.0.0-20240129070855-574f355deea8
|
||||
replace git.stormux.org/storm/barnard/gumble/go-openal => ./gumble/go-openal
|
||||
|
4
go.sum
4
go.sum
@ -1,7 +1,3 @@
|
||||
git.2mb.codes/~cmb/go-openal v0.0.0-20240129060821-44aa590ff5a5 h1:FmLSfqZQ6WVQxzvfhdPD4hjYQbNKZcK2qLP9oT9rgeo=
|
||||
git.2mb.codes/~cmb/go-openal v0.0.0-20240129060821-44aa590ff5a5/go.mod h1:zNFIOpVzzMAq/A8Nlv+iFhHFhZjp8lBPc/1rieycjJw=
|
||||
git.2mb.codes/~cmb/opus v0.0.0-20240129070855-574f355deea8 h1:F8MWDgOcsxqU8+uBlrlgF69al2u9oBALC/zWAlMrGa4=
|
||||
git.2mb.codes/~cmb/opus v0.0.0-20240129070855-574f355deea8/go.mod h1:XJUAtw77Ofc/j/tRGj+U991Fz8qvxu51x0S/M6F+yR8=
|
||||
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
|
||||
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Note
|
||||
|
||||
Added from git.2mb.codes/~cmb/barnard/gumble for ease of future work and development.
|
||||
Added from git.stormux.org/storm/barnard/gumble for ease of future work and development.
|
||||
|
||||
# gumble
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main // import "git.2mb.codes/~cmb/barnard/gumble/_examples/mumble-audio-player"
|
||||
package main // import "git.stormux.org/storm/barnard/gumble/_examples/mumble-audio-player"
|
||||
|
||||
import (
|
||||
"flag"
|
||||
@ -6,10 +6,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumbleffmpeg"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
_ "git.2mb.codes/~cmb/barnard/gumble/opus"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumbleffmpeg"
|
||||
"git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
_ "git.stormux.org/storm/barnard/gumble/opus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main // import "git.2mb.codes/~cmb/barnard/gumble/cmd/mumble-ping"
|
||||
package main // import "git.stormux.org/storm/barnard/gumble/cmd/mumble-ping"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
24
gumble/go-openal/LICENSE
Normal file
24
gumble/go-openal/LICENSE
Normal file
@ -0,0 +1,24 @@
|
||||
Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
17
gumble/go-openal/README
Normal file
17
gumble/go-openal/README
Normal file
@ -0,0 +1,17 @@
|
||||
OpenAL bindings for Go.
|
||||
====================================
|
||||
|
||||
I've forked this library from https://github.com/phf/go-openal
|
||||
and have started changing it quite a bit from the original library.
|
||||
|
||||
I don't have any experience with the original OpenAl libraries, so
|
||||
I'm going to be rearranging this a bit to fit my needs.
|
||||
|
||||
|
||||
To install simply install the openal and alc libs appropriately for
|
||||
your platform, or build them from scratch, then run
|
||||
|
||||
go get -u git.stormux.org/storm/barnard/gumble/go-openal/
|
||||
|
||||
|
||||
*Note, as of commit 0a4cd0b this library is no longer compatible with Go 1.3.3 and older.*
|
304
gumble/go-openal/openal/alcCore.go
Normal file
304
gumble/go-openal/openal/alcCore.go
Normal file
@ -0,0 +1,304 @@
|
||||
// Forked by Tim Shannon 2012
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// C-level binding for OpenAL's "alc" API.
|
||||
//
|
||||
// Please consider using the Go-level binding instead.
|
||||
//
|
||||
// Note that "alc" introduces the exact same types as "al"
|
||||
// but with different names. Check the documentation of
|
||||
// openal/al for more information about the mapping to
|
||||
// Go types.
|
||||
//
|
||||
// XXX: Sadly we have to returns pointers for both Device
|
||||
// and Context to avoid problems with implicit assignments
|
||||
// in clients. It's sad because it makes the overhead a
|
||||
// lot higher, each of those calls triggers an allocation.
|
||||
package openal
|
||||
|
||||
//#cgo linux LDFLAGS: -lopenal
|
||||
//#cgo darwin LDFLAGS: -framework OpenAL
|
||||
//#include <stdlib.h>
|
||||
//#include <string.h>
|
||||
//#include "local.h"
|
||||
/*
|
||||
ALCdevice *walcOpenDevice(const char *devicename) {
|
||||
return alcOpenDevice(devicename);
|
||||
}
|
||||
const ALCchar *alcGetString( ALCdevice *device, ALCenum param );
|
||||
void walcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, void *data) {
|
||||
alcGetIntegerv(device, param, size, data);
|
||||
}
|
||||
ALCdevice *walcCaptureOpenDevice(const char *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) {
|
||||
return alcCaptureOpenDevice(devicename, frequency, format, buffersize);
|
||||
}
|
||||
ALCint walcGetInteger(ALCdevice *device, ALCenum param) {
|
||||
ALCint result;
|
||||
alcGetIntegerv(device, param, 1, &result);
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
Frequency = 0x1007 // int Hz
|
||||
Refresh = 0x1008 // int Hz
|
||||
Sync = 0x1009 // bool
|
||||
MonoSources = 0x1010 // int
|
||||
StereoSources = 0x1011 // int
|
||||
)
|
||||
|
||||
// The Specifier string for default device?
|
||||
const (
|
||||
DefaultDeviceSpecifier = 0x1004
|
||||
DeviceSpecifier = 0x1005
|
||||
Extensions = 0x1006
|
||||
AllDevicesSpecifier = 0x1013
|
||||
)
|
||||
|
||||
// ?
|
||||
const (
|
||||
MajorVersion = 0x1000
|
||||
MinorVersion = 0x1001
|
||||
)
|
||||
|
||||
// ?
|
||||
const (
|
||||
AttributesSize = 0x1002
|
||||
AllAttributes = 0x1003
|
||||
)
|
||||
|
||||
// Capture extension
|
||||
const (
|
||||
CaptureDeviceSpecifier = 0x310
|
||||
CaptureDefaultDeviceSpecifier = 0x311
|
||||
CaptureSamples = 0x312
|
||||
)
|
||||
|
||||
//warning: this function does not free internal pointers
|
||||
//warning: memory leak
|
||||
func GetStrings(param int32) []string {
|
||||
start := C.alcGetString(nil,C.ALenum(param))
|
||||
ptr := unsafe.Pointer(start)
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
ret := make([]string,0)
|
||||
offset := uint(0)
|
||||
for {
|
||||
slen := uint(C.strlen((*C.char)(ptr)))
|
||||
if slen==0 {
|
||||
break
|
||||
}
|
||||
ret=append(ret,C.GoStringN((*C.char)(ptr),C.int(slen)))
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + uintptr(slen+1))
|
||||
offset+=(slen+1)
|
||||
}
|
||||
ptr = unsafe.Pointer(uintptr(ptr) - uintptr(offset))
|
||||
//This should be freeable; I've tried everything I can think of to free the returned pointer.
|
||||
//need to make sure alcchar doesn't have a weird free thingie, but that's all I can think of.
|
||||
//C.free(unsafe.Pointer(start))
|
||||
return ret
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
// Use uintptr instead of *C.ALCdevice.
|
||||
// On Mac OS X, this value is 0x18 and might cause crash with a raw pointer.
|
||||
handle uintptr
|
||||
}
|
||||
|
||||
func (self *Device) getError() uint32 {
|
||||
return uint32(C.alcGetError((*C.ALCdevice)(unsafe.Pointer(self.handle))))
|
||||
}
|
||||
|
||||
// Err() returns the most recent error generated
|
||||
// in the AL state machine.
|
||||
func (self *Device) Err() error {
|
||||
switch code := self.getError(); code {
|
||||
case 0x0000:
|
||||
return nil
|
||||
case 0xA001:
|
||||
return ErrInvalidDevice
|
||||
case 0xA002:
|
||||
return ErrInvalidContext
|
||||
case 0xA003:
|
||||
return ErrInvalidEnum
|
||||
case 0xA004:
|
||||
return ErrInvalidValue
|
||||
case 0xA005:
|
||||
return ErrOutOfMemory
|
||||
default:
|
||||
return ErrorCode(code)
|
||||
}
|
||||
}
|
||||
|
||||
func OpenDevice(name string) *Device {
|
||||
// TODO: turn empty string into nil?
|
||||
// TODO: what about an error return?
|
||||
p := C.CString(name)
|
||||
h := C.walcOpenDevice(p)
|
||||
C.free(unsafe.Pointer(p))
|
||||
if h==nil {
|
||||
return nil
|
||||
}
|
||||
return &Device{uintptr((unsafe.Pointer)(h))}
|
||||
}
|
||||
|
||||
func (self *Device) cHandle() *C.ALCdevice {
|
||||
return (*C.ALCdevice)(unsafe.Pointer(self.handle))
|
||||
}
|
||||
|
||||
func (self *Device) CloseDevice() bool {
|
||||
//TODO: really a method? or not?
|
||||
return C.alcCloseDevice(self.cHandle()) != 0
|
||||
}
|
||||
|
||||
func (self *Device) CreateContext() *Context {
|
||||
// TODO: really a method?
|
||||
// TODO: attrlist support
|
||||
c := C.alcCreateContext(self.cHandle(), nil)
|
||||
if c==nil {
|
||||
return nil
|
||||
}
|
||||
return &Context{uintptr(unsafe.Pointer(c))}
|
||||
}
|
||||
|
||||
func (self *Device) GetIntegerv(param uint32, size uint32) (result []int32) {
|
||||
result = make([]int32, size)
|
||||
C.walcGetIntegerv(self.cHandle(), C.ALCenum(param), C.ALCsizei(size), unsafe.Pointer(&result[0]))
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Device) GetInteger(param uint32) int32 {
|
||||
return int32(C.walcGetInteger(self.cHandle(), C.ALCenum(param)))
|
||||
}
|
||||
|
||||
type CaptureDevice struct {
|
||||
Device
|
||||
sampleSize uint32
|
||||
}
|
||||
|
||||
func CaptureOpenDevice(name string, freq uint32, format Format, size uint32) *CaptureDevice {
|
||||
// TODO: turn empty string into nil?
|
||||
// TODO: what about an error return?
|
||||
p := C.CString(name)
|
||||
h := C.walcCaptureOpenDevice(p, C.ALCuint(freq), C.ALCenum(format), C.ALCsizei(size))
|
||||
C.free(unsafe.Pointer(p))
|
||||
if h==nil {
|
||||
return nil
|
||||
}
|
||||
return &CaptureDevice{Device{uintptr(unsafe.Pointer(h))}, uint32(format.SampleSize())}
|
||||
}
|
||||
|
||||
// XXX: Override Device.CloseDevice to make sure the correct
|
||||
// C function is called even if someone decides to use this
|
||||
// behind an interface.
|
||||
func (self *CaptureDevice) CloseDevice() bool {
|
||||
return C.alcCaptureCloseDevice(self.cHandle()) != 0
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureCloseDevice() bool {
|
||||
return self.CloseDevice()
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureStart() {
|
||||
C.alcCaptureStart(self.cHandle())
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureStop() {
|
||||
C.alcCaptureStop(self.cHandle())
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureTo(data []byte) {
|
||||
C.alcCaptureSamples(self.cHandle(), unsafe.Pointer(&data[0]), C.ALCsizei(uint32(len(data))/self.sampleSize))
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureToInt16(data []int16) {
|
||||
C.alcCaptureSamples(self.cHandle(), unsafe.Pointer(&data[0]), C.ALCsizei(uint32(len(data))*2/self.sampleSize))
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureMono8To(data []byte) {
|
||||
self.CaptureTo(data)
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureMono16To(data []int16) {
|
||||
self.CaptureToInt16(data)
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureStereo8To(data [][2]byte) {
|
||||
C.alcCaptureSamples(self.cHandle(), unsafe.Pointer(&data[0]), C.ALCsizei(uint32(len(data))*2/self.sampleSize))
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureStereo16To(data [][2]int16) {
|
||||
C.alcCaptureSamples(self.cHandle(), unsafe.Pointer(&data[0]), C.ALCsizei(uint32(len(data))*4/self.sampleSize))
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureSamples(size uint32) (data []byte) {
|
||||
data = make([]byte, size*self.sampleSize)
|
||||
self.CaptureTo(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CaptureSamplesInt16(size uint32) (data []int16) {
|
||||
data = make([]int16, size*self.sampleSize/2)
|
||||
self.CaptureToInt16(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *CaptureDevice) CapturedSamples() (size uint32) {
|
||||
return uint32(self.GetInteger(CaptureSamples))
|
||||
}
|
||||
|
||||
///// Context ///////////////////////////////////////////////////////
|
||||
|
||||
// Context encapsulates the state of a given instance
|
||||
// of the OpenAL state machine. Only one context can
|
||||
// be active in a given process.
|
||||
type Context struct {
|
||||
// Use uintptr instead of *C.ALCcontext
|
||||
// On Mac OS X, this value is 0x19 and might cause crash with a raw pointer.
|
||||
handle uintptr
|
||||
}
|
||||
|
||||
// A context that doesn't exist, useful for certain
|
||||
// context operations (see OpenAL documentation for
|
||||
// details).
|
||||
var NullContext Context
|
||||
|
||||
func (self *Context) cHandle() *C.ALCcontext {
|
||||
return (*C.ALCcontext)(unsafe.Pointer(self.handle))
|
||||
}
|
||||
|
||||
// Renamed, was MakeContextCurrent.
|
||||
func (self *Context) Activate() bool {
|
||||
return C.alcMakeContextCurrent(self.cHandle()) != alFalse
|
||||
}
|
||||
|
||||
// Renamed, was ProcessContext.
|
||||
func (self *Context) Process() {
|
||||
C.alcProcessContext(self.cHandle())
|
||||
}
|
||||
|
||||
// Renamed, was SuspendContext.
|
||||
func (self *Context) Suspend() {
|
||||
C.alcSuspendContext(self.cHandle())
|
||||
}
|
||||
|
||||
// Renamed, was DestroyContext.
|
||||
func (self *Context) Destroy() {
|
||||
C.alcDestroyContext(self.cHandle())
|
||||
self.handle = uintptr(unsafe.Pointer(nil))
|
||||
}
|
||||
|
||||
// Renamed, was GetContextsDevice.
|
||||
func (self *Context) GetDevice() *Device {
|
||||
return &Device{uintptr(unsafe.Pointer(C.alcGetContextsDevice(self.cHandle())))}
|
||||
}
|
||||
|
||||
// Renamed, was GetCurrentContext.
|
||||
func CurrentContext() *Context {
|
||||
return &Context{uintptr(unsafe.Pointer(C.alcGetCurrentContext()))}
|
||||
}
|
207
gumble/go-openal/openal/buffer.go
Normal file
207
gumble/go-openal/openal/buffer.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openal
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#include "local.h"
|
||||
#include "wrapper.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Buffers are storage space for sample data.
|
||||
type Buffer uint32
|
||||
|
||||
// Attributes that can be queried with Buffer.Geti().
|
||||
const (
|
||||
alFrequency = 0x2001
|
||||
alBits = 0x2002
|
||||
alChannels = 0x2003
|
||||
alSize = 0x2004
|
||||
)
|
||||
|
||||
type Buffers []Buffer
|
||||
|
||||
// NewBuffers() creates n fresh buffers.
|
||||
// Renamed, was GenBuffers.
|
||||
func NewBuffers(n int) (buffers Buffers) {
|
||||
buffers = make(Buffers, n)
|
||||
C.walGenBuffers(C.ALsizei(n), unsafe.Pointer(&buffers[0]))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete() deletes the given buffers.
|
||||
func (self Buffers) Delete() {
|
||||
n := len(self)
|
||||
C.walDeleteBuffers(C.ALsizei(n), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was Bufferf.
|
||||
func (self Buffer) setf(param int32, value float32) {
|
||||
C.alBufferf(C.ALuint(self), C.ALenum(param), C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Renamed, was Buffer3f.
|
||||
func (self Buffer) set3f(param int32, value1, value2, value3 float32) {
|
||||
C.alBuffer3f(C.ALuint(self), C.ALenum(param), C.ALfloat(value1), C.ALfloat(value2), C.ALfloat(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Bufferfv.
|
||||
func (self Buffer) setfv(param int32, values []float32) {
|
||||
C.walBufferfv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was Bufferi.
|
||||
func (self Buffer) seti(param int32, value int32) {
|
||||
C.alBufferi(C.ALuint(self), C.ALenum(param), C.ALint(value))
|
||||
}
|
||||
|
||||
// Renamed, was Buffer3i.
|
||||
func (self Buffer) set3i(param int32, value1, value2, value3 int32) {
|
||||
C.alBuffer3i(C.ALuint(self), C.ALenum(param), C.ALint(value1), C.ALint(value2), C.ALint(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Bufferiv.
|
||||
func (self Buffer) setiv(param int32, values []int32) {
|
||||
C.walBufferiv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetBufferf.
|
||||
func (self Buffer) getf(param int32) float32 {
|
||||
return float32(C.walGetBufferf(C.ALuint(self), C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetBuffer3f.
|
||||
func (self Buffer) get3f(param int32) (value1, value2, value3 float32) {
|
||||
var v1, v2, v3 float32
|
||||
C.walGetBuffer3f(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
value1, value2, value3 = v1, v2, v3
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetBufferfv.
|
||||
func (self Buffer) getfv(param int32, values []float32) {
|
||||
C.walGetBufferfv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetBufferi.
|
||||
func (self Buffer) geti(param int32) int32 {
|
||||
return int32(C.walGetBufferi(C.ALuint(self), C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetBuffer3i.
|
||||
func (self Buffer) get3i(param int32) (value1, value2, value3 int32) {
|
||||
var v1, v2, v3 int32
|
||||
C.walGetBuffer3i(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
value1, value2, value3 = v1, v2, v3
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetBufferiv.
|
||||
func (self Buffer) getiv(param int32, values []int32) {
|
||||
C.walGetBufferiv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
type Format uint32
|
||||
|
||||
func (f Format) SampleSize() int {
|
||||
switch f {
|
||||
case FormatMono8:
|
||||
return 1
|
||||
case FormatMono16:
|
||||
return 2
|
||||
case FormatStereo8:
|
||||
return 2
|
||||
case FormatStereo16:
|
||||
return 4
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Format of sound samples passed to Buffer.SetData().
|
||||
const (
|
||||
FormatMono8 Format = 0x1100
|
||||
FormatMono16 Format = 0x1101
|
||||
FormatStereo8 Format = 0x1102
|
||||
FormatStereo16 Format = 0x1103
|
||||
)
|
||||
|
||||
// SetData() specifies the sample data the buffer should use.
|
||||
// For FormatMono16 and FormatStereo8 the data slice must be a
|
||||
// multiple of two bytes long; for FormatStereo16 the data slice
|
||||
// must be a multiple of four bytes long. The frequency is given
|
||||
// in Hz.
|
||||
// Renamed, was BufferData.
|
||||
func (self Buffer) SetData(format Format, data []byte, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(format), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
func (self Buffer) SetDataInt16(format Format, data []int16, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(format), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)*2), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
func (self Buffer) SetDataMono8(data []byte, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(FormatMono8), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
func (self Buffer) SetDataMono16(data []int16, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(FormatMono16), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)*2), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
func (self Buffer) SetDataStereo8(data [][2]byte, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(FormatStereo8), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)*2), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
func (self Buffer) SetDataStereo16(data [][2]int16, frequency int32) {
|
||||
C.alBufferData(C.ALuint(self), C.ALenum(FormatStereo16), unsafe.Pointer(&data[0]),
|
||||
C.ALsizei(len(data)*4), C.ALsizei(frequency))
|
||||
}
|
||||
|
||||
// NewBuffer() creates a single buffer.
|
||||
// Convenience function, see NewBuffers().
|
||||
func NewBuffer() Buffer {
|
||||
return Buffer(C.walGenBuffer())
|
||||
}
|
||||
|
||||
// Delete() deletes a single buffer.
|
||||
// Convenience function, see DeleteBuffers().
|
||||
func (self Buffer) Delete() {
|
||||
C.walDeleteSource(C.ALuint(self))
|
||||
}
|
||||
|
||||
// GetFrequency() returns the frequency, in Hz, of the buffer's sample data.
|
||||
// Convenience method.
|
||||
func (self Buffer) GetFrequency() uint32 {
|
||||
return uint32(self.geti(alFrequency))
|
||||
}
|
||||
|
||||
// GetBits() returns the resolution, either 8 or 16 bits, of the buffer's sample data.
|
||||
// Convenience method.
|
||||
func (self Buffer) GetBits() uint32 {
|
||||
return uint32(self.geti(alBits))
|
||||
}
|
||||
|
||||
// GetChannels() returns the number of channels, either 1 or 2, of the buffer's sample data.
|
||||
// Convenience method.
|
||||
func (self Buffer) GetChannels() uint32 {
|
||||
return uint32(self.geti(alChannels))
|
||||
}
|
||||
|
||||
// GetSize() returns the size, in bytes, of the buffer's sample data.
|
||||
// Convenience method.
|
||||
func (self Buffer) GetSize() uint32 {
|
||||
return uint32(self.geti(alSize))
|
||||
}
|
256
gumble/go-openal/openal/core.go
Normal file
256
gumble/go-openal/openal/core.go
Normal file
@ -0,0 +1,256 @@
|
||||
// Forked by Tim Shannon 2012
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Go binding for OpenAL's "al" API.
|
||||
//
|
||||
// See http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specification.htm
|
||||
// for details about OpenAL not described here.
|
||||
//
|
||||
// OpenAL types are (in principle) mapped to Go types as
|
||||
// follows:
|
||||
//
|
||||
// ALboolean bool (al.h says char, but Go's bool should be compatible)
|
||||
// ALchar uint8 (although al.h suggests int8, Go's uint8 (aka byte) seems better)
|
||||
// ALbyte int8 (al.h says char, implying that char is signed)
|
||||
// ALubyte uint8 (al.h says unsigned char)
|
||||
// ALshort int16
|
||||
// ALushort uint16
|
||||
// ALint int32
|
||||
// ALuint uint32
|
||||
// ALsizei int32 (although that's strange, it's what OpenAL wants)
|
||||
// ALenum int32 (although that's strange, it's what OpenAL wants)
|
||||
// ALfloat float32
|
||||
// ALdouble float64
|
||||
// ALvoid not applicable (but see below)
|
||||
//
|
||||
// We also stick to these (not mentioned explicitly in
|
||||
// OpenAL):
|
||||
//
|
||||
// ALvoid* unsafe.Pointer (but never exported)
|
||||
// ALchar* string
|
||||
//
|
||||
// Finally, in places where OpenAL expects pointers to
|
||||
// C-style arrays, we use Go slices if appropriate:
|
||||
//
|
||||
// ALboolean* []bool
|
||||
// ALvoid* []byte (see Buffer.SetData() for example)
|
||||
// ALint* []int32
|
||||
// ALuint* []uint32 []Source []Buffer
|
||||
// ALfloat* []float32
|
||||
// ALdouble* []float64
|
||||
//
|
||||
// Overall, the correspondence of types hopefully feels
|
||||
// natural enough. Note that many of these types do not
|
||||
// actually occur in the API.
|
||||
//
|
||||
// The names of OpenAL constants follow the established
|
||||
// Go conventions: instead of AL_FORMAT_MONO16 we use
|
||||
// FormatMono16 for example.
|
||||
//
|
||||
// Conversion to Go's camel case notation does however
|
||||
// lead to name clashes between constants and functions.
|
||||
// For example, AL_DISTANCE_MODEL becomes DistanceModel
|
||||
// which collides with the OpenAL function of the same
|
||||
// name used to set the current distance model. We have
|
||||
// to rename either the constant or the function, and
|
||||
// since the function name seems to be at fault (it's a
|
||||
// setter but doesn't make that obvious), we rename the
|
||||
// function.
|
||||
//
|
||||
// In fact, we renamed plenty of functions, not just the
|
||||
// ones where collisions with constants were the driving
|
||||
// force. For example, instead of the Sourcef/GetSourcef
|
||||
// abomination, we use Getf/Setf methods on a Source type.
|
||||
// Everything should still be easily recognizable for
|
||||
// OpenAL hackers, but this structure is a lot more
|
||||
// sensible (and reveals that the OpenAL API is actually
|
||||
// not such a bad design).
|
||||
//
|
||||
// There are a few cases where constants would collide
|
||||
// with the names of types we introduced here. Since the
|
||||
// types serve a much more important function, we renamed
|
||||
// the constants in those cases. For example AL_BUFFER
|
||||
// would collide with the type Buffer so it's name is now
|
||||
// Buffer_ instead. Not pretty, but in many cases you
|
||||
// don't need the constants anyway as the functionality
|
||||
// they represent is probably available through one of
|
||||
// the convenience functions we introduced as well. For
|
||||
// example consider the task of attaching a buffer to a
|
||||
// source. In C, you'd say alSourcei(sid, AL_BUFFER, bid).
|
||||
// In Go, you can say sid.Seti(Buffer_, bid) as well, but
|
||||
// you probably want to say sid.SetBuffer(bid) instead.
|
||||
//
|
||||
// TODO: Decide on the final API design; the current state
|
||||
// has only specialized methods, none of the generic ones
|
||||
// anymore; it exposes everything (except stuff we can't
|
||||
// do) but I am not sure whether this is the right API for
|
||||
// the level we operate on. Not yet anyway. Anyone?
|
||||
package openal
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -lopenal
|
||||
#cgo windows LDFLAGS: -lopenal32
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#include <stdlib.h>
|
||||
#include "local.h"
|
||||
#include "wrapper.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// General purpose constants. None can be used with SetDistanceModel()
|
||||
// to disable distance attenuation. None can be used with Source.SetBuffer()
|
||||
// to clear a Source of buffers.
|
||||
const (
|
||||
None = 0
|
||||
alFalse = 0
|
||||
alTrue = 1
|
||||
)
|
||||
|
||||
// GetInteger() queries.
|
||||
const (
|
||||
alDistanceModel = 0xD000
|
||||
)
|
||||
|
||||
// GetFloat() queries.
|
||||
const (
|
||||
alDopplerFactor = 0xC000
|
||||
alDopplerVelocity = 0xC001
|
||||
alSpeedOfSound = 0xC003
|
||||
)
|
||||
|
||||
// GetString() queries.
|
||||
const (
|
||||
alVendor = 0xB001
|
||||
alVersion = 0xB002
|
||||
alRenderer = 0xB003
|
||||
alExtensions = 0xB004
|
||||
)
|
||||
|
||||
// Shared Source/Listener properties.
|
||||
const (
|
||||
AlPosition = 0x1004
|
||||
AlVelocity = 0x1006
|
||||
AlGain = 0x100A
|
||||
)
|
||||
|
||||
func GetString(param int32) string {
|
||||
return C.GoString(C.walGetString(C.ALenum(param)))
|
||||
}
|
||||
|
||||
func getBoolean(param int32) bool {
|
||||
return C.alGetBoolean(C.ALenum(param)) != alFalse
|
||||
}
|
||||
|
||||
func getInteger(param int32) int32 {
|
||||
return int32(C.alGetInteger(C.ALenum(param)))
|
||||
}
|
||||
|
||||
func getFloat(param int32) float32 {
|
||||
return float32(C.alGetFloat(C.ALenum(param)))
|
||||
}
|
||||
|
||||
func getDouble(param int32) float64 {
|
||||
return float64(C.alGetDouble(C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetBooleanv.
|
||||
func getBooleans(param int32, data []bool) {
|
||||
C.walGetBooleanv(C.ALenum(param), unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetIntegerv.
|
||||
func getIntegers(param int32, data []int32) {
|
||||
C.walGetIntegerv(C.ALenum(param), unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetFloatv.
|
||||
func getFloats(param int32, data []float32) {
|
||||
C.walGetFloatv(C.ALenum(param), unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetDoublev.
|
||||
func getDoubles(param int32, data []float64) {
|
||||
C.walGetDoublev(C.ALenum(param), unsafe.Pointer(&data[0]))
|
||||
}
|
||||
|
||||
// GetError() returns the most recent error generated
|
||||
// in the AL state machine.
|
||||
func getError() uint32 {
|
||||
return uint32(C.alGetError())
|
||||
}
|
||||
|
||||
// Renamed, was DopplerFactor.
|
||||
func SetDopplerFactor(value float32) {
|
||||
C.alDopplerFactor(C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Renamed, was DopplerVelocity.
|
||||
func SetDopplerVelocity(value float32) {
|
||||
C.alDopplerVelocity(C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Renamed, was SpeedOfSound.
|
||||
func SetSpeedOfSound(value float32) {
|
||||
C.alSpeedOfSound(C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Distance models for SetDistanceModel() and GetDistanceModel().
|
||||
const (
|
||||
InverseDistance = 0xD001
|
||||
InverseDistanceClamped = 0xD002
|
||||
LinearDistance = 0xD003
|
||||
LinearDistanceClamped = 0xD004
|
||||
ExponentDistance = 0xD005
|
||||
ExponentDistanceClamped = 0xD006
|
||||
)
|
||||
|
||||
// SetDistanceModel() changes the current distance model.
|
||||
// Pass "None" to disable distance attenuation.
|
||||
// Renamed, was DistanceModel.
|
||||
func SetDistanceModel(model int32) {
|
||||
C.alDistanceModel(C.ALenum(model))
|
||||
}
|
||||
|
||||
///// Crap ///////////////////////////////////////////////////////////
|
||||
|
||||
// These functions are wrapped and should work fine, but they
|
||||
// have no purpose: There are *no* capabilities in OpenAL 1.1
|
||||
// which is the latest specification. So we removed from from
|
||||
// the API for now, it's complicated enough without them.
|
||||
//
|
||||
//func Enable(capability int32) {
|
||||
// C.alEnable(C.ALenum(capability));
|
||||
//}
|
||||
//
|
||||
//func Disable(capability int32) {
|
||||
// C.alDisable(C.ALenum(capability));
|
||||
//}
|
||||
//
|
||||
//func IsEnabled(capability int32) bool {
|
||||
// return C.alIsEnabled(C.ALenum(capability)) != alFalse;
|
||||
//}
|
||||
|
||||
// These constants are documented as "not yet exposed". We
|
||||
// keep them here in case they ever become valid. They are
|
||||
// buffer states.
|
||||
//
|
||||
//const (
|
||||
// Unused = 0x2010;
|
||||
// Pending = 0x2011;
|
||||
// Processed = 0x2012;
|
||||
//)
|
||||
|
||||
// These functions would work fine, but they are not very
|
||||
// useful since we have distinct Source and Buffer types.
|
||||
// Leaving them out reduces API complexity, a good thing.
|
||||
//
|
||||
//func IsSource(id uint32) bool {
|
||||
// return C.alIsSource(C.ALuint(id)) != alFalse;
|
||||
//}
|
||||
//
|
||||
//func IsBuffer(id uint32) bool {
|
||||
// return C.alIsBuffer(C.ALuint(id)) != alFalse;
|
||||
//}
|
BIN
gumble/go-openal/openal/data/welcome.wav
Normal file
BIN
gumble/go-openal/openal/data/welcome.wav
Normal file
Binary file not shown.
42
gumble/go-openal/openal/errors.go
Normal file
42
gumble/go-openal/openal/errors.go
Normal file
@ -0,0 +1,42 @@
|
||||
package openal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidName = errors.New("openal: invalid name")
|
||||
ErrInvalidEnum = errors.New("openal: invalid enum")
|
||||
ErrInvalidValue = errors.New("openal: invalid value")
|
||||
ErrInvalidOperation = errors.New("openal: invalid operation")
|
||||
|
||||
ErrInvalidContext = errors.New("openal: invalid context")
|
||||
ErrInvalidDevice = errors.New("openal: invalid device")
|
||||
ErrOutOfMemory = errors.New("openal: out of memory")
|
||||
)
|
||||
|
||||
type ErrorCode uint32
|
||||
|
||||
func (e ErrorCode) Error() string {
|
||||
return fmt.Sprintf("openal: error code %x", uint32(e))
|
||||
}
|
||||
|
||||
// Err() returns the most recent error generated
|
||||
// in the AL state machine.
|
||||
func Err() error {
|
||||
switch code := getError(); code {
|
||||
case 0x0000:
|
||||
return nil
|
||||
case 0xA001:
|
||||
return ErrInvalidName
|
||||
case 0xA002:
|
||||
return ErrInvalidEnum
|
||||
case 0xA003:
|
||||
return ErrInvalidValue
|
||||
case 0xA004:
|
||||
return ErrInvalidOperation
|
||||
default:
|
||||
return ErrorCode(code)
|
||||
}
|
||||
}
|
106
gumble/go-openal/openal/example_test.go
Normal file
106
gumble/go-openal/openal/example_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
package openal_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExamplePlay() {
|
||||
device := openal.OpenDevice("")
|
||||
defer device.CloseDevice()
|
||||
|
||||
context := device.CreateContext()
|
||||
defer context.Destroy()
|
||||
context.Activate()
|
||||
|
||||
source := openal.NewSource()
|
||||
defer source.Pause()
|
||||
source.SetLooping(false)
|
||||
|
||||
buffer := openal.NewBuffer()
|
||||
|
||||
if err := openal.Err(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile("data/welcome.wav")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
buffer.SetData(openal.FormatMono16, data, 44100)
|
||||
|
||||
source.SetBuffer(buffer)
|
||||
source.Play()
|
||||
for source.State() == openal.Playing {
|
||||
// loop long enough to let the wave file finish
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
source.Delete()
|
||||
fmt.Println("sound played")
|
||||
// Output: sound played
|
||||
}
|
||||
|
||||
func ExampleMonitor() {
|
||||
const (
|
||||
frequency = 44100
|
||||
format = openal.FormatStereo16
|
||||
captureSize = 512
|
||||
buffersCount = 10
|
||||
)
|
||||
mic := openal.CaptureOpenDevice("", frequency, format, frequency*2)
|
||||
mic.CaptureStart()
|
||||
defer mic.CloseDevice()
|
||||
|
||||
device := openal.OpenDevice("")
|
||||
defer device.CloseDevice()
|
||||
|
||||
context := device.CreateContext()
|
||||
context.Activate()
|
||||
defer context.Destroy()
|
||||
|
||||
source := openal.NewSource()
|
||||
source.SetLooping(false)
|
||||
defer source.Stop()
|
||||
|
||||
buffers := openal.NewBuffers(buffersCount)
|
||||
samples := make([]byte, captureSize*format.SampleSize())
|
||||
|
||||
start := time.Now()
|
||||
for time.Since(start) < time.Second { // play for 1 second
|
||||
if err := openal.Err(); err != nil {
|
||||
fmt.Println("error:", err)
|
||||
return
|
||||
}
|
||||
// Get any free buffers
|
||||
if prcessed := source.BuffersProcessed(); prcessed > 0 {
|
||||
buffersNew := make(openal.Buffers, prcessed)
|
||||
source.UnqueueBuffers(buffersNew)
|
||||
buffers = append(buffers, buffersNew...)
|
||||
}
|
||||
if len(buffers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if mic.CapturedSamples() >= captureSize {
|
||||
mic.CaptureTo(samples)
|
||||
buffer := buffers[len(buffers)-1]
|
||||
buffers = buffers[:len(buffers)-1]
|
||||
buffer.SetData(format, samples, frequency)
|
||||
source.QueueBuffer(buffer)
|
||||
|
||||
// If we have enough buffers, start playing
|
||||
if source.State() != openal.Playing {
|
||||
if source.BuffersQueued() > 2 {
|
||||
source.Play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println(source.State())
|
||||
// Output: Playing
|
||||
}
|
148
gumble/go-openal/openal/listener.go
Normal file
148
gumble/go-openal/openal/listener.go
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openal
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#include "local.h"
|
||||
#include "wrapper.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Listener properties.
|
||||
const (
|
||||
AlOrientation = 0x100F
|
||||
)
|
||||
|
||||
// Listener represents the singleton receiver of
|
||||
// sound in 3d space.
|
||||
//
|
||||
// We "fake" this type so we can provide OpenAL
|
||||
// listener calls as methods. This is convenient
|
||||
// and makes all those calls consistent with the
|
||||
// way they work for Source and Buffer. You can't
|
||||
// make new listeners, there's only one!
|
||||
type Listener struct{}
|
||||
|
||||
// Renamed, was Listenerf.
|
||||
func (self Listener) Setf(param int32, value float32) {
|
||||
C.alListenerf(C.ALenum(param), C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Renamed, was Listener3f.
|
||||
func (self Listener) Set3f(param int32, value1, value2, value3 float32) {
|
||||
C.alListener3f(C.ALenum(param), C.ALfloat(value1), C.ALfloat(value2), C.ALfloat(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Listenerfv.
|
||||
func (self Listener) Setfv(param int32, values []float32) {
|
||||
C.walListenerfv(C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was Listeneri.
|
||||
func (self Listener) Seti(param int32, value int32) {
|
||||
C.alListeneri(C.ALenum(param), C.ALint(value))
|
||||
}
|
||||
|
||||
// Renamed, was Listener3i.
|
||||
func (self Listener) Set3i(param int32, value1, value2, value3 int32) {
|
||||
C.alListener3i(C.ALenum(param), C.ALint(value1), C.ALint(value2), C.ALint(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Listeneriv.
|
||||
func (self Listener) Setiv(param int32, values []int32) {
|
||||
C.walListeneriv(C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetListenerf.
|
||||
func (self Listener) Getf(param int32) float32 {
|
||||
return float32(C.walGetListenerf(C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetListener3f.
|
||||
func (self Listener) Get3f(param int32) (v1, v2, v3 float32) {
|
||||
C.walGetListener3f(C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetListenerfv.
|
||||
func (self Listener) Getfv(param int32, values []float32) {
|
||||
C.walGetListenerfv(C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetListeneri.
|
||||
func (self Listener) Geti(param int32) int32 {
|
||||
return int32(C.walGetListeneri(C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetListener3i.
|
||||
func (self Listener) Get3i(param int32) (v1, v2, v3 int32) {
|
||||
C.walGetListener3i(C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetListeneriv.
|
||||
func (self Listener) Getiv(param int32, values []int32) {
|
||||
C.walGetListeneriv(C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
///// Convenience ////////////////////////////////////////////////////
|
||||
|
||||
// Convenience method, see Listener.Setf().
|
||||
func (self Listener) SetGain(gain float32) {
|
||||
self.Setf(AlGain, gain)
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Getf().
|
||||
func (self Listener) GetGain() (gain float32) {
|
||||
return self.Getf(AlGain)
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Setfv().
|
||||
func (self Listener) SetPosition(vector *Vector) {
|
||||
self.Set3f(AlPosition, vector[x], vector[y], vector[z])
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Getfv().
|
||||
func (self Listener) GetPosition(result *Vector) {
|
||||
result[x], result[y], result[z] = self.Get3f(AlPosition)
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Setfv().
|
||||
func (self Listener) SetVelocity(vector *Vector) {
|
||||
self.Set3f(AlVelocity, vector[x], vector[y], vector[z])
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Getfv().
|
||||
func (self Listener) GetVelocity(result *Vector) {
|
||||
result[x], result[y], result[z] = self.Get3f(AlVelocity)
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Setfv().
|
||||
func (self Listener) SetOrientation(at *Vector, up *Vector) {
|
||||
tempSlice[0] = at[x]
|
||||
tempSlice[1] = at[y]
|
||||
tempSlice[2] = at[z]
|
||||
tempSlice[3] = up[x]
|
||||
tempSlice[4] = up[y]
|
||||
tempSlice[5] = up[z]
|
||||
self.Setfv(AlOrientation, tempSlice)
|
||||
}
|
||||
|
||||
// Convenience method, see Listener.Getfv().
|
||||
func (self Listener) GetOrientation(resultAt, resultUp *Vector) {
|
||||
self.Getfv(AlOrientation, tempSlice)
|
||||
resultAt[x] = tempSlice[0]
|
||||
resultAt[y] = tempSlice[1]
|
||||
resultAt[z] = tempSlice[2]
|
||||
resultUp[x] = tempSlice[3]
|
||||
resultUp[y] = tempSlice[4]
|
||||
resultUp[z] = tempSlice[5]
|
||||
}
|
7
gumble/go-openal/openal/local.h
Normal file
7
gumble/go-openal/openal/local.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifdef __APPLE__
|
||||
#include<OpenAL/al.h>
|
||||
#include<OpenAL/alc.h>
|
||||
#else
|
||||
#include<AL/al.h>
|
||||
#include<AL/alc.h>
|
||||
#endif
|
23
gumble/go-openal/openal/openal_test.go
Normal file
23
gumble/go-openal/openal/openal_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
package openal_test
|
||||
|
||||
import (
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetVendor(t *testing.T) {
|
||||
device := openal.OpenDevice("")
|
||||
defer device.CloseDevice()
|
||||
|
||||
context := device.CreateContext()
|
||||
defer context.Destroy()
|
||||
context.Activate()
|
||||
|
||||
vendor := openal.GetVendor()
|
||||
|
||||
if err := openal.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if vendor == "" {
|
||||
t.Fatal("empty vendor returned")
|
||||
}
|
||||
}
|
452
gumble/go-openal/openal/source.go
Normal file
452
gumble/go-openal/openal/source.go
Normal file
@ -0,0 +1,452 @@
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openal
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#include "local.h"
|
||||
#include "wrapper.h"
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type State int32
|
||||
|
||||
func (s State) String() string {
|
||||
switch s {
|
||||
case Initial:
|
||||
return "Initial"
|
||||
case Playing:
|
||||
return "Playing"
|
||||
case Paused:
|
||||
return "Paused"
|
||||
case Stopped:
|
||||
return "Stopped"
|
||||
default:
|
||||
return fmt.Sprintf("%x", int32(s))
|
||||
}
|
||||
}
|
||||
|
||||
// Results from Source.State() query.
|
||||
const (
|
||||
Initial State = 0x1011
|
||||
Playing State = 0x1012
|
||||
Paused State = 0x1013
|
||||
Stopped State = 0x1014
|
||||
)
|
||||
|
||||
// Results from Source.Type() query.
|
||||
const (
|
||||
Static = 0x1028
|
||||
Streaming = 0x1029
|
||||
Undetermined = 0x1030
|
||||
)
|
||||
|
||||
// TODO: Source properties.
|
||||
// Regardless of what your al.h header may claim, Pitch
|
||||
// only applies to Sources, not to Listeners. And I got
|
||||
// that from Chris Robinson himself.
|
||||
const (
|
||||
AlSourceRelative = 0x202
|
||||
AlConeInnerAngle = 0x1001
|
||||
AlConeOuterAngle = 0x1002
|
||||
AlPitch = 0x1003
|
||||
AlDirection = 0x1005
|
||||
AlLooping = 0x1007
|
||||
AlBuffer = 0x1009
|
||||
AlMinGain = 0x100D
|
||||
AlMaxGain = 0x100E
|
||||
AlReferenceDistance = 0x1020
|
||||
AlRolloffFactor = 0x1021
|
||||
AlConeOuterGain = 0x1022
|
||||
AlMaxDistance = 0x1023
|
||||
AlSecOffset = 0x1024
|
||||
AlSampleOffset = 0x1025
|
||||
AlByteOffset = 0x1026
|
||||
)
|
||||
|
||||
// Sources represent sound emitters in 3d space.
|
||||
type Source uint32
|
||||
|
||||
type Sources []Source
|
||||
|
||||
// NewSources() creates n sources.
|
||||
// Renamed, was GenSources.
|
||||
func NewSources(n int) (sources Sources) {
|
||||
sources = make(Sources, n)
|
||||
C.walGenSources(C.ALsizei(n), unsafe.Pointer(&sources[0]))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes the sources.
|
||||
func (self Sources) Delete() {
|
||||
n := len(self)
|
||||
C.walDeleteSources(C.ALsizei(n), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was SourcePlayv.
|
||||
func (self Sources) Play() {
|
||||
C.walSourcePlayv(C.ALsizei(len(self)), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was SourceStopv.
|
||||
func (self Sources) Stop() {
|
||||
C.walSourceStopv(C.ALsizei(len(self)), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was SourceRewindv.
|
||||
func (self Sources) Rewind() {
|
||||
C.walSourceRewindv(C.ALsizei(len(self)), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was SourcePausev.
|
||||
func (self Sources) Pause() {
|
||||
C.walSourcePausev(C.ALsizei(len(self)), unsafe.Pointer(&self[0]))
|
||||
}
|
||||
|
||||
// Renamed, was Sourcef.
|
||||
func (self Source) Setf(param int32, value float32) {
|
||||
C.alSourcef(C.ALuint(self), C.ALenum(param), C.ALfloat(value))
|
||||
}
|
||||
|
||||
// Renamed, was Source3f.
|
||||
func (self Source) Set3f(param int32, value1, value2, value3 float32) {
|
||||
C.alSource3f(C.ALuint(self), C.ALenum(param), C.ALfloat(value1), C.ALfloat(value2), C.ALfloat(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Sourcefv.
|
||||
func (self Source) Setfv(param int32, values []float32) {
|
||||
C.walSourcefv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was Sourcei.
|
||||
func (self Source) Seti(param int32, value int32) {
|
||||
C.alSourcei(C.ALuint(self), C.ALenum(param), C.ALint(value))
|
||||
}
|
||||
|
||||
// Renamed, was Source3i.
|
||||
func (self Source) Set3i(param int32, value1, value2, value3 int32) {
|
||||
C.alSource3i(C.ALuint(self), C.ALenum(param), C.ALint(value1), C.ALint(value2), C.ALint(value3))
|
||||
}
|
||||
|
||||
// Renamed, was Sourceiv.
|
||||
func (self Source) Setiv(param int32, values []int32) {
|
||||
C.walSourceiv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetSourcef.
|
||||
func (self Source) Getf(param int32) float32 {
|
||||
return float32(C.walGetSourcef(C.ALuint(self), C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetSource3f.
|
||||
func (self Source) Get3f(param int32) (v1, v2, v3 float32) {
|
||||
C.walGetSource3f(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetSourcefv.
|
||||
func (self Source) Getfv(param int32, values []float32) {
|
||||
C.walGetSourcefv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Renamed, was GetSourcei.
|
||||
func (self Source) Geti(param int32) int32 {
|
||||
return int32(C.walGetSourcei(C.ALuint(self), C.ALenum(param)))
|
||||
}
|
||||
|
||||
// Renamed, was GetSource3i.
|
||||
func (self Source) Get3i(param int32) (v1, v2, v3 int32) {
|
||||
C.walGetSource3i(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&v1),
|
||||
unsafe.Pointer(&v2), unsafe.Pointer(&v3))
|
||||
return
|
||||
}
|
||||
|
||||
// Renamed, was GetSourceiv.
|
||||
func (self Source) Getiv(param int32, values []int32) {
|
||||
C.walGetSourceiv(C.ALuint(self), C.ALenum(param), unsafe.Pointer(&values[0]))
|
||||
}
|
||||
|
||||
// Delete deletes the source.
|
||||
// Convenience function, see DeleteSources().
|
||||
func (self Source) Delete() {
|
||||
C.walDeleteSource(C.ALuint(self))
|
||||
}
|
||||
|
||||
// Renamed, was SourcePlay.
|
||||
func (self Source) Play() {
|
||||
C.alSourcePlay(C.ALuint(self))
|
||||
}
|
||||
|
||||
// Renamed, was SourceStop.
|
||||
func (self Source) Stop() {
|
||||
C.alSourceStop(C.ALuint(self))
|
||||
}
|
||||
|
||||
// Renamed, was SourceRewind.
|
||||
func (self Source) Rewind() {
|
||||
C.alSourceRewind(C.ALuint(self))
|
||||
}
|
||||
|
||||
// Renamed, was SourcePause.
|
||||
func (self Source) Pause() {
|
||||
C.alSourcePause(C.ALuint(self))
|
||||
}
|
||||
|
||||
// Renamed, was SourceQueueBuffers.
|
||||
func (self Source) QueueBuffers(buffers Buffers) {
|
||||
C.walSourceQueueBuffers(C.ALuint(self), C.ALsizei(len(buffers)), unsafe.Pointer(&buffers[0]))
|
||||
}
|
||||
|
||||
// Renamed, was SourceUnqueueBuffers.
|
||||
func (self Source) UnqueueBuffers(buffers Buffers) {
|
||||
C.walSourceUnqueueBuffers(C.ALuint(self), C.ALsizei(len(buffers)), unsafe.Pointer(&buffers[0]))
|
||||
}
|
||||
|
||||
///// Convenience ////////////////////////////////////////////////////
|
||||
|
||||
// NewSource() creates a single source.
|
||||
// Convenience function, see NewSources().
|
||||
func NewSource() Source {
|
||||
return Source(C.walGenSource())
|
||||
}
|
||||
|
||||
// Convenience method, see Source.QueueBuffers().
|
||||
func (self Source) QueueBuffer(buffer Buffer) {
|
||||
C.walSourceQueueBuffer(C.ALuint(self), C.ALuint(buffer))
|
||||
}
|
||||
|
||||
// Convenience method, see Source.QueueBuffers().
|
||||
func (self Source) UnqueueBuffer() Buffer {
|
||||
return Buffer(C.walSourceUnqueueBuffer(C.ALuint(self)))
|
||||
}
|
||||
|
||||
// Source queries.
|
||||
// TODO: SourceType isn't documented as a query in the
|
||||
// al.h header, but it is documented that way in
|
||||
// the OpenAL 1.1 specification.
|
||||
const (
|
||||
AlSourceState = 0x1010
|
||||
AlBuffersQueued = 0x1015
|
||||
AlBuffersProcessed = 0x1016
|
||||
AlSourceType = 0x1027
|
||||
)
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) BuffersQueued() int32 {
|
||||
return self.Geti(AlBuffersQueued)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) BuffersProcessed() int32 {
|
||||
return self.Geti(AlBuffersProcessed)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) State() State {
|
||||
return State(self.Geti(AlSourceState))
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) Type() int32 {
|
||||
return self.Geti(AlSourceType)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetGain() (gain float32) {
|
||||
return self.Getf(AlGain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetGain(gain float32) {
|
||||
self.Setf(AlGain, gain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetMinGain() (gain float32) {
|
||||
return self.Getf(AlMinGain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetMinGain(gain float32) {
|
||||
self.Setf(AlMinGain, gain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetMaxGain() (gain float32) {
|
||||
return self.Getf(AlMaxGain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetMaxGain(gain float32) {
|
||||
self.Setf(AlMaxGain, gain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetReferenceDistance() (distance float32) {
|
||||
return self.Getf(AlReferenceDistance)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetReferenceDistance(distance float32) {
|
||||
self.Setf(AlReferenceDistance, distance)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetMaxDistance() (distance float32) {
|
||||
return self.Getf(AlMaxDistance)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetMaxDistance(distance float32) {
|
||||
self.Setf(AlMaxDistance, distance)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetPitch() float32 {
|
||||
return self.Getf(AlPitch)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetPitch(pitch float32) {
|
||||
self.Setf(AlPitch, pitch)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetRolloffFactor() (gain float32) {
|
||||
return self.Getf(AlRolloffFactor)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetRolloffFactor(gain float32) {
|
||||
self.Setf(AlRolloffFactor, gain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) GetLooping() bool {
|
||||
return self.Geti(AlLooping) != alFalse
|
||||
}
|
||||
|
||||
var bool2al map[bool]int32 = map[bool]int32{true: alTrue, false: alFalse}
|
||||
|
||||
// Convenience method, see Source.Seti().
|
||||
func (self Source) SetLooping(yes bool) {
|
||||
self.Seti(AlLooping, bool2al[yes])
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) GetSourceRelative() bool {
|
||||
return self.Geti(AlSourceRelative) != alFalse
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Seti().
|
||||
func (self Source) SetSourceRelative(yes bool) {
|
||||
self.Seti(AlSourceRelative, bool2al[yes])
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setfv().
|
||||
func (self Source) SetPosition(vector *Vector) {
|
||||
self.Set3f(AlPosition, vector[x], vector[y], vector[z])
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getfv().
|
||||
func (self Source) GetPosition(result *Vector) {
|
||||
result[x], result[y], result[z] = self.Get3f(AlPosition)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setfv().
|
||||
func (self Source) SetDirection(vector *Vector) {
|
||||
self.Set3f(AlDirection, vector[x], vector[y], vector[z])
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getfv().
|
||||
func (self Source) GetDirection(result *Vector) {
|
||||
result[x], result[y], result[z] = self.Get3f(AlDirection)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setfv().
|
||||
func (self Source) SetVelocity(vector *Vector) {
|
||||
self.Set3f(AlVelocity, vector[x], vector[y], vector[z])
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getfv().
|
||||
func (self Source) GetVelocity(result *Vector) {
|
||||
result[x], result[y], result[z] = self.Get3f(AlVelocity)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetOffsetSeconds() float32 {
|
||||
return self.Getf(AlSecOffset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetOffsetSeconds(offset float32) {
|
||||
self.Setf(AlSecOffset, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) GetOffsetSamples() int32 {
|
||||
return self.Geti(AlSampleOffset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Seti().
|
||||
func (self Source) SetOffsetSamples(offset int32) {
|
||||
self.Seti(AlSampleOffset, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) GetOffsetBytes() int32 {
|
||||
return self.Geti(AlByteOffset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Seti().
|
||||
func (self Source) SetOffsetBytes(offset int32) {
|
||||
self.Seti(AlByteOffset, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetInnerAngle() float32 {
|
||||
return self.Getf(AlConeInnerAngle)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetInnerAngle(offset float32) {
|
||||
self.Setf(AlConeInnerAngle, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetOuterAngle() float32 {
|
||||
return self.Getf(AlConeOuterAngle)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetOuterAngle(offset float32) {
|
||||
self.Setf(AlConeOuterAngle, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Getf().
|
||||
func (self Source) GetOuterGain() float32 {
|
||||
return self.Getf(AlConeOuterGain)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Setf().
|
||||
func (self Source) SetOuterGain(offset float32) {
|
||||
self.Setf(AlConeOuterGain, offset)
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) SetBuffer(buffer Buffer) {
|
||||
self.Seti(AlBuffer, int32(buffer))
|
||||
}
|
||||
|
||||
// Convenience method, see Source.Geti().
|
||||
func (self Source) GetBuffer() (buffer Buffer) {
|
||||
return Buffer(self.Geti(AlBuffer))
|
||||
}
|
74
gumble/go-openal/openal/util.go
Normal file
74
gumble/go-openal/openal/util.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Convenience functions in pure Go.
|
||||
//
|
||||
// Not all convenience functions are here: those that need
|
||||
// to call C code have to be in core.go instead due to cgo
|
||||
// limitations, while those that are methods have to be in
|
||||
// core.go due to language limitations. They should all be
|
||||
// here of course, at least conceptually.
|
||||
|
||||
package openal
|
||||
|
||||
import "strings"
|
||||
|
||||
// Convenience Interface.
|
||||
type Vector [3]float32
|
||||
|
||||
var tempSlice = make([]float32, 6)
|
||||
|
||||
const (
|
||||
x = iota
|
||||
y
|
||||
z
|
||||
)
|
||||
|
||||
// Convenience function, see GetInteger().
|
||||
func GetDistanceModel() int32 {
|
||||
return getInteger(alDistanceModel)
|
||||
}
|
||||
|
||||
// Convenience function, see GetFloat().
|
||||
func GetDopplerFactor() float32 {
|
||||
return getFloat(alDopplerFactor)
|
||||
}
|
||||
|
||||
// Convenience function, see GetFloat().
|
||||
func GetDopplerVelocity() float32 {
|
||||
return getFloat(alDopplerVelocity)
|
||||
}
|
||||
|
||||
// Convenience function, see GetFloat().
|
||||
func GetSpeedOfSound() float32 {
|
||||
return getFloat(alSpeedOfSound)
|
||||
}
|
||||
|
||||
// Convenience function, see GetString().
|
||||
func GetVendor() string {
|
||||
return GetString(alVendor)
|
||||
}
|
||||
|
||||
// Convenience function, see GetString().
|
||||
func GetVersion() string {
|
||||
return GetString(alVersion)
|
||||
}
|
||||
|
||||
// Convenience function, see GetString().
|
||||
func GetRenderer() string {
|
||||
return GetString(alRenderer)
|
||||
}
|
||||
|
||||
// Convenience function, see GetString().
|
||||
func GetExtensions() string {
|
||||
return GetString(alExtensions)
|
||||
}
|
||||
|
||||
func GetExtensionsSlice() []string {
|
||||
return strings.Split(GetExtensions(), " ")
|
||||
}
|
||||
|
||||
func IsExtensionPresent(ext string) bool {
|
||||
return strings.Index(GetExtensions(), ext) >= 0
|
||||
}
|
212
gumble/go-openal/openal/wrapper.c
Normal file
212
gumble/go-openal/openal/wrapper.c
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "local.h"
|
||||
#include "wrapper.h"
|
||||
|
||||
const char *walGetString(ALenum param) {
|
||||
return alGetString(param);
|
||||
}
|
||||
|
||||
void walGetBooleanv(ALenum param, void* data) {
|
||||
alGetBooleanv(param, data);
|
||||
}
|
||||
|
||||
void walGetIntegerv(ALenum param, void* data) {
|
||||
alGetIntegerv(param, data);
|
||||
}
|
||||
|
||||
void walGetFloatv(ALenum param, void* data) {
|
||||
alGetFloatv(param, data);
|
||||
}
|
||||
|
||||
void walGetDoublev(ALenum param, void* data) {
|
||||
alGetDoublev(param, data);
|
||||
}
|
||||
|
||||
// Listeners
|
||||
|
||||
void walListenerfv(ALenum param, const void* values) {
|
||||
alListenerfv(param, values);
|
||||
}
|
||||
|
||||
void walListeneriv(ALenum param, const void* values) {
|
||||
alListeneriv(param, values);
|
||||
}
|
||||
|
||||
ALfloat walGetListenerf(ALenum param) {
|
||||
ALfloat result;
|
||||
alGetListenerf(param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetListener3f(ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetListener3f(param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetListenerfv(ALenum param, void* values) {
|
||||
alGetListenerfv(param, values);
|
||||
}
|
||||
|
||||
ALint walGetListeneri(ALenum param) {
|
||||
ALint result;
|
||||
alGetListeneri(param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetListener3i(ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetListener3i(param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetListeneriv(ALenum param, void* values) {
|
||||
alGetListeneriv(param, values);
|
||||
}
|
||||
|
||||
// Sources
|
||||
|
||||
void walGenSources(ALsizei n, void *sources) {
|
||||
alGenSources(n, sources);
|
||||
}
|
||||
|
||||
void walDeleteSources(ALsizei n, const void *sources) {
|
||||
alDeleteSources(n, sources);
|
||||
}
|
||||
|
||||
void walSourcefv(ALuint sid, ALenum param, const void* values) {
|
||||
alSourcefv(sid, param, values);
|
||||
}
|
||||
|
||||
void walSourceiv(ALuint sid, ALenum param, const void* values) {
|
||||
alSourceiv(sid, param, values);
|
||||
}
|
||||
|
||||
ALfloat walGetSourcef(ALuint sid, ALenum param) {
|
||||
ALfloat result;
|
||||
alGetSourcef(sid, param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetSource3f(ALuint sid, ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetSource3f(sid, param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetSourcefv(ALuint sid, ALenum param, void* values) {
|
||||
alGetSourcefv(sid, param, values);
|
||||
}
|
||||
|
||||
ALint walGetSourcei(ALuint sid, ALenum param) {
|
||||
ALint result;
|
||||
alGetSourcei(sid, param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetSource3i(ALuint sid, ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetSource3i(sid, param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetSourceiv(ALuint sid, ALenum param, void* values) {
|
||||
alGetSourceiv(sid, param, values);
|
||||
}
|
||||
|
||||
void walSourcePlayv(ALsizei ns, const void *sids) {
|
||||
alSourcePlayv(ns, sids);
|
||||
}
|
||||
|
||||
void walSourceStopv(ALsizei ns, const void *sids) {
|
||||
alSourceStopv(ns, sids);
|
||||
}
|
||||
|
||||
void walSourceRewindv(ALsizei ns, const void *sids) {
|
||||
alSourceRewindv(ns, sids);
|
||||
}
|
||||
|
||||
void walSourcePausev(ALsizei ns, const void *sids) {
|
||||
alSourcePausev(ns, sids);
|
||||
}
|
||||
|
||||
void walSourceQueueBuffers(ALuint sid, ALsizei numEntries, const void *bids) {
|
||||
alSourceQueueBuffers(sid, numEntries, bids);
|
||||
}
|
||||
|
||||
void walSourceUnqueueBuffers(ALuint sid, ALsizei numEntries, void *bids) {
|
||||
alSourceUnqueueBuffers(sid, numEntries, bids);
|
||||
}
|
||||
|
||||
// Buffers
|
||||
|
||||
void walGenBuffers(ALsizei n, void *buffers) {
|
||||
alGenBuffers(n, buffers);
|
||||
}
|
||||
|
||||
void walDeleteBuffers(ALsizei n, const void *buffers) {
|
||||
alDeleteBuffers(n, buffers);
|
||||
}
|
||||
|
||||
void walBufferfv(ALuint bid, ALenum param, const void* values) {
|
||||
alBufferfv(bid, param, values);
|
||||
}
|
||||
|
||||
void walBufferiv(ALuint bid, ALenum param, const void* values) {
|
||||
alBufferiv(bid, param, values);
|
||||
}
|
||||
|
||||
ALfloat walGetBufferf(ALuint bid, ALenum param) {
|
||||
ALfloat result;
|
||||
alGetBufferf(bid, param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetBuffer3f(ALuint bid, ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetBuffer3f(bid, param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetBufferfv(ALuint bid, ALenum param, void* values) {
|
||||
alGetBufferfv(bid, param, values);
|
||||
}
|
||||
|
||||
ALint walGetBufferi(ALuint bid, ALenum param) {
|
||||
ALint result;
|
||||
alGetBufferi(bid, param, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void walGetBuffer3i(ALuint bid, ALenum param, void *value1, void *value2, void *value3) {
|
||||
alGetBuffer3i(bid, param, value1, value2, value3);
|
||||
}
|
||||
|
||||
void walGetBufferiv(ALuint bid, ALenum param, void* values) {
|
||||
alGetBufferiv(bid, param, values);
|
||||
}
|
||||
|
||||
// Singulars
|
||||
|
||||
ALuint walGenSource(void) {
|
||||
ALuint source;
|
||||
alGenSources(1, &source);
|
||||
return source;
|
||||
}
|
||||
|
||||
void walDeleteSource(ALuint source) {
|
||||
alDeleteSources(1, &source);
|
||||
}
|
||||
|
||||
ALuint walGenBuffer(void) {
|
||||
ALuint buffer;
|
||||
alGenBuffers(1, &buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void walDeleteBuffer(ALuint buffer) {
|
||||
alDeleteBuffers(1, &buffer);
|
||||
}
|
||||
|
||||
void walSourceQueueBuffer(ALuint sid, ALuint bid) {
|
||||
alSourceQueueBuffers(sid, 1, &bid);
|
||||
}
|
||||
|
||||
ALuint walSourceUnqueueBuffer(ALuint sid) {
|
||||
ALuint result;
|
||||
alSourceUnqueueBuffers(sid, 1, &result);
|
||||
return result;
|
||||
}
|
86
gumble/go-openal/openal/wrapper.h
Normal file
86
gumble/go-openal/openal/wrapper.h
Normal file
@ -0,0 +1,86 @@
|
||||
#ifndef _GO_WRAPPER_AL_
|
||||
#define _GO_WRAPPER_AL_
|
||||
|
||||
// Copyright 2009 Peter H. Froehlich. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// It's sad but the OpenAL C API uses lots and lots of typedefs
|
||||
// that require wrapper functions (using basic C types) for cgo
|
||||
// to grok them. So there's a lot more C code here than I would
|
||||
// like...
|
||||
|
||||
const char *walGetString(ALenum param);
|
||||
void walGetBooleanv(ALenum param, void* data);
|
||||
void walGetIntegerv(ALenum param, void* data);
|
||||
void walGetFloatv(ALenum param, void* data);
|
||||
void walGetDoublev(ALenum param, void* data);
|
||||
|
||||
// We don't define wrappers for these because we have
|
||||
// no clue how to make Go grok C function pointers at
|
||||
// runtime. So for now, OpenAL extensions can not be
|
||||
// used from Go. If you have an idea for how to make
|
||||
// it work, be sure to email! I suspect we'd need a
|
||||
// mechanism for generating cgo-style stubs at runtime,
|
||||
// sounds like work.
|
||||
//
|
||||
// ALboolean alIsExtensionPresent( const ALchar* extname );
|
||||
// void* alGetProcAddress( const ALchar* fname );
|
||||
// ALenum alGetEnumValue( const ALchar* ename );
|
||||
|
||||
// Listeners
|
||||
|
||||
void walListenerfv(ALenum param, const void* values);
|
||||
void walListeneriv(ALenum param, const void* values);
|
||||
ALfloat walGetListenerf(ALenum param);
|
||||
void walGetListener3f(ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetListenerfv(ALenum param, void* values);
|
||||
ALint walGetListeneri(ALenum param);
|
||||
void walGetListener3i(ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetListeneriv(ALenum param, void* values);
|
||||
|
||||
// Sources
|
||||
|
||||
void walGenSources(ALsizei n, void *sources);
|
||||
void walDeleteSources(ALsizei n, const void *sources);
|
||||
void walSourcefv(ALuint sid, ALenum param, const void* values);
|
||||
void walSourceiv(ALuint sid, ALenum param, const void* values);
|
||||
ALfloat walGetSourcef(ALuint sid, ALenum param);
|
||||
void walGetSource3f(ALuint sid, ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetSourcefv(ALuint sid, ALenum param, void* values);
|
||||
ALint walGetSourcei(ALuint sid, ALenum param);
|
||||
void walGetSource3i(ALuint sid, ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetSourceiv(ALuint sid, ALenum param, void* values);
|
||||
void walSourcePlayv(ALsizei ns, const void *sids);
|
||||
void walSourceStopv(ALsizei ns, const void *sids);
|
||||
void walSourceRewindv(ALsizei ns, const void *sids);
|
||||
void walSourcePausev(ALsizei ns, const void *sids);
|
||||
void walSourceQueueBuffers(ALuint sid, ALsizei numEntries, const void *bids);
|
||||
void walSourceUnqueueBuffers(ALuint sid, ALsizei numEntries, void *bids);
|
||||
|
||||
// Buffers
|
||||
|
||||
void walGenBuffers(ALsizei n, void *buffers);
|
||||
void walDeleteBuffers(ALsizei n, const void *buffers);
|
||||
void walBufferfv(ALuint bid, ALenum param, const void* values);
|
||||
void walBufferiv(ALuint bid, ALenum param, const void* values);
|
||||
ALfloat walGetBufferf(ALuint bid, ALenum param);
|
||||
void walGetBuffer3f(ALuint bid, ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetBufferfv(ALuint bid, ALenum param, void* values);
|
||||
ALint walGetBufferi(ALuint bid, ALenum param);
|
||||
void walGetBuffer3i(ALuint bid, ALenum param, void *value1, void *value2, void *value3);
|
||||
void walGetBufferiv(ALuint bid, ALenum param, void* values);
|
||||
|
||||
// For convenience we offer "singular" versions of the following
|
||||
// calls as well, which require different wrappers if we want to
|
||||
// be efficient. The main reason for "singular" versions is that
|
||||
// Go doesn't allow us to treat a variable as an array of size 1.
|
||||
|
||||
ALuint walGenSource(void);
|
||||
void walDeleteSource(ALuint source);
|
||||
ALuint walGenBuffer(void);
|
||||
void walDeleteBuffer(ALuint buffer);
|
||||
void walSourceQueueBuffer(ALuint sid, ALuint bid);
|
||||
ALuint walSourceUnqueueBuffer(ALuint sid);
|
||||
|
||||
#endif
|
12
gumble/go-opus/AUTHORS
Normal file
12
gumble/go-opus/AUTHORS
Normal file
@ -0,0 +1,12 @@
|
||||
All code and content in this project is Copyright © 2015-2022 Go Opus Authors
|
||||
|
||||
Go Opus Authors and copyright holders of this package are listed below, in no
|
||||
particular order. By adding yourself to this list you agree to license your
|
||||
contributions under the relevant license (see the LICENSE file).
|
||||
|
||||
Hraban Luyat <hraban@0brg.net>
|
||||
Dejian Xu <xudejian2008@gmail.com>
|
||||
Tobias Wellnitz <tobias.wellnitz@gmail.com>
|
||||
Elinor Natanzon <stop.start.dev@gmail.com>
|
||||
Victor Gaydov <victor@enise.org>
|
||||
Randy Reddig <ydnar@shaderlab.com>
|
19
gumble/go-opus/LICENSE
Normal file
19
gumble/go-opus/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright © 2015-2022 Go Opus Authors (see AUTHORS file)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
302
gumble/go-opus/README.md
Normal file
302
gumble/go-opus/README.md
Normal file
@ -0,0 +1,302 @@
|
||||
[![Test](https://github.com/hraban/opus/workflows/Test/badge.svg)](https://github.com/hraban/opus/actions?query=workflow%3ATest)
|
||||
|
||||
## Go wrapper for Opus
|
||||
|
||||
This package provides Go bindings for the xiph.org C libraries libopus and
|
||||
libopusfile.
|
||||
|
||||
The C libraries and docs are hosted at https://opus-codec.org/. This package
|
||||
just handles the wrapping in Go, and is unaffiliated with xiph.org.
|
||||
|
||||
Features:
|
||||
|
||||
- ✅ encode and decode raw PCM data to raw Opus data
|
||||
- ✅ useful when you control the recording device, _and_ the playback
|
||||
- ✅ decode .opus and .ogg files into raw audio data ("PCM")
|
||||
- ✅ reuse the system libraries for opus decoding (libopus)
|
||||
- ✅ works easily on Linux, Mac and Docker; needs libs on Windows
|
||||
- ❌ does not _create_ .opus or .ogg files (but feel free to send a PR)
|
||||
- ❌ does not work with .wav files (you need a separate .wav library for that)
|
||||
- ❌ no self-contained binary (you need the xiph.org libopus lib, e.g. through a package manager)
|
||||
- ❌ no cross compiling (because it uses CGo)
|
||||
|
||||
Good use cases:
|
||||
|
||||
- 👍 you are writing a music player app in Go, and you want to play back .opus files
|
||||
- 👍 you record raw wav in a web app or mobile app, you encode it as Opus on the client, you send the opus to a remote webserver written in Go, and you want to decode it back to raw audio data on that server
|
||||
|
||||
## Details
|
||||
|
||||
This wrapper provides a Go translation layer for three elements from the
|
||||
xiph.org opus libs:
|
||||
|
||||
* encoders
|
||||
* decoders
|
||||
* files & streams
|
||||
|
||||
### Import
|
||||
|
||||
```go
|
||||
import "gopkg.in/hraban/opus.v2"
|
||||
```
|
||||
|
||||
### Encoding
|
||||
|
||||
To encode raw audio to the Opus format, create an encoder first:
|
||||
|
||||
```go
|
||||
const sampleRate = 48000
|
||||
const channels = 1 // mono; 2 for stereo
|
||||
|
||||
enc, err := opus.NewEncoder(sampleRate, channels, opus.AppVoIP)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Then pass it some raw PCM data to encode.
|
||||
|
||||
Make sure that the raw PCM data you want to encode has a legal Opus frame size.
|
||||
This means it must be exactly 2.5, 5, 10, 20, 40 or 60 ms long. The number of
|
||||
bytes this corresponds to depends on the sample rate (see the [libopus
|
||||
documentation](https://www.opus-codec.org/docs/opus_api-1.1.3/group__opus__encoder.html)).
|
||||
|
||||
```go
|
||||
var pcm []int16 = ... // obtain your raw PCM data somewhere
|
||||
const bufferSize = 1000 // choose any buffer size you like. 1k is plenty.
|
||||
|
||||
// Check the frame size. You don't need to do this if you trust your input.
|
||||
frameSize := len(pcm) // must be interleaved if stereo
|
||||
frameSizeMs := float32(frameSize) / channels * 1000 / sampleRate
|
||||
switch frameSizeMs {
|
||||
case 2.5, 5, 10, 20, 40, 60:
|
||||
// Good.
|
||||
default:
|
||||
return fmt.Errorf("Illegal frame size: %d bytes (%f ms)", frameSize, frameSizeMs)
|
||||
}
|
||||
|
||||
data := make([]byte, bufferSize)
|
||||
n, err := enc.Encode(pcm, data)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
data = data[:n] // only the first N bytes are opus data. Just like io.Reader.
|
||||
```
|
||||
|
||||
Note that you must choose a target buffer size, and this buffer size will affect
|
||||
the encoding process:
|
||||
|
||||
> Size of the allocated memory for the output payload. This may be used to
|
||||
> impose an upper limit on the instant bitrate, but should not be used as the
|
||||
> only bitrate control. Use `OPUS_SET_BITRATE` to control the bitrate.
|
||||
|
||||
-- https://opus-codec.org/docs/opus_api-1.1.3/group__opus__encoder.html
|
||||
|
||||
### Decoding
|
||||
|
||||
To decode opus data to raw PCM format, first create a decoder:
|
||||
|
||||
```go
|
||||
dec, err := opus.NewDecoder(sampleRate, channels)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Now pass it the opus bytes, and a buffer to store the PCM sound in:
|
||||
|
||||
```go
|
||||
var frameSizeMs float32 = ... // if you don't know, go with 60 ms.
|
||||
frameSize := channels * frameSizeMs * sampleRate / 1000
|
||||
pcm := make([]int16, int(frameSize))
|
||||
n, err := dec.Decode(data, pcm)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
|
||||
// To get all samples (interleaved if multiple channels):
|
||||
pcm = pcm[:n*channels] // only necessary if you didn't know the right frame size
|
||||
|
||||
// or access sample per sample, directly:
|
||||
for i := 0; i < n; i++ {
|
||||
ch1 := pcm[i*channels+0]
|
||||
// For stereo output: copy ch1 into ch2 in mono mode, or deinterleave stereo
|
||||
ch2 := pcm[(i*channels)+(channels-1)]
|
||||
}
|
||||
```
|
||||
|
||||
To handle packet loss from an unreliable network, see the
|
||||
[DecodePLC](https://godoc.org/gopkg.in/hraban/opus.v2#Decoder.DecodePLC) and
|
||||
[DecodeFEC](https://godoc.org/gopkg.in/hraban/opus.v2#Decoder.DecodeFEC)
|
||||
options.
|
||||
|
||||
### Streams (and Files)
|
||||
|
||||
To decode a .opus file (or .ogg with Opus data), or to decode a "Opus stream"
|
||||
(which is a Ogg stream with Opus data), use the `Stream` interface. It wraps an
|
||||
io.Reader providing the raw stream bytes and returns the decoded Opus data.
|
||||
|
||||
A crude example for reading from a .opus file:
|
||||
|
||||
```go
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
s, err := opus.NewStream(f)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
defer s.Close()
|
||||
pcmbuf := make([]int16, 16384)
|
||||
for {
|
||||
n, err = s.Read(pcmbuf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
...
|
||||
}
|
||||
pcm := pcmbuf[:n*channels]
|
||||
|
||||
// send pcm to audio device here, or write to a .wav file
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
See https://godoc.org/gopkg.in/hraban/opus.v2#Stream for further info.
|
||||
|
||||
### "My .ogg/.opus file doesn't play!" or "How do I play Opus in VLC / mplayer / ...?"
|
||||
|
||||
Note: this package only does _encoding_ of your audio, to _raw opus data_. You can't just dump those all in one big file and play it back. You need extra info. First of all, you need to know how big each individual block is. Remember: opus data is a stream of encoded separate blocks, not one big stream of bytes. Second, you need meta-data: how many channels? What's the sampling rate? Frame size? Etc.
|
||||
|
||||
Look closely at the decoding sample code (not stream), above: we're passing all that meta-data in, hard-coded. If you just put all your encoded bytes in one big file and gave that to a media player, it wouldn't know what to do with it. It wouldn't even know that it's Opus data. It would just look like `/dev/random`.
|
||||
|
||||
What you need is a [container format](https://en.wikipedia.org/wiki/Container_format_(computing)).
|
||||
|
||||
Compare it to video:
|
||||
|
||||
* Encodings: MPEG[1234], VP9, H26[45], AV1
|
||||
* Container formats: .mkv, .avi, .mov, .ogv
|
||||
|
||||
For Opus audio, the most common container format is OGG, aka .ogg or .opus. You'll know OGG from OGG/Vorbis: that's [Vorbis](https://xiph.org/vorbis/) encoded audio in an OGG container. So for Opus, you'd call it OGG/Opus. But technically you could stick opus data in any container format that supports it, including e.g. Matroska (.mka for audio, you probably know it from .mkv for video).
|
||||
|
||||
Note: libopus, the C library that this wraps, technically comes with libopusfile, which can help with the creation of OGG/Opus streams from raw audio data. I just never needed it myself, so I haven't added the necessary code for it. If you find yourself adding it: send me a PR and we'll get it merged.
|
||||
|
||||
This libopus wrapper _does_ come with code for _decoding_ an OGG/Opus stream. Just not for writing one.
|
||||
|
||||
### API Docs
|
||||
|
||||
Go wrapper API reference:
|
||||
https://godoc.org/gopkg.in/hraban/opus.v2
|
||||
|
||||
Full libopus C API reference:
|
||||
https://www.opus-codec.org/docs/opus_api-1.1.3/
|
||||
|
||||
For more examples, see the `_test.go` files.
|
||||
|
||||
## Build & Installation
|
||||
|
||||
This package requires libopus and libopusfile development packages to be
|
||||
installed on your system. These are available on Debian based systems from
|
||||
aptitude as `libopus-dev` and `libopusfile-dev`, and on Mac OS X from homebrew.
|
||||
|
||||
They are linked into the app using pkg-config.
|
||||
|
||||
Debian, Ubuntu, ...:
|
||||
```sh
|
||||
sudo apt-get install pkg-config libopus-dev libopusfile-dev
|
||||
```
|
||||
|
||||
Mac:
|
||||
```sh
|
||||
brew install pkg-config opus opusfile
|
||||
```
|
||||
|
||||
### Building Without `libopusfile`
|
||||
|
||||
This package can be built without `libopusfile` by using the build tag `nolibopusfile`.
|
||||
This enables the compilation of statically-linked binaries with no external
|
||||
dependencies on operating systems without a static `libopusfile`, such as
|
||||
[Alpine Linux](https://pkgs.alpinelinux.org/contents?branch=edge&name=opusfile-dev&arch=x86_64&repo=main).
|
||||
|
||||
**Note:** this will disable all file and `Stream` APIs.
|
||||
|
||||
To enable this feature, add `-tags nolibopusfile` to your `go build` or `go test` commands:
|
||||
|
||||
```sh
|
||||
# Build
|
||||
go build -tags nolibopusfile ...
|
||||
|
||||
# Test
|
||||
go test -tags nolibopusfile ./...
|
||||
```
|
||||
|
||||
### Using in Docker
|
||||
|
||||
If your Dockerized app has this library as a dependency (directly or
|
||||
indirectly), it will need to install the aforementioned packages, too.
|
||||
|
||||
This means you can't use the standard `golang:*-onbuild` images, because those
|
||||
will try to build the app from source before allowing you to install extra
|
||||
dependencies. Instead, try this as a Dockerfile:
|
||||
|
||||
```Dockerfile
|
||||
# Choose any golang image, just make sure it doesn't have -onbuild
|
||||
FROM golang:1
|
||||
|
||||
RUN apt-get update && apt-get -y install libopus-dev libopusfile-dev
|
||||
|
||||
# Everything below is copied manually from the official -onbuild image,
|
||||
# with the ONBUILD keywords removed.
|
||||
|
||||
RUN mkdir -p /go/src/app
|
||||
WORKDIR /go/src/app
|
||||
|
||||
CMD ["go-wrapper", "run"]
|
||||
COPY . /go/src/app
|
||||
RUN go-wrapper download
|
||||
RUN go-wrapper install
|
||||
```
|
||||
|
||||
For more information, see <https://hub.docker.com/_/golang/>.
|
||||
|
||||
### Linking libopus and libopusfile
|
||||
|
||||
The opus and opusfile libraries will be linked into your application
|
||||
dynamically. This means everyone who uses the resulting binary will need those
|
||||
libraries available on their system. E.g. if you use this wrapper to write a
|
||||
music app in Go, everyone using that music app will need libopus and libopusfile
|
||||
on their system. On Debian systems the packages are called `libopus0` and
|
||||
`libopusfile0`.
|
||||
|
||||
The "cleanest" way to do this is to publish your software through a package
|
||||
manager and specify libopus and libopusfile as dependencies of your program. If
|
||||
that is not an option, you can compile the dynamic libraries yourself and ship
|
||||
them with your software as seperate (.dll or .so) files.
|
||||
|
||||
On Linux, for example, you would need the libopus.so.0 and libopusfile.so.0
|
||||
files in the same directory as the binary. Set your ELF binary's rpath to
|
||||
`$ORIGIN` (this is not a shell variable but elf magic):
|
||||
|
||||
```sh
|
||||
patchelf --set-origin '$ORIGIN' your-app-binary
|
||||
```
|
||||
|
||||
Now you can run the binary and it will automatically pick up shared library
|
||||
files from its own directory.
|
||||
|
||||
Wrap it all in a .zip, and ship.
|
||||
|
||||
I know there is a similar trick for Mac (involving prefixing the shared library
|
||||
names with `./`, which is, arguably, better). And Windows... probably just picks
|
||||
up .dll files from the same dir by default? I don't know. But there are ways.
|
||||
|
||||
## License
|
||||
|
||||
The licensing terms for the Go bindings are found in the LICENSE file. The
|
||||
authors and copyright holders are listed in the AUTHORS file.
|
||||
|
||||
The copyright notice uses range notation to indicate all years in between are
|
||||
subject to copyright, as well. This statement is necessary, apparently. For all
|
||||
those nefarious actors ready to abuse a copyright notice with incorrect
|
||||
notation, but thwarted by a mention in the README. Pfew!
|
29
gumble/go-opus/callbacks.c
Normal file
29
gumble/go-opus/callbacks.c
Normal file
@ -0,0 +1,29 @@
|
||||
// +build !nolibopusfile
|
||||
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
// Allocate callback struct in C to ensure it's not managed by the Go GC. This
|
||||
// plays nice with the CGo rules and avoids any confusion.
|
||||
|
||||
#include <opusfile.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Defined in Go. Uses the same signature as Go, no need for proxy function.
|
||||
int go_readcallback(void *p, unsigned char *buf, int nbytes);
|
||||
|
||||
static struct OpusFileCallbacks callbacks = {
|
||||
.read = go_readcallback,
|
||||
};
|
||||
|
||||
// Proxy function for op_open_callbacks, because it takes a void * context but
|
||||
// we want to pass it non-pointer data, namely an arbitrary uintptr_t
|
||||
// value. This is legal C, but go test -race (-d=checkptr) complains anyway. So
|
||||
// we have this wrapper function to shush it.
|
||||
// https://groups.google.com/g/golang-nuts/c/995uZyRPKlU
|
||||
OggOpusFile *
|
||||
my_open_callbacks(uintptr_t p, int *error)
|
||||
{
|
||||
return op_open_callbacks((void *)p, &callbacks, NULL, 0, error);
|
||||
}
|
276
gumble/go-opus/decoder.go
Normal file
276
gumble/go-opus/decoder.go
Normal file
@ -0,0 +1,276 @@
|
||||
// Copyright ÂGo Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: opus
|
||||
#include <opus.h>
|
||||
|
||||
int
|
||||
bridge_decoder_get_last_packet_duration(OpusDecoder *st, opus_int32 *samples)
|
||||
{
|
||||
return opus_decoder_ctl(st, OPUS_GET_LAST_PACKET_DURATION(samples));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var errDecUninitialized = fmt.Errorf("opus decoder uninitialized")
|
||||
|
||||
type Decoder struct {
|
||||
p *C.struct_OpusDecoder
|
||||
// Same purpose as encoder struct
|
||||
mem []byte
|
||||
sample_rate int
|
||||
channels int
|
||||
}
|
||||
|
||||
// NewDecoder allocates a new Opus decoder and initializes it with the
|
||||
// appropriate parameters. All related memory is managed by the Go GC.
|
||||
func NewDecoder(sample_rate int, channels int) (*Decoder, error) {
|
||||
var dec Decoder
|
||||
err := dec.Init(sample_rate, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dec, nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) Init(sample_rate int, channels int) error {
|
||||
if dec.p != nil {
|
||||
return fmt.Errorf("opus decoder already initialized")
|
||||
}
|
||||
if channels != 1 && channels != 2 {
|
||||
return fmt.Errorf("Number of channels must be 1 or 2: %d", channels)
|
||||
}
|
||||
size := C.opus_decoder_get_size(C.int(channels))
|
||||
dec.sample_rate = sample_rate
|
||||
dec.channels = channels
|
||||
dec.mem = make([]byte, size)
|
||||
dec.p = (*C.OpusDecoder)(unsafe.Pointer(&dec.mem[0]))
|
||||
errno := C.opus_decoder_init(
|
||||
dec.p,
|
||||
C.opus_int32(sample_rate),
|
||||
C.int(channels))
|
||||
if errno != 0 {
|
||||
return Error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode encoded Opus data into the supplied buffer. On success, returns the
|
||||
// number of samples correctly written to the target buffer.
|
||||
func (dec *Decoder) Decode(data []byte, pcm []int16) (int, error) {
|
||||
if dec.p == nil {
|
||||
return 0, errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return 0, fmt.Errorf("opus: target buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
0))
|
||||
if n < 0 {
|
||||
return 0, Error(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode encoded Opus data into the supplied buffer. On success, returns the
|
||||
// number of samples correctly written to the target buffer.
|
||||
func (dec *Decoder) DecodeFloat32(data []byte, pcm []float32) (int, error) {
|
||||
if dec.p == nil {
|
||||
return 0, errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return 0, fmt.Errorf("opus: target buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode_float(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
0))
|
||||
if n < 0 {
|
||||
return 0, Error(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// DecodeFEC encoded Opus data into the supplied buffer with forward error
|
||||
// correction.
|
||||
//
|
||||
// It is to be used on the packet directly following the lost one. The supplied
|
||||
// buffer needs to be exactly the duration of audio that is missing
|
||||
//
|
||||
// When a packet is considered "lost", DecodeFEC can be called on the next
|
||||
// packet in order to try and recover some of the lost data. The PCM needs to be
|
||||
// exactly the duration of audio that is missing. `LastPacketDuration()` can be
|
||||
// used on the decoder to get the length of the last packet. Note also that in
|
||||
// order to use this feature the encoder needs to be configured with
|
||||
// SetInBandFEC(true) and SetPacketLossPerc(x) options.
|
||||
//
|
||||
// Note that DecodeFEC automatically falls back to PLC when no FEC data is
|
||||
// available in the provided packet.
|
||||
func (dec *Decoder) DecodeFEC(data []byte, pcm []int16) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return fmt.Errorf("opus: target buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
1))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeFECFloat32 encoded Opus data into the supplied buffer with forward error
|
||||
// correction. It is to be used on the packet directly following the lost one.
|
||||
// The supplied buffer needs to be exactly the duration of audio that is missing
|
||||
func (dec *Decoder) DecodeFECFloat32(data []byte, pcm []float32) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return fmt.Errorf("opus: target buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode_float(
|
||||
dec.p,
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(len(data)),
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
1))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodePLC recovers a lost packet using Opus Packet Loss Concealment feature.
|
||||
//
|
||||
// The supplied buffer needs to be exactly the duration of audio that is missing.
|
||||
// When a packet is considered "lost", `DecodePLC` and `DecodePLCFloat32` methods
|
||||
// can be called in order to obtain something better sounding than just silence.
|
||||
// The PCM needs to be exactly the duration of audio that is missing.
|
||||
// `LastPacketDuration()` can be used on the decoder to get the length of the
|
||||
// last packet.
|
||||
//
|
||||
// This option does not require any additional encoder options. Unlike FEC,
|
||||
// PLC does not introduce additional latency. It is calculated from the previous
|
||||
// packet, not from the next one.
|
||||
func (dec *Decoder) DecodePLC(pcm []int16) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return fmt.Errorf("opus: output buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode(
|
||||
dec.p,
|
||||
nil,
|
||||
0,
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
0))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodePLCFloat32 recovers a lost packet using Opus Packet Loss Concealment feature.
|
||||
// The supplied buffer needs to be exactly the duration of audio that is missing.
|
||||
func (dec *Decoder) DecodePLCFloat32(pcm []float32) error {
|
||||
if dec.p == nil {
|
||||
return errDecUninitialized
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return fmt.Errorf("opus: target buffer empty")
|
||||
}
|
||||
if cap(pcm)%dec.channels != 0 {
|
||||
return fmt.Errorf("opus: output buffer capacity must be multiple of channels")
|
||||
}
|
||||
n := int(C.opus_decode_float(
|
||||
dec.p,
|
||||
nil,
|
||||
0,
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(cap(pcm)/dec.channels),
|
||||
0))
|
||||
if n < 0 {
|
||||
return Error(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastPacketDuration gets the duration (in samples)
|
||||
// of the last packet successfully decoded or concealed.
|
||||
func (dec *Decoder) LastPacketDuration() (int, error) {
|
||||
var samples C.opus_int32
|
||||
res := C.bridge_decoder_get_last_packet_duration(dec.p, &samples)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(samples), nil
|
||||
}
|
||||
|
||||
// Reset reinitializes the decoder with the existing parameters
|
||||
func (dec *Decoder) Reset() error {
|
||||
// Store current parameters
|
||||
sample_rate := dec.sample_rate
|
||||
channels := dec.channels
|
||||
|
||||
// Clear the decoder
|
||||
dec.p = nil
|
||||
dec.mem = nil
|
||||
|
||||
// Reinitialize with same parameters
|
||||
return dec.Init(sample_rate, channels)
|
||||
}
|
68
gumble/go-opus/decoder_test.go
Normal file
68
gumble/go-opus/decoder_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecoderNew(t *testing.T) {
|
||||
dec, err := NewDecoder(48000, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Errorf("Error creating new decoder: %v", err)
|
||||
}
|
||||
dec, err = NewDecoder(12345, 1)
|
||||
if err == nil || dec != nil {
|
||||
t.Errorf("Expected error for illegal samplerate 12345")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoderUnitialized(t *testing.T) {
|
||||
var dec Decoder
|
||||
_, err := dec.Decode(nil, nil)
|
||||
if err != errDecUninitialized {
|
||||
t.Errorf("Expected \"unitialized decoder\" error: %v", err)
|
||||
}
|
||||
_, err = dec.DecodeFloat32(nil, nil)
|
||||
if err != errDecUninitialized {
|
||||
t.Errorf("Expected \"unitialized decoder\" error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoder_GetLastPacketDuration(t *testing.T) {
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
pcm := make([]int16, FRAME_SIZE)
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
addSine(pcm, SAMPLE_RATE, G4)
|
||||
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(pcm, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
n, err = dec.Decode(data, pcm)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
samples, err := dec.LastPacketDuration()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't get last packet duration: %v", err)
|
||||
}
|
||||
if samples != n {
|
||||
t.Fatalf("Wrong duration length. Expected %d. Got %d", n, samples)
|
||||
}
|
||||
}
|
402
gumble/go-opus/encoder.go
Normal file
402
gumble/go-opus/encoder.go
Normal file
@ -0,0 +1,402 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: opus
|
||||
#include <opus.h>
|
||||
|
||||
int
|
||||
bridge_encoder_set_dtx(OpusEncoder *st, opus_int32 use_dtx)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_DTX(use_dtx));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_dtx(OpusEncoder *st, opus_int32 *dtx)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_DTX(dtx));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_in_dtx(OpusEncoder *st, opus_int32 *in_dtx)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_IN_DTX(in_dtx));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_sample_rate(OpusEncoder *st, opus_int32 *sample_rate)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_SAMPLE_RATE(sample_rate));
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bridge_encoder_set_bitrate(OpusEncoder *st, opus_int32 bitrate)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_BITRATE(bitrate));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_bitrate(OpusEncoder *st, opus_int32 *bitrate)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_BITRATE(bitrate));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_set_complexity(OpusEncoder *st, opus_int32 complexity)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_COMPLEXITY(complexity));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_complexity(OpusEncoder *st, opus_int32 *complexity)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_COMPLEXITY(complexity));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_set_max_bandwidth(OpusEncoder *st, opus_int32 max_bw)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_MAX_BANDWIDTH(max_bw));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_max_bandwidth(OpusEncoder *st, opus_int32 *max_bw)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_MAX_BANDWIDTH(max_bw));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_set_inband_fec(OpusEncoder *st, opus_int32 fec)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_INBAND_FEC(fec));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_inband_fec(OpusEncoder *st, opus_int32 *fec)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_INBAND_FEC(fec));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_set_packet_loss_perc(OpusEncoder *st, opus_int32 loss_perc)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_SET_PACKET_LOSS_PERC(loss_perc));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_get_packet_loss_perc(OpusEncoder *st, opus_int32 *loss_perc)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_GET_PACKET_LOSS_PERC(loss_perc));
|
||||
}
|
||||
|
||||
int
|
||||
bridge_encoder_reset_state(OpusEncoder *st)
|
||||
{
|
||||
return opus_encoder_ctl(st, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Bandwidth int
|
||||
|
||||
const (
|
||||
// 4 kHz passband
|
||||
Narrowband = Bandwidth(C.OPUS_BANDWIDTH_NARROWBAND)
|
||||
// 6 kHz passband
|
||||
Mediumband = Bandwidth(C.OPUS_BANDWIDTH_MEDIUMBAND)
|
||||
// 8 kHz passband
|
||||
Wideband = Bandwidth(C.OPUS_BANDWIDTH_WIDEBAND)
|
||||
// 12 kHz passband
|
||||
SuperWideband = Bandwidth(C.OPUS_BANDWIDTH_SUPERWIDEBAND)
|
||||
// 20 kHz passband
|
||||
Fullband = Bandwidth(C.OPUS_BANDWIDTH_FULLBAND)
|
||||
)
|
||||
|
||||
var errEncUninitialized = fmt.Errorf("opus encoder uninitialized")
|
||||
|
||||
// Encoder contains the state of an Opus encoder for libopus.
|
||||
type Encoder struct {
|
||||
p *C.struct_OpusEncoder
|
||||
channels int
|
||||
// Memory for the encoder struct allocated on the Go heap to allow Go GC to
|
||||
// manage it (and obviate need to free())
|
||||
mem []byte
|
||||
}
|
||||
|
||||
// NewEncoder allocates a new Opus encoder and initializes it with the
|
||||
// appropriate parameters. All related memory is managed by the Go GC.
|
||||
func NewEncoder(sample_rate int, channels int, application Application) (*Encoder, error) {
|
||||
var enc Encoder
|
||||
err := enc.Init(sample_rate, channels, application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &enc, nil
|
||||
}
|
||||
|
||||
// Init initializes a pre-allocated opus encoder. Unless the encoder has been
|
||||
// created using NewEncoder, this method must be called exactly once in the
|
||||
// life-time of this object, before calling any other methods.
|
||||
func (enc *Encoder) Init(sample_rate int, channels int, application Application) error {
|
||||
if enc.p != nil {
|
||||
return fmt.Errorf("opus encoder already initialized")
|
||||
}
|
||||
if channels != 1 && channels != 2 {
|
||||
return fmt.Errorf("Number of channels must be 1 or 2: %d", channels)
|
||||
}
|
||||
size := C.opus_encoder_get_size(C.int(channels))
|
||||
enc.channels = channels
|
||||
enc.mem = make([]byte, size)
|
||||
enc.p = (*C.OpusEncoder)(unsafe.Pointer(&enc.mem[0]))
|
||||
errno := int(C.opus_encoder_init(
|
||||
enc.p,
|
||||
C.opus_int32(sample_rate),
|
||||
C.int(channels),
|
||||
C.int(application)))
|
||||
if errno != 0 {
|
||||
return Error(int(errno))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode raw PCM data and store the result in the supplied buffer. On success,
|
||||
// returns the number of bytes used up by the encoded data.
|
||||
func (enc *Encoder) Encode(pcm []int16, data []byte) (int, error) {
|
||||
if enc.p == nil {
|
||||
return 0, errEncUninitialized
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, fmt.Errorf("opus: no target buffer")
|
||||
}
|
||||
// libopus talks about samples as 1 sample containing multiple channels. So
|
||||
// e.g. 20 samples of 2-channel data is actually 40 raw data points.
|
||||
if len(pcm)%enc.channels != 0 {
|
||||
return 0, fmt.Errorf("opus: input buffer length must be multiple of channels")
|
||||
}
|
||||
samples := len(pcm) / enc.channels
|
||||
n := int(C.opus_encode(
|
||||
enc.p,
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(samples),
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(cap(data))))
|
||||
if n < 0 {
|
||||
return 0, Error(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Encode raw PCM data and store the result in the supplied buffer. On success,
|
||||
// returns the number of bytes used up by the encoded data.
|
||||
func (enc *Encoder) EncodeFloat32(pcm []float32, data []byte) (int, error) {
|
||||
if enc.p == nil {
|
||||
return 0, errEncUninitialized
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, fmt.Errorf("opus: no data supplied")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, fmt.Errorf("opus: no target buffer")
|
||||
}
|
||||
if len(pcm)%enc.channels != 0 {
|
||||
return 0, fmt.Errorf("opus: input buffer length must be multiple of channels")
|
||||
}
|
||||
samples := len(pcm) / enc.channels
|
||||
n := int(C.opus_encode_float(
|
||||
enc.p,
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(samples),
|
||||
(*C.uchar)(&data[0]),
|
||||
C.opus_int32(cap(data))))
|
||||
if n < 0 {
|
||||
return 0, Error(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// SetDTX configures the encoder's use of discontinuous transmission (DTX).
|
||||
func (enc *Encoder) SetDTX(dtx bool) error {
|
||||
i := 0
|
||||
if dtx {
|
||||
i = 1
|
||||
}
|
||||
res := C.bridge_encoder_set_dtx(enc.p, C.opus_int32(i))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DTX reports whether this encoder is configured to use discontinuous
|
||||
// transmission (DTX).
|
||||
func (enc *Encoder) DTX() (bool, error) {
|
||||
var dtx C.opus_int32
|
||||
res := C.bridge_encoder_get_dtx(enc.p, &dtx)
|
||||
if res != C.OPUS_OK {
|
||||
return false, Error(res)
|
||||
}
|
||||
return dtx != 0, nil
|
||||
}
|
||||
|
||||
// InDTX returns whether the last encoded frame was either a comfort noise update
|
||||
// during DTX or not encoded because of DTX.
|
||||
func (enc *Encoder) InDTX() (bool, error) {
|
||||
var inDTX C.opus_int32
|
||||
res := C.bridge_encoder_get_in_dtx(enc.p, &inDTX)
|
||||
if res != C.OPUS_OK {
|
||||
return false, Error(res)
|
||||
}
|
||||
return inDTX != 0, nil
|
||||
}
|
||||
|
||||
// SampleRate returns the encoder sample rate in Hz.
|
||||
func (enc *Encoder) SampleRate() (int, error) {
|
||||
var sr C.opus_int32
|
||||
res := C.bridge_encoder_get_sample_rate(enc.p, &sr)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(sr), nil
|
||||
}
|
||||
|
||||
// SetBitrate sets the bitrate of the Encoder
|
||||
func (enc *Encoder) SetBitrate(bitrate int) error {
|
||||
res := C.bridge_encoder_set_bitrate(enc.p, C.opus_int32(bitrate))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBitrateToAuto will allow the encoder to automatically set the bitrate
|
||||
func (enc *Encoder) SetBitrateToAuto() error {
|
||||
res := C.bridge_encoder_set_bitrate(enc.p, C.opus_int32(C.OPUS_AUTO))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBitrateToMax causes the encoder to use as much rate as it can. This can be
|
||||
// useful for controlling the rate by adjusting the output buffer size.
|
||||
func (enc *Encoder) SetBitrateToMax() error {
|
||||
res := C.bridge_encoder_set_bitrate(enc.p, C.opus_int32(C.OPUS_BITRATE_MAX))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bitrate returns the bitrate of the Encoder
|
||||
func (enc *Encoder) Bitrate() (int, error) {
|
||||
var bitrate C.opus_int32
|
||||
res := C.bridge_encoder_get_bitrate(enc.p, &bitrate)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(bitrate), nil
|
||||
}
|
||||
|
||||
// SetComplexity sets the encoder's computational complexity
|
||||
func (enc *Encoder) SetComplexity(complexity int) error {
|
||||
res := C.bridge_encoder_set_complexity(enc.p, C.opus_int32(complexity))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complexity returns the computational complexity used by the encoder
|
||||
func (enc *Encoder) Complexity() (int, error) {
|
||||
var complexity C.opus_int32
|
||||
res := C.bridge_encoder_get_complexity(enc.p, &complexity)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(complexity), nil
|
||||
}
|
||||
|
||||
// SetMaxBandwidth configures the maximum bandpass that the encoder will select
|
||||
// automatically
|
||||
func (enc *Encoder) SetMaxBandwidth(maxBw Bandwidth) error {
|
||||
res := C.bridge_encoder_set_max_bandwidth(enc.p, C.opus_int32(maxBw))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxBandwidth gets the encoder's configured maximum allowed bandpass.
|
||||
func (enc *Encoder) MaxBandwidth() (Bandwidth, error) {
|
||||
var maxBw C.opus_int32
|
||||
res := C.bridge_encoder_get_max_bandwidth(enc.p, &maxBw)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return Bandwidth(maxBw), nil
|
||||
}
|
||||
|
||||
// SetInBandFEC configures the encoder's use of inband forward error
|
||||
// correction (FEC)
|
||||
func (enc *Encoder) SetInBandFEC(fec bool) error {
|
||||
i := 0
|
||||
if fec {
|
||||
i = 1
|
||||
}
|
||||
res := C.bridge_encoder_set_inband_fec(enc.p, C.opus_int32(i))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InBandFEC gets the encoder's configured inband forward error correction (FEC)
|
||||
func (enc *Encoder) InBandFEC() (bool, error) {
|
||||
var fec C.opus_int32
|
||||
res := C.bridge_encoder_get_inband_fec(enc.p, &fec)
|
||||
if res != C.OPUS_OK {
|
||||
return false, Error(res)
|
||||
}
|
||||
return fec != 0, nil
|
||||
}
|
||||
|
||||
// SetPacketLossPerc configures the encoder's expected packet loss percentage.
|
||||
func (enc *Encoder) SetPacketLossPerc(lossPerc int) error {
|
||||
res := C.bridge_encoder_set_packet_loss_perc(enc.p, C.opus_int32(lossPerc))
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PacketLossPerc gets the encoder's configured packet loss percentage.
|
||||
func (enc *Encoder) PacketLossPerc() (int, error) {
|
||||
var lossPerc C.opus_int32
|
||||
res := C.bridge_encoder_get_packet_loss_perc(enc.p, &lossPerc)
|
||||
if res != C.OPUS_OK {
|
||||
return 0, Error(res)
|
||||
}
|
||||
return int(lossPerc), nil
|
||||
}
|
||||
|
||||
// Reset resets the codec state to be equivalent to a freshly initialized state.
|
||||
func (enc *Encoder) Reset() error {
|
||||
res := C.bridge_encoder_reset_state(enc.p)
|
||||
if res != C.OPUS_OK {
|
||||
return Error(res)
|
||||
}
|
||||
return nil
|
||||
}
|
393
gumble/go-opus/encoder_test.go
Normal file
393
gumble/go-opus/encoder_test.go
Normal file
@ -0,0 +1,393 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncoderNew(t *testing.T) {
|
||||
enc, err := NewEncoder(48000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
enc, err = NewEncoder(12345, 1, AppVoIP)
|
||||
if err == nil || enc != nil {
|
||||
t.Errorf("Expected error for illegal samplerate 12345")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderUnitialized(t *testing.T) {
|
||||
var enc Encoder
|
||||
_, err := enc.Encode(nil, nil)
|
||||
if err != errEncUninitialized {
|
||||
t.Errorf("Expected \"unitialized encoder\" error: %v", err)
|
||||
}
|
||||
_, err = enc.EncodeFloat32(nil, nil)
|
||||
if err != errEncUninitialized {
|
||||
t.Errorf("Expected \"unitialized encoder\" error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderDTX(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []bool{true, false}
|
||||
for _, dtx := range vals {
|
||||
err := enc.SetDTX(dtx)
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting DTX to %t: %v", dtx, err)
|
||||
}
|
||||
gotv, err := enc.DTX()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting DTX (%t): %v", dtx, err)
|
||||
}
|
||||
if gotv != dtx {
|
||||
t.Errorf("Error set dtx: expect dtx=%v, got dtx=%v", dtx, gotv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderInDTX(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
pcm := make([]int16, FRAME_SIZE)
|
||||
silentPCM := make([]int16, FRAME_SIZE)
|
||||
out := make([]byte, FRAME_SIZE*4)
|
||||
addSine(pcm, SAMPLE_RATE, G4)
|
||||
|
||||
vals := []bool{true, false}
|
||||
for _, dtx := range vals {
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
enc.SetDTX(dtx)
|
||||
if _, err = enc.Encode(pcm, out); err != nil {
|
||||
t.Fatalf("Error encoding non-silent frame: %v", err)
|
||||
}
|
||||
gotDTX, err := enc.InDTX()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting in DTX (%t): %v", dtx, err)
|
||||
}
|
||||
if gotDTX {
|
||||
t.Fatalf("Error get in dtx: expect in dtx=false, got=true")
|
||||
}
|
||||
// Encode a few frames to let DTX kick in
|
||||
for i := 0; i < 5; i++ {
|
||||
if _, err = enc.Encode(silentPCM, out); err != nil {
|
||||
t.Fatalf("Error encoding silent frame: %v", err)
|
||||
}
|
||||
}
|
||||
gotDTX, err = enc.InDTX()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting in DTX (%t): %v", dtx, err)
|
||||
}
|
||||
if gotDTX != dtx {
|
||||
t.Errorf("Error set dtx: expect in dtx=%v, got in dtx=%v", dtx, gotDTX)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoderSampleRate(t *testing.T) {
|
||||
sample_rates := []int{8000, 12000, 16000, 24000, 48000}
|
||||
for _, f := range sample_rates {
|
||||
enc, err := NewEncoder(f, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder with sample_rate %d Hz: %v", f, err)
|
||||
}
|
||||
f2, err := enc.SampleRate()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting sample rate (%d Hz): %v", f, err)
|
||||
}
|
||||
if f != f2 {
|
||||
t.Errorf("Unexpected sample rate reported by %d Hz encoder: %d", f, f2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetBitrate(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{500, 100000, 300000}
|
||||
for _, bitrate := range vals {
|
||||
err := enc.SetBitrate(bitrate)
|
||||
if err != nil {
|
||||
t.Error("Error set bitrate:", err)
|
||||
}
|
||||
br, err := enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
if br != bitrate {
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, bitrate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetBitrateToAuto(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
bitrate := 5000
|
||||
if err := enc.SetBitrate(bitrate); err != nil {
|
||||
t.Error("Error setting bitrate:", err)
|
||||
}
|
||||
|
||||
br, err := enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
|
||||
if br != bitrate {
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, bitrate)
|
||||
}
|
||||
|
||||
err = enc.SetBitrateToAuto()
|
||||
if err != nil {
|
||||
t.Error("Error setting Auto bitrate:", err)
|
||||
}
|
||||
|
||||
br, err = enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
|
||||
brDefault := 32000 //default start value
|
||||
if br != brDefault {
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, brDefault)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetBitrateToMax(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
bitrate := 5000
|
||||
if err := enc.SetBitrate(bitrate); err != nil {
|
||||
t.Error("Error setting bitrate:", err)
|
||||
}
|
||||
|
||||
br, err := enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
|
||||
if br != bitrate {
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, bitrate)
|
||||
}
|
||||
|
||||
err = enc.SetBitrateToMax()
|
||||
if err != nil {
|
||||
t.Error("Error setting Max bitrate:", err)
|
||||
}
|
||||
|
||||
br, err = enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
|
||||
brMax := 4083200
|
||||
if br != brMax { //default start value
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, brMax)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInvalidBitrate(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
invalidVals := []int{-20, 0}
|
||||
for _, bitrate := range invalidVals {
|
||||
err := enc.SetBitrate(bitrate)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Error invalid bitrate: %d", bitrate)
|
||||
}
|
||||
br, err := enc.Bitrate()
|
||||
if err != nil {
|
||||
t.Error("Error getting bitrate", err)
|
||||
}
|
||||
// default bitrate is 32 kbit/s
|
||||
if br != 32000 {
|
||||
t.Errorf("Unexpected bitrate. Got %d, but expected %d", br, bitrate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetComplexity(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
for _, complexity := range vals {
|
||||
err := enc.SetComplexity(complexity)
|
||||
if err != nil {
|
||||
t.Error("Error setting complexity value:", err)
|
||||
}
|
||||
cpx, err := enc.Complexity()
|
||||
if err != nil {
|
||||
t.Error("Error getting complexity value", err)
|
||||
}
|
||||
if cpx != complexity {
|
||||
t.Errorf("Unexpected encoder complexity value. Got %d, but expected %d",
|
||||
cpx, complexity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInvalidComplexity(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
invalidVals := []int{-20, 11, 1000}
|
||||
for _, complexity := range invalidVals {
|
||||
err := enc.SetComplexity(complexity)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Error invalid complexity value: %d", complexity)
|
||||
}
|
||||
if err.Error() != "opus: invalid argument" {
|
||||
t.Error("Unexpected Error message")
|
||||
}
|
||||
|
||||
cpx, err := enc.Complexity()
|
||||
if err != nil {
|
||||
t.Error("Error getting complexity value", err)
|
||||
}
|
||||
|
||||
// default complexity value is 9
|
||||
if cpx != 9 {
|
||||
t.Errorf("Unexpected complexity value. Got %d, but expected %d",
|
||||
cpx, complexity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetMaxBandwidth(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []Bandwidth{
|
||||
Narrowband,
|
||||
Mediumband,
|
||||
Wideband,
|
||||
SuperWideband,
|
||||
Fullband,
|
||||
}
|
||||
for _, maxBw := range vals {
|
||||
err := enc.SetMaxBandwidth(maxBw)
|
||||
if err != nil {
|
||||
t.Error("Error setting max Bandwidth:", err)
|
||||
}
|
||||
maxBwRead, err := enc.MaxBandwidth()
|
||||
if err != nil {
|
||||
t.Error("Error getting max Bandwidth", err)
|
||||
}
|
||||
if maxBwRead != maxBw {
|
||||
t.Errorf("Unexpected max Bandwidth value. Got %d, but expected %d",
|
||||
maxBwRead, maxBw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInBandFEC(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
if err := enc.SetInBandFEC(true); err != nil {
|
||||
t.Error("Error setting fec:", err)
|
||||
}
|
||||
|
||||
fec, err := enc.InBandFEC()
|
||||
if err != nil {
|
||||
t.Error("Error getting fec", err)
|
||||
}
|
||||
if !fec {
|
||||
t.Errorf("Wrong fec value. Expected %t", true)
|
||||
}
|
||||
|
||||
if err := enc.SetInBandFEC(false); err != nil {
|
||||
t.Error("Error setting fec:", err)
|
||||
}
|
||||
|
||||
fec, err = enc.InBandFEC()
|
||||
if err != nil {
|
||||
t.Error("Error getting fec", err)
|
||||
}
|
||||
if fec {
|
||||
t.Errorf("Wrong fec value. Expected %t", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetPacketLossPerc(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{0, 5, 10, 20}
|
||||
for _, lossPerc := range vals {
|
||||
err := enc.SetPacketLossPerc(lossPerc)
|
||||
if err != nil {
|
||||
t.Error("Error setting loss percentage value:", err)
|
||||
}
|
||||
lp, err := enc.PacketLossPerc()
|
||||
if err != nil {
|
||||
t.Error("Error getting loss percentage value", err)
|
||||
}
|
||||
if lp != lossPerc {
|
||||
t.Errorf("Unexpected encoder loss percentage value. Got %d, but expected %d",
|
||||
lp, lossPerc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_SetGetInvalidPacketLossPerc(t *testing.T) {
|
||||
enc, err := NewEncoder(8000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
vals := []int{-1, 101}
|
||||
for _, lossPerc := range vals {
|
||||
err := enc.SetPacketLossPerc(lossPerc)
|
||||
if err == nil {
|
||||
t.Errorf("Expected Error invalid loss percentage: %d", lossPerc)
|
||||
}
|
||||
lp, err := enc.PacketLossPerc()
|
||||
if err != nil {
|
||||
t.Error("Error getting loss percentage value", err)
|
||||
}
|
||||
// default packet loss percentage is 0
|
||||
if lp != 0 {
|
||||
t.Errorf("Unexpected encoder loss percentage value. Got %d, but expected %d",
|
||||
lp, lossPerc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoder_Reset(t *testing.T) {
|
||||
enc, err := NewEncoder(48000, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Errorf("Error creating new encoder: %v", err)
|
||||
}
|
||||
RunTestCodec(t, enc)
|
||||
if err := enc.Reset(); err != nil {
|
||||
t.Errorf("Error reset encoder: %v", err)
|
||||
}
|
||||
RunTestCodec(t, enc)
|
||||
}
|
36
gumble/go-opus/errors.go
Normal file
36
gumble/go-opus/errors.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: opus
|
||||
#include <opus.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Error int
|
||||
|
||||
var _ error = Error(0)
|
||||
|
||||
// Libopus errors
|
||||
const (
|
||||
ErrOK = Error(C.OPUS_OK)
|
||||
ErrBadArg = Error(C.OPUS_BAD_ARG)
|
||||
ErrBufferTooSmall = Error(C.OPUS_BUFFER_TOO_SMALL)
|
||||
ErrInternalError = Error(C.OPUS_INTERNAL_ERROR)
|
||||
ErrInvalidPacket = Error(C.OPUS_INVALID_PACKET)
|
||||
ErrUnimplemented = Error(C.OPUS_UNIMPLEMENTED)
|
||||
ErrInvalidState = Error(C.OPUS_INVALID_STATE)
|
||||
ErrAllocFail = Error(C.OPUS_ALLOC_FAIL)
|
||||
)
|
||||
|
||||
// Error string (in human readable format) for libopus errors.
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("opus: %s", C.GoString(C.opus_strerror(C.int(e))))
|
||||
}
|
36
gumble/go-opus/opus.go
Normal file
36
gumble/go-opus/opus.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
/*
|
||||
// Link opus using pkg-config.
|
||||
#cgo pkg-config: opus
|
||||
#include <opus.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type Application int
|
||||
|
||||
const (
|
||||
// Optimize encoding for VoIP
|
||||
AppVoIP = Application(C.OPUS_APPLICATION_VOIP)
|
||||
// Optimize encoding for non-voice signals like music
|
||||
AppAudio = Application(C.OPUS_APPLICATION_AUDIO)
|
||||
// Optimize encoding for low latency applications
|
||||
AppRestrictedLowdelay = Application(C.OPUS_APPLICATION_RESTRICTED_LOWDELAY)
|
||||
)
|
||||
|
||||
const (
|
||||
xMAX_BITRATE = 48000
|
||||
xMAX_FRAME_SIZE_MS = 60
|
||||
xMAX_FRAME_SIZE = xMAX_BITRATE * xMAX_FRAME_SIZE_MS / 1000
|
||||
// Maximum size of an encoded frame. I actually have no idea, but this
|
||||
// looks like it's big enough.
|
||||
maxEncodedFrameSize = 10000
|
||||
)
|
||||
|
||||
func Version() string {
|
||||
return C.GoString(C.opus_get_version_string())
|
||||
}
|
643
gumble/go-opus/opus_test.go
Normal file
643
gumble/go-opus/opus_test.go
Normal file
@ -0,0 +1,643 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
if ver := Version(); !strings.HasPrefix(ver, "libopus") {
|
||||
t.Errorf("Unexpected linked libopus version: " + ver)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpusErrstr(t *testing.T) {
|
||||
// I scooped this -1 up from opus_defines.h, it's OPUS_BAD_ARG. Not pretty,
|
||||
// but it's better than not testing at all. Again, accessing #defines from
|
||||
// CGO is not possible.
|
||||
if ErrBadArg.Error() != "opus: invalid argument" {
|
||||
t.Errorf("Expected \"invalid argument\" error message for error code %d: %v",
|
||||
ErrBadArg, ErrBadArg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodec(t *testing.T) {
|
||||
const SAMPLE_RATE = 48000
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
RunTestCodec(t, enc)
|
||||
}
|
||||
|
||||
func RunTestCodec(t *testing.T, enc *Encoder) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
pcm := make([]int16, FRAME_SIZE)
|
||||
addSine(pcm, SAMPLE_RATE, G4)
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(pcm, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
n, err = dec.Decode(data, pcm)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
if len(pcm) != n {
|
||||
t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n)
|
||||
}
|
||||
// Checking the output programmatically is seriously not easy. I checked it
|
||||
// by hand and by ear, it looks fine. I'll just assume the API faithfully
|
||||
// passes error codes through, and that's that.
|
||||
}
|
||||
|
||||
func TestCodecFloat32(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
pcm := make([]float32, FRAME_SIZE)
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
addSineFloat32(pcm, SAMPLE_RATE, G4)
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.EncodeFloat32(pcm, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
// TODO: Uh-oh.. it looks like I forgot to put a data = data[:n] here, yet
|
||||
// the test is not failing. Why?
|
||||
n, err = dec.DecodeFloat32(data, pcm)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
if len(pcm) != n {
|
||||
t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodecFEC(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 10
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
const NUMBER_OF_FRAMES = 6
|
||||
pcm := make([]int16, 0)
|
||||
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
enc.SetPacketLossPerc(30)
|
||||
enc.SetInBandFEC(true)
|
||||
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
|
||||
mono := make([]int16, FRAME_SIZE*NUMBER_OF_FRAMES)
|
||||
addSine(mono, SAMPLE_RATE, G4)
|
||||
|
||||
encodedData := make([][]byte, NUMBER_OF_FRAMES)
|
||||
for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE {
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(mono[i:i+FRAME_SIZE], data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
encodedData[idx] = data
|
||||
idx++
|
||||
}
|
||||
|
||||
lost := false
|
||||
for i := 0; i < len(encodedData); i++ {
|
||||
// "Dropping" packets 2 and 4
|
||||
if i == 2 || i == 4 {
|
||||
lost = true
|
||||
continue
|
||||
}
|
||||
if lost {
|
||||
samples, err := dec.LastPacketDuration()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't get last packet duration: %v", err)
|
||||
}
|
||||
|
||||
pcmBuffer := make([]int16, samples)
|
||||
if err = dec.DecodeFEC(encodedData[i], pcmBuffer); err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
lost = false
|
||||
}
|
||||
|
||||
pcmBuffer := make([]int16, NUMBER_OF_FRAMES*FRAME_SIZE)
|
||||
n, err := dec.Decode(encodedData[i], pcmBuffer)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcmBuffer = pcmBuffer[:n]
|
||||
if n != FRAME_SIZE {
|
||||
t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
}
|
||||
|
||||
if len(mono) != len(pcm) {
|
||||
t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm))
|
||||
}
|
||||
|
||||
// Commented out for the same reason as in TestStereo
|
||||
/*
|
||||
fmt.Printf("in,out\n")
|
||||
for i := range mono {
|
||||
fmt.Printf("%d,%d\n", mono[i], pcm[i])
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
func TestCodecFECFloat32(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 10
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
const NUMBER_OF_FRAMES = 6
|
||||
pcm := make([]float32, 0)
|
||||
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
enc.SetPacketLossPerc(30)
|
||||
enc.SetInBandFEC(true)
|
||||
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
|
||||
mono := make([]float32, FRAME_SIZE*NUMBER_OF_FRAMES)
|
||||
addSineFloat32(mono, SAMPLE_RATE, G4)
|
||||
|
||||
encodedData := make([][]byte, NUMBER_OF_FRAMES)
|
||||
for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE {
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.EncodeFloat32(mono[i:i+FRAME_SIZE], data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
encodedData[idx] = data
|
||||
idx++
|
||||
}
|
||||
|
||||
lost := false
|
||||
for i := 0; i < len(encodedData); i++ {
|
||||
// "Dropping" packets 2 and 4
|
||||
if i == 2 || i == 4 {
|
||||
lost = true
|
||||
continue
|
||||
}
|
||||
if lost {
|
||||
samples, err := dec.LastPacketDuration()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't get last packet duration: %v", err)
|
||||
}
|
||||
|
||||
pcmBuffer := make([]float32, samples)
|
||||
if err = dec.DecodeFECFloat32(encodedData[i], pcmBuffer); err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
lost = false
|
||||
}
|
||||
|
||||
pcmBuffer := make([]float32, NUMBER_OF_FRAMES*FRAME_SIZE)
|
||||
n, err := dec.DecodeFloat32(encodedData[i], pcmBuffer)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcmBuffer = pcmBuffer[:n]
|
||||
if n != FRAME_SIZE {
|
||||
t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
}
|
||||
|
||||
if len(mono) != len(pcm) {
|
||||
t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm))
|
||||
}
|
||||
|
||||
// Commented out for the same reason as in TestStereo
|
||||
/*
|
||||
fmt.Printf("in,out\n")
|
||||
for i := 0; i < len(mono); i++ {
|
||||
fmt.Printf("%f,%f\n", mono[i], pcm[i])
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func TestCodecPLC(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 10
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
const NUMBER_OF_FRAMES = 6
|
||||
pcm := make([]int16, 0)
|
||||
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
|
||||
mono := make([]int16, FRAME_SIZE*NUMBER_OF_FRAMES)
|
||||
addSine(mono, SAMPLE_RATE, G4)
|
||||
|
||||
encodedData := make([][]byte, NUMBER_OF_FRAMES)
|
||||
for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE {
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(mono[i:i+FRAME_SIZE], data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
encodedData[idx] = data
|
||||
idx++
|
||||
}
|
||||
|
||||
lost := false
|
||||
for i := 0; i < len(encodedData); i++ {
|
||||
// "Dropping" packets 2 and 4
|
||||
if i == 2 || i == 4 {
|
||||
lost = true
|
||||
continue
|
||||
}
|
||||
if lost {
|
||||
samples, err := dec.LastPacketDuration()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't get last packet duration: %v", err)
|
||||
}
|
||||
|
||||
pcmBuffer := make([]int16, samples)
|
||||
if err = dec.DecodePLC(pcmBuffer); err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
nonZero := 0
|
||||
for n := range pcmBuffer {
|
||||
if pcmBuffer[n] != 0 {
|
||||
nonZero++
|
||||
}
|
||||
}
|
||||
if nonZero == 0 {
|
||||
t.Fatalf("DecodePLC produced only zero samples")
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
lost = false
|
||||
}
|
||||
|
||||
pcmBuffer := make([]int16, NUMBER_OF_FRAMES*FRAME_SIZE)
|
||||
n, err := dec.Decode(encodedData[i], pcmBuffer)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcmBuffer = pcmBuffer[:n]
|
||||
if n != FRAME_SIZE {
|
||||
t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
}
|
||||
|
||||
if len(mono) != len(pcm) {
|
||||
t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm))
|
||||
}
|
||||
|
||||
// Commented out for the same reason as in TestStereo
|
||||
/*
|
||||
fmt.Printf("in,out\n")
|
||||
for i := range mono {
|
||||
fmt.Printf("%d,%d\n", mono[i], pcm[i])
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
func TestCodecPLCFloat32(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 10
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
const NUMBER_OF_FRAMES = 6
|
||||
pcm := make([]float32, 0)
|
||||
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
|
||||
mono := make([]float32, FRAME_SIZE*NUMBER_OF_FRAMES)
|
||||
addSineFloat32(mono, SAMPLE_RATE, G4)
|
||||
|
||||
encodedData := make([][]byte, NUMBER_OF_FRAMES)
|
||||
for i, idx := 0, 0; i < len(mono); i += FRAME_SIZE {
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.EncodeFloat32(mono[i:i+FRAME_SIZE], data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
encodedData[idx] = data
|
||||
idx++
|
||||
}
|
||||
|
||||
lost := false
|
||||
for i := 0; i < len(encodedData); i++ {
|
||||
// "Dropping" packets 2 and 4
|
||||
if i == 2 || i == 4 {
|
||||
lost = true
|
||||
continue
|
||||
}
|
||||
if lost {
|
||||
samples, err := dec.LastPacketDuration()
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't get last packet duration: %v", err)
|
||||
}
|
||||
|
||||
pcmBuffer := make([]float32, samples)
|
||||
if err = dec.DecodePLCFloat32(pcmBuffer); err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
nonZero := 0
|
||||
for n := range pcmBuffer {
|
||||
if pcmBuffer[n] != 0.0 {
|
||||
nonZero++
|
||||
}
|
||||
}
|
||||
if nonZero == 0 {
|
||||
t.Fatalf("DecodePLC produced only zero samples")
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
lost = false
|
||||
}
|
||||
|
||||
pcmBuffer := make([]float32, NUMBER_OF_FRAMES*FRAME_SIZE)
|
||||
n, err := dec.DecodeFloat32(encodedData[i], pcmBuffer)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
pcmBuffer = pcmBuffer[:n]
|
||||
if n != FRAME_SIZE {
|
||||
t.Fatalf("Length mismatch: %d samples expected, %d out", FRAME_SIZE, n)
|
||||
}
|
||||
pcm = append(pcm, pcmBuffer...)
|
||||
}
|
||||
|
||||
if len(mono) != len(pcm) {
|
||||
t.Fatalf("Input/Output length mismatch: %d samples in, %d out", len(mono), len(pcm))
|
||||
}
|
||||
|
||||
// Commented out for the same reason as in TestStereo
|
||||
/*
|
||||
fmt.Printf("in,out\n")
|
||||
for i := 0; i < len(mono); i++ {
|
||||
fmt.Printf("%f,%f\n", mono[i], pcm[i])
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func TestStereo(t *testing.T) {
|
||||
// Create bogus input sound
|
||||
const G4 = 391.995
|
||||
const E3 = 164.814
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const CHANNELS = 2
|
||||
const FRAME_SIZE_MONO = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
|
||||
enc, err := NewEncoder(SAMPLE_RATE, CHANNELS, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
dec, err := NewDecoder(SAMPLE_RATE, CHANNELS)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
|
||||
// Source signal (left G4, right E3)
|
||||
left := make([]int16, FRAME_SIZE_MONO)
|
||||
right := make([]int16, FRAME_SIZE_MONO)
|
||||
addSine(left, SAMPLE_RATE, G4)
|
||||
addSine(right, SAMPLE_RATE, E3)
|
||||
pcm := interleave(left, right)
|
||||
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(pcm, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
data = data[:n]
|
||||
n, err = dec.Decode(data, pcm)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
if n*CHANNELS != len(pcm) {
|
||||
t.Fatalf("Length mismatch: %d samples in, %d out", len(pcm), n*CHANNELS)
|
||||
}
|
||||
|
||||
// This is hard to check programatically, but I plotted the graphs in a
|
||||
// spreadsheet and it looked great. The encoded waves both start out with a
|
||||
// string of zero samples before they pick up, and the G4 is phase shifted
|
||||
// by half a period, but that's all fine for lossy audio encoding.
|
||||
/*
|
||||
leftdec, rightdec := split(pcm)
|
||||
fmt.Printf("left_in,left_out,right_in,right_out\n")
|
||||
for i := range left {
|
||||
fmt.Printf("%d,%d,%d,%d\n", left[i], leftdec[i], right[i], rightdec[i])
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func TestBufferSize(t *testing.T) {
|
||||
const G4 = 391.995
|
||||
const SAMPLE_RATE = 48000
|
||||
const FRAME_SIZE_MS = 60
|
||||
const FRAME_SIZE = SAMPLE_RATE * FRAME_SIZE_MS / 1000
|
||||
const GUARD_SIZE = 100
|
||||
|
||||
checkGuardInt16 := func(t *testing.T, s []int16) {
|
||||
for n := range s {
|
||||
if s[n] != 0 {
|
||||
t.Fatal("Memory corruption detected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkGuardFloat32 := func(t *testing.T, s []float32) {
|
||||
for n := range s {
|
||||
if s[n] != 0 {
|
||||
t.Fatal("Memory corruption detected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkResult := func(t *testing.T, n int, err error, expectOK bool) {
|
||||
if expectOK {
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't decode data: %v", err)
|
||||
}
|
||||
if n != FRAME_SIZE {
|
||||
t.Fatalf("Length mismatch: %d samples in, %d out", FRAME_SIZE, n)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected decode failure, but it succeeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encodeFrame := func(t *testing.T) []byte {
|
||||
pcm := make([]int16, FRAME_SIZE)
|
||||
enc, err := NewEncoder(SAMPLE_RATE, 1, AppVoIP)
|
||||
if err != nil || enc == nil {
|
||||
t.Fatalf("Error creating new encoder: %v", err)
|
||||
}
|
||||
addSine(pcm, SAMPLE_RATE, G4)
|
||||
data := make([]byte, 1000)
|
||||
n, err := enc.Encode(pcm, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't encode data: %v", err)
|
||||
}
|
||||
return data[:n]
|
||||
}
|
||||
|
||||
createDecoder := func(t *testing.T) *Decoder {
|
||||
dec, err := NewDecoder(SAMPLE_RATE, 1)
|
||||
if err != nil || dec == nil {
|
||||
t.Fatalf("Error creating new decoder: %v", err)
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
decodeInt16 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) {
|
||||
dec := createDecoder(t)
|
||||
decodedMem := make([]int16, decodeSize+GUARD_SIZE*2)
|
||||
decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize]
|
||||
n, err := dec.Decode(data, decodedRef)
|
||||
checkGuardInt16(t, decodedMem[:GUARD_SIZE])
|
||||
checkGuardInt16(t, decodedMem[decodeSize+GUARD_SIZE:])
|
||||
checkResult(t, n, err, expectOK)
|
||||
}
|
||||
|
||||
decodeFloat32 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) {
|
||||
dec := createDecoder(t)
|
||||
decodedMem := make([]float32, decodeSize+GUARD_SIZE*2)
|
||||
decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize]
|
||||
n, err := dec.DecodeFloat32(data, decodedRef)
|
||||
checkGuardFloat32(t, decodedMem[:GUARD_SIZE])
|
||||
checkGuardFloat32(t, decodedMem[decodeSize+GUARD_SIZE:])
|
||||
checkResult(t, n, err, expectOK)
|
||||
}
|
||||
|
||||
decodeFecInt16 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) {
|
||||
dec := createDecoder(t)
|
||||
decodedMem := make([]int16, decodeSize+GUARD_SIZE*2)
|
||||
decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize]
|
||||
err := dec.DecodeFEC(data, decodedRef)
|
||||
checkGuardInt16(t, decodedMem[:GUARD_SIZE])
|
||||
checkGuardInt16(t, decodedMem[decodeSize+GUARD_SIZE:])
|
||||
checkResult(t, FRAME_SIZE, err, expectOK)
|
||||
}
|
||||
|
||||
decodeFecFloat32 := func(t *testing.T, data []byte, decodeSize int, expectOK bool) {
|
||||
dec := createDecoder(t)
|
||||
decodedMem := make([]float32, decodeSize+GUARD_SIZE*2)
|
||||
decodedRef := decodedMem[GUARD_SIZE : GUARD_SIZE+decodeSize : GUARD_SIZE+decodeSize]
|
||||
err := dec.DecodeFECFloat32(data, decodedRef)
|
||||
checkGuardFloat32(t, decodedMem[:GUARD_SIZE])
|
||||
checkGuardFloat32(t, decodedMem[decodeSize+GUARD_SIZE:])
|
||||
checkResult(t, FRAME_SIZE, err, expectOK)
|
||||
}
|
||||
|
||||
t.Run("smaller-buffer-int16", func(t *testing.T) {
|
||||
decodeInt16(t, encodeFrame(t), FRAME_SIZE-1, false)
|
||||
})
|
||||
|
||||
t.Run("smaller-buffer-float32", func(t *testing.T) {
|
||||
decodeFloat32(t, encodeFrame(t), FRAME_SIZE-1, false)
|
||||
})
|
||||
|
||||
t.Run("smaller-buffer-int16-fec", func(t *testing.T) {
|
||||
decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE-1, false)
|
||||
})
|
||||
|
||||
t.Run("smaller-buffer-float32-fec", func(t *testing.T) {
|
||||
decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE-1, false)
|
||||
})
|
||||
|
||||
t.Run("exact-buffer-int16", func(t *testing.T) {
|
||||
decodeInt16(t, encodeFrame(t), FRAME_SIZE, true)
|
||||
})
|
||||
|
||||
t.Run("exact-buffer-float32", func(t *testing.T) {
|
||||
decodeFloat32(t, encodeFrame(t), FRAME_SIZE, true)
|
||||
})
|
||||
|
||||
t.Run("exact-buffer-int16-fec", func(t *testing.T) {
|
||||
decodeFecInt16(t, encodeFrame(t), FRAME_SIZE, true)
|
||||
})
|
||||
|
||||
t.Run("exact-buffer-float32-fec", func(t *testing.T) {
|
||||
decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE, true)
|
||||
})
|
||||
|
||||
t.Run("larger-buffer-int16", func(t *testing.T) {
|
||||
decodeInt16(t, encodeFrame(t), FRAME_SIZE+1, true)
|
||||
})
|
||||
|
||||
t.Run("larger-buffer-float32", func(t *testing.T) {
|
||||
decodeFloat32(t, encodeFrame(t), FRAME_SIZE+1, true)
|
||||
})
|
||||
|
||||
t.Run("larger-buffer-int16-fec", func(t *testing.T) {
|
||||
decodeFecInt16(t, encodeFrame(t), FRAME_SIZE+1, false)
|
||||
})
|
||||
|
||||
t.Run("larger-buffer-float32-fec", func(t *testing.T) {
|
||||
decodeFecFloat32(t, encodeFrame(t), FRAME_SIZE+1, false)
|
||||
})
|
||||
}
|
183
gumble/go-opus/stream.go
Normal file
183
gumble/go-opus/stream.go
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
// +build !nolibopusfile
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: opusfile
|
||||
#include <opusfile.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
OggOpusFile *my_open_callbacks(uintptr_t p, int *error);
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Stream wraps a io.Reader in a decoding layer. It provides an API similar to
|
||||
// io.Reader, but it provides raw PCM data instead of the encoded Opus data.
|
||||
//
|
||||
// This is not the same as directly decoding the bytes on the io.Reader; opus
|
||||
// streams are Ogg Opus audio streams, which package raw Opus data.
|
||||
//
|
||||
// This wraps libopusfile. For more information, see the api docs on xiph.org:
|
||||
//
|
||||
// https://www.opus-codec.org/docs/opusfile_api-0.7/index.html
|
||||
type Stream struct {
|
||||
id uintptr
|
||||
oggfile *C.OggOpusFile
|
||||
read io.Reader
|
||||
// Preallocated buffer to pass to the reader
|
||||
buf []byte
|
||||
}
|
||||
|
||||
var streams = newStreamsMap()
|
||||
|
||||
//export go_readcallback
|
||||
func go_readcallback(p unsafe.Pointer, cbuf *C.uchar, cmaxbytes C.int) C.int {
|
||||
streamId := uintptr(p)
|
||||
stream := streams.Get(streamId)
|
||||
if stream == nil {
|
||||
// This is bad
|
||||
return -1
|
||||
}
|
||||
|
||||
maxbytes := int(cmaxbytes)
|
||||
if maxbytes > cap(stream.buf) {
|
||||
maxbytes = cap(stream.buf)
|
||||
}
|
||||
// Don't bother cleaning up old data because that's not required by the
|
||||
// io.Reader API.
|
||||
n, err := stream.read.Read(stream.buf[:maxbytes])
|
||||
// Go allows returning non-nil error (like EOF) and n>0, libopusfile doesn't
|
||||
// expect that. So return n first to indicate the valid bytes, let the
|
||||
// subsequent call (which will be n=0, same-error) handle the actual error.
|
||||
if n == 0 && err != nil {
|
||||
if err == io.EOF {
|
||||
return 0
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
C.memcpy(unsafe.Pointer(cbuf), unsafe.Pointer(&stream.buf[0]), C.size_t(n))
|
||||
return C.int(n)
|
||||
}
|
||||
|
||||
// NewStream creates and initializes a new stream. Don't call .Init() on this.
|
||||
func NewStream(read io.Reader) (*Stream, error) {
|
||||
var s Stream
|
||||
err := s.Init(read)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Init initializes a stream with an io.Reader to fetch opus encoded data from
|
||||
// on demand. Errors from the reader are all transformed to an EOF, any actual
|
||||
// error information is lost. The same happens when a read returns succesfully,
|
||||
// but with zero bytes.
|
||||
func (s *Stream) Init(read io.Reader) error {
|
||||
if s.oggfile != nil {
|
||||
return fmt.Errorf("opus stream is already initialized")
|
||||
}
|
||||
if read == nil {
|
||||
return fmt.Errorf("Reader must be non-nil")
|
||||
}
|
||||
|
||||
s.read = read
|
||||
s.buf = make([]byte, maxEncodedFrameSize)
|
||||
s.id = streams.NextId()
|
||||
var errno C.int
|
||||
|
||||
// Immediately delete the stream after .Init to avoid leaking if the
|
||||
// caller forgets to (/ doesn't want to) call .Close(). No need for that,
|
||||
// since the callback is only ever called during a .Read operation; just
|
||||
// Save and Delete from the map around that every time a reader function is
|
||||
// called.
|
||||
streams.Save(s)
|
||||
defer streams.Del(s)
|
||||
oggfile := C.my_open_callbacks(C.uintptr_t(s.id), &errno)
|
||||
if errno != 0 {
|
||||
return StreamError(errno)
|
||||
}
|
||||
s.oggfile = oggfile
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read a chunk of raw opus data from the stream and decode it. Returns the
|
||||
// number of decoded samples per channel. This means that a dual channel
|
||||
// (stereo) feed will have twice as many samples as the value returned.
|
||||
//
|
||||
// Read may successfully read less bytes than requested, but it will never read
|
||||
// exactly zero bytes succesfully if a non-zero buffer is supplied.
|
||||
//
|
||||
// The number of channels in the output data must be known in advance. It is
|
||||
// possible to extract this information from the stream itself, but I'm not
|
||||
// motivated to do that. Feel free to send a pull request.
|
||||
func (s *Stream) Read(pcm []int16) (int, error) {
|
||||
if s.oggfile == nil {
|
||||
return 0, fmt.Errorf("opus stream is uninitialized or already closed")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
streams.Save(s)
|
||||
defer streams.Del(s)
|
||||
n := C.op_read(
|
||||
s.oggfile,
|
||||
(*C.opus_int16)(&pcm[0]),
|
||||
C.int(len(pcm)),
|
||||
nil)
|
||||
if n < 0 {
|
||||
return 0, StreamError(n)
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
// ReadFloat32 is the same as Read, but decodes to float32 instead of int16.
|
||||
func (s *Stream) ReadFloat32(pcm []float32) (int, error) {
|
||||
if s.oggfile == nil {
|
||||
return 0, fmt.Errorf("opus stream is uninitialized or already closed")
|
||||
}
|
||||
if len(pcm) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
streams.Save(s)
|
||||
defer streams.Del(s)
|
||||
n := C.op_read_float(
|
||||
s.oggfile,
|
||||
(*C.float)(&pcm[0]),
|
||||
C.int(len(pcm)),
|
||||
nil)
|
||||
if n < 0 {
|
||||
return 0, StreamError(n)
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func (s *Stream) Close() error {
|
||||
if s.oggfile == nil {
|
||||
return fmt.Errorf("opus stream is uninitialized or already closed")
|
||||
}
|
||||
C.op_free(s.oggfile)
|
||||
if closer, ok := s.read.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
75
gumble/go-opus/stream_errors.go
Normal file
75
gumble/go-opus/stream_errors.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright © 2015-2017 Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
// +build !nolibopusfile
|
||||
|
||||
package opus
|
||||
|
||||
/*
|
||||
#cgo pkg-config: opusfile
|
||||
#include <opusfile.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// StreamError represents an error from libopusfile.
|
||||
type StreamError int
|
||||
|
||||
var _ error = StreamError(0)
|
||||
|
||||
// Libopusfile errors. The names are copied verbatim from the libopusfile
|
||||
// library.
|
||||
const (
|
||||
ErrStreamFalse = StreamError(C.OP_FALSE)
|
||||
ErrStreamEOF = StreamError(C.OP_EOF)
|
||||
ErrStreamHole = StreamError(C.OP_HOLE)
|
||||
ErrStreamRead = StreamError(C.OP_EREAD)
|
||||
ErrStreamFault = StreamError(C.OP_EFAULT)
|
||||
ErrStreamImpl = StreamError(C.OP_EIMPL)
|
||||
ErrStreamInval = StreamError(C.OP_EINVAL)
|
||||
ErrStreamNotFormat = StreamError(C.OP_ENOTFORMAT)
|
||||
ErrStreamBadHeader = StreamError(C.OP_EBADHEADER)
|
||||
ErrStreamVersion = StreamError(C.OP_EVERSION)
|
||||
ErrStreamNotAudio = StreamError(C.OP_ENOTAUDIO)
|
||||
ErrStreamBadPacked = StreamError(C.OP_EBADPACKET)
|
||||
ErrStreamBadLink = StreamError(C.OP_EBADLINK)
|
||||
ErrStreamNoSeek = StreamError(C.OP_ENOSEEK)
|
||||
ErrStreamBadTimestamp = StreamError(C.OP_EBADTIMESTAMP)
|
||||
)
|
||||
|
||||
func (i StreamError) Error() string {
|
||||
switch i {
|
||||
case ErrStreamFalse:
|
||||
return "OP_FALSE"
|
||||
case ErrStreamEOF:
|
||||
return "OP_EOF"
|
||||
case ErrStreamHole:
|
||||
return "OP_HOLE"
|
||||
case ErrStreamRead:
|
||||
return "OP_EREAD"
|
||||
case ErrStreamFault:
|
||||
return "OP_EFAULT"
|
||||
case ErrStreamImpl:
|
||||
return "OP_EIMPL"
|
||||
case ErrStreamInval:
|
||||
return "OP_EINVAL"
|
||||
case ErrStreamNotFormat:
|
||||
return "OP_ENOTFORMAT"
|
||||
case ErrStreamBadHeader:
|
||||
return "OP_EBADHEADER"
|
||||
case ErrStreamVersion:
|
||||
return "OP_EVERSION"
|
||||
case ErrStreamNotAudio:
|
||||
return "OP_ENOTAUDIO"
|
||||
case ErrStreamBadPacked:
|
||||
return "OP_EBADPACKET"
|
||||
case ErrStreamBadLink:
|
||||
return "OP_EBADLINK"
|
||||
case ErrStreamNoSeek:
|
||||
return "OP_ENOSEEK"
|
||||
case ErrStreamBadTimestamp:
|
||||
return "OP_EBADTIMESTAMP"
|
||||
default:
|
||||
return "libopusfile error: %d (unknown code)"
|
||||
}
|
||||
}
|
134
gumble/go-opus/stream_test.go
Normal file
134
gumble/go-opus/stream_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
// +build !nolibopusfile
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStreamIllegal(t *testing.T) {
|
||||
// Simple testing, no actual decoding
|
||||
reader := strings.NewReader("hello test test this is not a legal Opus stream")
|
||||
_, err := NewStream(reader)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error while initializing illegal opus stream")
|
||||
}
|
||||
}
|
||||
|
||||
func readStreamPcm(t *testing.T, stream *Stream, buffersize int) []int16 {
|
||||
var pcm []int16
|
||||
pcmbuf := make([]int16, buffersize)
|
||||
for {
|
||||
n, err := stream.Read(pcmbuf)
|
||||
switch err {
|
||||
case io.EOF:
|
||||
return pcm
|
||||
case nil:
|
||||
break
|
||||
default:
|
||||
t.Fatalf("Error while decoding opus file: %v", err)
|
||||
}
|
||||
if n == 0 {
|
||||
t.Fatal("Nil-error Read() must not return 0")
|
||||
}
|
||||
pcm = append(pcm, pcmbuf[:n]...)
|
||||
}
|
||||
}
|
||||
|
||||
func mustOpenFile(t *testing.T, fname string) *os.File {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while opening %s: %v", fname, err)
|
||||
return nil
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func mustOpenStream(t *testing.T, r io.Reader) *Stream {
|
||||
stream, err := NewStream(r)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while creating opus stream: %v", err)
|
||||
return nil
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
func opus2pcm(t *testing.T, fname string, buffersize int) []int16 {
|
||||
reader := mustOpenFile(t, fname)
|
||||
stream := mustOpenStream(t, reader)
|
||||
return readStreamPcm(t, stream, buffersize)
|
||||
}
|
||||
|
||||
// Extract raw pcm data from .wav file
|
||||
func extractWavPcm(t *testing.T, fname string) []int16 {
|
||||
bytes, err := ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading file data from %s: %v", fname, err)
|
||||
}
|
||||
const wavHeaderSize = 44
|
||||
if (len(bytes)-wavHeaderSize)%2 == 1 {
|
||||
t.Fatalf("Illegal wav data: payload must be encoded in byte pairs")
|
||||
}
|
||||
numSamples := (len(bytes) - wavHeaderSize) / 2
|
||||
samples := make([]int16, numSamples)
|
||||
for i := 0; i < numSamples; i++ {
|
||||
samples[i] += int16(bytes[wavHeaderSize+i*2])
|
||||
samples[i] += int16(bytes[wavHeaderSize+i*2+1]) << 8
|
||||
}
|
||||
return samples
|
||||
}
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
opuspcm := opus2pcm(t, "testdata/speech_8.opus", 10000)
|
||||
wavpcm := extractWavPcm(t, "testdata/speech_8.wav")
|
||||
if len(opuspcm) != len(wavpcm) {
|
||||
t.Fatalf("Unexpected length of decoded opus file: %d (.wav: %d)", len(opuspcm), len(wavpcm))
|
||||
}
|
||||
d := maxDiff(opuspcm, wavpcm)
|
||||
// No science behind this number
|
||||
const epsilon = 18
|
||||
if d > epsilon {
|
||||
t.Errorf("Maximum difference between decoded streams too high: %d", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamSmallBuffer(t *testing.T) {
|
||||
smallbuf := opus2pcm(t, "testdata/speech_8.opus", 1)
|
||||
bigbuf := opus2pcm(t, "testdata/speech_8.opus", 10000)
|
||||
if !reflect.DeepEqual(smallbuf, bigbuf) {
|
||||
t.Errorf("Reading with 1-sample buffer size yields different audio data")
|
||||
}
|
||||
}
|
||||
|
||||
type mockCloser struct {
|
||||
io.Reader
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (m *mockCloser) Close() error {
|
||||
if m.closed {
|
||||
return fmt.Errorf("Already closed")
|
||||
}
|
||||
m.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCloser(t *testing.T) {
|
||||
f := mustOpenFile(t, "testdata/speech_8.opus")
|
||||
mc := &mockCloser{Reader: f}
|
||||
stream := mustOpenStream(t, mc)
|
||||
stream.Close()
|
||||
if !mc.closed {
|
||||
t.Error("Expected opus stream to call .Close on the reader")
|
||||
}
|
||||
}
|
64
gumble/go-opus/streams_map.go
Normal file
64
gumble/go-opus/streams_map.go
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
// +build !nolibopusfile
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A map of simple integers to the actual pointers to stream structs. Avoids
|
||||
// passing pointers into the Go heap to C.
|
||||
//
|
||||
// As per the CGo pointers design doc for go 1.6:
|
||||
//
|
||||
// A particular unsafe area is C code that wants to hold on to Go func and
|
||||
// pointer values for future callbacks from C to Go. This works today but is not
|
||||
// permitted by the invariant. It is hard to detect. One safe approach is: Go
|
||||
// code that wants to preserve funcs/pointers stores them into a map indexed by
|
||||
// an int. Go code calls the C code, passing the int, which the C code may store
|
||||
// freely. When the C code wants to call into Go, it passes the int to a Go
|
||||
// function that looks in the map and makes the call. An explicit call is
|
||||
// required to release the value from the map if it is no longer needed, but
|
||||
// that was already true before.
|
||||
//
|
||||
// - https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md
|
||||
type streamsMap struct {
|
||||
sync.RWMutex
|
||||
m map[uintptr]*Stream
|
||||
counter uintptr
|
||||
}
|
||||
|
||||
func (sm *streamsMap) Get(id uintptr) *Stream {
|
||||
sm.RLock()
|
||||
defer sm.RUnlock()
|
||||
return sm.m[id]
|
||||
}
|
||||
|
||||
func (sm *streamsMap) Del(s *Stream) {
|
||||
sm.Lock()
|
||||
defer sm.Unlock()
|
||||
delete(sm.m, s.id)
|
||||
}
|
||||
|
||||
// NextId returns a unique ID for each call.
|
||||
func (sm *streamsMap) NextId() uintptr {
|
||||
return atomic.AddUintptr(&sm.counter, 1)
|
||||
}
|
||||
|
||||
func (sm *streamsMap) Save(s *Stream) {
|
||||
sm.Lock()
|
||||
defer sm.Unlock()
|
||||
sm.m[s.id] = s
|
||||
}
|
||||
|
||||
func newStreamsMap() *streamsMap {
|
||||
return &streamsMap{
|
||||
counter: 0,
|
||||
m: map[uintptr]*Stream{},
|
||||
}
|
||||
}
|
BIN
gumble/go-opus/testdata/speech_8.opus
vendored
Normal file
BIN
gumble/go-opus/testdata/speech_8.opus
vendored
Normal file
Binary file not shown.
BIN
gumble/go-opus/testdata/speech_8.wav
vendored
Normal file
BIN
gumble/go-opus/testdata/speech_8.wav
vendored
Normal file
Binary file not shown.
67
gumble/go-opus/utils_test.go
Normal file
67
gumble/go-opus/utils_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright © Go Opus Authors (see AUTHORS file)
|
||||
//
|
||||
// License for use of this code is detailed in the LICENSE file
|
||||
|
||||
package opus
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// utility functions for unit tests
|
||||
|
||||
func addSineFloat32(buf []float32, sampleRate int, freq float64) {
|
||||
factor := 2 * math.Pi * freq / float64(sampleRate)
|
||||
for i := range buf {
|
||||
buf[i] += float32(math.Sin(float64(i) * factor))
|
||||
}
|
||||
}
|
||||
|
||||
func addSine(buf []int16, sampleRate int, freq float64) {
|
||||
factor := 2 * math.Pi * freq / float64(sampleRate)
|
||||
for i := range buf {
|
||||
buf[i] += int16(math.Sin(float64(i)*factor) * (math.MaxInt16 - 1))
|
||||
}
|
||||
}
|
||||
|
||||
func maxDiff(a []int16, b []int16) int32 {
|
||||
if len(a) != len(b) {
|
||||
return math.MaxInt16
|
||||
}
|
||||
var max int32 = 0
|
||||
for i := range a {
|
||||
d := int32(a[i]) - int32(b[i])
|
||||
if d < 0 {
|
||||
d = -d
|
||||
}
|
||||
if d > max {
|
||||
max = d
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func interleave(a []int16, b []int16) []int16 {
|
||||
if len(a) != len(b) {
|
||||
panic("interleave: buffers must have equal length")
|
||||
}
|
||||
result := make([]int16, 2*len(a))
|
||||
for i := range a {
|
||||
result[2*i] = a[i]
|
||||
result[2*i+1] = b[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func split(interleaved []int16) ([]int16, []int16) {
|
||||
if len(interleaved)%2 != 0 {
|
||||
panic("split: interleaved buffer must have even number of samples")
|
||||
}
|
||||
left := make([]int16, len(interleaved)/2)
|
||||
right := make([]int16, len(interleaved)/2)
|
||||
for i := range left {
|
||||
left[i] = interleaved[2*i]
|
||||
right[i] = interleaved[2*i+1]
|
||||
}
|
||||
return left, right
|
||||
}
|
@ -36,7 +36,7 @@ It has these top-level messages:
|
||||
ServerConfig
|
||||
SuggestConfig
|
||||
*/
|
||||
package MumbleProto // import "git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
package MumbleProto // import "git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// AccessTokens are additional passwords that can be provided to the server to
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/varint"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/varint"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// ContextActionType is a bitmask of contexts where a ContextAction can be
|
||||
|
@ -28,7 +28,7 @@
|
||||
// for transmitting and receiving audio. It can be enabled by importing the
|
||||
// following package for its side effect:
|
||||
// import (
|
||||
// _ "git.2mb.codes/~cmb/barnard/gumble/opus"
|
||||
// _ "git.stormux.org/storm/barnard/gumble/opus"
|
||||
// )
|
||||
//
|
||||
// To ensure that gumble clients can always transmit and receive audio to and
|
||||
@ -42,4 +42,4 @@
|
||||
// (Users, Channels, Config, etc.), is thread-unsafe. Accessing or modifying
|
||||
// those structures should only be done from inside of an event listener or via
|
||||
// Client.Do.
|
||||
package gumble // import "git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
package gumble // import "git.stormux.org/storm/barnard/gumble/gumble"
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// EventListener is the interface that must be implemented by a type if it
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/varint"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/varint"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ package gumble
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// RejectType describes why a client connection was rejected by the server.
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// TextMessage is a chat message that can be received from and sent to the
|
||||
|
@ -1,8 +1,8 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.2mb.codes/~cmb/go-openal/openal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ package gumble
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// RegisteredUser represents a registered user on the server.
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
// "git.2mb.codes/~cmb/go-openal/openal"
|
||||
// "git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
)
|
||||
|
||||
// Users is a map of server users.
|
||||
|
@ -1 +1 @@
|
||||
package varint // import "git.2mb.codes/~cmb/barnard/gumble/gumble/varint"
|
||||
package varint // import "git.stormux.org/storm/barnard/gumble/gumble/varint"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package varint // import "git.2mb.codes/~cmb/barnard/gumble/gumble/varint"
|
||||
package varint // import "git.stormux.org/storm/barnard/gumble/gumble/varint"
|
||||
|
||||
import "testing"
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble/MumbleProto"
|
||||
)
|
||||
|
||||
// VoiceTargetLoopback is a special voice target which causes any audio sent to
|
||||
|
@ -1,4 +1,4 @@
|
||||
package gumbleffmpeg // import "git.2mb.codes/~cmb/barnard/gumble/gumbleffmpeg"
|
||||
package gumbleffmpeg // import "git.stormux.org/storm/barnard/gumble/gumbleffmpeg"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package gumbleffmpeg // import "git.2mb.codes/~cmb/barnard/gumble/gumbleffmpeg"
|
||||
package gumbleffmpeg // import "git.stormux.org/storm/barnard/gumble/gumbleffmpeg"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// State represents the state of a Stream.
|
||||
|
@ -6,8 +6,8 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/go-openal/openal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// UserGroups fetches the group names the given user belongs to in the given
|
||||
|
@ -1,9 +1,9 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
var autoBitrate = &Listener{
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// ChannelPath returns a slice of channel names, starting from the root channel
|
||||
|
@ -1,2 +1,2 @@
|
||||
// Package gumbleutil provides extras that can make working with gumble easier.
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// Listener is a struct that implements the gumble.EventListener interface. The
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// ListenerFunc is a single listener function that implements the
|
||||
|
@ -1,4 +1,4 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// Main aids in the creation of a basic command line gumble bot. It accepts the
|
||||
|
@ -1,11 +1,11 @@
|
||||
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
|
||||
package gumbleutil // import "git.stormux.org/storm/barnard/gumble/gumbleutil"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
)
|
||||
|
||||
// PlainText returns the Message string without HTML tags or entities.
|
||||
|
@ -1,10 +1,11 @@
|
||||
package opus
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"github.com/hraban/opus"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/gumble/go-opus"
|
||||
)
|
||||
|
||||
|
||||
var Codec gumble.AudioCodec
|
||||
|
||||
const (
|
||||
|
10
main.go
10
main.go
@ -16,12 +16,12 @@ import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"github.com/alessio/shellescape"
|
||||
"git.2mb.codes/~cmb/barnard/config"
|
||||
"git.stormux.org/storm/barnard/config"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
_ "git.2mb.codes/~cmb/barnard/gumble/opus"
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.2mb.codes/~cmb/go-openal/openal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
_ "git.stormux.org/storm/barnard/gumble/opus"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
)
|
||||
|
||||
func show_devs(name string, args []string) {
|
||||
|
4
ui.go
4
ui.go
@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
"github.com/kennygrant/sanitize"
|
||||
)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.2mb.codes/~cmb/barnard/gumble/gumble"
|
||||
"git.2mb.codes/~cmb/barnard/uiterm"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/uiterm"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
@ -1 +1 @@
|
||||
package uiterm // import "git.2mb.codes/~cmb/barnard/uiterm"
|
||||
package uiterm // import "git.stormux.org/storm/barnard/uiterm"
|
||||
|
Loading…
Reference in New Issue
Block a user