|
- package output
-
- import (
- "context"
- "encoding/binary"
- "fmt"
- "math"
- "os"
- "sync"
- )
-
- // FileBackend streams composite samples to disk so that playback or offline tooling can consume them.
- type FileBackend struct {
- mu sync.Mutex
- file *os.File
- order binary.ByteOrder
- info BackendInfo
- cfg BackendConfig
- closed bool
- }
-
- // NewFileBackend creates a writer that appends float32 interleaved I/Q pairs to the named file.
- func NewFileBackend(path string, order binary.ByteOrder, info BackendInfo) (*FileBackend, error) {
- f, err := os.Create(path)
- if err != nil {
- return nil, fmt.Errorf("open output file: %w", err)
- }
- if info.Name == "" {
- info.Name = path
- }
- if info.Capabilities.MaxSamplesPerWrite == 0 {
- info.Capabilities.MaxSamplesPerWrite = 4096
- }
- info.Capabilities.SupportsComposite = true
- info.Capabilities.FixedRate = true
-
- return &FileBackend{
- file: f,
- order: order,
- info: info,
- }, nil
- }
-
- // Configure stores the requested configuration, but the file backend simply preserves the values.
- func (fb *FileBackend) Configure(_ context.Context, cfg BackendConfig) error {
- fb.mu.Lock()
- defer fb.mu.Unlock()
- if fb.closed {
- return ErrBackendClosed
- }
- fb.cfg = cfg
- return nil
- }
-
- // Write emits the provided frame as binary interleaved float32 I/Q samples.
- func (fb *FileBackend) Write(ctx context.Context, frame *CompositeFrame) (int, error) {
- if err := ctx.Err(); err != nil {
- return 0, err
- }
- fb.mu.Lock()
- defer fb.mu.Unlock()
- if fb.closed {
- return 0, ErrBackendClosed
- }
- if frame == nil || len(frame.Samples) == 0 {
- return 0, nil
- }
- buf := make([]byte, 8)
- written := 0
- for _, sample := range frame.Samples {
- if err := ctx.Err(); err != nil {
- return written, err
- }
- fb.order.PutUint32(buf[0:], math.Float32bits(sample.I))
- fb.order.PutUint32(buf[4:], math.Float32bits(sample.Q))
- if _, err := fb.file.Write(buf); err != nil {
- return written, fmt.Errorf("write sample data: %w", err)
- }
- written++
- }
- return written, nil
- }
-
- // Flush commits the current file buffer to disk.
- func (fb *FileBackend) Flush(_ context.Context) error {
- fb.mu.Lock()
- defer fb.mu.Unlock()
- if fb.closed {
- return ErrBackendClosed
- }
- return fb.file.Sync()
- }
-
- // Close finalizes the file handle.
- func (fb *FileBackend) Close(_ context.Context) error {
- fb.mu.Lock()
- defer fb.mu.Unlock()
- if fb.closed {
- return ErrBackendClosed
- }
- fb.closed = true
- return fb.file.Close()
- }
-
- // Info returns the backend metadata.
- func (fb *FileBackend) Info() BackendInfo {
- fb.mu.Lock()
- defer fb.mu.Unlock()
- return fb.info
- }
|