44#
55# pyFLAC decoder
66#
7- # Copyright (c) 2020-2021 , Sonos, Inc.
7+ # Copyright (c) 2020-2024 , Sonos, Inc.
88# All rights reserved.
99#
1010# ------------------------------------------------------------------------------
@@ -93,7 +93,8 @@ def finish(self):
9393 Flushes the decoding buffer, releases resources, resets the decoder
9494 settings to their defaults, and returns the decoder state to `DecoderState.UNINITIALIZED`.
9595
96- A well behaved program should always call this at the end.
96+ A well behaved program should always call this at the end, otherwise the processing
97+ thread will be left running, awaiting more data.
9798 """
9899 _lib .FLAC__stream_decoder_finish (self ._decoder )
99100
@@ -121,6 +122,9 @@ class StreamDecoder(_Decoder):
121122 blocks of raw uncompressed audio is passed back to the user via
122123 the `callback`.
123124
125+ The `finish` method must be called at the end of the decoding process,
126+ otherwise the processing thread will be left running.
127+
124128 Args:
125129 write_callback (fn): Function to call when there is uncompressed
126130 audio data ready, see the example below for more information.
@@ -159,6 +163,8 @@ def __init__(self,
159163
160164 self ._done = False
161165 self ._buffer = deque ()
166+ self ._event = threading .Event ()
167+ self ._lock = threading .Lock ()
162168 self .write_callback = write_callback
163169
164170 rc = _lib .FLAC__stream_decoder_init_stream (
@@ -200,7 +206,10 @@ def process(self, data: bytes):
200206 Args:
201207 data (bytes): Bytes of FLAC data
202208 """
209+ self ._lock .acquire ()
203210 self ._buffer .append (data )
211+ self ._lock .release ()
212+ self ._event .set ()
204213
205214 def finish (self ):
206215 """
@@ -225,8 +234,9 @@ def finish(self):
225234 # Instruct the decoder to finish up and wait until it is done
226235 # --------------------------------------------------------------
227236 self ._done = True
237+ self ._event .set ()
238+ self ._thread .join ()
228239 super ().finish ()
229- self ._thread .join (timeout = 3 )
230240 if self ._error :
231241 raise DecoderProcessException (self ._error )
232242
@@ -303,6 +313,84 @@ def _write_callback(self, data: np.ndarray, sample_rate: int, num_channels: int,
303313 self .__output .write (data )
304314
305315
316+ class OneShotDecoder (_Decoder ):
317+ """
318+ A pyFLAC one-shot decoder converts a buffer of FLAC encoded
319+ bytes back to raw audio data. Unlike the `StreamDecoder` class,
320+ the one-shot decoder operates on a single block of data, and
321+ runs in a blocking manner, as opposed to in a background thread.
322+
323+ The compressed data is passed in via the constructor, and
324+ blocks of raw uncompressed audio is passed back to the user via
325+ the `callback`.
326+
327+ Args:
328+ write_callback (fn): Function to call when there is uncompressed
329+ audio data ready, see the example below for more information.
330+ buffer (bytes): The FLAC encoded audio data
331+
332+ Examples:
333+ An example callback which writes the audio data to file
334+ using SoundFile.
335+
336+ .. code-block:: python
337+ :linenos:
338+
339+ import soundfile as sf
340+
341+ def callback(self,
342+ audio: np.ndarray,
343+ sample_rate: int,
344+ num_channels: int,
345+ num_samples: int):
346+
347+ # ------------------------------------------------------
348+ # Note: num_samples is the number of samples per channel
349+ # ------------------------------------------------------
350+ if self.output is None:
351+ self.output = sf.SoundFile(
352+ 'output.wav', mode='w', channels=num_channels,
353+ samplerate=sample_rate
354+ )
355+ self.output.write(audio)
356+
357+ Raises:
358+ DecoderInitException: If initialisation of the decoder fails
359+ """
360+ def __init__ (self ,
361+ write_callback : Callable [[np .ndarray , int , int , int ], None ],
362+ buffer : bytes ):
363+ super ().__init__ ()
364+ self ._done = False
365+ self ._buffer = deque ()
366+ self ._buffer .append (buffer )
367+ self ._event = threading .Event ()
368+ self ._event .set ()
369+ self ._lock = threading .Lock ()
370+ self .write_callback = write_callback
371+
372+ rc = _lib .FLAC__stream_decoder_init_stream (
373+ self ._decoder ,
374+ _lib ._read_callback ,
375+ _ffi .NULL ,
376+ _ffi .NULL ,
377+ _ffi .NULL ,
378+ _ffi .NULL ,
379+ _lib ._write_callback ,
380+ _ffi .NULL ,
381+ _lib ._error_callback ,
382+ self ._decoder_handle
383+ )
384+ if rc != _lib .FLAC__STREAM_DECODER_INIT_STATUS_OK :
385+ raise DecoderInitException (rc )
386+
387+ while len (self ._buffer ) > 0 :
388+ _lib .FLAC__stream_decoder_process_single (self ._decoder )
389+
390+ self ._done = True
391+ super ().finish ()
392+
393+
306394@_ffi .def_extern (error = _lib .FLAC__STREAM_DECODER_READ_STATUS_ABORT )
307395def _read_callback (_decoder ,
308396 byte_buffer ,
@@ -314,6 +402,12 @@ def _read_callback(_decoder,
314402 If an exception is raised here, the abort status is returned.
315403 """
316404 decoder = _ffi .from_handle (client_data )
405+
406+ # ----------------------------------------------------------
407+ # Wait until there is something in the buffer, or an error
408+ # occurs, or the end of the stream is reached.
409+ # ----------------------------------------------------------
410+ decoder ._event .wait ()
317411 if decoder ._error :
318412 # ----------------------------------------------------------
319413 # If an error has been issued via the error callback, then
@@ -329,18 +423,13 @@ def _read_callback(_decoder,
329423 num_bytes [0 ] = 0
330424 return _lib .FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
331425
332- maximum_bytes = int (num_bytes [0 ])
333- while len (decoder ._buffer ) == 0 :
334- # ----------------------------------------------------------
335- # Wait until there is something in the buffer
336- # ----------------------------------------------------------
337- time .sleep (0.01 )
338-
339426 # --------------------------------------------------------------
340427 # Ensure only the maximum bytes or less is taken from
341428 # the thread safe queue.
342429 # --------------------------------------------------------------
343430 data = bytes ()
431+ maximum_bytes = int (num_bytes [0 ])
432+ decoder ._lock .acquire ()
344433 if len (decoder ._buffer [0 ]) <= maximum_bytes :
345434 data = decoder ._buffer .popleft ()
346435 maximum_bytes -= len (data )
@@ -349,6 +438,14 @@ def _read_callback(_decoder,
349438 data += decoder ._buffer [0 ][0 :maximum_bytes ]
350439 decoder ._buffer [0 ] = decoder ._buffer [0 ][maximum_bytes :]
351440
441+ # --------------------------------------------------------------
442+ # If there is no more data to process from the buffer, then
443+ # clear the event, the thread will await more data to process.
444+ # --------------------------------------------------------------
445+ if len (decoder ._buffer ) == 0 or (len (decoder ._buffer ) > 0 and len (decoder ._buffer [0 ]) == 0 ):
446+ decoder ._event .clear ()
447+ decoder ._lock .release ()
448+
352449 actual_bytes = len (data )
353450 num_bytes [0 ] = actual_bytes
354451 _ffi .memmove (byte_buffer , data , actual_bytes )
@@ -449,3 +546,4 @@ def _error_callback(_decoder,
449546 _lib .FLAC__StreamDecoderErrorStatusString [status ]).decode ()
450547 decoder .logger .error (f'Error in libFLAC decoder: { message } ' )
451548 decoder ._error = message
549+ decoder ._event .set ()
0 commit comments