mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-08-24 21:14:55 -04:00
151 lines
3.1 KiB
Go
151 lines
3.1 KiB
Go
|
|
package voip
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"math"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
lkmedia "github.com/livekit/media-sdk"
|
||
|
|
"github.com/purpshell/meowcaller"
|
||
|
|
)
|
||
|
|
|
||
|
|
var ErrAudioSourceClosed = errors.New("voip: audio source closed")
|
||
|
|
|
||
|
|
func Float32FrameToPCM16(frame []float32) lkmedia.PCM16Sample {
|
||
|
|
sample := make(lkmedia.PCM16Sample, len(frame))
|
||
|
|
for i, value := range frame {
|
||
|
|
switch {
|
||
|
|
case value > 1:
|
||
|
|
value = 1
|
||
|
|
case value < -1:
|
||
|
|
value = -1
|
||
|
|
}
|
||
|
|
if value == 1 {
|
||
|
|
sample[i] = math.MaxInt16
|
||
|
|
} else {
|
||
|
|
sample[i] = int16(value * 32768)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return sample
|
||
|
|
}
|
||
|
|
|
||
|
|
func PCM16ToFloat32Frame(sample lkmedia.PCM16Sample) []float32 {
|
||
|
|
frame := make([]float32, len(sample))
|
||
|
|
for i, value := range sample {
|
||
|
|
frame[i] = float32(value) / 32768
|
||
|
|
}
|
||
|
|
return frame
|
||
|
|
}
|
||
|
|
|
||
|
|
type LiveKitPCMWriter struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
track interface {
|
||
|
|
WriteSample(lkmedia.PCM16Sample) error
|
||
|
|
}
|
||
|
|
closed bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewLiveKitPCMWriter(track interface {
|
||
|
|
WriteSample(lkmedia.PCM16Sample) error
|
||
|
|
}) *LiveKitPCMWriter {
|
||
|
|
return &LiveKitPCMWriter{track: track}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *LiveKitPCMWriter) WriteFrame(frame []float32) error {
|
||
|
|
w.mu.RLock()
|
||
|
|
defer w.mu.RUnlock()
|
||
|
|
if w.closed {
|
||
|
|
return ErrAudioSourceClosed
|
||
|
|
}
|
||
|
|
if w.track == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return w.track.WriteSample(Float32FrameToPCM16(frame))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (w *LiveKitPCMWriter) Close() error {
|
||
|
|
w.mu.Lock()
|
||
|
|
w.closed = true
|
||
|
|
w.track = nil
|
||
|
|
w.mu.Unlock()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
type MeowcallerAudioSource struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
cond *sync.Cond
|
||
|
|
queue []float32
|
||
|
|
closed bool
|
||
|
|
maxSize int
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewMeowcallerAudioSource(maxFrames int) *MeowcallerAudioSource {
|
||
|
|
if maxFrames <= 0 {
|
||
|
|
maxFrames = 8
|
||
|
|
}
|
||
|
|
src := &MeowcallerAudioSource{
|
||
|
|
maxSize: maxFrames * meowcaller.FrameSamples,
|
||
|
|
}
|
||
|
|
src.cond = sync.NewCond(&src.mu)
|
||
|
|
return src
|
||
|
|
}
|
||
|
|
|
||
|
|
func (src *MeowcallerAudioSource) WriteSample(sample lkmedia.PCM16Sample) error {
|
||
|
|
src.mu.Lock()
|
||
|
|
defer src.mu.Unlock()
|
||
|
|
if src.closed {
|
||
|
|
return ErrAudioSourceClosed
|
||
|
|
}
|
||
|
|
frame := PCM16ToFloat32Frame(sample)
|
||
|
|
src.queue = append(src.queue, frame...)
|
||
|
|
if len(src.queue) > src.maxSize {
|
||
|
|
copy(src.queue, src.queue[len(src.queue)-src.maxSize:])
|
||
|
|
src.queue = src.queue[:src.maxSize]
|
||
|
|
}
|
||
|
|
src.cond.Signal()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (src *MeowcallerAudioSource) SampleRate() int {
|
||
|
|
return meowcaller.SampleRate
|
||
|
|
}
|
||
|
|
|
||
|
|
func (src *MeowcallerAudioSource) String() string {
|
||
|
|
return fmt.Sprintf("MeowcallerAudioSource(%d)", meowcaller.SampleRate)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (src *MeowcallerAudioSource) ReadFrame() ([]float32, error) {
|
||
|
|
src.mu.Lock()
|
||
|
|
defer src.mu.Unlock()
|
||
|
|
for len(src.queue) < meowcaller.FrameSamples && !src.closed {
|
||
|
|
src.cond.Wait()
|
||
|
|
}
|
||
|
|
if len(src.queue) == 0 && src.closed {
|
||
|
|
return nil, io.EOF
|
||
|
|
}
|
||
|
|
frame := make([]float32, meowcaller.FrameSamples)
|
||
|
|
n := copy(frame, src.queue)
|
||
|
|
if n == len(src.queue) {
|
||
|
|
src.queue = src.queue[:0]
|
||
|
|
} else {
|
||
|
|
copy(src.queue, src.queue[n:])
|
||
|
|
src.queue = src.queue[:len(src.queue)-n]
|
||
|
|
}
|
||
|
|
return frame, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (src *MeowcallerAudioSource) Close() error {
|
||
|
|
src.mu.Lock()
|
||
|
|
src.closed = true
|
||
|
|
src.queue = nil
|
||
|
|
src.cond.Broadcast()
|
||
|
|
src.mu.Unlock()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var (
|
||
|
|
_ meowcaller.AudioSink = (*LiveKitPCMWriter)(nil)
|
||
|
|
_ meowcaller.AudioSource = (*MeowcallerAudioSource)(nil)
|
||
|
|
)
|