stream.go (4064B)
1 package nativeaudio 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "time" 9 ) 10 11 // Stream decodes audio incrementally, yielding signed 16-bit 12 // little-endian PCM through [io.Reader]. 13 // 14 // It exists because the buffered API holds the whole decode in memory, 15 // and PCM is roughly an order of magnitude larger than the compressed 16 // input it came from. A few minutes of stereo audio is tens of 17 // megabytes, which is fine for a sound effect and wasteful for a track. 18 // 19 // A Stream must be closed. Closing the Decoder that produced it waits 20 // for every open Stream to be closed first. 21 type Stream struct { 22 r io.ReadCloser 23 format Format 24 done func() 25 } 26 27 // Format describes the PCM this Stream produces. It is known as soon as 28 // the Stream is opened, before any audio is read. 29 func (s *Stream) Format() Format { 30 return s.format 31 } 32 33 // Read fills p with decoded PCM, returning [io.EOF] when the audio is 34 // exhausted. 35 func (s *Stream) Read(p []byte) (int, error) { 36 return s.r.Read(p) 37 } 38 39 // Close releases the decoder state behind the Stream. It is safe to 40 // call more than once, and abandoning a Stream before [io.EOF] is fine 41 // as long as it is closed. 42 func (s *Stream) Close() error { 43 err := s.r.Close() 44 if s.done != nil { 45 s.done() 46 s.done = nil 47 } 48 return err 49 } 50 51 // newBufferedStream serves PCM that has already been fully decoded. 52 // 53 // Backends that cannot yet decode incrementally use this so that the 54 // streaming API behaves correctly everywhere, at the cost of the memory 55 // saving on those platforms. 56 func newBufferedStream(pcm []byte, format Format) *Stream { 57 return &Stream{ 58 r: io.NopCloser(bytes.NewReader(pcm)), 59 format: format, 60 } 61 } 62 63 // Limits bound a single decode. A zero field means no limit. 64 // 65 // Malformed audio can make a decoder grind: fuzzing found a half-megabyte 66 // file that Media Foundation decodes indefinitely, a few hundred 67 // kilobytes of PCM at a time, never finishing and never failing. No 68 // per-read timeout catches that, because every individual read completes. 69 // Limits are the backstop, and matter whenever the input is untrusted. 70 type Limits struct { 71 // MaxBytes caps the PCM a decode may produce. 72 MaxBytes int64 73 74 // MaxDuration caps how long a decode may run. 75 MaxDuration time.Duration 76 } 77 78 // DefaultLimits is applied to a Decoder created without WithLimits. 79 // 80 // The duration is generous by orders of magnitude: a healthy backend 81 // decodes half an hour of audio in well under a second, so anything 82 // still running after this is not making meaningful progress. No byte 83 // limit is imposed by default, since a legitimately long recording is 84 // legitimately large. 85 func DefaultLimits() Limits { 86 return Limits{MaxDuration: 2 * time.Minute} 87 } 88 89 // ErrLimitExceeded reports that a decode hit its [Limits]. 90 var ErrLimitExceeded = errors.New("nativeaudio: decode exceeded its limits") 91 92 // limited enforces Limits over a stream. 93 type limited struct { 94 r io.ReadCloser 95 limits Limits 96 read int64 97 deadline time.Time 98 } 99 100 func newLimited(r io.ReadCloser, l Limits) io.ReadCloser { 101 if l.MaxBytes <= 0 && l.MaxDuration <= 0 { 102 return r 103 } 104 lr := &limited{r: r, limits: l} 105 if l.MaxDuration > 0 { 106 lr.deadline = time.Now().Add(l.MaxDuration) 107 // Hand the deadline to the backend too, so a read already 108 // waiting on the decoder gives up with everything else rather 109 // than running on to its own backstop. 110 if d, ok := r.(interface{ SetDeadline(time.Time) }); ok { 111 d.SetDeadline(lr.deadline) 112 } 113 } 114 return lr 115 } 116 117 func (l *limited) Read(p []byte) (int, error) { 118 if !l.deadline.IsZero() && time.Now().After(l.deadline) { 119 return 0, fmt.Errorf("%w: still decoding after %s", ErrLimitExceeded, l.limits.MaxDuration) 120 } 121 if l.limits.MaxBytes > 0 { 122 if remaining := l.limits.MaxBytes - l.read; remaining <= 0 { 123 return 0, fmt.Errorf("%w: produced more than %d bytes", ErrLimitExceeded, l.limits.MaxBytes) 124 } else if int64(len(p)) > remaining { 125 p = p[:remaining] 126 } 127 } 128 n, err := l.r.Read(p) 129 l.read += int64(n) 130 return n, err 131 } 132 133 func (l *limited) Close() error { 134 return l.r.Close() 135 }