readline_cppflags := @readline_cppflags@
alsa_cppflags := @alsa_cppflags@
oss_cppflags := @oss_cppflags@
-mp4v2_cppflags := @mp4v2_cppflags@
id3tag_ldflags := @id3tag_ldflags@
ogg_ldflags := @ogg_ldflags@
curses_ldflags := @curses_ldflags@
crypto_ldflags := @crypto_ldflags@
iconv_ldflags := @iconv_ldflags@
-mp4v2_ldflags := @mp4v2_ldflags@
include Makefile.real
$(object_dir)/crypt.o $(dep_dir)/crypt.d: CPPFLAGS += $(openssl_cppflags)
$(object_dir)/gcrypt.o $(dep_dir)/gcrypt.d: CPPFLAGS += $(gcrypt_cppflags)
$(object_dir)/ao_write.o $(dep_dir)/ao_write.d: CPPFLAGS += $(ao_cppflags)
-$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d: CPPFLAGS += $(mp4v2_cppflags)
$(object_dir)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags)
$(object_dir)/interactive.o $(dep_dir)/interactive.d \
: CPPFLAGS += $(mad_cppflags)
$(object_dir)/aacdec_filter.o $(dep_dir)/aacdec_filter.d \
-$(object_dir)/aac_common.o $(dep_dir)/aac_common.d \
$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d \
: CPPFLAGS += $(faad_cppflags)
$(faad_ldflags) \
$(flac_ldflags)
-para_server \
-para_play \
-para_afh \
-para_recv \
-: LDFLAGS += \
- $(mp4v2_ldflags)
-
para_afh para_recv para_server para_play: LDFLAGS += $(iconv_ldflags)
$(foreach exe,$(executables),$(eval para_$(exe): $$($(exe)_objs)))
installation directory. This feature is orthogonal to the --prefix
option to configure.
- Minor WMA cleanups.
+- The aac audio format handler has been rewritten to use the mp4ff library.
+ See the manual for how to install the library on your system.
+- New status item: max_chunk_size. The value is stored in a previously
+ unused field of the afhi object of the aft table. Although backwards
+ compatible, users are encouraged to re-add m4a files to populate
+ the new field.
+- No more chunk tables for aac. Chunk boundaries are determined
+ dynamically at stream time.
Downloads:
[tarball](./releases/paraslash-git.tar.bz2),
+++ /dev/null
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file aac.h Exported symbols from aac_common.c. */
-
-#include <neaacdec.h>
-
-NeAACDecHandle aac_open(void);
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
- unsigned long *decoder_length);
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip);
/** \file aac_afh.c para_server's aac audio format handler. */
#include <regex.h>
-#include <mp4v2/mp4v2.h>
+#include <neaacdec.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 "string.h"
-#include "aac.h"
#include "fd.h"
-static int aac_find_stsz(char *buf, size_t buflen, off_t *skip)
+
+struct aac_afh_context {
+ const void *map;
+ size_t mapsize;
+ size_t fpos;
+ int32_t track;
+ mp4ff_t *mp4ff;
+ mp4AudioSpecificConfig masc;
+ mp4ff_callback_t cb;
+};
+
+static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want)
{
- 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;
+ struct aac_afh_context *c = user_data;
+ uint32_t have, rv;
+
+ if (want == 0 || c->fpos >= c->mapsize) {
+ PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want,
+ c->fpos);
+ errno = EAGAIN;
+ return -1;
}
- return -E_STSZ;
+ have = c->mapsize - c->fpos;
+ rv = PARA_MIN(have, want);
+ PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos);
+ memcpy(dest, c->map + c->fpos, rv);
+ c->fpos += rv;
+ return rv;
}
-static int atom_cmp(const char *buf1, const char *buf2)
+static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos)
{
- return memcmp(buf1, buf2, 4)? 1 : 0;
+ struct aac_afh_context *c = user_data;
+ c->fpos = pos;
+ return 0;
}
-static int read_atom_header(char *buf, uint64_t *subsize, char type[5])
+static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc)
{
- uint64_t size = read_u32_be(buf);
-
- memcpy(type, buf + 4, 4);
- type[4] = '\0';
-
- PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type);
- if (size != 1) {
- *subsize = size;
- return 8;
+ int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff);
+
+ assert(num_tracks >= 0);
+ for (i = 0; i < num_tracks; i++) {
+ unsigned char *buf = NULL;
+ unsigned buf_size = 0;
+
+ mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size);
+ if (buf) {
+ rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc);
+ free(buf);
+ if (rc < 0)
+ continue;
+ return i;
+ }
}
- buf += 4;
- size = 0;
- size = read_u64_be(buf);
- *subsize = size;
- return 16;
+ return -1; /* no audio track */
}
-static char *get_tag(char *p, int size)
+static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
{
- char *buf;
-
- assert(size > 0);
- buf = para_malloc(size + 1);
-
- memcpy(buf, p, size);
- buf[size] = '\0';
- PARA_DEBUG_LOG("size: %d: %s\n", size, buf);
- return buf;
+ int ret;
+ struct aac_afh_context *c = para_malloc(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 = -E_MP4FF_OPEN;
+ c->mp4ff = mp4ff_open_read(&c->cb);
+ if (!c->mp4ff)
+ goto free_ctx;
+ c->track = aac_afh_get_track(c->mp4ff, &c->masc);
+ ret = -E_MP4FF_TRACK;
+ if (c->track < 0)
+ goto close_mp4ff;
+ *afh_context = c;
+ return 0;
+close_mp4ff:
+ mp4ff_close(c->mp4ff);
+free_ctx:
+ free(c);
+ *afh_context = NULL;
+ return ret;
}
-static void read_tags(char *buf, size_t buflen, struct afh_info *afhi)
+static void aac_afh_close(void *afh_context)
{
- char *p = buf;
+ struct aac_afh_context *c = afh_context;
+ mp4ff_close(c->mp4ff);
+ free(c);
+}
- while (p + 32 < buf + buflen) {
- char *q, type1[5], type2[5];
- uint64_t size1, size2;
- int ret, ret2;
+/**
+ * Libmp4ff function to reposition the file to the given sample.
+ *
+ * \param f The opaque handle returned by mp4ff_open_read().
+ * \param track The number of the (audio) track.
+ * \param sample Destination.
+ *
+ * We need this function to obtain the offset of the sample within the audio
+ * file. Unfortunately, it is not exposed in the mp4ff header.
+ *
+ * \return This function always returns 0.
+ */
+int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample);
- ret = read_atom_header(p, &size1, type1);
- ret2 = read_atom_header(p + ret, &size2, type2);
+static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context,
+ const char **buf, size_t *len)
+{
+ struct aac_afh_context *c = afh_context;
+ int32_t ss;
+ size_t offset;
+
+ assert(chunk_num <= INT_MAX);
+ /* this function always returns zero */
+ mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num);
+ offset = c->fpos;
+ ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num);
+ if (ss <= 0)
+ return -E_MP4FF_BAD_SAMPLE;
+ assert(ss + offset <= c->mapsize);
+ *buf = c->map + offset;
+ *len = ss;
+ return 1;
+}
- 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;
- }
+static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags)
+{
+ 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 void read_meta(char *buf, size_t buflen, struct afh_info *afhi)
+/*
+ * 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)
{
- char *p = 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;
- while (p + 4 < buf + buflen) {
+ ret = -E_MP4FF_BAD_SAMPLERATE;
+ rv = mp4ff_get_sample_rate(c->mp4ff, c->track);
+ if (rv <= 0)
+ goto close;
+ afhi->frequency = rv;
- 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);
+ ret = -E_MP4FF_BAD_CHANNEL_COUNT;
+ rv = mp4ff_get_channel_count(c->mp4ff, c->track);
+ if (rv <= 0)
+ goto close;
+ afhi->channels = rv;
+
+ 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);
}
+
+ 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);
+
+ 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 aac_get_taginfo(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)
{
- 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 read(fd, dest, want);
}
-static ssize_t aac_compute_chunk_table(struct afh_info *afhi,
- char *map, size_t numbytes)
+static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos)
{
- int ret, i;
- size_t sum = 0;
- off_t skip;
+ int fd = *(int *)user_data;
+ return lseek(fd, pos, SEEK_SET);
+}
- ret = aac_find_stsz(map, numbytes, &skip);
- if (ret < 0)
- return ret;
- afhi->chunks_total = ret;
- PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total);
- afhi->chunk_table = para_malloc((afhi->chunks_total + 1) * sizeof(size_t));
- for (i = 1; i <= afhi->chunks_total; i++) {
- if (skip + 4 > numbytes)
- break;
- sum += read_u32_be(map + skip);
- afhi->chunk_table[i] = sum;
- skip += 4;
-// if (i < 10 || i + 10 > afhi->chunks_total)
-// PARA_DEBUG_LOG("offset #%d: %zu\n", i, afhi->chunk_table[i]);
- }
- return skip;
+static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want)
+{
+ int fd = *(int *)user_data;
+ return write(fd, dest, want);
}
-static int aac_set_chunk_tv(struct afh_info *afhi,
- mp4AudioSpecificConfig *mp4ASC, uint32_t *seconds)
+static uint32_t aac_afh_meta_truncate_cb(void *user_data)
{
- float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023;
- struct timeval total;
- long unsigned ms;
-
- if (!mp4ASC->samplingFrequency)
- return -E_MP4ASC;
- 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;
- *seconds = ms / 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)
{
- int i;
- 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;
- 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_table(afhi, map, numbytes);
- if (ret < 0)
- goto out;
- skip = ret;
- ret = aac_set_chunk_tv(afhi, &mp4ASC, &afhi->seconds_total);
- if (ret < 0)
- goto out;
- ret = aac_find_entry_point(map + skip, numbytes - skip, &skip);
- if (ret < 0)
- goto out;
- afhi->chunk_table[0] = ret;
- for (i = 1; i<= afhi->chunks_total; i++)
- afhi->chunk_table[i] += ret;
- afhi->channels = channels;
- afhi->frequency = rate;
- ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
- ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */
- afhi->bitrate = ret / (channels * afhi->seconds_total * 1000);
- 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;
}
+++ /dev/null
-/*
- * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-/*
- * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker,
- * Ahead Software AG
- */
-
-/** \file aac_common.c Common functions of aac_afh and aadcec. */
-
-#include "para.h"
-#include "aac.h"
-#include "error.h"
-#include "portable_io.h"
-
-/**
- * Get a new libfaad decoder handle.
- *
- * \return The handle returned by NeAACDecOpen().
- */
-NeAACDecHandle aac_open(void)
-{
- NeAACDecHandle h = NeAACDecOpen();
- NeAACDecConfigurationPtr c = NeAACDecGetCurrentConfiguration(h);
-
- c->defObjectType = LC;
- c->outputFormat = FAAD_FMT_16BIT;
- c->downMatrix = 0;
- NeAACDecSetConfiguration(h, c);
- return h;
-}
-
-static unsigned long aac_read_decoder_length(char *buf, int *description_len)
-{
- uint8_t b;
- uint8_t numBytes = 0;
- unsigned long length = 0;
-
- do {
- b = buf[numBytes];
- numBytes++;
- length = (length << 7) | (b & 0x7F);
- } while
- ((b & 0x80) && numBytes < 4);
- *description_len = numBytes;
- return length;
-}
-
-/**
- * search for the position and the length of the decoder configuration
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the offset in \a buf where
- * the decoder config starts.
- * \param decoder_length result pointer that is filled in with the length of
- * the decoder configuration on success.
- *
- * \return positive on success, negative on errors
- */
-int aac_find_esds(char *buf, size_t buflen, size_t *skip,
- unsigned long *decoder_length)
-{
- size_t i;
-
- for (i = 0; i + 4 < buflen; i++) {
- char *p = buf + i;
- int description_len;
-
- if (p[0] != 'e' || p[1] != 's' || p[2] != 'd' || p[3] != 's')
- continue;
- i += 8;
- p = buf + i;
- PARA_INFO_LOG("found esds@%zu, next: %x\n", i, (unsigned)*p);
- if (*p == 3)
- i += 8;
- else
- i += 6;
- p = buf + i;
- PARA_INFO_LOG("next: %x\n", (unsigned)*p);
- if (*p != 4)
- continue;
- i += 18;
- p = buf + i;
- PARA_INFO_LOG("next: %x\n", (unsigned)*p);
- if (*p != 5)
- continue;
- i++;
- p = buf + i;
- *decoder_length = aac_read_decoder_length(p, &description_len);
- PARA_INFO_LOG("decoder length: %lu\n", *decoder_length);
- i += description_len;
- *skip = i;
- return 1;
- }
- return -E_ESDS;
-}
-
-/**
- * search for the first entry in the stco table
- *
- * \param buf buffer to seach
- * \param buflen length of \a buf
- * \param skip Upon succesful return, this contains the number
- * of bytes to skip from the input buffer.
- *
- * \return the position of the first entry in the table on success,
- * -E_STCO on errors.
- */
-ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip)
-{
- ssize_t ret;
- size_t i;
-
- for (i = 0; i + 20 < buflen; i++) {
- char *p = buf + i;
-
- if (p[0] != 's' || p[1] != 't' || p[2] != 'c' || p[3] != 'o')
- continue;
- PARA_INFO_LOG("found stco@%zu\n", i);
- i += 12;
- ret = read_u32_be(buf + i); /* first offset */
- i += 4;
- PARA_INFO_LOG("entry point: %zd\n", ret);
- *skip = i;
- return ret;
- }
- PARA_WARNING_LOG("stco not found, buflen: %zu\n", buflen);
- return -E_STCO;
-}
/** \file aacdec_filter.c paraslash's aac (m4a) decoder. */
#include <regex.h>
+#include <neaacdec.h>
#include "para.h"
+#include "portable_io.h"
#include "list.h"
#include "sched.h"
#include "ggo.h"
#include "filter.h"
#include "error.h"
#include "string.h"
-#include "aac.h"
/** Give up decoding after that many errors. */
#define MAX_ERRORS 20
struct private_aacdec_data {
/** the return value of aac_open */
NeAACDecHandle handle;
- /** info about the currently decoded frame */
- NeAACDecFrameInfo frame_info;
/** whether this instance of the aac decoder is already initialized */
- int initialized;
- /**
- * return value of aac_find_esds(). Used to call the right aacdec
- * init function
- */
- unsigned long decoder_length;
+ bool initialized;
/** number of times the decoder returned an error */
unsigned error_count;
/** number of bytes already consumed from the imput stream */
size_t consumed_total;
- /** return value of aac_find_entry_point */
- size_t entry;
/** The number of channels of the current stream. */
unsigned int channels;
/** Current sample rate in Hz. */
static void aacdec_open(struct filter_node *fn)
{
+ NeAACDecConfigurationPtr c;
struct private_aacdec_data *padd = para_calloc(sizeof(*padd));
+ padd->handle = NeAACDecOpen();
+ c = NeAACDecGetCurrentConfiguration(padd->handle);
+ c->defObjectType = LC;
+ c->outputFormat = FAAD_FMT_16BIT;
+ c->downMatrix = 0;
+ NeAACDecSetConfiguration(padd->handle, c);
+
fn->private_data = padd;
fn->min_iqs = 2048;
- padd->handle = aac_open();
}
static void aacdec_close(struct filter_node *fn)
struct btr_node *btrn = fn->btrn;
struct private_aacdec_data *padd = fn->private_data;
int i, ret;
- char *p, *inbuf, *outbuffer;
- char *btr_buf;
- size_t len, skip, consumed, loaded;
+ char *inbuf, *outbuf, *btrbuf;
+ size_t len, consumed, loaded = 0;
+ NeAACDecFrameInfo frame_info;
next_buffer:
ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
if (!padd->initialized) {
unsigned long rate = 0;
unsigned char channels = 0;
- ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length);
+ ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
+ len, &rate, &channels);
+ PARA_INFO_LOG("decoder init: %d\n", ret);
if (ret < 0) {
- PARA_INFO_LOG("%s\n", para_strerror(-ret));
- ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf,
- len, &rate, &channels);
- PARA_INFO_LOG("decoder init: %d\n", ret);
- if (ret < 0) {
- ret = -E_AACDEC_INIT;
- goto out;
- }
- consumed = ret;
- } else {
- PARA_INFO_LOG("decoder len: %lu\n",
- padd->decoder_length);
- consumed += skip;
- p = inbuf + consumed;
ret = -E_AACDEC_INIT;
- if (NeAACDecInit2(padd->handle, (unsigned char *)p,
- padd->decoder_length, &rate,
- &channels) != 0)
- goto out;
+ goto err;
}
+ consumed = ret;
padd->sample_rate = rate;
padd->channels = channels;
PARA_INFO_LOG("rate: %u, channels: %u\n",
padd->sample_rate, padd->channels);
- padd->initialized = 1;
+ padd->initialized = true;
}
- if (padd->decoder_length > 0) {
- consumed = 0;
- if (!padd->entry) {
- ret = aac_find_entry_point(inbuf + consumed,
- len - consumed, &skip);
- if (ret < 0) {
- ret = len;
- goto out;
- }
- consumed += skip;
- padd->entry = ret;
- PARA_INFO_LOG("entry: %zu\n", padd->entry);
- }
- ret = len;
- if (padd->consumed_total + len < padd->entry)
- goto out;
- if (padd->consumed_total < padd->entry)
- consumed = padd->entry - padd->consumed_total;
- }
- for (; consumed < len; consumed++)
- if ((inbuf[consumed] & 0xfe) == 0x20)
- break;
if (consumed >= len)
goto success;
- p = inbuf + consumed;
//PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed,
// padd->consumed_total, consumed, len - consumed);
- outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info,
- (unsigned char *)p, len - consumed);
- if (padd->frame_info.error) {
- int err = padd->frame_info.error;
+ outbuf = NeAACDecDecode(padd->handle, &frame_info,
+ (unsigned char *)inbuf + consumed, len - consumed);
+ if (frame_info.error) {
+ int err = frame_info.error;
ret = -E_AAC_DECODE;
if (padd->error_count++ > MAX_ERRORS)
goto err;
- /* Suppress non-fatal bitstream error message at BOF/EOF */
- if (len < fn->min_iqs || padd->consumed_total == 0) {
- consumed = len;
- goto success;
- }
- PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err));
- PARA_ERROR_LOG("consumed: %zu + %zu + %lu\n",
+ PARA_NOTICE_LOG("error #%u: (%s)\n", padd->error_count,
+ NeAACDecGetErrorMessage(err));
+ PARA_NOTICE_LOG("consumed (total, buffer, frame): "
+ "%zu, %zu, %lu\n",
padd->consumed_total, consumed,
- padd->frame_info.bytesconsumed);
- if (consumed < len)
- consumed++; /* catch 21 */
+ frame_info.bytesconsumed);
+ consumed++; /* just eat one byte and hope for the best */
goto success;
}
padd->error_count = 0;
- //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed);
- consumed += padd->frame_info.bytesconsumed;
- ret = consumed;
- if (!padd->frame_info.samples)
- goto out;
- btr_buf = para_malloc(2 * padd->frame_info.samples);
- loaded = 0;
- for (i = 0; i < padd->frame_info.samples; i++) {
- short sh = ((short *)outbuffer)[i];
- write_int16_host_endian(btr_buf + loaded, sh);
+ //PARA_CRIT_LOG("decoder ate %lu\n", frame_info.bytesconsumed);
+ consumed += frame_info.bytesconsumed;
+ if (!frame_info.samples)
+ goto success;
+ btrbuf = para_malloc(2 * frame_info.samples);
+ for (i = 0; i < frame_info.samples; i++) {
+ short sh = ((short *)outbuf)[i];
+ write_int16_host_endian(btrbuf + loaded, sh);
loaded += 2;
}
- btr_add_output(btr_buf, loaded, btrn);
+ btr_add_output(btrbuf, loaded, btrn);
success:
- ret = consumed;
-out:
- if (ret >= 0) {
- padd->consumed_total += ret;
- btr_consume(btrn, ret);
+ btr_consume(btrn, consumed);
+ padd->consumed_total += consumed;
+ if (loaded == 0)
goto next_buffer;
- }
+ return 1;
err:
assert(ret < 0);
btr_remove_node(&fn->btrn);
free(msg);
}
-static void print_chunk_table(struct afh_info *afhi)
+static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
+ const void *map, size_t mapsize)
{
- int i;
+ int i, ret;
+ void *ctx = NULL;
- if (conf.parser_friendly_given) {
- printf("chunk_table: ");
- for (i = 0; i <= afhi->chunks_total; i++)
- printf("%u ", afhi->chunk_table[i]);
- printf("\n");
- return;
- }
- for (i = 1; i <= afhi->chunks_total; i++) {
+ for (i = 0; i < afhi->chunks_total; i++) {
struct timeval tv;
long unsigned from, to;
- tv_scale(i - 1, &afhi->chunk_tv, &tv);
- from = tv2ms(&tv);
+ const char *buf;
+ size_t len;
tv_scale(i, &afhi->chunk_tv, &tv);
+ from = tv2ms(&tv);
+ tv_scale(i + 1, &afhi->chunk_tv, &tv);
to = tv2ms(&tv);
- printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1,
- from / 1000, from % 1000, to / 1000, to % 1000,
- afhi->chunk_table[i - 1], afhi->chunk_table[i],
- afhi->chunk_table[i] - afhi->chunk_table[i - 1]);
+ ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize,
+ &buf, &len, &ctx);
+ if (ret < 0) {
+ PARA_ERROR_LOG("fatal: chunk %d: %s\n", i,
+ para_strerror(-ret));
+ return;
+ }
+ if (!conf.parser_friendly_given)
+ printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000,
+ from % 1000, to / 1000, to % 1000);
+ printf("%td - %td", buf - (const char *)map,
+ buf + len - (const char *)map);
+ if (!conf.parser_friendly_given)
+ printf(" (%zu)", len);
+ printf("\n");
}
+ afh_close(ctx, audio_format_id);
}
__noreturn static void print_help_and_die(void)
printf("File %d: %s\n", i + 1, conf.inputs[i]);
print_info(audio_format_num, &afhi);
if (conf.chunk_table_given)
- print_chunk_table(&afhi);
- printf("\n");
+ print_chunk_table(&afhi, audio_format_num,
+ audio_file_data, audio_file_size);
}
clear_afhi(&afhi);
}
* the current audio file.
*/
uint32_t *chunk_table;
+ /** Size of the largest chunk, introduced in v0.6.0. */
+ uint32_t max_chunk_size;
/** Period of time between sending data chunks. */
struct timeval chunk_tv;
/**
int fd;
/** Vss needs this for streaming. */
struct afh_info afhi;
- /** Size of the largest chunk. */
+ /**
+ * Size of the largest chunk. Superseded by afhi->max_chunk_size. May
+ * be removed after v0.6.1.
+ */
uint32_t max_chunk_size;
/** Needed to get the audio file header. */
uint8_t audio_format_id;
struct afh_info *afhi);
/** Optional, used for header-rewriting. See \ref afh_get_header(). */
void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len);
+ /**
+ * An audio format handler may signify support for dynamic chunks by
+ * defining ->get_chunk below. In this case the vss calls ->open() at
+ * BOS, ->get_chunk() for each chunk while streaming, and ->close() at
+ * EOS. The chunk table is not accessed at all.
+ *
+ * The function may return its (opaque) context through the last
+ * argument. The returned pointer is passed to subsequent calls to
+ * ->get_chunk() and ->close().
+ */
+ int (*open)(const void *map, size_t mapsize, void **afh_context);
+ /**
+ * Return a reference to one chunk. The returned pointer points to a
+ * portion of the memory mapped audio file. The caller must not call
+ * free() on it.
+ */
+ int (*get_chunk)(long unsigned chunk_num, void *afh_context,
+ const char **buf, size_t *len);
+ /** Deallocate the resources occupied by ->open(). */
+ void (*close)(void *afh_context);
/**
* Write audio file with altered tags, optional.
*
int compute_afhi(const char *path, char *data, size_t size,
int fd, struct afh_info *afhi);
const char *audio_format_name(int);
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
- void *map, const char **buf, size_t *len);
+__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, size_t *len, void **afh_context);
+void afh_close(void *afh_context, uint8_t audio_format_id);
int32_t afh_get_start_chunk(int32_t approx_chunk_num,
- const struct afh_info *afhi);
+ const struct afh_info *afhi, uint8_t audio_format_id);
void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id,
void *map, size_t mapsize, char **buf, size_t *len);
void afh_free_header(char *header_buf, uint8_t audio_format_id);
unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result);
int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize,
struct taginfo *tags, int output_fd, const char *filename);
+void set_max_chunk_size(struct afh_info *afhi);
+bool afh_supports_dynamic_chunks(int audio_format_id);
},
{
.name = "aac",
-#if defined(HAVE_MP4V2)
+#if defined(HAVE_FAAD)
.init = aac_afh_init,
#endif
},
if (afl[format].init)
return format;
}
-
}
/** Iterate over each supported audio format. */
}
}
+/**
+ * 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.
*
/**
* Get one chunk of audio data.
*
+ * This implicitly calls the ->open method of the audio format handler at the
+ * first call.
+ *
* \param chunk_num The number of the chunk to get.
* \param afhi Describes the audio file.
+ * \param audio_format_id Determines the afh.
* \param map The memory mapped audio file.
+ * \param mapsize Passed to the afh's ->open() method.
* \param buf Result pointer.
* \param len The length of the chunk in bytes.
+ * \param afh_context Value/result, determines whether ->open() is called.
*
* Upon return, \a buf will point so memory inside \a map. The returned buffer
* must therefore not be freed by the caller.
+ *
+ * \return Standard.
+ */
+__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, size_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 = afl[audio_format_id].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 allocated due to dynamic chunk handling.
+ *
+ * This function should be called if 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 the ->open method of the afh.
+ * \param audio_format_id Determines the afh.
*/
-void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
- void *map, const char **buf, size_t *len)
+void afh_close(void *afh_context, uint8_t audio_format_id)
{
- size_t pos = afhi->chunk_table[chunk_num];
- *buf = map + pos;
- *len = get_chunk_len(chunk_num, afhi);
+ 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);
}
/**
*
* \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 The first non-empty chunk <= \a approx_chunk_num.
+ * \return For audio format handlers which support dynamic chunks, the function
+ * returns the given chunk number. Otherwise it returns the first non-empty
+ * chunk <= \a approx_chunk_num.
*
* \sa \ref afh_get_chunk().
*/
int32_t afh_get_start_chunk(int32_t approx_chunk_num,
- const struct afh_info *afhi)
+ 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;
"%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 */
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 : "",
);
}
+/**
+ * Determine the maximal chunk size by investigating the chunk table.
+ *
+ * \param afhi Value/result.
+ *
+ * This function iterates over the chunk table and sets ->max_chunk_size
+ * accordingly. The function exists only for backward compatibility since as of
+ * version 0.6.0, para_server stores the maximal chunk size in its database.
+ * This function is only called if the database value is zero, indicating that
+ * the file was added by an older server version.
+ */
+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.
*
long unsigned last_chunk;
struct timeval stream_start;
uint32_t current_chunk;
+ void *afh_context;
};
static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
return ret;
if (x >= pard->afhi.chunks_total)
return -ERRNO_TO_PARA_ERROR(EINVAL);
- pard->first_chunk = afh_get_start_chunk(x, &pard->afhi);
+ pard->first_chunk = afh_get_start_chunk(x, &pard->afhi,
+ pard->audio_format_num);
pard->current_chunk = pard->first_chunk;
return 1;
}
goto out_clear_afhi;
if (conf->begin_chunk_arg >= 0)
pard->first_chunk = afh_get_start_chunk(
- conf->begin_chunk_arg, &pard->afhi);
+ conf->begin_chunk_arg, &pard->afhi,
+ pard->audio_format_num);
else
pard->first_chunk = afh_get_start_chunk(
afhi->chunks_total + conf->begin_chunk_arg,
- &pard->afhi);
+ &pard->afhi, pard->audio_format_num);
if (conf->end_chunk_given) {
ret = -ERRNO_TO_PARA_ERROR(EINVAL);
if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total)
clear_afhi(&pard->afhi);
para_munmap(pard->map, pard->map_size);
close(pard->fd);
+ afh_close(pard->afh_context, pard->audio_format_num);
freep(&rn->private_data);
}
struct afh_info *afhi = &pard->afhi;
int ret;
char *buf;
- const char *start, *end;
+ const char *start;
size_t size;
struct timeval chunk_time;
}
}
if (!conf->just_in_time_given) {
- afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size);
- afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size);
- end += size;
- PARA_INFO_LOG("adding %td bytes\n", end - start);
- btr_add_output_dont_free(start, end - start, btrn);
+ long unsigned n;
+ for (n = pard->first_chunk; n < pard->last_chunk; n++) {
+ ret = afh_get_chunk(n, afhi, pard->audio_format_num,
+ pard->map, pard->map_size, &start, &size,
+ &pard->afh_context);
+ if (ret < 0)
+ goto out;
+ PARA_INFO_LOG("adding %zu bytes\n", size);
+ btr_add_output_dont_free(start, size, btrn);
+ }
ret = -E_RECV_EOF;
goto out;
}
if (ret > 0)
goto out;
}
- afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size);
+ ret = afh_get_chunk(pard->current_chunk, afhi,
+ pard->audio_format_num, pard->map,
+ pard->map_size, &start, &size,
+ &pard->afh_context);
+ if (ret < 0)
+ goto out;
PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
btr_add_output_dont_free(start, size, btrn);
if (pard->current_chunk >= pard->last_chunk) {
CHUNKS_TOTAL_OFFSET = 20,
/** The length of the audio file header (4 bytes). */
HEADER_LEN_OFFSET = 24,
- /** Was: The start of the audio file header (4 bytes). */
- AFHI_UNUSED2_OFFSET = 28,
+ /** Size of the largest chunk in bytes. (4 bytes). */
+ AFHI_MAX_CHUNK_SIZE_OFFSET = 28,
/** The seconds part of the chunk time (4 bytes). */
CHUNK_TV_TV_SEC_OFFSET = 32,
/** The microseconds part of the chunk time (4 bytes). */
write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total);
write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len);
- write_u32(buf + AFHI_UNUSED2_OFFSET, 0);
+ write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size);
write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec);
p = buf + AFHI_INFO_STRING_OFFSET;
afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET);
afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET);
+ afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET);
afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET);
afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET);
afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1;
}
+/* Only used for saving the chunk table, but not for loading. */
static unsigned sizeof_chunk_table(struct afh_info *afhi)
{
- if (!afhi)
+ if (!afhi || !afhi->chunk_table)
return 0;
return 4 * (afhi->chunks_total + 1);
}
-static uint32_t save_chunk_table(struct afh_info *afhi, char *buf)
+static void save_chunk_table(struct afh_info *afhi, char *buf)
{
- int i;
- uint32_t max = 0, old = 0;
+ uint32_t n;
- for (i = 0; i <= afhi->chunks_total; i++) {
- uint32_t val = afhi->chunk_table[i];
- write_u32(buf + 4 * i, val);
- /*
- * If the first chunk is the header, do not consider it for the
- * calculation of the largest chunk size.
- */
- if (i == 0 || (i == 1 && afhi->header_len > 0)) {
- old = val;
- continue;
- }
- max = PARA_MAX(max, val - old);
- old = val;
- }
- return max;
+ if (!afhi->chunk_table)
+ return;
+ for (n = 0; n <= afhi->chunks_total; n++)
+ write_u32(buf + 4 * n, afhi->chunk_table[n]);
}
-static void load_chunk_table(struct afh_info *afhi, char *buf)
+static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct)
{
int i;
+ size_t sz;
- afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi));
- for (i = 0; i <= afhi->chunks_total; i++)
- afhi->chunk_table[i] = read_u32(buf + 4 * i);
+ if (!ct->data || ct->size < 4) {
+ afhi->chunk_table = NULL;
+ return;
+ }
+ sz = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1;
+ afhi->chunk_table = para_malloc(sz);
+ for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++)
+ afhi->chunk_table[i] = read_u32(ct->data + 4 * i);
}
/**
goto err;
buf = shm_afd;
buf += sizeof(*afd);
- afd->max_chunk_size = save_chunk_table(&afd->afhi, buf);
+ save_chunk_table(&afd->afhi, buf);
+ if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */
+ set_max_chunk_size(&afd->afhi);
+ PARA_NOTICE_LOG("max chunk size unset, re-add required\n");
+ } else
+ PARA_INFO_LOG("using max chunk size from afhi\n");
+ afd->max_chunk_size = afd->afhi.max_chunk_size;
*(struct audio_file_data *)shm_afd = *afd;
shm_detach(shm_afd);
return shmid;
{
void *shm_afd;
int ret;
+ struct osl_object obj;
ret = shm_attach(shmid, ATTACH_RO, &shm_afd);
if (ret < 0)
return ret;
+ ret = shm_size(shmid, &obj.size);
+ if (ret < 0)
+ goto detach;
*afd = *(struct audio_file_data *)shm_afd;
- load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd));
+ obj.data = shm_afd + sizeof(*afd);
+ obj.size -= sizeof(*afd);
+ load_chunk_table(&afd->afhi, &obj);
+ ret = 1;
+detach:
shm_detach(shm_afd);
- return 1;
+ return ret;
}
static int get_local_time(uint64_t *seconds, char *buf, size_t size,
(long unsigned) d->afhi.chunk_tv.tv_usec
);
buf = chunk_table_obj.data;
- for (i = 0; i <= d->afhi.chunks_total; i++)
+ for (
+ i = 0;
+ i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size;
+ i++
+ )
para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i));
para_printf(b, "\n");
ret = 1;
WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", tv2ms(&afhi->chunk_tv));
WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%" PRIu32 "\n",
afhi->chunks_total);
+ WRITE_STATUS_ITEM(b, SI_MAX_CHUNK_SIZE, "%" PRIu32 "\n",
+ afhi->max_chunk_size);
WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
AFTCOL_CHUNKS, &chunk_table_obj));
- if (ret < 0)
- return ret;
+ if (ret < 0) {
+ if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id))
+ return ret;
+ PARA_INFO_LOG("no chunk table for %s\n", d->path);
+ chunk_table_obj.data = NULL;
+ chunk_table_obj.size = 0;
+ } else {
+ PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size);
+ }
ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
if (ret < 0)
goto out;
save_afsi(&new_afsi, &afsi_obj); /* in-place update */
afd->audio_format_id = d->afsi.audio_format_id;
- load_chunk_table(&afd->afhi, chunk_table_obj.data);
+ load_chunk_table(&afd->afhi, &chunk_table_obj);
aced.aft_row = current_aft_row;
aced.old_afsi = &d->afsi;
/*
ret = save_afd(afd);
out:
free(afd->afhi.chunk_table);
- osl_close_disk_object(&chunk_table_obj);
+ if (chunk_table_obj.data)
+ osl_close_disk_object(&chunk_table_obj);
if (ret < 0) {
PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret));
ret = score_delete(current_aft_row);
&objs[AFTCOL_AFHI]));
if (ret < 0)
goto out;
+ /* truncate the file to size zero if there is no chunk table */
ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS,
&objs[AFTCOL_CHUNKS]));
if (ret < 0)
}])
########################################################################### faad
STASH_FLAGS
-LIB_ARG_WITH([faad], [-lfaad])
+LIB_ARG_WITH([faad], [-lfaad -lmp4ff])
HAVE_FAAD=yes
AC_CHECK_HEADER(neaacdec.h, [], HAVE_FAAD=no)
+AC_CHECK_HEADER(mp4ff.h, [], HAVE_FAAD=no)
AC_CHECK_LIB([faad], [NeAACDecOpen], [], HAVE_FAAD=no)
+AC_CHECK_LIB([mp4ff], [mp4ff_meta_get_artist], [], HAVE_FAAD=no)
LIB_SUBST_FLAGS(faad)
UNSTASH_FLAGS
########################################################################### mad
AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no)
LIB_SUBST_FLAGS(samplerate)
UNSTASH_FLAGS
-########################################################################## mp4v2
-STASH_FLAGS
-LIB_ARG_WITH([mp4v2], [-lmp4v2])
-HAVE_MP4V2=yes
-AC_CHECK_HEADER([mp4v2/mp4v2.h], [], [HAVE_MP4V2=no])
-AC_CHECK_LIB([mp4v2], [MP4Read], [], [HAVE_MP4V2=no])
-LIB_SUBST_FLAGS(mp4v2)
-UNSTASH_FLAGS
######################################################################### server
if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
build_server="yes"
NEED_SPEEX_OBJECTS() && server_errlist_objs="$server_errlist_objs spx_afh spx_common"
NEED_OPUS_OBJECTS() && server_errlist_objs="$server_errlist_objs opus_afh opus_common"
NEED_FLAC_OBJECTS && server_errlist_objs="$server_errlist_objs flac_afh"
- if test $HAVE_FAAD = yes && test $HAVE_MP4V2 = yes; then
- server_errlist_objs="$server_errlist_objs aac_afh aac_common"
+ if test $HAVE_FAAD = yes; then
+ server_errlist_objs="$server_errlist_objs aac_afh"
fi
server_objs="add_cmdline($server_cmdline_objs) $server_errlist_objs"
AC_SUBST(server_objs, add_dot_o($server_objs))
audiod_audio_formats="$audiod_audio_formats flac"
}
if test $HAVE_FAAD = yes; then
- audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common"
+ audiod_errlist_objs="$audiod_errlist_objs aacdec_filter"
audiod_audio_formats="$audiod_audio_formats aac"
fi
if test $HAVE_MAD = yes; then
filters="$filters flacdec"
}
if test $HAVE_FAAD = yes; then
- filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common"
+ filter_errlist_objs="$filter_errlist_objs aacdec_filter"
filters="$filters aacdec"
fi
if test $HAVE_MAD = yes; then
NEED_OPUS_OBJECTS && recv_errlist_objs="$recv_errlist_objs opus_afh opus_common"
NEED_FLAC_OBJECTS && recv_errlist_objs="$recv_errlist_objs flac_afh"
-if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then
- recv_errlist_objs="$recv_errlist_objs aac_afh aac_common"
+if test $HAVE_FAAD = yes; then
+ recv_errlist_objs="$recv_errlist_objs aac_afh"
fi
recv_objs="add_cmdline($recv_cmdline_objs) $recv_errlist_objs"
AC_SUBST(receivers, "http dccp udp afh")
afh_errlist_objs="$afh_errlist_objs flac_afh"
audio_format_handlers="$audio_format_handlers flac"
}
-if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then
- afh_errlist_objs="$afh_errlist_objs aac_afh aac_common"
+if test $HAVE_FAAD = yes; then
+ afh_errlist_objs="$afh_errlist_objs aac_afh"
audio_format_handlers="$audio_format_handlers aac"
fi
play_errlist_objs="$play_errlist_objs flacdec_filter flac_afh"
}
if test $HAVE_FAAD = yes; then
- play_errlist_objs="$play_errlist_objs aacdec_filter"
-fi
-if test $HAVE_MP4V2 = yes; then
- play_errlist_objs="$play_errlist_objs aac_afh"
-fi
-if test $HAVE_MP4V2 = yes || test $HAVE_FAAD = yes; then
- play_errlist_objs="$play_errlist_objs aac_common"
+ play_errlist_objs="$play_errlist_objs aac_afh aacdec_filter"
fi
if test $HAVE_MAD = yes; then
play_cmdline_objs="$play_cmdline_objs mp3dec_filter"
offset seconds_total stream_start current_time audiod_uptime image_id
lyrics_id duration directory lyrics_name image_name path hash channels
last_played num_chunks chunk_time amplification artist title year album
-comment"
+comment max_chunk_size"
result=
for i in $status_items; do
readline (interactive CLIs): $HAVE_READLINE
id3 version 2 support: $HAVE_ID3TAG
faad: $HAVE_FAAD
-mp4v2: $HAVE_MP4V2
audio format handlers: $audio_format_handlers
filters: $(echo $filters)
/** Codes and messages. */
#define PARA_ERRORS \
- PARA_ERROR(AAC_AFH_INIT, "failed to init aac decoder"), \
PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \
PARA_ERROR(AAC_DECODE, "aac decode error"), \
PARA_ERROR(ACL_PERM, "access denied by acl"), \
PARA_ERROR(EMPTY, "file is empty"), \
PARA_ERROR(ENCRYPT, "encrypt error"), \
PARA_ERROR(EOF, "end of file"), \
- PARA_ERROR(ESDS, "did not find esds atom"), \
PARA_ERROR(FEC_BAD_IDX, "invalid index vector"), \
PARA_ERROR(FECDEC_EOF, "received eof packet"), \
PARA_ERROR(FECDEC_OVERRUN, "fecdec output buffer overrun"), \
PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \
PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \
PARA_ERROR(MP3_INFO, "could not read mp3 info"), \
- PARA_ERROR(MP4ASC, "audio spec config error"), \
- PARA_ERROR(MP4V2, "mp4v2 library error"), \
+ PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \
+ PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \
+ PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \
+ PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \
+ PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \
+ PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \
+ PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \
+ PARA_ERROR(MP4FF_TRACK, "mp4fF: no audio track"), \
+ PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \
PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
PARA_ERROR(NO_AFHI, "audio format handler info required"), \
PARA_ERROR(SSH_PARSE, "could not parse ssh public key"), \
PARA_ERROR(STAT_ITEM_PARSE, "failed to parse status item"), \
PARA_ERROR(STATUS_TIMEOUT, "status item timeout"), \
- PARA_ERROR(STCO, "did not find stco atom"), \
PARA_ERROR(STREAM_PACKETIN, "ogg stream packet-in error"), \
PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error"), \
PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error"), \
PARA_ERROR(STREAM_PAGEOUT, "ogg stream page-out error"), \
PARA_ERROR(STRFTIME, "strftime() failed"), \
- PARA_ERROR(STSZ, "did not find stcz atom"), \
PARA_ERROR(SYNC_COMPLETE, "all buddies in sync"), \
PARA_ERROR(SYNC_LISTEN_FD, "no fd to listen on"), \
PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
break;
}
afhi->chunks_total = c;
+ set_max_chunk_size(afhi);
ret = 1;
free_decoder:
FLAC__stream_decoder_finish(decoder);
return *result == (void *) -1? -ERRNO_TO_PARA_ERROR(errno) : 1;
}
+/**
+ * Get the size of a shared memory segment.
+ *
+ * \param id The shared memory segment identifier.
+ * \param result Size in bytes is returned here, zero on errors.
+ *
+ * \return Standard.
+ *
+ * \sa shmctl(2).
+ */
+int shm_size(int id, size_t *result)
+{
+ struct shmid_ds ds; /* data structure */
+
+ *result = 0;
+ if (shmctl(id, IPC_STAT, &ds) < 0)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ *result = ds.shm_segsz;
+ return 1;
+}
+
/**
* Detach a shared memory segment.
*
int shm_attach(int id, enum shm_attach_mode mode, void **result);
int shm_detach(void *addr);
int shm_destroy(int id);
+int shm_size(int id, size_t *result);
size_t shm_get_shmmax(void);
tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv);
PARA_DEBUG_LOG("%" PRIu32 "chunks, each %lums\n", afhi->chunks_total,
tv2ms(&afhi->chunk_tv));
+ set_max_chunk_size(afhi);
ret = mp3_get_id3(map, numbytes, fd, &afhi->tags);
afhi->techinfo = make_message("%cbr, %s, %s tags", vbr? 'v' : 'c',
header_mode(&header), tag_versions[ret]);
}
}
afhi->chunks_total = j;
+ set_max_chunk_size(afhi);
set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
ret = 0;
out:
* propagate to the stat command handlers.
*/
struct misc_meta_data {
- /** The size of the current audio file in bytes. */
- size_t size;
/** The "old" status flags -- commands may only read them. */
unsigned int vss_status_flags;
/** The new status flags -- commands may set them. */
enum afs_socket_status afsss;
/** The memory mapped audio file. */
char *map;
+ /** The size of the memory mapping. */
+ size_t mapsize;
/** Used by the scheduler. */
struct task *task;
/** Pointer to the header of the mapped audio file. */
size_t header_len;
/** Time between audio file headers are sent. */
struct timeval header_interval;
+ /* Only used if afh supports dynamic chunks. */
+ void *afh_context;
};
/**
static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
char **buf, size_t *sz)
{
+ int ret;
+
/*
* Chunk zero is special for header streams: It is the first portion of
* the audio file which consists of the audio file header. It may be
*sz = vsst->header_len;
return;
}
- afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, (const char **)buf,
- sz);
+ ret = afh_get_chunk(chunk_num, &mmd->afd.afhi,
+ mmd->afd.audio_format_id, vsst->map, vsst->mapsize,
+ (const char **)buf, sz, &vsst->afh_context);
+ if (ret < 0) {
+ PARA_WARNING_LOG("could not get chunk %d: %s\n",
+ chunk_num, para_strerror(-ret));
+ *buf = NULL;
+ *sz = 0;
+ }
}
static void compute_group_size(struct vss_task *vsst, struct fec_group *g,
set_eof_barrier(vsst);
afh_free_header(vsst->header_buf, mmd->afd.audio_format_id);
vsst->header_buf = NULL;
- para_munmap(vsst->map, mmd->size);
+ para_munmap(vsst->map, vsst->mapsize);
vsst->map = NULL;
mmd->chunks_sent = 0;
//mmd->offset = 0;
mmd->afd.afhi.chunk_tv.tv_usec = 0;
free(mmd->afd.afhi.chunk_table);
mmd->afd.afhi.chunk_table = NULL;
- mmd->size = 0;
+ vsst->mapsize = 0;
+ afh_close(vsst->afh_context, mmd->afd.audio_format_id);
+ vsst->afh_context = NULL;
mmd->events++;
}
ret = -ERRNO_TO_PARA_ERROR(errno);
goto err;
}
- mmd->size = statbuf.st_size;
- ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
+ ret = para_mmap(statbuf.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
passed_fd, 0, &vsst->map);
if (ret < 0)
goto err;
+ vsst->mapsize = statbuf.st_size;
close(passed_fd);
mmd->chunks_sent = 0;
mmd->current_chunk = 0;
mmd->num_played++;
mmd->new_vss_status_flags &= (~VSS_NEXT);
afh_get_header(&mmd->afd.afhi, mmd->afd.audio_format_id,
- vsst->map, mmd->size, &vsst->header_buf, &vsst->header_len);
+ vsst->map, vsst->mapsize, &vsst->header_buf, &vsst->header_len);
return;
err:
free(mmd->afd.afhi.chunk_table);
*/
if (mmd->current_chunk > 0) { /* chunk 0 might be on the heap */
buf += len;
- for (i = 0; i < 5 && buf < vsst->map + mmd->size; i++) {
+ for (i = 0; i < 5 && buf < vsst->map + vsst->mapsize; i++) {
__a_unused volatile char x = *buf;
buf += 4096;
}
set_eof_barrier(vsst);
mmd->chunks_sent = 0;
mmd->current_chunk = afh_get_start_chunk(mmd->repos_request,
- &mmd->afd.afhi);
+ &mmd->afd.afhi, mmd->afd.audio_format_id);
mmd->new_vss_status_flags &= ~VSS_REPOS;
set_mmd_offset();
}
you need libogg, libvorbis, libvorbisfile. The corresponding Debian
packages are called `libogg-dev` and `libvorbis-dev`.
-- [libfaad](http://www.audiocoding.com/). For aac files (m4a) you
-need libfaad (`libfaad-dev`).
+- [libfaad and mp4ff](http://www.audiocoding.com/). For aac files
+(m4a) you need libfaad and libmp4ff (package: `libfaad-dev`). Note
+that for some distributions, e.g. Ubuntu, mp4ff is not part of the
+libfaad package. Install the faad library from sources (available
+through the above link) to get the mp4ff library and header files.
- [speex](http://www.speex.org/). In order to stream or decode speex
files, libspeex (`libspeex-dev`) is required.
}
}
afhi->chunks_total = j;
+ set_max_chunk_size(afhi);
set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
return 1;
fail: