mp4ff_plugin.c 10.4 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
 *
 * 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.
18 19
 */

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

Max Kellermann's avatar
Max Kellermann committed
23
#include <glib.h>
24

Max Kellermann's avatar
Max Kellermann committed
25
#include <mp4ff.h>
26
#include <faad.h>
Max Kellermann's avatar
Max Kellermann committed
27

28
#include <assert.h>
29 30 31
#include <stdlib.h>
#include <unistd.h>

32 33 34
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mp4ff"

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

37
struct mp4_context {
38
	struct decoder *decoder;
39 40 41
	struct input_stream *input_stream;
};

Max Kellermann's avatar
Max Kellermann committed
42
static int
43 44
mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder,
		  uint32_t *sample_rate, unsigned char *channels_r)
Avuton Olrich's avatar
Avuton Olrich committed
45
{
46 47 48 49 50 51 52 53
#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
54
	int i, rc;
Max Kellermann's avatar
Max Kellermann committed
55
	int num_tracks = mp4ff_total_tracks(infile);
56

Max Kellermann's avatar
Max Kellermann committed
57
	for (i = 0; i < num_tracks; i++) {
58
		unsigned char *buff = NULL;
59
		unsigned int buff_size = 0;
60 61 62 63 64 65 66 67 68 69

		if (mp4ff_get_track_type(infile, i) != 1)
			/* not an audio track */
			continue;

		if (decoder == NULL)
			/* have don't have a decoder to initialize -
			   we're done now, because we found an audio
			   track */
			return i;
Avuton Olrich's avatar
Avuton Olrich committed
70

71
		mp4ff_get_decoder_config(infile, i, &buff, &buff_size);
72 73
		if (buff == NULL)
			continue;
74

75 76 77 78 79 80
		rc = faacDecInit2(decoder, buff, buff_size,
				  sample_rate_r, channels_r);
		free(buff);

		if (rc >= 0)
			/* found a valid AAC track */
81
			return i;
82 83 84 85 86 87
	}

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

Max Kellermann's avatar
Max Kellermann committed
88 89
static uint32_t
mp4_read(void *user_data, void *buffer, uint32_t length)
90
{
91 92
	struct mp4_context *ctx = user_data;

93
	return decoder_read(ctx->decoder, ctx->input_stream, buffer, length);
94
}
95

Max Kellermann's avatar
Max Kellermann committed
96 97
static uint32_t
mp4_seek(void *user_data, uint64_t position)
Avuton Olrich's avatar
Avuton Olrich committed
98
{
99 100 101
	struct mp4_context *ctx = user_data;

	return input_stream_seek(ctx->input_stream, position, SEEK_SET)
102
		? 0 : -1;
103 104
}

105
static faacDecHandle
106
mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
107 108 109
{
	faacDecHandle decoder;
	faacDecConfigurationPtr config;
110
	int track;
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
	uint32_t sample_rate;
	unsigned char channels;

	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);

126 127 128
	track = mp4_get_aac_track(mp4fh, decoder, &sample_rate, &channels);
	if (track < 0) {
		g_warning("No AAC track found");
129 130 131 132
		faacDecClose(decoder);
		return NULL;
	}

133
	*track_r = track;
134
	audio_format_init(audio_format, sample_rate, 16, channels);
135 136 137 138 139 140 141 142 143 144 145 146 147

	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);
		faacDecClose(decoder);
		return NULL;
	}

	return decoder;
}

148
static void
Max Kellermann's avatar
Max Kellermann committed
149
mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
Avuton Olrich's avatar
Avuton Olrich committed
150
{
151
	struct mp4_context ctx = {
152
		.decoder = mpd_decoder,
153 154
		.input_stream = input_stream,
	};
155 156 157
	mp4ff_callback_t callback = {
		.read = mp4_read,
		.seek = mp4_seek,
158
		.user_data = &ctx,
159
	};
Avuton Olrich's avatar
Avuton Olrich committed
160
	mp4ff_t *mp4fh;
161
	int32_t track;
162
	float file_time, total_time;
163
	int32_t scale;
164
	faacDecHandle decoder;
165
	struct audio_format audio_format;
Max Kellermann's avatar
Max Kellermann committed
166 167 168 169 170
	faacDecFrameInfo frame_info;
	unsigned char *mp4_buffer;
	unsigned int mp4_buffer_size;
	long sample_id;
	long num_samples;
Warren Dukes's avatar
Warren Dukes committed
171
	long dur;
Max Kellermann's avatar
Max Kellermann committed
172 173 174
	unsigned int sample_count;
	char *sample_buffer;
	size_t sample_buffer_length;
Warren Dukes's avatar
Warren Dukes committed
175
	unsigned int initial = 1;
Max Kellermann's avatar
Max Kellermann committed
176 177 178
	float *seek_table;
	long seek_table_end = -1;
	bool seek_position_found = false;
179
	long offset;
Max Kellermann's avatar
Max Kellermann committed
180
	uint16_t bit_rate = 0;
181
	bool seeking = false;
182
	double seek_where = 0;
183
	enum decoder_command cmd = DECODE_COMMAND_NONE;
184

185
	mp4fh = mp4ff_open_read(&callback);
Avuton Olrich's avatar
Avuton Olrich committed
186
	if (!mp4fh) {
187
		g_warning("Input does not appear to be a mp4 stream.\n");
188
		return;
189 190
	}

191
	decoder = mp4_faad_new(mp4fh, &track, &audio_format);
192
	if (decoder == NULL) {
193
		mp4ff_close(mp4fh);
194
		return;
195 196
	}

197
	file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
Avuton Olrich's avatar
Avuton Olrich committed
198
	scale = mp4ff_time_scale(mp4fh, track);
199

Avuton Olrich's avatar
Avuton Olrich committed
200
	if (scale < 0) {
201
		g_warning("Error getting audio format of mp4 AAC track.\n");
202 203
		faacDecClose(decoder);
		mp4ff_close(mp4fh);
204
		return;
205
	}
206
	total_time = ((float)file_time) / scale;
207

Max Kellermann's avatar
Max Kellermann committed
208
	num_samples = mp4ff_num_samples(mp4fh, track);
Max Kellermann's avatar
Max Kellermann committed
209
	if (num_samples > (long)(G_MAXINT / sizeof(float))) {
210
		 g_warning("Integer overflow.\n");
211 212
		 faacDecClose(decoder);
		 mp4ff_close(mp4fh);
213
		 return;
214
	}
215

216
	file_time = 0.0;
Warren Dukes's avatar
Warren Dukes committed
217

218 219 220
	seek_table = input_stream->seekable
		? g_malloc(sizeof(float) * num_samples)
		: NULL;
Warren Dukes's avatar
Warren Dukes committed
221

222 223 224 225
	decoder_initialized(mpd_decoder, &audio_format,
			    input_stream->seekable,
			    total_time);

226 227 228 229
	for (sample_id = 0;
	     sample_id < num_samples && cmd != DECODE_COMMAND_STOP;
	     sample_id++) {
		if (cmd == DECODE_COMMAND_SEEK) {
230 231
			assert(seek_table != NULL);

232
			seeking = true;
233 234
			seek_where = decoder_seek_where(mpd_decoder);
		}
235

Max Kellermann's avatar
Max Kellermann committed
236 237
		if (seeking && seek_table_end > 1 &&
		    seek_table[seek_table_end] >= seek_where) {
Warren Dukes's avatar
Warren Dukes committed
238
			int i = 2;
239 240 241

			assert(seek_table != NULL);

Max Kellermann's avatar
Max Kellermann committed
242
			while (seek_table[i] < seek_where)
Avuton Olrich's avatar
Avuton Olrich committed
243
				i++;
Max Kellermann's avatar
Max Kellermann committed
244 245
			sample_id = i - 1;
			file_time = seek_table[sample_id];
Warren Dukes's avatar
Warren Dukes committed
246 247
		}

Max Kellermann's avatar
Max Kellermann committed
248 249
		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
250

251
		if (seek_table != NULL && sample_id > seek_table_end) {
Max Kellermann's avatar
Max Kellermann committed
252 253
			seek_table[sample_id] = file_time;
			seek_table_end = sample_id;
Warren Dukes's avatar
Warren Dukes committed
254 255
		}

Max Kellermann's avatar
Max Kellermann committed
256
		if (sample_id == 0)
Avuton Olrich's avatar
Avuton Olrich committed
257 258 259 260 261
			dur = 0;
		if (offset > dur)
			dur = 0;
		else
			dur -= offset;
262
		file_time += ((float)dur) / scale;
Warren Dukes's avatar
Warren Dukes committed
263

264
		if (seeking && file_time > seek_where)
Max Kellermann's avatar
Max Kellermann committed
265
			seek_position_found = true;
Warren Dukes's avatar
Warren Dukes committed
266

Max Kellermann's avatar
Max Kellermann committed
267 268
		if (seeking && seek_position_found) {
			seek_position_found = false;
269
			seeking = 0;
270
			decoder_command_finished(mpd_decoder);
Warren Dukes's avatar
Warren Dukes committed
271
		}
272

Avuton Olrich's avatar
Avuton Olrich committed
273 274 275
		if (seeking)
			continue;

Max Kellermann's avatar
Max Kellermann committed
276 277
		if (mp4ff_read_sample(mp4fh, track, sample_id, &mp4_buffer,
				      &mp4_buffer_size) == 0)
278 279
			break;

280
#ifdef HAVE_FAAD_BUFLEN_FUNCS
Max Kellermann's avatar
Max Kellermann committed
281 282
		sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer,
					      mp4_buffer_size);
283
#else
Max Kellermann's avatar
Max Kellermann committed
284
		sample_buffer = faacDecDecode(decoder, &frame_info, mp4_buffer);
285
#endif
Warren Dukes's avatar
Warren Dukes committed
286

287 288
		free(mp4_buffer);

Max Kellermann's avatar
Max Kellermann committed
289
		if (frame_info.error > 0) {
290 291
			g_warning("faad2 error: %s\n",
				  faacDecGetErrorMessage(frame_info.error));
292 293 294
			break;
		}

295 296 297 298 299
		if (frame_info.channels != audio_format.channels) {
			g_warning("channel count changed from %u to %u",
				  audio_format.channels, frame_info.channels);
			break;
		}
300

301
#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
302 303 304 305 306
		if (frame_info.samplerate != audio_format.sample_rate) {
			g_warning("sample rate changed from %u to %lu",
				  audio_format.sample_rate,
				  (unsigned long)frame_info.samplerate);
			break;
307
		}
308
#endif
309

310 311
		if (audio_format.channels * (unsigned long)(dur + offset) > frame_info.samples) {
			dur = frame_info.samples / audio_format.channels;
312 313
			offset = 0;
		}
314

315
		sample_count = (unsigned long)(dur * audio_format.channels);
Warren Dukes's avatar
Warren Dukes committed
316

Max Kellermann's avatar
Max Kellermann committed
317
		if (sample_count > 0) {
Avuton Olrich's avatar
Avuton Olrich committed
318
			initial = 0;
Max Kellermann's avatar
Max Kellermann committed
319 320 321
			bit_rate = frame_info.bytesconsumed * 8.0 *
			    frame_info.channels * scale /
			    frame_info.samples / 1000 + 0.5;
322 323
		}

Max Kellermann's avatar
Max Kellermann committed
324
		sample_buffer_length = sample_count * 2;
325

326
		sample_buffer += offset * audio_format.channels * 2;
327

328 329 330
		cmd = decoder_data(mpd_decoder, input_stream,
				   sample_buffer, sample_buffer_length,
				   file_time, bit_rate, NULL);
Warren Dukes's avatar
Warren Dukes committed
331
	}
332

333
	g_free(seek_table);
Warren Dukes's avatar
Warren Dukes committed
334 335
	faacDecClose(decoder);
	mp4ff_close(mp4fh);
336 337
}

Max Kellermann's avatar
Max Kellermann committed
338
static struct tag *
339
mp4_tag_dup(const char *file)
Avuton Olrich's avatar
Avuton Olrich committed
340
{
341
	struct tag *ret = NULL;
Max Kellermann's avatar
Max Kellermann committed
342
	struct input_stream input_stream;
343
	struct mp4_context ctx = {
344
		.decoder = NULL,
345 346
		.input_stream = &input_stream,
	};
347 348 349
	mp4ff_callback_t callback = {
		.read = mp4_read,
		.seek = mp4_seek,
350
		.user_data = &ctx,
351
	};
Avuton Olrich's avatar
Avuton Olrich committed
352
	mp4ff_t *mp4fh;
Warren Dukes's avatar
Warren Dukes committed
353
	int32_t track;
354
	int32_t file_time;
Warren Dukes's avatar
Warren Dukes committed
355
	int32_t scale;
356
	int i;
Warren Dukes's avatar
Warren Dukes committed
357

Max Kellermann's avatar
Max Kellermann committed
358
	if (!input_stream_open(&input_stream, file)) {
359
		g_warning("Failed to open file: %s", file);
360 361
		return NULL;
	}
Warren Dukes's avatar
Warren Dukes committed
362

363
	mp4fh = mp4ff_open_read(&callback);
Avuton Olrich's avatar
Avuton Olrich committed
364
	if (!mp4fh) {
Max Kellermann's avatar
Max Kellermann committed
365
		input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
366 367 368
		return NULL;
	}

369
	track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL);
Avuton Olrich's avatar
Avuton Olrich committed
370
	if (track < 0) {
Warren Dukes's avatar
Warren Dukes committed
371
		mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
372
		input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
373 374 375
		return NULL;
	}

376
	ret = tag_new();
377
	file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
Avuton Olrich's avatar
Avuton Olrich committed
378 379
	scale = mp4ff_time_scale(mp4fh, track);
	if (scale < 0) {
Warren Dukes's avatar
Warren Dukes committed
380
		mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
381
		input_stream_close(&input_stream);
382
		tag_free(ret);
Warren Dukes's avatar
Warren Dukes committed
383 384
		return NULL;
	}
385
	ret->time = ((float)file_time) / scale + 0.5;
Warren Dukes's avatar
Warren Dukes committed
386

Avuton Olrich's avatar
Avuton Olrich committed
387 388 389
	for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
		char *item;
		char *value;
Warren Dukes's avatar
Warren Dukes committed
390

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

393
		if (0 == g_ascii_strcasecmp("artist", item)) {
394
			tag_add_item(ret, TAG_ITEM_ARTIST, value);
395
		} else if (0 == g_ascii_strcasecmp("title", item)) {
396
			tag_add_item(ret, TAG_ITEM_TITLE, value);
397
		} else if (0 == g_ascii_strcasecmp("album", item)) {
398
			tag_add_item(ret, TAG_ITEM_ALBUM, value);
399
		} else if (0 == g_ascii_strcasecmp("track", item)) {
400
			tag_add_item(ret, TAG_ITEM_TRACK, value);
401 402
		} else if (0 == g_ascii_strcasecmp("disc", item)) {
			/* Is that the correct id? */
403
			tag_add_item(ret, TAG_ITEM_DISC, value);
404
		} else if (0 == g_ascii_strcasecmp("genre", item)) {
405
			tag_add_item(ret, TAG_ITEM_GENRE, value);
406
		} else if (0 == g_ascii_strcasecmp("date", item)) {
407
			tag_add_item(ret, TAG_ITEM_DATE, value);
408
		} else if (0 == g_ascii_strcasecmp("writer", item)) {
409
			tag_add_item(ret, TAG_ITEM_COMPOSER, value);
410
		}
Warren Dukes's avatar
Warren Dukes committed
411

412 413
		free(item);
		free(value);
Warren Dukes's avatar
Warren Dukes committed
414 415 416
	}

	mp4ff_close(mp4fh);
Max Kellermann's avatar
Max Kellermann committed
417
	input_stream_close(&input_stream);
Warren Dukes's avatar
Warren Dukes committed
418 419 420 421

	return ret;
}

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

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