From: Andre Noll Date: Fri, 9 Jan 2009 00:16:44 +0000 (+0100) Subject: Merge commit 'meins/master' X-Git-Tag: v0.3.4~75^2~35 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=40de1dd2fdbb054444d585aa70e2d50166a66e07;hp=2ca70d00ec9b4486bca372873041c9e0233d36c4 Merge commit 'meins/master' --- diff --git a/INSTALL b/INSTALL index 3c059058..9cc36f41 100644 --- a/INSTALL +++ b/INSTALL @@ -92,6 +92,14 @@ Finally, tell para_client to connect to server_host: Start para_server ----------------- +Before starting the server make sure you have write permissions to +the directory /var/paraslash. + + sudo chown $user /var/paraslash + +Alternatively, use the --afs_socket Option to specify a different +location for the afs command socket. + For this first try, we'll use a debug level of two to make the output of para_server more verbose. diff --git a/Makefile.in b/Makefile.in index 506b2af0..4e6ef962 100644 --- a/Makefile.in +++ b/Makefile.in @@ -12,7 +12,7 @@ build_date := $(shell date) uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS") uname_rs := $(shell uname -rs) cc_version := $(shell $(CC) --version | head -n 1) -codename := axiomatic perspectivity +codename := elliptic inheritance DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W DEBUG_CPPFLAGS += -Wredundant-decls @@ -23,6 +23,7 @@ DEBUG_CPPFLAGS += -Wredundant-decls # invalid option for gcc-3.3.3 # DEBUG_CPPFLAGS += -Wextra # DEBUG_CPPFLAGS += -Wold-style-definition +# DEBUG_CPPFLAGS += -Wdeclaration-after-statement # many warnings about trivial stuff # CPPFLAGS += -Wconversion @@ -45,6 +46,7 @@ CPPFLAGS += -DCODENAME='"$(codename)"' CPPFLAGS += -DCC_VERSION='"$(cc_version)"' CPPFLAGS += -Werror-implicit-function-declaration CPPFLAGS += -Wmissing-format-attribute +CPPFLAGS += -Wmissing-noreturn CPPFLAGS += -Wunused-macros CPPFLAGS += -Wbad-function-cast CPPFLAGS += -DMAIN_INPUT_FILE_IS_$(*F) @@ -114,6 +116,10 @@ grab_client.cmdline.h grab_client.cmdline.c: grab_client.ggo audioc.ggo) O="--unamed-opts=command";; \ fsck.ggo) O="--unamed-opts=table";; \ afh.ggo) O="--unamed-opts=audio_file";; \ + recv.ggo) O="--no-handle-help";; \ + filter.ggo) O="--no-handle-help";; \ + write.ggo) O="--no-handle-help";; \ + audiod.ggo) O="--no-handle-help";; \ esac; \ if test $< != fsck.ggo; then O="$$O --conf-parser "; fi; \ gengetopt $$O \ @@ -141,6 +147,18 @@ man/man1/para_audiod.1: para_audiod audiod_command_list.man mkdir -p man/man1 help2man -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@ +man/man1/para_filter.1: para_filter + mkdir -p man/man1 + help2man -h --detailed-help -N ./$< > $@ + +man/man1/para_write.1: para_write + mkdir -p man/man1 + help2man -h --detailed-help -N ./$< > $@ + +man/man1/para_recv.1: para_recv + mkdir -p man/man1 + help2man -h --detailed-help -N ./$< > $@ + man/man1/%.1: % mkdir -p man/man1 help2man -N ./$< > $@ @@ -159,15 +177,15 @@ ortp_recv.o: ortp_recv.c ortp_send.o: ortp_send.c $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ortp_cppflags@ $< -oggdec.o: oggdec.c +oggdec_filter.o: oggdec_filter.c $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $< ogg_afh.o: ogg_afh.c $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $< -mp3dec.o: mp3dec.c +mp3dec_filter.o: mp3dec_filter.c $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @mad_cppflags@ $< -aacdec.o: aacdec.c +aacdec_filter.o: aacdec_filter.c $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< aac_common.o: aac_common.c @@ -238,7 +256,7 @@ install: all man @PACKAGE_TARNAME@-@PACKAGE_VERSION@.tar.bz2: $(tarball_add) rm -rf $(tarball_pfx).tar.bz2 $(tarball_pfx) - git-archive --format=tar --prefix=$(tarball_pfx)/ HEAD \ + git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \ | tar --delete $(tarball_delete) > $(tarball_pfx).tar mkdir $(tarball_pfx) cp -r $(tarball_add) $(tarball_pfx) diff --git a/NEWS b/NEWS index 684485cf..8418cd8a 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,18 @@ NEWS ==== -------------------------------------------------- -0.3.3 (to be announced) "axiomatic perspectivity" -------------------------------------------------- +---------------------------------------------- +0.3.4 (to be announced) "elliptic inheritance" +---------------------------------------------- + - new options for mp3dec: --ignore-crc, --bufsize + - Improved help/man pages: The documentation of para_audiod, + para_recv, para_filter and para_write now also contains + all options of the available receivers/filters/writers. + - new audiod option: --config-file. + +-------------------------------------------- +0.3.3 (2008-12-01) "axiomatic perspectivity" +-------------------------------------------- Internal code cleanups, bug fixes, improved tag handling and the new amplification filter. @@ -12,11 +21,14 @@ amplification filter. - overhaul of the virtual streaming system. - mp3: id3 version 2 support via libid3tag (optional) - ogg: vorbis comment support. + - aac meta info support. - mp3 audio format handler cleanups. - new filter: "amp" to amplify the amplitude of the audio stream - new status item/database entry: amplification. It is used by the amp filter to pre-amplify the audio stream. - fix a close-without-open bug in para_write. + - fix a bug in com_init() which was introduced in 0.3.2. + - better error diagnostics for para_client. ----------------------------------------- 0.3.2 (2008-04-11) "probabilistic parity" diff --git a/README.afs b/README.afs index d77f6bfa..9e588a6a 100644 --- a/README.afs +++ b/README.afs @@ -1,7 +1,7 @@ The audio file selector ======================= -Paraslash comes with a sophisticated audio file selector called afs. +Paraslash comes with a sophisticated audio file selector called *afs*. In the << installation notes, @@ -31,12 +31,12 @@ an audio file by executing para_client setatt test+ /path/to/the/audio/file -Similarly, the "test" bit can be removed from a audio file with +Similarly, the "test" bit can be removed from an audio file with para_client setatt test- /path/to/the/audio/file -Instead of a path you can also use a pattern, and the attribute is -applied to all audio files matching that pattern: +Instead of a path you may use a shell wildcard pattern. The attribute +is applied to all audio files matching that pattern: para_client setatt test+ '/test/directory/*' @@ -53,9 +53,9 @@ you find this annoying, just say alias para='para_client --' -and be happy. In the remainder part this alias is being used. +and be happy. In what follows we shall use this alias. -Drop the test attribute entirely from the database with +The "test" attribute can be dropped from the database with para rmatt test @@ -74,13 +74,14 @@ Abstract mood nonsense [skip this part if you don't like formal definitions] -A mood consists of a unique name and its *mood definition*, which is a set of -*mood lines* containing expressions in terms of attributes and other data -contained in the database. +A mood consists of a unique name and its *mood definition*, which is +a set of *mood lines* containing expressions in terms of attributes +and other data contained in the database. -A mood defines a subset of audio files called the *admissible audio files* -for that mood. A mood can be *active* which means that para_server -is going to select only files from that subset of admissible files. +A mood defines a subset of audio files called the *admissible audio +files* for that mood. At any time, at most one mood can be *active* +which means that para_server is going to select only files from that +subset of admissible files. So in order to create a mood definition one has to write a set of mood lines. Mood lines come in three flavours: Accept lines, deny @@ -99,7 +100,7 @@ a random score to all matching files. The score value changes the order in which admissible files are going to be selected, but is of minor importance for this introduction. -So we concentrate on the first two forms, that is accept and deny +So we concentrate on the first two forms, i.e. accept and deny lines. As usual, everything in square brackets is optional, i.e. accept/deny lines take the following form when ignoring scores: @@ -121,12 +122,12 @@ or The set of admissible files for the whole mood is now defined as those files which match at least one accept mood line, but no deny mood line. -More formally, a file is admissible if and only if +More formally, an audio file F is admissible if and only if (F ~ AL1 or F ~ AL2...) and not (F ~ DL1 or F ~ DN2 ...) -where F is the file, AL1, AL2... are the accept lines, DL1, DL2... are -the deny lines and "~" means "matches". +where AL1, AL2... are the accept lines, DL1, DL2... are the deny +lines and "~" means "matches". The cases where no mood lines of accept/deny type are defined need special treatment: @@ -192,7 +193,7 @@ the catmood command to get it back: A mood can be activated by executing - para chmood my_mood + para select m/my_mood Once active, the list of admissible files is shown by the ls command if the "-a" switch is given: @@ -226,14 +227,14 @@ Troubles? Use loglevel one (option -l 1 for most commands) to show debugging info. Almost all paraslash executables have a brief online help which -is displayed by using the -h switch. +is displayed by using the -h switch. The --detailed-help option prints +the full help text. para_fsck tries to fix your database. Use --force (even if your name -isn't Luke), to clean up after a crash. However, first make sure +isn't Luke) to clean up after a crash. However, first make sure para_server isn't running before executing para_fsck if para_fsck complains about busy (dirty) tables. para_fsck also contains an option -to dump the contents of your the contents of the database to the file -system. +to dump the contents of the database to the file system. If you don't mind to recreate your database you can start from scratch by removing the entire database directory, i.e. diff --git a/REQUIREMENTS b/REQUIREMENTS index a83c4f39..b03c4c3f 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -19,17 +19,19 @@ In any case you need Optional features: - *mp3*: The mp3 decoder of para_filter is based on libmad: - http://www.underbit.com/products/mad/. If you prefer to use - the libmad package provided by your distributor, make sure - to install the corresponding development package as well. - - For version 2 id3 tag support, you'll need libid3tag which - is also available through the above link. Without libid3tag, - only version 1 tags are recognized. - + http://www.underbit.com/products/mad/. If you prefer to + use the libmad package provided by your distributor, make + sure to install the corresponding development package as + well. It is called libmad0-dev on debian-based systems. Note that libmad is not necessary for the server side, i.e. for sending mp3 files. + - *id3 tags*: + For version-2 id3 tag support, you'll need libid3tag which + is also available through the above link (alternatively: + install package libid3tag0-dev). Without libid3tag, only + version one tags are recognized. + - *ogg vorbis*: For ogg vorbis streams you'll need libogg, libvorbis, libvorbisfile: http://www.xiph.org/downloads/. The corresponding Debian packages are called libogg-dev @@ -37,7 +39,8 @@ Optional features: - *aac*: For aac files (m4a) you'll need libfaad. Get it at - http://www.audiocoding.com/modules/wiki/?page=AAC + http://www.audiocoding.com/. + Debian package: libfaad-dev. - *ortp*: If you intend to use the optional ortp streamer, you'll diff --git a/aac_afh.c b/aac_afh.c index a4166d1a..80b50a4d 100644 --- a/aac_afh.c +++ b/aac_afh.c @@ -42,6 +42,135 @@ static int aac_find_stsz(unsigned char *buf, size_t buflen, off_t *skip) return -E_STSZ; } +static int atom_cmp(unsigned char *buf1, char *buf2) +{ + unsigned char *b2 = (unsigned char *)buf2; + + if (buf1[0] != b2[0]) + return 1; + if (buf1[1] != b2[1]) + return 1; + if (buf1[2] != b2[2]) + return 1; + if (buf1[3] != b2[3]) + return 1; + return 0; +} + +static int read_atom_header(unsigned char *buf, uint64_t *subsize, unsigned char type[5]) +{ + int i; + uint64_t size = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; + + memcpy(type, buf + 4, 4); + type[4] = '\0'; + + PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type); + if (size != 1) { + *subsize = size; + return 8; + } + buf += 4; + size = 0; + for (i = 0; i < 8; i++) + size |= ((uint64_t)buf[i]) << ((7 - i) * 8); + *subsize = size; + return 16; +} + +static char *get_tag(unsigned char *p, int size) +{ + char *buf; + + assert(size > 0); + buf = para_malloc(size + 1); + + memcpy(buf, p, size); + buf[size] = '\0'; + PARA_DEBUG_LOG("size: %d: %s\n", size, buf); + return buf; +} + +static char *read_tags(unsigned char *buf, size_t buflen) +{ + unsigned char *p = buf; + char *title = NULL, *artist = NULL, *album = NULL, *year = NULL, + *comment = NULL, *result; + + while (p + 32 < buf + buflen) { + unsigned char *q, type1[5], type2[5]; + uint64_t size1, size2; + int ret, ret2; + + ret = read_atom_header(p, &size1, type1); + ret2 = read_atom_header(p + ret, &size2, type2); + + if (size2 <= 16 || atom_cmp(type2, "data")) { + p += size1; + continue; + } + size2 -= 16; + q = p + ret + ret2 + 8; + if (q + size2 > buf + buflen) + break; + if (!atom_cmp(type1, "©ART")) + artist = get_tag(q, size2); + else if (!atom_cmp(type1, "©alb")) + album = get_tag(q, size2); + else if (!atom_cmp(type1, "©nam")) + title = get_tag(q, size2); + else if (!atom_cmp(type1, "©cmt")) + comment = get_tag(q, size2); + else if (!atom_cmp(type1, "©day")) + year = get_tag(q, size2); + p += size1; + } + result = make_taginfo(title, artist, album, year, comment); + free(title); + free(artist); + free(album); + free(year); + free(comment); + return result; +} + +static char *read_meta(unsigned char *buf, size_t buflen) +{ + unsigned char *p = buf; + + while (p + 4 < buf + buflen) { + + if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') { + p++; + continue; + } + p += 4; + return read_tags(p, buflen - (p - buf)); + } + return make_taginfo(NULL, NULL, NULL, NULL, NULL); +} + +static char *aac_get_taginfo(unsigned char *buf, size_t buflen) +{ + int i; + uint64_t subsize; + unsigned char type[5]; + + for (i = 0; i + 24 < buflen; i++) { + unsigned char *p = buf + i; + if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a') + continue; + PARA_INFO_LOG("found metadata at offset %d\n", i); + i += 8; + p = buf + i; + i += read_atom_header(p, &subsize, type); + p = buf + i; + return read_meta(p, buflen - i); + } + PARA_INFO_LOG("no meta data\n"); + return make_taginfo(NULL, NULL, NULL, NULL, NULL); +} + static ssize_t aac_compute_chunk_table(struct afh_info *afhi, unsigned char *map, size_t numbytes) { @@ -103,10 +232,12 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, mp4AudioSpecificConfig mp4ASC; NeAACDecHandle handle = NULL; unsigned char *umap = (unsigned char *) map; + char *taginfo; ret = aac_find_esds(umap, numbytes, &skip, &decoder_len); if (ret < 0) goto out; + taginfo = aac_get_taginfo(umap, numbytes); handle = aac_open(); ret = -E_AAC_AFH_INIT; if (NeAACDecInit(handle, umap + skip, decoder_len, &rate, &channels)) @@ -137,11 +268,10 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */ ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */ afhi->bitrate = ret / (channels * afhi->seconds_total * 1000); - afhi->info_string = make_message("%s:\n%s:\n%s:\n", + afhi->info_string = make_message("%s:\n%s", status_item_list[SI_AUDIO_FILE_INFO], - status_item_list[SI_TAGINFO1], - status_item_list[SI_TAGINFO2] - ); + taginfo); + free(taginfo); tv_scale(20, &afhi->chunk_tv, &afhi->eof_tv); ret = 1; out: diff --git a/aacdec.c b/aacdec.c deleted file mode 100644 index 235b292d..00000000 --- a/aacdec.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2006-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ -/* - * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker, - * Ahead Software AG - */ - -/** \file aacdec.c paraslash's aac (m4a) decoder */ - -#include "para.h" - -#include "list.h" -#include "sched.h" -#include "filter.h" -#include "error.h" -#include "string.h" -#include "aac.h" - -/** the output buffer size */ -#define AAC_OUTBUF_SIZE (32 * 1024) - -/** give up decoding after that many errors */ -#define MAX_ERRORS 20 - -/** - * data specific to the aacdec filter - * - * \sa filter, filter_node - */ -struct private_aacdec_data { - /** the return value of aac_open */ - NeAACDecHandle handle; - /** info about the currently decoded frame */ - NeAACDecFrameInfo frame_info; - /** whether this instance of the aac decoder is already initialized */ - int initialized; - /** - * return value of aac_find_esds(). Used to call the right aacdec - * init function - */ - unsigned long decoder_length; - /** number of times the decoder returned an error */ - unsigned error_count; - /** number of bytes already consumed from the imput stream */ - size_t consumed_total; - /** return value of aac_find_entry_point */ - size_t entry; -}; - -static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn) -{ - struct private_aacdec_data *padd = fn->private_data; - struct filter_chain *fc = fn->fc; - int i, ret; - unsigned char *p, *outbuffer; - unsigned char *inbuf = (unsigned char*)input_buffer; - size_t skip, consumed = 0; - - if (fn->loaded > fn->bufsize * 3 / 5) - return 0; - if (len < 2048 && !*fc->input_error) - return 0; - - if (!padd->initialized) { - unsigned long rate = 0; - unsigned char channels = 0; - ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length); - if (ret < 0) { - PARA_INFO_LOG("%s\n", para_strerror(-ret)); - ret = NeAACDecInit(padd->handle, inbuf, - len, &rate, &channels); - PARA_INFO_LOG("decoder init: %d\n", ret); - if (ret < 0) { - ret = -E_AACDEC_INIT; - goto out; - } - consumed = ret; - } else { - PARA_INFO_LOG("decoder len: %lu\n", - padd->decoder_length); - consumed += skip; - p = inbuf + consumed; - ret = -E_AACDEC_INIT; - if (NeAACDecInit2(padd->handle, p, - padd->decoder_length, &rate, - &channels) < 0) - goto out; - } - fc->samplerate = rate; - fc->channels = channels; - PARA_INFO_LOG("rate: %u, channels: %d\n", - fc->samplerate, fc->channels); - padd->initialized = 1; - } - if (padd->decoder_length > 0) { - consumed = 0; - if (!padd->entry) { - ret = aac_find_entry_point(inbuf + consumed, - len - consumed, &skip); - if (ret < 0) { - ret = len; - goto out; - } - consumed += skip; - padd->entry = ret; - PARA_INFO_LOG("entry: %zu\n", padd->entry); - } - ret = len; - if (padd->consumed_total + len < padd->entry) - goto out; - if (padd->consumed_total < padd->entry) - consumed = padd->entry - padd->consumed_total; - } - for (; consumed < len; consumed++) - if ((inbuf[consumed] & 0xfe) == 0x20) - break; - if (consumed >= len) - goto success; - p = inbuf + consumed; - outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info, p, - len - consumed); - if (padd->frame_info.error) { - ret = -E_AAC_DECODE; - if (padd->error_count++ > MAX_ERRORS) - goto out; - PARA_ERROR_LOG("frame_error: %d, consumed: %zu + %zd + %lu\n", - padd->frame_info.error, padd->consumed_total, - consumed, padd->frame_info.bytesconsumed); - PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage( - padd->frame_info.error)); - consumed++; /* catch 21 */ - goto success; - } - padd->error_count = 0; - consumed += padd->frame_info.bytesconsumed; - ret = consumed; - if (!padd->frame_info.samples) - goto out; - ret = -E_AAC_OVERRUN; - if (padd->frame_info.samples * 2 + fn->loaded > fn->bufsize) - goto out; - for (i = 0; i < padd->frame_info.samples; i++) { - short *s = (short *)outbuffer; - write_int16_host_endian(fn->buf + fn->loaded, s[i]); - fn->loaded += 2; - } -success: - ret = consumed; -out: - if (ret > 0) - padd->consumed_total += ret; - return ret; -} - -static void aacdec_open(struct filter_node *fn) -{ - fn->private_data = para_calloc(sizeof(struct private_aacdec_data)); - struct private_aacdec_data *padd = fn->private_data; - - fn->bufsize = AAC_OUTBUF_SIZE; - fn->buf = para_calloc(fn->bufsize); - padd->handle = aac_open(); -} - -static void aacdec_close(struct filter_node *fn) -{ - struct private_aacdec_data *padd = fn->private_data; - - NeAACDecClose(padd->handle); - free(fn->buf); - fn->buf = NULL; - free(padd); - fn->private_data = NULL; -} - -/** - * the init function of the aacdec filter - * - * \param f pointer to the filter struct to initialize - * - * \sa filter::init - */ -void aacdec_init(struct filter *f) -{ - f->open = aacdec_open; - f->convert = aacdec; - f->close = aacdec_close; -} diff --git a/aacdec_filter.c b/aacdec_filter.c new file mode 100644 index 00000000..8eddc0e7 --- /dev/null +++ b/aacdec_filter.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2006-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ +/* + * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker, + * Ahead Software AG + */ + +/** \file aacdec_filter.c paraslash's aac (m4a) decoder. */ + +#include "para.h" + +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "filter.h" +#include "error.h" +#include "string.h" +#include "aac.h" + +/** the output buffer size */ +#define AAC_OUTBUF_SIZE (32 * 1024) + +/** give up decoding after that many errors */ +#define MAX_ERRORS 20 + +/** + * data specific to the aacdec filter + * + * \sa filter, filter_node + */ +struct private_aacdec_data { + /** the return value of aac_open */ + NeAACDecHandle handle; + /** info about the currently decoded frame */ + NeAACDecFrameInfo frame_info; + /** whether this instance of the aac decoder is already initialized */ + int initialized; + /** + * return value of aac_find_esds(). Used to call the right aacdec + * init function + */ + unsigned long decoder_length; + /** number of times the decoder returned an error */ + unsigned error_count; + /** number of bytes already consumed from the imput stream */ + size_t consumed_total; + /** return value of aac_find_entry_point */ + size_t entry; +}; + +static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn) +{ + struct private_aacdec_data *padd = fn->private_data; + struct filter_chain *fc = fn->fc; + int i, ret; + unsigned char *p, *outbuffer; + unsigned char *inbuf = (unsigned char*)input_buffer; + size_t skip, consumed = 0; + + if (fn->loaded > fn->bufsize * 3 / 5) + return 0; + if (len < 2048 && !*fc->input_error) + return 0; + + if (!padd->initialized) { + unsigned long rate = 0; + unsigned char channels = 0; + ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length); + if (ret < 0) { + PARA_INFO_LOG("%s\n", para_strerror(-ret)); + ret = NeAACDecInit(padd->handle, inbuf, + len, &rate, &channels); + PARA_INFO_LOG("decoder init: %d\n", ret); + if (ret < 0) { + ret = -E_AACDEC_INIT; + goto out; + } + consumed = ret; + } else { + PARA_INFO_LOG("decoder len: %lu\n", + padd->decoder_length); + consumed += skip; + p = inbuf + consumed; + ret = -E_AACDEC_INIT; + if (NeAACDecInit2(padd->handle, p, + padd->decoder_length, &rate, + &channels) < 0) + goto out; + } + fc->samplerate = rate; + fc->channels = channels; + PARA_INFO_LOG("rate: %u, channels: %d\n", + fc->samplerate, fc->channels); + padd->initialized = 1; + } + if (padd->decoder_length > 0) { + consumed = 0; + if (!padd->entry) { + ret = aac_find_entry_point(inbuf + consumed, + len - consumed, &skip); + if (ret < 0) { + ret = len; + goto out; + } + consumed += skip; + padd->entry = ret; + PARA_INFO_LOG("entry: %zu\n", padd->entry); + } + ret = len; + if (padd->consumed_total + len < padd->entry) + goto out; + if (padd->consumed_total < padd->entry) + consumed = padd->entry - padd->consumed_total; + } + for (; consumed < len; consumed++) + if ((inbuf[consumed] & 0xfe) == 0x20) + break; + if (consumed >= len) + goto success; + p = inbuf + consumed; + outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info, p, + len - consumed); + if (padd->frame_info.error) { + ret = -E_AAC_DECODE; + if (padd->error_count++ > MAX_ERRORS) + goto out; + PARA_ERROR_LOG("frame_error: %d, consumed: %zu + %zd + %lu\n", + padd->frame_info.error, padd->consumed_total, + consumed, padd->frame_info.bytesconsumed); + PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage( + padd->frame_info.error)); + consumed++; /* catch 21 */ + goto success; + } + padd->error_count = 0; + consumed += padd->frame_info.bytesconsumed; + ret = consumed; + if (!padd->frame_info.samples) + goto out; + ret = -E_AAC_OVERRUN; + if (padd->frame_info.samples * 2 + fn->loaded > fn->bufsize) + goto out; + for (i = 0; i < padd->frame_info.samples; i++) { + short *s = (short *)outbuffer; + write_int16_host_endian(fn->buf + fn->loaded, s[i]); + fn->loaded += 2; + } +success: + ret = consumed; +out: + if (ret > 0) + padd->consumed_total += ret; + return ret; +} + +static void aacdec_open(struct filter_node *fn) +{ + struct private_aacdec_data *padd = para_calloc(sizeof(*padd)); + + fn->private_data = padd; + fn->bufsize = AAC_OUTBUF_SIZE; + fn->buf = para_calloc(fn->bufsize); + padd->handle = aac_open(); +} + +static void aacdec_close(struct filter_node *fn) +{ + struct private_aacdec_data *padd = fn->private_data; + + NeAACDecClose(padd->handle); + free(fn->buf); + fn->buf = NULL; + free(padd); + fn->private_data = NULL; +} + +/** + * the init function of the aacdec filter + * + * \param f pointer to the filter struct to initialize + * + * \sa filter::init + */ +void aacdec_filter_init(struct filter *f) +{ + f->open = aacdec_open; + f->convert = aacdec; + f->close = aacdec_close; +} diff --git a/afh.c b/afh.c index 454517f4..24c2bc67 100644 --- a/afh.c +++ b/afh.c @@ -112,6 +112,8 @@ static int cat_file(void *audio_file_data, struct afh_info *afhi) return ret; } } + if (!size) + continue; PARA_INFO_LOG("writing chunk %lu\n", i); ret = write_all(STDOUT_FILENO, buf, &size); if (ret < 0) @@ -120,9 +122,17 @@ static int cat_file(void *audio_file_data, struct afh_info *afhi) return 1; } +/** + * The main function of para_afh. + * + * \param argc Usual argument count. + * \param argv Usual argument vector. + * + * \return \p EXIT_FAILURE or \p EXIT_SUCCESS. + */ int main(int argc, char **argv) { - int ret, audio_format_num, fd; + int i, ret, audio_format_num, fd; void *audio_file_data; size_t audio_file_size; struct afh_info afhi; @@ -130,25 +140,36 @@ int main(int argc, char **argv) afh_cmdline_parser(argc, argv, &conf); HANDLE_VERSION_FLAG("afh", conf); ret = -E_AFH_SYNTAX; - if (conf.inputs_num != 1) - goto out; - afh_init(); - ret = mmap_full_file(conf.inputs[0], O_RDONLY, &audio_file_data, - &audio_file_size, &fd); - if (ret < 0) + if (conf.inputs_num == 0) goto out; - ret = compute_afhi(conf.inputs[0], audio_file_data, audio_file_size, - fd, &afhi); - if (ret < 0) + if (conf.stream_given && conf.inputs_num != 1) goto out; - 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; + afh_init(); + for (i = 0; i < conf.inputs_num; i++) { + int ret2; + ret = mmap_full_file(conf.inputs[i], O_RDONLY, &audio_file_data, + &audio_file_size, &fd); + if (ret < 0) + goto out; + ret = compute_afhi(conf.inputs[i], audio_file_data, audio_file_size, + fd, &afhi); + if (ret < 0) + goto out; + audio_format_num = ret; + if (conf.stream_given) + ret = cat_file(audio_file_data, &afhi); + else { + printf("File %d: %s\n", i + 1, conf.inputs[i]); + print_info(audio_format_num, &afhi); + if (conf.chunk_table_given) + print_chunk_table(&afhi); + printf("\n"); + } + ret2 = para_munmap(audio_file_data, audio_file_size); + if (ret2 < 0 && ret >= 0) + ret = ret2; + if (ret < 0) + break; } out: if (ret < 0) diff --git a/afh_common.c b/afh_common.c index 07e6a78c..bec99bf4 100644 --- a/afh_common.c +++ b/afh_common.c @@ -91,15 +91,14 @@ void afh_init(void) } } - /** * Guess the audio format judging from filename. * * \param name The filename. * - * \return This function returns -1 if it has no idea what kind of audio - * file this might be. Otherwise the (non-negative) number of the audio format - * is returned. + * \return This function returns \p -E_AUDIO_FORMAT if it has no idea what kind + * of audio file this might be. Otherwise the (non-negative) number of the + * audio format is returned. */ int guess_audio_format(const char *name) { @@ -119,14 +118,32 @@ int guess_audio_format(const char *name) return i; } } - return -1; + return -E_AUDIO_FORMAT; } +/** + * Pretty-print the given meta-info. + * + * \param title The title of the audio file. + * \param artist The artist. + * \param album The name of the album. + * \param year Year of release. + * \param comment Further comments. + * + * This function is called by each audio format handler to produce the tag info + * status items. Usually, the audio format handlers read this info from the + * audio file (id3 tags, vorbis comments, ...). + * + * It is OK to pass \p NULL pointers for any argument in which case a suitable + * string is inserted which indicates that this information is not available. + * + * \return The status item string. It must be freed by the caller. + */ char *make_taginfo(char *title, char *artist, char *album, char *year, char *comment) { return make_message("%s: %s, by %s\n" /* taginfo1 */ - "%s: A: %s, Y: %s, C: %s\n", /* taginfo 2*/ + "%s: A: %s, Y: %s, C: %s\n", /* taginfo2 */ status_item_list[SI_TAGINFO1], (title && *title)? title : "(title tag not set)", (artist && *artist)? artist : "(artist tag not set)", @@ -196,7 +213,18 @@ const char *audio_format_name(int i) return i >= 0? afl[i].name : "(none)"; } - +/** + * Get one chunk of audio data. + * + * \param chunk_num The number of the chunk to get. + * \param afhi Describes the audio file. + * \param map The memory mapped audio file. + * \param buf Result pointer. + * \param len The length of the chunk in bytes. + * + * Upon return, \a buf will point so memory inside \a map. The returned buffer + * must therefore not be freed by the caller. + */ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, void *map, const char **buf, size_t *len) { diff --git a/afs.c b/afs.c index 64d3b0f8..5f47941e 100644 --- a/afs.c +++ b/afs.c @@ -892,7 +892,7 @@ static int call_callback(int fd, int query_shmid) query.data = (char *)query_shm + sizeof(*cq); query.size = cq->query_size; cq->handler(fd, &query); - return 1; + return shm_detach(query_shm); } static int execute_server_command(void) @@ -1105,7 +1105,7 @@ int com_init(int fd, int argc, char * const * const argv) return -E_BAD_TABLE_NAME; } } - ret = send_callback_request(create_tables_callback, &query, NULL, NULL); + ret = send_callback_request(create_tables_callback, &query, &send_result, &fd); if (ret < 0) return send_va_buffer(fd, "%s\n", para_strerror(-ret)); return ret; diff --git a/aft.c b/aft.c index 1b23823e..d5a35556 100644 --- a/aft.c +++ b/aft.c @@ -20,6 +20,7 @@ #include "vss.h" #include "fd.h" #include "ipc.h" +#include "portable_io.h" static struct osl_table *audio_file_table; @@ -588,8 +589,6 @@ int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi) static int save_afd(struct audio_file_data *afd) { size_t size = sizeof(*afd) + sizeof_chunk_table(&afd->afhi); - - PARA_DEBUG_LOG("size: %zu\n", size); int shmid, ret = shm_new(size); void *shm_afd; char *buf; @@ -1261,8 +1260,8 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) GET_NUM_DIGITS(d->afsi.num_played, &num_digits); w->num_played_width = PARA_MAX(w->num_played_width, num_digits); /* get the number of chars to print this amount of time */ - tmp = get_duration_width(d->afhi.seconds_total); - w->duration_width = PARA_MAX(w->duration_width, tmp); + num_digits = get_duration_width(d->afhi.seconds_total); + 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); if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { @@ -1599,6 +1598,7 @@ static void com_add_callback(int fd, const struct osl_object *query) struct afs_info default_afsi = {.last_played = 0}; struct para_buffer msg = {.max_size = SHMMAX, .max_size_handler = pass_buffer_as_shm, .private_data = &fd}; + uint16_t afhi_offset, chunks_offset; hash = (HASH_TYPE *)buf + CAB_HASH_OFFSET; hash_to_asc(hash, asc);; @@ -1653,8 +1653,8 @@ static void com_add_callback(int fd, const struct osl_object *query) goto out; } /* no hs or force mode, child must have sent afhi */ - uint16_t afhi_offset = read_u16(buf + CAB_AFHI_OFFSET_POS); - uint16_t chunks_offset = read_u16(buf + CAB_CHUNKS_OFFSET_POS); + afhi_offset = read_u16(buf + CAB_AFHI_OFFSET_POS); + chunks_offset = read_u16(buf + CAB_CHUNKS_OFFSET_POS); objs[AFTCOL_AFHI].data = buf + afhi_offset; objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset; @@ -1816,6 +1816,7 @@ static int add_one_audio_file(const char *path, void *private_data) afhi_ptr = &afhi; } munmap(map.data, map.size); + close(fd); if (pad->flags & ADD_FLAG_VERBOSE) { send_ret = send_va_buffer(pad->fd, "adding %s\n", path); if (send_ret < 0) @@ -2303,10 +2304,6 @@ static void com_cpsi_callback(int fd, const struct osl_object *query) }; int ret; char *source_path = (char *)query->data + sizeof(cad.flags); - - ret = get_afsi_of_path(source_path, &cad.source_afsi); - if (ret < 0) - goto out; struct pattern_match_data pmd = { .table = audio_file_table, .loop_col_num = AFTCOL_HASH, @@ -2317,6 +2314,10 @@ static void com_cpsi_callback(int fd, const struct osl_object *query) .data = &cad, .action = copy_selector_info }; + + ret = get_afsi_of_path(source_path, &cad.source_afsi); + if (ret < 0) + goto out; ret = for_each_matching_row(&pmd); out: if (ret < 0) diff --git a/alsa_write.c b/alsa_write.c index 8e8b693c..ae471679 100644 --- a/alsa_write.c +++ b/alsa_write.c @@ -21,6 +21,7 @@ #include "string.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "write.h" #include "alsa_write.cmdline.h" #include "error.h" @@ -41,7 +42,7 @@ struct private_alsa_write_data { /** * the samplerate given by command line option or the decoder * of the writer node group - */ + */ unsigned samplerate; /** * the number of channels, also given by command line option or the @@ -133,8 +134,9 @@ static int alsa_open(struct writer_node *w) return -E_SW_PARAMS; pad->bytes_per_frame = snd_pcm_format_physical_width(FORMAT) * pad->channels / 8; + PARA_INFO_LOG("bytes per frame: %zu\n", pad->bytes_per_frame); if (snd_pcm_nonblock(pad->handle, 1)) - PARA_ERROR_LOG("%s\n", "failed to set nonblock mode"); + PARA_ERROR_LOG("failed to set nonblock mode\n"); return period_size * pad->bytes_per_frame; } @@ -186,14 +188,16 @@ static int alsa_write_post_select(__a_unused struct sched *s, frames, (int)ret); return -E_ALSA_WRITE; } - if (ret == -EAGAIN) { /* try again in 5ms */ - PARA_WARNING_LOG("EAGAIN\n"); - ms2tv(5, &tv); - } else { + if (ret == -EAGAIN) + PARA_DEBUG_LOG("EAGAIN\n"); + else wn->written += ret * pad->bytes_per_frame; - ms2tv(pad->buffer_time / 4000, &tv); + if (ret == frames) /* we wrote everything, try again immediately */ + pad->next_chunk = *now; + else { + ms2tv(pad->buffer_time / pad->bytes_per_frame / 1000, &tv); + tv_add(now, &tv, &pad->next_chunk); } - tv_add(now, &tv, &pad->next_chunk); return 1; } @@ -209,10 +213,12 @@ static void alsa_close(struct writer_node *wn) __malloc static void *alsa_parse_config(const char *options) { + int ret; struct alsa_write_args_info *conf = para_calloc(sizeof(struct alsa_write_args_info)); + PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t")); - int ret = alsa_cmdline_parser_string(options, conf, "alsa_write"); + ret = alsa_cmdline_parser_string(options, conf, "alsa_write"); if (ret) goto err_out; PARA_INFO_LOG("help given: %d\n", conf->help_given); @@ -231,10 +237,17 @@ err_out: */ void alsa_write_init(struct writer *w) { + struct alsa_write_args_info dummy; + + alsa_cmdline_parser_init(&dummy); w->open = alsa_open; w->close = alsa_close; w->pre_select = alsa_write_pre_select; w->post_select = alsa_write_post_select; w->parse_config = alsa_parse_config; w->shutdown = NULL; /* nothing to do */ + w->help = (struct ggo_help) { + .short_help = alsa_write_args_info_help, + .detailed_help = alsa_write_args_info_detailed_help + }; } diff --git a/alsa_write.ggo b/alsa_write.ggo index 371ec89f..84f49d58 100644 --- a/alsa_write.ggo +++ b/alsa_write.ggo @@ -1,28 +1,34 @@ -section "alsa options" -###################### - option "device" d #~~~~~~~~~~~~~~~~ "set PCM device" - string typestr="device" - default="default" - optional +string typestr="device" +default="default" +optional +details=" + On systems with dmix, a better choice than the default + value might be to use \"plug:swmix\". +" option "channels" c #~~~~~~~~~~~~~~~~~~ -"number of channels (only necessary for raw -audio)" - - int typestr="num" - default="2" - optional +"specify number of channels" +int typestr="num" +default="2" +optional +details=" + This option is only necessary for playing raw audio with + para_write. In all other cases (plaing wav files with + para_write or using this writer with para_audiod), the number + of channels will be obtained from other resources. +" option "samplerate" s #~~~~~~~~~~~~~~~~~~~~~ - -"force given sample rate (only necessary for -raw audio)" - - int typestr="num" - default="44100" - optional +"force given sample rate" +int typestr="num" +default="44100" +optional +details=" + Again, it is only necessary to specify this when playing raw + audio with para_write. +" diff --git a/amp_filter.c b/amp_filter.c index 392fff65..a88719e3 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -4,12 +4,13 @@ * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file amp.c Paraslash's amplify filter. */ +/** \file amp_filter.c Paraslash's amplify filter. */ #include "para.h" #include "amp_filter.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "filter.h" #include "string.h" #include "error.h" @@ -50,20 +51,23 @@ static void amp_close(struct filter_node *fn) free(fn->buf); } -static void *amp_parse_config(int argc, char **argv) +static int amp_parse_config(int argc, char **argv, void **config) { - struct amp_filter_args_info *conf = para_calloc(sizeof(*conf)); + struct amp_filter_args_info *amp_conf = para_calloc(sizeof(*amp_conf)); + int ret = -E_AMP_SYNTAX; - if (amp_cmdline_parser(argc, argv, conf)) + if (amp_cmdline_parser(argc, argv, amp_conf)) goto err; - if (conf->amp_arg < 0) + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (amp_conf->amp_arg < 0) goto err; - PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n", conf->amp_arg, - conf->amp_arg / 64.0 + 1.0); - return conf; + PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n", + amp_conf->amp_arg, amp_conf->amp_arg / 64.0 + 1.0); + *config = amp_conf; + return 1; err: - free(conf); - return NULL; + free(amp_conf); + return ret; } static void amp_open(struct filter_node *fn) @@ -87,11 +91,17 @@ static void amp_open(struct filter_node *fn) * * \param f Pointer to the struct to initialize. */ -void amp_init(struct filter *f) +void amp_filter_init(struct filter *f) { + struct amp_filter_args_info dummy; + + amp_cmdline_parser_init(&dummy); f->open = amp_open; f->close = amp_close; f->convert = amp_convert; - f->print_help = amp_cmdline_parser_print_help; f->parse_config = amp_parse_config; + f->help = (struct ggo_help) { + .short_help = amp_filter_args_info_help, + .detailed_help = amp_filter_args_info_detailed_help + }; } diff --git a/amp_filter.ggo b/amp_filter.ggo index 1849dc3a..865df3ac 100644 --- a/amp_filter.ggo +++ b/amp_filter.ggo @@ -1,6 +1,5 @@ -section "The amplify filter" - option "amp" a +#~~~~~~~~~~~~~ "amplification value" int typestr="number" default="32" @@ -10,12 +9,9 @@ details=" which the amplitude of the audio stream is multiplied. The formula for the scaling factor is - factor = 1 + amp / 64 + factor = 1 + amp / 64. - amp value scaling factor - ~~~~~~~~~ ~~~~~~~~~~~~~~ - 0 1 - 32 1.5 - 64 2 - 128 3 + For example, an amplifiction value of zero results in a + scaling factor of one while an amplification value of 64 + means to double the volume. " diff --git a/attribute.c b/attribute.c index e7fba902..655c2866 100644 --- a/attribute.c +++ b/attribute.c @@ -301,9 +301,9 @@ static void com_addatt_callback(int fd, const struct osl_object *query) struct osl_object objs[NUM_ATT_COLUMNS]; struct osl_row *row; unsigned char bitnum; - len = strlen(p); struct addatt_event_data aed; + len = strlen(p); if (!len || p[len - 1] == '-' || p[len - 1] == '+') { ret2 = para_printf(&pb, "invalid attribute name: %s\n", p); if (ret2 < 0) @@ -343,7 +343,7 @@ static void com_addatt_callback(int fd, const struct osl_object *query) aed.name = p; aed.bitnum = bitnum; afs_event(ATTRIBUTE_ADD, &pb, &aed); - greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, bitnum); + greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, (int)bitnum); } out: if (ret < 0 && ret2 >= 0) diff --git a/audiod.c b/audiod.c index f0ce6258..63d42cac 100644 --- a/audiod.c +++ b/audiod.c @@ -14,6 +14,7 @@ #include "audiod.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "filter.h" #include "grab_client.cmdline.h" @@ -186,7 +187,7 @@ out: * \param ll loglevel * \param fmt the format string */ -void para_log(int ll, const char* fmt,...) +__printf_2_3 void para_log(int ll, const char* fmt,...) { va_list argp; FILE *outfd; @@ -213,16 +214,39 @@ void para_log(int ll, const char* fmt,...) va_end(argp); } -static char *configfile_exists(void) +static void parse_config_or_die(void) { - char *home = para_homedir(); - char *config_file = make_message("%s/.paraslash/audiod.conf", - home); - free(home); - if (file_exists(config_file)) - return config_file; + char *config_file; + + if (conf.config_file_given) + config_file = para_strdup(conf.config_file_arg); + else { + char *home = para_homedir(); + config_file = make_message("%s/.paraslash/audiod.conf", home); + free(home); + } + if (conf.config_file_given && !file_exists(config_file)) { + PARA_EMERG_LOG("can not read config file %s\n", config_file); + goto err; + } + if (config_file) { + struct audiod_cmdline_parser_params params = { + .override = 0, + .initialize = 0, + .check_required = 1, + .check_ambiguity = 0, + .print_errors = 1 + }; + if (audiod_cmdline_parser_config_file(config_file, &conf, ¶ms)) { + PARA_EMERG_LOG("parse error in config file\n"); + goto err; + } + } free(config_file); - return NULL; + return; +err: + free(config_file); + exit(EXIT_FAILURE); } static void setup_signal_handling(void) @@ -603,7 +627,7 @@ static int init_writers(void) struct audio_format_info *a; init_supported_writers(); - nw = PARA_MAX(1, conf.writer_given); + nw = PARA_MAX(1U, conf.writer_given); PARA_INFO_LOG("maximal number of writers: %d\n", nw); FOR_EACH_AUDIO_FORMAT(i) { a = &afi[i]; @@ -720,7 +744,7 @@ static int init_filters(void) int i, ret, nf; filter_init(filters); - nf = PARA_MAX(1, conf.filter_given); + nf = PARA_MAX(1U, conf.filter_given); PARA_INFO_LOG("maximal number of filters: %d\n", nf); FOR_EACH_AUDIO_FORMAT(i) { afi[i].filter_conf = para_malloc(nf * sizeof(void *)); @@ -874,11 +898,6 @@ static void close_stat_pipe(void) stat_task->offset_seconds = 0; audiod_status_dump(); stat_task->playing = 0; - stat_item_values[SI_BASENAME] = make_message( - "%s: no connection to para_server\n", - status_item_list[SI_BASENAME]); - stat_client_write(stat_item_values[SI_BASENAME], - SI_BASENAME); } /** @@ -1035,6 +1054,12 @@ static void status_pre_select(struct sched *s, struct task *t) client_open(argc, argv, &st->ct); set_stat_task_restart_barrier(5); } + free(stat_item_values[SI_BASENAME]); + stat_item_values[SI_BASENAME] = make_message( + "%s: no connection to para_server\n", + status_item_list[SI_BASENAME]); + stat_client_write(stat_item_values[SI_BASENAME], + SI_BASENAME); st->last_status_read = *now; out: start_stop_decoders(s); @@ -1067,6 +1092,24 @@ static void set_initial_status(void) PARA_WARNING_LOG("invalid mode\n"); } +__noreturn static void print_help_and_die(void) +{ + int d = conf.detailed_help_given; + const char **p = d? audiod_args_info_detailed_help + : audiod_args_info_help; + + printf_or_die("%s\n\n", AUDIOD_CMDLINE_PARSER_PACKAGE "-" + AUDIOD_CMDLINE_PARSER_VERSION); + printf_or_die("%s\n\n", audiod_args_info_usage); + for (; *p; p++) + printf_or_die("%s\n", *p); + print_receiver_helps(d); + print_filter_helps(d); + print_writer_helps(d); + exit(0); +} + + /** * the main function of para_audiod * @@ -1079,7 +1122,6 @@ static void set_initial_status(void) * */ int main(int argc, char *argv[]) { - char *config_file; int ret, i; static struct sched s; struct command_task command_task_struct, *cmd_task = &command_task_struct; @@ -1095,27 +1137,17 @@ int main(int argc, char *argv[]) audiod_cmdline_parser_ext(argc, argv, &conf, ¶ms); HANDLE_VERSION_FLAG("audiod", conf); para_drop_privileges(conf.user_arg, conf.group_arg); - config_file = configfile_exists(); - if (config_file) { - params.override = 0; - params.initialize = 0; - params.check_required = 1; - params.check_ambiguity = 0; - params.print_errors = 1; - if (audiod_cmdline_parser_config_file(config_file, &conf, ¶ms)) { - PARA_EMERG_LOG("parse error in config file\n"); - exit(EXIT_FAILURE); - } - free(config_file); - } + parse_config_or_die(); if (conf.logfile_given) logfile = open_log(conf.logfile_arg); - log_welcome("para_audiod", conf.loglevel_arg); i = init_stream_io(); + if (conf.help_given || conf.detailed_help_given) + print_help_and_die(); if (i < 0) { PARA_EMERG_LOG("init stream io error: %s\n", para_strerror(-i)); exit(EXIT_FAILURE); } + log_welcome("para_audiod", conf.loglevel_arg); server_uptime(UPTIME_SET); set_initial_status(); FOR_EACH_SLOT(i) diff --git a/audiod.ggo b/audiod.ggo index 41af85e9..8f960c67 100644 --- a/audiod.ggo +++ b/audiod.ggo @@ -14,6 +14,12 @@ int typestr="level" default="4" optional +option "config_file" c +#~~~~~~~~~~~~~~~~~~~~~ +"(default='~/.paraslash/audiod.conf'" +string typestr="filename" +optional + option "logfile" L #~~~~~~~~~~~~~~~~~ "where to write log output" @@ -98,7 +104,7 @@ details=" option "user_allow" - #~~~~~~~~~~~~~~~~~~~~ -"allow thus uid" +"allow this uid" int typestr="uid" default="-1" optional diff --git a/audiod_command.c b/audiod_command.c index fe8143ba..27ef63ca 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -14,6 +14,7 @@ #include "list.h" #include "close_on_fork.h" #include "sched.h" +#include "ggo.h" #include "filter.h" #include "grab_client.cmdline.h" #include "grab_client.h" diff --git a/blob.c b/blob.c index cdad8d7f..fd8ddc80 100644 --- a/blob.c +++ b/blob.c @@ -55,7 +55,7 @@ enum blob_ls_flags { /** Structure passed to the \p print_blob function. */ struct lsblob_action_data { - /* The flags given at the command line. */ + /** The flags given at the command line. */ uint32_t flags; /** Message buffer. */ struct para_buffer pb; diff --git a/client.c b/client.c index cf115e03..4208579b 100644 --- a/client.c +++ b/client.c @@ -80,7 +80,7 @@ int main(int argc, char *argv[]) s.default_timeout.tv_sec = 1; s.default_timeout.tv_usec = 0; ret = client_open(argc, argv, &ct); - if (ret < 0) /* can not use PARA_LOG here */ + if (ret < 0) /* can not use PARA_LOG here because ct is NULL */ exit(EXIT_FAILURE); register_task(&svt); ret = schedule(&s); diff --git a/client.ggo b/client.ggo index 702358cb..1d6b410d 100644 --- a/client.ggo +++ b/client.ggo @@ -1,4 +1,5 @@ # file client.conf +args "--no-handle-error" option "hostname" i "ip or host to connect" string typestr="host" default="localhost" optional option "user" u "paraslash username" string typestr="username" default="" optional option "server_port" p "port to connect" int typestr="port" default="2990" optional diff --git a/client_common.c b/client_common.c index d8eaba66..9886cfe5 100644 --- a/client_common.c +++ b/client_common.c @@ -328,7 +328,9 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr) *ct_ptr = ct; ct->fd = -1; - ret = client_cmdline_parser(argc, argv, &ct->conf); + ret = -E_CLIENT_SYNTAX; + if (client_cmdline_parser(argc, argv, &ct->conf)) + goto out; HANDLE_VERSION_FLAG("client", ct->conf); ret = -E_CLIENT_SYNTAX; if (!ct->conf.inputs_num) @@ -353,10 +355,13 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr) .override = 0, .initialize = 0, .check_required = 0, - .check_ambiguity = 0 + .check_ambiguity = 0, + .print_errors = 0 }; - client_cmdline_parser_config_file(ct->config_file, - &ct->conf, ¶ms); + ret = -E_BAD_CONFIG; + if (client_cmdline_parser_config_file(ct->config_file, + &ct->conf, ¶ms)) + goto out; } ret = 1; PARA_INFO_LOG("loglevel: %d\n", ct->conf.loglevel_arg); @@ -374,4 +379,3 @@ out: } return ret; } - diff --git a/compress.c b/compress.c deleted file mode 100644 index c30c998a..00000000 --- a/compress.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2005-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file compress.c Paraslash's dynamic audio range compressor. */ - -/* - * Uses ideas of AudioCompress, (C) 2002-2004 M. Hari Nezumi - */ - -#include "para.h" -#include "compress_filter.cmdline.h" -#include "list.h" -#include "sched.h" -#include "filter.h" -#include "string.h" - -/** The size of the output data buffer. */ -#define COMPRESS_CHUNK_SIZE 40960 - -extern char *stat_item_values[NUM_STAT_ITEMS]; - -/** Data specific to the compress filter. */ -struct private_compress_data { - /** The current multiplier. */ - unsigned current_gain; - /** Points to the configuration data for this instance of the compress filter. */ - struct compress_filter_args_info *conf; - /** Maximal admissible gain. */ - unsigned max_gain; - /** Number of samples already seen. */ - unsigned num_samples; - /** Absolute value of the maximal sample in the current block. */ - unsigned peak; -}; - -static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) -{ - size_t i, length = PARA_MIN((inbuf_len / 2) * 2, - (fn->bufsize - fn->loaded) / 2 * 2); - struct private_compress_data *pcd = fn->private_data; - int16_t *ip = (int16_t *)inbuf, *op = (int16_t *)(fn->buf + fn->loaded); - unsigned gain_shift = pcd->conf->inertia_arg + pcd->conf->damp_arg, - mask = (1 << pcd->conf->blocksize_arg) - 1; - - if (!length) - return 0; - for (i = 0; i < length / 2; i++) { - /* be careful in that heat, my dear */ - int sample = *ip++, adjusted_sample = (PARA_ABS(sample) * - pcd->current_gain) >> gain_shift; - if (unlikely(adjusted_sample > 32767)) { /* clip */ - PARA_NOTICE_LOG("clip: sample: %d, adjusted sample: %d\n", - sample, adjusted_sample); - adjusted_sample = 32767; - pcd->current_gain = (3 * pcd->current_gain + - (1 << pcd->conf->inertia_arg)) / 4; - pcd->peak = 0; - } else - pcd->peak = PARA_MAX(pcd->peak, adjusted_sample); - *op++ = sample >= 0? adjusted_sample : -adjusted_sample; - if (likely(++pcd->num_samples & mask)) - continue; -// PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain, -// pcd->peak); - if (pcd->peak < pcd->conf->target_level_arg) { - if (pcd->current_gain < pcd->max_gain) - pcd->current_gain++; - } else - pcd->current_gain = PARA_MAX(pcd->current_gain - 2, - 1 << pcd->conf->inertia_arg); - pcd->peak = 0; - } - fn->loaded += length; - return length; -} - -static void close_compress(struct filter_node *fn) -{ - free(fn->private_data); - free(fn->buf); -} - -static void *compress_parse_config(int argc, char **argv) -{ - struct compress_filter_args_info *ret = para_calloc(sizeof(struct compress_filter_args_info)); - if (!compress_cmdline_parser(argc, argv, ret)) - return ret; - free(ret); - return NULL; -} - -static void open_compress(struct filter_node *fn) -{ - struct private_compress_data *pcd = para_calloc( - sizeof(struct private_compress_data)); - pcd->conf = fn->conf; - fn->private_data = pcd; - fn->bufsize = COMPRESS_CHUNK_SIZE; - fn->buf = para_malloc(fn->bufsize); - pcd->current_gain = 1 << pcd->conf->inertia_arg; - pcd->max_gain = 1 << (pcd->conf->inertia_arg + pcd->conf->aggressiveness_arg); -} - -/** - * The init function of the compress filter. - * - * \param f Pointer to the struct to initialize. - */ -void compress_init(struct filter *f) -{ - f->open = open_compress; - f->close = close_compress; - f->convert = compress; - f->print_help = compress_cmdline_parser_print_help; - f->parse_config = compress_parse_config; -} diff --git a/compress_filter.c b/compress_filter.c new file mode 100644 index 00000000..c7af3253 --- /dev/null +++ b/compress_filter.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2005-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file compress_filter.c Paraslash's dynamic audio range compressor. */ + +/* + * Uses ideas of AudioCompress, (C) 2002-2004 M. Hari Nezumi + */ + +#include "para.h" +#include "compress_filter.cmdline.h" +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "filter.h" +#include "string.h" +#include "error.h" + +/** The size of the output data buffer. */ +#define COMPRESS_CHUNK_SIZE 40960 + +extern char *stat_item_values[NUM_STAT_ITEMS]; + +/** Data specific to the compress filter. */ +struct private_compress_data { + /** The current multiplier. */ + unsigned current_gain; + /** Points to the configuration data for this instance of the compress filter. */ + struct compress_filter_args_info *conf; + /** Maximal admissible gain. */ + unsigned max_gain; + /** Number of samples already seen. */ + unsigned num_samples; + /** Absolute value of the maximal sample in the current block. */ + int peak; +}; + +static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) +{ + size_t i, length = PARA_MIN((inbuf_len / 2) * 2, + (fn->bufsize - fn->loaded) / 2 * 2); + struct private_compress_data *pcd = fn->private_data; + int16_t *ip = (int16_t *)inbuf, *op = (int16_t *)(fn->buf + fn->loaded); + unsigned gain_shift = pcd->conf->inertia_arg + pcd->conf->damp_arg, + mask = (1 << pcd->conf->blocksize_arg) - 1; + + if (!length) + return 0; + for (i = 0; i < length / 2; i++) { + /* be careful in that heat, my dear */ + int sample = *ip++, adjusted_sample = (PARA_ABS(sample) * + pcd->current_gain) >> gain_shift; + if (unlikely(adjusted_sample > 32767)) { /* clip */ + PARA_NOTICE_LOG("clip: sample: %d, adjusted sample: %d\n", + sample, adjusted_sample); + adjusted_sample = 32767; + pcd->current_gain = (3 * pcd->current_gain + + (1 << pcd->conf->inertia_arg)) / 4; + pcd->peak = 0; + } else + pcd->peak = PARA_MAX(pcd->peak, adjusted_sample); + *op++ = sample >= 0? adjusted_sample : -adjusted_sample; + if (likely(++pcd->num_samples & mask)) + continue; +// PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain, +// pcd->peak); + if (pcd->peak < pcd->conf->target_level_arg) { + if (pcd->current_gain < pcd->max_gain) + pcd->current_gain++; + } else + pcd->current_gain = PARA_MAX(pcd->current_gain - 2, + 1U << pcd->conf->inertia_arg); + pcd->peak = 0; + } + fn->loaded += length; + return length; +} + +static void close_compress(struct filter_node *fn) +{ + free(fn->private_data); + free(fn->buf); +} + +/** TODO: Add sanity checks */ +static int compress_parse_config(int argc, char **argv, void **config) +{ + int ret; + struct compress_filter_args_info *compress_conf + = para_calloc(sizeof(*compress_conf)); + + ret = -E_COMPRESS_SYNTAX; + if (compress_cmdline_parser(argc, argv, compress_conf)) + goto err; + *config = compress_conf; + return 1; +err: + free(compress_conf); + return ret; +} + +static void open_compress(struct filter_node *fn) +{ + struct private_compress_data *pcd = para_calloc( + sizeof(struct private_compress_data)); + pcd->conf = fn->conf; + fn->private_data = pcd; + fn->bufsize = COMPRESS_CHUNK_SIZE; + fn->buf = para_malloc(fn->bufsize); + pcd->current_gain = 1 << pcd->conf->inertia_arg; + pcd->max_gain = 1 << (pcd->conf->inertia_arg + pcd->conf->aggressiveness_arg); +} + +/** + * The init function of the compress filter. + * + * \param f Pointer to the struct to initialize. + */ +void compress_filter_init(struct filter *f) +{ + struct compress_filter_args_info dummy; + + compress_cmdline_parser_init(&dummy); + f->open = open_compress; + f->close = close_compress; + f->convert = compress; + f->parse_config = compress_parse_config; + f->help = (struct ggo_help) { + .short_help = compress_filter_args_info_help, + .detailed_help = compress_filter_args_info_detailed_help + }; +} diff --git a/compress_filter.ggo b/compress_filter.ggo index 3f4f8e2e..74dcbc03 100644 --- a/compress_filter.ggo +++ b/compress_filter.ggo @@ -1,7 +1,37 @@ -section "The dynamic audio range compressor" +option "blocksize" b +#~~~~~~~~~~~~~~~~~~~ +"adjust block size" +int typestr="number" +default="15" +optional +details = " + Larger blocksize means fewer volume adjustments per time unit. +" -option "blocksize" b "larger blocksize means fewer volume adjustments per time unit" int typestr="number" default="15" optional -option "aggressiveness" a "controls the maximum amount to amplify by" int typestr="number" default="4" optional -option "inertia" i "how much inertia ramping has" int typestr="number" default="6" optional -option "target_level" t "target signal level (0-32768)" int typestr="number" default="20000" optional -option "damp" d "if non-zero, scale down after normalizing" int typestr="number" default="0" optional +option "aggressiveness" a +#~~~~~~~~~~~~~~~~~~~~~~~~ + "controls the maximum amount to amplify by" +int typestr="number" +default="4" +optional + +option "inertia" i +#~~~~~~~~~~~~~~~~~ + "how much inertia ramping has" + int typestr="number" +default="6" +optional + +option "target_level" t +#~~~~~~~~~~~~~~~~~~~~~~ +"target signal level (0-32768)" +int typestr="number" +default="20000" +optional + +option "damp" d +#~~~~~~~~~~~~~~ +"if non-zero, scale down after normalizing" +int typestr="number" +default="0" +optional diff --git a/configure.ac b/configure.ac index 63cbdc97..664280ac 100644 --- a/configure.ac +++ b/configure.ac @@ -81,21 +81,22 @@ AC_CHECK_FUNCS([atexit dup2 memchr memmove memset \ all_errlist_objs="server mp3_afh afh_common vss command net string signal time daemon stat crypt http_send close_on_fork ipc acl afh fade amp_filter dccp_send fd user_list chunk_queue afs osl aft mood score attribute blob ringbuffer -playlist sha1 rbtree sched audiod grab_client filter_chain wav compress +playlist sha1 rbtree sched audiod grab_client filter_common wav_filter compress_filter http_recv dccp_recv recv_common write_common file_write audiod_command -client_common recv stdout filter stdin audioc write client fsck exec send_common" -all_executables="server recv filter audioc write client fsck afh amp_filter" +client_common recv stdout filter stdin audioc write client fsck exec send_common ggo" + +all_executables="server recv filter audioc write client fsck afh" recv_cmdline_objs="recv.cmdline http_recv.cmdline dccp_recv.cmdline" recv_errlist_objs="http_recv recv_common recv time string net dccp_recv - fd sched stdout" + fd sched stdout ggo" recv_ldflags="" receivers=" http dccp" senders=" http dccp" filter_cmdline_objs="filter.cmdline compress_filter.cmdline amp_filter.cmdline" -filter_errlist_objs="filter_chain wav compress filter string stdin stdout sched fd amp_filter" +filter_errlist_objs="filter_common wav_filter compress_filter filter string stdin stdout sched fd amp_filter ggo" filter_ldflags="" filters=" compress wav amp" @@ -107,9 +108,9 @@ audiod_cmdline_objs="audiod.cmdline grab_client.cmdline compress_filter.cmdline http_recv.cmdline dccp_recv.cmdline file_write.cmdline client.cmdline audiod_command_list amp_filter.cmdline" audiod_errlist_objs="audiod signal string daemon stat net - time grab_client filter_chain wav compress amp_filter http_recv dccp_recv + time grab_client filter_common wav_filter compress_filter amp_filter http_recv dccp_recv recv_common fd sched write_common file_write audiod_command crypt - client_common" + client_common ggo" audiod_ldflags="" audiod_audio_formats="" @@ -126,7 +127,7 @@ server_ldflags="" server_audio_formats=" mp3" write_cmdline_objs="write.cmdline file_write.cmdline" -write_errlist_objs="write write_common file_write time fd string sched stdin" +write_errlist_objs="write write_common file_write time fd string sched stdin ggo" write_ldflags="" writers=" file" default_writer="FILE_WRITE" @@ -315,7 +316,7 @@ AC_CHECK_LIB([ogg], [ogg_stream_init], [], [ have_ogg="no" ]) AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ have_ogg="no" ]) AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [ have_ogg="no" ]) if test "$have_ogg" = "yes"; then - all_errlist_objs="$all_errlist_objs oggdec ogg_afh" + all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh" AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support) filters="$filters oggdec" if test "$OSTYPE" = "Darwin"; then @@ -330,13 +331,12 @@ if test "$have_ogg" = "yes"; then audiod_cmdline_objs="$audiod_cmdline_objs oggdec_filter.cmdline" server_errlist_objs="$server_errlist_objs ogg_afh" - filter_errlist_objs="$filter_errlist_objs oggdec" - audiod_errlist_objs="$audiod_errlist_objs oggdec" + filter_errlist_objs="$filter_errlist_objs oggdec_filter" + audiod_errlist_objs="$audiod_errlist_objs oggdec_filter" afh_errlist_objs="$afh_errlist_objs ogg_afh" audiod_audio_formats="ogg" server_audio_formats="$server_audio_formats ogg" - filter_filters="$filter_filters oggdec" AC_SUBST(oggvorbis_cppflags) AC_SUBST(oggvorbis_libs) else @@ -366,11 +366,10 @@ AC_CHECK_HEADER(neaacdec.h, [], have_faad=no) AC_CHECK_LIB([faad], [NeAACDecOpen], [], have_faad=no) if test "$have_faad" = "yes"; then AC_DEFINE(HAVE_FAAD, 1, define to 1 if you want to build the aacdec filter) - all_errlist_objs="$all_errlist_objs aac_common aacdec aac_afh" - filter_errlist_objs="$filter_errlist_objs aacdec aac_common" + all_errlist_objs="$all_errlist_objs aac_common aacdec_filter aac_afh" + filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common" afh_errlist_objs="$afh_errlist_objs aac_common aac_afh" - filter_filters="$filter_filters aacdec" - audiod_errlist_objs="$audiod_errlist_objs aacdec aac_common" + audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common" server_errlist_objs="$server_errlist_objs aac_afh aac_common" server_ldflags="$server_ldflags $faad_libs -lfaad" filter_ldflags="$filter_ldflags $faad_libs -lfaad" @@ -413,9 +412,11 @@ AC_CHECK_LIB([mad], [mad_stream_init], [], [ ]) if test "$have_mad" = "yes"; then AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter) - all_errlist_objs="$all_errlist_objs mp3dec" - filter_errlist_objs="$filter_errlist_objs mp3dec" - audiod_errlist_objs="$audiod_errlist_objs mp3dec" + filter_cmdline_objs="$filter_cmdline_objs mp3dec_filter.cmdline" + audiod_cmdline_objs="$audiod_cmdline_objs mp3dec_filter.cmdline" + all_errlist_objs="$all_errlist_objs mp3dec_filter" + filter_errlist_objs="$filter_errlist_objs mp3dec_filter" + audiod_errlist_objs="$audiod_errlist_objs mp3dec_filter" filter_ldflags="$filter_ldflags $mad_libs -lmad" audiod_ldflags="$audiod_ldflags $mad_libs -lmad" audiod_audio_formats="$audiod_audio_formats mp3" @@ -706,6 +707,15 @@ AC_SUBST(fade_objs, add_dot_o($fade_objs)) AC_DEFINE_UNQUOTED(INIT_FADE_ERRLISTS, objlist_to_errlist($fade_errlist_objs), errors used by para_fade) + +enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)" +AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS, + enum of supported filters) +inits="$(for i in $filters; do printf 'extern void '$i'_filter_init(struct filter *f); '; done)" +AC_DEFINE_UNQUOTED(DECLARE_FILTER_INITS, $inits, init functions of the supported filters) +array="$(for i in $filters; do printf '{.name = \"'$i'\", .init = '$i'_filter_init},'; done)" +AC_DEFINE_UNQUOTED(FILTER_ARRAY, $array, array of supported filters) + enum="$(for i in $writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)" AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS, enum of supported writers) @@ -716,6 +726,7 @@ inits="$(for i in $writers; do printf 'extern void '$i'_write_init(struct writer AC_DEFINE_UNQUOTED(DECLARE_WRITER_INITS, $inits, init functions of the supported writers) array="$(for i in $writers; do printf '{.init = '$i'_write_init},'; done)" AC_DEFINE_UNQUOTED(WRITER_ARRAY, $array, array of supported writers) + enum="$(for i in $audiod_audio_formats; do printf "AUDIO_FORMAT_${i}, " | tr '[a-z]' '[A-Z]'; done)" AC_DEFINE_UNQUOTED(AUDIOD_AUDIO_FORMATS_ENUM, $enum NUM_AUDIO_FORMATS, enum of audio formats supported by audiod) diff --git a/dccp_recv.c b/dccp_recv.c index d0bc5354..c73088f8 100644 --- a/dccp_recv.c +++ b/dccp_recv.c @@ -18,6 +18,7 @@ #include "error.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "string.h" #include "net.h" @@ -143,10 +144,17 @@ static void dccp_recv_post_select(struct sched *s, struct task *t) */ void dccp_recv_init(struct receiver *r) { + struct dccp_recv_args_info dummy; + + dccp_recv_cmdline_parser_init(&dummy); r->shutdown = dccp_shutdown; r->open = dccp_recv_open; r->close = dccp_recv_close; r->pre_select = dccp_recv_pre_select; r->post_select = dccp_recv_post_select; r->parse_config = dccp_recv_parse_config; + r->help = (struct ggo_help) { + .short_help = dccp_recv_args_info_help, + .detailed_help = dccp_recv_args_info_detailed_help + }; } diff --git a/dccp_recv.ggo b/dccp_recv.ggo index 52cfef6d..9717329d 100644 --- a/dccp_recv.ggo +++ b/dccp_recv.ggo @@ -1,3 +1,13 @@ -section "options of the dccp receiver" -option "host" i "ip or host" string default="localhost" optional -option "port" p "port to connect to" int default="8000" optional +option "host" i +"ip or host" +string default="localhost" +optional +details=" + Both IPv4 and IPv6 addresses are supported. +" + +option "port" p +"port to connect to" +int +default="8000" +optional diff --git a/error.h b/error.h index 4674ec04..37ff48a9 100644 --- a/error.h +++ b/error.h @@ -13,8 +13,7 @@ DEFINE_ERRLIST_OBJECT_ENUM; /* these do not need error handling (yet) */ #define SERVER_ERRORS -#define WAV_ERRORS -#define COMPRESS_ERRORS +#define WAV_FILTER_ERRORS #define TIME_ERRORS #define CLOSE_ON_FORK_ERRORS #define DAEMON_ERRORS @@ -25,15 +24,27 @@ DEFINE_ERRLIST_OBJECT_ENUM; #define SHA1_ERRORS #define RBTREE_ERRORS #define RECV_ERRORS -#define SEND_COMMON_ERRORS #define STDOUT_ERRORS #define IPC_ERRORS -#define AMP_FILTER_ERRORS - +#define DCCP_SEND_ERRORS +#define HTTP_SEND_ERRORS +#define GGO_ERRORS extern const char **para_errlist[]; +#define COMPRESS_FILTER_ERRORS \ + PARA_ERROR(COMPRESS_SYNTAX, "syntax error in compress filter config"), \ + + +#define AMP_FILTER_ERRORS \ + PARA_ERROR(AMP_SYNTAX, "syntax error in amp filter config"), \ + + +#define SEND_COMMON_ERRORS \ + PARA_ERROR(MAX_CLIENTS, "maximal number of clients exceeded"), \ + + #define FADE_ERRORS \ PARA_ERROR(FADE_SYNTAX, "fade syntax error"), \ @@ -113,6 +124,7 @@ extern const char **para_errlist[]; PARA_ERROR(MOOD_SYNTAX, "mood syntax error"), \ PARA_ERROR(NO_MOOD, "no mood available"), \ PARA_ERROR(NOT_ADMISSIBLE, "file is not admissible"), \ + PARA_ERROR(DUMMY_ROW, "attempted to access blob dummy object"), \ #define ATTRIBUTE_ERRORS \ @@ -123,7 +135,6 @@ extern const char **para_errlist[]; #define BLOB_ERRORS \ PARA_ERROR(BLOB_SYNTAX, "blob syntax error"), \ - PARA_ERROR(DUMMY_ROW, "attempted to access blob dummy object"), \ #define PLAYLIST_ERRORS \ @@ -169,6 +180,7 @@ extern const char **para_errlist[]; PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \ PARA_ERROR(INVALID_CHALLENGE, "did not receive valid challenge"), \ PARA_ERROR(NO_CONFIG, "config file not found"), \ + PARA_ERROR(BAD_CONFIG, "syntax error in config file"), \ PARA_ERROR(CLIENT_AUTH, "authentication failed"), \ PARA_ERROR(SERVER_EOF, "connection closed by para_server"), \ @@ -229,7 +241,7 @@ extern const char **para_errlist[]; PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \ -#define FILTER_CHAIN_ERRORS \ +#define FILTER_COMMON_ERRORS \ PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \ PARA_ERROR(BAD_FILTER_OPTIONS, "invalid filter option given"), \ PARA_ERROR(FC_EOF, "filter chain: eof"), \ @@ -240,13 +252,14 @@ extern const char **para_errlist[]; PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \ -#define OGGDEC_ERRORS \ +#define OGGDEC_FILTER_ERRORS \ PARA_ERROR(OGGDEC_READ, "read from media returned an error"), \ PARA_ERROR(OGGDEC_NOTVORBIS, "bitstream is not vorbis data"), \ PARA_ERROR(OGGDEC_VERSION, "vorbis version mismatch"), \ PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \ PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \ PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \ + PARA_ERROR(OGGDEC_SYNTAX, "syntax error in oggdec config"), \ #define GRAB_CLIENT_ERRORS \ @@ -259,9 +272,10 @@ extern const char **para_errlist[]; PARA_ERROR(GC_VERSION_GIVEN, ""), /* not really an error */ \ -#define MP3DEC_ERRORS \ +#define MP3DEC_FILTER_ERRORS \ PARA_ERROR(MAD_FRAME_DECODE, "mad frame decode error"), \ PARA_ERROR(MP3DEC_OVERRUN, "mp3 output buffer overrun"), \ + PARA_ERROR(MP3DEC_SYNTAX, "syntax error in mp3dec config"), \ #define FILTER_ERRORS \ @@ -316,7 +330,6 @@ extern const char **para_errlist[]; #define VSS_ERRORS \ - PARA_ERROR(CHUNK, "unable to get chunk"), \ PARA_ERROR(NOFD, "did not receive open fd from afs"), \ @@ -329,10 +342,6 @@ extern const char **para_errlist[]; PARA_ERROR(CHALLENGE, "failed to read challenge"), \ -#define HTTP_SEND_ERRORS \ - PARA_ERROR(MAX_CLIENTS, "maximal number of clients exceeded"), \ - - #define COMMAND_ERRORS \ PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \ PARA_ERROR(AUTH, "did not receive auth request"), \ @@ -349,9 +358,6 @@ extern const char **para_errlist[]; PARA_ERROR(DCCP_OVERRUN, "dccp output buffer buffer overrun"), \ -#define DCCP_SEND_ERRORS \ - - #define FD_ERRORS \ PARA_ERROR(FGETS, "fgets error"), \ PARA_ERROR(EMPTY, "file empty"), \ @@ -394,7 +400,7 @@ extern const char **para_errlist[]; PARA_ERROR(WRITE_COMMON_SYNTAX, "syntax error in write option"), \ -#define AACDEC_ERRORS \ +#define AACDEC_FILTER_ERRORS \ PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \ PARA_ERROR(AAC_DECODE, "aac decode error"), \ PARA_ERROR(AAC_OVERRUN, "aac output buffer overrun"), \ diff --git a/exec.c b/exec.c index 86362c38..bafae240 100644 --- a/exec.c +++ b/exec.c @@ -37,7 +37,7 @@ static int para_exec(pid_t *pid, const char *file, char *const *const args, int if (fds[2] > 0 && pipe(err) < 0) goto err_out; if (!fds[0] || !fds[1] || !fds[2]) { - ret = para_open("/dev/null", O_RDONLY, 42); + ret = para_open("/dev/null", O_RDWR, 42); if (ret < 0) goto err_out; null = ret; @@ -139,8 +139,6 @@ int para_exec_cmdline_pid(pid_t *pid, const char *cmdline, int *fds) char **argv; char *tmp = para_strdup(cmdline); - if (!tmp) - exit(EXIT_FAILURE); argc = split_args(tmp, &argv, " \t"); ret = para_exec(pid, argv[0], argv, fds); free(argv); diff --git a/fade.c b/fade.c index 48faed83..33f1ccbe 100644 --- a/fade.c +++ b/fade.c @@ -28,7 +28,7 @@ INIT_FADE_ERRLISTS; struct fade_args_info conf; -void para_log(__a_unused int ll, const char *fmt,...) +__printf_2_3 void para_log(__a_unused int ll, const char *fmt, ...) { va_list argp; time_t t1; diff --git a/fade.ggo b/fade.ggo index 1ab71a53..5965cba9 100644 --- a/fade.ggo +++ b/fade.ggo @@ -74,7 +74,7 @@ option "wake_hour" H option "wake_min" M #~~~~~~~~~~~~~~~~~~ "(0-59)" - int typestr="seconds" + int typestr="minutes" default="0" optional diff --git a/fd.c b/fd.c index 8fc1abad..c675aaff 100644 --- a/fd.c +++ b/fd.c @@ -285,9 +285,10 @@ int para_opendir(const char *dirname, DIR **dir, int *cwd) if (*dir) return 1; ret = -ERRNO_TO_PARA_ERROR(errno); -/* Ignore return value of fchdir() and close(). We're busted anyway. */ - if (cwd) - fchdir(*cwd); + /* Ignore return value of fchdir() and close(). We're busted anyway. */ + if (cwd) { + int __a_unused ret2 = fchdir(*cwd); /* STFU, gcc */ + } close_cwd: if (cwd) close(*cwd); diff --git a/file_write.c b/file_write.c index d7cea2f9..533d3331 100644 --- a/file_write.c +++ b/file_write.c @@ -12,6 +12,7 @@ #include "para.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "write.h" #include "string.h" #include "fd.h" @@ -95,10 +96,10 @@ static void file_write_close(struct writer_node *wn) __malloc static void *file_write_parse_config(const char *options) { - PARA_INFO_LOG("options: %s\n", options); struct file_write_args_info *conf = para_calloc(sizeof(struct file_write_args_info)); int ret = file_cmdline_parser_string(options, conf, "file_write"); + PARA_INFO_LOG("conf->filename_given: %d\n", conf->filename_given); if (!ret) return conf; @@ -109,10 +110,17 @@ __malloc static void *file_write_parse_config(const char *options) /** the init function of the file writer */ void file_write_init(struct writer *w) { + struct file_write_args_info dummy; + + file_cmdline_parser_init(&dummy); w->open = file_write_open; w->pre_select = file_write_pre_select; w->post_select = file_write_post_select; w->parse_config = file_write_parse_config; w->close = file_write_close; w->shutdown = NULL; /* nothing to do */ + w->help = (struct ggo_help) { + .short_help = file_write_args_info_help, + .detailed_help = file_write_args_info_detailed_help + }; } diff --git a/file_write.ggo b/file_write.ggo index a172f757..56c44f7a 100644 --- a/file_write.ggo +++ b/file_write.ggo @@ -1,11 +1,9 @@ -section "file writer options" - option "filename" f #~~~~~~~~~~~~~~~~~~ - -"select output file name. Defaults to a -random filename in ~/.paraslash." - - string typestr="filename" - optional +"specify output file name" +string typestr="filename" +optional +details=" + Defaults to a random filename in ~/.paraslash. +" diff --git a/filter.c b/filter.c index 09b64415..d67aeae4 100644 --- a/filter.c +++ b/filter.c @@ -11,6 +11,7 @@ #include "filter.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "filter.h" #include "string.h" #include "stdin.h" @@ -94,15 +95,31 @@ err: return ret; } +__noreturn static void print_help_and_die(void) +{ + int d = conf.detailed_help_given; + const char **p = d? filter_args_info_detailed_help + : filter_args_info_help; + + printf_or_die("%s\n\n", FILTER_CMDLINE_PARSER_PACKAGE "-" + FILTER_CMDLINE_PARSER_VERSION); + printf_or_die("%s\n\n", filter_args_info_usage); + for (; *p; p++) + printf_or_die("%s\n", *p); + print_filter_helps(d); + exit(0); +} + static int parse_config(int argc, char *argv[]) { static char *cf; /* config file */ struct stat statbuf; - int i; if (filter_cmdline_parser(argc, argv, &conf)) return -E_FILTER_SYNTAX; HANDLE_VERSION_FLAG("filter", conf); + if (conf.help_given || conf.detailed_help_given) + print_help_and_die(); if (!cf) { char *home = para_homedir(); cf = make_message("%s/.paraslash/filter.conf", home); @@ -118,15 +135,7 @@ static int parse_config(int argc, char *argv[]) if (filter_cmdline_parser_config_file(cf, &conf, ¶ms)) return -E_FILTER_SYNTAX; } - if (!conf.list_filters_given) - return 1; - printf("available filters: "); - for (i = 0; filters[i].name; i++) - printf("%s%s%s", i? " " : "", filters[i].name, - filters[i].parse_config? "*": ""); - printf("\nFilters marked with \"*\" have further command line options. Try\n" - "\tpara_filter -f ' -h'\nfor more information.\n"); - exit(EXIT_SUCCESS); + return 1; } /** @@ -156,6 +165,7 @@ int main(int argc, char *argv[]) ret = init_filter_chain(); if (ret < 0) goto out; + sit->output_error = &fc->task.error; stdout_set_defaults(sot); sot->buf = fc->outbuf; diff --git a/filter.ggo b/filter.ggo index 111e87c6..5376a6f8 100644 --- a/filter.ggo +++ b/filter.ggo @@ -1,17 +1,24 @@ -option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" optional -option "filter" f "Specify filter. +option "loglevel" l +#~~~~~~~~~~~~~~~~~~ + "set loglevel (0-6)" +int typestr="level" +default="4" +optional -May be given multiple times to 'pipe' the stream -through arbitrary many filters in an efficient -way. The same filter may appear more than once, -order matters. +option "filter" f +#~~~~~~~~~~~~~~~~ +"Specify filter." +string typestr="filter_spec" +optional +multiple +details=" + May be given multiple times to 'pipe' the stream through + arbitrary many filters in an efficient way. The same filter + may appear more than once, order matters. -Filter options may be specified for each '-f' -option separately. Note that you will have to -quote these options like this: + Filter options may be specified for each '-f' option + separately. Note that you will have to quote these options + like this: - -f 'compress --inertia 5 --damp 2' + -f 'compress --inertia 5 --damp 2' " -string typestr="filter_spec" optional multiple - -option "list_filters" L "print list of available filters and exit" flag off diff --git a/filter.h b/filter.h index 500a8537..c2a6310b 100644 --- a/filter.h +++ b/filter.h @@ -4,8 +4,10 @@ * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file filter.h Filter-related structures and exported symbols from filter_chain.c. */ +/** \file filter.h Filter-related structures and exported symbols from filter_common.c. */ +/** The list of supported filters. */ +enum filter_enum {FILTER_ENUM}; /** * Describes one running instance of a filter. @@ -34,6 +36,7 @@ struct filter_node { /** Describes one running instance of a chain of filters */ struct filter_chain { + /** The length of the filter chain. */ unsigned int num_filters; /** * The number of channels of the current stream. @@ -140,7 +143,7 @@ struct filter_callback { * Note: As several instances of the same filter may be running at the same * time, all these filter functions must be reentrant; no static non-constant * variables may be used. - * \sa mp3dec.c, oggdec.c, wav.c, compress.c, filter_node + * \sa mp3dec_filter.c, oggdec_filter.c, wav_filter.c, compress_filter.c, filter_node */ struct filter { /** The name of the filter. */ @@ -180,31 +183,28 @@ struct filter { * by the open() function. */ void (*close)(struct filter_node *fn); - /** - * Print the help text for this filter and exit. - * - * This is optional and it is not necessary to initialize this pointer if - * the filter does not have a help text. - */ - void (*print_help)(void); /** * A pointer to the filter's command line parser. * - * If this optional function pointer is not NULL, any filter options are passed - * from the main program to this command line parser once at application - * startup. The command line parser should check its command line options given - * by \a argc and \a argv and abort on errors. On success, it should return a - * pointer to the filter-specific configuration data determined by \a argc and - * \a argv. + * If this optional function pointer is not NULL, any filter options + * are passed from the main program to this command line parser once at + * application startup. The command line parser should check its + * command line options given by \a argc and \a argv and abort on + * errors. Success must be indicated by a non-negative return value. In + * this case the function should return a pointer to the + * filter-specific configuration data determined by \a argc and \a + * argv. On failure, a negative paraslash error code must be returned. */ - void *(*parse_config)(int argc, char **argv); + int (*parse_config)(int argc, char **argv, void **config); + + struct ggo_help help; }; void close_filters(struct filter_chain *fc); void filter_init(struct filter *all_filters); int check_filter_arg(char *filter_arg, void **conf); void filter_pre_select(__a_unused struct sched *s, struct task *t); - +void print_filter_helps(int detailed); static inline void write_int16_host_endian(char *buf, int val) { @@ -217,55 +217,9 @@ static inline void write_int16_host_endian(char *buf, int val) #endif } +DECLARE_FILTER_INITS -/** \cond */ -extern struct filter filters[]; -#define DECLARE_EXTERN_FILTER_INIT(name) \ - extern void name ## _init(struct filter *f) - -#define FILTER_INIT(filter) { \ - .name = #filter, \ - .init = filter ## _init, \ - .parse_config = NULL, \ - .print_help = NULL \ -}, - -/* filters that are always present */ -DECLARE_EXTERN_FILTER_INIT(wav); -DECLARE_EXTERN_FILTER_INIT(compress); -DECLARE_EXTERN_FILTER_INIT(amp); - -/* next the optional filters */ -#ifdef HAVE_MAD -DECLARE_EXTERN_FILTER_INIT(mp3dec); -#define MP3DEC_FILTER FILTER_INIT(mp3dec) -#else -#define MP3DEC_FILTER -#endif - -#ifdef HAVE_FAAD -DECLARE_EXTERN_FILTER_INIT(aacdec); -#define AACDEC_FILTER FILTER_INIT(aacdec) -#else -#define AACDEC_FILTER -#endif - -#ifdef HAVE_OGGVORBIS -DECLARE_EXTERN_FILTER_INIT(oggdec); -#define OGGDEC_FILTER FILTER_INIT(oggdec) -#else -#define OGGDEC_FILTER -#endif -/** \endcond */ - -/** define an array of all available filters */ -#define DEFINE_FILTER_ARRAY(filters) struct filter filters[] = { \ - FILTER_INIT(wav) \ - FILTER_INIT(compress) \ - FILTER_INIT(amp) \ - MP3DEC_FILTER \ - AACDEC_FILTER \ - OGGDEC_FILTER \ - { .name = NULL } }; - +#define FOR_EACH_SUPPORTED_FILTER(j) for (j = 0; j < NUM_SUPPORTED_FILTERS; j++) +/** The filter array, one structure for each supported filter. */ +extern struct filter filters[NUM_SUPPORTED_FILTERS]; diff --git a/filter_chain.c b/filter_chain.c deleted file mode 100644 index 1a290247..00000000 --- a/filter_chain.c +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2005-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file filter_chain.c Common helper functions for filter input/output. */ - -#include -#include - -#include "para.h" -#include "list.h" -#include "sched.h" -#include "fd.h" -#include "filter.h" -#include "error.h" -#include "string.h" - -DEFINE_FILTER_ARRAY(filters); - -/** - * Call the init function of each supported filter. - * - * \param all_filters the array of all supported filters. - * - * \sa filter::init - */ -void filter_init(struct filter *all_filters) -{ - struct filter *f; - - for (f = all_filters; f->name; f++) - f->init(f); -} - -/** - * Close and destroy a filter callback. - * - * \param fcb The filter callback to close. - * - * This removes \a fcb from the list of filter callbacks and calls - * the close callback associated with \a fcb. - */ -static void close_filter_callback(struct filter_callback *fcb) -{ - PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data); - list_del(&fcb->node); - fcb->close(fcb); -} - -/** - * Close all callbacks of a filter node. - * - * \param fn The filter node which contains the filter callbacks to be closed. - * - * Call close_filter_callback() for each entry in the filter callback list - * of \a fn. - */ -static void close_callbacks(struct filter_node *fn) -{ - struct filter_callback *fcb, *tmp; - - list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { - PARA_INFO_LOG("closing %s filter callback\n", - filters[fn->filter_num].name); - close_filter_callback(fcb); - } -} - -static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen, - char *outbuf, size_t outlen) -{ - struct filter_callback *fcb, *tmp; - list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { - int ret; - if (inlen && fcb->input_cb) { - ret = fcb->input_cb(inbuf, inlen, fcb); - if (ret < 0) { - close_filter_callback(fcb); - continue; - } - } - if (!outlen || !fcb->output_cb) - continue; - ret = fcb->output_cb(outbuf, outlen, fcb); - if (ret < 0) - close_filter_callback(fcb); - } -} - -/** - * Call the convert function of each filter. - * - * \param s Unused. - * \param t The task identifying the filter chain. - * - * This is the core function of the filter subsystem. It loops over the list of - * filter nodes determined by \a t and calls the filter's convert function if - * there is input available for the filter node in question. If the convert - * function consumed some or all of its input data, all registered input - * callbacks are called. Similarly, if a convert function produced output, all - * registerd output callbacks get called. - * - * On errors a (negative) error code is stored in t->error. - * - * \sa filter_node, filter#convert, filter_callback. - */ -void filter_pre_select(__a_unused struct sched *s, struct task *t) -{ - struct filter_chain *fc = container_of(t, struct filter_chain, task); - struct filter_node *fn; - char *ib; - size_t *loaded; - int i, conv, conv_total = 0; - - if (fc->output_error && *fc->output_error < 0) { - t->error = *fc->output_error; - return; - } -again: - ib = fc->inbuf; - loaded = fc->in_loaded; - conv = 0; - FOR_EACH_FILTER_NODE(fn, fc, i) { - struct filter *f = filters + fn->filter_num; - if (*loaded && fn->loaded < fn->bufsize) { - size_t size, old_fn_loaded = fn->loaded; -// PARA_DEBUG_LOG("fc %p loaded: %zd, calling %s convert\n", -// fc, *loaded, fn->filter->name); - t->error = f->convert(ib, *loaded, fn); - if (t->error < 0) - return; - size = t->error; - call_callbacks(fn, ib, size, fn->buf + old_fn_loaded, - fn->loaded - old_fn_loaded); - *loaded -= size; - conv += size; - if (*loaded && size) { -// PARA_DEBUG_LOG("moving %zd bytes in input " -// "buffer for %s filter\n", -// *loaded, fn->filter->name); - memmove(ib, ib + size, *loaded); - } - } - ib = fn->buf; - loaded = &fn->loaded; - } - conv_total += conv; -// PARA_DEBUG_LOG("eof (in/out/fc): %d/%d/%d out_loaded: %zd, " -// "conv: %d, conv_total: %d\n", *fc->input_eof, -// fc->output_eof? *fc->output_eof : -42, -// fc->eof, *fc->out_loaded, conv, conv_total); - if (conv) - goto again; - if (!*fc->input_error) - return; - if (*fc->out_loaded) - return; - if (*fc->in_loaded && conv_total) - return; - t->error = -E_FC_EOF; -} - -/** - * Close all filter nodes and their callbacks. - * - * \param fc The filter chain to close. - * - * For each filter node determined by \a fc, call the close function of each - * registered filter callback as well as the close function of the - * corresponding filter. Free all resources and destroy all callback lists and - * the filter node list. - * - * \sa filter::close, filter_callback::close - */ -void close_filters(struct filter_chain *fc) -{ - struct filter_node *fn; - int i; - - if (!fc) - return; - PARA_NOTICE_LOG("closing filter chain %p\n", fc); - FOR_EACH_FILTER_NODE(fn, fc, i) { - struct filter *f = filters + fn->filter_num; - close_callbacks(fn); - PARA_INFO_LOG("closing %s filter\n", f->name); - f->close(fn); - } - free(fc->filter_nodes); -} - -/* - * If the filter has a command line parser and options is not NULL, run it. - * Returns filter_num on success, negative on errors - */ -static int parse_filter_args(int filter_num, char *options, void **conf) -{ - struct filter *f = &filters[filter_num]; - int i, argc = 2; - char **argv; - -// PARA_DEBUG_LOG("%s, options: %s, parser: %p\n", f->name, -// options? options : "(none)", f->parse_config); - if (!f->parse_config) - return strlen(options)? -E_BAD_FILTER_OPTIONS : filter_num; -// PARA_DEBUG_LOG("options: %s\n", options); - argc = split_args(options, &argv, " \t"); -// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]); - for (i = argc - 1; i >= 0; i--) - argv[i + 1] = argv[i]; - argv[0] = para_strdup(f->name); - argc += 1; - *conf = f->parse_config(argc, argv); - free(argv[0]); - free(argv); - return *conf? filter_num : -E_BAD_FILTER_OPTIONS; -} - -/** - * Check the filter command line options. - * - * \param fa The command line options. - * \param conf Points to the filter configuration upon successful return. - * - * Check if \a fa starts with a the name of a supported filter, followed by - * a colon. If yes, call the command line parser of that filter. - * - * \return On success, the number of the filter is returned and \a conf - * is initialized to point to the filter configuration determined by \a fa. - * On errors, a negative value is returned. - * - * Note: If \a fa specifies a filter that has no command line parser success is - * returned, and \a conf is initialized to \p NULL. - * - * \sa filter::parse_config - */ -int check_filter_arg(char *fa, void **conf) -{ - int j; - - *conf = NULL; -// PARA_DEBUG_LOG("arg: %s\n", fa); - for (j = 0; filters[j].name; j++) { - const char *name = filters[j].name; - size_t len = strlen(name); - char c; - if (strlen(fa) < len) - continue; - if (strncmp(name, fa, len)) - continue; - c = fa[len]; - if (c && c != ' ') - continue; - if (c && !filters[j].parse_config) - return -E_BAD_FILTER_OPTIONS; - return parse_filter_args(j, c? fa + len + 1 : - fa + strlen(fa), conf); - } - return -E_UNSUPPORTED_FILTER; -} - diff --git a/filter_common.c b/filter_common.c new file mode 100644 index 00000000..a39eefd6 --- /dev/null +++ b/filter_common.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2005-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file filter_common.c Common helper functions for filter input/output. */ + +#include +#include + +#include "para.h" +#include "list.h" +#include "sched.h" +#include "fd.h" +#include "ggo.h" +#include "filter.h" +#include "error.h" +#include "string.h" + +/** The array of supported filters. */ +struct filter filters[NUM_SUPPORTED_FILTERS] = {FILTER_ARRAY}; + +/** + * Call the init function of each supported filter. + * + * \param all_filters the array of all supported filters. + * + * \sa filter::init + */ +void filter_init(struct filter *all_filters) +{ + int i; + + FOR_EACH_SUPPORTED_FILTER(i) + all_filters[i].init(all_filters + i); +} + +/** + * Close and destroy a filter callback. + * + * \param fcb The filter callback to close. + * + * This removes \a fcb from the list of filter callbacks and calls + * the close callback associated with \a fcb. + */ +static void close_filter_callback(struct filter_callback *fcb) +{ + PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data); + list_del(&fcb->node); + fcb->close(fcb); +} + +/** + * Close all callbacks of a filter node. + * + * \param fn The filter node which contains the filter callbacks to be closed. + * + * Call close_filter_callback() for each entry in the filter callback list + * of \a fn. + */ +static void close_callbacks(struct filter_node *fn) +{ + struct filter_callback *fcb, *tmp; + + list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { + PARA_INFO_LOG("closing %s filter callback\n", + filters[fn->filter_num].name); + close_filter_callback(fcb); + } +} + +static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen, + char *outbuf, size_t outlen) +{ + struct filter_callback *fcb, *tmp; + list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { + int ret; + if (inlen && fcb->input_cb) { + ret = fcb->input_cb(inbuf, inlen, fcb); + if (ret < 0) { + close_filter_callback(fcb); + continue; + } + } + if (!outlen || !fcb->output_cb) + continue; + ret = fcb->output_cb(outbuf, outlen, fcb); + if (ret < 0) + close_filter_callback(fcb); + } +} + +/** + * Call the convert function of each filter. + * + * \param s Unused. + * \param t The task identifying the filter chain. + * + * This is the core function of the filter subsystem. It loops over the list of + * filter nodes determined by \a t and calls the filter's convert function if + * there is input available for the filter node in question. If the convert + * function consumed some or all of its input data, all registered input + * callbacks are called. Similarly, if a convert function produced output, all + * registerd output callbacks get called. + * + * On errors a (negative) error code is stored in t->error. + * + * \sa filter_node, filter#convert, filter_callback. + */ +void filter_pre_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_chain *fc = container_of(t, struct filter_chain, task); + struct filter_node *fn; + char *ib; + size_t *loaded; + int i, conv, conv_total = 0; + + if (fc->output_error && *fc->output_error < 0) { + t->error = *fc->output_error; + return; + } +again: + ib = fc->inbuf; + loaded = fc->in_loaded; + conv = 0; + FOR_EACH_FILTER_NODE(fn, fc, i) { + struct filter *f = filters + fn->filter_num; + if (fn->loaded < fn->bufsize) { + size_t size, old_fn_loaded = fn->loaded; +// PARA_DEBUG_LOG("fc %p loaded: %zd, calling %s convert\n", +// fc, *loaded, fn->filter->name); + t->error = f->convert(ib, *loaded, fn); + if (t->error < 0) + return; + size = t->error; + call_callbacks(fn, ib, size, fn->buf + old_fn_loaded, + fn->loaded - old_fn_loaded); + *loaded -= size; + conv += size; + if (*loaded && size) { +// PARA_DEBUG_LOG("moving %zd bytes in input " +// "buffer for %s filter\n", +// *loaded, fn->filter->name); + memmove(ib, ib + size, *loaded); + } + } + ib = fn->buf; + loaded = &fn->loaded; + } + conv_total += conv; +// PARA_DEBUG_LOG("eof (in/out/fc): %d/%d/%d out_loaded: %zd, " +// "conv: %d, conv_total: %d\n", *fc->input_eof, +// fc->output_eof? *fc->output_eof : -42, +// fc->eof, *fc->out_loaded, conv, conv_total); + if (conv) + goto again; + if (!*fc->input_error) + return; + if (*fc->out_loaded) + return; + if (*fc->in_loaded && conv_total) + return; + t->error = -E_FC_EOF; +} + +/** + * Close all filter nodes and their callbacks. + * + * \param fc The filter chain to close. + * + * For each filter node determined by \a fc, call the close function of each + * registered filter callback as well as the close function of the + * corresponding filter. Free all resources and destroy all callback lists and + * the filter node list. + * + * \sa filter::close, filter_callback::close + */ +void close_filters(struct filter_chain *fc) +{ + struct filter_node *fn; + int i; + + if (!fc) + return; + PARA_NOTICE_LOG("closing filter chain %p\n", fc); + FOR_EACH_FILTER_NODE(fn, fc, i) { + struct filter *f = filters + fn->filter_num; + close_callbacks(fn); + PARA_INFO_LOG("closing %s filter\n", f->name); + f->close(fn); + } + free(fc->filter_nodes); +} + +/* + * If the filter has a command line parser and options is not NULL, run it. + * Returns filter_num on success, negative on errors + */ +static int parse_filter_args(int filter_num, char *options, void **conf) +{ + struct filter *f = &filters[filter_num]; + int ret, i, argc = 2; + char **argv; + +// PARA_DEBUG_LOG("%s, options: %s, parser: %p\n", f->name, +// options? options : "(none)", f->parse_config); + if (!f->parse_config) + return strlen(options)? -E_BAD_FILTER_OPTIONS : filter_num; +// PARA_DEBUG_LOG("options: %s\n", options); + argc = split_args(options, &argv, " \t"); +// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]); + for (i = argc - 1; i >= 0; i--) + argv[i + 1] = argv[i]; + argv[0] = para_strdup(f->name); + argc += 1; + ret = f->parse_config(argc, argv, conf); + free(argv[0]); + free(argv); + return ret < 0? ret : filter_num; +} + +/** + * Check the filter command line options. + * + * \param fa The command line options. + * \param conf Points to the filter configuration upon successful return. + * + * Check if \a fa starts with a the name of a supported filter, followed by + * a colon. If yes, call the command line parser of that filter. + * + * \return On success, the number of the filter is returned and \a conf + * is initialized to point to the filter configuration determined by \a fa. + * On errors, a negative value is returned. + * + * Note: If \a fa specifies a filter that has no command line parser success is + * returned, and \a conf is initialized to \p NULL. + * + * \sa filter::parse_config + */ +int check_filter_arg(char *fa, void **conf) +{ + int j; + + *conf = NULL; +// PARA_DEBUG_LOG("arg: %s\n", fa); + FOR_EACH_SUPPORTED_FILTER(j) { + const char *name = filters[j].name; + size_t len = strlen(name); + char c; + if (strlen(fa) < len) + continue; + if (strncmp(name, fa, len)) + continue; + c = fa[len]; + if (c && c != ' ') + continue; + if (c && !filters[j].parse_config) + return -E_BAD_FILTER_OPTIONS; + return parse_filter_args(j, c? fa + len + 1 : + fa + strlen(fa), conf); + } + return -E_UNSUPPORTED_FILTER; +} + +void print_filter_helps(int detailed) +{ + int i; + + printf_or_die("\nAvailable filters: \n\t"); + FOR_EACH_SUPPORTED_FILTER(i) + printf_or_die("%s%s", i? " " : "", filters[i].name); + printf_or_die("\n\n"); + + FOR_EACH_SUPPORTED_FILTER(i) { + struct filter *f = filters + i; + + if (!f->help.short_help) + continue; + printf_or_die("Options for %s:\n", f->name); + ggo_print_help(&f->help, detailed); + } + +} diff --git a/ggo.c b/ggo.c new file mode 100644 index 00000000..55f10430 --- /dev/null +++ b/ggo.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file ggo.c Function for printing help. */ + + +#include "para.h" +#include "ggo.h" + +__printf_1_2 void printf_or_die(const char *fmt, ...) +{ + va_list argp; + int ret; + + va_start(argp, fmt); + ret = vprintf(fmt, argp); + va_end(argp); + if (ret >= 0) + return; + fprintf(stderr, "%s: %s\n", __FUNCTION__, strerror(errno)); +} + +void ggo_print_help(struct ggo_help *help, int detailed_help) +{ + const char **p; + + if (!help) + return; + if (detailed_help) + p = help->detailed_help; + else + p = help->short_help; + if (!p) + return; + p += 3; /* skip -h and -V */ + for (; *p; p++) + printf_or_die("\t%s\n", *p); +} diff --git a/ggo.h b/ggo.h new file mode 100644 index 00000000..c9111b16 --- /dev/null +++ b/ggo.h @@ -0,0 +1,7 @@ +struct ggo_help { + const char **short_help; + const char **detailed_help; +}; + +void ggo_print_help(struct ggo_help *help, int detailed_help); +void printf_or_die(const char *fmt, ...); diff --git a/grab_client.c b/grab_client.c index 77d3616d..b9d70147 100644 --- a/grab_client.c +++ b/grab_client.c @@ -18,6 +18,7 @@ #include "grab_client.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "filter.h" #include "grab_client.h" #include "audiod.h" diff --git a/gui.c b/gui.c index 9a02bdca..61689029 100644 --- a/gui.c +++ b/gui.c @@ -489,7 +489,7 @@ static int add_output_line(char *line, __a_unused void *data) return 1; } -void para_log(int ll, const char *fmt,...) +__printf_2_3 void para_log(int ll, const char *fmt,...) { int color; char *msg; @@ -523,7 +523,7 @@ static void setup_signal_handling(void) signal(SIGHUP, SIG_IGN); } -static void do_exit(int ret) +__noreturn static void do_exit(int ret) { signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); @@ -539,7 +539,7 @@ static void shutdown_curses(void) endwin(); } -static void finish(int ret) +__noreturn static void finish(int ret) { shutdown_curses(); do_exit(ret); @@ -548,7 +548,7 @@ static void finish(int ret) /* * exit curses and print given message to stdout/stderr */ -__printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...) +__noreturn __printf_2_3 static void msg_n_exit(int ret, const char* fmt, ...) { va_list argp; FILE *outfd = ret? stderr: stdout; @@ -1280,7 +1280,7 @@ static void com_version(void) CODENAME "\""); } -static void com_quit(void) +__noreturn static void com_quit(void) { finish(0); } diff --git a/gui_theme.c b/gui_theme.c index 78f7aa07..ce79ca63 100644 --- a/gui_theme.c +++ b/gui_theme.c @@ -261,16 +261,25 @@ static void init_theme_colorful_blackness(struct gui_theme *t) d[SI_NUM_CHUNKS].align = RIGHT; d[SI_NUM_CHUNKS].x = 73; d[SI_NUM_CHUNKS].y = 27; - d[SI_NUM_CHUNKS].len = 12; + d[SI_NUM_CHUNKS].len = 11; - d[SI_CHUNK_TIME].prefix = " "; + d[SI_CHUNK_TIME].prefix = ""; d[SI_CHUNK_TIME].postfix = "ms"; d[SI_CHUNK_TIME].fg = COLOR_MAGENTA; d[SI_CHUNK_TIME].bg = COLOR_BLACK; d[SI_CHUNK_TIME].align = LEFT; - d[SI_CHUNK_TIME].x = 85; + d[SI_CHUNK_TIME].x = 84; d[SI_CHUNK_TIME].y = 27; - d[SI_CHUNK_TIME].len = 15; + d[SI_CHUNK_TIME].len = 8; + + d[SI_AMPLIFICATION].prefix = "amp:"; + d[SI_AMPLIFICATION].postfix = ""; + d[SI_AMPLIFICATION].fg = COLOR_MAGENTA; + d[SI_AMPLIFICATION].bg = COLOR_BLACK; + d[SI_AMPLIFICATION].align = RIGHT; + d[SI_AMPLIFICATION].x = 92; + d[SI_AMPLIFICATION].y = 27; + d[SI_AMPLIFICATION].len = 8; d[SI_AUDIO_FILE_INFO].prefix = ""; d[SI_AUDIO_FILE_INFO].postfix = ""; diff --git a/hash.h b/hash.h index 03b45e04..2d6de8ab 100644 --- a/hash.h +++ b/hash.h @@ -6,8 +6,6 @@ /** \file hash.h Inline functions for hash values. */ -#include "portable_io.h" - /** hash arrays are always unsigned char. */ #define HASH_TYPE unsigned char diff --git a/http_recv.c b/http_recv.c index 2e518b20..7716bb0f 100644 --- a/http_recv.c +++ b/http_recv.c @@ -14,6 +14,7 @@ #include "http.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "http_recv.cmdline.h" #include "net.h" @@ -119,7 +120,7 @@ static void http_recv_post_select(struct sched *s, struct task *t) if (!FD_ISSET(phd->fd, &s->rfds)) return; if (phd->status == HTTP_SENT_GET_REQUEST) { - t->error = recv_pattern(phd->fd, HTTP_OK_MSG, MAXLINE); + t->error = recv_pattern(phd->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG)); if (t->error >= 0) { PARA_INFO_LOG("received ok msg, streaming\n"); phd->status = HTTP_STREAMING; @@ -189,10 +190,17 @@ static int http_recv_open(struct receiver_node *rn) */ void http_recv_init(struct receiver *r) { + struct http_recv_args_info dummy; + + http_recv_cmdline_parser_init(&dummy); r->open = http_recv_open; r->close = http_recv_close; r->pre_select = http_recv_pre_select; r->post_select = http_recv_post_select; r->shutdown = http_shutdown; r->parse_config = http_recv_parse_config; + r->help = (struct ggo_help) { + .short_help = http_recv_args_info_help, + .detailed_help = http_recv_args_info_detailed_help + }; } diff --git a/http_recv.ggo b/http_recv.ggo index c368efd7..2f1b4d61 100644 --- a/http_recv.ggo +++ b/http_recv.ggo @@ -1,3 +1,15 @@ -section "options of the http receiver" -option "host" i "ip or host" string default="localhost" optional -option "port" p "tcp port to connect to" int default="8000" optional +option "host" i +#~~~~~~~~~~~~~~ +"ip or host" +string +default="localhost" +optional +details=" + Both IPv4 and IPv6 addresses are supported. +" + +option "port" p +#~~~~~~~~~~~~~~ +"tcp port to connect to" +int default="8000" +optional diff --git a/mood.c b/mood.c index aa220f1e..cdc0745a 100644 --- a/mood.c +++ b/mood.c @@ -84,7 +84,7 @@ typedef void mood_cleanup_function(void *); * Used for scoring and to determine whether a file is admissible. */ struct mood_method { - /* The name of the method. */ + /** The name of the method. */ const char *name; /** Pointer to the mood parser. */ mood_parser *parser; diff --git a/mp3_afh.c b/mp3_afh.c index ca644160..b809e8b3 100644 --- a/mp3_afh.c +++ b/mp3_afh.c @@ -280,7 +280,7 @@ static int frame_length(struct mp3header *header) static int compare_headers(struct mp3header *h1,struct mp3header *h2) { - if ((*(uint*)h1) == (*(uint*)h2)) + if ((*(unsigned int*)h1) == (*(unsigned int*)h2)) return 1; if ((h1->version == h2->version) && (h1->layer == h2->layer) && diff --git a/mp3dec.c b/mp3dec.c deleted file mode 100644 index 763ca37f..00000000 --- a/mp3dec.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2005-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file mp3dec.c Paraslash's mp3 decoder. */ - -#include "para.h" -#include "list.h" -#include "sched.h" -#include "filter.h" -#include "error.h" -#include -#include "string.h" - -/** The output buffer size. */ -#define MP3_OUTBUF_SIZE (128 * 1024) - -/** Convert a sample value from libmad to a signed short. */ -#define MAD_TO_SHORT(f) (f) >= MAD_F_ONE? SHRT_MAX :\ - (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15)) - -/** Data specific to the mp3dec filter. */ -struct private_mp3dec_data { - /** Information on the current mp3 stream. */ - struct mad_stream stream; - /** Information about the frame which is currently decoded. */ - struct mad_frame frame; - /** Contains the PCM output. */ - struct mad_synth synth; -}; - -static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn) -{ - int i, ret; - struct private_mp3dec_data *pmd = fn->private_data; - size_t copy = PARA_MIN(len, 4096); - - if (fn->loaded > fn->bufsize * 4 / 5) - return 0; - mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy); - pmd->stream.error = 0; -next_frame: - ret = mad_header_decode(&pmd->frame.header, &pmd->stream); - if (ret < 0) - goto out; - fn->fc->samplerate = pmd->frame.header.samplerate; - fn->fc->channels = MAD_NCHANNELS(&pmd->frame.header); - ret = mad_frame_decode(&pmd->frame, &pmd->stream); - if (ret) { - if (MAD_RECOVERABLE(pmd->stream.error) || pmd->stream.error == MAD_ERROR_BUFLEN) - goto out; - PARA_ERROR_LOG("fatal: ret = %d, loaded = %zd\n", ret, fn->loaded); - return -E_MAD_FRAME_DECODE; - } - mad_synth_frame(&pmd->synth, &pmd->frame); - - for (i = 0; i < pmd->synth.pcm.length; i++) { - int s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); - write_int16_host_endian(fn->buf + fn->loaded, s); - fn->loaded += 2; - if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */ - s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); - write_int16_host_endian(fn->buf + fn->loaded, s); - fn->loaded += 2; - } - if (fn->loaded != fn->bufsize) /* output buffer not full */ - continue; - PARA_ERROR_LOG("output buffer full: %zd\n", fn->loaded); - return -E_MP3DEC_OVERRUN; - } - if (fn->loaded <= fn->bufsize * 4 / 5) - goto next_frame; -out: - if (pmd->stream.next_frame) { /* we still have some data */ - size_t off = pmd->stream.bufend - pmd->stream.next_frame; -// PARA_INFO_LOG("off: %zd, rate: %u, returning %zd\n", off, -// fn->fc->samplerate, copy - off); - return copy - off; - } - return copy; -} - -static void mp3dec_close(struct filter_node *fn) -{ - struct private_mp3dec_data *pmd = fn->private_data; - - mad_synth_finish(&pmd->synth); - mad_frame_finish(&pmd->frame); - mad_stream_finish(&pmd->stream); - - free(fn->buf); - fn->buf = NULL; - free(pmd); - fn->private_data = NULL; -} - -static void mp3dec_open(struct filter_node *fn) -{ - fn->private_data = para_calloc(sizeof(struct private_mp3dec_data)); - struct private_mp3dec_data *pmd = fn->private_data; - - mad_stream_init(&pmd->stream); - mad_frame_init(&pmd->frame); - mad_synth_init(&pmd->synth); - fn->loaded = 0; - fn->bufsize = MP3_OUTBUF_SIZE; - fn->buf = para_calloc(fn->bufsize); -} - -/** - * The init function of the mp3dec filter. - * - * \param f Pointer to the filter struct to initialize. - * - * \sa filter::init. - */ -void mp3dec_init(struct filter *f) -{ - f->open = mp3dec_open; - f->convert = mp3dec; - f->close = mp3dec_close; -} diff --git a/mp3dec_filter.c b/mp3dec_filter.c new file mode 100644 index 00000000..ce6cfbcb --- /dev/null +++ b/mp3dec_filter.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file mp3dec_filter.c Paraslash's mp3 decoder. */ + +#include "para.h" +#include "mp3dec_filter.cmdline.h" +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "filter.h" +#include "error.h" +#include +#include "string.h" + +/** Convert a sample value from libmad to a signed short. */ +#define MAD_TO_SHORT(f) (f) >= MAD_F_ONE? SHRT_MAX :\ + (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15)) + +/** Data specific to the mp3dec filter. */ +struct private_mp3dec_data { + /** Information on the current mp3 stream. */ + struct mad_stream stream; + /** Information about the frame which is currently decoded. */ + struct mad_frame frame; + /** Contains the PCM output. */ + struct mad_synth synth; +}; + +static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn) +{ + int i, ret; + struct private_mp3dec_data *pmd = fn->private_data; + size_t copy = PARA_MIN(len, (size_t)4096); + + if (fn->loaded + 16384 > fn->bufsize) + return 0; + mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy); + pmd->stream.error = 0; +next_frame: + ret = mad_header_decode(&pmd->frame.header, &pmd->stream); + if (ret < 0) { + if (pmd->stream.error != MAD_ERROR_BUFLEN && + pmd->stream.error != MAD_ERROR_LOSTSYNC) + PARA_DEBUG_LOG("header decode: %s\n", + mad_stream_errorstr(&pmd->stream)); + goto out; + } + fn->fc->samplerate = pmd->frame.header.samplerate; + fn->fc->channels = MAD_NCHANNELS(&pmd->frame.header); + ret = mad_frame_decode(&pmd->frame, &pmd->stream); + if (ret) { + if (MAD_RECOVERABLE(pmd->stream.error) || + pmd->stream.error == MAD_ERROR_BUFLEN) { + PARA_DEBUG_LOG("frame decode: %s\n", + mad_stream_errorstr(&pmd->stream)); + goto out; + } + PARA_ERROR_LOG("frame decode: %s\n", + mad_stream_errorstr(&pmd->stream)); + return -E_MAD_FRAME_DECODE; + } + mad_synth_frame(&pmd->synth, &pmd->frame); + + for (i = 0; i < pmd->synth.pcm.length; i++) { + int s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); + write_int16_host_endian(fn->buf + fn->loaded, s); + fn->loaded += 2; + if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */ + s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); + write_int16_host_endian(fn->buf + fn->loaded, s); + fn->loaded += 2; + } + if (fn->loaded != fn->bufsize) /* output buffer not full */ + continue; + PARA_ERROR_LOG("output buffer full: %zd\n", fn->loaded); + return -E_MP3DEC_OVERRUN; + } + if (fn->loaded + 16384 <= fn->bufsize) + goto next_frame; +out: + if (pmd->stream.next_frame) { /* we still have some data */ + size_t off = pmd->stream.bufend - pmd->stream.next_frame; +// PARA_INFO_LOG("off: %zd, rate: %u, returning %zd\n", off, +// fn->fc->samplerate, copy - off); + return copy - off; + } + return copy; +} + +static void mp3dec_close(struct filter_node *fn) +{ + struct private_mp3dec_data *pmd = fn->private_data; + + mad_synth_finish(&pmd->synth); + mad_frame_finish(&pmd->frame); + mad_stream_finish(&pmd->stream); + + free(fn->buf); + fn->buf = NULL; + free(pmd); + fn->private_data = NULL; +} + +static void mp3dec_open(struct filter_node *fn) +{ + struct private_mp3dec_data *pmd = para_calloc(sizeof(*pmd)); + struct mp3dec_filter_args_info *mp3_conf = fn->conf; + + fn->private_data = pmd; + mad_stream_init(&pmd->stream); + mad_frame_init(&pmd->frame); + mad_synth_init(&pmd->synth); + fn->loaded = 0; + fn->bufsize = mp3_conf->bufsize_arg * 1024; + fn->buf = para_calloc(fn->bufsize); + if (mp3_conf->ignore_crc_given) + mad_stream_options(&pmd->stream, MAD_OPTION_IGNORECRC); +} + +static int mp3dec_parse_config(int argc, char **argv, void **config) +{ + int ret; + struct mp3dec_filter_args_info *mp3_conf; + + mp3_conf = para_calloc(sizeof(*mp3_conf)); + ret = -E_MP3DEC_SYNTAX; + if (mp3dec_cmdline_parser(argc, argv, mp3_conf)) + goto err; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (mp3_conf->bufsize_arg < 32) + goto err; + if (mp3_conf->bufsize_arg >= INT_MAX / 1024) + goto err; + *config = mp3_conf; + return 1; +err: + free(mp3_conf); + return ret; +} + +/** + * The init function of the mp3dec filter. + * + * \param f Pointer to the filter struct to initialize. + * + * \sa filter::init. + */ +void mp3dec_filter_init(struct filter *f) +{ + struct mp3dec_filter_args_info dummy; + + mp3dec_cmdline_parser_init(&dummy); + f->open = mp3dec_open; + f->convert = mp3dec; + f->close = mp3dec_close; + f->parse_config = mp3dec_parse_config; + f->help = (struct ggo_help) { + .short_help = mp3dec_filter_args_info_help, + .detailed_help = mp3dec_filter_args_info_detailed_help + }; +} diff --git a/mp3dec_filter.ggo b/mp3dec_filter.ggo new file mode 100644 index 00000000..a34c30ae --- /dev/null +++ b/mp3dec_filter.ggo @@ -0,0 +1,22 @@ +option "bufsize" b +#~~~~~~~~~~~~~~~~~ +"size of output buffer" +int typestr="kilobyte" +default="128" +optional +details=" + Increase this if you encounter output buffer overrun + errors. Smaller values make the mp3dec filter use less + memory. The minimal size is 32K. +" + +option "ignore-crc" i +#~~~~~~~~~~~~~~~~~~~~ +"ignore CRC information in the audio stream." +flag off +details=" + This causes frames with CRC errors to be decoded and played + anyway. This option is not recommended, but since some encoders + have been known to generate bad CRC information, this option + is a work-around to play streams from such encoders. +" diff --git a/net.c b/net.c index 60f8e9a1..9309ac1f 100644 --- a/net.c +++ b/net.c @@ -728,10 +728,9 @@ int recv_cred_buffer(int fd, char *buf, size_t size) * * \return Positive if \a pattern was received, negative otherwise. * - * This function creates a buffer of size \a bufsize and tries - * to receive at most \a bufsize bytes from file descriptor \a fd. - * If at least \p strlen(\a pattern) bytes were received, the beginning of - * the received buffer is compared with \a pattern, ignoring case. + * This function tries to receive at most \a bufsize bytes from file descriptor + * \a fd. If at least \p strlen(\a pattern) bytes were received, the beginning + * of the received buffer is compared with \a pattern, ignoring case. * * \sa recv_buffer(), \sa strncasecmp(3). */ @@ -739,7 +738,7 @@ int recv_pattern(int fd, const char *pattern, size_t bufsize) { size_t len = strlen(pattern); char *buf = para_malloc(bufsize + 1); - int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize); + int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize + 1); if (n < len) goto out; diff --git a/ogg_afh.c b/ogg_afh.c index 11f7f5ad..2030eada 100644 --- a/ogg_afh.c +++ b/ogg_afh.c @@ -34,8 +34,15 @@ struct ogg_datasource { static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource) { struct ogg_datasource *ods = datasource; - size_t copy = PARA_MIN(ods->numbytes - ods->fpos, size * nmemb), - ret = copy / size; + size_t copy, ret; + + if (!size) + return 0; + + assert(ods->numbytes >= ods->fpos); + ret = ods->numbytes - ods->fpos; + copy = PARA_MIN(ret, size * nmemb); + ret = copy / size; if (!ret) return 0; memcpy(buf, ods->map + ods->fpos, copy); @@ -115,7 +122,7 @@ static int ogg_compute_header_len(char *map, size_t numbytes, struct afh_info *afhi) { int ret; - size_t len = PARA_MIN(numbytes, CHUNK_SIZE); + size_t len = PARA_MIN(numbytes, (size_t)CHUNK_SIZE); int serial; char *buf; @@ -200,37 +207,30 @@ static long unsigned ogg_compute_chunk_table(OggVorbis_File *of, struct afh_info *afhi, long unsigned time_total) { int i, ret, num; - ssize_t max_chunk_len, pos = 0, min = 0, old_pos; long unsigned num_chunks; + ogg_int64_t max = 0, min = 0, old_pos = 0; - old_pos = 0; ret = 0; num = time_total / chunk_time + 3; PARA_DEBUG_LOG("chunk time: %g allocating %d chunk pointers\n", chunk_time, num); afhi->chunk_table = para_malloc((num + 1) * sizeof(size_t)); afhi->chunk_table[0] = 0; - max_chunk_len = 0; - for (i = 1; ret <= num; i++) { - ogg_int64_t diff; + for (i = 1; i <= num; i++) { + ogg_int64_t diff, pos; ret = ov_time_seek(of, i * chunk_time); if (ret) break; pos = ov_raw_tell(of); diff = pos - old_pos; - max_chunk_len = PARA_MAX(max_chunk_len, diff); + max = PARA_MAX(max, diff); min = (i == 1)? diff : PARA_MIN(min, diff); afhi->chunk_table[i] = pos; -// if (i < 11 || !((i - 1) % 1000)|| i > num - 11) -// PARA_DEBUG_LOG("chunk #%d: %g secs, pos: %zd, " -// "size: %zd\n", i - 1, -// i * chunk_time, pos, pos - old_pos); old_pos = pos; } num_chunks = i - 1; -//fi->chunk_table[i] = pos; - PARA_DEBUG_LOG("%lu chunks (%fs), max chunk: %zd, min chunk: %zd\n", - num_chunks, chunk_time, max_chunk_len, min); + PARA_DEBUG_LOG("%lu chunks (%fs), max chunk: %lld, min chunk: %lld\n", + num_chunks, chunk_time, (long long)max, (long long)min); return num_chunks; } diff --git a/oggdec.c b/oggdec.c deleted file mode 100644 index b82f12ef..00000000 --- a/oggdec.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2005-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file oggdec.c Paraslash's ogg vorbis decoder. */ - -#include "para.h" - -#include "oggdec_filter.cmdline.h" -#include "list.h" -#include "sched.h" -#include "filter.h" -#include "error.h" -#include "string.h" - -#include - -/** Determine byte sex. */ -#ifdef WORDS_BIGENDIAN -#define ENDIAN 1 -#else -#define ENDIAN 0 -#endif - -/** Data specific to the oggdec filter. */ -struct private_oggdec_data { - /** Describes an ogg vorbis file. */ - OggVorbis_File *vf; - /** The input buffer. */ - char *inbuf; - /** The length of \a inbuf. */ - size_t inbuf_len; - /** The number of bytes consumed from the input buffer. */ - size_t converted; -}; - -static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource) -{ - struct filter_node *fn = datasource; - struct private_oggdec_data *pod = fn->private_data; - size_t ret, have = pod->inbuf_len - pod->converted; - char *p = pod->inbuf + pod->converted; - -// PARA_DEBUG_LOG("pod = %p\n", pod); -// PARA_DEBUG_LOG("vorbis requests %d bytes, have %d\n", size * nmemb, have); - if (pod->inbuf_len < size) { - if (*fn->fc->input_error) - return 0; - errno = EAGAIN; - return (size_t)-1; - } - ret = PARA_MIN(nmemb, have / size) * size; - memcpy(buf, p, ret); - pod->converted += ret; - return ret; -} - -/* - * Custom data seeking function. - * - * Since we want the data source to be treated as unseekable at all - * times, the provided seek callback always returns -1 (failure). - */ -static int cb_seek(__a_unused void *datasource, __a_unused ogg_int64_t offset, - __a_unused int whence) -{ - return -1; -} - -static int cb_close(__a_unused void *datasource) -{ - return 0; -} - -static const ov_callbacks ovc = { - .read_func = cb_read, - .seek_func = cb_seek, - .close_func = cb_close, - /* - * The tell function need not be provided if the data IO abstraction is - * not seekable - */ - .tell_func = NULL -}; - -static void ogg_open(struct filter_node *fn) -{ - struct private_oggdec_data *pod = para_calloc( - sizeof(struct private_oggdec_data)); - struct oggdec_filter_args_info *conf = fn->conf; - - fn->private_data = pod; - fn->bufsize = conf->bufsize_arg * 1024; - fn->buf = para_malloc(fn->bufsize); -} - -static void ogg_close(struct filter_node *fn) -{ - struct private_oggdec_data *pod = fn->private_data; - if (pod->vf) { - PARA_DEBUG_LOG("ov_clearing %p, pod = %p\n", pod->vf, pod); - ov_clear(pod->vf); - free(pod->vf); - pod->vf = NULL; - } else - PARA_DEBUG_LOG("nothing to close in fc %p, pod = %p\n", pod->vf, pod); - free(fn->buf); - fn->buf = NULL; - free(fn->private_data); - fn->private_data = NULL; -} - -static ssize_t ogg_convert(char *inbuffer, size_t len, struct filter_node *fn) -{ - ssize_t ret; - struct private_oggdec_data *pod = fn->private_data; - struct oggdec_filter_args_info *conf = fn->conf; - /* make the buffer known to the read callback cb_read() */ - pod->inbuf = inbuffer; - pod->inbuf_len = len; - pod->converted = 0; - - if (!pod->vf) { - int ib = 1024 * conf->initial_buffer_arg; /* initial buffer */ - if (len fc->input_error) { - PARA_DEBUG_LOG("initial input buffer %zd/%d, " - "waiting for more data\n", len, ib); - return 0; - } - pod->vf = para_malloc(sizeof(struct OggVorbis_File)); - PARA_NOTICE_LOG("input buffer: %zd, opening ov callbacks\n", len); - ret = ov_open_callbacks(fn, pod->vf, - NULL, /* no initial buffer */ - 0, /* no initial bytes */ - ovc); /* the ov_open_callbacks */ - if (ret == OV_EREAD) - return -E_OGGDEC_READ; - if (ret == OV_ENOTVORBIS) - return -E_OGGDEC_NOTVORBIS; - if (ret == OV_EVERSION) - return -E_OGGDEC_VERSION; - if (ret == OV_EBADHEADER) - return -E_OGGDEC_BADHEADER; - if (ret < 0) - return -E_OGGDEC_FAULT; - fn->fc->channels = ov_info(pod->vf, 0)->channels; - fn->fc->samplerate = ov_info(pod->vf, 0)->rate; - PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fc->channels, - fn->fc->samplerate); - } - while (!*fn->fc->input_error && fn->loaded < fn->bufsize) { - int length = fn->bufsize - fn->loaded; - long read_ret = ov_read(pod->vf, fn->buf + fn->loaded, length, - ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL); - if (read_ret == OV_HOLE || !read_ret) - return pod->converted; - if (read_ret < 0) - return -E_OGGDEC_BADLINK; - fn->loaded += read_ret; - } - return pod->converted; -} - -static void *oggdec_parse_config(int argc, char **argv) -{ - struct oggdec_filter_args_info *ret = para_calloc(sizeof(struct oggdec_filter_args_info)); - if (!oggdec_cmdline_parser(argc, argv, ret)) - return ret; - free(ret); - return NULL; -} - -/** - * The init function of the ogg vorbis decoder. - * - * \param f Its fields are filled in by the function. - */ -void oggdec_init(struct filter *f) -{ - f->open = ogg_open; - f->close = ogg_close; - f->convert = ogg_convert; - f->print_help = oggdec_cmdline_parser_print_help; - f->parse_config = oggdec_parse_config; -} diff --git a/oggdec_filter.c b/oggdec_filter.c new file mode 100644 index 00000000..0653f7da --- /dev/null +++ b/oggdec_filter.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file oggdec_filter.c Paraslash's ogg vorbis decoder. */ + +#include "para.h" + +#include "oggdec_filter.cmdline.h" +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "filter.h" +#include "error.h" +#include "string.h" + +#include + +/** Determine byte sex. */ +#ifdef WORDS_BIGENDIAN +#define ENDIAN 1 +#else +#define ENDIAN 0 +#endif + +/** Data specific to the oggdec filter. */ +struct private_oggdec_data { + /** Describes an ogg vorbis file. */ + OggVorbis_File *vf; + /** The input buffer. */ + char *inbuf; + /** The length of \a inbuf. */ + size_t inbuf_len; + /** The number of bytes consumed from the input buffer. */ + size_t converted; +}; + +static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource) +{ + struct filter_node *fn = datasource; + struct private_oggdec_data *pod = fn->private_data; + size_t ret, have = pod->inbuf_len - pod->converted; + char *p = pod->inbuf + pod->converted; + +// PARA_DEBUG_LOG("pod = %p\n", pod); +// PARA_DEBUG_LOG("vorbis requests %d bytes, have %d\n", size * nmemb, have); + if (pod->inbuf_len < size) { + if (*fn->fc->input_error) + return 0; + errno = EAGAIN; + return (size_t)-1; + } + ret = PARA_MIN(nmemb, have / size) * size; + memcpy(buf, p, ret); + pod->converted += ret; + return ret; +} + +/* + * Custom data seeking function. + * + * Since we want the data source to be treated as unseekable at all + * times, the provided seek callback always returns -1 (failure). + */ +static int cb_seek(__a_unused void *datasource, __a_unused ogg_int64_t offset, + __a_unused int whence) +{ + return -1; +} + +static int cb_close(__a_unused void *datasource) +{ + return 0; +} + +static const ov_callbacks ovc = { + .read_func = cb_read, + .seek_func = cb_seek, + .close_func = cb_close, + /* + * The tell function need not be provided if the data IO abstraction is + * not seekable + */ + .tell_func = NULL +}; + +static void ogg_open(struct filter_node *fn) +{ + struct private_oggdec_data *pod = para_calloc( + sizeof(struct private_oggdec_data)); + struct oggdec_filter_args_info *conf = fn->conf; + + fn->private_data = pod; + fn->bufsize = conf->bufsize_arg * 1024; + fn->buf = para_malloc(fn->bufsize); +} + +static void ogg_close(struct filter_node *fn) +{ + struct private_oggdec_data *pod = fn->private_data; + if (pod->vf) { + PARA_DEBUG_LOG("ov_clearing %p, pod = %p\n", pod->vf, pod); + ov_clear(pod->vf); + free(pod->vf); + pod->vf = NULL; + } else + PARA_DEBUG_LOG("nothing to close in fc %p, pod = %p\n", pod->vf, pod); + free(fn->buf); + fn->buf = NULL; + free(fn->private_data); + fn->private_data = NULL; +} + +static ssize_t ogg_convert(char *inbuffer, size_t len, struct filter_node *fn) +{ + ssize_t ret; + struct private_oggdec_data *pod = fn->private_data; + struct oggdec_filter_args_info *conf = fn->conf; + /* make the buffer known to the read callback cb_read() */ + pod->inbuf = inbuffer; + pod->inbuf_len = len; + pod->converted = 0; + + if (!pod->vf) { + int ib = 1024 * conf->initial_buffer_arg; /* initial buffer */ + if (len fc->input_error) { + PARA_DEBUG_LOG("initial input buffer %zd/%d, " + "waiting for more data\n", len, ib); + return 0; + } + pod->vf = para_malloc(sizeof(struct OggVorbis_File)); + PARA_NOTICE_LOG("input buffer: %zd, opening ov callbacks\n", len); + ret = ov_open_callbacks(fn, pod->vf, + NULL, /* no initial buffer */ + 0, /* no initial bytes */ + ovc); /* the ov_open_callbacks */ + if (ret == OV_EREAD) + return -E_OGGDEC_READ; + if (ret == OV_ENOTVORBIS) + return -E_OGGDEC_NOTVORBIS; + if (ret == OV_EVERSION) + return -E_OGGDEC_VERSION; + if (ret == OV_EBADHEADER) + return -E_OGGDEC_BADHEADER; + if (ret < 0) + return -E_OGGDEC_FAULT; + fn->fc->channels = ov_info(pod->vf, 0)->channels; + fn->fc->samplerate = ov_info(pod->vf, 0)->rate; + PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fc->channels, + fn->fc->samplerate); + } + while (fn->loaded < fn->bufsize) { + int length = fn->bufsize - fn->loaded; + long read_ret = ov_read(pod->vf, fn->buf + fn->loaded, length, + ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL); + if (read_ret == OV_HOLE || !read_ret) + return pod->converted; + if (read_ret < 0) + return -E_OGGDEC_BADLINK; + fn->loaded += read_ret; + } + return pod->converted; +} + +static int oggdec_parse_config(int argc, char **argv, void **config) +{ + int ret; + struct oggdec_filter_args_info *ogg_conf; + + ogg_conf = para_calloc(sizeof(*ogg_conf)); + ret = -E_OGGDEC_SYNTAX; + if (oggdec_cmdline_parser(argc, argv, ogg_conf)) + goto err; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (ogg_conf->bufsize_arg < 0) + goto err; + if (ogg_conf->bufsize_arg >= INT_MAX / 1024) + goto err; + if (ogg_conf->initial_buffer_arg < 0) + goto err; + if (ogg_conf->initial_buffer_arg >= INT_MAX / 1024) + goto err; + *config = ogg_conf; + return 1; +err: + free(ogg_conf); + return ret; +} + +/** + * The init function of the ogg vorbis decoder. + * + * \param f Its fields are filled in by the function. + */ +void oggdec_filter_init(struct filter *f) +{ + struct oggdec_filter_args_info dummy; + + oggdec_cmdline_parser_init(&dummy); + f->open = ogg_open; + f->close = ogg_close; + f->convert = ogg_convert; + f->parse_config = oggdec_parse_config; + f->help = (struct ggo_help) { + .short_help = oggdec_filter_args_info_help, + .detailed_help = oggdec_filter_args_info_detailed_help + }; +} diff --git a/oggdec_filter.ggo b/oggdec_filter.ggo index 70f73452..e2d653d7 100644 --- a/oggdec_filter.ggo +++ b/oggdec_filter.ggo @@ -1,2 +1,21 @@ -option "bufsize" b "size of output buffer" int typestr="kilobyte" default="128" optional -option "initial_buffer" i "size of initial input buffer" int typestr="kilobyte" default="16" optional +option "bufsize" b +#~~~~~~~~~~~~~~~~~ +"size of output buffer" +int typestr="kilobyte" +default="128" +optional +details=" + Increase this if you encounter output buffer overrun errors. Smaller + values make the oggdec filter use less memory. +" + +option "initial_buffer" i +#~~~~~~~~~~~~~~~~~~~~~~~~ +"size of initial input buffer" +int typestr="kilobyte" +default="16" +optional +details=" + On startup, defer decoding until that many kilobytes are + available in the input buffer. +" diff --git a/ortp_recv.c b/ortp_recv.c index 164c1cb0..102fe9ac 100644 --- a/ortp_recv.c +++ b/ortp_recv.c @@ -11,6 +11,7 @@ #include "ortp.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "ortp_recv.cmdline.h" @@ -280,12 +281,19 @@ static int ortp_recv_open(struct receiver_node *rn) */ void ortp_recv_init(struct receiver *r) { + struct ortp_recv_args_info dummy; + + ortp_recv_cmdline_parser_init(&dummy); r->shutdown = ortp_shutdown; r->open = ortp_recv_open; r->close = ortp_recv_close; r->pre_select = ortp_recv_pre_select; r->post_select = ortp_recv_post_select; r->parse_config = ortp_recv_parse_config; + r->help = (struct ggo_help) { + .short_help = ortp_recv_args_info_help, + .detailed_help = ortp_recv_args_info_detailed_help + }; ortp_init(); } diff --git a/ortp_recv.ggo b/ortp_recv.ggo index 192f21c2..a706d543 100644 --- a/ortp_recv.ggo +++ b/ortp_recv.ggo @@ -1,4 +1,19 @@ -section "ortp options" -option "host" i "ip or host to receive rtp packets from" string default="224.0.1.38" optional -option "port" p "udp port." int typestr="portnumber" default="1500" optional -option "jitter_compensation" j "set ortp's adaptive jitter compensation" int typestr="milliseconds" default="0" optional +option "host" i +"ip or host to receive rtp packets from" +string default="224.0.1.38" +optional +details=" + The default address resoves to DANTZ.MCAST.NET and activates + multicast. +" + +option "port" p "udp port" +int typestr="portnumber" +default="1500" +optional + +option "jitter_compensation" j +"set ortp's adaptive jitter compensation" +int typestr="milliseconds" +default="0" +optional diff --git a/osl.c b/osl.c index f06d4ba3..2aee5b6a 100644 --- a/osl.c +++ b/osl.c @@ -31,8 +31,9 @@ */ int para_lseek(int fd, off_t *offset, int whence) { - *offset = lseek(fd, *offset, whence); int ret = -E_LSEEK; + + *offset = lseek(fd, *offset, whence); if (*offset == -1) return ret; return 1; @@ -1447,8 +1448,10 @@ int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects, goto out; rollback: /* rollback all changes made, ignore further errors */ for (i--; i >= 0; i--) { + enum osl_storage_type st; + cd = get_column_description(t->desc, i); - enum osl_storage_type st = cd->storage_type; + st = cd->storage_type; if (st == OSL_NO_STORAGE) continue; diff --git a/osl_core.h b/osl_core.h index e042665a..5e2acaa0 100644 --- a/osl_core.h +++ b/osl_core.h @@ -9,6 +9,7 @@ #include "rbtree.h" #include "osl.h" #include "string.h" +#include "portable_io.h" #include "hash.h" /** Internal representation of a column of an osl table. */ diff --git a/osx_write.c b/osx_write.c index 6001b859..057526e3 100644 --- a/osx_write.c +++ b/osx_write.c @@ -148,6 +148,12 @@ static OSStatus osx_callback(void * inClientData, return 0; } +#ifdef WORDS_BIGENDIAN /* ppc */ +#define ENDIAN_FLAGS kLinearPCMFormatFlagIsBigEndian +#else +#define ENDIAN_FLAGS 0 +#endif + static int osx_write_open(struct writer_node *wn) { struct private_osx_write_data *powd = para_calloc( @@ -195,7 +201,7 @@ static int osx_write_open(struct writer_node *wn) /* flags specific to each format */ format.mFormatFlags = kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsPacked - | kLinearPCMFormatFlagIsBigEndian; + | ENDIAN_FLAGS; if (!conf->channels_given && wng->channels) powd->channels = *wng->channels; else diff --git a/para.h b/para.h index a39ed97d..218b8147 100644 --- a/para.h +++ b/para.h @@ -30,12 +30,24 @@ /** used in various contexts */ #define MAXLINE 255 -/** compute the minimum of \a a and \a b */ -#define PARA_MIN(a,b) ((a) < (b) ? (a) : (b)) -/** compute the maximum of \a a and \a b */ -#define PARA_MAX(a,b) ((a) > (b) ? (a) : (b)) -/** compute the absolute value of \a a */ -#define PARA_ABS(a) ((a) > 0 ? (a) : -(a)) +/** Compute the minimum of \a x and \a y. */ +#define PARA_MIN(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) + +/** Compute the maximum of \a x and \a y. */ +#define PARA_MAX(x, y) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + (void) (&_max1 == &_max2); \ + _max1 < _max2 ? _max2 : _max1; }) + +/** Compute the absolute value of \a x. */ +#define PARA_ABS(x) ({ \ + typeof(x) _x = (x); \ + _x > 0? _x : -_x; }) /** debug loglevel, gets really noisy */ #define DEBUG 1 diff --git a/rbtree.c b/rbtree.c index 5d24aeb5..95bcee39 100644 --- a/rbtree.c +++ b/rbtree.c @@ -438,9 +438,9 @@ struct rb_node *rb_nth(struct rb_node *node, unsigned n) */ int rb_rank(struct rb_node *node, unsigned *rank) { - *rank = 1; struct rb_node *parent; + *rank = 1; if (!node) return -1; if (node->rb_left) diff --git a/recv.c b/recv.c index 5bce847e..52935a88 100644 --- a/recv.c +++ b/recv.c @@ -12,6 +12,7 @@ #include "para.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "recv.cmdline.h" #include "fd.h" @@ -27,21 +28,28 @@ INIT_STDERR_LOGGING(conf.loglevel_arg); /** init array of error codes used by para_recv */ INIT_RECV_ERRLISTS; -static void *parse_config(int argc, char *argv[], int *receiver_num) +__noreturn static void print_help_and_die(void) { - int i; + int d = conf.detailed_help_given; + const char **p = d? recv_args_info_detailed_help + : recv_args_info_help; + + printf_or_die("%s\n\n", RECV_CMDLINE_PARSER_PACKAGE "-" + RECV_CMDLINE_PARSER_VERSION); + printf_or_die("%s\n\n", recv_args_info_usage); + for (; *p; p++) + printf_or_die("%s\n", *p); + print_receiver_helps(d); + exit(0); +} +static void *parse_config(int argc, char *argv[], int *receiver_num) +{ if (recv_cmdline_parser(argc, argv, &conf)) return NULL; HANDLE_VERSION_FLAG("recv", conf); - if (conf.list_receivers_given) { - printf("available receivers: "); - for (i = 0; receivers[i].name; i++) - printf("%s%s", i? " " : "", receivers[i].name); - printf("\nTry\n\tpara_recv -r ' -h'\n" - "for help on .\n"); - exit(EXIT_SUCCESS); - } + if (conf.help_given || conf.detailed_help_given) + print_help_and_die(); return check_receiver_arg(conf.receiver_arg, receiver_num); } @@ -67,8 +75,9 @@ int main(int argc, char *argv[]) s.default_timeout.tv_sec = 1; s.default_timeout.tv_usec = 0; + memset(&sot, 0, sizeof(struct stdout_task)); memset(&rn, 0, sizeof(struct receiver_node)); - for (ret = 0; receivers[ret].name; ret++) + FOR_EACH_RECEIVER(ret) receivers[ret].init(&receivers[ret]); ret = -E_RECV_SYNTAX; rn.conf = parse_config(argc, argv, &receiver_num); diff --git a/recv.ggo b/recv.ggo index febe1a9c..b7003b0e 100644 --- a/recv.ggo +++ b/recv.ggo @@ -1,11 +1,19 @@ -option "loglevel" l "set loglevel (0-6)" int typestr="level" default="4" optional -option "list_receivers" L "print list of available receivers" flag off -option "receiver" r "Select receiver. +option "loglevel" l +#~~~~~~~~~~~~~~~~~~ +"set loglevel (0-6)" +int typestr="level" +default="4" +optional -Any options for the selected receiver must -be quoted. Example: +option "receiver" r +"Select receiver" +string typestr="receiver_spec" +default="http" +optional +details=" + Any options for the selected receiver must + be quoted. Example: - -r 'http -i www.paraslash.org -p 8009' + -r 'http -i www.paraslash.org -p 8009' " -string typestr="receiver_spec" default="http" optional diff --git a/recv.h b/recv.h index d6c012b6..3aed0180 100644 --- a/recv.h +++ b/recv.h @@ -104,6 +104,8 @@ struct receiver { * \sa select(2), struct receiver. */ void (*post_select)(struct sched *s, struct task *t); + + struct ggo_help help; }; @@ -130,4 +132,7 @@ extern struct receiver receivers[]; ORTP_RECEIVER \ {.name = NULL}}; +#define FOR_EACH_RECEIVER(i) for (i = 0; receivers[i].name; i++) + void *check_receiver_arg(char *ra, int *receiver_num); +void print_receiver_helps(int detailed); diff --git a/recv_common.c b/recv_common.c index c8f97c9e..5f2412b5 100644 --- a/recv_common.c +++ b/recv_common.c @@ -10,6 +10,7 @@ #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "string.h" @@ -83,3 +84,21 @@ void *check_receiver_arg(char *ra, int *receiver_num) PARA_ERROR_LOG("receiver not found\n"); return NULL; } + +void print_receiver_helps(int detailed) +{ + int i; + + printf_or_die("\nAvailable receivers: \n\t"); + FOR_EACH_RECEIVER(i) + printf_or_die("%s%s", i? " " : "", receivers[i].name); + printf_or_die("\n\n"); + FOR_EACH_RECEIVER(i) { + struct receiver *r = receivers + i; + + if (!r->help.short_help) + continue; + printf_or_die("Options for %s:\n", r->name); + ggo_print_help(&r->help, detailed); + } +} diff --git a/send.h b/send.h index 270affd5..2fe1cc56 100644 --- a/send.h +++ b/send.h @@ -107,7 +107,7 @@ struct sender_client { /** Describes the current status of one paraslash sender. */ struct sender_status { - /* The file descriptor of the socket this sender is listening on. */ + /** The file descriptor of the socket this sender is listening on. */ int listen_fd; /** The TCP/DCCP port used by this sender. */ int port; diff --git a/send_common.c b/send_common.c index c0098b98..3d870088 100644 --- a/send_common.c +++ b/send_common.c @@ -62,6 +62,8 @@ static int open_sender(unsigned l4type, int port) * Close the file descriptor given by \a sc, remove it from the close-on-fork * list, destroy the chunk queue of this client, delete the client from the * list of connected clients and free the sender_client struct. + * + * \sa shutdown_clients(). */ void shutdown_client(struct sender_client *sc, struct sender_status *ss) { @@ -76,6 +78,14 @@ void shutdown_client(struct sender_client *sc, struct sender_status *ss) ss->num_clients--; } +/** + * Shut down all clients connected to a paraslash sender. + * + * \param ss The sender whose clients are to be shut down. + * + * This just loops over all connected clients and calls shutdown_client() + * for each client. + */ void shutdown_clients(struct sender_status *ss) { struct sender_client *sc, *tmp; diff --git a/server.c b/server.c index 91568764..0924ace4 100644 --- a/server.c +++ b/server.c @@ -23,8 +23,8 @@ * The gory details, listed by topic: * * - Audio format handlers: \ref send_common.c \ref mp3_afh.c, \ref ogg_afh.c, \ref aac_afh.c, - * - Decoders: \ref mp3dec.c, \ref oggdec.c, \ref aacdec.c, - * - Volume normalizer: \ref compress.c, + * - Decoders: \ref mp3dec_filter.c, \ref oggdec_filter.c, \ref aacdec_filter.c, + * - Volume normalizer: \ref compress_filter.c, * - Output: \ref alsa_write.c, \ref osx_write.c, * - http: \ref http_recv.c, \ref http_send.c, * - ortp: \ref ortp_recv.c, \ref ortp_send.c, @@ -136,7 +136,7 @@ struct server_command_task { * \param ll The log level. * \param fmt The format string describing the log message. */ -void para_log(int ll, const char* fmt,...) +__printf_2_3 void para_log(int ll, const char* fmt,...) { va_list argp; FILE *outfd; diff --git a/server.ggo b/server.ggo index 012e1041..9f391203 100644 --- a/server.ggo +++ b/server.ggo @@ -162,9 +162,8 @@ option "afs_database_dir" D string typestr="path" optional details=" - Where para_server should look for the osl - database of the audio file selector. The default is - '~/.paraslash/afs_database'. + Where para_server should look for the osl database of the audio + file selector. The default is '~/.paraslash/afs_database'. " option "afs_socket" s diff --git a/signal.c b/signal.c index f8be2f46..9168e918 100644 --- a/signal.c +++ b/signal.c @@ -57,8 +57,15 @@ err_out: */ static void generic_signal_handler(int s) { - write(signal_pipe[1], &s, sizeof(int)); - //fprintf(stderr, "got sig %i, write returned %d\n", s, ret); + ssize_t ret = write(signal_pipe[1], &s, sizeof(int)); + + if (ret == sizeof(int)) + return; + if (ret < 0) + PARA_EMERG_LOG("%s\n", strerror(errno)); + else + PARA_EMERG_LOG("short write to signal pipe\n"); + exit(EXIT_FAILURE); } /** diff --git a/stdin.c b/stdin.c index 5b6bf520..0b84cdc7 100644 --- a/stdin.c +++ b/stdin.c @@ -31,6 +31,11 @@ static void stdin_pre_select(struct sched *s, struct task *t) { struct stdin_task *sit = container_of(t, struct stdin_task, task); + + if (sit->output_error && *sit->output_error < 0) { + t->error = *sit->output_error; + return; + } t->error = 0; sit->check_fd = 0; if (sit->loaded >= sit->bufsize) @@ -55,6 +60,10 @@ static void stdin_post_select(struct sched *s, struct task *t) struct stdin_task *sit = container_of(t, struct stdin_task, task); ssize_t ret; + if (sit->output_error && *sit->output_error < 0) { + t->error = *sit->output_error; + return; + } t->error = 0; if (!sit->check_fd) return; @@ -89,6 +98,7 @@ void stdin_set_defaults(struct stdin_task *sit) ret = mark_fd_nonblocking(STDIN_FILENO); if (ret >= 0) return; + sit->output_error = NULL; PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } diff --git a/stdin.h b/stdin.h index 63d00d5d..99233f9f 100644 --- a/stdin.h +++ b/stdin.h @@ -14,6 +14,8 @@ struct stdin_task { size_t bufsize; /** Number of bytes currently loaded in \a buf. */ size_t loaded; + /** Pointer to the error member of the consumer. */ + int *output_error; /** Whether \p STDIN_FILENO was included in the read fd set. */ int check_fd; /** The task structure. */ diff --git a/string.c b/string.c index c73ec1e6..bdc113d3 100644 --- a/string.c +++ b/string.c @@ -59,9 +59,10 @@ __must_check __malloc void *para_realloc(void *p, size_t size) */ __must_check __malloc void *para_malloc(size_t size) { - assert(size); - void *p = malloc(size); + void *p; + assert(size); + p = malloc(size); if (!p) { PARA_EMERG_LOG("malloc failed (size = %zu), aborting\n", size); diff --git a/time.c b/time.c index 1fd7b098..0ebc6ec0 100644 --- a/time.c +++ b/time.c @@ -170,6 +170,16 @@ int tv_convex_combination(const long a, const struct timeval *tv1, return ret; } +/** + * Compute when to send a chunk of an audio file. + * + * \param chunk_num The number of the chunk. + * \param chunk_tv The duration of one chunk. + * \param stream_start When the first chunk was sent. + * \param result The time when to send chunk number \a chunk_num. + * + * This function computes stream_start + chunk_num * chunk_time. + */ void compute_chunk_time(long unsigned chunk_num, struct timeval *chunk_tv, struct timeval *stream_start, struct timeval *result) diff --git a/versions/paraslash-0.3.3.tar.bz2 b/versions/paraslash-0.3.3.tar.bz2 new file mode 100644 index 00000000..40834de6 Binary files /dev/null and b/versions/paraslash-0.3.3.tar.bz2 differ diff --git a/versions/paraslash-0.3.3.tar.bz2.asc b/versions/paraslash-0.3.3.tar.bz2.asc new file mode 100644 index 00000000..2392f88a --- /dev/null +++ b/versions/paraslash-0.3.3.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBJM93LWto1QDEAkw8RAkqoAJ4otG8p97XWBP7x2o1zy1R3M80BVQCfZy7K +WxcDbEGOvK1Vk0zhGPWr7gU= +=Ni2V +-----END PGP SIGNATURE----- diff --git a/vss.c b/vss.c index f92e15e0..4ed75792 100644 --- a/vss.c +++ b/vss.c @@ -418,7 +418,6 @@ static void vss_send_chunk(struct vss_task *vsst) if (chk_barrier("data send", &vsst->data_send_barrier, &due, 1) < 0) return; - mmd->new_vss_status_flags &= ~VSS_REPOS; if (mmd->current_chunk >= mmd->afd.afhi.chunks_total) { /* eof */ mmd->new_vss_status_flags |= VSS_NEXT; return; @@ -502,6 +501,7 @@ void init_vss_task(int afs_socket) free(hn); free(home); mmd->sender_cmd_data.cmd_num = -1; + make_empty_status_items(mmd->afd.verbose_ls_output); if (conf.autoplay_given) { struct timeval tmp; mmd->vss_status_flags |= VSS_PLAYING; diff --git a/wav.c b/wav.c deleted file mode 100644 index 52a97af0..00000000 --- a/wav.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2005-2008 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file wav.c a filter that inserts a wave header */ - -#include "para.h" - -#include "list.h" -#include "sched.h" -#include "filter.h" -#include "string.h" - -/** size of the output buffer */ -#define WAV_OUTBUF_SIZE 81920 -/** a wav header is always 44 bytes */ -#define WAV_HEADER_LEN 44 -/** always write 16 bit header */ -#define BITS 16 - -/** - * write a 32 bit unsigned value to a buffer - * - * \param buf the buffer to write to - * \param x the value to write - */ -#define WRITE_U32(buf, x) (buf)[0] = (unsigned char)((x) & 0xff);\ - (buf)[1] = (unsigned char)(((x) >> 8) & 0xff);\ - (buf)[2] = (unsigned char)(((x) >> 16) & 0xff);\ - (buf)[3] = (unsigned char)(((x) >> 24) & 0xff); - -/** - * write a 16 bit unsigned value to a buffer - * - * \param buf the buffer to write to - * \param x the value to write - */ -#define WRITE_U16(buf, x) (buf)[0] = (unsigned char)((x) & 0xff); - -static void make_wav_header(unsigned int channels, unsigned int samplerate, - struct filter_node *fn) -{ - - char *headbuf = fn->buf; - unsigned int size = 0x7fffffff; - int bytespersec = channels * samplerate * BITS / 8; - int align = channels * BITS / 8; - - PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate); - memset(headbuf, 0, WAV_HEADER_LEN); - memcpy(headbuf, "RIFF", 4); - WRITE_U32(headbuf + 4, size - 8); - memcpy(headbuf + 8, "WAVE", 4); - memcpy(headbuf + 12, "fmt ", 4); - WRITE_U32(headbuf + 16, 16); - WRITE_U16(headbuf + 20, 1); /* format */ - WRITE_U16(headbuf + 22, channels); - WRITE_U32(headbuf + 24, samplerate); - WRITE_U32(headbuf + 28, bytespersec); - WRITE_U16(headbuf + 32, align); - WRITE_U16(headbuf + 34, BITS); - memcpy(headbuf + 36, "data", 4); - WRITE_U32(headbuf + 40, size - 44); -} - -static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn) -{ - size_t copy; - int *bof = fn->private_data; - - if (*bof) { - make_wav_header(fn->fc->channels, fn->fc->samplerate, fn); - fn->loaded = WAV_HEADER_LEN; - *bof = 0; -// return 0; - } - copy = PARA_MIN(len, fn->bufsize - fn->loaded); - memmove(fn->buf + fn->loaded, inbuf, copy); - fn->loaded += copy; -// PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy); - return copy; -} - -static void wav_close(struct filter_node *fn) -{ - free(fn->buf); - fn->buf = NULL; - free(fn->private_data); - fn->private_data = NULL; -} - -static void wav_open(struct filter_node *fn) -{ - int *bof; - - fn->bufsize = WAV_OUTBUF_SIZE; - fn->buf = para_malloc(fn->bufsize); - fn->private_data = para_malloc(sizeof(int)); - bof = fn->private_data; - *bof = 1; - PARA_INFO_LOG("wav filter node: %p, output buffer: %p, loaded: %zd\n", - fn, fn->buf, fn->loaded); -} - -/** - * the init function of the wav filter - * - * \param f struct to initialize - */ -void wav_init(struct filter *f) -{ - f->convert = wav_convert; - f->close = wav_close; - f->open = wav_open; -} diff --git a/wav_filter.c b/wav_filter.c new file mode 100644 index 00000000..d30b018d --- /dev/null +++ b/wav_filter.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2005-2008 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file wav_filter.c A filter that inserts a wave header. */ + +#include "para.h" + +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "filter.h" +#include "string.h" +#include "portable_io.h" + +/** size of the output buffer */ +#define WAV_OUTBUF_SIZE 81920 +/** a wav header is always 44 bytes */ +#define WAV_HEADER_LEN 44 +/** always write 16 bit header */ +#define BITS 16 + +static void make_wav_header(unsigned int channels, unsigned int samplerate, + struct filter_node *fn) +{ + + char *headbuf = fn->buf; + unsigned int size = 0x7fffffff; + int bytespersec = channels * samplerate * BITS / 8; + int align = channels * BITS / 8; + + assert(channels); + PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate); + memset(headbuf, 0, WAV_HEADER_LEN); + memcpy(headbuf, "RIFF", 4); + write_u32(headbuf + 4, size - 8); + memcpy(headbuf + 8, "WAVE", 4); + memcpy(headbuf + 12, "fmt ", 4); + write_u32(headbuf + 16, 16); + write_u16(headbuf + 20, 1); /* format */ + write_u16(headbuf + 22, channels); + write_u32(headbuf + 24, samplerate); + write_u32(headbuf + 28, bytespersec); + write_u16(headbuf + 32, align); + write_u16(headbuf + 34, BITS); + memcpy(headbuf + 36, "data", 4); + write_u32(headbuf + 40, size - 44); +} + +static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn) +{ + size_t copy; + int *bof = fn->private_data; + + if (*bof) { + if (!len) + return 0; + make_wav_header(fn->fc->channels, fn->fc->samplerate, fn); + fn->loaded = WAV_HEADER_LEN; + *bof = 0; +// return 0; + } + copy = PARA_MIN(len, fn->bufsize - fn->loaded); + memmove(fn->buf + fn->loaded, inbuf, copy); + fn->loaded += copy; +// PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy); + return copy; +} + +static void wav_close(struct filter_node *fn) +{ + free(fn->buf); + fn->buf = NULL; + free(fn->private_data); + fn->private_data = NULL; +} + +static void wav_open(struct filter_node *fn) +{ + int *bof; + + fn->bufsize = WAV_OUTBUF_SIZE; + fn->buf = para_malloc(fn->bufsize); + fn->private_data = para_malloc(sizeof(int)); + bof = fn->private_data; + fn->loaded = 0; + *bof = 1; + PARA_INFO_LOG("wav filter node: %p, output buffer: %p, loaded: %zd\n", + fn, fn->buf, fn->loaded); +} + +/** + * the init function of the wav filter + * + * \param f struct to initialize + */ +void wav_filter_init(struct filter *f) +{ + f->convert = wav_convert; + f->close = wav_close; + f->open = wav_open; +} diff --git a/web/index.in.html b/web/index.in.html index 86a53872..ac3d3cd0 100644 --- a/web/index.in.html +++ b/web/index.in.html @@ -1,6 +1,10 @@

Events


    +
  • 2008-12-01: paraslash-0.3.3 + (sig) + "axiomatic perspectivity" +
  • 2008-04-11: paraslash-0.3.2 (sig) "probabilistic parity" diff --git a/write.c b/write.c index 140e80e5..9b7a9211 100644 --- a/write.c +++ b/write.c @@ -14,6 +14,7 @@ #include "write.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "stdin.h" #include "write.h" #include "write_common.h" @@ -34,6 +35,7 @@ struct check_wav_task { unsigned channels; /** Sample rate specified in wav header given by \a buf. */ unsigned samplerate; + /** The task structure used by the scheduler. */ struct task task; }; @@ -105,14 +107,19 @@ static void initial_delay_pre_select(struct sched *s, struct task *t) if (!idt->start_time.tv_sec && !idt->start_time.tv_usec) { t->error = -E_NO_DELAY; - return; + goto register_check_wav; } if (tv_diff(now, &idt->start_time, &diff) > 0) { t->error = -E_DELAY_TIMEOUT; - return; + goto register_check_wav; } if (tv_diff(&s->timeout , &diff, NULL) > 0) s->timeout = diff; + return; +register_check_wav: + register_task(&the_check_wav_task.task); + s->timeout.tv_sec = 0; + s->timeout.tv_usec = 1; } INIT_STDERR_LOGGING(conf.loglevel_arg) @@ -123,20 +130,6 @@ static struct writer_node_group *check_args(void) struct writer_node_group *g = NULL; struct initial_delay_task *idt = &the_initial_delay_task; - if (conf.list_writers_given) { - char *msg = NULL; - FOR_EACH_WRITER(i) { - char *tmp = make_message("%s%s%s", - i? msg : "", - i? " " : "", - writer_names[i]); - free(msg); - msg = tmp; - } - fprintf(stderr, "%s\n", msg); - free(msg); - exit(EXIT_SUCCESS); - } if (conf.start_time_given) { long unsigned sec, usec; if (sscanf(conf.start_time_arg, "%lu:%lu", @@ -168,6 +161,21 @@ out: return NULL; } +__noreturn static void print_help_and_die(void) +{ + int d = conf.detailed_help_given; + const char **p = d? write_args_info_detailed_help + : write_args_info_help; + + printf_or_die("%s\n\n", WRITE_CMDLINE_PARSER_PACKAGE "-" + WRITE_CMDLINE_PARSER_VERSION); + printf_or_die("%s\n\n", write_args_info_usage); + for (; *p; p++) + printf_or_die("%s\n", *p); + print_writer_helps(d); + exit(0); +} + /** * Para_write's main function. * @@ -186,16 +194,22 @@ int main(int argc, char *argv[]) struct check_wav_task *cwt = &the_check_wav_task; struct initial_delay_task *idt = &the_initial_delay_task; + init_supported_writers(); write_cmdline_parser(argc, argv, &conf); HANDLE_VERSION_FLAG("write", conf); - init_supported_writers(); + if (conf.help_given || conf.detailed_help_given) + print_help_and_die(); wng = check_args(); if (!wng) goto out; stdin_set_defaults(&sit); - if (conf.bufsize_given) - sit.bufsize = conf.bufsize_arg; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (conf.bufsize_arg < 0) + goto out; + if (conf.bufsize_arg >= INT_MAX / 1024) + goto out; + sit.bufsize = conf.bufsize_arg * 1024; sit.buf = para_malloc(sit.bufsize); wng->buf = sit.buf; @@ -209,7 +223,6 @@ int main(int argc, char *argv[]) cwt->input_error = &sit.task.error; sprintf(cwt->task.status, "check wav"); cwt->task.pre_select = check_wav_pre_select; - register_task(&cwt->task); idt->task.pre_select = initial_delay_pre_select; sprintf(idt->task.status, "initial_delay"); diff --git a/write.ggo b/write.ggo index 1ea8abf2..8f557bb9 100644 --- a/write.ggo +++ b/write.ggo @@ -1,45 +1,35 @@ -section "general options" -######################### - -option "list_writers" L -#~~~~~~~~~~~~~~~~~~~~~~ -"print available writers and exit" - - flag off - option "loglevel" l #~~~~~~~~~~~~~~~~~~ "set loglevel (0-6)" - - int typestr="level" - default="4" - optional +int typestr="level" +default="4" +optional option "bufsize" b #~~~~~~~~~~~~~~~~~ "input buffer size" - - int typestr="kilobytes" - default="64" - optional +int typestr="kilobytes" +default="64" +optional option "writer" w #~~~~~~~~~~~~~~~~ - -"select stream writer -may be give multiple times. The same writer -may be specified more than once" - - string typestr="name" - default="alsa (file if alsa is unsupported)" - optional - multiple +"select stream writer" +string typestr="name" +default="alsa (file if alsa is unsupported)" +optional +multiple +details=" + May be give multiple times. The same writer may be specified + more than once. +" option "start_time" t #~~~~~~~~~~~~~~~~~~~~ -"start playback at given time which must be -in a:b format where a denotes seconds and b -denotes microseconds since the epoch" - - string typestr="timeval" - optional +"defer playback" +string typestr="timeval" +optional +details=" + Start playback at given time which must be in a:b format where + a denotes seconds and b denotes microseconds since the epoch. +" diff --git a/write.h b/write.h index 091f8c77..7df62f9f 100644 --- a/write.h +++ b/write.h @@ -4,54 +4,49 @@ * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file write.h writer-related structures */ +/** \file write.h Writer-related structures. */ -/** the list of supported writers */ +/** The list of supported writers. */ enum writer_enum {WRITER_ENUM}; /** * Describes one running instance of a writer. */ struct writer_node { - /** points to the writer structure associated with this node */ - struct writer *writer; /* FIXME: Should better be only the number */ - /** writer-specific data */ + /** Points to the writer structure associated with this node. */ + struct writer *writer; /* FIXME: Should better be only the number. */ + /** Writer-specific data. */ void *private_data; - /** send that many bytes in one go */ + /** Send that many bytes in one go. */ int chunk_bytes; - /** pointer to the group this node belongs to */ + /** Pointer to the group this node belongs to. */ struct writer_node_group *wng; - /** the writer-specific configuration of this node */ + /** The writer-specific configuration of this node. */ void *conf; - /** how much of the wng's buffer is already written */ + /** How much of the wng's buffer is already written. */ size_t written; }; -/** describes one supported writer */ +/** Describes one supported writer. */ struct writer { /** - * the init function of the writer + * The init function of the writer. * * It must fill in all other function pointers of the given * writer structure. - * */ void (*init)(struct writer *w); /** - * - * - * the command line parser of the writer + * The command line parser of the writer. * * It should check whether the command line options given by \a options are * valid. On success, it should return a pointer to the writer-specific * configuration data determined by \a options. Note that this might be called * more than once with different values of \a options. - * */ - void * (*parse_config)(const char *options); + void *(*parse_config)(const char *options); /** - * - * open one instance of this writer + * Open one instance of this writer. * * This function should perform any work necessary to write the incoming * stream. If To this aim, it may allocate its private data structure and store @@ -59,8 +54,7 @@ struct writer { */ int (*open)(struct writer_node *); /** - * - * write a chunk of audio data + * Prepare the fd sets for select. * * This is called from the writer node group task's pre_select(). It * may use the sched pointer to add any file descriptors or to decrease @@ -69,64 +63,66 @@ struct writer { */ int (*pre_select)(struct sched *s, struct writer_node *wn); /** + * Write audio data. + * * Called from the post_select function of the wng task. It must keep * track of the number of bytes consumed from the wng's buffer via - * the wn->written variable (which may be modified by the wng handling + * the \p wn->written variable (which may be modified by the wng handling * functions). This function must return positive on success and * negative on errors. */ int (*post_select)(struct sched *s, struct writer_node *wn); /** - * close one instance of the writer + * Close one instance of the writer. * * This function is assumed to succeed. */ void (*close)(struct writer_node *); /** - * shutdown the writer + * Shutdown the writer * - * This is a optional function pointer used for cleaning - * up. + * This is a optional function pointer used for cleaning up. */ void (*shutdown)(struct writer_node *); + struct ggo_help help; }; /** - * describes a set of writer nodes that all write the same stream. + * Describes a set of writer nodes that all write the same stream. */ struct writer_node_group { - /** number of nodes belonging to this group */ + /** Number of nodes belonging to this group. */ unsigned num_writers; - /** array of pointers to the corresponding writer nodes */ + /** Array of pointers to the corresponding writer nodes. */ struct writer_node *writer_nodes; - /** the maximum of the chunk_bytes values of the writer nodes in this group */ - size_t max_chunk_bytes; + /** The maximum of the chunk_bytes values of the writer nodes in this group. */ + int max_chunk_bytes; /** Non-zero if an error or end of file was encountered by the feeding task. */ int *input_error; - /** current output buffer */ + /** Current output buffer. */ char *buf; - /** number of bytes loaded in the output buffer */ + /** Number of bytes loaded in the output buffer. */ size_t *loaded; - /** number of audio channels of the current stream */ + /** Number of audio channels of the current stream. */ unsigned int *channels; - /** sample rate of the current stream */ + /** Sample rate of the current stream. */ unsigned int *samplerate; - /** the task associated to this group */ + /** The task associated to this group. */ struct task task; /** Whether the group is open, i.e. wng_open() was called. */ int open; }; -/** loop over each writer node in a writer group */ +/** Loop over each writer node in a writer group. */ #define FOR_EACH_WRITER_NODE(i, wng) for (i = 0; i < (wng)->num_writers; i++) -/** loop over each supported writer */ +/** Loop over each supported writer. */ #define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) -/** declare the init functions of all supported writers */ +/** Declare the init functions of all supported writers. */ DECLARE_WRITER_INITS; -/** array containing the name of each writer */ +/** Array containing the name of each writer. */ extern const char *writer_names[]; -/** the writer structure for each supported writer */ +/** The writer structure for each supported writer. */ extern struct writer writers[NUM_SUPPORTED_WRITERS]; diff --git a/write_common.c b/write_common.c index 3bf5d119..5896d91c 100644 --- a/write_common.c +++ b/write_common.c @@ -10,6 +10,7 @@ #include "string.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "write.h" #include "error.h" @@ -210,3 +211,21 @@ struct writer_node_group *setup_default_wng(void) wng->writer_nodes[0].conf = writers[DEFAULT_WRITER].parse_config(""); return wng; } + +void print_writer_helps(int detailed) +{ + int i; + + printf_or_die("\nAvailable writers: \n\t"); + FOR_EACH_WRITER(i) + printf_or_die("%s%s", i? " " : "", writer_names[i]); + printf_or_die("\n\n"); + FOR_EACH_WRITER(i) { + struct writer *w = writers + i; + + if (!w->help.short_help) + continue; + printf_or_die("Options for %s:\n", writer_names[i]); + ggo_print_help(&w->help, detailed); + } +} diff --git a/write_common.h b/write_common.h index 49938fe6..7f0bbc57 100644 --- a/write_common.h +++ b/write_common.h @@ -9,7 +9,7 @@ int wng_open(struct writer_node_group *g); void wng_close(struct writer_node_group *g); struct writer_node_group *wng_new(unsigned num_writers); -void wng_unregister(struct writer_node_group *g); void init_supported_writers(void); void *check_writer_arg(const char *wa, int *writer_num); struct writer_node_group *setup_default_wng(void); +void print_writer_helps(int detailed);