/** \file aac_afh.c para_server's aac audio format handler. */
#include <regex.h>
-#include <mp4v2/mp4v2.h>
#include "para.h"
+
+/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */
+#define USE_TAGGING
#include <mp4ff.h>
+
#include "error.h"
#include "portable_io.h"
#include "afh.h"
#include "aac.h"
#include "fd.h"
+
struct aac_afh_context {
const void *map;
size_t mapsize;
*len = ss;
return 1;
}
-static int aac_find_stsz(char *buf, size_t buflen, size_t *skip)
-{
- int i;
-
- for (i = 0; i + 16 < buflen; i++) {
- char *p = buf + i;
- unsigned sample_count, sample_size;
-
- if (p[0] != 's' || p[1] != 't' || p[2] != 's' || p[3] != 'z')
- continue;
- PARA_DEBUG_LOG("found stsz@%d\n", i);
- i += 8;
- sample_size = read_u32_be(buf + i);
- PARA_DEBUG_LOG("sample size: %u\n", sample_size);
- i += 4;
- sample_count = read_u32_be(buf + i);
- i += 4;
- PARA_DEBUG_LOG("sample count: %u\n", sample_count);
- *skip = i;
- return sample_count;
- }
- return -E_STSZ;
-}
-static int atom_cmp(const char *buf1, const char *buf2)
+static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags)
{
- return memcmp(buf1, buf2, 4)? 1 : 0;
+ mp4ff_meta_get_artist(mp4ff, &tags->artist);
+ mp4ff_meta_get_title(mp4ff, &tags->title);
+ mp4ff_meta_get_date(mp4ff, &tags->year);
+ mp4ff_meta_get_album(mp4ff, &tags->album);
+ mp4ff_meta_get_comment(mp4ff, &tags->comment);
}
-static int read_atom_header(char *buf, uint64_t *subsize, char type[5])
+/*
+ * Init m4a file and write some tech data to given pointers.
+ */
+static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+ struct afh_info *afhi)
{
- uint64_t size = read_u32_be(buf);
+ int ret;
+ int32_t rv;
+ struct aac_afh_context *c;
+ int64_t tmp;
+ const char *buf;
+ size_t sz;
+ uint32_t n;
+
+ ret = aac_afh_open(map, numbytes, (void **)&c);
+ if (ret < 0)
+ return ret;
- memcpy(type, buf + 4, 4);
- type[4] = '\0';
+ ret = -E_MP4FF_BAD_SAMPLERATE;
+ rv = mp4ff_get_sample_rate(c->mp4ff, c->track);
+ if (rv <= 0)
+ goto close;
+ afhi->frequency = rv;
- PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type);
- if (size != 1) {
- *subsize = size;
- return 8;
- }
- buf += 4;
- size = 0;
- size = read_u64_be(buf);
- *subsize = size;
- return 16;
-}
+ ret = -E_MP4FF_BAD_CHANNEL_COUNT;
+ rv = mp4ff_get_channel_count(c->mp4ff, c->track);
+ if (rv <= 0)
+ goto close;
+ afhi->channels = rv;
-static char *get_tag(char *p, int size)
-{
- char *buf;
+ ret = -E_MP4FF_BAD_SAMPLE_COUNT;
+ rv = mp4ff_num_samples(c->mp4ff, c->track);
+ if (rv <= 0)
+ goto close;
+ afhi->chunks_total = rv;
+ afhi->max_chunk_size = 0;
+ for (n = 0; n < afhi->chunks_total; n++) {
+ if (aac_afh_get_chunk(n, c, &buf, &sz) < 0)
+ break;
+ afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz);
+ }
- assert(size > 0);
- buf = para_malloc(size + 1);
+ tmp = c->masc.sbr_present_flag == 1? 2048 : 1024;
+ afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency;
+ ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv);
- memcpy(buf, p, size);
- buf[size] = '\0';
- PARA_DEBUG_LOG("size: %d: %s\n", size, buf);
- return buf;
+ if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0)
+ numbytes -= buf - map;
+ afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000;
+ _aac_afh_get_taginfo(c->mp4ff, &afhi->tags);
+ ret = 1;
+close:
+ aac_afh_close(c);
+ return ret;
}
-static void read_tags(char *buf, size_t buflen, struct afh_info *afhi)
+static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want)
{
- char *p = buf;
-
- while (p + 32 < buf + buflen) {
- char *q, type1[5], type2[5];
- uint64_t size1, size2;
- int ret, ret2;
-
- ret = read_atom_header(p, &size1, type1);
- ret2 = read_atom_header(p + ret, &size2, type2);
-
- if (size2 <= 16 || atom_cmp(type2, "data")) {
- p += size1;
- continue;
- }
- size2 -= 16;
- q = p + ret + ret2 + 8;
- if (q + size2 > buf + buflen)
- break;
- if (!atom_cmp(type1, "\xa9" "ART"))
- afhi->tags.artist = get_tag(q, size2);
- else if (!atom_cmp(type1, "\xa9" "alb"))
- afhi->tags.album = get_tag(q, size2);
- else if (!atom_cmp(type1, "\xa9" "nam"))
- afhi->tags.title = get_tag(q, size2);
- else if (!atom_cmp(type1, "\xa9" "cmt"))
- afhi->tags.comment = get_tag(q, size2);
- else if (!atom_cmp(type1, "\xa9" "day"))
- afhi->tags.year = get_tag(q, size2);
- p += size1;
- }
+ int fd = *(int *)user_data;
+ return read(fd, dest, want);
}
-static void read_meta(char *buf, size_t buflen, struct afh_info *afhi)
+static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos)
{
- char *p = buf;
-
- while (p + 4 < buf + buflen) {
-
- if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') {
- p++;
- continue;
- }
- p += 4;
- return read_tags(p, buflen - (p - buf), afhi);
- }
+ int fd = *(int *)user_data;
+ return lseek(fd, pos, SEEK_SET);
}
-static void aac_get_taginfo(char *buf, size_t buflen, struct afh_info *afhi)
+static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want)
{
- int i;
- uint64_t subsize;
- char type[5];
-
- for (i = 0; i + 24 < buflen; i++) {
- char *p = buf + i;
- if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a')
- continue;
- PARA_INFO_LOG("found metadata at offset %d\n", i);
- i += 8;
- p = buf + i;
- i += read_atom_header(p, &subsize, type);
- p = buf + i;
- return read_meta(p, buflen - i, afhi);
- }
- PARA_INFO_LOG("no meta data\n");
+ int fd = *(int *)user_data;
+ return write(fd, dest, want);
}
-static ssize_t aac_compute_chunk_info(struct afh_info *afhi,
- char *map, size_t numbytes, mp4AudioSpecificConfig *mp4ASC)
+static uint32_t aac_afh_meta_truncate_cb(void *user_data)
{
- int ret, i;
- size_t skip;
- float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023;
- struct timeval total;
- long unsigned ms;
-
- afhi->chunk_table = NULL;
- ret = aac_find_stsz(map, numbytes, &skip);
- if (ret < 0)
- return ret;
- afhi->chunks_total = ret;
- afhi->max_chunk_size = 0;
- PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total);
- for (i = 1; i <= afhi->chunks_total; i++) {
- uint32_t val;
- if (skip + 4 > numbytes)
- break;
- val = read_u32_be(map + skip);
- afhi->max_chunk_size = PARA_MAX(afhi->max_chunk_size, val);
- skip += 4;
- }
-
- ms = 1000.0 * afhi->chunks_total * tmp / mp4ASC->samplingFrequency;
- ms2tv(ms, &total);
- tv_divide(afhi->chunks_total, &total, &afhi->chunk_tv);
- PARA_INFO_LOG("%luHz, %lus (%" PRIu32 " x %lums)\n",
- mp4ASC->samplingFrequency, ms / 1000,
- afhi->chunks_total, tv2ms(&afhi->chunk_tv));
- if (ms < 1000)
- return -E_MP4ASC;
- afhi->seconds_total = ms / 1000;
- ret = aac_find_entry_point(map, numbytes, &skip);
- if (ret < 0)
- return ret;
- ret = (numbytes - ret) * 8;
- ret += (afhi->channels * afhi->seconds_total * 500); /* avoid rounding error */
- afhi->bitrate = ret / (afhi->channels * afhi->seconds_total * 1000);
- return 1;
+ int fd = *(int *)user_data;
+ off_t offset = lseek(fd, 0, SEEK_CUR);
+ return ftruncate(fd, offset);
}
-/*
- * Init m4a file and write some tech data to given pointers.
- */
-static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
- struct afh_info *afhi)
+static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found)
{
- size_t skip;
- ssize_t ret;
- unsigned long rate = 0, decoder_len;
- unsigned char channels = 0;
- mp4AudioSpecificConfig mp4ASC;
- NeAACDecHandle handle = NULL;
-
- ret = aac_find_esds(map, numbytes, &skip, &decoder_len);
- if (ret < 0)
- goto out;
- aac_get_taginfo(map, numbytes, afhi);
- handle = aac_open();
- ret = -E_AAC_AFH_INIT;
- if (NeAACDecInit(handle, (unsigned char *)map + skip, decoder_len,
- &rate, &channels))
- goto out;
- if (!channels)
- goto out;
- afhi->channels = channels;
- afhi->frequency = rate;
- PARA_DEBUG_LOG("rate: %lu, channels: %d\n", rate, channels);
- ret = -E_MP4ASC;
- if (NeAACDecAudioSpecificConfig((unsigned char *)map + skip,
- numbytes - skip, &mp4ASC))
- goto out;
- if (!mp4ASC.samplingFrequency)
- goto out;
- ret = aac_compute_chunk_info(afhi, map, numbytes, &mp4ASC);
- if (ret < 0)
- goto out;
- ret = 1;
-out:
- if (handle)
- NeAACDecClose(handle);
- return ret;
+ free(tag->value);
+ tag->value = para_strdup(new_val);
+ *found = true;
}
-static int aac_rewrite_tags(const char *map, size_t mapsize,
- struct taginfo *tags, int fd, const char *filename)
+static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value)
{
- MP4FileHandle h;
- const MP4Tags *mdata;
- int ret = write_all(fd, map, mapsize);
+ md->tags[md->count].item = para_strdup(item);
+ md->tags[md->count].value = para_strdup(value);
+ md->count++;
+}
+static int aac_afh_rewrite_tags(const char *map, size_t mapsize,
+ struct taginfo *tags, int fd, __a_unused const char *filename)
+{
+ int ret, i;
+ int32_t rv;
+ mp4ff_metadata_t metadata;
+ mp4ff_t *mp4ff;
+ mp4ff_callback_t 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
+ };
+ bool found_artist = false, found_title = false, found_album = false,
+ found_year = false, found_comment = false;
+
+ ret = write_all(fd, map, mapsize);
if (ret < 0)
return ret;
lseek(fd, 0, SEEK_SET);
- h = MP4Modify(filename, 0);
- if (!h) {
- PARA_ERROR_LOG("MP4Modify() failed, fd = %d\n", fd);
- return -E_MP4V2;
- }
- mdata = MP4TagsAlloc();
- assert(mdata);
- if (!MP4TagsFetch(mdata, h)) {
- PARA_ERROR_LOG("MP4Tags_Fetch() failed\n");
- ret = -E_MP4V2;
- goto close;
- }
- if (!MP4TagsSetAlbum(mdata, tags->album)) {
- PARA_ERROR_LOG("Could not set album\n");
- ret = -E_MP4V2;
- goto tags_free;
- }
- if (!MP4TagsSetArtist(mdata, tags->artist)) {
- PARA_ERROR_LOG("Could not set album\n");
- ret = -E_MP4V2;
- goto tags_free;
- }
- if (!MP4TagsSetComments(mdata, tags->comment)) {
- PARA_ERROR_LOG("Could not set comment\n");
- ret = -E_MP4V2;
- goto tags_free;
- }
- if (!MP4TagsSetName(mdata, tags->title)) {
- PARA_ERROR_LOG("Could not set title\n");
- ret = -E_MP4V2;
- goto tags_free;
- }
- if (!MP4TagsSetReleaseDate(mdata, tags->year)) {
- PARA_ERROR_LOG("Could not set release date\n");
- ret = -E_MP4V2;
- goto tags_free;
- }
+ mp4ff = mp4ff_open_read_metaonly(&cb);
+ if (!mp4ff)
+ return -E_MP4FF_OPEN;
- if (!MP4TagsStore(mdata, h)) {
- PARA_ERROR_LOG("Could not store tags\n");
- ret = -E_MP4V2;
- goto tags_free;
+ ret = -E_MP4FF_META_READ;
+ rv = mp4ff_meta_get_num_items(mp4ff);
+ if (rv < 0)
+ goto close;
+ metadata.count = rv;
+ PARA_NOTICE_LOG("%d metadata item(s) found\n", rv);
+
+ metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t));
+ for (i = 0; i < metadata.count; i++) {
+ mp4ff_tag_t *tag = metadata.tags + i;
+
+ ret = -E_MP4FF_META_READ;
+ if (mp4ff_meta_get_by_index(mp4ff, i,
+ &tag->item, &tag->value) < 0)
+ goto free_tags;
+ PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value);
+ if (!strcmp(tag->item, "artist"))
+ replace_tag(tag, tags->artist, &found_artist);
+ else if (!strcmp(tag->item, "title"))
+ replace_tag(tag, tags->title, &found_title);
+ else if (!strcmp(tag->item, "album"))
+ replace_tag(tag, tags->album, &found_album);
+ else if (!strcmp(tag->item, "date"))
+ replace_tag(tag, tags->year, &found_year);
+ else if (!strcmp(tag->item, "comment"))
+ replace_tag(tag, tags->comment, &found_comment);
}
+ if (!found_artist)
+ add_tag(&metadata, "artist", tags->artist);
+ if (!found_title)
+ add_tag(&metadata, "title", tags->title);
+ if (!found_album)
+ add_tag(&metadata, "album", tags->album);
+ if (!found_year)
+ add_tag(&metadata, "date", tags->year);
+ if (!found_comment)
+ add_tag(&metadata, "comment", tags->comment);
+ ret = -E_MP4FF_META_WRITE;
+ if (mp4ff_meta_update(&cb, &metadata) < 0)
+ goto free_tags;
ret = 1;
-tags_free:
- MP4TagsFree(mdata);
+free_tags:
+ for (; i > 0; i--) {
+ free(metadata.tags[i - 1].item);
+ free(metadata.tags[i - 1].value);
+ }
+ free(metadata.tags);
close:
- MP4Close(h, 0);
+ mp4ff_close(mp4ff);
return ret;
}
{
afh->get_file_info = aac_get_file_info,
afh->suffixes = aac_suffixes;
- afh->rewrite_tags = aac_rewrite_tags;
+ afh->rewrite_tags = aac_afh_rewrite_tags;
afh->open = aac_afh_open;
afh->get_chunk = aac_afh_get_chunk;
afh->close = aac_afh_close;