mp4_plugin.c 10.3 KB
Newer Older
1
/* the Music Player Daemon (MPD)
2
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3
 * This project's homepage is: http://www.musicpd.org
4
 *
5 6 7 8 9 10 11 12 13 14 15 16 17 18
 * 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.
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

19
#include "../decoder_api.h"
20
#include "config.h"
21

22
#include "mp4ff.h"
23

24
#include <limits.h>
25
#include <faad.h>
26 27 28 29
#include <glib.h>
#include <stdlib.h>
#include <unistd.h>

Warren Dukes's avatar
Warren Dukes committed
30 31
/* all code here is either based on or copied from FAAD2's frontend code */

32
struct mp4_context {
33
	struct decoder *decoder;
34 35 36
	struct input_stream *input_stream;
};

Max Kellermann's avatar
Max Kellermann committed
37 38
static int
mp4_get_aac_track(mp4ff_t * infile)
Avuton Olrich's avatar
Avuton Olrich committed
39
{
40 41
	/* find AAC track */
	int i, rc;
Max Kellermann's avatar
Max Kellermann committed
42
	int num_tracks = mp4ff_total_tracks(infile);
43

Max Kellermann's avatar
Max Kellermann committed
44
	for (i = 0; i < num_tracks; i++) {
45
		unsigned char *buff = NULL;
46
		unsigned int buff_size = 0;
47
#ifdef HAVE_MP4AUDIOSPECIFICCONFIG
48
		mp4AudioSpecificConfig mp4ASC;
49 50
#else
		unsigned long dummy1_32;
51
		unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8,
Avuton Olrich's avatar
Avuton Olrich committed
52
		    dummy7_8, dummy8_8;
53
#endif
Avuton Olrich's avatar
Avuton Olrich committed
54

55
		mp4ff_get_decoder_config(infile, i, &buff, &buff_size);
56 57

		if (buff) {
58
#ifdef HAVE_MP4AUDIOSPECIFICCONFIG
59
			rc = AudioSpecificConfig(buff, buff_size, &mp4ASC);
60 61
#else
			rc = AudioSpecificConfig(buff, &dummy1_32, &dummy2_8,
Avuton Olrich's avatar
Avuton Olrich committed
62 63 64
						 &dummy3_8, &dummy4_8,
						 &dummy5_8, &dummy6_8,
						 &dummy7_8, &dummy8_8);
65
#endif
66
			free(buff);
Avuton Olrich's avatar
Avuton Olrich committed
67 68
			if (rc < 0)
				continue;
69
			return i;
70 71 72 73 74 75 76
		}
	}

	/* can't decode this */
	return -1;
}

Max Kellermann's avatar
Max Kellermann committed
77 78
static uint32_t
mp4_read(void *user_data, void *buffer, uint32_t length)
79
{
80 81
	struct mp4_context *ctx = user_data;

82
	return decoder_read(ctx->decoder, ctx->input_stream, buffer, length);
83
}
84

Max Kellermann's avatar
Max Kellermann committed
85 86
static uint32_t
mp4_seek(void *user_data, uint64_t position)
Avuton Olrich's avatar
Avuton Olrich committed
87
{
88 89 90
	struct mp4_context *ctx = user_data;

	return input_stream_seek(ctx->input_stream, position, SEEK_SET)
91
		? 0 : -1;
92 93
}

94
static void
Max Kellermann's avatar
Max Kellermann committed
95
mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
Avuton Olrich's avatar
Avuton Olrich committed
96
{
97
	struct mp4_context ctx = {
98
		.decoder = mpd_decoder,
99 100
		.input_stream = input_stream,
	};
101 102 103
	mp4ff_callback_t callback = {
		.read = mp4_read,
		.seek = mp4_seek,
104
		.user_data = &ctx,
105
	};
Avuton Olrich's avatar
Avuton Olrich committed
106
	mp4ff_t *mp4fh;
107
	int32_t track;
108
	float file_time, total_time;
109
	int32_t scale;
110
	faacDecHandle decoder;
Max Kellermann's avatar
Max Kellermann committed
111
	faacDecFrameInfo frame_info;
112
	faacDecConfigurationPtr config;
Max Kellermann's avatar
Max Kellermann committed
113 114
	unsigned char *mp4_buffer;
	unsigned int mp4_buffer_size;
115
	uint32_t sample_rate;
116 117 118 119 120 121 122 123
#ifdef HAVE_FAAD_LONG
	/* neaacdec.h declares all arguments as "unsigned long", but
	   internally expects uint32_t pointers.  To avoid gcc
	   warnings, use this workaround. */
	unsigned long *sample_rate_r = (unsigned long*)&sample_rate;
#else
	uint32_t *sample_rate_r = &sample_rate;
#endif
124
	unsigned char channels;
Max Kellermann's avatar
Max Kellermann committed
125 126
	long sample_id;
	long num_samples;
Warren Dukes's avatar
Warren Dukes committed
127
	long dur;
Max Kellermann's avatar
Max Kellermann committed
128 129 130
	unsigned int sample_count;
	char *sample_buffer;
	size_t sample_buffer_length;
Warren Dukes's avatar
Warren Dukes committed
131
	unsigned int initial = 1;
Max Kellermann's avatar
Max Kellermann committed
132 133 134
	float *seek_table;
	long seek_table_end = -1;
	bool seek_position_found = false;
135
	long offset;
Max Kellermann's avatar
Max Kellermann committed
136
	uint16_t bit_rate = 0;
137
	bool seeking = false;
138
	double seek_where = 0;
139
	bool initialized = false;
140
	enum decoder_command cmd = DECODE_COMMAND_NONE;
141

142
	mp4fh = mp4ff_open_read(&callback);
Avuton Olrich's avatar
Avuton Olrich committed
143
	if (!mp4fh) {
144
		g_warning("Input does not appear to be a mp4 stream.\n");
145
		return;
146 147
	}

Max Kellermann's avatar
Max Kellermann committed
148
	track = mp4_get_aac_track(mp4fh);
Avuton Olrich's avatar
Avuton Olrich committed
149
	if (track < 0) {
150
		g_warning("No AAC track found in mp4 stream.\n");
151
		mp4ff_close(mp4fh);
152
		return;
153 154
	}

155 156 157 158 159 160 161 162 163 164 165
	decoder = faacDecOpen();

	config = faacDecGetCurrentConfiguration(decoder);
	config->outputFormat = FAAD_FMT_16BIT;
#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
	config->downMatrix = 1;
#endif
#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
	config->dontUpSampleImplicitSBR = 0;
#endif
	faacDecSetConfiguration(decoder, config);
166

Max Kellermann's avatar
Max Kellermann committed
167 168 169
	mp4_buffer = NULL;
	mp4_buffer_size = 0;
	mp4ff_get_decoder_config(mp4fh, track, &mp4_buffer, &mp4_buffer_size);
170

Max Kellermann's avatar
Max Kellermann committed
171
	if (faacDecInit2(decoder, mp4_buffer, mp4_buffer_size,
172
			 sample_rate_r, &channels) < 0) {
173
		g_warning("Not an AAC stream.\n");
174 175
		faacDecClose(decoder);
		mp4ff_close(mp4fh);
176
		return;
177 178
	}

179
	file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
Avuton Olrich's avatar
Avuton Olrich committed
180
	scale = mp4ff_time_scale(mp4fh, track);
181

182
	g_free(mp4_buffer);
183

Avuton Olrich's avatar
Avuton Olrich committed
184
	if (scale < 0) {
185
		g_warning("Error getting audio format of mp4 AAC track.\n");
186 187
		faacDecClose(decoder);
		mp4ff_close(mp4fh);
188
		return;
189
	}
190
	total_time = ((float)file_time) / scale;
191

Max Kellermann's avatar
Max Kellermann committed
192 193
	num_samples = mp4ff_num_samples(mp4fh, track);
	if (num_samples > (long)(INT_MAX / sizeof(float))) {
194
		 g_warning("Integer overflow.\n");
195 196
		 faacDecClose(decoder);
		 mp4ff_close(mp4fh);
197
		 return;
198
	}
199

200
	file_time = 0.0;
Warren Dukes's avatar
Warren Dukes committed
201

202
	seek_table = g_malloc(sizeof(float) * num_samples);
Warren Dukes's avatar
Warren Dukes committed
203

204 205 206 207
	for (sample_id = 0;
	     sample_id < num_samples && cmd != DECODE_COMMAND_STOP;
	     sample_id++) {
		if (cmd == DECODE_COMMAND_SEEK) {
208
			seeking = true;
209 210
			seek_where = decoder_seek_where(mpd_decoder);
		}
211

Max Kellermann's avatar
Max Kellermann committed
212 213
		if (seeking && seek_table_end > 1 &&
		    seek_table[seek_table_end] >= seek_where) {
Warren Dukes's avatar
Warren Dukes committed
214
			int i = 2;
Max Kellermann's avatar
Max Kellermann committed
215
			while (seek_table[i] < seek_where)
Avuton Olrich's avatar
Avuton Olrich committed
216
				i++;
Max Kellermann's avatar
Max Kellermann committed
217 218
			sample_id = i - 1;
			file_time = seek_table[sample_id];
Warren Dukes's avatar
Warren Dukes committed
219 220
		}

Max Kellermann's avatar
Max Kellermann committed
221 222
		dur = mp4ff_get_sample_duration(mp4fh, track, sample_id);
		offset = mp4ff_get_sample_offset(mp4fh, track, sample_id);
Warren Dukes's avatar
Warren Dukes committed
223

Max Kellermann's avatar
Max Kellermann committed
224 225 226
		if (sample_id > seek_table_end) {
			seek_table[sample_id] = file_time;
			seek_table_end = sample_id;
Warren Dukes's avatar
Warren Dukes committed
227 228
		}

Max Kellermann's avatar
Max Kellermann committed
229
		if (sample_id == 0)
Avuton Olrich's avatar
Avuton Olrich committed
230 231 232 233 234
			dur = 0;
		if (offset > dur)
			dur = 0;
		else
			dur -= offset;
235
		file_time += ((float)dur) / scale;
Warren Dukes's avatar
Warren Dukes committed
236

237
		if (seeking && file_time > seek_where)
Max Kellermann's avatar
Max Kellermann committed
238
			seek_position_found = true;
Warren Dukes's avatar
Warren Dukes committed
239

Max Kellermann's avatar
Max Kellermann committed
240 241
		if (seeking && seek_position_found) {
			seek_position_found = false;
242
			seeking = 0;
243
			decoder_command_finished(mpd_decoder);
Warren Dukes's avatar
Warren Dukes committed
244
		}
245

Avuton Olrich's avatar
Avuton Olrich committed
246 247 248
		if (seeking)
			continue;

Max Kellermann's avatar
Max Kellermann committed
249 250
		if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer,
				      &mp4_buffer_size) == 0)
251 252
			break;

253
#ifdef HAVE_FAAD_BUFLEN_FUNCS
Max Kellermann's avatar
Max Kellermann committed
254 255
		sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer,
					      mp4_buffer_size);
256
#else
Max Kellermann's avatar
Max Kellermann committed
257
		sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer);
258
#endif
Warren Dukes's avatar
Warren Dukes committed
259

Max Kellermann's avatar
Max Kellermann committed
260 261 262
		if (mp4_buffer)
			free(mp4_buffer);
		if (frame_info.error > 0) {
263 264
			g_warning("faad2 error: %s\n",
				  faacDecGetErrorMessage(frame_info.error));
265 266 267
			break;
		}

268
		if (!initialized) {
269 270 271 272 273
			struct audio_format audio_format = {
				.bits = 16,
				.channels = frame_info.channels,
			};

Max Kellermann's avatar
Max Kellermann committed
274
			channels = frame_info.channels;
275
#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
Max Kellermann's avatar
Max Kellermann committed
276
			scale = frame_info.samplerate;
277
#endif
278
			audio_format.sample_rate = scale;
279 280 281 282 283 284 285 286 287

			if (!audio_format_valid(&audio_format)) {
				g_warning("Invalid audio format: %u:%u:%u\n",
					  audio_format.sample_rate,
					  audio_format.bits,
					  audio_format.channels);
				break;
			}

288
			decoder_initialized(mpd_decoder, &audio_format,
Max Kellermann's avatar
Max Kellermann committed
289 290
					    input_stream->seekable,
					    total_time);
291
			initialized = true;
292 293
		}

Max Kellermann's avatar
Max Kellermann committed
294 295
		if (channels * (unsigned long)(dur + offset) > frame_info.samples) {
			dur = frame_info.samples / channels;
296 297
			offset = 0;
		}
298

Max Kellermann's avatar
Max Kellermann committed
299
		sample_count = (unsigned long)(dur * channels);
Warren Dukes's avatar
Warren Dukes committed
300

Max Kellermann's avatar
Max Kellermann committed
301
		if (sample_count > 0) {
Avuton Olrich's avatar
Avuton Olrich committed
302
			initial = 0;
Max Kellermann's avatar
Max Kellermann committed
303 304 305
			bit_rate = frame_info.bytesconsumed * 8.0 *
			    frame_info.channels * scale /
			    frame_info.samples / 1000 + 0.5;
306 307
		}

Max Kellermann's avatar
Max Kellermann committed
308
		sample_buffer_length = sample_count * 2;
309

Max Kellermann's avatar
Max Kellermann committed
310
		sample_buffer += offset * channels * 2;
311

312 313 314
		cmd = decoder_data(mpd_decoder, input_stream,
				   sample_buffer, sample_buffer_length,
				   file_time, bit_rate, NULL);
Warren Dukes's avatar
Warren Dukes committed
315
	}
316

Max Kellermann's avatar
Max Kellermann committed
317
	free(seek_table);
Warren Dukes's avatar
Warren Dukes committed
318 319
	faacDecClose(decoder);
	mp4ff_close(mp4fh);
320 321
}

Max Kellermann's avatar
Max Kellermann committed
322
static struct tag *
323
mp4_load_tag(const char *file)
Avuton Olrich's avatar
Avuton Olrich committed
324
{
325
	struct tag *ret = NULL;
Max Kellermann's avatar
Max Kellermann committed
326
	struct input_stream input_stream;
327
	struct mp4_context ctx = {
328
		.decoder = NULL,
329 330
		.input_stream = &input_stream,
	};
331 332 333
	mp4ff_callback_t callback = {
		.read = mp4_read,
		.seek = mp4_seek,
334
		.user_data = &ctx,
335
	};
Avuton Olrich's avatar
Avuton Olrich committed
336
	mp4ff_t *mp4fh;
Warren Dukes's avatar
Warren Dukes committed
337
	int32_t track;
338
	int32_t file_time;
Warren Dukes's avatar
Warren Dukes committed
339
	int32_t scale;
340
	int i;
Warren Dukes's avatar
Warren Dukes committed
341

Max Kellermann's avatar
Max Kellermann committed
342
	if (!input_stream_open(&input_stream, file)) {
343
		g_warning("mp4_load_tag: Failed to open file: %s\n", file);
344 345
		return NULL;
	}
Warren Dukes's avatar
Warren Dukes committed
346

347
	mp4fh = mp4ff_open_read(&callback);
Avuton Olrich's avatar
Avuton Olrich committed
348
	if (!mp4fh) {
Max Kellermann's avatar
Max Kellermann committed
349
		input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
350 351 352
		return NULL;
	}

Max Kellermann's avatar
Max Kellermann committed
353
	track = mp4_get_aac_track(mp4fh);
Avuton Olrich's avatar
Avuton Olrich committed
354
	if (track < 0) {
Warren Dukes's avatar
Warren Dukes committed
355
		mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
356
		input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
357 358 359
		return NULL;
	}

360
	ret = tag_new();
361
	file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
Avuton Olrich's avatar
Avuton Olrich committed
362 363
	scale = mp4ff_time_scale(mp4fh, track);
	if (scale < 0) {
Warren Dukes's avatar
Warren Dukes committed
364
		mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
365
		input_stream_close(&input_stream);
366
		tag_free(ret);
Warren Dukes's avatar
Warren Dukes committed
367 368
		return NULL;
	}
369
	ret->time = ((float)file_time) / scale + 0.5;
Warren Dukes's avatar
Warren Dukes committed
370

Avuton Olrich's avatar
Avuton Olrich committed
371 372 373
	for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
		char *item;
		char *value;
Warren Dukes's avatar
Warren Dukes committed
374

375
		mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
Warren Dukes's avatar
Warren Dukes committed
376

Avuton Olrich's avatar
Avuton Olrich committed
377
		if (0 == strcasecmp("artist", item)) {
378
			tag_add_item(ret, TAG_ITEM_ARTIST, value);
Avuton Olrich's avatar
Avuton Olrich committed
379
		} else if (0 == strcasecmp("title", item)) {
380
			tag_add_item(ret, TAG_ITEM_TITLE, value);
Avuton Olrich's avatar
Avuton Olrich committed
381
		} else if (0 == strcasecmp("album", item)) {
382
			tag_add_item(ret, TAG_ITEM_ALBUM, value);
Avuton Olrich's avatar
Avuton Olrich committed
383
		} else if (0 == strcasecmp("track", item)) {
384
			tag_add_item(ret, TAG_ITEM_TRACK, value);
Avuton Olrich's avatar
Avuton Olrich committed
385
		} else if (0 == strcasecmp("disc", item)) {	/* Is that the correct id? */
386
			tag_add_item(ret, TAG_ITEM_DISC, value);
Avuton Olrich's avatar
Avuton Olrich committed
387
		} else if (0 == strcasecmp("genre", item)) {
388
			tag_add_item(ret, TAG_ITEM_GENRE, value);
Avuton Olrich's avatar
Avuton Olrich committed
389
		} else if (0 == strcasecmp("date", item)) {
390
			tag_add_item(ret, TAG_ITEM_DATE, value);
391 392
		} else if (0 == strcasecmp("writer", item)) {
			tag_add_item(ret, TAG_ITEM_COMPOSER, value);
393
		}
Warren Dukes's avatar
Warren Dukes committed
394

395 396
		free(item);
		free(value);
Warren Dukes's avatar
Warren Dukes committed
397 398 399
	}

	mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
400
	input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
401 402 403 404

	return ret;
}

Max Kellermann's avatar
Max Kellermann committed
405 406
static struct tag *
mp4_tag_dup(const char *file)
Avuton Olrich's avatar
Avuton Olrich committed
407
{
408
	struct tag *ret = NULL;
Warren Dukes's avatar
Warren Dukes committed
409

410
	ret = mp4_load_tag(file);
Avuton Olrich's avatar
Avuton Olrich committed
411 412
	if (!ret)
		return NULL;
413
	if (tag_is_empty(ret)) {
414
		struct tag *temp = tag_id3_load(file);
Avuton Olrich's avatar
Avuton Olrich committed
415
		if (temp) {
Warren Dukes's avatar
Warren Dukes committed
416
			temp->time = ret->time;
417
			tag_free(ret);
Warren Dukes's avatar
Warren Dukes committed
418 419 420 421 422 423 424
			ret = temp;
		}
	}

	return ret;
}

425
static const char *const mp4_suffixes[] = { "m4a", "mp4", NULL };
Max Kellermann's avatar
Max Kellermann committed
426
static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
Warren Dukes's avatar
Warren Dukes committed
427

Max Kellermann's avatar
Max Kellermann committed
428
const struct decoder_plugin mp4_plugin = {
429 430
	.name = "mp4",
	.stream_decode = mp4_decode,
Max Kellermann's avatar
Max Kellermann committed
431
	.tag_dup = mp4_tag_dup,
432
	.suffixes = mp4_suffixes,
Max Kellermann's avatar
Max Kellermann committed
433
	.mime_types = mp4_mime_types,
Warren Dukes's avatar
Warren Dukes committed
434
};