82 lines
2.1 KiB
Go
82 lines
2.1 KiB
Go
package callflow
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"git.ipao.vip/rogee/go-sip/internal/media"
|
|
)
|
|
|
|
// RecordingSession records only PCM16 frames the approved callflow actually
|
|
// reads or successfully sends. It never writes a business file or encodes RTP.
|
|
// Capture failure is retained and returned by WAV; the call can still finish.
|
|
type RecordingSession struct {
|
|
MediaSession
|
|
mu sync.Mutex
|
|
pcm []byte
|
|
maxBytes int64
|
|
failure error
|
|
}
|
|
|
|
func NewRecordingSession(session MediaSession, maxWAVBytes int64) (*RecordingSession, error) {
|
|
if session == nil || maxWAVBytes <= 44 {
|
|
return nil, errors.New("recording requires a media session and a positive bounded WAV allowance")
|
|
}
|
|
return &RecordingSession{MediaSession: session, maxBytes: maxWAVBytes}, nil
|
|
}
|
|
|
|
func (r *RecordingSession) ReadPayload(ctx context.Context) ([]byte, error) {
|
|
payload, err := r.MediaSession.ReadPayload(ctx)
|
|
if err == nil {
|
|
r.capture(payload)
|
|
}
|
|
return payload, err
|
|
}
|
|
|
|
func (r *RecordingSession) SendPCM16(ctx context.Context, pcm []byte, sampleRateHz int) error {
|
|
if sampleRateHz != 16000 {
|
|
return fmt.Errorf("recording requires 16-kHz PCM16, received %d Hz", sampleRateHz)
|
|
}
|
|
if err := r.MediaSession.SendPCM16(ctx, pcm, sampleRateHz); err != nil {
|
|
return err
|
|
}
|
|
r.capture(pcm)
|
|
return nil
|
|
}
|
|
|
|
func (r *RecordingSession) capture(pcm []byte) {
|
|
if len(pcm) == 0 {
|
|
return
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.failure != nil {
|
|
return
|
|
}
|
|
if len(pcm)%2 != 0 {
|
|
r.failure = media.ErrWAVInvalidPCM
|
|
r.pcm = nil
|
|
return
|
|
}
|
|
if int64(len(pcm)) > r.maxBytes-44-int64(len(r.pcm)) {
|
|
r.failure = media.ErrWAVTooLarge
|
|
r.pcm = nil
|
|
return
|
|
}
|
|
r.pcm = append(r.pcm, pcm...)
|
|
}
|
|
|
|
// WAV returns an in-memory 16-kHz mono recording and its duration. The caller
|
|
// must inspect its error after the call; a failed capture must not be reported
|
|
// as a successful upload or a healthy no-recording outcome.
|
|
func (r *RecordingSession) WAV() ([]byte, int64, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.failure != nil {
|
|
return nil, 0, r.failure
|
|
}
|
|
return media.EncodeMonoWAV(r.pcm, r.maxBytes)
|
|
}
|