_flac_common.c 11.1 KB
Newer Older
1 2 3
/*
 * Copyright (C) 2003-2009 The Music Player Daemon Project
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17 18 19 20 21
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * Common data structures and functions used by FLAC and OggFLAC
22 23 24 25
 */

#include "_flac_common.h"

26 27
#include <glib.h>

28 29
#include <assert.h>

Max Kellermann's avatar
Max Kellermann committed
30 31 32
void
flac_data_init(struct flac_data *data, struct decoder * decoder,
	       struct input_stream *input_stream)
33 34 35
{
	data->time = 0;
	data->position = 0;
Max Kellermann's avatar
Max Kellermann committed
36
	data->bit_rate = 0;
Max Kellermann's avatar
Max Kellermann committed
37
	data->decoder = decoder;
Max Kellermann's avatar
Max Kellermann committed
38 39
	data->input_stream = input_stream;
	data->replay_gain_info = NULL;
40 41 42
	data->tag = NULL;
}

43
static bool
Max Kellermann's avatar
Max Kellermann committed
44 45
flac_find_float_comment(const FLAC__StreamMetadata *block,
			const char *cmnt, float *fl)
46
{
Avuton Olrich's avatar
Avuton Olrich committed
47 48
	int offset =
	    FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt);
49

Avuton Olrich's avatar
Avuton Olrich committed
50 51
	if (offset >= 0) {
		size_t pos = strlen(cmnt) + 1;	/* 1 is for '=' */
52
		int len = block->data.vorbis_comment.comments[offset].length
Avuton Olrich's avatar
Avuton Olrich committed
53 54
		    - pos;
		if (len > 0) {
55
			unsigned char tmp;
Max Kellermann's avatar
Max Kellermann committed
56 57 58 59
			unsigned char *p = &(block->data.vorbis_comment.
					     comments[offset].entry[pos]);
			tmp = p[len];
			p[len] = '\0';
Max Kellermann's avatar
Max Kellermann committed
60
			*fl = (float)atof((char *)p);
Max Kellermann's avatar
Max Kellermann committed
61
			p[len] = tmp;
Avuton Olrich's avatar
Avuton Olrich committed
62

63
			return true;
64 65 66
		}
	}

67
	return false;
68 69 70
}

/* replaygain stuff by AliasMrJones */
Max Kellermann's avatar
Max Kellermann committed
71 72 73
static void
flac_parse_replay_gain(const FLAC__StreamMetadata *block,
		       struct flac_data *data)
Avuton Olrich's avatar
Avuton Olrich committed
74
{
75
	bool found;
76

Max Kellermann's avatar
Max Kellermann committed
77 78
	if (data->replay_gain_info)
		replay_gain_info_free(data->replay_gain_info);
79

Max Kellermann's avatar
Max Kellermann committed
80
	data->replay_gain_info = replay_gain_info_new();
81

82 83 84 85 86 87 88 89
	found = flac_find_float_comment(block, "replaygain_album_gain",
					&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain) ||
		flac_find_float_comment(block, "replaygain_album_peak",
					&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak) ||
		flac_find_float_comment(block, "replaygain_track_gain",
					&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain) ||
		flac_find_float_comment(block, "replaygain_track_peak",
					&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak);
90 91

	if (!found) {
Max Kellermann's avatar
Max Kellermann committed
92 93
		replay_gain_info_free(data->replay_gain_info);
		data->replay_gain_info = NULL;
94 95 96
	}
}

97 98 99 100 101 102
/**
 * Checks if the specified name matches the entry's name, and if yes,
 * returns the comment value (not null-temrinated).
 */
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
103
		   const char *name, const char *char_tnum, size_t *length_r)
104 105
{
	size_t name_length = strlen(name);
106
	size_t char_tnum_length = 0;
107 108 109
	const char *comment = (const char*)entry->entry;

	if (entry->length > name_length &&
110 111 112
	    g_ascii_strncasecmp(comment, name, name_length) == 0) {
	        if (char_tnum != NULL) {
	            char_tnum_length = strlen(char_tnum);
113 114 115 116 117 118 119 120
		    if (entry->length > name_length + char_tnum_length + 2 &&
		        comment[name_length] == '[' &&
		        g_ascii_strncasecmp(comment + name_length + 1,
			char_tnum, char_tnum_length) == 0 &&
			comment[name_length + char_tnum_length + 1] == ']')
			    name_length = name_length + char_tnum_length + 2;
		    else if (entry->length > name_length + char_tnum_length &&
		        g_ascii_strncasecmp(comment + name_length,
121 122 123 124 125 126 127
		        char_tnum, char_tnum_length) == 0)
			    name_length = name_length + char_tnum_length;
	        }
	        if (comment[name_length] == '=') {
		    *length_r = entry->length - name_length - 1;
		    return comment + name_length + 1;
		}
128 129 130 131 132
	}

	return NULL;
}

133 134 135 136
/**
 * Check if the comment's name equals the passed name, and if so, copy
 * the comment value into the tag.
 */
137
static bool
138 139
flac_copy_comment(struct tag *tag,
		  const FLAC__StreamMetadata_VorbisComment_Entry *entry,
140 141
		  const char *name, enum tag_type tag_type,
		  const char *char_tnum)
142
{
143 144
	const char *value;
	size_t value_length;
Eric Wong's avatar
Eric Wong committed
145

146
	value = flac_comment_value(entry, name, char_tnum, &value_length);
147
	if (value != NULL) {
148
		tag_add_item_n(tag, tag_type, value, value_length);
149
		return true;
150
	}
Avuton Olrich's avatar
Avuton Olrich committed
151

152
	return false;
153
}
Avuton Olrich's avatar
Avuton Olrich committed
154

155 156 157 158 159 160
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
 * tag names match */
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";

static void
161
flac_parse_comment(struct tag *tag, const char *char_tnum,
162 163 164 165 166
		   const FLAC__StreamMetadata_VorbisComment_Entry *entry)
{
	assert(tag != NULL);

	if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
167
			      TAG_ITEM_TRACK, char_tnum) ||
168
	    flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
169
			      TAG_ITEM_DISC, char_tnum) ||
170
	    flac_copy_comment(tag, entry, "album artist",
171
			      TAG_ITEM_ALBUM_ARTIST, char_tnum))
172 173 174 175
		return;

	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
		if (flac_copy_comment(tag, entry,
176
				      tag_item_names[i], i, char_tnum))
177 178 179
			return;
}

180
void
181
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
182
			    const FLAC__StreamMetadata *block)
183
{
184 185
	FLAC__StreamMetadata_VorbisComment_Entry *comments =
		block->data.vorbis_comment.comments;
Avuton Olrich's avatar
Avuton Olrich committed
186

187
	for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i)
188
		flac_parse_comment(tag, char_tnum, comments++);
189 190
}

Avuton Olrich's avatar
Avuton Olrich committed
191
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
Max Kellermann's avatar
Max Kellermann committed
192
			     struct flac_data *data)
193 194 195
{
	const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);

Avuton Olrich's avatar
Avuton Olrich committed
196
	switch (block->type) {
197
	case FLAC__METADATA_TYPE_STREAMINFO:
198
		data->audio_format.bits = (int8_t)si->bits_per_sample;
199
		data->audio_format.sample_rate = si->sample_rate;
200
		data->audio_format.channels = (int8_t)si->channels;
201
		data->total_time = ((float)si->total_samples) / (si->sample_rate);
202 203
		break;
	case FLAC__METADATA_TYPE_VORBIS_COMMENT:
Max Kellermann's avatar
Max Kellermann committed
204
		flac_parse_replay_gain(block, data);
Max Kellermann's avatar
Max Kellermann committed
205 206

		if (data->tag != NULL)
207
			flac_vorbis_comments_to_tag(data->tag, NULL, block);
Max Kellermann's avatar
Max Kellermann committed
208

Avuton Olrich's avatar
Avuton Olrich committed
209 210
	default:
		break;
211 212 213
	}
}

Avuton Olrich's avatar
Avuton Olrich committed
214 215
void flac_error_common_cb(const char *plugin,
			  const FLAC__StreamDecoderErrorStatus status,
Max Kellermann's avatar
Max Kellermann committed
216
			  struct flac_data *data)
217
{
218
	if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
Avuton Olrich's avatar
Avuton Olrich committed
219
		return;
220

Avuton Olrich's avatar
Avuton Olrich committed
221
	switch (status) {
222
	case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
223
		g_warning("%s lost sync\n", plugin);
224 225
		break;
	case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
226
		g_warning("bad %s header\n", plugin);
227 228
		break;
	case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
229
		g_warning("%s crc mismatch\n", plugin);
230 231
		break;
	default:
232
		g_warning("unknown %s error\n", plugin);
233 234 235
	}
}

236
static void flac_convert_stereo16(int16_t *dest,
237 238 239 240
				  const FLAC__int32 * const buf[],
				  unsigned int position, unsigned int end)
{
	for (; position < end; ++position) {
241 242
		*dest++ = buf[0][position];
		*dest++ = buf[1][position];
243 244 245
	}
}

246 247 248 249 250 251 252 253 254 255 256 257 258
static void
flac_convert_16(int16_t *dest,
		unsigned int num_channels,
		const FLAC__int32 * const buf[],
		unsigned int position, unsigned int end)
{
	unsigned int c_chan;

	for (; position < end; ++position)
		for (c_chan = 0; c_chan < num_channels; c_chan++)
			*dest++ = buf[c_chan][position];
}

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
/**
 * Note: this function also handles 24 bit files!
 */
static void
flac_convert_32(int32_t *dest,
		unsigned int num_channels,
		const FLAC__int32 * const buf[],
		unsigned int position, unsigned int end)
{
	unsigned int c_chan;

	for (; position < end; ++position)
		for (c_chan = 0; c_chan < num_channels; c_chan++)
			*dest++ = buf[c_chan][position];
}

static void
flac_convert_8(int8_t *dest,
	       unsigned int num_channels,
	       const FLAC__int32 * const buf[],
	       unsigned int position, unsigned int end)
{
	unsigned int c_chan;

	for (; position < end; ++position)
		for (c_chan = 0; c_chan < num_channels; c_chan++)
			*dest++ = buf[c_chan][position];
}

288 289 290 291 292 293
static void flac_convert(unsigned char *dest,
			 unsigned int num_channels,
			 unsigned int bytes_per_sample,
			 const FLAC__int32 * const buf[],
			 unsigned int position, unsigned int end)
{
294 295 296 297 298 299 300 301 302
	switch (bytes_per_sample) {
	case 2:
		if (num_channels == 2)
			flac_convert_stereo16((int16_t*)dest, buf,
					      position, end);
		else
			flac_convert_16((int16_t*)dest, num_channels, buf,
					position, end);
		break;
303

304 305 306 307 308 309 310 311 312
	case 4:
		flac_convert_32((int32_t*)dest, num_channels, buf,
				position, end);
		break;

	case 1:
		flac_convert_8((int8_t*)dest, num_channels, buf,
			       position, end);
		break;
313 314 315 316
	}
}

FLAC__StreamDecoderWriteStatus
Max Kellermann's avatar
Max Kellermann committed
317
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
318 319 320 321 322 323 324 325 326 327
		  const FLAC__int32 *const buf[])
{
	unsigned int c_samp;
	const unsigned int num_channels = frame->header.channels;
	const unsigned int bytes_per_sample =
		audio_format_sample_size(&data->audio_format);
	const unsigned int bytes_per_channel =
		bytes_per_sample * frame->header.channels;
	const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
	unsigned int num_samples;
328
	enum decoder_command cmd;
329

330 331 332 333
	if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
	    bytes_per_sample != 4)
		/* exotic unsupported bit rate */
		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
334 335 336 337 338 339 340

	for (c_samp = 0; c_samp < frame->header.blocksize;
	     c_samp += num_samples) {
		num_samples = frame->header.blocksize - c_samp;
		if (num_samples > max_samples)
			num_samples = max_samples;

341 342 343 344
		flac_convert(data->chunk,
			     num_channels, bytes_per_sample, buf,
			     c_samp, c_samp + num_samples);

Max Kellermann's avatar
Max Kellermann committed
345
		cmd = decoder_data(data->decoder, data->input_stream,
346
				   data->chunk,
347
				   num_samples * bytes_per_channel,
Max Kellermann's avatar
Max Kellermann committed
348 349
				   data->time, data->bit_rate,
				   data->replay_gain_info);
350 351 352 353 354 355
		switch (cmd) {
		case DECODE_COMMAND_NONE:
		case DECODE_COMMAND_START:
			break;

		case DECODE_COMMAND_STOP:
356 357
			return
				FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
358 359

		case DECODE_COMMAND_SEEK:
360 361 362 363 364 365 366
			return
				FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
		}
	}

	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417

#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7

char*
flac_cue_track(	const char* pathname,
		const unsigned int tnum)
{
	FLAC__StreamMetadata* cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);

	FLAC__metadata_get_cuesheet(pathname, &cs);

	if (cs == NULL)
		return NULL;

	if (cs->data.cue_sheet.num_tracks <= 1)
	{
		FLAC__metadata_object_delete(cs);
		return NULL;
	}

	if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks)
	{
		char* track = g_strdup_printf("track_%03u.flac", tnum);

		FLAC__metadata_object_delete(cs);

		return track;
	}
	else
	{
		FLAC__metadata_object_delete(cs);
		return NULL;
	}
}

unsigned int
flac_vtrack_tnum(const char* fname)
{
	/* find last occurrence of '_' in fname
	 * which is hopefully something like track_xxx.flac
	 * another/better way would be to use tag struct
	 */
	char* ptr = strrchr(fname, '_');

	// copy ascii tracknumber to int
	char vtrack[4];
	g_strlcpy(vtrack, ++ptr, 4);
	return (unsigned int)strtol(vtrack, NULL, 10);
}

#endif /* FLAC_API_VERSION_CURRENT >= 7 */