Implement streaming mode for para_afh.
authorAndre Noll <maan@systemlinux.org>
Sat, 15 Mar 2008 18:03:18 +0000 (19:03 +0100)
committerAndre Noll <maan@systemlinux.org>
Sat, 15 Mar 2008 18:03:18 +0000 (19:03 +0100)
README
afh.c
afh.ggo
error.h

diff --git a/README b/README
index 0e1e5cb..3308d0b 100644 (file)
--- a/README
+++ b/README
@@ -102,10 +102,12 @@ para_afh
 
 A small stand-alone program that prints tech info about the given
 audio file to stdout. It can be instructed to print a "chunk table",
 
 A small stand-alone program that prints tech info about the given
 audio file to stdout. It can be instructed to print a "chunk table",
-an array of offsets within the audio file. This allows third-party
-streaming software that is unaware of the particular audio format
-to send complete frames in real time. Currently, mp3, ogg vorbis and
-aac are supported.
+an array of offsets within the audio file or to write the content of
+the audio file in complete chunks 'just in time'.
+
+This allows third-party streaming software that is unaware of
+the particular audio format to send complete frames in real
+time. Currently, mp3, ogg vorbis and aac are supported.
 
 ----------
 para_write
 
 ----------
 para_write
diff --git a/afh.c b/afh.c
index 84a061b..2ff2c78 100644 (file)
--- a/afh.c
+++ b/afh.c
@@ -7,6 +7,7 @@
 /** \file afh.c Paraslash's standalone audio format handler tool. */
 
 #include <dirent.h>
 /** \file afh.c Paraslash's standalone audio format handler tool. */
 
 #include <dirent.h>
+#include <sys/time.h>
 
 #include "para.h"
 #include "string.h"
 
 #include "para.h"
 #include "string.h"
@@ -22,9 +23,100 @@ const char *status_item_list[] = {STATUS_ITEM_ARRAY};
 INIT_AFH_ERRLISTS;
 INIT_STDERR_LOGGING(conf.loglevel_arg)
 
 INIT_AFH_ERRLISTS;
 INIT_STDERR_LOGGING(conf.loglevel_arg)
 
+static void print_info(int audio_format_num, struct afh_info *afhi)
+{
+       printf("%s: %dkbit/s\n" /* bitrate */
+               "%s: %s\n" /* format */
+               "%s: %dHz\n" /* frequency */
+               "%s: %d\n" /* channels */
+               "%s: %lu\n" /* seconds total */
+               "%s" /* tag info */
+               "%s: %lu: %lu\n" /* chunk time */
+               "%s: %lu\n", /* num chunks */
+               status_item_list[SI_BITRATE], afhi->bitrate,
+               status_item_list[SI_FORMAT], audio_format_name(audio_format_num),
+               status_item_list[SI_FREQUENCY], afhi->frequency,
+               status_item_list[SI_CHANNELS], afhi->channels,
+               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
+               afhi->info_string,
+               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec,
+                       (long unsigned)afhi->chunk_tv.tv_usec,
+               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total
+       );
+}
+
+static void print_chunk_table(struct afh_info *afhi)
+{
+       int i;
+
+       printf("chunk_table: ");
+       for (i = 0; i <= afhi->chunks_total; i++)
+               printf("%u ", afhi->chunk_table[i]);
+       printf("\n");
+}
+
+static int cat_file(void *audio_file_data, struct afh_info *afhi)
+{
+       int ret;
+       struct timeval stream_start;
+       long unsigned i, first_chunk, last_chunk;
+       char *buf;
+       size_t size;
+
+
+       if (conf.begin_chunk_arg < 0)
+               return -ERRNO_TO_PARA_ERROR(EINVAL);
+       first_chunk = conf.begin_chunk_arg;
+
+       if (conf.end_chunk_given) {
+               if (conf.end_chunk_arg < 0)
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               if (conf.end_chunk_arg >= afhi->chunks_total)
+                       return -ERRNO_TO_PARA_ERROR(EINVAL);
+               last_chunk = conf.end_chunk_arg;
+       } else
+               last_chunk = afhi->chunks_total - 1;
+       if (!afhi->chunks_total)
+               return 1;
+       afh_get_header(afhi, audio_file_data, &buf, &size);
+       if (size && first_chunk && !conf.no_header_given) {
+               PARA_INFO_LOG("writing audio file header (%zu bytes)\n", size);
+               ret = write(STDOUT_FILENO, buf, size);
+               if (ret < 0)
+                       return ret;
+               if (ret != size)
+                       return -E_AFH_SHORT_WRITE;
+       }
+       PARA_NOTICE_LOG("writing chunks %lu - %lu\n", first_chunk, last_chunk);
+       gettimeofday(&stream_start, NULL);
+       for (i = first_chunk; i <= last_chunk; i++) {
+               struct timeval now, diff, next_chunk;
+               afh_get_chunk(i, afhi, audio_file_data, &buf, &size);
+               PARA_DEBUG_LOG("chunk %lu: size %zu\n", i, size);
+               if (conf.just_in_time_given) {
+                       compute_chunk_time(i - first_chunk, &afhi->chunk_tv,
+                               &stream_start, &next_chunk);
+                       gettimeofday(&now, NULL);
+                       ret = tv_diff(&next_chunk, &now, &diff);
+                       if (ret > 0) {
+                               ret = para_select(1, NULL, NULL, &diff);
+                               if (ret < 0)
+                                       return ret;
+                       }
+               }
+               PARA_INFO_LOG("writing chunk %lu\n", i);
+               ret = write(STDOUT_FILENO, buf, size);
+               if (ret < 0)
+                       return ret;
+               if (ret != size)
+                       return -E_AFH_SHORT_WRITE;
+       }
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
 int main(int argc, char **argv)
 {
-       int i, ret;
+       int ret, audio_format_num;
        void *audio_file_data;
        size_t audio_file_size;
        struct afh_info afhi;
        void *audio_file_data;
        size_t audio_file_size;
        struct afh_info afhi;
@@ -42,32 +134,15 @@ int main(int argc, char **argv)
        ret = compute_afhi(conf.inputs[0], audio_file_data, audio_file_size, &afhi);
        if (ret < 0)
                goto out;
        ret = compute_afhi(conf.inputs[0], audio_file_data, audio_file_size, &afhi);
        if (ret < 0)
                goto out;
-       printf("%s: %dkbit/s\n" /* bitrate */
-               "%s: %s\n" /* format */
-               "%s: %dHz\n" /* frequency */
-               "%s: %d\n" /* channels */
-               "%s: %lu\n" /* seconds total */
-               "%s" /* tag info */
-               "%s: %lu: %lu\n" /* chunk time */
-               "%s: %lu\n", /* num chunks */
-               status_item_list[SI_BITRATE], afhi.bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(ret),
-               status_item_list[SI_FREQUENCY], afhi.frequency,
-               status_item_list[SI_CHANNELS], afhi.channels,
-               status_item_list[SI_SECONDS_TOTAL], afhi.seconds_total,
-               afhi.info_string,
-               status_item_list[SI_CHUNK_TIME], (long unsigned)afhi.chunk_tv.tv_sec,
-                       (long unsigned)afhi.chunk_tv.tv_usec,
-               status_item_list[SI_NUM_CHUNKS], afhi.chunks_total
-       );
-       ret = 1;
-       if (!conf.chunk_table_given)
-               goto out;
-       printf("chunk_table: ");
-       for (i = 0; i <= afhi.chunks_total; i++)
-               printf("%u ", afhi.chunk_table[i]);
-       printf("\n");
-       ret = 1;
+       audio_format_num = ret;
+       if (conf.stream_given)
+               ret = cat_file(audio_file_data, &afhi);
+       else {
+               print_info(audio_format_num, &afhi);
+               if (conf.chunk_table_given)
+                       print_chunk_table(&afhi);
+               ret = 1;
+       }
 out:
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
 out:
        if (ret < 0)
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
diff --git a/afh.ggo b/afh.ggo
index e013cc3..7fb9d5a 100644 (file)
--- a/afh.ggo
+++ b/afh.ggo
+text "
+para_afh, the audio format handler tool, is a stand-alone program
+contained in the paraslash package for analyzing and streaming audio
+files. It can be used to
+
+       - print tech info about the given audio file to stdout.
+       In particular, the 'chunk table' of the audio file, an array
+       of offsets within the audio file, may be printed. This table
+       can be used by other programs unaware of the particular audio
+       format to stream the audio file.
+
+       - write selected parts of the given audio file in complete
+       chunks without decoding. Thus para_afh can be used to 'cut'
+       an audio file.
+
+       - write selected parts of the given audio files 'just in time'
+       to sdout.  This may be useful for third-party software that
+       is capable of reading from stdin.
+"
+
 option "loglevel" l
 #~~~~~~~~~~~~~~~~~~
 "set loglevel (0-6)"
 option "loglevel" l
 #~~~~~~~~~~~~~~~~~~
 "set loglevel (0-6)"
+int typestr="level"
+default="4"
+optional
+
+defgroup "mode"
+#--------------
+groupdesc="
+       There are two modes of operation: Info mode and stream mode,
+       one of which must be selected by the corresponding option.
+       See below.
+"
+required
+
+groupoption "info" i
+#~~~~~~~~~~~~~~~~~~~
+"select info mode"
+group="mode"
+details="
+       In this mode, the program prints technical information about
+       the given audio file to stdout.
+"
 
 
-       int typestr="level"
-       default="4"
-       optional
+groupoption "stream" s
+#~~~~~~~~~~~~~~~~~~~~~
+"select stream mode"
+group="mode"
+details="
+       If this mode is selected, the selected parts of the content
+       of the audio file are written to stdout. Only complete chunks
+       with respect of the underlying audio format are written.
+       For example, only complete frames in case of mp3 files.
+"
+
+section "Options for info mode"
+#==============================
 
 option "chunk_table" c
 
 option "chunk_table" c
+#~~~~~~~~~~~~~~~~~~~~~
 "print also the chunk table"
 "print also the chunk table"
+flag off
+dependon="info"
+
+section "Options for stream mode"
+#================================
+
+
+option "begin_chunk" b
+#~~~~~~~~~~~~~~~~~~~~~
+"skip a number of chunks"
+int typestr="chunk_num"
+default="0"
+dependon="stream"
+optional
+details="
+       The chunk_num argument must be non-negative as chunks start
+       at zero.
+"
+
+option "end_chunk" e
+#~~~~~~~~~~~~~~~~~~~
+"only write up to chunk chunk_num"
+int typestr="chunk_num"
+dependon="stream"
+optional
+details="
+       The chunk_num argument must be less than the total number of
+       chunks. The default is to write up to the last chunk.
+"
+
+option "just_in_time" j
+#~~~~~~~~~~~~~~~~~~~~~~
+"use timed writes"
+flag off
+dependon="stream"
+details="
+       Write the specified chunks of data 'just in time', i.e. the
+       write of each chunk is delayed until the time it is needed
+       by the decoder/player in order to guarantee an uninterupted
+       audio stream.
+"
 
 
-       flag off
+option "no_header" H
+#~~~~~~~~~~~~~~~~~~~
+"do not write an audio file header"
+flag off
+dependon="stream"
+details="
+       If an audio format needs information about the audio file
+       in a format-specific header in order to be understood by
+       the decoding software, a suitable header is automatically
+       send. This option changes the default behaviour so that no
+       header is sent.
+"
diff --git a/error.h b/error.h
index 415262d..df2a240 100644 (file)
--- a/error.h
+++ b/error.h
@@ -32,6 +32,7 @@ extern const char **para_errlist[];
 
 #define AFH_ERRORS \
        PARA_ERROR(AFH_SYNTAX, "afh syntax error"), \
 
 #define AFH_ERRORS \
        PARA_ERROR(AFH_SYNTAX, "afh syntax error"), \
+       PARA_ERROR(AFH_SHORT_WRITE, "afh short write"), \
 
 
 #define AFH_COMMON_ERRORS \
 
 
 #define AFH_COMMON_ERRORS \