]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 'refs/heads/t/dynamic_chunks'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 23 Apr 2017 19:12:22 +0000 (21:12 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 23 Apr 2017 19:20:25 +0000 (21:20 +0200)
A rather long and intrusive series that finally improves the aac
decoder and audio format handler.

The merge had a minor conflict in error.h, which was easily resolved
by removing both affected error codes.

Cooking for a month.

* refs/heads/t/dynamic_chunks: (24 commits)
  afh_recv: Check return value of afh_get_chunk().
  aacdec: Decode only one frame per iteration.
  aacdec: Combine aac_open() and aacdec_open().
  aacdec: Make "initialized" a boolean.
  aacdec: Make frame_info local to ->post_select().
  aacdec: Remove pointless assignment.
  aacdec: Remove pointless check and improve comment.
  aacdec: Rename buffer variables.
  aacdec: Remove pointless variable p.
  aacdec: Remove check which is always true.
  aacdec: Kill pointless label.
  aacdec: Remove superfluous assignment.
  aacdec: Improve and silence error message.
  aacdec: Prefer NeAACDecInit() over NeAACDecInit2().
  aacdec: Don't eat full buffer on errors.
  Combine aacdec and aac_common.
  Convert the aac audio format handler to libmp4ff.
  aac_afh: Don't create chunk tables any more.
  afh: Dynamic chunks.
  server: Store max chunk size in database.
  ...

23 files changed:
Makefile.in
Makefile.real
NEWS.md
aac.h [deleted file]
aac_afh.c
aac_common.c [deleted file]
aacdec_filter.c
afh.c
afh.h
afh_common.c
afh_recv.c
aft.c
configure.ac
error.h
flac_afh.c
ipc.c
ipc.h
mp3_afh.c
ogg_afh_common.c
server.h
vss.c
web/manual.md
wma_afh.c

index 6e84b6fd291b5111f2c0aef473b7d05f10950f5f..aff321fd95d8d5f5114ccd7abe513451e3b09061 100644 (file)
@@ -49,7 +49,6 @@ samplerate_cppflags := @samplerate_cppflags@
 readline_cppflags := @readline_cppflags@
 alsa_cppflags := @alsa_cppflags@
 oss_cppflags := @oss_cppflags@
-mp4v2_cppflags := @mp4v2_cppflags@
 
 id3tag_ldflags := @id3tag_ldflags@
 ogg_ldflags := @ogg_ldflags@
@@ -69,6 +68,5 @@ osl_ldflags := @osl_ldflags@
 curses_ldflags := @curses_ldflags@
 crypto_ldflags := @crypto_ldflags@
 iconv_ldflags := @iconv_ldflags@
-mp4v2_ldflags := @mp4v2_ldflags@
 
 include Makefile.real
index 5e4dbe68c0308bf1b385c84e94d808d4f740b625..d2d316673d4dba08e650f2ecf28d075d8bcacc8f 100644 (file)
@@ -204,7 +204,6 @@ $(object_dir)/mp3_afh.o $(dep_dir)/mp3_afh.d: CPPFLAGS += $(id3tag_cppflags)
 $(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 \
@@ -223,7 +222,6 @@ $(object_dir)/mp3dec_filter.o $(dep_dir)/mp3dec_filter.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)
 
@@ -321,13 +319,6 @@ para_recv \
        $(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)))
diff --git a/NEWS.md b/NEWS.md
index df1a80e352c19edd24dba2683aff1f5c4fd4db5d..9b6ef0a7e9d23e8ef08a078afa74da86ef47828a 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -26,6 +26,14 @@ NEWS
   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),
diff --git a/aac.h b/aac.h
deleted file mode 100644 (file)
index eeed252..0000000
--- a/aac.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * 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);
index 1c7fd706f0a7c9fe8c9d4d8db43846be18a9304d..8550a8ac6801d421e5cf186b48360922face11e8 100644 (file)
--- a/aac_afh.c
+++ b/aac_afh.c
 /** \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;
 }
 
@@ -326,5 +346,8 @@ void aac_afh_init(struct audio_format_handler *afh)
 {
        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;
 }
diff --git a/aac_common.c b/aac_common.c
deleted file mode 100644 (file)
index 812c742..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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;
-}
index 5725ce043089361092ddb68e05bb3c60fcf66801..e1cf802cb0e4c807e8684957424c7237eee787e4 100644 (file)
 /** \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"
@@ -20,7 +22,6 @@
 #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. */
@@ -64,11 +56,18 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result)
 
 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)
@@ -86,9 +85,9 @@ static int aacdec_post_select(__a_unused struct sched *s, void *context)
        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);
@@ -103,103 +102,58 @@ next_buffer:
        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);
diff --git a/afh.c b/afh.c
index 36c432e54546d7f6079f9eb0ea4b35b675ab806b..e6c46c3f9d68471ad31529205a46d4590a92b985 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -125,29 +125,38 @@ static void print_info(int audio_format_num, struct afh_info *afhi)
        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)
@@ -201,8 +210,8 @@ int main(int argc, char **argv)
                                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);
                }
diff --git a/afh.h b/afh.h
index 801c168c0ca1dc578745e9af87c60028a9774827..6dc5a3fc663659b67f658c9ef9b58883adde6266 100644 (file)
--- a/afh.h
+++ b/afh.h
@@ -40,6 +40,8 @@ struct afh_info {
         * 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;
        /**
@@ -64,7 +66,10 @@ struct audio_file_data {
        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;
@@ -104,6 +109,26 @@ struct audio_format_handler {
                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.
         *
@@ -119,10 +144,12 @@ int guess_audio_format(const char *name);
 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);
@@ -130,3 +157,5 @@ void clear_afhi(struct afh_info *afhi);
 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);
index dfbf75132f4e4372eb0b4c95bff8ef25793dc545..6feb7c3565dc6ed1f9023ba4eb6ad6b19bcadd48 100644 (file)
@@ -48,7 +48,7 @@ static struct audio_format_handler afl[] = {
        },
        {
                .name = "aac",
-#if defined(HAVE_MP4V2)
+#if defined(HAVE_FAAD)
                .init = aac_afh_init,
 #endif
        },
@@ -88,7 +88,6 @@ static inline int next_audio_format(int format)
                if (afl[format].init)
                        return format;
        }
-
 }
 
 /** Iterate over each supported audio format. */
@@ -109,6 +108,22 @@ void afh_init(void)
        }
 }
 
+/**
+ * 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.
  *
@@ -261,21 +276,73 @@ static inline size_t get_chunk_len(long unsigned chunk_num,
 /**
  * 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);
 }
 
 /**
@@ -283,16 +350,22 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi,
  *
  * \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;
@@ -374,6 +447,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
                "%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 */
@@ -388,6 +462,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
                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 : "",
@@ -397,6 +472,37 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re
        );
 }
 
+/**
+ * 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.
  *
index 28d8f3980f814763cfe249e5762203ff5038a9a5..1eac9a5b38c212ae845ce823574ac3d6718cdba6 100644 (file)
@@ -31,6 +31,7 @@ struct private_afh_recv_data {
        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)
@@ -58,7 +59,8 @@ 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;
        }
@@ -110,11 +112,12 @@ static int afh_recv_open(struct receiver_node *rn)
                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)
@@ -150,6 +153,7 @@ static void afh_recv_close(struct receiver_node *rn)
        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);
 }
 
@@ -182,7 +186,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
        struct afh_info *afhi = &pard->afhi;
        int ret;
        char *buf;
-       const char *start, *end;
+       const char *start;
        size_t size;
        struct timeval chunk_time;
 
@@ -202,11 +206,16 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                }
        }
        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;
        }
@@ -219,7 +228,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context)
                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) {
diff --git a/aft.c b/aft.c
index bfcd1fb0162fe8a8a03c0f5ba165acc60be71cf0..7d3a6e0d98a01e40974f22285e9be2d9fac01b46 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -335,8 +335,8 @@ enum afhi_offsets {
        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). */
@@ -376,7 +376,7 @@ static void save_afhi(struct afh_info *afhi, char *buf)
        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;
@@ -398,6 +398,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        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;
@@ -408,42 +409,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi)
        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);
 }
 
 /**
@@ -638,7 +634,13 @@ static int save_afd(struct audio_file_data *afd)
                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;
@@ -663,14 +665,22 @@ int load_afd(int shmid, struct audio_file_data *afd)
 {
        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,
@@ -820,7 +830,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b)
                (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;
@@ -935,6 +949,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
        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);
@@ -1054,8 +1070,15 @@ again:
        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;
@@ -1072,7 +1095,7 @@ again:
        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;
        /*
@@ -1085,7 +1108,8 @@ again:
        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);
@@ -1736,6 +1760,7 @@ static int com_add_callback(struct afs_callback_arg *aca)
                        &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)
index eed441d639a115b6d78d5f9b4934eaff2d4fc107..929d44f35fd482a83d3713d5ab24ffe9b0edd8db 100644 (file)
@@ -262,10 +262,12 @@ AC_DEFUN([NEED_FLAC_OBJECTS], [{
 }])
 ########################################################################### 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
@@ -342,14 +344,6 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no)
 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"
@@ -405,8 +399,8 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then
        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))
@@ -526,7 +520,7 @@ if test -n "$CRYPTOLIB"; then
                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
@@ -687,7 +681,7 @@ NEED_FLAC_OBJECTS && {
        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
@@ -749,8 +743,8 @@ NEED_SPEEX_OBJECTS && recv_errlist_objs="$recv_errlist_objs spx_afh spx_common"
 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")
@@ -787,8 +781,8 @@ NEED_FLAC_OBJECTS && {
        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
 
@@ -858,13 +852,7 @@ NEED_FLAC_OBJECTS && {
        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"
@@ -973,7 +961,7 @@ attributes_txt decoder_flags audiod_status play_time attributes_bitmap
 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
@@ -1003,7 +991,6 @@ unix socket credentials: $have_ucred
 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)
diff --git a/error.h b/error.h
index fad417297df865267937ea48e8ba2f217cbfe652..32e525cd36e220e28a61359a008889e5b1e2c08b 100644 (file)
--- a/error.h
+++ b/error.h
@@ -8,7 +8,6 @@
 
 /** 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"), \
@@ -92,7 +91,6 @@
        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?)"), \
index 385d4f0c3a0235b9be24cdc83666308c30b959b8..e2d5380240240ddfaf3ce2b989ab0847f2e6e4fb 100644 (file)
@@ -391,6 +391,7 @@ static int flac_afh_read_chunks(struct private_flac_afh_data *pfad)
                        break;
        }
        afhi->chunks_total = c;
+       set_max_chunk_size(afhi);
        ret = 1;
 free_decoder:
        FLAC__stream_decoder_finish(decoder);
diff --git a/ipc.c b/ipc.c
index 9488224a1d4a622860adc957b27977e2bcbfc7e2..d7f515dfec8788f267a41f8c33264d25b15abf8a 100644 (file)
--- a/ipc.c
+++ b/ipc.c
@@ -161,6 +161,27 @@ int shm_attach(int id, enum shm_attach_mode mode, void **result)
        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.
  *
diff --git a/ipc.h b/ipc.h
index c8d31c0c991641a1f71627c855705bdf7decd5c6..c44371045cdfb7a4313068a5e91bfb22fd366a84 100644 (file)
--- a/ipc.h
+++ b/ipc.h
@@ -11,4 +11,5 @@ int shm_new(size_t size);
 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);
index 2115f71c77ce745b356b917058792d0c64db42ef..e5d0ff138fcfc5efed849679ca430bff87726074 100644 (file)
--- a/mp3_afh.c
+++ b/mp3_afh.c
@@ -657,6 +657,7 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd,
        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]);
index 6e5a89347e383340f943e591254f64f05cc6f358..734fd58680b1f944f6adf51c76aebb19d1b5b5c7 100644 (file)
@@ -181,6 +181,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi,
                }
        }
        afhi->chunks_total = j;
+       set_max_chunk_size(afhi);
        set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
        ret = 0;
 out:
index 8de691ca321bd49db2d7c25398537f31b7d03a0c..7f276ab92edb4d06d312fc1c1c12f0003b7ae9ac 100644 (file)
--- a/server.h
+++ b/server.h
@@ -48,8 +48,6 @@ struct sender_command_data {
  * 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. */
diff --git a/vss.c b/vss.c
index 5484db9d9479401fe90e481f842c37a9afed8e4f..24dfc6bb51af4edf67c645c0d3ccbda2ad8f1868 100644 (file)
--- a/vss.c
+++ b/vss.c
@@ -88,6 +88,8 @@ struct vss_task {
        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. */
@@ -96,6 +98,8 @@ struct vss_task {
        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;
 };
 
 /**
@@ -347,6 +351,8 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst)
 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
@@ -360,8 +366,15 @@ static void vss_get_chunk(int chunk_num, struct vss_task *vsst,
                *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,
@@ -846,7 +859,7 @@ static void vss_eof(struct vss_task *vsst)
        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;
@@ -855,7 +868,9 @@ static void vss_eof(struct vss_task *vsst)
        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++;
 }
 
@@ -973,11 +988,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
                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;
@@ -986,7 +1001,7 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds)
        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);
@@ -1076,7 +1091,7 @@ static void vss_send(struct vss_task *vsst)
                 */
                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;
                        }
@@ -1113,7 +1128,7 @@ static int vss_post_select(struct sched *s, void *context)
                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();
        }
index fef8123a3ac13bf44f0551e53aece8d001ac7495..dff8a22a659c7c61f33ce6fef8534dd89f133526 100644 (file)
@@ -248,8 +248,11 @@ recognized. The mp3 tagger also needs this library for modifying
 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.
index 0d543195151922a27e4f5d7a72749e26f0ad34cc..6bf2d64134539f07237fb31db9a83a7629db3f83 100644 (file)
--- a/wma_afh.c
+++ b/wma_afh.c
@@ -228,6 +228,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size
                }
        }
        afhi->chunks_total = j;
+       set_max_chunk_size(afhi);
        set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv);
        return 1;
 fail: