#include <mp4v2/mp4v2.h>
#include "para.h"
+#include <mp4ff.h>
#include "error.h"
#include "portable_io.h"
#include "afh.h"
#include "aac.h"
#include "fd.h"
+struct aac_afh_context {
+ const void *map;
+ size_t mapsize;
+ 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)
+{
+ 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;
+ }
+ 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 uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos)
+{
+ struct aac_afh_context *c = user_data;
+ c->fpos = pos;
+ return 0;
+}
+
+static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc)
+{
+ 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;
+ }
+ }
+ return -1; /* no audio track */
+}
+
+static int aac_afh_open(const void *map, size_t mapsize, void **afh_context)
+{
+ 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 aac_afh_close(void *afh_context)
+{
+ struct aac_afh_context *c = afh_context;
+ mp4ff_close(c->mp4ff);
+ free(c);
+}
+
+/**
+ * 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);
+
+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;
+}
static int aac_find_stsz(char *buf, size_t buflen, off_t *skip)
{
int i;
afh->get_file_info = aac_get_file_info,
afh->suffixes = aac_suffixes;
afh->rewrite_tags = aac_rewrite_tags;
+ afh->open = aac_afh_open;
+ afh->get_chunk = aac_afh_get_chunk;
+ afh->close = aac_afh_close;
}
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;
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);
}
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);
}
}
+static 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.
*/
-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)
{
- 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)) {
+ 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_close(void *afh_context, uint8_t audio_format_id)
+{
+ struct audio_format_handler *afh = afl + audio_format_id;
+
+ if (!afh_supports_dynamic_chunks(audio_format_id))
+ return;
+ if (!afh->close)
+ return;
+ if (!afh_context)
+ return;
+ afh->close(afh_context);
}
/**
*
* \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;
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);
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) {
}])
########################################################################### 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
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(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \
+ PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \
+ PARA_ERROR(MP4FF_TRACK, "mp4fF: no audio track"), \
PARA_ERROR(MP4V2, "mp4v2 library error"), \
PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \
PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \
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,
free(mmd->afd.afhi.chunk_table);
mmd->afd.afhi.chunk_table = NULL;
vsst->mapsize = 0;
+ afh_close(vsst->afh_context, mmd->afd.audio_format_id);
+ vsst->afh_context = NULL;
mmd->events++;
}
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.