61 lines
1.6 KiB
Go
61 lines
1.6 KiB
Go
// ABOUTME: Opus audio encoder for bandwidth-efficient streaming
|
|
// ABOUTME: Wraps libopus to encode PCM audio to Opus format
|
|
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"gopkg.in/hraban/opus.v2"
|
|
)
|
|
|
|
// OpusEncoder wraps the Opus encoder
|
|
type OpusEncoder struct {
|
|
encoder *opus.Encoder
|
|
sampleRate int
|
|
channels int
|
|
frameSize int // samples per channel per frame
|
|
}
|
|
|
|
// NewOpusEncoder creates a new Opus encoder.
|
|
// frameSize is in samples per channel (e.g., 960 for 20ms at 48kHz).
|
|
func NewOpusEncoder(sampleRate, channels, frameSize int) (*OpusEncoder, error) {
|
|
// AppAudio mode tunes the encoder for full-bandwidth music rather than speech
|
|
encoder, err := opus.NewEncoder(sampleRate, channels, opus.AppAudio)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create opus encoder: %w", err)
|
|
}
|
|
|
|
// 128 kbps per channel — aggressive enough for transparent music quality
|
|
bitrate := 128000 * channels
|
|
if err := encoder.SetBitrate(bitrate); err != nil {
|
|
log.Printf("Warning: Failed to set Opus bitrate: %v", err)
|
|
}
|
|
|
|
return &OpusEncoder{
|
|
encoder: encoder,
|
|
sampleRate: sampleRate,
|
|
channels: channels,
|
|
frameSize: frameSize,
|
|
}, nil
|
|
}
|
|
|
|
// Encode encodes PCM samples to Opus.
|
|
// Input: []int16 interleaved samples; output: single Opus packet.
|
|
func (e *OpusEncoder) Encode(pcm []int16) ([]byte, error) {
|
|
// Opus spec maximum packet size is 4000 bytes
|
|
output := make([]byte, 4000)
|
|
|
|
n, err := e.encoder.Encode(pcm, output)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opus encode failed: %w", err)
|
|
}
|
|
|
|
return output[:n], nil
|
|
}
|
|
|
|
// Close is a no-op; opus.Encoder has no Close method.
|
|
func (e *OpusEncoder) Close() error {
|
|
return nil
|
|
}
|