summaryrefslogtreecommitdiff
path: root/afh_common.c
blob: 3c6e8064d224c0f1f91ad4e4c402eff9e272336f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
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
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/* SPDX-License-Identifier: GPL-2.0 */

/** \file afh_common.c Common audio format handler functions. */

#include <sys/mman.h> /* mmap */
#include <sys/types.h>

#include "para.h"
#include "error.h"
#include "string.h"
#include "afh.h"

/** The list of all status items */
const char *status_item_list[] = {STATUS_ITEMS};

/**
 * For each audio file the number of its audio format is stored in the
 * database. Therefore this list, in particular its order, is part of the ABI.
 * So it's only OK to append new audio formats. All audio formats are listed
 * here, regardless of whether the audio format handler is compiled in.
 */
#define ALL_AUDIO_FORMATS \
	AUDIO_FORMAT(mp3) \
	AUDIO_FORMAT(ogg) \
	AUDIO_FORMAT(aac) \
	AUDIO_FORMAT(wma) \
	AUDIO_FORMAT(spx) \
	AUDIO_FORMAT(flac) \
	AUDIO_FORMAT(opus) \

/** \cond doxygen_ignore */
#define AUDIO_FORMAT(_fmt) #_fmt,
static const char * const audio_format_names[] = {ALL_AUDIO_FORMATS};
#undef AUDIO_FORMAT
/* Weak declarations must be public. */
#define AUDIO_FORMAT(_fmt) \
	struct audio_format_handler _fmt ## _afh __attribute__ ((weak)) \
	= {.get_file_info = NULL};
ALL_AUDIO_FORMATS
#undef AUDIO_FORMAT
#define AUDIO_FORMAT(_fmt) & _fmt ## _afh,
static struct audio_format_handler *afl[] = {ALL_AUDIO_FORMATS};
#undef AUDIO_FORMAT
#define NUM_AUDIO_FORMATS (ARRAY_SIZE(afl))
/** \endcond */

/**
 * Get the name of the given audio format.
 *
 * \param i The audio format number.
 *
 * \return This returns a pointer to statically allocated memory so it
 * must not be freed by the caller.
 */
const char *audio_format_name(int i)
{
	if (i < 0 || i >= NUM_AUDIO_FORMATS)
		return "???";
	return audio_format_names[i];
}

/** Iterate over each supported audio format. */
#define FOR_EACH_AUDIO_FORMAT(i) for (i = 0; i < NUM_AUDIO_FORMATS; i++)

/**
 * Tell whether an audio format handler provides chunk tables.
 *
 * Each audio format handler either provides a chunk table or supports dynamic
 * chunks.
 *
 * \param audio_format_id Offset in the afl array.
 *
 * \return True if dynamic chunks are supported, false if the audio format
 * handler provides chunk tables.
 */
bool afh_supports_dynamic_chunks(int audio_format_id)
{
	return afl[audio_format_id]->get_chunk;
}

/**
 * Guess the audio format judging from filename.
 *
 * This looks for matches in the \ref audio_format_handler::suffixes array
 * of each audio format handler. The function has to be implemented in this
 * file because the audio format handler array is a static variable.
 *
 * \param name The filename.
 *
 * \return This function returns -E_AUDIO_FORMAT if it has no idea what kind
 * of audio file this might be. Otherwise the (non-negative) number of the
 * audio format is returned.
 */
int guess_audio_format(const char *name)
{
	int i,j, len = strlen(name);

	FOR_EACH_AUDIO_FORMAT(i) {
		if (!afl[i]->get_file_info)
			continue;
		for (j = 0; afl[i]->suffixes[j]; j++) {
			const char *p = afl[i]->suffixes[j];
			int plen = strlen(p);
			if (len < plen + 1)
				continue;
			if (name[len - plen - 1] != '.')
				continue;
			if (strcasecmp(name + len - plen, p))
				continue;
//			PARA_DEBUG_LOG("might be %s\n", audio_format_name(i));
			return i;
		}
	}
	return -E_AUDIO_FORMAT;
}

static int get_file_info(int format, const char *path, char *data,
		size_t size, struct afh_info *afhi)
{
	int ret;
	const char *fmt = audio_format_name(format);

	memset(afhi, 0, sizeof(*afhi));
	ret = afl[format]->get_file_info(data, size, afhi);
	if (ret < 0) {
		PARA_WARNING_LOG("%s: %s format not detected: %s\n",
			path, fmt, para_strerror(-ret));
		return ret;
	}
	PARA_NOTICE_LOG("%s: detected %s format\n", path, fmt);
	return format;
}

/**
 * Initialize an instance of struct \ref afh_info.
 *
 * \param path The full path of the audio file.
 * \param data Pointer to the contents of the (mapped) file.
 * \param size The file size in bytes.
 * \param afhi Result pointer.
 *
 * This function guesses the audio format from the given pathname and calls
 * \ref audio_format_handler::get_file_info() of the audio format handler
 * that corresponds to this guess. If this doesn't work, all other audio
 * format handlers are tried until one is found that can handle the file or
 * we run out of audio format handlers to try.
 *
 * \return The number of the audio format on success, -E_AUDIO_FORMAT if no
 * compiled in audio format handler is able to handle the file.
 */
int compute_afhi(const char *path, char *data, size_t size,
		struct afh_info *afhi)
{
	int ret, i, format;

	format = guess_audio_format(path);
	if (format >= 0) {
		ret = get_file_info(format, path, data, size, afhi);
		if (ret >= 0)
			goto success;
	}
	FOR_EACH_AUDIO_FORMAT(i) {
		if (!afl[i]->get_file_info)
			continue;
		if (i == format) /* we already tried this one to no avail */
			continue;
		ret = get_file_info(i, path, data, size, afhi);
		if (ret >= 0)
			goto success;
	}
	return -E_AUDIO_FORMAT;
success:
	if (!afhi->techinfo)
		afhi->techinfo = para_strdup(NULL);
	if (!afhi->tags.artist)
		afhi->tags.artist = para_strdup(NULL);
	if (!afhi->tags.title)
		afhi->tags.title = para_strdup(NULL);
	if (!afhi->tags.year)
		afhi->tags.year = para_strdup(NULL);
	if (!afhi->tags.album)
		afhi->tags.album = para_strdup(NULL);
	if (!afhi->tags.comment)
		afhi->tags.comment = para_strdup(NULL);
	PARA_DEBUG_LOG("techinfo: %s\n", afhi->techinfo);
	PARA_DEBUG_LOG("artist: %s\n", afhi->tags.artist);
	PARA_DEBUG_LOG("title: %s\n", afhi->tags.title);
	PARA_DEBUG_LOG("year: %s\n", afhi->tags.year);
	PARA_DEBUG_LOG("album: %s\n", afhi->tags.album);
	PARA_DEBUG_LOG("comment: %s\n", afhi->tags.comment);
	return ret;
}

/**
 * Deallocate the contents of an afh_info structure.
 *
 * \param afhi The structure to clear.
 *
 * This only frees the memory the various pointer fields of the \ref afh_info
 * structure point to. It does *not* attempt to call free(3) on its argument.
 */
void clear_afhi(struct afh_info *afhi)
{
	if (!afhi)
		return;
	free(afhi->chunk_table);
	free(afhi->techinfo);
	free(afhi->tags.artist);
	free(afhi->tags.title);
	free(afhi->tags.year);
	free(afhi->tags.album);
	free(afhi->tags.comment);
}

static inline uint32_t get_chunk_len(long unsigned chunk_num,
		const struct afh_info *afhi)
{
	return afhi->chunk_table[chunk_num + 1] - afhi->chunk_table[chunk_num];
}

/**
 * Get one chunk of audio data.
 *
 * This implicitly calls \ref audio_format_handler::open at the first call.
 *
 * \param chunk_num The number of the chunk to get.
 * \param afhi As returned from \ref audio_format_handler::get_file_info().
 * \param audio_format_id Determines the audio format handler to use.
 * \param map The memory mapped audio file.
 * \param mapsize Passed to \ref audio_format_handler::open().
 * \param buf Result pointer.
 * \param len The length of the chunk in bytes.
 * \param afh_context Value/result, determines whether ->open() is called.
 *
 * \return Standard. On success, the buffer pointer points to a chunk of
 * memory inside of the memory mapping. It must not be freed by the caller.
 */
__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
		uint8_t audio_format_id, const void *map, size_t mapsize,
		const char **buf, uint32_t *len, void **afh_context)
{
	struct audio_format_handler *afh = afl[audio_format_id];

	if (afh_supports_dynamic_chunks(audio_format_id)) {
		int ret;

		if (!*afh_context) {
			ret = afh->open(map, mapsize, afh_context);
			if (ret < 0)
				return ret;
		}
		ret = afh->get_chunk(chunk_num, *afh_context,
			buf, len);
		if (ret < 0) {
			afh->close(*afh_context);
			*afh_context = NULL;
		}
		return ret;
	} else {
		size_t pos = afhi->chunk_table[chunk_num];
		*buf = map + pos;
		*len = get_chunk_len(chunk_num, afhi);
		return 0;
	}
}

/**
 * Deallocate resources (dynamic chunk handling only).
 *
 * This function should be called if \ref afh_get_chunk() was called at
 * least once. It is OK to call it even for audio formats which do not
 * support dynamic chunks, in which case the function does nothing.
 *
 * \param afh_context As returned from \ref audio_format_handler::open().
 * \param audio_format_id Determines the audio format handler to use.
 */
void afh_close(void *afh_context, uint8_t audio_format_id)
{
	struct audio_format_handler *afh = afl[audio_format_id];

	if (!afh_supports_dynamic_chunks(audio_format_id))
		return;
	if (!afh->close)
		return;
	if (!afh_context)
		return;
	afh->close(afh_context);
}

/**
 * Find a suitable start chunk.
 *
 * \param approx_chunk_num Upper bound for the chunk number to return.
 * \param afhi Needed for the chunk table.
 * \param audio_format_id Determines the afh.
 *
 * \return For audio format handlers which support dynamic chunks, the function
 * returns the given chunk number. Otherwise it returns the first non-empty
 * chunk <= approx_chunk_num.
 *
 * \sa \ref afh_get_chunk().
 */
int32_t afh_get_start_chunk(int32_t approx_chunk_num,
		const struct afh_info *afhi, uint8_t audio_format_id)
{
	int32_t k;

	if (afh_supports_dynamic_chunks(audio_format_id))
		return approx_chunk_num;

	for (k = PARA_MAX(0, approx_chunk_num); k >= 0; k--)
		if (get_chunk_len(k, afhi) > 0)
			return k;
	return 0;
}

/**
 * Get the header of an audio file.
 *
 * \param afhi The audio file handler data describing the file.
 * \param audio_format_id Determines the audio format handler.
 * \param map The data of the audio file.
 * \param mapsize The number of bytes of the mapped audio file.
 * \param buf Result: Points to a buffer containing the header on return.
 * \param len Result: The length of the header is stored here.
 *
 * This function sets the buffer pointer to NULL and the length to zero if
 * either input pointer is NULL, if the map size is zero or if the audio
 * format does not need special header treatment.
 *
 * Otherwise, if the audio format handler defines \ref
 * audio_format_handler::get_header(), this method is called to obtain the
 * header. Otherwise a reference to the first chunk is returned.
 *
 * Once the header is no longer needed, the caller must call \ref
 * afh_free_header() to free the resources allocated by this function.
 */
void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
		void *map, size_t mapsize, char **buf, size_t *len)
{
	struct audio_format_handler *afh = afl[audio_format_id];

	if (!map || !afhi || !afhi->header_len) {
		*buf = NULL;
		*len = 0;
		return;
	}
	if (!afh->get_header) {
		*len = afhi->header_len;
		*buf = map;
		return;
	}
	afh->get_header(map, mapsize, buf, len);
}

/**
 * Deallocate any resources obtained from afh_get_header().
 *
 * \param header_buf Pointer obtained via afh_get_header().
 * \param audio_format_id Determines the audio format handler.
 */
void afh_free_header(char *header_buf, uint8_t audio_format_id)
{
	struct audio_format_handler *afh = afl[audio_format_id];

	if (afh->get_header)
		free(header_buf);
}

/**
 * Pretty-print the contents of a struct \ref afh_info into a buffer.
 *
 * \param audio_format_num The audio format number.
 * \param afhi Pointer to the structure that contains the information.
 * \param result Return pointer.
 *
 * The result buffer is dynamically allocated and should be freed by the
 * caller.
 *
 * \return The number of bytes. This function never fails.
 */
unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result)
{
	return xasprintf(result, "%s: %dkbit/s\n" /* bitrate */
		"%s: %s\n" /* format */
		"%s: %dHz\n" /* frequency */
		"%s: %d\n" /* channels */
		"%s: %" PRIu32 "\n" /* seconds total */
		"%s: %lu: %lu\n" /* chunk time */
		"%s: %" PRIu32 "\n" /* num chunks */
		"%s: %" PRIu32 "\n" /* max chunk size */
		"%s: %s\n" /* techinfo */
		"%s: %s\n" /* artist */
		"%s: %s\n" /* title */
		"%s: %s\n" /* year */
		"%s: %s\n" /* album */
		"%s: %s\n", /* comment */
		status_item_list[SI_bitrate], afhi->bitrate,
		status_item_list[SI_format], audio_format_name(audio_format_num),
		status_item_list[SI_frequency], afhi->frequency,
		status_item_list[SI_channels], afhi->channels,
		status_item_list[SI_seconds_total], afhi->seconds_total,
		status_item_list[SI_chunk_time], (long unsigned)afhi->chunk_tv.tv_sec,
			(long unsigned)afhi->chunk_tv.tv_usec,
		status_item_list[SI_num_chunks], afhi->chunks_total,
		status_item_list[SI_max_chunk_size], afhi->max_chunk_size,
		status_item_list[SI_techinfo], afhi->techinfo? afhi->techinfo : "",
		status_item_list[SI_artist], afhi->tags.artist? afhi->tags.artist : "",
		status_item_list[SI_title], afhi->tags.title? afhi->tags.title : "",
		status_item_list[SI_year], afhi->tags.year? afhi->tags.year : "",
		status_item_list[SI_album], afhi->tags.album? afhi->tags.album : "",
		status_item_list[SI_comment], afhi->tags.comment? afhi->tags.comment : ""
	);
}

/**
 * Determine the maximal chunk size by iterating the chunk table.
 *
 * \param afhi Value/result.
 *
 * This function is called by the audio format handlers, for example when a
 * new audio file is added to the afs database. The chunk table is determined
 * from the given afhi pointer, and the maximal chunk size is returned via
 * the ->max_chunk_size member of the same pointer.
 */
void set_max_chunk_size(struct afh_info *afhi)
{
	uint32_t n, max = 0, old = 0;

	for (n = 0; n <= afhi->chunks_total; n++) {
		uint32_t val = afhi->chunk_table[n];
		/*
		 * If the first chunk is the header, do not consider it for the
		 * calculation of the largest chunk size.
		 */
		if (n == 0 || (n == 1 && afhi->header_len > 0)) {
			old = val;
			continue;
		}
		max = PARA_MAX(max, val - old);
		old = val;
	}
	afhi->max_chunk_size = max;
}

/**
 * Create a copy of the given file with altered meta tags.
 *
 * \param audio_format_id Determines which audio format handler to use.
 * \param map The (read-only) memory map of the input file.
 * \param mapsize The size of the input file in bytes.
 * \param tags The new tags.
 * \param output_fd Altered file is created using this file descriptor.
 *
 * This simply calls the ->rewrite_tags() method of the audio format handler
 * associated with the given ID, passing the remaining arguments verbatim.
 *
 * \return The return value of the underlying call to \ref
 * audio_format_handler::rewrite_tags().
 */
int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
		struct taginfo *tags, int output_fd)
{
	struct audio_format_handler *afh = afl[audio_format_id];
	return afh->rewrite_tags(map, mapsize, tags, output_fd);
}