Merge lp:~mixxxdevelopers/mixxx/features_flac into lp:~mixxxdevelopers/mixxx/trunk
- features_flac
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 2548 | ||||||||
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_flac | ||||||||
Merge into: | lp:~mixxxdevelopers/mixxx/trunk | ||||||||
Diff against target: |
523 lines (+477/-1) 4 files modified
mixxx/build/depends.py (+13/-1) mixxx/src/soundsourceflac.cpp (+374/-0) mixxx/src/soundsourceflac.h (+87/-0) mixxx/src/soundsourceproxy.cpp (+3/-0) |
||||||||
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_flac | ||||||||
Related bugs: |
|
||||||||
Related blueprints: |
FLAC support through libFLAC
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Albert Santoni | Needs Fixing | ||
Review via email: mp+31018@code.launchpad.net |
Commit message
Description of the change
Adds a new SoundSource using libFLAC. Written to replace Mixxx's use of libsndfile for FLAC due to the bugs linked to this branch.
- 2416. By William Good
-
Changed qDebugs to qWarnings as necessary and broke up some way-long lines.
- 2417. By William Good
-
Fixed memory allocation bug (was allocating based on number of channels when
it really uses 2 samples per frame no matter the number of channels).
William Good (bkgood) wrote : | # |
- 2418. By William Good
-
Marking a todo (code cleanup before taking a diff for gsoc)
- 2419. By William Good
-
Merging with trunk
- 2420. By William Good
-
Merging from lp:mixxx
Albert Santoni (gamegod) wrote : | # |
+ setBitrate(
- Should that be 1024 instead of 1000? Hardcoded bit depth too. Where do you tell FLAC that you want 16-bit samples? Can you make this a constant at least?
+inline int SoundSourceFLAC
- same hardcoded 16 constant?
- How do you deal with 24-bit FLAC files?
parseHeader():
- This is some undocumented bad news: parseHeader() has to be re-entrant. You can't use the existing file handle, because it can get called after an open() on an existing SoundSource object. This is the most important thing to fix.
William Good (bkgood) wrote : | # |
> + setBitrate(
>
> - Should that be 1024 instead of 1000? Hardcoded bit depth too. Where do you
> tell FLAC that you want 16-bit samples? Can you make this a constant at least?
>
I believe it should be 1000 as setBitrate is supposed to get its argument in
kbits/s (according to the wiki SoundSource page). Fixed the hardcoded bps
thing, that was left over from when I only did 16-bit. FLAC gives me whatever
size samples are in the file, then I shift them to 16-bit since that's what
Mixxx wants.
SSSndfile uses setBitrate(
> +inline int SoundSourceFLAC
> - same hardcoded 16 constant?
>
> - How do you deal with 24-bit FLAC files?
>
The sample value is shifted so that the sample fits in a 16-bit short.
getShift gives the number of places needed to shift the sample in order
to make the sample fit in 16 bits (so 24-bit samples are shifted right
abs(16-bps) times and <16-bit samples are shifted left 16-bps times),
which is the meaning of the constant (doxygen-style comment above the
method kinda explains this). shift() is then called on every sample to
normalize it to 16-bits using a shift amount/direction from getShift.
See ::flacWrite().
>
> parseHeader():
> - This is some undocumented bad news: parseHeader() has to be re-entrant. You
> can't use the existing file handle, because it can get called after an open()
> on an existing SoundSource object. This is the most important thing to fix.
I believe this is now fixed.
- 2421. By William Good
-
Made SSFLAC::parseHeader re-entrant. Commented/fixed some magic numbers.
- 2422. By William Good
-
Silenced some debugs
- 2423. By William Good
-
Merged from lp:mixxx
- 2424. By William Good
-
Switched SSFLAC to using TagLib for metadata parsing
- 2425. By William Good
-
Copied FLAC filename to QByteArray before passing to TagLib for safety
- 2426. By William Good
-
Merged from lp:mixxx
Preview Diff
1 | === modified file 'mixxx/build/depends.py' | |||
2 | --- mixxx/build/depends.py 2010-11-02 19:50:15 +0000 | |||
3 | +++ mixxx/build/depends.py 2010-11-14 02:21:19 +0000 | |||
4 | @@ -100,6 +100,18 @@ | |||
5 | 100 | def sources(self, build): | 100 | def sources(self, build): |
6 | 101 | return ['soundsourcesndfile.cpp'] | 101 | return ['soundsourcesndfile.cpp'] |
7 | 102 | 102 | ||
8 | 103 | class FLAC(Dependence): | ||
9 | 104 | def configure(self, build, conf): | ||
10 | 105 | if not conf.CheckHeader('FLAC/stream_decoder.h'): | ||
11 | 106 | raise Exception('Did not find libFLAC development headers, exiting!') | ||
12 | 107 | elif not conf.CheckLib(['libFLAC', 'FLAC']): | ||
13 | 108 | raise Exception('Did not find libFLAC development libraries, exiting!') | ||
14 | 109 | return | ||
15 | 110 | |||
16 | 111 | def sources(self, build): | ||
17 | 112 | return ['soundsourceflac.cpp',] | ||
18 | 113 | |||
19 | 114 | |||
20 | 103 | class Qt(Dependence): | 115 | class Qt(Dependence): |
21 | 104 | DEFAULT_QTDIRS = {'linux': '/usr/share/qt4', | 116 | DEFAULT_QTDIRS = {'linux': '/usr/share/qt4', |
22 | 105 | 'bsd': '/usr/local/lib/qt4', | 117 | 'bsd': '/usr/local/lib/qt4', |
23 | @@ -681,7 +693,7 @@ | |||
24 | 681 | 693 | ||
25 | 682 | def depends(self, build): | 694 | def depends(self, build): |
26 | 683 | return [SoundTouch, KissFFT, PortAudio, PortMIDI, Qt, | 695 | return [SoundTouch, KissFFT, PortAudio, PortMIDI, Qt, |
28 | 684 | FidLib, Mad, SndFile, OggVorbis, OpenGL, TagLib] | 696 | FidLib, Mad, SndFile, FLAC, OggVorbis, OpenGL, TagLib] |
29 | 685 | 697 | ||
30 | 686 | def post_dependency_check_configure(self, build, conf): | 698 | def post_dependency_check_configure(self, build, conf): |
31 | 687 | """Sets up additional things in the Environment that must happen | 699 | """Sets up additional things in the Environment that must happen |
32 | 688 | 700 | ||
33 | === added file 'mixxx/src/soundsourceflac.cpp' | |||
34 | --- mixxx/src/soundsourceflac.cpp 1970-01-01 00:00:00 +0000 | |||
35 | +++ mixxx/src/soundsourceflac.cpp 2010-11-14 02:21:19 +0000 | |||
36 | @@ -0,0 +1,374 @@ | |||
37 | 1 | /** | ||
38 | 2 | * \file soundsourceflac.cpp | ||
39 | 3 | * \author Bill Good <bkgood at gmail dot com> | ||
40 | 4 | * \date May 22, 2010 | ||
41 | 5 | */ | ||
42 | 6 | |||
43 | 7 | /*************************************************************************** | ||
44 | 8 | * * | ||
45 | 9 | * This program is free software; you can redistribute it and/or modify * | ||
46 | 10 | * it under the terms of the GNU General Public License as published by * | ||
47 | 11 | * the Free Software Foundation; either version 2 of the License, or * | ||
48 | 12 | * (at your option) any later version. * | ||
49 | 13 | * * | ||
50 | 14 | ***************************************************************************/ | ||
51 | 15 | |||
52 | 16 | #include <cstring> // memcpy | ||
53 | 17 | #include <QtDebug> | ||
54 | 18 | #include <taglib/flacfile.h> | ||
55 | 19 | |||
56 | 20 | #include "soundsourceflac.h" | ||
57 | 21 | |||
58 | 22 | SoundSourceFLAC::SoundSourceFLAC(QString filename) | ||
59 | 23 | : SoundSource(filename) | ||
60 | 24 | , m_file(filename) | ||
61 | 25 | , m_decoder(NULL) | ||
62 | 26 | , m_samples(0) | ||
63 | 27 | , m_bps(0) | ||
64 | 28 | , m_flacBuffer(NULL) | ||
65 | 29 | , m_flacBufferLength(0) | ||
66 | 30 | , m_leftoverBuffer(NULL) | ||
67 | 31 | , m_leftoverBufferLength(0) { | ||
68 | 32 | } | ||
69 | 33 | |||
70 | 34 | SoundSourceFLAC::~SoundSourceFLAC() { | ||
71 | 35 | if (m_flacBuffer != NULL) { | ||
72 | 36 | delete [] m_flacBuffer; | ||
73 | 37 | m_flacBuffer = NULL; | ||
74 | 38 | } | ||
75 | 39 | if (m_leftoverBuffer != NULL) { | ||
76 | 40 | delete [] m_leftoverBuffer; | ||
77 | 41 | m_leftoverBuffer = NULL; | ||
78 | 42 | } | ||
79 | 43 | if (m_decoder) { | ||
80 | 44 | FLAC__stream_decoder_finish(m_decoder); | ||
81 | 45 | FLAC__stream_decoder_delete(m_decoder); // frees memory | ||
82 | 46 | m_decoder = NULL; // probably not necessary | ||
83 | 47 | } | ||
84 | 48 | } | ||
85 | 49 | |||
86 | 50 | // soundsource overrides | ||
87 | 51 | int SoundSourceFLAC::open() { | ||
88 | 52 | m_file.open(QIODevice::ReadOnly); | ||
89 | 53 | m_decoder = FLAC__stream_decoder_new(); | ||
90 | 54 | if (m_decoder == NULL) { | ||
91 | 55 | qWarning() << "SSFLAC: decoder allocation failed!"; | ||
92 | 56 | return ERR; | ||
93 | 57 | } | ||
94 | 58 | if (!FLAC__stream_decoder_set_metadata_respond(m_decoder, | ||
95 | 59 | FLAC__METADATA_TYPE_VORBIS_COMMENT)) { | ||
96 | 60 | qWarning() << "SSFLAC: set metadata respond to vorbis comments failed"; | ||
97 | 61 | goto decoderError; | ||
98 | 62 | } | ||
99 | 63 | FLAC__StreamDecoderInitStatus initStatus; | ||
100 | 64 | initStatus = FLAC__stream_decoder_init_stream( | ||
101 | 65 | m_decoder, FLAC_read_cb, FLAC_seek_cb, FLAC_tell_cb, FLAC_length_cb, | ||
102 | 66 | FLAC_eof_cb, FLAC_write_cb, FLAC_metadata_cb, FLAC_error_cb, | ||
103 | 67 | (void*) this); | ||
104 | 68 | if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { | ||
105 | 69 | qWarning() << "SSFLAC: decoder init failed!"; | ||
106 | 70 | goto decoderError; | ||
107 | 71 | } | ||
108 | 72 | if (!FLAC__stream_decoder_process_until_end_of_metadata(m_decoder)) { | ||
109 | 73 | qWarning() << "SSFLAC: process to end of meta failed!"; | ||
110 | 74 | qWarning() << "SSFLAC: decoder state: " << FLAC__stream_decoder_get_state(m_decoder); | ||
111 | 75 | goto decoderError; | ||
112 | 76 | } // now number of samples etc. should be populated | ||
113 | 77 | if (m_flacBuffer == NULL) { | ||
114 | 78 | // we want 2 samples per frame, see ::flacWrite code -- bkgood | ||
115 | 79 | m_flacBuffer = new FLAC__int16[m_maxBlocksize * 2 /*m_iChannels*/]; | ||
116 | 80 | } | ||
117 | 81 | if (m_leftoverBuffer == NULL) { | ||
118 | 82 | m_leftoverBuffer = new FLAC__int16[m_maxBlocksize * 2 /*m_iChannels*/]; | ||
119 | 83 | } | ||
120 | 84 | // qDebug() << "SSFLAC: Total samples: " << m_samples; | ||
121 | 85 | // qDebug() << "SSFLAC: Sampling rate: " << m_iSampleRate << " Hz"; | ||
122 | 86 | // qDebug() << "SSFLAC: Channels: " << m_iChannels; | ||
123 | 87 | // qDebug() << "SSFLAC: BPS: " << m_bps; | ||
124 | 88 | return OK; | ||
125 | 89 | decoderError: | ||
126 | 90 | FLAC__stream_decoder_finish(m_decoder); | ||
127 | 91 | FLAC__stream_decoder_delete(m_decoder); | ||
128 | 92 | m_decoder = NULL; | ||
129 | 93 | return ERR; | ||
130 | 94 | } | ||
131 | 95 | |||
132 | 96 | long SoundSourceFLAC::seek(long filepos) { | ||
133 | 97 | if (!m_decoder) return 0; | ||
134 | 98 | FLAC__bool seekResult; | ||
135 | 99 | // important division here, filepos is in audio samples (i.e. shorts) | ||
136 | 100 | // but libflac expects a number in time samples. I _think_ this should | ||
137 | 101 | // be hard-coded at two because *2 is the assumption the caller makes | ||
138 | 102 | // -- bkgood | ||
139 | 103 | seekResult = FLAC__stream_decoder_seek_absolute(m_decoder, filepos / 2); | ||
140 | 104 | m_leftoverBufferLength = 0; // clear internal buffer since we moved | ||
141 | 105 | return filepos; | ||
142 | 106 | } | ||
143 | 107 | |||
144 | 108 | unsigned int SoundSourceFLAC::read(unsigned long size, const SAMPLE *destination) { | ||
145 | 109 | if (!m_decoder) return 0; | ||
146 | 110 | SAMPLE *destBuffer = const_cast<SAMPLE*>(destination); | ||
147 | 111 | unsigned int samplesWritten = 0; | ||
148 | 112 | unsigned int i = 0; | ||
149 | 113 | while (samplesWritten < size) { | ||
150 | 114 | // if our buffer from libflac is empty (either because we explicitly cleared | ||
151 | 115 | // it or because we've simply used all the samples), ask for a new buffer | ||
152 | 116 | if (m_flacBufferLength == 0) { | ||
153 | 117 | i = 0; | ||
154 | 118 | if (!FLAC__stream_decoder_process_single(m_decoder)) { | ||
155 | 119 | qWarning() << "SSFLAC: decoder_process_single returned false"; | ||
156 | 120 | break; | ||
157 | 121 | } else if (m_flacBufferLength == 0) { | ||
158 | 122 | // EOF | ||
159 | 123 | break; | ||
160 | 124 | } | ||
161 | 125 | } | ||
162 | 126 | destBuffer[samplesWritten++] = m_flacBuffer[i++]; | ||
163 | 127 | --m_flacBufferLength; | ||
164 | 128 | } | ||
165 | 129 | if (m_flacBufferLength != 0) { | ||
166 | 130 | memcpy(m_leftoverBuffer, &m_flacBuffer[i], | ||
167 | 131 | m_flacBufferLength * sizeof(m_flacBuffer[0])); // safe because leftoverBuffer | ||
168 | 132 | // is as long as flacbuffer | ||
169 | 133 | memcpy(m_flacBuffer, m_leftoverBuffer, | ||
170 | 134 | m_flacBufferLength * sizeof(m_leftoverBuffer[0])); | ||
171 | 135 | // this whole if block could go away if this just used a ring buffer but I'd | ||
172 | 136 | // rather do that after I've gotten off the inital happiness of getting this right, | ||
173 | 137 | // if I see SIGSEGV one more time I'll pop -- bkgood | ||
174 | 138 | } | ||
175 | 139 | return samplesWritten; | ||
176 | 140 | } | ||
177 | 141 | |||
178 | 142 | inline unsigned long SoundSourceFLAC::length() { | ||
179 | 143 | return m_samples * m_iChannels; | ||
180 | 144 | } | ||
181 | 145 | |||
182 | 146 | int SoundSourceFLAC::parseHeader() { | ||
183 | 147 | setType("flac"); | ||
184 | 148 | QByteArray fileName(m_file.fileName().toUtf8()); | ||
185 | 149 | TagLib::FLAC::File f(fileName.constData()); | ||
186 | 150 | bool result = processTaglibFile(f); | ||
187 | 151 | TagLib::ID3v2::Tag *id3v2 = f.ID3v2Tag(); | ||
188 | 152 | TagLib::Ogg::XiphComment *xiph = f.xiphComment(); | ||
189 | 153 | if (id3v2) { | ||
190 | 154 | processID3v2Tag(id3v2); | ||
191 | 155 | } | ||
192 | 156 | if (xiph) { | ||
193 | 157 | processXiphComment(xiph); | ||
194 | 158 | } | ||
195 | 159 | return result ? OK : ERR; | ||
196 | 160 | } | ||
197 | 161 | |||
198 | 162 | void SoundSourceFLAC::setTag(const QString &tag) { | ||
199 | 163 | QString key = tag.left(tag.indexOf("=")).toUpper(); | ||
200 | 164 | QString value = tag.right(tag.length() - tag.indexOf("=") - 1); | ||
201 | 165 | // standard here: http://www.xiph.org/vorbis/doc/v-comment.html | ||
202 | 166 | if (key == "ARTIST") { | ||
203 | 167 | m_sArtist = value; | ||
204 | 168 | } else if (key == "TITLE") { | ||
205 | 169 | m_sTitle = value; | ||
206 | 170 | } else if (key == "ALBUM") { | ||
207 | 171 | m_sAlbum = value; | ||
208 | 172 | } else if (key == "COMMENT") { // this doesn't exist in standard vorbis comments | ||
209 | 173 | m_sComment = value; | ||
210 | 174 | } else if (key == "DATE") { | ||
211 | 175 | m_sYear = value; | ||
212 | 176 | } else if (key == "GENRE") { | ||
213 | 177 | m_sGenre = value; | ||
214 | 178 | } else if (key == "TRACKNUMBER") { | ||
215 | 179 | m_sTrackNumber = value; | ||
216 | 180 | } else if (key == "BPM") { // this doesn't exist in standard vorbis comments | ||
217 | 181 | m_fBPM = value.toFloat(); | ||
218 | 182 | } | ||
219 | 183 | } | ||
220 | 184 | |||
221 | 185 | /** | ||
222 | 186 | * Shift needed to take our FLAC sample size to Mixxx's 16-bit samples. | ||
223 | 187 | * Shift right on negative, left on positive. | ||
224 | 188 | */ | ||
225 | 189 | inline int SoundSourceFLAC::getShift() const { | ||
226 | 190 | return 16 - m_bps; | ||
227 | 191 | } | ||
228 | 192 | |||
229 | 193 | /** | ||
230 | 194 | * Shift a sample from FLAC as necessary to get a 16-bit value. | ||
231 | 195 | */ | ||
232 | 196 | inline FLAC__int16 SoundSourceFLAC::shift(FLAC__int32 sample) const { | ||
233 | 197 | // this is how libsndfile does this operation and is wonderfully | ||
234 | 198 | // straightforward. Just shift the sample left or right so that | ||
235 | 199 | // it fits in a 16-bit short. -- bkgood | ||
236 | 200 | int shift = getShift(); | ||
237 | 201 | if (shift == 0) { | ||
238 | 202 | return sample; | ||
239 | 203 | } else if (shift < 0) { | ||
240 | 204 | return sample >> abs(shift); | ||
241 | 205 | } else { | ||
242 | 206 | return sample << shift; | ||
243 | 207 | } | ||
244 | 208 | }; | ||
245 | 209 | |||
246 | 210 | // static | ||
247 | 211 | QList<QString> SoundSourceFLAC::supportedFileExtensions() { | ||
248 | 212 | QList<QString> list; | ||
249 | 213 | list.push_back("flac"); | ||
250 | 214 | return list; | ||
251 | 215 | } | ||
252 | 216 | |||
253 | 217 | |||
254 | 218 | // flac callback methods | ||
255 | 219 | FLAC__StreamDecoderReadStatus SoundSourceFLAC::flacRead(FLAC__byte buffer[], size_t *bytes) { | ||
256 | 220 | *bytes = m_file.read((char*) buffer, *bytes); | ||
257 | 221 | if (*bytes > 0) { | ||
258 | 222 | return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; | ||
259 | 223 | } else if (*bytes == 0) { | ||
260 | 224 | return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; | ||
261 | 225 | } else { | ||
262 | 226 | return FLAC__STREAM_DECODER_READ_STATUS_ABORT; | ||
263 | 227 | } | ||
264 | 228 | } | ||
265 | 229 | |||
266 | 230 | FLAC__StreamDecoderSeekStatus SoundSourceFLAC::flacSeek(FLAC__uint64 offset) { | ||
267 | 231 | if (m_file.seek(offset)) { | ||
268 | 232 | return FLAC__STREAM_DECODER_SEEK_STATUS_OK; | ||
269 | 233 | } else { | ||
270 | 234 | return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; | ||
271 | 235 | } | ||
272 | 236 | } | ||
273 | 237 | |||
274 | 238 | FLAC__StreamDecoderTellStatus SoundSourceFLAC::flacTell(FLAC__uint64 *offset) { | ||
275 | 239 | if (m_file.isSequential()) { | ||
276 | 240 | return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; | ||
277 | 241 | } | ||
278 | 242 | *offset = m_file.pos(); | ||
279 | 243 | return FLAC__STREAM_DECODER_TELL_STATUS_OK; | ||
280 | 244 | } | ||
281 | 245 | |||
282 | 246 | FLAC__StreamDecoderLengthStatus SoundSourceFLAC::flacLength(FLAC__uint64 *length) { | ||
283 | 247 | if (m_file.isSequential()) { | ||
284 | 248 | return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; | ||
285 | 249 | } | ||
286 | 250 | *length = m_file.size(); | ||
287 | 251 | return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; | ||
288 | 252 | } | ||
289 | 253 | |||
290 | 254 | FLAC__bool SoundSourceFLAC::flacEOF() { | ||
291 | 255 | if (m_file.isSequential()) { | ||
292 | 256 | return false; | ||
293 | 257 | } | ||
294 | 258 | return m_file.atEnd(); | ||
295 | 259 | } | ||
296 | 260 | |||
297 | 261 | FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite(const FLAC__Frame *frame, | ||
298 | 262 | const FLAC__int32 *const buffer[]) { | ||
299 | 263 | unsigned int i; | ||
300 | 264 | m_flacBufferLength = 0; | ||
301 | 265 | if (frame->header.channels > 1) { | ||
302 | 266 | // stereo (or greater) | ||
303 | 267 | for (i = 0; i < frame->header.blocksize; ++i) { | ||
304 | 268 | m_flacBuffer[m_flacBufferLength++] = shift(buffer[0][i]); // left channel | ||
305 | 269 | m_flacBuffer[m_flacBufferLength++] = shift(buffer[1][i]); // right channel | ||
306 | 270 | } | ||
307 | 271 | } else { | ||
308 | 272 | // mono | ||
309 | 273 | for (i = 0; i < frame->header.blocksize; ++i) { | ||
310 | 274 | m_flacBuffer[m_flacBufferLength++] = shift(buffer[0][i]); // left channel | ||
311 | 275 | m_flacBuffer[m_flacBufferLength++] = shift(buffer[0][i]); // mono channel | ||
312 | 276 | } | ||
313 | 277 | } | ||
314 | 278 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; // can't anticipate any errors here | ||
315 | 279 | } | ||
316 | 280 | |||
317 | 281 | void SoundSourceFLAC::flacMetadata(const FLAC__StreamMetadata *metadata) { | ||
318 | 282 | switch (metadata->type) { | ||
319 | 283 | case FLAC__METADATA_TYPE_STREAMINFO: | ||
320 | 284 | m_samples = metadata->data.stream_info.total_samples; | ||
321 | 285 | m_iChannels = metadata->data.stream_info.channels; | ||
322 | 286 | m_iSampleRate = metadata->data.stream_info.sample_rate; | ||
323 | 287 | m_bps = metadata->data.stream_info.bits_per_sample; | ||
324 | 288 | m_minBlocksize = metadata->data.stream_info.min_blocksize; | ||
325 | 289 | m_maxBlocksize = metadata->data.stream_info.max_blocksize; | ||
326 | 290 | m_minFramesize = metadata->data.stream_info.min_framesize; | ||
327 | 291 | m_maxFramesize = metadata->data.stream_info.max_framesize; | ||
328 | 292 | // qDebug() << "FLAC file " << m_qFilename; | ||
329 | 293 | // qDebug() << m_iChannels << " @ " << m_iSampleRate << " Hz, " << m_samples | ||
330 | 294 | // << " total, " << m_bps << " bps"; | ||
331 | 295 | // qDebug() << "Blocksize in [" << m_minBlocksize << ", " << m_maxBlocksize | ||
332 | 296 | // << "], Framesize in [" << m_minFramesize << ", " << m_maxFramesize << "]"; | ||
333 | 297 | break; | ||
334 | 298 | case FLAC__METADATA_TYPE_VORBIS_COMMENT: | ||
335 | 299 | for (unsigned int i = 0; i < metadata->data.vorbis_comment.num_comments; ++i) { | ||
336 | 300 | m_tags.append(QString::fromUtf8( | ||
337 | 301 | (const char*) metadata->data.vorbis_comment.comments[i].entry, | ||
338 | 302 | metadata->data.vorbis_comment.comments[i].length)); | ||
339 | 303 | } | ||
340 | 304 | break; | ||
341 | 305 | default: | ||
342 | 306 | // don't care, and libflac won't send us any others anyway... | ||
343 | 307 | break; | ||
344 | 308 | } | ||
345 | 309 | } | ||
346 | 310 | |||
347 | 311 | void SoundSourceFLAC::flacError(FLAC__StreamDecoderErrorStatus status) { | ||
348 | 312 | QString error; | ||
349 | 313 | // not much can be done at this point -- luckly the decoder seems to be | ||
350 | 314 | // pretty forgiving -- bkgood | ||
351 | 315 | switch (status) { | ||
352 | 316 | case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: | ||
353 | 317 | error = "STREAM_DECODER_ERROR_STATUS_LOST_SYNC"; | ||
354 | 318 | break; | ||
355 | 319 | case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: | ||
356 | 320 | error = "STREAM_DECODER_ERROR_STATUS_BAD_HEADER"; | ||
357 | 321 | break; | ||
358 | 322 | case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: | ||
359 | 323 | error = "STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH"; | ||
360 | 324 | break; | ||
361 | 325 | case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: | ||
362 | 326 | error = "STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM"; | ||
363 | 327 | break; | ||
364 | 328 | } | ||
365 | 329 | qWarning() << "SSFLAC got error" << error << "from libFLAC for file" | ||
366 | 330 | << m_file.fileName(); | ||
367 | 331 | // not much else to do here... whatever function that initiated whatever | ||
368 | 332 | // decoder method resulted in this error will return an error, and the caller | ||
369 | 333 | // will bail. libFLAC docs say to not close the decoder here -- bkgood | ||
370 | 334 | } | ||
371 | 335 | |||
372 | 336 | // begin callbacks (have to be regular functions because normal libFLAC isn't C++-aware) | ||
373 | 337 | |||
374 | 338 | FLAC__StreamDecoderReadStatus FLAC_read_cb(const FLAC__StreamDecoder*, FLAC__byte buffer[], | ||
375 | 339 | size_t *bytes, void *client_data) { | ||
376 | 340 | return ((SoundSourceFLAC*) client_data)->flacRead(buffer, bytes); | ||
377 | 341 | } | ||
378 | 342 | |||
379 | 343 | FLAC__StreamDecoderSeekStatus FLAC_seek_cb(const FLAC__StreamDecoder*, | ||
380 | 344 | FLAC__uint64 absolute_byte_offset, void *client_data) { | ||
381 | 345 | return ((SoundSourceFLAC*) client_data)->flacSeek(absolute_byte_offset); | ||
382 | 346 | } | ||
383 | 347 | |||
384 | 348 | FLAC__StreamDecoderTellStatus FLAC_tell_cb(const FLAC__StreamDecoder*, | ||
385 | 349 | FLAC__uint64 *absolute_byte_offset, void *client_data) { | ||
386 | 350 | return ((SoundSourceFLAC*) client_data)->flacTell(absolute_byte_offset); | ||
387 | 351 | } | ||
388 | 352 | |||
389 | 353 | FLAC__StreamDecoderLengthStatus FLAC_length_cb(const FLAC__StreamDecoder*, | ||
390 | 354 | FLAC__uint64 *stream_length, void *client_data) { | ||
391 | 355 | return ((SoundSourceFLAC*) client_data)->flacLength(stream_length); | ||
392 | 356 | } | ||
393 | 357 | |||
394 | 358 | FLAC__bool FLAC_eof_cb(const FLAC__StreamDecoder*, void *client_data) { | ||
395 | 359 | return ((SoundSourceFLAC*) client_data)->flacEOF(); | ||
396 | 360 | } | ||
397 | 361 | |||
398 | 362 | FLAC__StreamDecoderWriteStatus FLAC_write_cb(const FLAC__StreamDecoder*, const FLAC__Frame *frame, | ||
399 | 363 | const FLAC__int32 *const buffer[], void *client_data) { | ||
400 | 364 | return ((SoundSourceFLAC*) client_data)->flacWrite(frame, buffer); | ||
401 | 365 | } | ||
402 | 366 | |||
403 | 367 | void FLAC_metadata_cb(const FLAC__StreamDecoder*, const FLAC__StreamMetadata *metadata, void *client_data) { | ||
404 | 368 | ((SoundSourceFLAC*) client_data)->flacMetadata(metadata); | ||
405 | 369 | } | ||
406 | 370 | |||
407 | 371 | void FLAC_error_cb(const FLAC__StreamDecoder*, FLAC__StreamDecoderErrorStatus status, void *client_data) { | ||
408 | 372 | ((SoundSourceFLAC*) client_data)->flacError(status); | ||
409 | 373 | } | ||
410 | 374 | // end callbacks | ||
411 | 0 | 375 | ||
412 | === added file 'mixxx/src/soundsourceflac.h' | |||
413 | --- mixxx/src/soundsourceflac.h 1970-01-01 00:00:00 +0000 | |||
414 | +++ mixxx/src/soundsourceflac.h 2010-11-14 02:21:19 +0000 | |||
415 | @@ -0,0 +1,87 @@ | |||
416 | 1 | /** | ||
417 | 2 | * \file sourdsourceflac.h | ||
418 | 3 | * \class SoundSourceFLAC | ||
419 | 4 | * \brief Decodes FLAC files using libFLAC for Mixxx. | ||
420 | 5 | * \author Bill Good <bkgood at gmail dot com> | ||
421 | 6 | * \date May 22, 2010 | ||
422 | 7 | */ | ||
423 | 8 | |||
424 | 9 | /*************************************************************************** | ||
425 | 10 | * * | ||
426 | 11 | * This program is free software; you can redistribute it and/or modify * | ||
427 | 12 | * it under the terms of the GNU General Public License as published by * | ||
428 | 13 | * the Free Software Foundation; either version 2 of the License, or * | ||
429 | 14 | * (at your option) any later version. * | ||
430 | 15 | * * | ||
431 | 16 | ***************************************************************************/ | ||
432 | 17 | |||
433 | 18 | #ifndef SOUNDSOURCEFLAC_H | ||
434 | 19 | #define SOUNDSOURCEFLAC_H | ||
435 | 20 | |||
436 | 21 | #include <QFile> | ||
437 | 22 | #include <QString> | ||
438 | 23 | #include <FLAC/stream_decoder.h> | ||
439 | 24 | |||
440 | 25 | #include "defs.h" | ||
441 | 26 | #include "soundsource.h" | ||
442 | 27 | |||
443 | 28 | class TrackInfoObject; | ||
444 | 29 | |||
445 | 30 | class SoundSourceFLAC : public SoundSource { | ||
446 | 31 | public: | ||
447 | 32 | SoundSourceFLAC(QString filename); | ||
448 | 33 | ~SoundSourceFLAC(); | ||
449 | 34 | int open(); | ||
450 | 35 | long seek(long filepos); | ||
451 | 36 | unsigned read(unsigned long size, const SAMPLE *buffer); | ||
452 | 37 | inline long unsigned length(); | ||
453 | 38 | int parseHeader(); | ||
454 | 39 | static QList<QString> supportedFileExtensions(); | ||
455 | 40 | // callback methods | ||
456 | 41 | FLAC__StreamDecoderReadStatus flacRead(FLAC__byte buffer[], size_t *bytes); | ||
457 | 42 | FLAC__StreamDecoderSeekStatus flacSeek(FLAC__uint64 offset); | ||
458 | 43 | FLAC__StreamDecoderTellStatus flacTell(FLAC__uint64 *offset); | ||
459 | 44 | FLAC__StreamDecoderLengthStatus flacLength(FLAC__uint64 *length); | ||
460 | 45 | FLAC__bool flacEOF(); | ||
461 | 46 | FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__Frame *frame, const FLAC__int32 *const buffer[]); | ||
462 | 47 | void flacMetadata(const FLAC__StreamMetadata *metadata); | ||
463 | 48 | void flacError(FLAC__StreamDecoderErrorStatus status); | ||
464 | 49 | private: | ||
465 | 50 | void setTag(const QString &tag); | ||
466 | 51 | // these next two are inline but are defined in the cpp file because | ||
467 | 52 | // they should only be used there -- bkgood | ||
468 | 53 | inline int getShift() const; | ||
469 | 54 | inline FLAC__int16 shift(const FLAC__int32 sample) const; | ||
470 | 55 | QFile m_file; | ||
471 | 56 | FLAC__StreamDecoder *m_decoder; | ||
472 | 57 | FLAC__StreamMetadata_StreamInfo *m_streamInfo; | ||
473 | 58 | unsigned int m_samples; // total number of samples | ||
474 | 59 | unsigned int m_bps; // bits per sample | ||
475 | 60 | // misc bits about the flac format: | ||
476 | 61 | // flac encodes from and decodes to LPCM in blocks, each block is made up of | ||
477 | 62 | // subblocks (one for each chan) | ||
478 | 63 | // flac stores in 'frames', each of which has a header and a certain number | ||
479 | 64 | // of subframes (one for each channel) | ||
480 | 65 | unsigned int m_minBlocksize; // in time samples (audio samples = time samples * chanCount) | ||
481 | 66 | unsigned int m_maxBlocksize; | ||
482 | 67 | unsigned int m_minFramesize; | ||
483 | 68 | unsigned int m_maxFramesize; | ||
484 | 69 | FLAC__int16 *m_flacBuffer; // buffer for the write callback to write a single frame's samples | ||
485 | 70 | unsigned int m_flacBufferLength; | ||
486 | 71 | FLAC__int16 *m_leftoverBuffer; // buffer to place any samples which haven't been used | ||
487 | 72 | // at the end of a read call | ||
488 | 73 | unsigned int m_leftoverBufferLength; | ||
489 | 74 | QList<QString> m_tags; // stored in vorbis comment format as received, ex. "ARTIST=blah" | ||
490 | 75 | }; | ||
491 | 76 | |||
492 | 77 | // callbacks for libFLAC | ||
493 | 78 | FLAC__StreamDecoderReadStatus FLAC_read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); | ||
494 | 79 | FLAC__StreamDecoderSeekStatus FLAC_seek_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data); | ||
495 | 80 | FLAC__StreamDecoderTellStatus FLAC_tell_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); | ||
496 | 81 | FLAC__StreamDecoderLengthStatus FLAC_length_cb(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data); | ||
497 | 82 | FLAC__bool FLAC_eof_cb(const FLAC__StreamDecoder *decoder, void *client_data); | ||
498 | 83 | FLAC__StreamDecoderWriteStatus FLAC_write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data); | ||
499 | 84 | void FLAC_metadata_cb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); | ||
500 | 85 | void FLAC_error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); | ||
501 | 86 | |||
502 | 87 | #endif // ifndef SOUNDSOURCEFLAC_H | ||
503 | 0 | 88 | ||
504 | === modified file 'mixxx/src/soundsourceproxy.cpp' | |||
505 | --- mixxx/src/soundsourceproxy.cpp 2010-10-14 04:25:11 +0000 | |||
506 | +++ mixxx/src/soundsourceproxy.cpp 2010-11-14 02:21:19 +0000 | |||
507 | @@ -26,6 +26,7 @@ | |||
508 | 26 | #ifdef __FFMPEGFILE__ | 26 | #ifdef __FFMPEGFILE__ |
509 | 27 | #include "soundsourceffmpeg.h" | 27 | #include "soundsourceffmpeg.h" |
510 | 28 | #endif | 28 | #endif |
511 | 29 | #include "soundsourceflac.h" | ||
512 | 29 | 30 | ||
513 | 30 | #include <QLibrary> | 31 | #include <QLibrary> |
514 | 31 | #include <QMutexLocker> | 32 | #include <QMutexLocker> |
515 | @@ -117,6 +118,8 @@ | |||
516 | 117 | return new SoundSourceMp3(qFilename); | 118 | return new SoundSourceMp3(qFilename); |
517 | 118 | } else if (SoundSourceOggVorbis::supportedFileExtensions().contains(extension)) { | 119 | } else if (SoundSourceOggVorbis::supportedFileExtensions().contains(extension)) { |
518 | 119 | return new SoundSourceOggVorbis(qFilename); | 120 | return new SoundSourceOggVorbis(qFilename); |
519 | 121 | } else if (SoundSourceFLAC::supportedFileExtensions().contains(extension)) { | ||
520 | 122 | return new SoundSourceFLAC(qFilename); | ||
521 | 120 | } else if (m_extensionsSupportedByPlugins.contains(extension)) { | 123 | } else if (m_extensionsSupportedByPlugins.contains(extension)) { |
522 | 121 | getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension); | 124 | getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension); |
523 | 122 | if (getter) | 125 | if (getter) |
Tested and works with all the FLAC files generated by the file format script in src/test.