Add standards-aware recording
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
Package MumbleProto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
|
||||
Mumble.proto
|
||||
|
||||
It has these top-level messages:
|
||||
|
||||
Version
|
||||
UDPTunnel
|
||||
Authenticate
|
||||
@@ -2070,8 +2072,10 @@ type ServerConfig struct {
|
||||
// Maximum image message length.
|
||||
ImageMessageLength *uint32 `protobuf:"varint,5,opt,name=image_message_length,json=imageMessageLength" json:"image_message_length,omitempty"`
|
||||
// The maximum number of users allowed on the server.
|
||||
MaxUsers *uint32 `protobuf:"varint,6,opt,name=max_users,json=maxUsers" json:"max_users,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
MaxUsers *uint32 `protobuf:"varint,6,opt,name=max_users,json=maxUsers" json:"max_users,omitempty"`
|
||||
// Whether using Mumble's recording feature is allowed on the server.
|
||||
RecordingAllowed *bool `protobuf:"varint,7,opt,name=recording_allowed,json=recordingAllowed" json:"recording_allowed,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ServerConfig) Reset() { *m = ServerConfig{} }
|
||||
@@ -2121,6 +2125,13 @@ func (m *ServerConfig) GetMaxUsers() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ServerConfig) GetRecordingAllowed() bool {
|
||||
if m != nil && m.RecordingAllowed != nil {
|
||||
return *m.RecordingAllowed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sent by the server to inform the clients of suggested client configuration
|
||||
// specified by the server administrator.
|
||||
type SuggestConfig struct {
|
||||
|
||||
@@ -210,6 +210,7 @@ type ServerConfigEvent struct {
|
||||
MaximumMessageLength *int
|
||||
MaximumImageMessageLength *int
|
||||
MaximumUsers *int
|
||||
RecordingAllowed *bool
|
||||
|
||||
CodecAlpha *int32
|
||||
CodecBeta *int32
|
||||
|
||||
@@ -467,6 +467,7 @@ func (c *Client) handleUserRemove(buffer []byte) error {
|
||||
event.Type |= UserChangeBanned
|
||||
}
|
||||
if event.User == c.Self {
|
||||
c.disconnectEvent.String = event.String
|
||||
if packet.Ban != nil && *packet.Ban {
|
||||
c.disconnectEvent.Type = DisconnectBanned
|
||||
} else {
|
||||
@@ -1236,10 +1237,57 @@ func (c *Client) handleServerConfig(buffer []byte) error {
|
||||
val := int(*packet.MaxUsers)
|
||||
event.MaximumUsers = &val
|
||||
}
|
||||
if packet.RecordingAllowed != nil {
|
||||
event.RecordingAllowed = packet.RecordingAllowed
|
||||
} else if val := parseServerConfigRecordingAllowed(buffer); val != nil {
|
||||
event.RecordingAllowed = val
|
||||
}
|
||||
c.Config.Listeners.onServerConfig(&event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseServerConfigRecordingAllowed(buffer []byte) *bool {
|
||||
for len(buffer) > 0 {
|
||||
key, n := varint.Decode(buffer)
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
buffer = buffer[n:]
|
||||
field := key >> 3
|
||||
wireType := key & 0x7
|
||||
if field == 7 && wireType == 0 {
|
||||
val, n := varint.Decode(buffer)
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
allowed := val != 0
|
||||
return &allowed
|
||||
}
|
||||
skip := 0
|
||||
switch wireType {
|
||||
case 0:
|
||||
_, skip = varint.Decode(buffer)
|
||||
case 1:
|
||||
skip = 8
|
||||
case 2:
|
||||
length, n := varint.Decode(buffer)
|
||||
if n <= 0 || length < 0 {
|
||||
return nil
|
||||
}
|
||||
skip = n + int(length)
|
||||
case 5:
|
||||
skip = 4
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if skip <= 0 || skip > len(buffer) {
|
||||
return nil
|
||||
}
|
||||
buffer = buffer[skip:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleSuggestConfig(buffer []byte) error {
|
||||
var packet MumbleProto.SuggestConfig
|
||||
if err := proto.Unmarshal(buffer, &packet); err != nil {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package gumble
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type serverConfigListener struct {
|
||||
EventListener
|
||||
event *ServerConfigEvent
|
||||
}
|
||||
|
||||
func (l *serverConfigListener) OnServerConfig(e *ServerConfigEvent) {
|
||||
l.event = e
|
||||
}
|
||||
|
||||
func TestHandleServerConfigRecordingAllowed(t *testing.T) {
|
||||
data := []byte{56, 0} // field 7, varint false
|
||||
|
||||
client := &Client{Config: NewConfig()}
|
||||
listener := &serverConfigListener{}
|
||||
client.Config.Attach(listener)
|
||||
|
||||
if err := client.handleServerConfig(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if listener.event == nil {
|
||||
t.Fatal("expected server config event")
|
||||
}
|
||||
if listener.event.RecordingAllowed == nil {
|
||||
t.Fatal("expected recording allowed value")
|
||||
}
|
||||
if *listener.event.RecordingAllowed {
|
||||
t.Fatal("expected recording to be disallowed")
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/barnard/audio"
|
||||
@@ -30,6 +31,12 @@ type FilePlayer interface {
|
||||
IsPlaying() bool
|
||||
}
|
||||
|
||||
type Recorder interface {
|
||||
RecordAudioFrame(source uint32, samples []int16)
|
||||
}
|
||||
|
||||
const recorderOutgoingSource uint32 = ^uint32(0)
|
||||
|
||||
const (
|
||||
maxBufferSize = 11520 // Max frame size (2880) * bytes per stereo sample (4)
|
||||
)
|
||||
@@ -72,6 +79,8 @@ type Stream struct {
|
||||
effectsProcessor EffectsProcessor
|
||||
effectsProcessorRight EffectsProcessor
|
||||
filePlayer FilePlayer
|
||||
recorderMu sync.RWMutex
|
||||
recorder Recorder
|
||||
}
|
||||
|
||||
func New(client *gumble.Client, inputDevice *string, outputDevice *string, test bool) (*Stream, error) {
|
||||
@@ -161,6 +170,18 @@ func (s *Stream) GetFilePlayer() FilePlayer {
|
||||
return s.filePlayer
|
||||
}
|
||||
|
||||
func (s *Stream) SetRecorder(recorder Recorder) {
|
||||
s.recorderMu.Lock()
|
||||
defer s.recorderMu.Unlock()
|
||||
s.recorder = recorder
|
||||
}
|
||||
|
||||
func (s *Stream) getRecorder() Recorder {
|
||||
s.recorderMu.RLock()
|
||||
defer s.recorderMu.RUnlock()
|
||||
return s.recorder
|
||||
}
|
||||
|
||||
func (s *Stream) Destroy() {
|
||||
if s.link != nil {
|
||||
s.link.Detach()
|
||||
@@ -265,6 +286,12 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
}
|
||||
|
||||
boost = e.User.Boost
|
||||
recorder := s.getRecorder()
|
||||
var recordBuffer []int16
|
||||
recordPtr := 0
|
||||
if recorder != nil {
|
||||
recordBuffer = make([]int16, len(packet.AudioBuffer)*gumble.AudioChannels)
|
||||
}
|
||||
|
||||
// Check if sample count suggests stereo data
|
||||
isStereo := samples > gumble.AudioDefaultFrameSize && samples%2 == 0
|
||||
@@ -290,6 +317,10 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, e.User.Volume)
|
||||
recordPtr++
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
|
||||
@@ -305,6 +336,10 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordBuffer[recordPtr] = scaleForRecording(sample, e.User.Volume)
|
||||
recordPtr++
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
}
|
||||
@@ -322,10 +357,19 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
if recorder != nil {
|
||||
recordSample := scaleForRecording(sample, e.User.Volume)
|
||||
recordBuffer[recordPtr] = recordSample
|
||||
recordBuffer[recordPtr+1] = recordSample
|
||||
recordPtr += 2
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
}
|
||||
}
|
||||
if recorder != nil && recordPtr > 0 {
|
||||
recorder.RecordAudioFrame(e.User.Session, recordBuffer[:recordPtr])
|
||||
}
|
||||
|
||||
reclaim()
|
||||
if len(emptyBufs) == 0 {
|
||||
@@ -472,14 +516,31 @@ func (s *Stream) sourceRoutine(inputDevice *string) {
|
||||
if hasFileAudio {
|
||||
// Send stereo buffer when file is playing
|
||||
outgoing <- gumble.AudioBuffer(outputBuffer)
|
||||
if recorder := s.getRecorder(); recorder != nil {
|
||||
recorder.RecordAudioFrame(recorderOutgoingSource, outputBuffer)
|
||||
}
|
||||
} else if hasMicInput {
|
||||
// Send mic when no file is playing
|
||||
outgoing <- gumble.AudioBuffer(int16Buffer)
|
||||
if recorder := s.getRecorder(); recorder != nil {
|
||||
recorder.RecordAudioFrame(recorderOutgoingSource, int16Buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scaleForRecording(sample int16, volume float32) int16 {
|
||||
scaled := int32(float32(sample) * volume)
|
||||
if scaled > 32767 {
|
||||
return 32767
|
||||
}
|
||||
if scaled < -32768 {
|
||||
return -32768
|
||||
}
|
||||
return int16(scaled)
|
||||
}
|
||||
|
||||
func (s *Stream) processMonoSamples(samples []int16) {
|
||||
s.processChannel(samples, s.noiseProcessor, s.micAGC, s.effectsProcessor)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user