* 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
* Sophisticated audio file selector
* Small memory footprint
* Command line interface for easy scripting in high-level languages
+ * Interactive sessions offer command completion and command line history
* RSA user authentication
* Encrypted communications
* GPL licensed
0.4.9 (to be announced) "hybrid causality"
------------------------------------------
- Support for another audio format and many small improvements/fixes
- all over the place.
++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
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"
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;
}
}
PARA_EMERG_LOG("terminating on signal %d\n", signum);
shutdown:
- sched_shutdown();
+ sched_shutdown(s);
t->error = -E_AFS_SIGNAL;
}
- static void register_signal_task(void)
+ static void register_signal_task(struct sched *s)
{
struct signal_task *st = &signal_task_struct;
st->task.pre_select = signal_pre_select;
st->task.post_select = afs_signal_post_select;
sprintf(st->task.status, "signal task");
- register_task(&st->task);
+ register_task(s, &st->task);
}
static struct list_head afs_client_list;
ret = execute_server_command(&s->rfds);
if (ret < 0) {
PARA_EMERG_LOG("%s\n", para_strerror(-ret));
- sched_shutdown();
+ sched_shutdown(s);
return;
}
/* Check the list of connected clients. */
para_list_add(&client->node, &afs_client_list);
}
- static void register_command_task(uint32_t cookie)
+ static void register_command_task(uint32_t cookie, struct sched *s)
{
struct command_task *ct = &command_task_struct;
ct->fd = setup_command_socket_or_die();
ct->task.pre_select = command_pre_select;
ct->task.post_select = command_post_select;
sprintf(ct->task.status, "afs command task");
- register_task(&ct->task);
+ register_task(s, &ct->task);
}
/**
static struct sched s;
int i, ret;
- register_signal_task();
+ register_signal_task(&s);
INIT_LIST_HEAD(&afs_client_list);
for (i = 0; i < NUM_AFS_TABLES; i++)
afs_tables[i].init(&afs_tables[i]);
PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n",
server_socket, (unsigned) cookie);
init_admissible_files(conf.afs_initial_mode_arg);
- register_command_task(cookie);
+ register_command_task(cookie, &s);
s.default_timeout.tv_sec = 0;
s.default_timeout.tv_usec = 999 * 1000;
ret = schedule(&s);
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"
CPPFLAGS="$OLD_CPPFLAGS"
LDFLAGS="$OLD_LDFLAGS"
LIBS="$OLD_LIBS"
+ ############################################################# readline
+ OLD_CPPFLAGS="$CPPFLAGS"
+ OLD_LD_FLAGS="$LDFLAGS"
+ OLD_LIBS="$LIBS"
+
+ have_readline="yes"
+ AC_ARG_WITH(readline_headers, [AC_HELP_STRING(--with-readline-headers=dir,
+ [look for libreadline header files also in dir])])
+ if test -n "$with_readline_headers"; then
+ readline_cppflags="-I$with_readline_headers"
+ CPPFLAGS="$CPPFLAGS $readline_cppflags"
+ fi
+ AC_ARG_WITH(readline_libs, [AC_HELP_STRING(--with-readline-libs=dir,
+ [look for readline library also in dir])])
+ if test -n "$with_readline_libs"; then
+ readline_libs="-L$with_readline_libs"
+ LDFLAGS="$LDFLAGS $readline_libs"
+ fi
+ msg="no interactive cli support"
+ AC_CHECK_HEADERS([readline/readline.h], [
+ ], [
+ have_readline="no"
+ AC_MSG_WARN([readline/readline.h not found, $msg])
+ ])
+ if test "$have_readline" = "yes"; then
+ readline_libs="$readline_libs -lreadline"
+ AC_SEARCH_LIBS([rl_replace_line], [readline], [], [have_readline="no"])
+ if test "$have_readline" = "no"; then # try with -lcurses
+ # clear cache
+ AC_MSG_NOTICE([trying again with -lcurses])
+ unset ac_cv_search_rl_replace_line 2> /dev/null
+ AC_SEARCH_LIBS([rl_replace_line], [readline], [
+ have_readline=yes
+ readline_libs="$readline_libs -lcurses"
+ ], [], [-lcurses])
+ fi
+ if test "$have_readline" = "no"; then # try with -ltermcap
+ # clear cache
+ AC_MSG_NOTICE([trying again with -ltermcap])
+ unset ac_cv_search_rl_replace_line 2> /dev/null
+ AC_SEARCH_LIBS([rl_replace_line], [readline], [
+ have_readline=yes
+ readline_libs="$readline_libs -ltermcap"
+ ], [], [-ltermcap])
+ fi
+ fi
+
+ if test "$have_readline" = "yes"; then
+ all_errlist_objs="$all_errlist_objs interactive"
+ client_errlist_objs="$client_errlist_objs interactive"
+ client_ldflags="$client_ldflags $readline_libs"
+ audioc_errlist_objs="$audioc_errlist_objs buffer_tree interactive sched time"
+ audioc_ldflags="$audioc_ldflags $readline_libs"
+ AC_SUBST(readline_cppflags)
+ AC_DEFINE(HAVE_READLINE, 1, define to 1 to turn on readline support)
+ else
+ AC_MSG_WARN([libreadline not found or unusable])
+ fi
+ CPPFLAGS="$OLD_CPPFLAGS"
+ LDFLAGS="$OLD_LDFLAGS"
+ LIBS="$OLD_LIBS"
+ #############################################################
AC_SUBST(install_sh, [$INSTALL])
paraslash configuration:
~~~~~~~~~~~~~~~~~~~~~~~~
unix socket credentials: $have_ucred
+ readline (interactive CLIs): $have_readline
audio formats supported by para_server/para_afh: $server_audio_formats
id3 version2 support: $have_libid3tag
filters supported by para_audiod/para_filter: $filters
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?)"), \
#define AUDIOC_ERRORS \
PARA_ERROR(AUDIOC_SYNTAX, "audioc syntax error"), \
+ PARA_ERROR(AUDIOC_EOF, "audioc: end of file"), \
#define CLIENT_COMMON_ERRORS \
PARA_ERROR(QUEUE, "packet queue overrun"), \
+ #define INTERACTIVE_ERRORS \
+ PARA_ERROR(I9E_EOF, "end of input"), \
+ PARA_ERROR(I9E_SETUPTERM, "failed to set up terminal"), \
+
/** \endcond errors */
/**
return ret;
}
+ /**
+ * Get the number of the word the cursor is on.
+ *
+ * \param buf The zero-terminated line buffer.
+ * \param delim Characters that separate words.
+ * \param point The cursor position.
+ *
+ * \return Zero-based word number.
+ */
+ int compute_word_num(const char *buf, const char *delim, int point)
+ {
+ int ret, num_words;
+ const char *p;
+ char *word;
+
+ for (p = buf, num_words = 0; ; p += ret, num_words++) {
+ ret = get_next_word(p, delim, &word);
+ if (ret <= 0)
+ break;
+ free(word);
+ if (p + ret >= buf + point)
+ break;
+ }
+ return num_words;
+ }
+
/**
* Free an array of words created by create_argv().
*
free(buf);
return -E_REGEX;
}
+
+/**
+ * strdup() for not necessarily zero-terminated strings.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes to be copied.
+ *
+ * \return A 0-terminated buffer of length \a len + 1.
+ *
+ * This is similar to strndup(), which is a GNU extension. However, one
+ * difference is that strndup() returns \p NULL if insufficient memory was
+ * available while this function aborts in this case.
+ *
+ * \sa strdup(), \ref para_strdup().
+ */
+char *safe_strdup(const char *src, size_t len)
+{
+ char *p;
+
+ assert(len < (size_t)-1);
+ p = para_malloc(len + 1);
+ if (len > 0)
+ memcpy(p, src, len);
+ p[len] = '\0';
+ return p;
+}
+
+/**
+ * Copy the value of a key=value pair.
+ *
+ * This checks whether the given buffer starts with "key=", ignoring case. If
+ * yes, a copy of the value is returned. The source buffer may not be
+ * zero-terminated.
+ *
+ * \param src The source buffer.
+ * \param len The number of bytes of the tag.
+ * \param key Only copy if it is the value of this key.
+ *
+ * \return A zero-terminated buffer, or \p NULL if the key was
+ * not of the given type.
+ */
+char *key_value_copy(const char *src, size_t len, const char *key)
+{
+ int keylen = strlen(key);
+
+ if (len <= keylen)
+ return NULL;
+ if (strncasecmp(src, key, keylen))
+ return NULL;
+ if (src[keylen] != '=')
+ return NULL;
+ return safe_strdup(src + keylen + 1, len - keylen - 1);
+}
void free_argv(char **argv);
int para_regcomp(regex_t *preg, const char *regex, int cflags);
void freep(void *arg);
+ 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);
with a symmetric RC4 session key. For each user of paraslash you must
create a public/secret RSA key pair for authentication.
+ If para_client is started without non-option arguments, an interactive
+ session (shell) is started. Command history and command completion are
+ supported through libreadline.
*para_audiod*
The client program which talks to para_audiod. Used to control
para_audiod, to receive status info, or to grab the stream at any
- point of the decoding process.
+ point of the decoding process. Like para_client, para_audioc supports
+ interactive sessions on systems with libreadline.
*para_recv*
- 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.
libao). Needed to build the ao writer (ESD, PulseAudio,...).
Debian package: libao-dev.
+ - XREFERENCE(http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html,
+ GNU Readline). If this library (libreadline-dev) is installed,
+ para_client and para_audioc support interactive sessions.
+
Installation
~~~~~~~~~~~~
is composed of superframes, each containing one or more frames of
2048 samples. For 16 bit stereo a WMA superframe is about 8K large.
+*FLAC*
+
+The Free Lossless Audio Codec (FLAC) compresses audio without quality
+loss. It gives better compression ratios than a general purpose
+compressor like zip or bzip2 because FLAC is designed specifically
+for audio. A FLAC-encoded file consits of frames of varying size, up
+to 16K. Each frame starts with a header that contains all information
+necessary to decode the frame.
+
Meta data
~~~~~~~~~
32 characters long. ID3, version 2 is much more flexible but requires
a separate library being installed for paraslash to support it.
-Ogg vorbis files contain meta data as Vorbis comments, which are
-typically implemented as strings of the form "[TAG]=[VALUE]". Unlike
-ID3 version 1 tags, one may use whichever tags are appropriate for
-the content.
+Ogg vorbis, ogg speex and flac files contain meta data as Vorbis
+comments, which are typically implemented as strings of the form
+"[TAG]=[VALUE]". Unlike ID3 version 1 tags, one may use whichever
+tags are appropriate for the content.
AAC files usually use the MPEG-4 container format for storing meta
data while WMA files wrap meta data as special objects within the
of an audio file. For MP3 files, a chunk is the same as an MP3 frame,
while for OGG files a chunk is an OGG page, etc. Therefore the chunk
size varies considerably between audio formats, from a few hundred
-bytes (MP3) up to 8K (WMA).
+bytes (MP3) up to 16K (FLAC).
The chunk table contains the offsets within the audio file that
correspond to the chunk boundaries of the file. Like the meta data,