Merge branch 't/flac'
authorAndre Noll <maan@systemlinux.org>
Tue, 29 Nov 2011 07:36:49 +0000 (08:36 +0100)
committerAndre Noll <maan@systemlinux.org>
Tue, 29 Nov 2011 07:41:33 +0000 (08:41 +0100)
15 files changed:
FEATURES
NEWS
afh_common.c
aft.c
buffer_tree.c
buffer_tree.h
configure.ac
error.h
flac_afh.c [new file with mode: 0644]
flacdec_filter.c [new file with mode: 0644]
spx_afh.c
stdin.c
string.c
string.h
web/manual.m4

index 2839d79cc0393f4ee30afa9b86ed7f2a3c3d126f..613acc19c2f34a14d15a86921aaf94486ac04df2 100644 (file)
--- a/FEATURES
+++ b/FEATURES
@@ -5,7 +5,7 @@ Features
 
        * 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
diff --git a/NEWS b/NEWS
index 0b8b33e8d2a5034be6b171335cc032be003aafdc..8bb2862e93012c815d50be0513dee99c2b8eda87 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@
 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
index 44a6fd62cbe50c758aba17b33dd45c0e7eb831c6..1228c0d80e5e8c276072bf38a29b0d1f635f2b74 100644 (file)
@@ -28,6 +28,9 @@ void mp3_init(struct audio_format_handler *);
 #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 *);
 /**
@@ -67,6 +70,12 @@ static struct audio_format_handler afl[] = {
                .name = "spx",
 #ifdef HAVE_SPEEX
                .init = spx_afh_init,
+#endif
+       },
+       {
+               .name = "flac",
+#ifdef HAVE_FLAC
+               .init = flac_afh_init,
 #endif
        },
        {
diff --git a/aft.c b/aft.c
index f6d0c4b35a22160317d6d3bac20556208ac2669d..d2113f1c0d327825b1ebf91ca934f69752cccf4d 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -107,6 +107,8 @@ struct ls_widths {
        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. */
@@ -902,7 +904,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        "%*d "  /* image_id  */
                        "%*d "  /* lyrics_id */
                        "%*d "  /* bitrate */
-                       "%s "   /* audio format */
+                       "%*s "  /* audio format */
                        "%*d "  /* frequency */
                        "%d "   /* channels */
                        "%s "   /* duration */
@@ -914,6 +916,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        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,
@@ -1328,6 +1331,8 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        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 "-") */
index 88fb8362bb0c9233d9a5916519b4acf47f4a63ed..cb9d514f1aa5adfc501d5eee89150b7aa31821bc 100644 (file)
@@ -563,40 +563,95 @@ static size_t btr_get_buffer_by_reference(struct btr_buffer_reference *br, char
 }
 
 /**
- * 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.
  *
index fcf9df62570a6a2bab3acc1fc1a54831b541ccc4..b60751e5db3ae697d5cee80a7321925e5cd92e43 100644 (file)
@@ -189,6 +189,7 @@ size_t btr_get_input_queue_size(struct btr_node *btrn);
 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);
index 6415668b50560d6ec89af8ec192544c1c3775735..a2e5fed14569deb9c059172d76b83dce0f10daa1 100644 (file)
@@ -729,6 +729,47 @@ if test ${have_libid3tag} = yes; then
 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"
diff --git a/error.h b/error.h
index b3eb7e6edef673e5c6c5929e26c1e9157630c560..311b6ebdab1c4248acefd0c08d1c5346948ec996 100644 (file)
--- a/error.h
+++ b/error.h
@@ -38,6 +38,24 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 
 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?)"), \
diff --git a/flac_afh.c b/flac_afh.c
new file mode 100644 (file)
index 0000000..b3a0f6e
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * 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;
+}
diff --git a/flacdec_filter.c b/flacdec_filter.c
new file mode 100644 (file)
index 0000000..0275964
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * 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;
+}
index e693c4150e03b7dae9b991c343ec7abbfe980db0..a95f234c5e318322a436ad07d74dd78acf169d05 100644 (file)
--- a/spx_afh.c
+++ b/spx_afh.c
@@ -57,31 +57,14 @@ struct private_spx_data {
        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;
 }
 
@@ -100,7 +83,7 @@ static int spx_get_comments(unsigned char *comments, int length,
        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)
@@ -129,7 +112,7 @@ static int spx_get_comments(unsigned char *comments, int length,
                        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);
        }
diff --git a/stdin.c b/stdin.c
index 14dd9d40f4d56e2cb5845fa95546db7a7baa8581..c4ab323d488da7dc4e1aa1b6fc05af9e88b4b717 100644 (file)
--- a/stdin.c
+++ b/stdin.c
@@ -104,7 +104,7 @@ void stdin_set_defaults(struct stdin_task *sit)
 
        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)
index cefb45d674075cf1fcb2e42b42e5b0a929a52c16..f7d70353db977d6543055c19c7bd68a17b83a3ce 100644 (file)
--- a/string.c
+++ b/string.c
@@ -793,3 +793,56 @@ int para_regcomp(regex_t *preg, const char *regex, int cflags)
        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);
+}
index 06f4d3e6ca88646fbe531b42af49089be23680ba..403b9c6f15d15404076eeed7ea641b8b22ed600f 100644 (file)
--- a/string.h
+++ b/string.h
@@ -83,3 +83,5 @@ int create_argv(const char *buf, const char *delim, char ***result);
 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);
index 6bddbf11a7f28824d871530812d879e72ac9ec44..7eecdbb31ad09169666f4d2e353f215432e12961 100644 (file)
@@ -254,6 +254,10 @@ Optional:
        - 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.
@@ -1111,6 +1115,15 @@ how meta data about the file is to be encoded. The bit stream of WMA
 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
 ~~~~~~~~~
 
@@ -1123,10 +1136,10 @@ title, album, year and comment tags. Each of these can only be at most
 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
@@ -1144,7 +1157,7 @@ paraslash uses the word "chunk" as common term for the building blocks
 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,