/* SPDX-License-Identifier: GPL-2.0 */ /** \file afh_common.c Common audio format handler functions. */ #include /* mmap */ #include #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); }