* Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other
Unixes
- * Mp3, ogg/vorbis, ogg/speex, aac (m4a) and wma support
+ * Mp3, ogg/vorbis, ogg/speex, aac (m4a), wma and flac support
* Native Alsa, OSS, CoreAudio output support
* Support for ESD, Pulseaudio, AIX, Solaris, IRIX through libao
* Local or remote http, dccp and udp network audio streaming
0.4.9 (to be announced) "hybrid causality"
------------------------------------------
+Support for another audio format and many small improvements/fixes
+all over the place.
+
+ - Support for flac, the free lossless audio codec.
- Fix for an endless loop in the mp3 decoder for certain
(corrupt) mp3 files.
- autogen.sh now detects a distcc setup and adjusts the
#ifdef HAVE_SPEEX
void spx_afh_init(struct audio_format_handler *);
#endif
+#ifdef HAVE_FLAC
+ void flac_afh_init(struct audio_format_handler *);
+#endif
void wma_afh_init(struct audio_format_handler *);
/**
.name = "spx",
#ifdef HAVE_SPEEX
.init = spx_afh_init,
+#endif
+ },
+ {
+ .name = "flac",
+#ifdef HAVE_FLAC
+ .init = flac_afh_init,
#endif
},
{
unsigned short num_played_width;
/** size of the amp field. */
unsigned short amp_width;
+ /** size of the audio format field. */
+ unsigned short audio_format_width;
};
/** Data passed from the ls command handler to its callback function. */
"%*d " /* image_id */
"%*d " /* lyrics_id */
"%*d " /* bitrate */
- "%s " /* audio format */
+ "%*s " /* audio format */
"%*d " /* frequency */
"%d " /* channels */
"%s " /* duration */
w->image_id_width, afsi->image_id,
w->lyrics_id_width, afsi->lyrics_id,
w->bitrate_width, afhi->bitrate,
+ w->audio_format_width,
audio_format_name(afsi->audio_format_id),
w->frequency_width, afhi->frequency,
afhi->channels,
w->duration_width = PARA_MAX(w->duration_width, num_digits);
GET_NUM_DIGITS(d->afsi.amp, &num_digits);
w->amp_width = PARA_MAX(w->amp_width, num_digits);
+ num_digits = strlen(audio_format_name(d->afsi.audio_format_id));
+ w->audio_format_width = PARA_MAX(w->audio_format_width, num_digits);
if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
GET_NUM_DIGITS(score, &num_digits);
num_digits++; /* add one for the sign (space or "-") */
}
/**
- * Obtain the next buffer of the input queue of a buffer tree node.
+ * Obtain the next buffer of the input queue, omitting data.
*
* \param btrn The node whose input queue is to be queried.
+ * \param omit Number of bytes to be omitted.
* \param bufp Result pointer.
*
- * \return The number of bytes that can be read from buf. Zero if the input
- * buffer queue is empty. In this case the value of \a bufp is undefined.
+ * If a buffer tree node needs more input data but can not consume the data it
+ * already has (because it might be needed again later) this function can be
+ * used instead of btr_next_buffer() to get a reference to the buffer obtained
+ * by skipping the given number of bytes. Skipped input bytes are not consumed.
+ *
+ * With a zero \a omit argument, this function is equivalent to \ref
+ * btr_next_buffer().
+ *
+ * \return Number of bytes in \a bufp. If there are less than or equal to \a
+ * omit many bytes available in the input queue of the buffer tree node pointed
+ * to by \a btrn, the function returns zero and the value of \a bufp is
+ * undefined.
*/
-size_t btr_next_buffer(struct btr_node *btrn, char **bufp)
+size_t btr_next_buffer_omit(struct btr_node *btrn, size_t omit, char **bufp)
{
struct btr_buffer_reference *br;
+ size_t wrap_count, sz, rv = 0;
char *buf, *result = NULL;
- size_t sz, rv = 0;
- FOR_EACH_BUFFER_REF(br, btrn) {
+ br = get_first_input_br(btrn);
+ if (!br)
+ return 0;
+ wrap_count = br->wrap_count;
+ if (wrap_count > 0) { /* we have a wrap buffer */
sz = btr_get_buffer_by_reference(br, &buf);
- if (!result) {
- result = buf;
- rv = sz;
- if (!br->btrb->pool)
- break;
- continue;
+ if (sz > omit) { /* and it's big enough */
+ result = buf + omit;
+ rv = sz - omit;
+ /*
+ * Wrap buffers are allocated by malloc(), so the next
+ * buffer ref will not align nicely, so we return the
+ * tail of the wrap buffer.
+ */
+ goto out;
}
- if (!br->btrb->pool)
- break;
- if (result + rv != buf)
- break;
- rv += sz;
+ /*
+ * The next wrap_count bytes exist twice, in the wrap buffer
+ * and as a buffer reference in the buffer tree pool.
+ */
+ omit += wrap_count;
+ }
+ /*
+ * For buffer tree pools, the buffers in the list align, i.e. the next
+ * buffer in the list starts directly at the end of its predecessor. In
+ * this case we merge adjacent buffers and return one larger buffer
+ * instead.
+ */
+ FOR_EACH_BUFFER_REF(br, btrn) {
+ sz = btr_get_buffer_by_reference(br, &buf);
+ if (result) {
+ if (result + rv != buf)
+ goto out;
+ rv += sz;
+ } else if (sz > omit) {
+ result = buf + omit;
+ rv = sz - omit;
+ } else
+ omit -= sz;
}
+ if (!result)
+ return 0;
+out:
if (bufp)
*bufp = result;
return rv;
}
+/**
+ * Obtain the next buffer of the input queue of a buffer tree node.
+ *
+ * \param btrn The node whose input queue is to be queried.
+ * \param bufp Result pointer.
+ *
+ * \return The number of bytes that can be read from buf.
+ *
+ * The call of this function is is equivalent to calling \ref
+ * btr_next_buffer_omit() with an \a omit value of zero.
+ */
+size_t btr_next_buffer(struct btr_node *btrn, char **bufp)
+{
+ return btr_next_buffer_omit(btrn, 0, bufp);
+}
+
/**
* Deallocate the given number of bytes from the input queue.
*
size_t btr_get_output_queue_size(struct btr_node *btrn);
bool btr_no_parent(struct btr_node *btrn);
size_t btr_next_buffer(struct btr_node *btrn, char **bufp);
+size_t btr_next_buffer_omit(struct btr_node *btrn, size_t omit, char **bufp);
void btr_consume(struct btr_node *btrn, size_t numbytes);
int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result);
void btr_splice_out_node(struct btr_node *btrn);
else
AC_MSG_WARN([no support for id3v2 tags])
fi
+########################################################################### flac
+OLD_CPPFLAGS="$CPPFLAGS"
+OLD_LD_FLAGS="$LDFLAGS"
+OLD_LIBS="$LIBS"
+
+have_flac="yes"
+AC_ARG_WITH(flac_headers, [AC_HELP_STRING(--with-flac-headers=dir,
+ [look for flac headers also in dir])])
+if test -n "$with_flac_headers"; then
+ flac_cppflags="-I$with_flac_headers"
+ CPPFLAGS="$CPPFLAGS $flac_cppflags"
+fi
+AC_ARG_WITH(flac_libs, [AC_HELP_STRING(--with-flac-libs=dir,
+ [look for flac libs also in dir])])
+if test -n "$with_flac_libs"; then
+ flac_libs="-L$with_flac_libs"
+ LDFLAGS="$LDFLAGS $flac_libs"
+fi
+AC_CHECK_HEADER(FLAC/stream_decoder.h, [], have_flac=no)
+AC_CHECK_LIB([FLAC], [FLAC__stream_decoder_init_file], [], have_flac=no)
+if test "$have_flac" = "yes"; then
+ AC_DEFINE(HAVE_FLAC, 1, define to 1 if you want to build the flacdec filter)
+ all_errlist_objs="$all_errlist_objs flacdec_filter flac_afh"
+ filter_errlist_objs="$filter_errlist_objs flacdec_filter"
+ audiod_errlist_objs="$audiod_errlist_objs flacdec_filter"
+ afh_errlist_objs="$afh_errlist_objs flac_afh"
+ server_errlist_objs="$server_errlist_objs flac_afh"
+ filter_ldflags="$filter_ldflags $flac_libs -lFLAC"
+ audiod_ldflags="$audiod_ldflags $flac_libs -lFLAC"
+ server_ldflags="$server_ldflags $flac_libs -lFLAC"
+ afh_ldflags="$afh_ldflags $flac_libs -lFLAC"
+ filters="$filters flacdec"
+ server_audio_formats="$server_audio_formats flac"
+ audiod_audio_formats="$audiod_audio_formats flac"
+ AC_SUBST(flac_cppflags)
+else
+ AC_MSG_WARN([no flac support in para_audiod/para_filter])
+fi
+CPPFLAGS="$OLD_CPPFLAGS"
+LDFLAGS="$OLD_LDFLAGS"
+LIBS="$OLD_LIBS"
########################################################################### oss
OLD_CPPFLAGS="$CPPFLAGS"
OLD_LD_FLAGS="$LDFLAGS"
extern const char **para_errlist[];
+#define FLACDEC_FILTER_ERRORS \
+ PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \
+ PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \
+ PARA_ERROR(FLACDEC_EOF, "flacdec encountered end of file condition"), \
+
+
+#define FLAC_AFH_ERRORS \
+ PARA_ERROR(FLAC_CHAIN_ALLOC, "could not create metadata chain"), \
+ PARA_ERROR(FLAC_CHAIN_READ, "could not read meta chain"), \
+ PARA_ERROR(FLAC_ITER_ALLOC, "could not allocate meta iterator"), \
+ PARA_ERROR(FLAC_VARBLOCK, "variable blocksize not supported"), \
+ PARA_ERROR(FLAC_AFH_DECODER_ALLOC, "could not allocate stream decoder"), \
+ PARA_ERROR(FLAC_AFH_DECODER_INIT, "could not init stream decoder"), \
+ PARA_ERROR(FLAC_SKIP_META, "could not skip metadata"), \
+ PARA_ERROR(FLAC_DECODE_POS, "could not get decode position"), \
+ PARA_ERROR(FLAC_STREAMINFO, "could not read stream info meta block"), \
+
+
#define OGG_AFH_COMMON_ERRORS \
PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
--- /dev/null
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file flac_afh.c Audio format handler for flac files. */
+
+#include <regex.h>
+#include <stdbool.h>
+#include <FLAC/stream_decoder.h>
+#include <FLAC/metadata.h>
+
+#include "para.h"
+#include "error.h"
+#include "afh.h"
+#include "string.h"
+
+struct private_flac_afh_data {
+ char *map;
+ size_t map_bytes;
+ size_t fpos;
+ struct afh_info *afhi;
+ unsigned blocksize;
+};
+
+static size_t copy_data(struct private_flac_afh_data *pfad, void *buf,
+ size_t want)
+{
+ size_t copy, have = pfad->map_bytes - pfad->fpos;
+
+ if (have == 0)
+ return 0;
+ copy = have < want? have : want;
+ memcpy(buf, pfad->map + pfad->fpos, copy);
+ pfad->fpos += copy;
+ return copy;
+}
+
+static size_t meta_read_cb(void *ptr, size_t size, size_t nmemb,
+ FLAC__IOHandle handle)
+{
+ struct private_flac_afh_data *pfad = handle;
+ return copy_data(pfad, ptr, nmemb * size);
+}
+
+static int meta_seek_cb(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
+{
+ struct private_flac_afh_data *pfad = handle;
+
+ if (offset < 0)
+ return -1;
+
+ switch (whence) {
+ case SEEK_SET:
+ if (offset >= pfad->map_bytes)
+ return -1;
+ pfad->fpos = offset;
+ return 0;
+ case SEEK_CUR:
+ if (pfad->fpos + offset >= pfad->map_bytes)
+ return -1;
+ pfad->fpos += offset;
+ return 0;
+ case SEEK_END:
+ if (offset >= pfad->map_bytes)
+ return -1;
+ pfad->fpos = offset;
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+static FLAC__int64 meta_tell_cb(FLAC__IOHandle handle)
+{
+ struct private_flac_afh_data *pfad = handle;
+ return pfad->fpos;
+}
+
+static int meta_eof_cb(FLAC__IOHandle handle)
+{
+ struct private_flac_afh_data *pfad = handle;
+ return pfad->fpos == pfad->map_bytes - 1;
+}
+
+static int meta_close_cb(FLAC__IOHandle __a_unused handle)
+{
+ return 0;
+}
+
+static void free_tags(struct taginfo *tags)
+{
+ freep(&tags->artist);
+ freep(&tags->title);
+ freep(&tags->album);
+ freep(&tags->year);
+ freep(&tags->comment);
+}
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+ char **p)
+{
+ char *q = key_value_copy(tag, taglen, type);
+ if (!q)
+ return false;
+ free(*p);
+ *p = q;
+ return true;
+}
+
+static void flac_read_vorbis_comments(FLAC__StreamMetadata_VorbisComment *vc,
+ struct taginfo *tags)
+{
+ int i;
+ FLAC__StreamMetadata_VorbisComment_Entry *comment = vc->comments;
+
+ PARA_INFO_LOG("found %u vorbis comments\n", vc->num_comments);
+ for (i = 0; i < vc->num_comments; i++) {
+ char *e = (char *)comment[i].entry;
+ int len = comment[i].length;
+ if (copy_if_tag_type(e, len, "artist", &tags->artist))
+ continue;
+ if (copy_if_tag_type(e, len, "title", &tags->title))
+ continue;
+ if (copy_if_tag_type(e, len, "album", &tags->album))
+ continue;
+ if (copy_if_tag_type(e, len, "year", &tags->year))
+ continue;
+ if (copy_if_tag_type(e, len, "comment", &tags->comment))
+ continue;
+ }
+}
+
+static int flac_read_meta(struct private_flac_afh_data *pfad)
+{
+ int ret;
+ FLAC__IOCallbacks meta_callbacks = {
+ .read = meta_read_cb,
+ .write = NULL,
+ .seek = meta_seek_cb,
+ .tell = meta_tell_cb,
+ .eof = meta_eof_cb,
+ .close = meta_close_cb
+ };
+ FLAC__Metadata_Chain *chain;
+ FLAC__Metadata_Iterator *iter;
+ FLAC__StreamMetadata_StreamInfo *info = NULL;
+ FLAC__bool ok;
+
+ chain = FLAC__metadata_chain_new();
+ if (!chain)
+ return -E_FLAC_CHAIN_ALLOC;
+ ret = -E_FLAC_CHAIN_READ;
+ ok = FLAC__metadata_chain_read_with_callbacks(chain, pfad,
+ meta_callbacks);
+ if (!ok)
+ goto free_chain;
+ ret = -E_FLAC_ITER_ALLOC;
+ iter = FLAC__metadata_iterator_new();
+ if (!iter)
+ goto free_chain;
+ FLAC__metadata_iterator_init(iter, chain);
+ for (;;) {
+ FLAC__StreamMetadata *b;
+ b = FLAC__metadata_iterator_get_block(iter);
+ if (!b)
+ break;
+ if (b->type == FLAC__METADATA_TYPE_STREAMINFO) {
+ info = &b->data.stream_info;
+ ret = -E_FLAC_VARBLOCK;
+ if (info->min_blocksize != info->max_blocksize)
+ goto free_iter;
+ pfad->afhi->frequency = info->sample_rate;
+ pfad->afhi->channels = info->channels;
+ pfad->blocksize = info->min_blocksize;
+ }
+ if (b->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
+ flac_read_vorbis_comments(&b->data.vorbis_comment,
+ &pfad->afhi->tags);
+ ok = FLAC__metadata_iterator_next(iter);
+ if (!ok)
+ break;
+ }
+ ret = info? 0: -E_FLAC_STREAMINFO;
+free_iter:
+ FLAC__metadata_iterator_delete(iter);
+free_chain:
+ FLAC__metadata_chain_delete(chain);
+ if (ret < 0)
+ free_tags(&pfad->afhi->tags);
+ return ret;
+}
+
+static FLAC__StreamDecoderReadStatus read_cb(
+ __a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__byte buffer[], size_t *bytes, void *client_data)
+{
+ struct private_flac_afh_data *pfad = client_data;
+
+ assert(*bytes > 0);
+ *bytes = copy_data(pfad, buffer, *bytes);
+ if (*bytes == 0)
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ else
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+static FLAC__StreamDecoderTellStatus tell_cb(
+ __a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *absolute_byte_offset, void *client_data)
+{
+ struct private_flac_afh_data *pfad = client_data;
+
+ *absolute_byte_offset = pfad->fpos;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+/* libflac insits on this callback being present. */
+static FLAC__StreamDecoderWriteStatus write_cb(
+ __a_unused const FLAC__StreamDecoder *decoder,
+ __a_unused const FLAC__Frame *frame,
+ __a_unused const FLAC__int32 *const buffer[],
+ __a_unused void *client_data)
+{
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static void error_cb(
+ __a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status,
+ __a_unused void *client_data)
+{
+ PARA_ERROR_LOG("%s\n", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+static int flac_afh_read_chunks(struct private_flac_afh_data *pfad)
+{
+ FLAC__StreamDecoder *decoder;
+ FLAC__StreamDecoderInitStatus init_status;
+ FLAC__bool ok;
+ FLAC__uint64 c;
+ unsigned chunk_table_size = 0;
+ int ret;
+ struct afh_info *afhi = pfad->afhi;
+
+ PARA_INFO_LOG("reading chunk table\n");
+ afhi->chunk_table = NULL;
+ decoder = FLAC__stream_decoder_new();
+ if (!decoder)
+ return -E_FLAC_AFH_DECODER_ALLOC;
+ ret = -E_FLAC_AFH_DECODER_INIT;
+ init_status = FLAC__stream_decoder_init_stream(
+ decoder,
+ read_cb,
+ NULL, /* seek */
+ tell_cb,
+ NULL, /* length */
+ NULL, /* eof */
+ write_cb,
+ NULL,
+ error_cb,
+ pfad
+ );
+ if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+ goto free_decoder;
+ ret = -E_FLAC_SKIP_META;
+ ok = FLAC__stream_decoder_process_until_end_of_metadata(decoder);
+ if (!ok)
+ goto free_decoder;
+ for (c = 0;; c++) {
+ FLAC__uint64 pos;
+ FLAC__StreamDecoderState state;
+
+ ret = -E_FLAC_DECODE_POS;
+ ok = FLAC__stream_decoder_get_decode_position(decoder, &pos);
+ if (!ok)
+ goto free_decoder;
+ if (c >= chunk_table_size) {
+ chunk_table_size = 2 * chunk_table_size + 100;
+ afhi->chunk_table = para_realloc(afhi->chunk_table,
+ chunk_table_size * sizeof(uint32_t));
+ }
+ afhi->chunk_table[c] = pos;
+
+ ok = FLAC__stream_decoder_skip_single_frame(decoder);
+ if (!ok)
+ break;
+ state = FLAC__stream_decoder_get_state(decoder);
+ if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
+ break;
+ }
+ afhi->chunks_total = c;
+ ret = 1;
+free_decoder:
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ if (ret < 0)
+ freep(&afhi->chunk_table);
+ return ret;
+}
+
+static int flac_get_file_info(char *map, size_t map_bytes, __a_unused int fd,
+ struct afh_info *afhi)
+{
+ struct private_flac_afh_data pfad_struct = {
+ .map = map,
+ .map_bytes = map_bytes,
+ .afhi = afhi
+ }, *pfad = &pfad_struct;
+ int ret;
+ double chunk_time;
+
+ afhi->header_len = 0;
+ ret = flac_read_meta(pfad);
+ if (ret < 0)
+ return ret;
+ pfad->fpos = 0;
+ ret = flac_afh_read_chunks(pfad);
+ if (ret < 0) {
+ free_tags(&afhi->tags);
+ return ret;
+ }
+ afhi->techinfo = make_message("blocksize: %u", pfad->blocksize);
+ afhi->seconds_total = DIV_ROUND_UP(afhi->chunks_total * pfad->blocksize,
+ afhi->frequency);
+ afhi->bitrate = pfad->map_bytes * 8 / afhi->seconds_total / 1024;
+ chunk_time = (double)pfad->blocksize / afhi->frequency;
+ afhi->chunk_tv.tv_sec = chunk_time;
+ chunk_time *= 1000 * 1000;
+ chunk_time -= afhi->chunk_tv.tv_sec * 1000 * 1000;
+ afhi->chunk_tv.tv_usec = chunk_time;
+ return 1;
+}
+
+static const char* flac_suffixes[] = {"flac", NULL};
+
+/**
+ * The init function of the flac audio format handler.
+ *
+ * \param afh pointer to the struct to initialize
+ */
+void flac_afh_init(struct audio_format_handler *afh)
+{
+ afh->get_file_info = flac_get_file_info,
+ afh->suffixes = flac_suffixes;
+}
--- /dev/null
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file flacdec_filter.c The flac decoder. */
+
+#include <regex.h>
+#include <stdbool.h>
+#include <FLAC/stream_decoder.h>
+
+#include "para.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.h"
+#include "buffer_tree.h"
+#include "filter.h"
+#include "error.h"
+#include "string.h"
+
+struct private_flacdec_data {
+ FLAC__StreamDecoder *decoder;
+ bool have_more;
+ /*
+ * We can not consume directly what was copied by the read callback
+ * because we might need to feed unconsumend bytes to the decoder again
+ * after the read callback ran out of data and returned ABORT. So we
+ * track how many bytes are unconsumed so far.
+ */
+ size_t unconsumed;
+};
+
+static FLAC__StreamDecoderReadStatus read_cb(
+ __a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__byte buffer[], size_t *bytes, void *client_data)
+{
+ struct filter_node *fn = client_data;
+ struct private_flacdec_data *pfd = fn->private_data;
+ struct btr_node *btrn = fn->btrn;
+ char *btr_buf;
+ size_t copy, want = *bytes, have;
+ int ns;
+
+ *bytes = 0;
+ assert(want > 0);
+ ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+ if (ns < 0)
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+ for (;;) {
+ have = btr_next_buffer_omit(btrn, pfd->unconsumed, &btr_buf);
+ if (have == 0)
+ break;
+ copy = PARA_MIN(want, have);
+ //PARA_CRIT_LOG("want: %zu, have: %zu, unconsumed %zu\n",
+ // want, have, pfd->unconsumed);
+ memcpy(buffer, btr_buf, copy);
+ pfd->unconsumed += copy;
+ *bytes += copy;
+ buffer += copy;
+ want -= copy;
+ if (want == 0)
+ break;
+ }
+ if (*bytes > 0)
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+ /*
+ * We are kind of screwed here. Returning CONTINUE with a byte count of
+ * zero leads to an endless loop, so we must return either EOF or
+ * ABORT. Unfortunately, both options require to flush the decoder
+ * afterwards because libFLAC refuses to resume decoding if the decoder
+ * is in EOF or ABORT state. But flushing implies dropping the decoder
+ * input queue, so buffered data is lost.
+ *
+ * We work around this shortcoming by remembering the number of
+ * unconsumed bytes in pfd->unconsumed. In the write/meta callbacks,
+ * this number is decreased whenever a frame has been decoded
+ * successfully and btr_consume() has been called to consume the bytes
+ * corresponding to the decoded frame. After returning ABORT here, the
+ * decoder can be flushed, and we will feed the unconsumed bytes again.
+ */
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+}
+
+/*
+ * The exact value does not really matter. It just has to be larger than the
+ * size of the input buffer of the bitstream reader of libflac.
+ */
+#define TELL_CB_DUMMY_VAL 1000000
+
+/*
+ * FLAC__stream_decoder_get_decode_position() invokes this callback. The flac
+ * library then gets the number of unconsumed bytes from the bitstream reader,
+ * subtracts this number from the offset returned here and returns the
+ * difference as the decode position.
+ */
+static FLAC__StreamDecoderTellStatus tell_cb(__a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__uint64 *absolute_byte_offset, __a_unused void *client_data)
+{
+ *absolute_byte_offset = TELL_CB_DUMMY_VAL;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+/*
+ * There is no API function that returns the number of unconsumed bytes
+ * directly. The trick is to define a tell callback which always returns a
+ * fixed dummy value and compute the number of unconsumed bytes from the return
+ * value of FLAC__stream_decoder_get_decode_position().
+ */
+static void flac_consume(struct filter_node *fn)
+{
+ struct private_flacdec_data *pfd = fn->private_data;
+ struct btr_node *btrn = fn->btrn;
+ FLAC__uint64 x;
+
+ FLAC__stream_decoder_get_decode_position(pfd->decoder, &x);
+ assert(x <= TELL_CB_DUMMY_VAL);
+ x = TELL_CB_DUMMY_VAL - x; /* number of unconsumed bytes */
+ assert(x <= pfd->unconsumed);
+ btr_consume(btrn, pfd->unconsumed - x);
+ pfd->unconsumed = x;
+}
+
+static FLAC__StreamDecoderWriteStatus write_cb(
+ const FLAC__StreamDecoder *decoder,
+ const FLAC__Frame *frame,
+ const FLAC__int32 *const buffer[],
+ void *client_data)
+{
+ struct filter_node *fn = client_data;
+ struct btr_node *btrn = fn->btrn;
+ size_t k, n = frame->header.blocksize;
+ unsigned channels = FLAC__stream_decoder_get_channels(decoder);
+ char *outbuffer = para_malloc(n * channels * 2);
+
+ if (channels == 1) {
+ for (k = 0; k < n; k++) {
+ int sample = buffer[0][k];
+ write_int16_host_endian(outbuffer + 2 * k, sample);
+ }
+ } else {
+ for (k = 0; k < n; k++) {
+ int left = buffer[0][k], right = buffer[1][k];
+ write_int16_host_endian(outbuffer + 4 * k, left);
+ write_int16_host_endian(outbuffer + 4 * k + 2, right);
+ }
+ }
+ btr_add_output(outbuffer, n * 4, btrn);
+ flac_consume(fn);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static void meta_cb (__a_unused const FLAC__StreamDecoder *decoder,
+ __a_unused const FLAC__StreamMetadata *metadata,
+ void *client_data)
+{
+ flac_consume(client_data);
+}
+
+static void error_cb( __a_unused const FLAC__StreamDecoder *decoder,
+ FLAC__StreamDecoderErrorStatus status,
+ __a_unused void *client_data)
+{
+ PARA_ERROR_LOG("%s\n", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+static int flacdec_init(struct filter_node *fn)
+{
+ struct private_flacdec_data *pfd = fn->private_data;
+ FLAC__StreamDecoderInitStatus init_status;
+
+ PARA_INFO_LOG("initializing flac decoder\n");
+ pfd->decoder = FLAC__stream_decoder_new();
+ if (!pfd->decoder)
+ return -E_FLACDEC_DECODER_ALLOC;
+ FLAC__stream_decoder_set_metadata_respond_all(pfd->decoder);
+ init_status = FLAC__stream_decoder_init_stream(pfd->decoder, read_cb,
+ NULL /* seek */, tell_cb, NULL /* length_cb */, NULL /* eof_cb */,
+ write_cb, meta_cb, error_cb, fn);
+ if (init_status == FLAC__STREAM_DECODER_INIT_STATUS_OK)
+ return 1;
+ FLAC__stream_decoder_delete(pfd->decoder);
+ return -E_FLACDEC_DECODER_INIT;
+}
+
+static int flacdec_execute(struct btr_node *btrn, const char *cmd,
+ char **result)
+{
+ struct filter_node *fn = btr_context(btrn);
+ struct private_flacdec_data *pfd = fn->private_data;
+ unsigned sample_rate = FLAC__stream_decoder_get_sample_rate(pfd->decoder);
+ unsigned channels = FLAC__stream_decoder_get_channels(pfd->decoder);
+
+ return decoder_execute(cmd, sample_rate, channels, result);
+}
+
+#define FLACDEC_MAX_OUTPUT_SIZE (640 * 1024)
+
+static bool output_queue_full(struct btr_node *btrn)
+{
+ return btr_get_output_queue_size(btrn) > FLACDEC_MAX_OUTPUT_SIZE;
+}
+
+static void flacdec_pre_select(struct sched *s, struct task *t)
+{
+ struct filter_node *fn = container_of(t, struct filter_node, task);
+ struct private_flacdec_data *pfd = fn->private_data;
+ struct btr_node *btrn = fn->btrn;
+ int ret;
+
+ ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+ if (ret < 0)
+ return sched_min_delay(s);
+ if (output_queue_full(btrn))
+ return sched_request_timeout_ms(30, s);
+ if (ret > 0 || pfd->have_more)
+ return sched_min_delay(s);
+}
+
+static void flacdec_post_select(__a_unused struct sched *s, struct task *t)
+{
+ struct filter_node *fn = container_of(t, struct filter_node, task);
+ struct private_flacdec_data *pfd = fn->private_data;
+ struct btr_node *btrn = fn->btrn;
+ int ret;
+
+ if (output_queue_full(btrn))
+ return;
+ ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL);
+ if (ret < 0 && ret != -E_BTR_EOF) /* fatal error */
+ goto out;
+ if (ret <= 0 && !pfd->have_more) /* nothing to do */
+ goto out;
+ if (!pfd->decoder) {
+ ret = flacdec_init(fn);
+ goto out;
+ }
+ pfd->unconsumed = 0;
+ for (;;) {
+ if (output_queue_full(btrn)) {
+ pfd->have_more = true;
+ break;
+ }
+ pfd->have_more = false;
+ FLAC__StreamDecoderState state;
+ FLAC__stream_decoder_process_single(pfd->decoder);
+ state = FLAC__stream_decoder_get_state(pfd->decoder);
+ //PARA_CRIT_LOG("state: %s\n", FLAC__stream_decoder_get_resolved_state_string(pfd->decoder));
+ ret = -E_FLACDEC_EOF;
+ if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
+ goto out;
+ if (state == FLAC__STREAM_DECODER_ABORTED) {
+ FLAC__stream_decoder_flush(pfd->decoder);
+ fn->min_iqs = pfd->unconsumed + 1;
+ break;
+ }
+ fn->min_iqs = 0;
+ }
+ ret = 1;
+out:
+ t->error = ret;
+ if (ret < 0)
+ btr_remove_node(btrn);
+}
+
+static void flacdec_close(struct filter_node *fn)
+{
+ struct private_flacdec_data *pfd = fn->private_data;
+
+ FLAC__stream_decoder_finish(pfd->decoder);
+ FLAC__stream_decoder_delete(pfd->decoder);
+ free(pfd);
+ fn->private_data = NULL;
+}
+
+static void flacdec_open(struct filter_node *fn)
+{
+ struct private_flacdec_data *pfd = para_calloc(sizeof(*pfd));
+ fn->private_data = pfd;
+ fn->min_iqs = 0;
+}
+
+/**
+ * The init function of the flacdec filter.
+ *
+ * \param f Pointer to the filter struct to initialize.
+ *
+ * \sa filter::init.
+ */
+void flacdec_filter_init(struct filter *f)
+{
+ f->open = flacdec_open;
+ f->close = flacdec_close;
+ f->pre_select = flacdec_pre_select;
+ f->post_select = flacdec_post_select;
+ f->execute = flacdec_execute;
+}
struct spx_header_info shi;
};
-
-static char *copy_comment(const char *src, int len)
-{
- char *p = para_malloc(len + 1);
-
- if (len > 0)
- memcpy(p, src, len);
- p[len] = '\0';
- PARA_DEBUG_LOG("%s\n", p);
- return p;
-}
-
static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
char **p)
{
- int len = strlen(type);
-
- if (taglen <= len)
- return false;
- if (strncasecmp(tag, type, len))
- return false;
- if (tag[len] != '=')
+ char *q = key_value_copy(tag, taglen, type);
+ if (!q)
return false;
free(*p);
- *p = copy_comment(tag + len + 1, taglen - len - 1);
+ *p = q;
return true;
}
c += 4;
if (c + len > end)
return -E_SPX_COMMENT;
- tags->comment = copy_comment(c, len);
+ tags->comment = safe_strdup(c, len);
c += len;
if (c + 4 > end)
continue;
if (copy_if_tag_type(c, len, "comment", &tags->comment))
continue;
- tag = copy_comment(c, len);
+ tag = safe_strdup(c, len);
PARA_NOTICE_LOG("unrecognized comment: %s\n", tag);
free(tag);
}
sit->task.pre_select = stdin_pre_select;
sit->task.post_select = stdin_post_select;
- sit->btrp = btr_pool_new("stdin", 64 * 1024);
+ sit->btrp = btr_pool_new("stdin", 128 * 1024);
sprintf(sit->task.status, "stdin reader");
ret = mark_fd_nonblocking(STDIN_FILENO);
if (ret >= 0)
free(buf);
return -E_REGEX;
}
+
+/**
+ * strdup() for not necessarily zero-terminated strings.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes to be copied.
+ *
+ * \return A 0-terminated buffer of length \a len + 1.
+ *
+ * This is similar to strndup(), which is a GNU extension. However, one
+ * difference is that strndup() returns \p NULL if insufficient memory was
+ * available while this function aborts in this case.
+ *
+ * \sa strdup(), \ref para_strdup().
+ */
+char *safe_strdup(const char *src, size_t len)
+{
+ char *p;
+
+ assert(len < (size_t)-1);
+ p = para_malloc(len + 1);
+ if (len > 0)
+ memcpy(p, src, len);
+ p[len] = '\0';
+ return p;
+}
+
+/**
+ * Copy the value of a key=value pair.
+ *
+ * This checks whether the given buffer starts with "key=", ignoring case. If
+ * yes, a copy of the value is returned. The source buffer may not be
+ * zero-terminated.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes of the tag.
+ * \param key Only copy if it is the value of this key.
+ *
+ * \return A zero-terminated buffer, or \p NULL if the key was
+ * not of the given type.
+ */
+char *key_value_copy(const char *src, size_t len, const char *key)
+{
+ int keylen = strlen(key);
+
+ if (len <= keylen)
+ return NULL;
+ if (strncasecmp(src, key, keylen))
+ return NULL;
+ if (src[keylen] != '=')
+ return NULL;
+ return safe_strdup(src + keylen + 1, len - keylen - 1);
+}
void free_argv(char **argv);
int para_regcomp(regex_t *preg, const char *regex, int cflags);
void freep(void *arg);
+char *safe_strdup(const char *src, size_t len);
+char *key_value_copy(const char *src, size_t len, const char *key);
- XREFERENCE(http://www.speex.org/, speex). In order to stream
or decode speex files, libspeex (libspeex-dev) is required.
+ - XREFERENCE(http://flac.sourceforge.net/, flac). To stream
+ or decode files encoded with the _Free Lossless Audio Codec_,
+ libFLAC (libFLAC-dev) must be installed.
+
- XREFERENCE(ftp://ftp.alsa-project.org/pub/lib/, alsa-lib). On
Linux, you'll need to have ALSA's development package
libasound2-dev installed.
is composed of superframes, each containing one or more frames of
2048 samples. For 16 bit stereo a WMA superframe is about 8K large.
+*FLAC*
+
+The Free Lossless Audio Codec (FLAC) compresses audio without quality
+loss. It gives better compression ratios than a general purpose
+compressor like zip or bzip2 because FLAC is designed specifically
+for audio. A FLAC-encoded file consits of frames of varying size, up
+to 16K. Each frame starts with a header that contains all information
+necessary to decode the frame.
+
Meta data
~~~~~~~~~
32 characters long. ID3, version 2 is much more flexible but requires
a separate library being installed for paraslash to support it.
-Ogg vorbis files contain meta data as Vorbis comments, which are
-typically implemented as strings of the form "[TAG]=[VALUE]". Unlike
-ID3 version 1 tags, one may use whichever tags are appropriate for
-the content.
+Ogg vorbis, ogg speex and flac files contain meta data as Vorbis
+comments, which are typically implemented as strings of the form
+"[TAG]=[VALUE]". Unlike ID3 version 1 tags, one may use whichever
+tags are appropriate for the content.
AAC files usually use the MPEG-4 container format for storing meta
data while WMA files wrap meta data as special objects within the
of an audio file. For MP3 files, a chunk is the same as an MP3 frame,
while for OGG files a chunk is an OGG page, etc. Therefore the chunk
size varies considerably between audio formats, from a few hundred
-bytes (MP3) up to 8K (WMA).
+bytes (MP3) up to 16K (FLAC).
The chunk table contains the offsets within the audio file that
correspond to the chunk boundaries of the file. Like the meta data,