From c87045a40b9462ebeb28cc169d7bb3721c9a81d3 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 31 Dec 2009 02:04:01 +0100 Subject: [PATCH] Add btr support to filter code. A bit rough and only the mp3dec filter kind of works. Execute support for mp3dec is also missing. --- aacdec_filter.c | 2 + amp_filter.c | 2 + audiod.c | 2 + audiod_command.c | 2 + buffer_tree.c | 50 ++++++++++++++++++- buffer_tree.h | 3 +- compress_filter.c | 2 + error.h | 1 + fecdec_filter.c | 2 + filter.c | 39 +++++++++++++-- filter.h | 4 ++ filter_common.c | 2 + grab_client.c | 2 + mp3dec_filter.c | 121 ++++++++++++++++++++++++++++++++++++++++++++- oggdec_filter.c | 2 + prebuffer_filter.c | 2 + stdin.c | 2 +- wav_filter.c | 2 + wmadec_filter.c | 2 + 19 files changed, 236 insertions(+), 8 deletions(-) diff --git a/aacdec_filter.c b/aacdec_filter.c index 59c49893..ae3a90e6 100644 --- a/aacdec_filter.c +++ b/aacdec_filter.c @@ -11,11 +11,13 @@ /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */ #include +#include #include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" diff --git a/amp_filter.c b/amp_filter.c index e180db43..b3aa2700 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -7,12 +7,14 @@ /** \file amp_filter.c Paraslash's amplify filter. */ #include +#include #include "para.h" #include "amp_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" diff --git a/audiod.c b/audiod.c index 074c2610..d9460934 100644 --- a/audiod.c +++ b/audiod.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -19,6 +20,7 @@ #include "sched.h" #include "ggo.h" #include "recv.h" +#include "buffer_tree.h" #include "filter.h" #include "grab_client.h" #include "client.cmdline.h" diff --git a/audiod_command.c b/audiod_command.c index bb916713..b790fc15 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -9,12 +9,14 @@ #include #include #include +#include #include "para.h" #include "audiod.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "grab_client.h" #include "error.h" diff --git a/buffer_tree.c b/buffer_tree.c index 7b3355a2..28999df5 100644 --- a/buffer_tree.c +++ b/buffer_tree.c @@ -47,7 +47,7 @@ struct btr_node { #define FOR_EACH_BUFFER_REF_SAFE(_br, _tmp, _btrn) \ list_for_each_entry_safe((_br), (_tmp), &(_btrn)->input_queue, node) -struct btr_node *btr_new_node(char *name, struct btr_node *parent, +struct btr_node *btr_new_node(const char *name, struct btr_node *parent, btr_command_handler handler, void *context) { struct btr_node *btrn = para_malloc(sizeof(*btrn)); @@ -287,3 +287,51 @@ void *btr_context(struct btr_node *btrn) { return btrn->context; } + +/** + * Merge the first two input buffers into one. + * + * This is a quite expensive operation. + * + * \return The number of buffers that have been merged (zero, one or two). + */ +int btr_merge(struct btr_node *btrn) +{ + struct btr_buffer_reference *brs[2], *br; + char *bufs[2], *buf; + size_t szs[2], sz; + int i; + + if (list_empty(&btrn->input_queue)) + return 0; + if (list_is_singular(&btrn->input_queue)) + return 1; + i = 0; + /* get references to the first two buffers */ + FOR_EACH_BUFFER_REF(br, btrn) { + brs[i] = br; + szs[i] = btr_get_buffer_by_reference(brs[i], bufs + i); + i++; + if (i == 2) + break; + } + /* make a new btrb that combines the two buffers and a br to it. */ + sz = szs[0] + szs[1]; + //PARA_CRIT_LOG("merging input buffers: (%zu, %zu) -> %zu\n", + // szs[0], szs[1], sz); + buf = para_malloc(sz); + /* TODO: Avoid this memcopy by introducing btr buffer pool. */ + memcpy(buf, bufs[0], szs[0]); + memcpy(buf + szs[0], bufs[1], szs[1]); + + br = para_malloc(sizeof(*br)); + br->btrb = new_btrb(buf, sz); + br->btrb->refcount = 1; + br->consumed = 0; + + /* replace the first two refs by the new one */ + btr_drop_buffer_reference(brs[0]); + btr_drop_buffer_reference(brs[1]); + para_list_add(&br->node, &btrn->input_queue); + return 2; +} diff --git a/buffer_tree.h b/buffer_tree.h index 27f5da2f..927d3b67 100644 --- a/buffer_tree.h +++ b/buffer_tree.h @@ -4,7 +4,7 @@ struct btr_node; typedef int (*btr_command_handler)(struct btr_node *btrn, const char *command, char **result); -struct btr_node *btr_new_node(char *name, struct btr_node *parent, +struct btr_node *btr_new_node(const char *name, struct btr_node *parent, btr_command_handler handler, void *context); void btr_del_node(struct btr_node *btrn); void btr_add_output(char *buf, size_t size, struct btr_node *btrn); @@ -19,3 +19,4 @@ int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result) int btr_splice_out_node(struct btr_node *btrn); void btr_pushdown(struct btr_node *btrn); void *btr_context(struct btr_node *btrn); +int btr_merge(struct btr_node *btrn); diff --git a/compress_filter.c b/compress_filter.c index 7bad4c25..90af8abd 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -11,12 +11,14 @@ */ #include +#include #include "para.h" #include "compress_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" diff --git a/error.h b/error.h index 1a3e6483..92039f64 100644 --- a/error.h +++ b/error.h @@ -296,6 +296,7 @@ extern const char **para_errlist[]; 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"), \ + PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \ #define FILTER_ERRORS \ diff --git a/fecdec_filter.c b/fecdec_filter.c index 8acaf815..901a9169 100644 --- a/fecdec_filter.c +++ b/fecdec_filter.c @@ -7,6 +7,7 @@ /** \file fecdec_filter.c A filter that fec-decodes an audio stream. */ #include +#include #include #include "para.h" @@ -14,6 +15,7 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "portable_io.h" diff --git a/filter.c b/filter.c index 20ea1333..45f4bff0 100644 --- a/filter.c +++ b/filter.c @@ -14,6 +14,7 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "stdin.h" @@ -154,9 +155,41 @@ static int parse_config(int argc, char *argv[]) } /* TODO: support more than one filter, actually parse options */ -static int main_btr(void) +static int __noreturn main_btr(int argc, char *argv[]) { - return 42; + static struct sched s; + struct filter_node *fn = para_calloc(sizeof(*fn)); + int ret; + struct filter *f; + + sit->btrn = btr_new_node("stdin", NULL, NULL, NULL); + stdin_set_defaults(sit); + register_task(&sit->task); + + ret = check_filter_arg("mp3dec ", &fn->conf); + if (ret < 0) + goto err; + fn->filter_num = ret; + f = filters + fn->filter_num; + fn->btrn = btr_new_node(f->name, sit->btrn, f->execute, fn); + fn->task.pre_select = f->pre_select; + fn->task.post_select = f->post_select; + sprintf(fn->task.status, "mp3dec"); + f->open(fn); + register_task(&fn->task); + + sot->btrn = btr_new_node("stdout", fn->btrn, NULL, NULL); + stdout_set_defaults(sot); + register_task(&sot->task); + + s.default_timeout.tv_sec = 1; + s.default_timeout.tv_usec = 0; + ret = schedule(&s); +err: + free(fn); + if (ret < 0) + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } /** @@ -181,7 +214,7 @@ int main(int argc, char *argv[]) if (ret < 0) goto out; if (conf.buffer_tree_given) { - ret = main_btr(); + ret = main_btr(argc, argv); goto out; } stdin_set_defaults(sit); diff --git a/filter.h b/filter.h index 9f3ddd69..ab187df2 100644 --- a/filter.h +++ b/filter.h @@ -193,6 +193,10 @@ struct filter { /** The help texts for this filter. */ struct ggo_help help; + + void (*pre_select)(struct sched *s, struct task *t); + void (*post_select)(struct sched *s, struct task *t); + btr_command_handler execute; }; void close_filters(struct filter_chain *fc); diff --git a/filter_common.c b/filter_common.c index 7c71ff39..94e553b6 100644 --- a/filter_common.c +++ b/filter_common.c @@ -9,12 +9,14 @@ #include #include #include +#include #include "para.h" #include "list.h" #include "sched.h" #include "fd.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" diff --git a/grab_client.c b/grab_client.c index d1ef43f9..853dfba2 100644 --- a/grab_client.c +++ b/grab_client.c @@ -14,11 +14,13 @@ #include #include #include +#include #include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "grab_client.h" #include "audiod.h" diff --git a/mp3dec_filter.c b/mp3dec_filter.c index bd2b4be6..75745044 100644 --- a/mp3dec_filter.c +++ b/mp3dec_filter.c @@ -8,12 +8,14 @@ #include #include +#include #include "para.h" #include "mp3dec_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" @@ -28,6 +30,7 @@ enum mp3dec_flags { MP3DEC_FLAG_BAD_DATA = 1, /** Some output has already been produced. */ MP3DEC_FLAG_DECODE_STARTED = 2, + MP3DEC_FLAG_NEED_MORE = 4, }; /** Data specific to the mp3dec filter. */ @@ -44,6 +47,10 @@ struct private_mp3dec_data { struct timeval stream_start_barrier; /** Wait until this many input bytes are available. */ size_t input_len_barrier; + /** The number of channels of the current stream. */ + unsigned int channels; + /** Current sample rate in Hz. */ + unsigned int samplerate; }; static int need_bad_data_delay(struct private_mp3dec_data *pmd, @@ -66,6 +73,7 @@ static int need_bad_data_delay(struct private_mp3dec_data *pmd, */ static int handle_decode_error(struct private_mp3dec_data *pmd, size_t len) { + const struct timeval delay = {0, 60 * 1000}; if (!MAD_RECOVERABLE(pmd->stream.error) && pmd->stream.error != MAD_ERROR_BUFLEN) { PARA_ERROR_LOG("%s\n", mad_stream_errorstr(&pmd->stream)); @@ -82,11 +90,28 @@ static int handle_decode_error(struct private_mp3dec_data *pmd, size_t len) */ pmd->flags |= MP3DEC_FLAG_BAD_DATA; pmd->input_len_barrier = len; - tv_add(now, &(struct timeval){0, 60 * 1000}, - &pmd->stream_start_barrier); + tv_add(now, &delay, &pmd->stream_start_barrier); return 1; } +/** 640K ought to be enough for everybody ;) */ +#define MP3DEC_MAX_PENDING (640 * 1024) + +static void mp3dec_pre_select(struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + size_t iqs = btr_get_input_queue_size(fn->btrn); + struct private_mp3dec_data *pmd = fn->private_data; + + t->error = 0; + if (iqs <= pmd->input_len_barrier) + return; + if (btr_bytes_pending(fn->btrn) > MP3DEC_MAX_PENDING) + return; /* FIXME, should use reasonable bound on timeout */ + s->timeout.tv_sec = 0; + s->timeout.tv_usec = 1; +} + static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn) { int i, ret; @@ -150,6 +175,18 @@ out: return copy; } +static size_t used_mad_buffer_bytes(struct mad_stream *s, size_t max) +{ + size_t rv; + + if (!s->next_frame) + return max; + /* we still have some data */ + rv = s->next_frame - s->buffer; + assert(rv <= max); + return rv; +} + static void mp3dec_close(struct filter_node *fn) { struct private_mp3dec_data *pmd = fn->private_data; @@ -164,6 +201,84 @@ static void mp3dec_close(struct filter_node *fn) fn->private_data = NULL; } +static void mp3dec_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + int i, ret; + struct private_mp3dec_data *pmd = fn->private_data; + struct btr_node *btrn = fn->btrn; + size_t loaded, used, len = btr_get_input_queue_size(btrn); + char *inbuffer, *outbuffer; + + pmd->stream.error = 0; + t->error = 0; + if (btr_bytes_pending(btrn) > MP3DEC_MAX_PENDING) + return; + if (need_bad_data_delay(pmd, len)) + return; + if (pmd->input_len_barrier != 0 && btr_no_parent(btrn)) { + ret = -E_MP3DEC_EOF; + goto err; + } +next_buffer: + len = btr_next_buffer(btrn, &inbuffer); + if (len == 0) + return; + mad_stream_buffer(&pmd->stream, (unsigned char *)inbuffer, len); +next_frame: + ret = mad_header_decode(&pmd->frame.header, &pmd->stream); + if (ret < 0) { + used = used_mad_buffer_bytes(&pmd->stream, len); + btr_consume(btrn, used); + if (pmd->stream.error == MAD_ERROR_BUFLEN) { + pmd->input_len_barrier = len - used; + ret = btr_merge(btrn); + if (ret != 2) + return; + } else if (pmd->stream.error != MAD_ERROR_LOSTSYNC) + PARA_DEBUG_LOG("header decode: %s\n", + mad_stream_errorstr(&pmd->stream)); + goto next_buffer; + } + pmd->input_len_barrier = 0; + pmd->samplerate = pmd->frame.header.samplerate; + pmd->channels = MAD_NCHANNELS(&pmd->frame.header); + ret = mad_frame_decode(&pmd->frame, &pmd->stream); + if (ret != 0) { + PARA_CRIT_LOG("frame decode: %d\n", ret); + used = used_mad_buffer_bytes(&pmd->stream, len); + ret = handle_decode_error(pmd, used); + btr_consume(btrn, used); + if (ret < 0) + goto err; + if (ret == 0) + goto next_buffer; + return; + } + mad_synth_frame(&pmd->synth, &pmd->frame); + pmd->flags |= MP3DEC_FLAG_DECODE_STARTED; + + outbuffer = para_malloc(pmd->synth.pcm.length * 4); + loaded = 0; + for (i = 0; i < pmd->synth.pcm.length; i++) { + int sample = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); + write_int16_host_endian(outbuffer + loaded, sample); + loaded += 2; + if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */ + sample = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); + write_int16_host_endian(outbuffer + loaded, sample); + loaded += 2; + } + } + btr_add_output(outbuffer, loaded, btrn); + goto next_frame; +err: + assert(ret < 0); + mp3dec_close(fn); + t->error = ret; + btr_del_node(btrn); +} + static void mp3dec_open(struct filter_node *fn) { struct private_mp3dec_data *pmd = para_calloc(sizeof(*pmd)); @@ -217,6 +332,8 @@ void mp3dec_filter_init(struct filter *f) f->convert = mp3dec; f->close = mp3dec_close; f->parse_config = mp3dec_parse_config; + f->pre_select = mp3dec_pre_select; + f->post_select = mp3dec_post_select; f->help = (struct ggo_help) { .short_help = mp3dec_filter_args_info_help, .detailed_help = mp3dec_filter_args_info_detailed_help diff --git a/oggdec_filter.c b/oggdec_filter.c index c158df7e..3de62cbd 100644 --- a/oggdec_filter.c +++ b/oggdec_filter.c @@ -8,12 +8,14 @@ #include #include +#include #include "para.h" #include "oggdec_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" diff --git a/prebuffer_filter.c b/prebuffer_filter.c index f23e4e57..357e86c7 100644 --- a/prebuffer_filter.c +++ b/prebuffer_filter.c @@ -7,12 +7,14 @@ /** \file prebuffer_filter.c Paraslash's prebuffering filter. */ #include +#include #include "para.h" #include "prebuffer_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" diff --git a/stdin.c b/stdin.c index 83636999..d978ee9a 100644 --- a/stdin.c +++ b/stdin.c @@ -52,7 +52,7 @@ static void stdin_pre_select_btr(struct sched *s, struct task *t) { struct stdin_task *sit = container_of(t, struct stdin_task, task); - if (btr_no_children(sit->btrn)) { + if (btr_no_children(sit->btrn)) { /* TODO: defer node deletion to post select */ t->error = -E_STDIN_NO_CHILD; btr_del_node(sit->btrn); sit->btrn = NULL; diff --git a/wav_filter.c b/wav_filter.c index 3c69311f..eada2434 100644 --- a/wav_filter.c +++ b/wav_filter.c @@ -7,12 +7,14 @@ /** \file wav_filter.c A filter that inserts a wave header. */ #include +#include #include "para.h" #include "error.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "portable_io.h" diff --git a/wmadec_filter.c b/wmadec_filter.c index b78d7627..9cc94f4c 100644 --- a/wmadec_filter.c +++ b/wmadec_filter.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -32,6 +33,7 @@ #include "ggo.h" #include "string.h" #include "sched.h" +#include "buffer_tree.h" #include "filter.h" #include "bitstream.h" #include "imdct.h" -- 2.39.2