Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 1 | /* FLAC input plugin for Winamp3
|
Josh Coalson | 305ae2e | 2002-01-26 17:36:39 +0000 | [diff] [blame] | 2 | * Copyright (C) 2000,2001,2002 Josh Coalson
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 3 | *
|
| 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"
|
| 50 | extern "C" {
|
Josh Coalson | 5b72e17 | 2002-05-04 17:47:20 +0000 | [diff] [blame] | 51 | #include "FLAC/metadata.h"
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 52 | };
|
| 53 |
|
| 54 |
|
| 55 | struct 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 |
|
| 68 | static bool get_id3v1_tag_(const char *filename, id3v1_struct *tag);
|
| 69 |
|
| 70 |
|
| 71 | FlacPcm::FlacPcm():
|
| 72 | needs_seek(false),
|
| 73 | seek_sample(0),
|
| 74 | samples_in_reservoir(0),
|
| 75 | abort_flag(false),
|
| 76 | reader(0),
|
| 77 | decoder(0)
|
| 78 | {
|
| 79 | }
|
| 80 |
|
| 81 | FlacPcm::~FlacPcm()
|
| 82 | {
|
| 83 | cleanup();
|
| 84 | }
|
| 85 |
|
| 86 | int 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 Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 92 | if(!FLAC__metadata_get_streaminfo(infos->getFilename(), &streaminfo))
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 93 | return 1;
|
| 94 |
|
| 95 | id3v1_struct tag;
|
| 96 |
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 97 | 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 Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 102 | 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 Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 103 | 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 |
|
| 112 | int 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 Coalson | 9e37d05 | 2002-06-18 16:08:36 +0000 | [diff] [blame^] | 155 | 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 Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 158 | break;
|
Josh Coalson | 9e37d05 | 2002-06-18 16:08:36 +0000 | [diff] [blame^] | 159 | }
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 160 | }
|
| 161 |
|
| 162 | if(samples_in_reservoir == 0) {
|
| 163 | eof = true;
|
| 164 | }
|
| 165 | else {
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 166 | const unsigned channels = streaminfo.data.stream_info.channels;
|
| 167 | const unsigned bits_per_sample = streaminfo.data.stream_info.bits_per_sample;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 168 | const unsigned bytes_per_sample = (bits_per_sample+7)/8;
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 169 | const unsigned sample_rate = streaminfo.data.stream_info.sample_rate;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 170 | 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 |
|
| 196 | int FlacPcm::corecb_onSeeked(int newpos)
|
| 197 | {
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 198 | if(streaminfo.data.stream_info.total_samples == 0 || newpos < 0)
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 199 | return 1;
|
| 200 |
|
| 201 | needs_seek = true;
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 202 | seek_sample = (FLAC__uint64)((double)newpos / (double)lengthInMsec() * (double)(FLAC__int64)streaminfo.data.stream_info.total_samples);
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 203 | return 0;
|
| 204 | }
|
| 205 |
|
| 206 | void FlacPcm::cleanup()
|
| 207 | {
|
| 208 | if(decoder) {
|
Josh Coalson | 25da0d9 | 2001-12-04 01:23:50 +0000 | [diff] [blame] | 209 | FLAC__seekable_stream_decoder_finish(decoder);
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 210 | FLAC__seekable_stream_decoder_delete(decoder);
|
| 211 | decoder = 0;
|
| 212 | }
|
| 213 | }
|
| 214 | FLAC__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 |
|
| 221 | FLAC__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 |
|
| 230 | FLAC__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 |
|
| 242 | FLAC__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 |
|
| 254 | FLAC__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 Coalson | 878ba45 | 2002-06-13 07:02:10 +0000 | [diff] [blame] | 260 | FLAC__StreamDecoderWriteStatus FlacPcm::writeCallback_(const FLAC__SeekableStreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 261 | {
|
| 262 | FlacPcm *instance = (FlacPcm*)client_data;
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 263 | const unsigned bps = instance->streaminfo.data.stream_info.bits_per_sample, channels = instance->streaminfo.data.stream_info.channels, wide_samples = frame->header.blocksize;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 264 | unsigned wide_sample, sample, channel;
|
| 265 |
|
| 266 | (void)decoder;
|
| 267 |
|
| 268 | if(instance->abort_flag)
|
Josh Coalson | a2c7814 | 2002-06-04 05:44:32 +0000 | [diff] [blame] | 269 | return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 270 |
|
| 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 Coalson | a2c7814 | 2002-06-04 05:44:32 +0000 | [diff] [blame] | 277 | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 278 | }
|
| 279 |
|
Josh Coalson | cc68251 | 2002-06-08 04:53:42 +0000 | [diff] [blame] | 280 | void FlacPcm::metadataCallback_(const FLAC__SeekableStreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 281 | {
|
| 282 | FlacPcm *instance = (FlacPcm*)client_data;
|
| 283 | (void)decoder;
|
| 284 | if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
Josh Coalson | 878ba45 | 2002-06-13 07:02:10 +0000 | [diff] [blame] | 285 | instance->streaminfo = *metadata;
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 286 |
|
Josh Coalson | 3b678d5 | 2002-06-10 18:25:43 +0000 | [diff] [blame] | 287 | if(instance->streaminfo.data.stream_info.bits_per_sample != 16) {
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 288 | //@@@ 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 |
|
| 295 | void FlacPcm::errorCallback_(const FLAC__SeekableStreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
|
| 296 | {
|
| 297 | FlacPcm *instance = (FlacPcm*)client_data;
|
| 298 | (void)decoder;
|
Josh Coalson | a2c7814 | 2002-06-04 05:44:32 +0000 | [diff] [blame] | 299 | if(status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC)
|
Josh Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 300 | instance->abort_flag = true;
|
| 301 | }
|
| 302 |
|
| 303 | /***********************************************************************
|
| 304 | * local routines
|
| 305 | **********************************************************************/
|
| 306 |
|
| 307 | bool 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 Coalson | 53ad911 | 2001-11-08 23:54:10 +0000 | [diff] [blame] | 312 |
|
| 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 | }
|