]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/interactive'
authorAndre Noll <maan@systemlinux.org>
Sun, 4 Dec 2011 23:13:32 +0000 (00:13 +0100)
committerAndre Noll <maan@systemlinux.org>
Sun, 4 Dec 2011 23:22:32 +0000 (00:22 +0100)
Conflicts:
string.h

18 files changed:
FEATURES
NEWS
afh_common.c
afs.c
afs.h
aft.c
blob.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 d723541c7269ab670411f69d54ee7e64e0ba691c..6ad420dfe7a63800b3725f1295ec3c84cb580d5d 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 d4a58fd1e9a4e24f2a2d7e0b4d00ce1431602db1..1c6d821dbbc362ae77190724a37c2cc497d7fcd6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,8 +2,16 @@
 0.4.9 (to be announced) "hybrid causality"
 ------------------------------------------
 
+Support for another audio format, interactive mode for para_client
+and para_audiod 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.
+       - When executed without specifying a command, para_client
+         and para_audioc start an interactive shell (requires
+         libreadline being installed). The interactive mode offers
+         full tab completion and command line history.
        - autogen.sh now detects a distcc setup and adjusts the
          parameter for the -j option of make accordingly.
        - Shared memory areas are no longer restricted to 64K. We now
@@ -17,6 +25,9 @@
          use the DMIX plugin.
        - Simplified and unified receiver code.
        - Makefile cleanups.
+       - Commands which print a list of matching audio files now
+         emit a meaningful error message if no audio file matched the
+         given pattern(s).
 
 --------------------------------------
 0.4.8 (2011-08-19) "nested assignment"
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/afs.c b/afs.c
index b4c85dcb82eb2061501c2567005f66015b30b6f0..ff3c95fe03853b0aaa80d873ea75748a10bf73d2 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -363,7 +363,10 @@ static int action_if_pattern_matches(struct osl_row *row, void *data)
                        continue;
                if (ret)
                        return -E_FNMATCH;
-               return pmd->action(pmd->table, row, name, pmd->data);
+               ret = pmd->action(pmd->table, row, name, pmd->data);
+               if (ret >= 0)
+                       pmd->num_matches++;
+               return ret;
        }
        return 1;
 }
diff --git a/afs.h b/afs.h
index f54aa87ccbc3c2ad0a3ab44b054f22a6560aaa85..2fdefa70c1830ac28f5787361ad2d1f965327926 100644 (file)
--- a/afs.h
+++ b/afs.h
@@ -181,6 +181,8 @@ struct pattern_match_data {
        struct osl_object patterns;
        /** Data pointer passed to the action function. */
        void *data;
+       /** Gets increased by one for each match. */
+       unsigned num_matches;
        /** For each matching row, this function will be called. */
        int (*action)(struct osl_table *table, struct osl_row *row, const char *name, void *data);
 };
diff --git a/aft.c b/aft.c
index 3d22e242ef94ab21b17487b345d636a01dabb3d9..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 "-") */
@@ -1364,8 +1369,11 @@ static void com_ls_callback(int fd, const struct osl_object *query)
                        prepare_ls_row));
        if (ret < 0)
                goto out;
-       if (!opts->num_matching_paths)
+       if (opts->num_matching_paths == 0) {
+               if (opts->num_patterns > 0)
+                       para_printf(&b, "no matches\n");
                goto out;
+       }
        ret = sort_matching_paths(opts);
        if (ret < 0)
                goto out;
@@ -2020,8 +2028,6 @@ struct touch_action_data {
        struct com_touch_options *cto;
        /** Message buffer. */
        struct para_buffer pb;
-       /** How many audio files matched the given pattern. */
-       unsigned num_matches;
 };
 
 static int touch_audio_file(__a_unused struct osl_table *table,
@@ -2068,7 +2074,6 @@ static int touch_audio_file(__a_unused struct osl_table *table,
                if (tad->cto->amp >= 0)
                        new_afsi.amp = tad->cto->amp;
        }
-       tad->num_matches++;
        save_afsi(&new_afsi, &obj); /* in-place update */
        aced.aft_row = row;
        aced.old_afsi = &old_afsi;
@@ -2100,9 +2105,8 @@ static void com_touch_callback(int fd, const struct osl_object *query)
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                ret2 = para_printf(&tad.pb, "%s\n", para_strerror(-ret));
-       else
-               if (!tad.num_matches)
-                       ret2 = para_printf(&tad.pb, "no matches\n");
+       else if (pmd.num_matches == 0)
+               ret2 = para_printf(&tad.pb, "no matches\n");
        if (ret2 >= 0 && tad.pb.offset)
                pass_buffer_as_shm(tad.pb.buf, tad.pb.offset, &fd);
        free(tad.pb.buf);
@@ -2198,8 +2202,6 @@ struct com_rm_action_data {
        uint32_t flags;
        /** Message buffer. */
        struct para_buffer pb;
-       /** Number of audio files removed. */
-       unsigned num_removed;
 };
 
 static int remove_audio_file(__a_unused struct osl_table *table,
@@ -2217,8 +2219,6 @@ static int remove_audio_file(__a_unused struct osl_table *table,
        ret = osl(osl_del_row(audio_file_table, row));
        if (ret < 0)
                para_printf(&crd->pb, "%s: %s\n", name, para_strerror(-ret));
-       else
-               crd->num_removed++;
        return ret;
 }
 
@@ -2248,11 +2248,11 @@ static void com_rm_callback(int fd, const struct osl_object *query)
                para_printf(&crd.pb, "%s\n", para_strerror(-ret));
                return;
        }
-       if (!crd.num_removed && !(crd.flags & RM_FLAG_FORCE))
+       if ((pmd.num_matches == 0) && !(crd.flags & RM_FLAG_FORCE))
                ret = para_printf(&crd.pb, "no matches -- nothing removed\n");
-       else {
-               if (crd.flags & RM_FLAG_VERBOSE)
-                       ret = para_printf(&crd.pb, "removed %u files\n", crd.num_removed);
+       else if (crd.flags & RM_FLAG_VERBOSE) {
+               ret = para_printf(&crd.pb, "removed %u files\n",
+                       pmd.num_matches);
        }
        if (ret >= 0 && crd.pb.offset)
                pass_buffer_as_shm(crd.pb.buf, crd.pb.offset, &fd);
@@ -2321,8 +2321,6 @@ enum cpsi_flags {
 struct cpsi_action_data {
        /** command line flags (see \ref cpsi_flags). */
        unsigned flags;
-       /** Number of audio files changed. */
-       unsigned num_copied;
        /** Message buffer. */
        struct para_buffer pb;
        /** Values are copied from here. */
@@ -2354,7 +2352,6 @@ static int copy_selector_info(__a_unused struct osl_table *table,
        if (cad->flags & CPSI_FLAG_COPY_ATTRIBUTES)
                target_afsi.attributes = cad->source_afsi.attributes;
        save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */
-       cad->num_copied++;
        if (cad->flags & CPSI_FLAG_VERBOSE) {
                ret = para_printf(&cad->pb, "copied afsi to %s\n", name);
                if (ret < 0)
@@ -2397,9 +2394,9 @@ out:
        if (ret < 0)
                para_printf(&cad.pb, "%s\n", para_strerror(-ret));
        else if (cad.flags & CPSI_FLAG_VERBOSE) {
-               if (cad.num_copied)
+               if (pmd.num_matches > 0)
                        para_printf(&cad.pb, "copied requested afsi from %s "
-                               "to %u files\n", source_path, cad.num_copied);
+                               "to %u files\n", source_path, pmd.num_matches);
                else
                        para_printf(&cad.pb, "nothing copied\n");
        }
diff --git a/blob.c b/blob.c
index 707c6c6254b88a359a5297c469a1f3105528e5bc..2545aea9af2d9dbb5d34b4a41a6d3363c35c3385 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -156,6 +156,8 @@ static void com_lsblob_callback(struct osl_table *table,
        ret = for_each_matching_row(&pmd);
        if (ret < 0)
                para_printf(&lbad.pb, "%s\n", para_strerror(-ret));
+       else if (pmd.num_matches == 0 && pmd.patterns.size > 0)
+               para_printf(&lbad.pb, "no matches\n");
        if (lbad.pb.offset)
                pass_buffer_as_shm(lbad.pb.buf, lbad.pb.offset, &fd);
        free(lbad.pb.buf);
@@ -203,9 +205,9 @@ static int cat_blob(struct osl_table *table, struct osl_row *row,
 
        ret = osl(osl_open_disk_object(table, row, BLOBCOL_DEF, &obj));
        if (ret < 0)
-               return ret;
-       if (obj.size)
-               ret = pass_buffer_as_shm(obj.data, obj.size, data);
+               return (ret == osl(-E_OSL_EMPTY))? 0 : ret;
+       assert(obj.size > 0);
+       ret = pass_buffer_as_shm(obj.data, obj.size, data);
        ret2 = osl(osl_close_disk_object(&obj));
        return (ret < 0)? ret : ret2;
 }
@@ -223,6 +225,10 @@ static void com_catblob_callback(struct osl_table *table, int fd,
                .action = cat_blob
        };
        for_each_matching_row(&pmd);
+       if (pmd.num_matches == 0) {
+               char err_msg[] = "no matches\n";
+               pass_buffer_as_shm(err_msg, sizeof(err_msg), &fd);
+       }
 }
 
 static int com_catblob(callback_function *f, struct stream_cipher_context *scc, int argc,
@@ -238,8 +244,6 @@ static int com_catblob(callback_function *f, struct stream_cipher_context *scc,
 struct rmblob_data {
        /** Message buffer. */
        struct para_buffer pb;
-       /** Number of removed blobs. */
-       unsigned num_removed;
 };
 
 static int remove_blob(struct osl_table *table, struct osl_row *row,
@@ -251,7 +255,6 @@ static int remove_blob(struct osl_table *table, struct osl_row *row,
                para_printf(&rmbd->pb, "%s: %s\n", name, para_strerror(-ret));
                return ret;
        }
-       rmbd->num_removed++;
        return 1;
 }
 
@@ -260,7 +263,6 @@ static void com_rmblob_callback(struct osl_table *table, int fd,
 {
        int ret, ret2 = 0;
        struct rmblob_data rmbd = {
-               .num_removed = 0,
                .pb = {
                        .max_size = shm_get_shmmax(),
                        .private_data = &fd,
@@ -282,10 +284,10 @@ static void com_rmblob_callback(struct osl_table *table, int fd,
                if (ret2 < 0)
                        goto out;
        }
-       if (!rmbd.num_removed)
+       if (pmd.num_matches == 0)
                ret2 = para_printf(&rmbd.pb, "no matches, nothing removed\n");
        else {
-               ret2 = para_printf(&rmbd.pb, "removed %d blobs\n", rmbd.num_removed);
+               ret2 = para_printf(&rmbd.pb, "removed %d blobs\n", pmd.num_matches);
                afs_event(BLOB_RENAME, NULL, table);
        }
 out:
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 66541fe4fd637c564875f5f45f734a086c6a86de..89242d969bbab440a8ac085262b3de45ea3d3392 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 55c0ab0264b241f50207cc1e0311443fdbc33f42..ae94157f81690568fc20a741c0f1757ef664e83a 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 d226af963af82c6c99e557402694b919b26c2192..3bddd5e80896e843470687671a05cb0ef09051e2 100644 (file)
--- a/string.c
+++ b/string.c
@@ -819,3 +819,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 23b790021277475ea281e12a9b2a39d05e7ffb62..30af1fdb647f6701086b235324a6ff2867210822 100644 (file)
--- a/string.h
+++ b/string.h
@@ -84,3 +84,5 @@ void free_argv(char **argv);
 int para_regcomp(regex_t *preg, const char *regex, int cflags);
 void freep(void *arg);
 int compute_word_num(const char *buf, const char *delim, int offset);
+char *safe_strdup(const char *src, size_t len);
+char *key_value_copy(const char *src, size_t len, const char *key);
index 5d9fbbb7c78514317238b49f08a538fe1f7abb92..041457e6157a585cd479edba8e65ab45fcd5e207 100644 (file)
@@ -258,6 +258,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.
@@ -1119,6 +1123,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
 ~~~~~~~~~
 
@@ -1131,10 +1144,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
@@ -1152,7 +1165,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,