/src/vlc/modules/codec/flac.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * flac.c: flac decoder/encoder module making use of libflac |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 1999-2001 VLC authors and VideoLAN |
5 | | * |
6 | | * Authors: Gildas Bazin <gbazin@videolan.org> |
7 | | * Sigmund Augdal Helberg <dnumgis@videolan.org> |
8 | | * |
9 | | * This program is free software; you can redistribute it and/or modify it |
10 | | * under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation; either version 2.1 of the License, or |
12 | | * (at your option) any later version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with this program; if not, write to the Free Software Foundation, |
21 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
22 | | *****************************************************************************/ |
23 | | |
24 | | /***************************************************************************** |
25 | | * Preamble |
26 | | *****************************************************************************/ |
27 | | |
28 | | /* workaround libflac overriding assert.h system header */ |
29 | | #define assert(x) do {} while(0) |
30 | | |
31 | | #ifdef HAVE_CONFIG_H |
32 | | # include "config.h" |
33 | | #endif |
34 | | |
35 | | #include <stdbit.h> |
36 | | |
37 | | #include <vlc_common.h> |
38 | | #include <vlc_plugin.h> |
39 | | #include <vlc_codec.h> |
40 | | #include <vlc_codecs.h> |
41 | | #include <vlc_aout.h> |
42 | | |
43 | | #ifdef _WIN32 |
44 | | # define FLAC__NO_DLL |
45 | | #endif |
46 | | |
47 | | #include <FLAC/stream_decoder.h> |
48 | | #include <FLAC/stream_encoder.h> |
49 | | |
50 | | #include <vlc_block_helper.h> |
51 | | #include <vlc_bits.h> |
52 | | |
53 | | #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT >= 8 |
54 | | # define USE_NEW_FLAC_API |
55 | | #endif |
56 | | |
57 | | /***************************************************************************** |
58 | | * decoder_sys_t : FLAC decoder descriptor |
59 | | *****************************************************************************/ |
60 | | typedef struct |
61 | | { |
62 | | /* |
63 | | * Input/Output properties |
64 | | */ |
65 | | block_t *p_block; |
66 | | block_t *p_aout_buffer; |
67 | | date_t end_date; |
68 | | |
69 | | /* |
70 | | * FLAC properties |
71 | | */ |
72 | | FLAC__StreamDecoder *p_flac; |
73 | | FLAC__StreamMetadata_StreamInfo stream_info; |
74 | | |
75 | | uint8_t rgi_channels_reorder[AOUT_CHAN_MAX]; |
76 | | bool b_stream_info; |
77 | | } decoder_sys_t; |
78 | | |
79 | | static const int pi_channels_maps[FLAC__MAX_CHANNELS + 1] = |
80 | | { |
81 | | 0, |
82 | | AOUT_CHAN_CENTER, |
83 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT, |
84 | | AOUT_CHAN_CENTER | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT, |
85 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARLEFT |
86 | | | AOUT_CHAN_REARRIGHT, |
87 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER |
88 | | | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT, |
89 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER |
90 | | | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE, |
91 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER |
92 | | | AOUT_CHAN_REARCENTER | AOUT_CHAN_MIDDLELEFT| AOUT_CHAN_MIDDLERIGHT |
93 | | | AOUT_CHAN_LFE, |
94 | | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER | AOUT_CHAN_REARLEFT |
95 | | | AOUT_CHAN_REARRIGHT | AOUT_CHAN_MIDDLELEFT | AOUT_CHAN_MIDDLERIGHT |
96 | | | AOUT_CHAN_LFE |
97 | | }; |
98 | | |
99 | | /* XXX it supposes our internal format is WG4 */ |
100 | | static const uint8_t ppi_reorder[1+FLAC__MAX_CHANNELS][FLAC__MAX_CHANNELS] = |
101 | | { |
102 | | { 0 }, |
103 | | { 0, }, |
104 | | { 0, 1 }, |
105 | | { 0, 1, 2 }, |
106 | | { 0, 1, 2, 3 }, |
107 | | { 0, 1, 3, 4, 2 }, |
108 | | { 0, 1, 4, 5, 2, 3 }, |
109 | | { 0, 1, 5, 6, 4, 2, 3 }, |
110 | | { 0, 1, 6, 7, 4, 5, 2, 3 }, |
111 | | }; |
112 | | |
113 | | /* Flac uses same order as waveformatex */ |
114 | 0 | #define MAPPED_WFX_CHANNELS 9 |
115 | | static const uint32_t wfx_remapping[MAPPED_WFX_CHANNELS][2] = |
116 | | { |
117 | | { WAVE_SPEAKER_FRONT_LEFT, AOUT_CHAN_LEFT }, |
118 | | { WAVE_SPEAKER_FRONT_RIGHT, AOUT_CHAN_RIGHT }, |
119 | | { WAVE_SPEAKER_FRONT_CENTER, AOUT_CHAN_CENTER }, |
120 | | { WAVE_SPEAKER_LOW_FREQUENCY, AOUT_CHAN_LFE }, |
121 | | { WAVE_SPEAKER_BACK_LEFT, AOUT_CHAN_REARLEFT }, |
122 | | { WAVE_SPEAKER_BACK_RIGHT, AOUT_CHAN_REARRIGHT }, |
123 | | { WAVE_SPEAKER_BACK_CENTER, AOUT_CHAN_REARCENTER }, |
124 | | { WAVE_SPEAKER_SIDE_LEFT, AOUT_CHAN_MIDDLELEFT }, |
125 | | { WAVE_SPEAKER_SIDE_RIGHT, AOUT_CHAN_MIDDLERIGHT }, |
126 | | }; |
127 | | |
128 | | static const uint32_t wfx_chans_order[MAPPED_WFX_CHANNELS + 1] = |
129 | | { |
130 | | AOUT_CHAN_LEFT, |
131 | | AOUT_CHAN_RIGHT, |
132 | | AOUT_CHAN_CENTER, |
133 | | AOUT_CHAN_LFE, |
134 | | AOUT_CHAN_REARLEFT, |
135 | | AOUT_CHAN_REARRIGHT, |
136 | | AOUT_CHAN_REARCENTER, |
137 | | AOUT_CHAN_MIDDLELEFT, |
138 | | AOUT_CHAN_MIDDLERIGHT, |
139 | | 0 |
140 | | }; |
141 | | |
142 | | /***************************************************************************** |
143 | | * Local prototypes |
144 | | *****************************************************************************/ |
145 | | static int OpenDecoder ( vlc_object_t * ); |
146 | | static void CloseDecoder ( vlc_object_t * ); |
147 | | |
148 | | #ifdef ENABLE_SOUT |
149 | | static int OpenEncoder ( vlc_object_t * ); |
150 | | static void CloseEncoder ( encoder_t * ); |
151 | | #endif |
152 | | |
153 | | static int DecodeBlock( decoder_t *, block_t * ); |
154 | | static void Flush( decoder_t * ); |
155 | | |
156 | | /***************************************************************************** |
157 | | * Module descriptor |
158 | | *****************************************************************************/ |
159 | 168 | vlc_module_begin () |
160 | | |
161 | 84 | set_subcategory( SUBCAT_INPUT_ACODEC ) |
162 | 84 | add_shortcut( "flac" ) |
163 | | |
164 | 84 | set_description( N_("Flac audio decoder") ) |
165 | 84 | set_capability( "audio decoder", 100 ) |
166 | 168 | set_callbacks( OpenDecoder, CloseDecoder ) |
167 | | |
168 | 84 | #ifdef ENABLE_SOUT |
169 | 84 | add_submodule () |
170 | 84 | add_shortcut( "flac" ) |
171 | 84 | set_description( N_("Flac audio encoder") ) |
172 | 84 | set_capability( "audio encoder", 100 ) |
173 | 84 | set_callback( OpenEncoder ) |
174 | 84 | #endif |
175 | | |
176 | 84 | vlc_module_end () |
177 | | |
178 | | /***************************************************************************** |
179 | | * Interleave: helper function to interleave channels |
180 | | *****************************************************************************/ |
181 | | static void Interleave( int32_t *p_out, const int32_t * const *pp_in, |
182 | | const uint8_t *restrict pi_index, unsigned i_nb_channels, |
183 | | unsigned i_samples, unsigned bits ) |
184 | 0 | { |
185 | 0 | unsigned shift = 32 - bits; |
186 | |
|
187 | 0 | for( unsigned j = 0; j < i_samples; j++ ) |
188 | 0 | for( unsigned i = 0; i < i_nb_channels; i++ ) |
189 | 0 | { |
190 | 0 | union { int32_t i; uint32_t u; } spl; |
191 | |
|
192 | 0 | spl.u = ((uint32_t)pp_in[pi_index[i]][j]) << shift; |
193 | 0 | p_out[j * i_nb_channels + i] = spl.i; |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | /***************************************************************************** |
198 | | * DecoderSetOutputFormat: helper function to convert and check frame format |
199 | | *****************************************************************************/ |
200 | | static int DecoderSetOutputFormat( unsigned i_channels, unsigned i_rate, |
201 | | unsigned i_streaminfo_rate, |
202 | | unsigned i_bitspersample, |
203 | | audio_format_t *fmt, |
204 | | uint8_t *pi_channels_reorder ) |
205 | 13.2k | { |
206 | 13.2k | if( i_channels == 0 || i_channels > FLAC__MAX_CHANNELS || |
207 | 13.2k | i_bitspersample == 0 || (i_rate == 0 && i_streaminfo_rate == 0) ) |
208 | 0 | return VLC_EGENERIC; |
209 | | |
210 | 13.2k | fmt->i_channels = i_channels; |
211 | 13.2k | fmt->i_rate = (i_rate > 0 ) ? i_rate : i_streaminfo_rate; |
212 | 13.2k | fmt->i_physical_channels = pi_channels_maps[i_channels]; |
213 | 13.2k | memcpy( pi_channels_reorder, ppi_reorder[i_channels], i_channels ); |
214 | 13.2k | fmt->i_bitspersample = i_bitspersample; |
215 | | |
216 | 13.2k | return VLC_SUCCESS; |
217 | 13.2k | } |
218 | | |
219 | | /***************************************************************************** |
220 | | * DecoderWriteCallback: called by libflac to output decoded samples |
221 | | *****************************************************************************/ |
222 | | static FLAC__StreamDecoderWriteStatus |
223 | | DecoderWriteCallback( const FLAC__StreamDecoder *decoder, |
224 | | const FLAC__Frame *frame, |
225 | | const FLAC__int32 *const buffer[], void *client_data ) |
226 | 3.38k | { |
227 | 3.38k | VLC_UNUSED(decoder); |
228 | 3.38k | decoder_t *p_dec = (decoder_t *)client_data; |
229 | 3.38k | decoder_sys_t *p_sys = p_dec->p_sys; |
230 | | |
231 | 3.38k | if( DecoderSetOutputFormat( frame->header.channels, |
232 | 3.38k | frame->header.sample_rate, |
233 | 3.38k | p_sys->b_stream_info ? p_sys->stream_info.sample_rate : 0, |
234 | 3.38k | frame->header.bits_per_sample, |
235 | 3.38k | &p_dec->fmt_out.audio, |
236 | 3.38k | p_sys->rgi_channels_reorder ) ) |
237 | 0 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
238 | | |
239 | 3.38k | if( p_sys->end_date.i_divider_num != p_dec->fmt_out.audio.i_rate ) |
240 | 0 | { |
241 | 0 | if( p_sys->end_date.i_divider_num > 0 ) |
242 | 0 | date_Change( &p_sys->end_date, p_dec->fmt_out.audio.i_rate, 1 ); |
243 | 0 | else |
244 | 0 | date_Init( &p_sys->end_date, p_dec->fmt_out.audio.i_rate, 1 ); |
245 | 0 | } |
246 | | |
247 | 3.38k | if( decoder_UpdateAudioFormat( p_dec ) ) |
248 | 3.38k | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
249 | | |
250 | 0 | if( date_Get( &p_sys->end_date ) == VLC_TICK_INVALID ) |
251 | 0 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
252 | | |
253 | 0 | p_sys->p_aout_buffer = |
254 | 0 | decoder_NewAudioBuffer( p_dec, frame->header.blocksize ); |
255 | |
|
256 | 0 | if( p_sys->p_aout_buffer == NULL ) |
257 | 0 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
258 | | |
259 | 0 | Interleave( (int32_t *)p_sys->p_aout_buffer->p_buffer, buffer, |
260 | 0 | p_sys->rgi_channels_reorder, |
261 | 0 | frame->header.channels, frame->header.blocksize, |
262 | 0 | frame->header.bits_per_sample ); |
263 | | |
264 | | /* Date management (already done by packetizer) */ |
265 | 0 | p_sys->p_aout_buffer->i_pts = date_Get( &p_sys->end_date ); |
266 | 0 | p_sys->p_aout_buffer->i_length = |
267 | 0 | date_Increment( &p_sys->end_date, frame->header.blocksize ) - |
268 | 0 | p_sys->p_aout_buffer->i_pts; |
269 | |
|
270 | 0 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
271 | 0 | } |
272 | | |
273 | | /***************************************************************************** |
274 | | * DecoderReadCallback: called by libflac when it needs more data |
275 | | *****************************************************************************/ |
276 | | static FLAC__StreamDecoderReadStatus |
277 | | DecoderReadCallback( const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], |
278 | | size_t *bytes, void *client_data ) |
279 | 90.5k | { |
280 | 90.5k | VLC_UNUSED(decoder); |
281 | 90.5k | decoder_t *p_dec = (decoder_t *)client_data; |
282 | 90.5k | decoder_sys_t *p_sys = p_dec->p_sys; |
283 | | |
284 | 90.5k | if( p_sys->p_block && p_sys->p_block->i_buffer ) |
285 | 51.6k | { |
286 | 51.6k | *bytes = __MIN(*bytes, p_sys->p_block->i_buffer); |
287 | 51.6k | memcpy( buffer, p_sys->p_block->p_buffer, *bytes ); |
288 | 51.6k | p_sys->p_block->i_buffer -= *bytes; |
289 | 51.6k | p_sys->p_block->p_buffer += *bytes; |
290 | 51.6k | } |
291 | 38.8k | else |
292 | 38.8k | { |
293 | 38.8k | *bytes = 0; |
294 | 38.8k | return FLAC__STREAM_DECODER_READ_STATUS_ABORT; |
295 | 38.8k | } |
296 | | |
297 | 51.6k | return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; |
298 | 90.5k | } |
299 | | |
300 | | /***************************************************************************** |
301 | | * DecoderMetadataCallback: called by libflac to when it encounters metadata |
302 | | *****************************************************************************/ |
303 | | static void DecoderMetadataCallback( const FLAC__StreamDecoder *decoder, |
304 | | const FLAC__StreamMetadata *metadata, |
305 | | void *client_data ) |
306 | 9.90k | { |
307 | 9.90k | VLC_UNUSED(decoder); |
308 | 9.90k | decoder_t *p_dec = (decoder_t *)client_data; |
309 | 9.90k | decoder_sys_t *p_sys = p_dec->p_sys; |
310 | | |
311 | 9.90k | switch(metadata->type) |
312 | 9.90k | { |
313 | 9.90k | case FLAC__METADATA_TYPE_STREAMINFO: |
314 | | /* Setup the format */ |
315 | 9.90k | DecoderSetOutputFormat( metadata->data.stream_info.channels, |
316 | 9.90k | metadata->data.stream_info.sample_rate, |
317 | 9.90k | metadata->data.stream_info.sample_rate, |
318 | 9.90k | metadata->data.stream_info.bits_per_sample, |
319 | 9.90k | &p_dec->fmt_out.audio, p_sys->rgi_channels_reorder ); |
320 | | |
321 | 9.90k | msg_Dbg( p_dec, "channels:%d samplerate:%d bitspersamples:%d", |
322 | 9.90k | p_dec->fmt_out.audio.i_channels, p_dec->fmt_out.audio.i_rate, |
323 | 9.90k | p_dec->fmt_out.audio.i_bitspersample ); |
324 | | |
325 | 9.90k | p_sys->b_stream_info = true; |
326 | 9.90k | p_sys->stream_info = metadata->data.stream_info; |
327 | | |
328 | 9.90k | date_Init( &p_sys->end_date, p_dec->fmt_out.audio.i_rate, 1 ); |
329 | 9.90k | break; |
330 | | |
331 | 0 | case FLAC__METADATA_TYPE_VORBIS_COMMENT: |
332 | 0 | for( FLAC__uint32 i=0; i<metadata->data.vorbis_comment.num_comments; i++ ) |
333 | 0 | { |
334 | 0 | const FLAC__StreamMetadata_VorbisComment_Entry *comment = |
335 | 0 | &metadata->data.vorbis_comment.comments[i]; |
336 | | /* Check for custom WAVEFORMATEX channel ordering */ |
337 | 0 | if( comment->length > 34 && |
338 | 0 | !strncmp( "WAVEFORMATEXTENSIBLE_CHANNEL_MASK=", (char *) comment->entry, 34 ) ) |
339 | 0 | { |
340 | 0 | char *endptr = (char *) &comment->entry[34] + comment->length; |
341 | 0 | const uint32_t i_wfxmask = strtoul( (char *) &comment->entry[34], &endptr, 16 ); |
342 | 0 | const unsigned i_wfxchannels = stdc_count_ones( i_wfxmask ); |
343 | 0 | if( i_wfxchannels > 0 && i_wfxchannels <= AOUT_CHAN_MAX ) |
344 | 0 | { |
345 | | /* Create the vlc bitmap from wfx channels */ |
346 | 0 | uint32_t i_vlcmask = 0; |
347 | 0 | for( uint32_t i_chan = 1; i_chan && i_chan <= i_wfxmask; i_chan <<= 1 ) |
348 | 0 | { |
349 | 0 | if( (i_chan & i_wfxmask) == 0 ) |
350 | 0 | continue; |
351 | 0 | for( size_t j=0; j<MAPPED_WFX_CHANNELS; j++ ) |
352 | 0 | { |
353 | 0 | if( wfx_remapping[j][0] == i_chan ) |
354 | 0 | i_vlcmask |= wfx_remapping[j][1]; |
355 | 0 | } |
356 | 0 | } |
357 | | /* Check if we have the 1 to 1 mapping */ |
358 | 0 | if( stdc_count_ones(i_vlcmask) != i_wfxchannels ) |
359 | 0 | { |
360 | 0 | msg_Warn( p_dec, "Unsupported channel mask %x", i_wfxmask ); |
361 | 0 | return; |
362 | 0 | } |
363 | | |
364 | | /* Compute the remapping */ |
365 | 0 | uint8_t neworder[AOUT_CHAN_MAX] = {0}; |
366 | 0 | aout_CheckChannelReorder( wfx_chans_order, NULL, |
367 | 0 | i_vlcmask, neworder ); |
368 | | |
369 | | /* /!\ Invert our source/dest reordering, |
370 | | * as Interleave() here works source indexes */ |
371 | 0 | for( unsigned j=0; j<i_wfxchannels; j++ ) |
372 | 0 | p_sys->rgi_channels_reorder[neworder[j]] = j; |
373 | |
|
374 | 0 | p_dec->fmt_out.audio.i_physical_channels = i_vlcmask; |
375 | 0 | p_dec->fmt_out.audio.i_channels = i_wfxchannels; |
376 | 0 | } |
377 | | |
378 | 0 | break; |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | 0 | default: |
383 | 0 | break; |
384 | 9.90k | } |
385 | 9.90k | } |
386 | | |
387 | | /***************************************************************************** |
388 | | * DecoderErrorCallback: called when the libflac decoder encounters an error |
389 | | *****************************************************************************/ |
390 | | static void DecoderErrorCallback( const FLAC__StreamDecoder *decoder, |
391 | | FLAC__StreamDecoderErrorStatus status, |
392 | | void *client_data ) |
393 | 38.8k | { |
394 | 38.8k | VLC_UNUSED(decoder); |
395 | 38.8k | decoder_t *p_dec = (decoder_t *)client_data; |
396 | 38.8k | decoder_sys_t *p_sys = p_dec->p_sys; |
397 | | |
398 | 38.8k | switch( status ) |
399 | 38.8k | { |
400 | 8.88k | case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: |
401 | 8.88k | msg_Warn( p_dec, "an error in the stream caused the decoder to " |
402 | 8.88k | "lose synchronization." ); |
403 | 8.88k | break; |
404 | 875 | case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: |
405 | 875 | msg_Err( p_dec, "the decoder encountered a corrupted frame header." ); |
406 | 875 | break; |
407 | 3.03k | case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: |
408 | 3.03k | msg_Err( p_dec, "frame's data did not match the CRC in the " |
409 | 3.03k | "footer." ); |
410 | 3.03k | break; |
411 | 25.2k | case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: |
412 | 25.2k | msg_Err( p_dec, "The decoder encountered reserved fields in use in " |
413 | 25.2k | "the stream." ); |
414 | 25.2k | break; |
415 | 723 | default: |
416 | 723 | msg_Err( p_dec, "got decoder error: %d", status ); |
417 | 38.8k | } |
418 | | |
419 | 38.8k | FLAC__stream_decoder_flush( p_sys->p_flac ); |
420 | 38.8k | return; |
421 | 38.8k | } |
422 | | /***************************************************************************** |
423 | | * OpenDecoder: probe the decoder and return score |
424 | | *****************************************************************************/ |
425 | | static int OpenDecoder( vlc_object_t *p_this ) |
426 | 706k | { |
427 | 706k | decoder_t *p_dec = (decoder_t*)p_this; |
428 | 706k | decoder_sys_t *p_sys; |
429 | | |
430 | 706k | if( p_dec->fmt_in->i_codec != VLC_CODEC_FLAC ) |
431 | 696k | { |
432 | 696k | return VLC_EGENERIC; |
433 | 696k | } |
434 | | |
435 | | /* Allocate the memory needed to store the decoder's structure */ |
436 | 10.4k | if( ( p_dec->p_sys = p_sys = malloc(sizeof(*p_sys)) ) == NULL ) |
437 | 0 | return VLC_ENOMEM; |
438 | | |
439 | | /* Misc init */ |
440 | 10.4k | p_sys->b_stream_info = false; |
441 | 10.4k | memset(p_sys->rgi_channels_reorder, 0, AOUT_CHAN_MAX); |
442 | 10.4k | p_sys->p_block = NULL; |
443 | | |
444 | | /* Take care of flac init */ |
445 | 10.4k | if( !(p_sys->p_flac = FLAC__stream_decoder_new()) ) |
446 | 0 | { |
447 | 0 | msg_Err( p_dec, "FLAC__stream_decoder_new() failed" ); |
448 | 0 | free( p_sys ); |
449 | 0 | return VLC_EGENERIC; |
450 | 0 | } |
451 | | |
452 | | /* Enable STREAMINFO + COMMENTS */ |
453 | 10.4k | FLAC__stream_decoder_set_metadata_respond( p_sys->p_flac, |
454 | 10.4k | FLAC__METADATA_TYPE_VORBIS_COMMENT ); |
455 | | |
456 | 10.4k | #ifdef USE_NEW_FLAC_API |
457 | 10.4k | if( FLAC__stream_decoder_init_stream( p_sys->p_flac, |
458 | 10.4k | DecoderReadCallback, |
459 | 10.4k | NULL, |
460 | 10.4k | NULL, |
461 | 10.4k | NULL, |
462 | 10.4k | NULL, |
463 | 10.4k | DecoderWriteCallback, |
464 | 10.4k | DecoderMetadataCallback, |
465 | 10.4k | DecoderErrorCallback, |
466 | 10.4k | p_dec ) |
467 | 10.4k | != FLAC__STREAM_DECODER_INIT_STATUS_OK ) |
468 | 0 | { |
469 | 0 | msg_Err( p_dec, "FLAC__stream_decoder_init_stream() failed" ); |
470 | 0 | FLAC__stream_decoder_delete( p_sys->p_flac ); |
471 | 0 | free( p_sys ); |
472 | 0 | return VLC_EGENERIC; |
473 | 0 | } |
474 | | #else |
475 | | FLAC__stream_decoder_set_read_callback( p_sys->p_flac, |
476 | | DecoderReadCallback ); |
477 | | FLAC__stream_decoder_set_write_callback( p_sys->p_flac, |
478 | | DecoderWriteCallback ); |
479 | | FLAC__stream_decoder_set_metadata_callback( p_sys->p_flac, |
480 | | DecoderMetadataCallback ); |
481 | | FLAC__stream_decoder_set_error_callback( p_sys->p_flac, |
482 | | DecoderErrorCallback ); |
483 | | FLAC__stream_decoder_set_client_data( p_sys->p_flac, p_dec ); |
484 | | |
485 | | FLAC__stream_decoder_init( p_sys->p_flac ); |
486 | | #endif |
487 | | |
488 | | /* Set output properties */ |
489 | 10.4k | p_dec->fmt_out.i_codec = VLC_CODEC_S32N; |
490 | | |
491 | | /* Set callbacks */ |
492 | 10.4k | p_dec->pf_decode = DecodeBlock; |
493 | 10.4k | p_dec->pf_flush = Flush; |
494 | | |
495 | 10.4k | return VLC_SUCCESS; |
496 | 10.4k | } |
497 | | |
498 | | /***************************************************************************** |
499 | | * CloseDecoder: flac decoder destruction |
500 | | *****************************************************************************/ |
501 | | static void CloseDecoder( vlc_object_t *p_this ) |
502 | 10.4k | { |
503 | 10.4k | decoder_t *p_dec = (decoder_t *)p_this; |
504 | 10.4k | decoder_sys_t *p_sys = p_dec->p_sys; |
505 | | |
506 | 10.4k | FLAC__stream_decoder_finish( p_sys->p_flac ); |
507 | 10.4k | FLAC__stream_decoder_delete( p_sys->p_flac ); |
508 | | |
509 | 10.4k | if( p_sys->p_block ) |
510 | 0 | block_Release( p_sys->p_block ); |
511 | 10.4k | free( p_sys ); |
512 | 10.4k | } |
513 | | |
514 | | /***************************************************************************** |
515 | | * ProcessHeader: process Flac header. |
516 | | *****************************************************************************/ |
517 | | static void ProcessHeader( decoder_t *p_dec ) |
518 | | { |
519 | | decoder_sys_t *p_sys = p_dec->p_sys; |
520 | | |
521 | | if( !p_dec->fmt_in->i_extra ) |
522 | | return; |
523 | | |
524 | | /* Decode STREAMINFO */ |
525 | | msg_Dbg( p_dec, "decode STREAMINFO" ); |
526 | | size_t i_extra = p_dec->fmt_in->i_extra; |
527 | | |
528 | | static const char header[4] = { 'f', 'L', 'a', 'C' }; |
529 | | |
530 | | if( memcmp( p_dec->fmt_in->p_extra, header, 4 ) ) |
531 | | i_extra += 8; |
532 | | |
533 | | p_sys->p_block = block_Alloc( i_extra ); |
534 | | if( p_sys->p_block == NULL ) |
535 | | return; |
536 | | |
537 | | uint8_t *p_data = p_sys->p_block->p_buffer; |
538 | | if( i_extra != p_dec->fmt_in->i_extra ) |
539 | | { |
540 | | memcpy( p_data, header, 4); |
541 | | p_data[4] = 0x80 | 0; /* STREAMINFO faked as last block */ |
542 | | p_data[5] = 0; |
543 | | p_data[6] = 0; |
544 | | p_data[7] = 34; /* block size */ |
545 | | p_data += 8; |
546 | | } |
547 | | memcpy( p_data, p_dec->fmt_in->p_extra, p_dec->fmt_in->i_extra ); |
548 | | |
549 | | FLAC__stream_decoder_process_until_end_of_metadata( p_sys->p_flac ); |
550 | | msg_Dbg( p_dec, "STREAMINFO decoded" ); |
551 | | |
552 | | block_Release( p_sys->p_block ); |
553 | | p_sys->p_block = NULL; |
554 | | } |
555 | | |
556 | | /***************************************************************************** |
557 | | * decoder_state_error: print meaningful error messages |
558 | | *****************************************************************************/ |
559 | | static void decoder_state_error( decoder_t *p_dec, |
560 | | FLAC__StreamDecoderState state ) |
561 | 1.84k | { |
562 | 1.84k | switch ( state ) |
563 | 1.84k | { |
564 | 0 | case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: |
565 | 0 | msg_Dbg( p_dec, "the decoder is ready to search for metadata." ); |
566 | 0 | break; |
567 | 0 | case FLAC__STREAM_DECODER_READ_METADATA: |
568 | 0 | msg_Dbg( p_dec, "the decoder is ready to or is in the process of " |
569 | 0 | "reading metadata." ); |
570 | 0 | break; |
571 | 0 | case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: |
572 | 0 | msg_Dbg( p_dec, "the decoder is ready to or is in the process of " |
573 | 0 | "searching for the frame sync code." ); |
574 | 0 | break; |
575 | 0 | case FLAC__STREAM_DECODER_READ_FRAME: |
576 | 0 | msg_Dbg( p_dec, "the decoder is ready to or is in the process of " |
577 | 0 | "reading a frame." ); |
578 | 0 | break; |
579 | 0 | case FLAC__STREAM_DECODER_END_OF_STREAM: |
580 | 0 | msg_Dbg( p_dec, "the decoder has reached the end of the stream." ); |
581 | 0 | break; |
582 | 0 | #ifdef USE_NEW_FLAC_API |
583 | 0 | case FLAC__STREAM_DECODER_OGG_ERROR: |
584 | 0 | msg_Err( p_dec, "error occurred in the Ogg layer." ); |
585 | 0 | break; |
586 | 0 | case FLAC__STREAM_DECODER_SEEK_ERROR: |
587 | 0 | msg_Err( p_dec, "error occurred while seeking." ); |
588 | 0 | break; |
589 | 0 | #endif |
590 | 1.84k | case FLAC__STREAM_DECODER_ABORTED: |
591 | 1.84k | msg_Warn( p_dec, "the decoder was aborted by the read callback." ); |
592 | 1.84k | break; |
593 | | #ifndef USE_NEW_FLAC_API |
594 | | case FLAC__STREAM_DECODER_UNPARSEABLE_STREAM: |
595 | | msg_Warn( p_dec, "the decoder encountered reserved fields in use " |
596 | | "in the stream." ); |
597 | | break; |
598 | | #endif |
599 | 0 | case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: |
600 | 0 | msg_Err( p_dec, "error when allocating memory." ); |
601 | 0 | break; |
602 | | #ifndef USE_NEW_FLAC_API |
603 | | case FLAC__STREAM_DECODER_ALREADY_INITIALIZED: |
604 | | msg_Err( p_dec, "FLAC__stream_decoder_init() was called when the " |
605 | | "decoder was already initialized, usually because " |
606 | | "FLAC__stream_decoder_finish() was not called." ); |
607 | | break; |
608 | | case FLAC__STREAM_DECODER_INVALID_CALLBACK: |
609 | | msg_Err( p_dec, "FLAC__stream_decoder_init() was called without " |
610 | | "all callbacks being set." ); |
611 | | break; |
612 | | #endif |
613 | 0 | case FLAC__STREAM_DECODER_UNINITIALIZED: |
614 | 0 | msg_Err( p_dec, "decoder in uninitialized state." ); |
615 | 0 | break; |
616 | 0 | default: |
617 | 0 | msg_Warn(p_dec, "unknown error" ); |
618 | 1.84k | } |
619 | 1.84k | } |
620 | | |
621 | | /***************************************************************************** |
622 | | * Flush: |
623 | | *****************************************************************************/ |
624 | | static void Flush( decoder_t *p_dec ) |
625 | | { |
626 | | decoder_sys_t *p_sys = p_dec->p_sys; |
627 | | |
628 | | if( p_sys->b_stream_info ) |
629 | | FLAC__stream_decoder_flush( p_sys->p_flac ); |
630 | | date_Set( &p_sys->end_date, VLC_TICK_INVALID ); |
631 | | } |
632 | | |
633 | | /**************************************************************************** |
634 | | * DecodeBlock: the whole thing |
635 | | ****************************************************************************/ |
636 | | static int DecodeBlock( decoder_t *p_dec, block_t *p_block ) |
637 | 215k | { |
638 | 215k | decoder_sys_t *p_sys = p_dec->p_sys; |
639 | | |
640 | 215k | if( p_block == NULL ) /* No Drain */ |
641 | 174k | return VLCDEC_SUCCESS; |
642 | 40.1k | if( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY | BLOCK_FLAG_CORRUPTED) ) |
643 | 7.67k | { |
644 | 7.67k | Flush( p_dec ); |
645 | 7.67k | if( p_block->i_flags & BLOCK_FLAG_CORRUPTED ) |
646 | 0 | { |
647 | 0 | block_Release( p_block ); |
648 | 0 | return VLCDEC_SUCCESS; |
649 | 0 | } |
650 | 7.67k | } |
651 | | |
652 | 40.1k | if( !p_sys->b_stream_info ) |
653 | 9.90k | { |
654 | 9.90k | ProcessHeader( p_dec ); |
655 | 9.90k | if( !p_sys->b_stream_info ) |
656 | 4 | { |
657 | 4 | block_Release( p_block ); |
658 | 4 | return VLCDEC_ECRITICAL; |
659 | 4 | } |
660 | 9.90k | } |
661 | | |
662 | 40.1k | p_sys->p_block = p_block; |
663 | | |
664 | 40.1k | if( p_sys->p_block->i_pts != VLC_TICK_INVALID && |
665 | 39.6k | p_sys->p_block->i_pts != date_Get( &p_sys->end_date ) ) |
666 | 39.6k | date_Set( &p_sys->end_date, p_sys->p_block->i_pts ); |
667 | | |
668 | 40.1k | p_sys->p_aout_buffer = 0; |
669 | | |
670 | 40.1k | if( !FLAC__stream_decoder_process_single( p_sys->p_flac ) ) |
671 | 1.84k | { |
672 | 1.84k | decoder_state_error( p_dec, |
673 | 1.84k | FLAC__stream_decoder_get_state( p_sys->p_flac ) ); |
674 | 1.84k | FLAC__stream_decoder_flush( p_sys->p_flac ); |
675 | 1.84k | } |
676 | | |
677 | | /* If the decoder is in the "aborted" state, |
678 | | * FLAC__stream_decoder_process_single() won't return an error. */ |
679 | 40.1k | switch ( FLAC__stream_decoder_get_state(p_sys->p_flac) ) |
680 | 40.1k | { |
681 | 37.0k | case FLAC__STREAM_DECODER_ABORTED: |
682 | 37.0k | FLAC__stream_decoder_flush( p_sys->p_flac ); |
683 | 37.0k | break; |
684 | 0 | case FLAC__STREAM_DECODER_END_OF_STREAM: |
685 | 0 | FLAC__stream_decoder_reset( p_sys->p_flac ); |
686 | 0 | break; |
687 | 3.11k | default: |
688 | 3.11k | break; |
689 | 40.1k | } |
690 | | |
691 | 40.1k | block_Release( p_sys->p_block ); |
692 | 40.1k | p_sys->p_block = NULL; |
693 | | |
694 | 40.1k | if( p_sys->p_aout_buffer != NULL ) |
695 | 0 | decoder_QueueAudio( p_dec, p_sys->p_aout_buffer ); |
696 | 40.1k | return VLCDEC_SUCCESS; |
697 | 40.1k | } |
698 | | |
699 | | #ifdef ENABLE_SOUT |
700 | | |
701 | | /***************************************************************************** |
702 | | * encoder_sys_t : flac encoder descriptor |
703 | | *****************************************************************************/ |
704 | | typedef struct |
705 | | { |
706 | | /* |
707 | | * Input properties |
708 | | */ |
709 | | int i_headers; |
710 | | |
711 | | int i_samples_delay; |
712 | | |
713 | | FLAC__int32 *p_buffer; |
714 | | unsigned int i_buffer; |
715 | | |
716 | | block_t *p_chain; |
717 | | |
718 | | /* |
719 | | * FLAC properties |
720 | | */ |
721 | | FLAC__StreamEncoder *p_flac; |
722 | | FLAC__StreamMetadata_StreamInfo stream_info; |
723 | | |
724 | | /* |
725 | | * Common properties |
726 | | */ |
727 | | vlc_tick_t i_pts; |
728 | | } encoder_sys_t; |
729 | | |
730 | 0 | #define STREAMINFO_SIZE 34 |
731 | | |
732 | | static block_t *Encode( encoder_t *, block_t * ); |
733 | | |
734 | | /***************************************************************************** |
735 | | * EncoderWriteCallback: called by libflac to output encoded samples |
736 | | *****************************************************************************/ |
737 | | static FLAC__StreamEncoderWriteStatus |
738 | | EncoderWriteCallback( const FLAC__StreamEncoder *encoder, |
739 | | const FLAC__byte buffer[], |
740 | | size_t bytes, unsigned samples, |
741 | | unsigned current_frame, void *client_data ) |
742 | 0 | { |
743 | 0 | VLC_UNUSED(encoder); VLC_UNUSED(current_frame); |
744 | 0 | encoder_t *p_enc = (encoder_t *)client_data; |
745 | 0 | encoder_sys_t *p_sys = p_enc->p_sys; |
746 | 0 | block_t *p_block; |
747 | |
|
748 | 0 | if( samples == 0 ) |
749 | 0 | { |
750 | 0 | if( p_sys->i_headers == 1 ) |
751 | 0 | { |
752 | 0 | msg_Dbg( p_enc, "Writing STREAMINFO: %zu", bytes ); |
753 | | |
754 | | /* Backup the STREAMINFO metadata block */ |
755 | 0 | p_enc->fmt_out.i_extra = STREAMINFO_SIZE + 8; |
756 | 0 | p_enc->fmt_out.p_extra = xmalloc( STREAMINFO_SIZE + 8); |
757 | 0 | memcpy(p_enc->fmt_out.p_extra, "fLaC", 4); |
758 | 0 | memcpy((uint8_t*)p_enc->fmt_out.p_extra + 4, buffer, STREAMINFO_SIZE ); |
759 | | /* Fake this as the last metadata block */ |
760 | 0 | ((uint8_t*)p_enc->fmt_out.p_extra)[4] |= 0x80; |
761 | 0 | } |
762 | 0 | p_sys->i_headers++; |
763 | 0 | return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; |
764 | 0 | } |
765 | | |
766 | 0 | p_block = block_Alloc( bytes ); |
767 | | /* FLAC__STREAM_ENCODER_MEMORY_ALLOCATION_ERROR would be more appropriate |
768 | | but it's not the right type of enum */ |
769 | 0 | if( unlikely(p_block == NULL) ) |
770 | 0 | return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; |
771 | | |
772 | 0 | memcpy( p_block->p_buffer, buffer, bytes ); |
773 | |
|
774 | 0 | p_block->i_dts = p_block->i_pts = p_sys->i_pts; |
775 | |
|
776 | 0 | p_sys->i_samples_delay -= samples; |
777 | |
|
778 | 0 | p_block->i_length = vlc_tick_from_samples(samples, p_enc->fmt_in.audio.i_rate); |
779 | | |
780 | | /* Update pts */ |
781 | 0 | p_sys->i_pts += p_block->i_length; |
782 | |
|
783 | 0 | block_ChainAppend( &p_sys->p_chain, p_block ); |
784 | |
|
785 | 0 | return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; |
786 | 0 | } |
787 | | /***************************************************************************** |
788 | | * EncoderMetadataCallback: called by libflac to output metadata |
789 | | *****************************************************************************/ |
790 | | static void EncoderMetadataCallback( const FLAC__StreamEncoder *encoder, |
791 | | const FLAC__StreamMetadata *metadata, |
792 | | void *client_data ) |
793 | 0 | { |
794 | 0 | VLC_UNUSED(encoder); |
795 | 0 | encoder_t *p_enc = (encoder_t *)client_data; |
796 | |
|
797 | 0 | msg_Err( p_enc, "MetadataCallback: %i", metadata->type ); |
798 | 0 | return; |
799 | 0 | } |
800 | | |
801 | | /***************************************************************************** |
802 | | * OpenEncoder: probe the encoder and return score |
803 | | *****************************************************************************/ |
804 | | static int OpenEncoder( vlc_object_t *p_this ) |
805 | 0 | { |
806 | 0 | encoder_t *p_enc = (encoder_t *)p_this; |
807 | 0 | encoder_sys_t *p_sys; |
808 | |
|
809 | 0 | if( p_enc->fmt_out.i_codec != VLC_CODEC_FLAC && |
810 | 0 | !p_enc->obj.force ) |
811 | 0 | { |
812 | 0 | return VLC_EGENERIC; |
813 | 0 | } |
814 | | |
815 | | /* Allocate the memory needed to store the decoder's structure */ |
816 | 0 | if( ( p_sys = (encoder_sys_t *)malloc(sizeof(encoder_sys_t)) ) == NULL ) |
817 | 0 | return VLC_ENOMEM; |
818 | 0 | p_enc->p_sys = p_sys; |
819 | 0 | p_enc->fmt_out.i_codec = VLC_CODEC_FLAC; |
820 | |
|
821 | 0 | p_sys->i_headers = 0; |
822 | 0 | p_sys->p_buffer = 0; |
823 | 0 | p_sys->i_buffer = 0; |
824 | 0 | p_sys->i_samples_delay = 0; |
825 | | |
826 | | /* Create flac encoder */ |
827 | 0 | if( !(p_sys->p_flac = FLAC__stream_encoder_new()) ) |
828 | 0 | { |
829 | 0 | msg_Err( p_enc, "FLAC__stream_encoder_new() failed" ); |
830 | 0 | free( p_sys ); |
831 | 0 | return VLC_EGENERIC; |
832 | 0 | } |
833 | | |
834 | 0 | FLAC__stream_encoder_set_streamable_subset( p_sys->p_flac, 1 ); |
835 | 0 | FLAC__stream_encoder_set_channels( p_sys->p_flac, |
836 | 0 | p_enc->fmt_in.audio.i_channels ); |
837 | 0 | FLAC__stream_encoder_set_sample_rate( p_sys->p_flac, |
838 | 0 | p_enc->fmt_in.audio.i_rate ); |
839 | 0 | FLAC__stream_encoder_set_bits_per_sample( p_sys->p_flac, 16 ); |
840 | 0 | p_enc->fmt_in.i_codec = VLC_CODEC_S16N; |
841 | | |
842 | | /* Get and store the STREAMINFO metadata block as a p_extra */ |
843 | 0 | p_sys->p_chain = 0; |
844 | |
|
845 | 0 | #ifdef USE_NEW_FLAC_API |
846 | 0 | if( FLAC__stream_encoder_init_stream( p_sys->p_flac, |
847 | 0 | EncoderWriteCallback, |
848 | 0 | NULL, |
849 | 0 | NULL, |
850 | 0 | EncoderMetadataCallback, |
851 | 0 | p_enc ) |
852 | 0 | != FLAC__STREAM_ENCODER_INIT_STATUS_OK ) |
853 | 0 | { |
854 | 0 | msg_Err( p_enc, "FLAC__stream_encoder_init_stream() failed" ); |
855 | 0 | FLAC__stream_encoder_delete( p_sys->p_flac ); |
856 | 0 | free( p_sys ); |
857 | 0 | return VLC_EGENERIC; |
858 | 0 | } |
859 | | #else |
860 | | FLAC__stream_encoder_set_write_callback( p_sys->p_flac, |
861 | | EncoderWriteCallback ); |
862 | | FLAC__stream_encoder_set_metadata_callback( p_sys->p_flac, |
863 | | EncoderMetadataCallback ); |
864 | | FLAC__stream_encoder_set_client_data( p_sys->p_flac, p_enc ); |
865 | | |
866 | | FLAC__stream_encoder_init( p_sys->p_flac ); |
867 | | #endif |
868 | | |
869 | 0 | static const struct vlc_encoder_operations ops = |
870 | 0 | { |
871 | 0 | .close = CloseEncoder, |
872 | 0 | .encode_audio = Encode, |
873 | 0 | }; |
874 | 0 | p_enc->ops = &ops; |
875 | |
|
876 | 0 | return VLC_SUCCESS; |
877 | 0 | } |
878 | | |
879 | | /**************************************************************************** |
880 | | * Encode: the whole thing |
881 | | **************************************************************************** |
882 | | * This function spits out ogg packets. |
883 | | ****************************************************************************/ |
884 | | static block_t *Encode( encoder_t *p_enc, block_t *p_aout_buf ) |
885 | 0 | { |
886 | 0 | encoder_sys_t *p_sys = p_enc->p_sys; |
887 | 0 | block_t *p_chain; |
888 | | |
889 | | /* FIXME: p_aout_buf is NULL when it's time to flush*/ |
890 | 0 | if( unlikely( !p_aout_buf ) ) return NULL; |
891 | | |
892 | 0 | p_sys->i_pts = p_aout_buf->i_pts - |
893 | 0 | vlc_tick_from_samples( p_sys->i_samples_delay, |
894 | 0 | p_enc->fmt_in.audio.i_rate ); |
895 | |
|
896 | 0 | p_sys->i_samples_delay += p_aout_buf->i_nb_samples; |
897 | | |
898 | | /* Convert samples to FLAC__int32 */ |
899 | 0 | if( p_sys->i_buffer < p_aout_buf->i_buffer * sizeof(FLAC__int32) ) |
900 | 0 | { |
901 | 0 | p_sys->p_buffer = |
902 | 0 | xrealloc( p_sys->p_buffer, p_aout_buf->i_buffer * sizeof(FLAC__int32) ); |
903 | 0 | p_sys->i_buffer = p_aout_buf->i_buffer * 2; |
904 | 0 | } |
905 | |
|
906 | 0 | for( unsigned i = 0 ; i < p_aout_buf->i_buffer / 2 ; i++ ) |
907 | 0 | { |
908 | 0 | p_sys->p_buffer[i]= ((int16_t *)p_aout_buf->p_buffer)[i]; |
909 | 0 | } |
910 | |
|
911 | 0 | FLAC__stream_encoder_process_interleaved( p_sys->p_flac, p_sys->p_buffer, |
912 | 0 | p_aout_buf->i_nb_samples ); |
913 | |
|
914 | 0 | p_chain = p_sys->p_chain; |
915 | 0 | p_sys->p_chain = 0; |
916 | |
|
917 | 0 | return p_chain; |
918 | 0 | } |
919 | | |
920 | | /***************************************************************************** |
921 | | * CloseEncoder: encoder destruction |
922 | | *****************************************************************************/ |
923 | | static void CloseEncoder( encoder_t *p_enc ) |
924 | 0 | { |
925 | 0 | encoder_sys_t *p_sys = p_enc->p_sys; |
926 | |
|
927 | 0 | FLAC__stream_encoder_delete( p_sys->p_flac ); |
928 | |
|
929 | 0 | free( p_sys->p_buffer ); |
930 | 0 | free( p_sys ); |
931 | 0 | } |
932 | | #endif |