/* SPDX-License-Identifier: GPL-2.0 */ /* * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker, * Ahead Software AG */ /** \file aac_afh.c para_server's aac audio format handler. */ #include #include "para.h" #include "mp4.h" #include "error.h" #include "portable_io.h" #include "afh.h" #include "string.h" #include "fd.h" struct aac_afh_context { const void *map; size_t mapsize; size_t fpos; struct mp4 *mp4; struct mp4_callback cb; }; static ssize_t aac_afh_read_cb(void *user_data, void *dest, size_t want) { struct aac_afh_context *c = user_data; size_t have, rv; if (want == 0 || c->fpos >= c->mapsize) return 0; have = c->mapsize - c->fpos; rv = PARA_MIN(have, want); memcpy(dest, c->map + c->fpos, rv); c->fpos += rv; return rv; } static off_t aac_afh_seek_cb(void *user_data, off_t offset, int whence) { struct aac_afh_context *c = user_data; if (whence == SEEK_SET) c->fpos = offset; else if (whence == SEEK_CUR) c->fpos += offset; else if (whence == SEEK_END) c->fpos = c->mapsize + offset; else assert(false); return c->fpos; } static int aac_afh_open(const void *map, size_t mapsize, void **afh_context) { int ret; struct aac_afh_context *c = alloc(sizeof(*c)); c->map = map; c->mapsize = mapsize; c->fpos = 0; c->cb.read = aac_afh_read_cb; c->cb.seek = aac_afh_seek_cb; c->cb.user_data = c; ret = mp4_open(&c->cb, &c->mp4); if (ret < 0) goto free_ctx; *afh_context = c; return 0; free_ctx: free(c); *afh_context = NULL; return ret; } static void aac_afh_close(void *afh_context) { struct aac_afh_context *c = afh_context; mp4_close(c->mp4); free(c); } static int aac_afh_get_chunk(uint32_t chunk_num, void *afh_context, const char **buf, uint32_t *len) { struct aac_afh_context *c = afh_context; uint32_t ss; size_t offset; int ret; ret = mp4_set_sample_position(c->mp4, chunk_num); if (ret < 0) return ret; offset = c->fpos; ret = mp4_get_sample_size(c->mp4, chunk_num, &ss); if (ret < 0) return ret; if (ss + offset > c->mapsize) /* file got truncated?! */ return -E_MP4_CORRUPT; *buf = c->map + offset; *len = ss; return 1; } static void aac_afh_get_taginfo(const struct mp4 *mp4, struct taginfo *tags) { tags->artist = mp4_get_tag_value(mp4, "artist"); tags->title = mp4_get_tag_value(mp4, "title"); tags->year = mp4_get_tag_value(mp4, "date"); tags->album = mp4_get_tag_value(mp4, "album"); tags->comment = mp4_get_tag_value(mp4, "comment"); } /* * Init m4a file and write some tech data to given pointers. */ static int aac_get_file_info(char *map, size_t numbytes, struct afh_info *afhi) { int ret; struct aac_afh_context *c; uint64_t milliseconds; const char *buf; uint32_t n, len; ret = aac_afh_open(map, numbytes, (void **)&c); if (ret < 0) return ret; afhi->frequency = mp4_get_sample_rate(c->mp4); assert(afhi->frequency > 0); afhi->channels = mp4_get_channel_count(c->mp4); assert(afhi->channels > 0); afhi->chunks_total = mp4_num_samples(c->mp4); assert(afhi->chunks_total > 0); afhi->max_chunk_size = 0; for (n = 0; n < afhi->chunks_total; n++) { ret = aac_afh_get_chunk(n, c, &buf, &len); if (ret < 0) goto out; afhi->max_chunk_size = PARA_MAX(afhi->max_chunk_size, len); } milliseconds = mp4_get_duration(c->mp4); afhi->seconds_total = milliseconds / 1000; ms2tv(milliseconds / afhi->chunks_total, &afhi->chunk_tv); if (aac_afh_get_chunk(0, c, &buf, &len) < 0) goto out; numbytes -= buf - map; afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000; aac_afh_get_taginfo(c->mp4, &afhi->tags); ret = 1; out: aac_afh_close(c); return ret; } static ssize_t aac_afh_meta_read_cb(void *user_data, void *dest, size_t want) { int fd = *(int *)user_data; return read(fd, dest, want); } static off_t aac_afh_meta_seek_cb(void *user_data, off_t offset, int whence) { int fd = *(int *)user_data; off_t ret = lseek(fd, offset, whence); assert(ret != (off_t)-1); return ret; } static ssize_t aac_afh_meta_write_cb(void *user_data, void *dest, size_t count) { int fd = *(int *)user_data; return write(fd, dest, count); } static int aac_afh_meta_truncate_cb(void *user_data) { int fd = *(int *)user_data; off_t offset = lseek(fd, 0, SEEK_CUR); assert(offset != (off_t)-1); return ftruncate(fd, offset); } static void replace_or_add_tag(const char *item, const char *value, struct mp4_metadata *meta) { uint32_t n; struct mp4_tag *t; for (n = 0; n < meta->count; n++) { t = meta->tags + n; if (strcasecmp(t->item, item)) continue; free(t->value); t->value = para_strdup(value); return; } /* item not found, add new tag */ meta->tags = para_realloc(meta->tags, (meta->count + 1) * sizeof(struct mp4_tag)); t = meta->tags + meta->count; t->item = para_strdup(item); t->value = para_strdup(value); meta->count++; } static int aac_rewrite_tags(const char *map, size_t mapsize, struct taginfo *tags, int fd) { int ret; struct mp4_metadata *metadata; struct mp4 *mp4; struct mp4_callback cb = { .read = aac_afh_meta_read_cb, .seek = aac_afh_meta_seek_cb, .write = aac_afh_meta_write_cb, .truncate = aac_afh_meta_truncate_cb, .user_data = &fd }; ret = write_all(fd, map, mapsize); if (ret < 0) return ret; assert(lseek(fd, 0, SEEK_SET) != (off_t)-1); ret = mp4_open_meta(&cb, &mp4); if (ret < 0) return ret; metadata = mp4_get_meta(mp4); PARA_NOTICE_LOG("%u metadata item(s) found\n", metadata->count); replace_or_add_tag("artist", tags->artist, metadata); replace_or_add_tag("title", tags->title, metadata); replace_or_add_tag("album", tags->album, metadata); replace_or_add_tag("date", tags->year, metadata); replace_or_add_tag("comment", tags->comment, metadata); ret = mp4_update_meta(mp4); mp4_close(mp4); return ret; } static const char * const aac_suffixes[] = {"m4a", "mp4", NULL}; /** * The audio format handler for the Advanced Audio Codec. * * This is only compiled in if the faad library is installed. */ const struct audio_format_handler aac_afh = { .get_file_info = aac_get_file_info, .suffixes = aac_suffixes, .rewrite_tags = aac_rewrite_tags, .open = aac_afh_open, .get_chunk = aac_afh_get_chunk, .close = aac_afh_close, };