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) }