blob: 88c73b60bbc918b1beaf7856cbf95aa8e85b737f [file] [log] [blame]
Josh Coalson53ad9112001-11-08 23:54:10 +00001/* FLAC input plugin for Winamp3
Josh Coalson305ae2e2002-01-26 17:36:39 +00002 * Copyright (C) 2000,2001,2002 Josh Coalson
Josh Coalson53ad9112001-11-08 23:54:10 +00003 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 *
18 * NOTE: this code is derived from the 'rawpcm' example by
19 * Nullsoft; the original license for the 'rawpcm' example follows.
20 */
21/*
22
23 Nullsoft WASABI Source File License
24
25 Copyright 1999-2001 Nullsoft, Inc.
26
27 This software is provided 'as-is', without any express or implied
28 warranty. In no event will the authors be held liable for any damages
29 arising from the use of this software.
30
31 Permission is granted to anyone to use this software for any purpose,
32 including commercial applications, and to alter it and redistribute it
33 freely, subject to the following restrictions:
34
35 1. The origin of this software must not be misrepresented; you must not
36 claim that you wrote the original software. If you use this software
37 in a product, an acknowledgment in the product documentation would be
38 appreciated but is not required.
39 2. Altered source versions must be plainly marked as such, and must not be
40 misrepresented as being the original software.
41 3. This notice may not be removed or altered from any source distribution.
42
43
44 Brennan Underwood
45 brennan@nullsoft.com
46
47*/
48
49#include "flacpcm.h"
50extern "C" {
Josh Coalson5b72e172002-05-04 17:47:20 +000051#include "FLAC/metadata.h"
Josh Coalson53ad9112001-11-08 23:54:10 +000052};
53
54
55struct id3v1_struct {
56 FLAC__byte raw[128];
57 char title[31];
58 char artist[31];
59 char album[31];
60 char comment[31];
61 unsigned year;
62 unsigned track; /* may be 0 if v1 (not v1.1) tag */
63 unsigned genre;
64 char description[1024]; /* the formatted description passed to the gui */
65};
66
67
68static bool get_id3v1_tag_(const char *filename, id3v1_struct *tag);
69
70
71FlacPcm::FlacPcm():
72needs_seek(false),
73seek_sample(0),
74samples_in_reservoir(0),
75abort_flag(false),
76reader(0),
77decoder(0)
78{
79}
80
81FlacPcm::~FlacPcm()
82{
83 cleanup();
84}
85
86int FlacPcm::getInfos(MediaInfo *infos)
87{
88 reader = infos->getReader();
89 if(!reader) return 0;
90
91 //@@@ to be really "clean" we should go through the reader instead of directly to the file...
Josh Coalson3b678d52002-06-10 18:25:43 +000092 if(!FLAC__metadata_get_streaminfo(infos->getFilename(), &streaminfo))
Josh Coalson53ad9112001-11-08 23:54:10 +000093 return 1;
94
95 id3v1_struct tag;
96
Josh Coalson53ad9112001-11-08 23:54:10 +000097 bool has_tag = get_id3v1_tag_(infos->getFilename(), &tag);
98
99 infos->setLength(lengthInMsec());
100 //@@@ infos->setTitle(Std::filename(infos->getFilename()));
101 infos->setTitle(tag.description);
Josh Coalson3b678d52002-06-10 18:25:43 +0000102 infos->setInfo(StringPrintf("FLAC:<%ihz:%ibps:%dch>", streaminfo.data.stream_info.sample_rate, streaminfo.data.stream_info.bits_per_sample, streaminfo.data.stream_info.channels)); //@@@ fix later
Josh Coalson53ad9112001-11-08 23:54:10 +0000103 if(has_tag) {
104 infos->setData("Title", tag.title);
105 infos->setData("Artist", tag.artist);
106 infos->setData("Album", tag.album);
107 }
108
109 return 0;
110}
111
112int FlacPcm::processData(MediaInfo *infos, ChunkList *chunk_list, bool *killswitch)
113{
114 reader = infos->getReader();
115 if(reader == 0)
116 return 0;
117
118 if(decoder == 0) {
119 decoder = FLAC__seekable_stream_decoder_new();
120 if(decoder == 0)
121 return 0;
122 FLAC__seekable_stream_decoder_set_md5_checking(decoder, false);
123 FLAC__seekable_stream_decoder_set_read_callback(decoder, readCallback_);
124 FLAC__seekable_stream_decoder_set_seek_callback(decoder, seekCallback_);
125 FLAC__seekable_stream_decoder_set_tell_callback(decoder, tellCallback_);
126 FLAC__seekable_stream_decoder_set_length_callback(decoder, lengthCallback_);
127 FLAC__seekable_stream_decoder_set_eof_callback(decoder, eofCallback_);
128 FLAC__seekable_stream_decoder_set_write_callback(decoder, writeCallback_);
129 FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadataCallback_);
130 FLAC__seekable_stream_decoder_set_error_callback(decoder, errorCallback_);
131 FLAC__seekable_stream_decoder_set_client_data(decoder, this);
132
133 if(FLAC__seekable_stream_decoder_init(decoder) != FLAC__SEEKABLE_STREAM_DECODER_OK) {
134 cleanup();
135 return 0;
136 }
137 if(!FLAC__seekable_stream_decoder_process_metadata(decoder)) {
138 cleanup();
139 return 0;
140 }
141 }
142
143 if(needs_seek) {
144 FLAC__seekable_stream_decoder_seek_absolute(decoder, seek_sample);
145 needs_seek = false;
146 }
147
148 bool eof = false;
149
150 while(samples_in_reservoir < 576) {
151 if(FLAC__seekable_stream_decoder_get_state(decoder) == FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM) {
152 eof = true;
153 break;
154 }
Josh Coalson9e37d052002-06-18 16:08:36 +0000155 else if(!FLAC__seekable_stream_decoder_process_one_frame(decoder)) {
156 //@@@ how to do this? MessageBox(mod_.hMainWindow, FLAC__FileDecoderStateString[FLAC__file_decoder_get_state(decoder_)], "READ ERROR processing frame", 0);
157 eof = true;
Josh Coalson53ad9112001-11-08 23:54:10 +0000158 break;
Josh Coalson9e37d052002-06-18 16:08:36 +0000159 }
Josh Coalson53ad9112001-11-08 23:54:10 +0000160 }
161
162 if(samples_in_reservoir == 0) {
163 eof = true;
164 }
165 else {
Josh Coalson3b678d52002-06-10 18:25:43 +0000166 const unsigned channels = streaminfo.data.stream_info.channels;
167 const unsigned bits_per_sample = streaminfo.data.stream_info.bits_per_sample;
Josh Coalson53ad9112001-11-08 23:54:10 +0000168 const unsigned bytes_per_sample = (bits_per_sample+7)/8;
Josh Coalson3b678d52002-06-10 18:25:43 +0000169 const unsigned sample_rate = streaminfo.data.stream_info.sample_rate;
Josh Coalson53ad9112001-11-08 23:54:10 +0000170 unsigned i, n = min(samples_in_reservoir, 576), delta;
171 signed short *ssbuffer = (signed short*)output;
172
173 for(i = 0; i < n*channels; i++)
174 ssbuffer[i] = reservoir[i];
175 delta = i;
176 for( ; i < samples_in_reservoir*channels; i++)
177 reservoir[i-delta] = reservoir[i];
178 samples_in_reservoir -= n;
179
180 const int bytes = n * channels * bytes_per_sample;
181
182 ChunkInfosI *ci=new ChunkInfosI();
183 ci->addInfo("srate", sample_rate);
184 ci->addInfo("bps", bits_per_sample);
185 ci->addInfo("nch", channels);
186
187 chunk_list->setChunk("PCM", output, bytes, ci);
188 }
189
190 if(eof)
191 return 0;
192
193 return 1;
194}
195
196int FlacPcm::corecb_onSeeked(int newpos)
197{
Josh Coalson3b678d52002-06-10 18:25:43 +0000198 if(streaminfo.data.stream_info.total_samples == 0 || newpos < 0)
Josh Coalson53ad9112001-11-08 23:54:10 +0000199 return 1;
200
201 needs_seek = true;
Josh Coalson3b678d52002-06-10 18:25:43 +0000202 seek_sample = (FLAC__uint64)((double)newpos / (double)lengthInMsec() * (double)(FLAC__int64)streaminfo.data.stream_info.total_samples);
Josh Coalson53ad9112001-11-08 23:54:10 +0000203 return 0;
204}
205
206void FlacPcm::cleanup()
207{
208 if(decoder) {
Josh Coalson25da0d92001-12-04 01:23:50 +0000209 FLAC__seekable_stream_decoder_finish(decoder);
Josh Coalson53ad9112001-11-08 23:54:10 +0000210 FLAC__seekable_stream_decoder_delete(decoder);
211 decoder = 0;
212 }
213}
214FLAC__SeekableStreamDecoderReadStatus FlacPcm::readCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], unsigned *bytes, void *client_data)
215{
216 FlacPcm *instance = (FlacPcm*)client_data;
217 *bytes = instance->reader->read((char*)buffer, *bytes);
218 return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
219}
220
221FLAC__SeekableStreamDecoderSeekStatus FlacPcm::seekCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data)
222{
223 FlacPcm *instance = (FlacPcm*)client_data;
224 if(instance->reader->seek((int)absolute_byte_offset) < 0)
225 return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
226 else
227 return FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
228}
229
230FLAC__SeekableStreamDecoderTellStatus FlacPcm::tellCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data)
231{
232 FlacPcm *instance = (FlacPcm*)client_data;
233 int pos = instance->reader->getPos();
234 if(pos < 0)
235 return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR;
236 else {
237 *absolute_byte_offset = pos;
238 return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
239 }
240}
241
242FLAC__SeekableStreamDecoderLengthStatus FlacPcm::lengthCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data)
243{
244 FlacPcm *instance = (FlacPcm*)client_data;
245 int length = instance->reader->getPos();
246 if(length < 0)
247 return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR;
248 else {
249 *stream_length = length;
250 return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
251 }
252}
253
254FLAC__bool FlacPcm::eofCallback_(const FLAC__SeekableStreamDecoder *decoder, void *client_data)
255{
256 FlacPcm *instance = (FlacPcm*)client_data;
257 return instance->reader->getPos() == instance->reader->getLength();
258}
259
Josh Coalson878ba452002-06-13 07:02:10 +0000260FLAC__StreamDecoderWriteStatus FlacPcm::writeCallback_(const FLAC__SeekableStreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
Josh Coalson53ad9112001-11-08 23:54:10 +0000261{
262 FlacPcm *instance = (FlacPcm*)client_data;
Josh Coalson3b678d52002-06-10 18:25:43 +0000263 const unsigned bps = instance->streaminfo.data.stream_info.bits_per_sample, channels = instance->streaminfo.data.stream_info.channels, wide_samples = frame->header.blocksize;
Josh Coalson53ad9112001-11-08 23:54:10 +0000264 unsigned wide_sample, sample, channel;
265
266 (void)decoder;
267
268 if(instance->abort_flag)
Josh Coalsona2c78142002-06-04 05:44:32 +0000269 return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
Josh Coalson53ad9112001-11-08 23:54:10 +0000270
271 for(sample = instance->samples_in_reservoir*channels, wide_sample = 0; wide_sample < wide_samples; wide_sample++)
272 for(channel = 0; channel < channels; channel++, sample++)
273 instance->reservoir[sample] = (FLAC__int16)buffer[channel][wide_sample];
274
275 instance->samples_in_reservoir += wide_samples;
276
Josh Coalsona2c78142002-06-04 05:44:32 +0000277 return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
Josh Coalson53ad9112001-11-08 23:54:10 +0000278}
279
Josh Coalsoncc682512002-06-08 04:53:42 +0000280void FlacPcm::metadataCallback_(const FLAC__SeekableStreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
Josh Coalson53ad9112001-11-08 23:54:10 +0000281{
282 FlacPcm *instance = (FlacPcm*)client_data;
283 (void)decoder;
284 if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
Josh Coalson878ba452002-06-13 07:02:10 +0000285 instance->streaminfo = *metadata;
Josh Coalson53ad9112001-11-08 23:54:10 +0000286
Josh Coalson3b678d52002-06-10 18:25:43 +0000287 if(instance->streaminfo.data.stream_info.bits_per_sample != 16) {
Josh Coalson53ad9112001-11-08 23:54:10 +0000288 //@@@ how to do this? MessageBox(mod.hMainWindow, "ERROR: plugin can only handle 16-bit samples\n", "ERROR: plugin can only handle 16-bit samples", 0);
289 instance->abort_flag = true;
290 return;
291 }
292 }
293}
294
295void FlacPcm::errorCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
296{
297 FlacPcm *instance = (FlacPcm*)client_data;
298 (void)decoder;
Josh Coalsona2c78142002-06-04 05:44:32 +0000299 if(status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
Josh Coalson53ad9112001-11-08 23:54:10 +0000300 instance->abort_flag = true;
301}
302
303/***********************************************************************
304 * local routines
305 **********************************************************************/
306
307bool get_id3v1_tag_(const char *filename, id3v1_struct *tag)
308{
309 const char *temp;
310 FILE *f = fopen(filename, "rb");
311 memset(tag, 0, sizeof(id3v1_struct));
Josh Coalson53ad9112001-11-08 23:54:10 +0000312
313 /* set the title and description to the filename by default */
314 temp = strrchr(filename, '/');
315 if(!temp)
316 temp = filename;
317 else
318 temp++;
319 strcpy(tag->description, temp);
320 *strrchr(tag->description, '.') = '\0';
321 strncpy(tag->title, tag->description, 30); tag->title[30] = '\0';
322
323 if(0 == f)
324 return false;
325 if(-1 == fseek(f, -128, SEEK_END)) {
326 fclose(f);
327 return false;
328 }
329 if(fread(tag->raw, 1, 128, f) < 128) {
330 fclose(f);
331 return false;
332 }
333 fclose(f);
334 if(strncmp((const char*)tag->raw, "TAG", 3))
335 return false;
336 else {
337 char year_str[5];
338
339 memcpy(tag->title, tag->raw+3, 30);
340 memcpy(tag->artist, tag->raw+33, 30);
341 memcpy(tag->album, tag->raw+63, 30);
342 memcpy(year_str, tag->raw+93, 4); year_str[4] = '\0'; tag->year = atoi(year_str);
343 memcpy(tag->comment, tag->raw+97, 30);
344 tag->genre = (unsigned)((FLAC__byte)tag->raw[127]);
345 tag->track = (unsigned)((FLAC__byte)tag->raw[126]);
346
347 sprintf(tag->description, "%s - %s", tag->artist, tag->title);
348
349 return true;
350 }
351}