From 2559a56209c6729574b8cb16c5be63e1cc2f1c04 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 6 Apr 2006 17:53:07 +0200 Subject: Add a52 output plugin Added (experimental) a52 output plugin. The plugin requires libavcodec as the audio encoding engine. See doc/a52.txt for the usage. --- Makefile.am | 5 +- a52/Makefile.am | 9 + a52/pcm_a52.c | 638 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ configure.in | 22 ++ doc/a52.txt | 48 +++++ 5 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 a52/Makefile.am create mode 100644 a52/pcm_a52.c create mode 100644 doc/a52.txt diff --git a/Makefile.am b/Makefile.am index 6b9e37a..f33191d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,8 +7,11 @@ endif if HAVE_SAMPLERATE SAMPLERATEDIR = rate endif +if HAVE_AVCODEC +A52DIR = a52 +endif -SUBDIRS = oss mix $(JACKDIR) $(POLYPDIR) $(SAMPLERATEDIR) doc +SUBDIRS = oss mix $(JACKDIR) $(POLYPDIR) $(SAMPLERATEDIR) $(A52DIR) doc EXTRA_DIST = cvscompile version COPYING.GPL AUTOMAKE_OPTIONS = foreign diff --git a/a52/Makefile.am b/a52/Makefile.am new file mode 100644 index 0000000..4021496 --- /dev/null +++ b/a52/Makefile.am @@ -0,0 +1,9 @@ +asound_module_pcm_a52_LTLIBRARIES = libasound_module_pcm_a52.la + +asound_module_pcm_a52dir = $(libdir)/alsa-lib + +AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ @AVCODEC_CFLAGS@ +AM_LDFLAGS = -module -avoid-version -export-dynamic + +libasound_module_pcm_a52_la_SOURCES = pcm_a52.c +libasound_module_pcm_a52_la_LIBADD = @ALSA_LIBS@ @AVCODEC_LIBS@ diff --git a/a52/pcm_a52.c b/a52/pcm_a52.c new file mode 100644 index 0000000..3d376e2 --- /dev/null +++ b/a52/pcm_a52.c @@ -0,0 +1,638 @@ +/* + * A52 Output Plugin + * + * Copyright (c) 2006 by Takashi Iwai + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#define __USE_XOPEN +#include +#include +#include +#include + +struct a52_ctx { + snd_pcm_ioplug_t io; + snd_pcm_t *slave; + AVCodec *codec; + AVCodecContext *avctx; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + unsigned int bitrate; + short *inbuf; + unsigned char *outbuf; + int outbuf_size; + snd_pcm_uframes_t transfer; + int remain; + int filled; + unsigned int slave_period_size; + unsigned int slave_buffer_size; + snd_pcm_hw_params_t *hw_params; +}; + +/* convert to SPDIF output */ +static void convert_data(struct a52_ctx *rec) +{ + int out_bytes; + + out_bytes = avcodec_encode_audio(rec->avctx, rec->outbuf + 8, + rec->outbuf_size - 8, + rec->inbuf); + rec->outbuf[0] = 0x72; + rec->outbuf[1] = 0xf8; + rec->outbuf[2] = 0x1f; + rec->outbuf[3] = 0x4e; + rec->outbuf[4] = 0x01; + rec->outbuf[5] = rec->outbuf[5] & 7; + rec->outbuf[6] = (out_bytes * 8) & 0xff; + rec->outbuf[7] = ((out_bytes * 8) >> 8) & 0xff; + if (rec->format == SND_PCM_FORMAT_S16_LE) + swab(rec->outbuf + 8, rec->outbuf + 8, out_bytes); + memset(rec->outbuf + 8 + out_bytes, 0, + rec->outbuf_size - 8 - out_bytes); + rec->remain = rec->outbuf_size / 4; + rec->filled = 0; +} + +static int write_out_pending(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED, + struct a52_ctx *rec) +{ + int err, ofs = 0; + + if (! rec->remain) + return 0; + + while (rec->remain) { + err = snd_pcm_writei(rec->slave, rec->outbuf + ofs, rec->remain); + if (err < 0) + return err; + else if (! err) + break; + if (err < rec->remain) + ofs += (rec->remain - err) * 4; + rec->remain -= err; + } + if (rec->remain && ofs) + memmove(rec->outbuf, rec->outbuf + ofs, rec->remain * 4); + return 0; +} + +static int a52_drain(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + int err; + + if (rec->filled) { + memset(rec->inbuf + rec->filled * io->channels, 0, + (rec->avctx->frame_size - rec->filled) * io->channels * 2); + convert_data(rec); + } + err = write_out_pending(io, rec); + if (err < 0) + return err; + snd_pcm_drain(rec->slave); + return 0; +} + +static int check_interleaved(const snd_pcm_channel_area_t *areas, + unsigned int channels) +{ + unsigned int ch; + + if (channels > 4) /* we need re-routing for 6 channels */ + return 0; + + for (ch = 0; ch < channels; ch++) { + if (areas[ch].addr != areas[0].addr || + areas[ch].first != ch * 16 || + areas[ch].step != channels * 16) + return 0; + } + return 1; +} + +static int fill_data(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + unsigned int offset, unsigned int size, + int interleaved) +{ + struct a52_ctx *rec = io->private_data; + unsigned int len = rec->avctx->frame_size - rec->filled; + short *src, *dst; + unsigned int src_step; + int err; + + if ((err = write_out_pending(io, rec)) < 0) + return err; + + if (size > len) + size = len; + + dst = rec->inbuf + rec->filled * io->channels; + if (interleaved) { + memcpy(dst, areas->addr + offset * io->channels * 2, + size * io->channels * 2); + } else { + unsigned int i, ch, dst_step; + short *dst1; + static unsigned int ch_index[3][6] = { + { 0, 1 }, + { 0, 1, 2, 3 }, + { 0, 4, 1, 2, 3, 5 }, + }; + /* flatten copy to n-channel interleaved */ + dst_step = io->channels; + for (ch = 0; ch < io->channels; ch++, dst++) { + const snd_pcm_channel_area_t *ap; + ap = &areas[ch_index[io->channels / 2 - 1][ch]]; + dst1 = dst; + src = (short *)(ap->addr + + (ap->first + offset * ap->step) / 8); + src_step = ap->step / 16; /* in word */ + for (i = 0; i < size; i++) { + *dst1 = *src; + src += src_step; + dst1 += dst_step; + } + } + } + rec->filled += size; + if (rec->filled == rec->avctx->frame_size) { + convert_data(rec); + write_out_pending(io, rec); + } + return (int)size; +} + +static snd_pcm_sframes_t a52_transfer(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct a52_ctx *rec = io->private_data; + snd_pcm_sframes_t result = 0; + int err = 0; + int interleaved = check_interleaved(areas, io->channels); + + do { + err = fill_data(io, areas, offset, size, interleaved); + if (err < 0) + break; + offset += (unsigned int)err; + size -= (unsigned int)err; + result += err; + rec->transfer += err; + } while (size); + return result > 0 ? result : err; +} + +static snd_pcm_sframes_t a52_pointer(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + snd_pcm_sframes_t delay; + snd_pcm_state_t state; + int err; + + state = snd_pcm_state(rec->slave); + if (state == SND_PCM_STATE_RUNNING || + state == SND_PCM_STATE_DRAINING) { + if ((err = snd_pcm_delay(rec->slave, &delay)) < 0) + return err; + } else + return 0; + + if (delay < 0 || delay >= (snd_pcm_sframes_t)rec->slave_buffer_size) + delay = 0; + delay = (snd_pcm_sframes_t)io->appl_ptr - delay; + if (delay < 0) { + delay += io->buffer_size; + if (delay < 0) + delay = 0; + } + delay %= io->buffer_size; + return delay; +} + +static int a52_slave_hw_params_half(struct a52_ctx *rec) +{ + int err; + + if ((err = snd_pcm_hw_params_malloc(&rec->hw_params)) < 0) + return err; + + if ((err = snd_pcm_hw_params_any(rec->slave, rec->hw_params)) < 0) { + SNDERR("Cannot get slave hw_params"); + goto out; + } + if ((err = snd_pcm_hw_params_set_access(rec->slave, rec->hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + SNDERR("Cannot set slave access RW_INTERLEAVED"); + goto out; + } + if ((err = snd_pcm_hw_params_set_channels(rec->slave, rec->hw_params, 2)) < 0) { + SNDERR("Cannot set slave channels 2"); + goto out; + } + if ((err = snd_pcm_hw_params_set_format(rec->slave, rec->hw_params, + rec->format)) < 0) { + SNDERR("Cannot set slave format"); + goto out; + } + if ((err = snd_pcm_hw_params_set_rate(rec->slave, rec->hw_params, rec->rate, 0)) < 0) { + SNDERR("Cannot set slave rate %d", rec->rate); + goto out; + } + return 0; + + out: + free(rec->hw_params); + rec->hw_params = NULL; + return err; +} + +static int a52_hw_params(snd_pcm_ioplug_t *io, + snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED) +{ + struct a52_ctx *rec = io->private_data; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + int err; + + if (! rec->hw_params) { + err = a52_slave_hw_params_half(rec); + if (err < 0) + return err; + } + period_size = io->period_size; + if ((err = snd_pcm_hw_params_set_period_size_near(rec->slave, rec->hw_params, + &period_size, NULL)) < 0) { + SNDERR("Cannot set slave period size %ld", period_size); + return err; + } + buffer_size = io->buffer_size; + if ((err = snd_pcm_hw_params_set_buffer_size_near(rec->slave, rec->hw_params, + &buffer_size)) < 0) { + SNDERR("Cannot set slave buffer size %ld", buffer_size); + return err; + } + if ((err = snd_pcm_hw_params(rec->slave, rec->hw_params)) < 0) { + SNDERR("Cannot set slave hw_params"); + return err; + } + rec->slave_period_size = period_size; + rec->slave_buffer_size = buffer_size; + + return 0; +} + +static int a52_hw_free(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + + free(rec->hw_params); + rec->hw_params = NULL; + return snd_pcm_hw_free(rec->slave); +} + +static int a52_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) +{ + struct a52_ctx *rec = io->private_data; + snd_pcm_sw_params_t *sparams; + snd_pcm_uframes_t avail_min, start_threshold; + int len; + + snd_pcm_sw_params_get_avail_min(params, &avail_min); + snd_pcm_sw_params_get_start_threshold(params, &start_threshold); + + len = avail_min; + len += (int)rec->slave_buffer_size - (int)io->buffer_size; + if (len < 0) + avail_min = 1; + else + avail_min = len; + snd_pcm_sw_params_alloca(&sparams); + snd_pcm_sw_params_current(rec->slave, sparams); + snd_pcm_sw_params_set_avail_min(rec->slave, sparams, avail_min); + snd_pcm_sw_params_set_start_threshold(rec->slave, sparams, + start_threshold); + + return snd_pcm_sw_params(rec->slave, sparams); +} + +static int a52_start(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + + snd_pcm_start(rec->slave); + return 0; +} + +static int a52_stop(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + + snd_pcm_drop(rec->slave); + return 0; +} + +static void a52_free(struct a52_ctx *rec) +{ + if (rec->avctx) { + avcodec_close(rec->avctx); + av_free(rec->avctx); + rec->avctx = NULL; + } + free(rec->inbuf); + rec->inbuf = NULL; + free(rec->outbuf); + rec->outbuf = NULL; +} + +static int a52_prepare(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + + a52_free(rec); + + rec->avctx = avcodec_alloc_context(); + if (! rec->avctx) + return -ENOMEM; + + rec->avctx->bit_rate = rec->bitrate * 1000; + rec->avctx->sample_rate = io->rate; + rec->avctx->channels = io->channels; + + if (avcodec_open(rec->avctx, rec->codec) < 0) + return -EINVAL; + + rec->inbuf = malloc(rec->avctx->frame_size * 2 * io->channels); + if (! rec->inbuf) + return -ENOMEM; + rec->outbuf_size = rec->avctx->frame_size * 4; + rec->outbuf = malloc(rec->outbuf_size); + if (! rec->outbuf) + return -ENOMEM; + + rec->transfer = 0; + rec->remain = 0; + rec->filled = 0; + + return snd_pcm_prepare(rec->slave); +} + +static int a52_poll_descriptors_count(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + return snd_pcm_poll_descriptors_count(rec->slave); +} + +static int a52_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, + unsigned int space) +{ + struct a52_ctx *rec = io->private_data; + return snd_pcm_poll_descriptors(rec->slave, pfd, space); +} + +static int a52_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, + unsigned int nfds, unsigned short *revents) +{ + struct a52_ctx *rec = io->private_data; + return snd_pcm_poll_descriptors_revents(rec->slave, pfd, nfds, revents); +} + +static int a52_close(snd_pcm_ioplug_t *io) +{ + struct a52_ctx *rec = io->private_data; + + a52_free(rec); + if (rec->slave) + snd_pcm_close(rec->slave); + return 0; +} + +static snd_pcm_ioplug_callback_t a52_ops = { + .start = a52_start, + .stop = a52_stop, + .pointer = a52_pointer, + .transfer = a52_transfer, + .close = a52_close, + .hw_params = a52_hw_params, + .hw_free = a52_hw_free, + .sw_params = a52_sw_params, + .prepare = a52_prepare, + .drain = a52_drain, + .poll_descriptors_count = a52_poll_descriptors_count, + .poll_descriptors = a52_poll_descriptors, + .poll_revents = a52_poll_revents, +}; + +#define A52_FRAME_SIZE 1536 + +#define ARRAY_SIZE(ary) (sizeof(ary)/sizeof(ary[0])) + +static int a52_set_hw_constraint(struct a52_ctx *rec) +{ + unsigned int accesses[] = { + SND_PCM_ACCESS_MMAP_INTERLEAVED, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + SND_PCM_ACCESS_RW_INTERLEAVED, + SND_PCM_ACCESS_RW_NONINTERLEAVED + }; + unsigned int formats[] = { SND_PCM_FORMAT_S16 }; + int err; + snd_pcm_uframes_t buffer_max; + unsigned int period_bytes, max_periods; + + if ((err = snd_pcm_ioplug_set_param_list(&rec->io, SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_SIZE(accesses), accesses)) < 0 || + (err = snd_pcm_ioplug_set_param_list(&rec->io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_SIZE(formats), formats)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&rec->io, SND_PCM_IOPLUG_HW_CHANNELS, + rec->channels, rec->channels)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&rec->io, SND_PCM_IOPLUG_HW_RATE, + rec->rate, rec->rate)) < 0) + return err; + + if ((err = a52_slave_hw_params_half(rec)) < 0) + return err; + + snd_pcm_hw_params_get_buffer_size_max(rec->hw_params, &buffer_max); + period_bytes = A52_FRAME_SIZE * 2 * rec->channels; + max_periods = buffer_max / A52_FRAME_SIZE; + + if ((err = snd_pcm_ioplug_set_param_minmax(&rec->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, + period_bytes, period_bytes)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&rec->io, SND_PCM_IOPLUG_HW_PERIODS, + 2, max_periods)) < 0) + return err; + + return 0; +} + +SND_PCM_PLUGIN_DEFINE_FUNC(a52) +{ + snd_config_iterator_t i, next; + int err; + const char *card = NULL; + unsigned int rate = 48000; + unsigned int bitrate = 448; + unsigned int channels = 6; + snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; + char devstr[128]; + struct a52_ctx *rec; + + if (stream != SND_PCM_STREAM_PLAYBACK) { + SNDERR("a52 is only for playback"); + return -EINVAL; + } + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0) + continue; + if (strcmp(id, "card") == 0) { + if (snd_config_get_string(n, &card) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + if (strcmp(id, "rate") == 0) { + long val; + if (snd_config_get_integer(n, &val) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + rate = val; + if (rate != 44100 && rate != 48000) { + SNDERR("rate must be 44100 or 48000"); + return -EINVAL; + } + continue; + } + if (strcmp(id, "bitrate") == 0) { + long val; + if (snd_config_get_integer(n, &val) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + bitrate = val; + if (bitrate < 128 || bitrate > 1000) { + SNDERR("Invalid bitrate value %d", bitrate); + return -EINVAL; + } + continue; + } + if (strcmp(id, "channels") == 0) { + long val; + if (snd_config_get_integer(n, &val) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + channels = val; + if (channels != 2 && channels != 4 && channels != 6) { + SNDERR("channels must be 2, 4 or 6"); + return -EINVAL; + } + continue; + } + if (strcmp(id, "format") == 0) { + const char *str; + err = snd_config_get_string(n, &str); + if (err < 0) { + SNDERR("invalid type for %s", id); + return -EINVAL; + } + format = snd_pcm_format_value(str); + if (format == SND_PCM_FORMAT_UNKNOWN) { + SNDERR("unknown format %s", str); + return -EINVAL; + } + if (format != SND_PCM_FORMAT_S16_LE && + format != SND_PCM_FORMAT_S16_BE) { + SNDERR("Only S16_LE/BE formats are allowed"); + return -EINVAL; + } + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + rec = calloc(1, sizeof(*rec)); + if (! rec) { + SNDERR("cannot allocate"); + return -ENOMEM; + } + + rec->rate = rate; + rec->bitrate = bitrate; + rec->channels = channels; + rec->format = format; + + avcodec_register_all(); + rec->codec = avcodec_find_encoder(CODEC_ID_AC3); + if (! rec->codec) { + SNDERR("Cannot find codec engine"); + err = -EINVAL; + goto error; + } + + snprintf(devstr, sizeof(devstr), + "plug:iec958:{AES0 0x%x AES1 0x%x AES2 0x%x AES3 0x%x %s%s}", + IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO | + IEC958_AES0_CON_NOT_COPYRIGHT, + IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER, + 0, rate == 48000 ? IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100, + card ? " CARD " : "", + card ? card : ""); + + err = snd_pcm_open(&rec->slave, devstr, stream, mode); + if (err < 0) + goto error; + + rec->io.version = SND_PCM_IOPLUG_VERSION; + rec->io.name = "A52 Output Plugin"; + rec->io.mmap_rw = 0; + rec->io.callback = &a52_ops; + rec->io.private_data = rec; + + err = snd_pcm_ioplug_create(&rec->io, name, stream, mode); + if (err < 0) + goto error; + + if ((err = a52_set_hw_constraint(rec)) < 0) { + snd_pcm_ioplug_delete(&rec->io); + return err; + } + + *pcmp = rec->io.pcm; + return 0; + + error: + if (rec->slave) + snd_pcm_close(rec->slave); + free(rec); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(a52); diff --git a/configure.in b/configure.in index 7691770..b45ee56 100644 --- a/configure.in +++ b/configure.in @@ -22,6 +22,27 @@ AM_CONDITIONAL(HAVE_POLYP, test x$HAVE_POLYP = xyes) PKG_CHECK_MODULES(samplerate, [samplerate], [HAVE_SAMPLERATE=yes], [HAVE_SAMPLERATE=no]) AM_CONDITIONAL(HAVE_SAMPLERATE, test x$HAVE_SAMPLERATE = xyes) +AC_ARG_WITH([avcodec-includedir], + [--with-avcodec-includedir=dir AVcodec include directory], + [AVCODEC_CFLAGS="-I$withval"], [AVCODEC_CFLAGS=""]) +AC_ARG_WITH([avcodec-libdir], + [--with-avcodec-libdir=dir AVcodec library directory], + [AVCODEC_LIBS="-L$withval"], [AVCODEC_LIBS=""]) +CFLAGS_saved="$CFLAGS" +LDFLAGS_saved="$LDFLAGS" +CFLAGS="$CFLAGS $AVCODEC_CFLAGS" +LDFLAGS="$LDFLAGS $AVCODEC_LIBS" +AC_SUBST(AVCODEC_CFLAGS) +AVCODEC_LIBS="$AVCODEC_LIBS -lavcodec" +AC_SUBST(AVCODEC_LIBS) +AC_CHECK_LIB([avcodec], [avcodec_open], [HAVE_AVCODEC=yes], [HAVE_AVCODEC=no]) +if test x$HAVE_AVCODEC = xyes; then + AC_CHECK_HEADER([ffmpeg/avcodec.h], [], [HAVE_AVCODEC=no]) +fi +AM_CONDITIONAL(HAVE_AVCODEC, test x$HAVE_AVCODEC = xyes) +CFLAGS="$CFLAGS_saved" +LDFLAGS="$LDFLAGS_saved" + SAVE_PLUGINS_VERSION AC_OUTPUT([ @@ -31,5 +52,6 @@ AC_OUTPUT([ polyp/Makefile mix/Makefile rate/Makefile + a52/Makefile doc/Makefile ]) diff --git a/doc/a52.txt b/doc/a52.txt new file mode 100644 index 0000000..236e64a --- /dev/null +++ b/doc/a52.txt @@ -0,0 +1,48 @@ +A52 OUTPUT PLUGIN +================= + +This plugin converts S16 linear format to A52 compressed stream and +send to an SPDIF output. It requires libavcodec for encoding the +audio stream. + +A PCM using this plugin can be defined like below: + + pcm.myout { + type a52 + } + +In addition, the following options are available: + +- The "card" option specifies the card ID or number of the SPDIF. + The output PCM becomes "iec958:{CARD=$CARD}" with extra AESx + settings. When omitted, the default card is used. + +- The "rate" option specifies the input/output sample rate in HZ. + The accepted rate is either 44100 or 48000. + When omitted, 48000 is used. + +- The "channels" option specifies the number of _input_ channels. + It must be either 2, 4 or 6. The default value is 6. + +- The "bitrate" option specifies the bit-rate of the compressed + stream in kbps. Too small or too big value may not be accepted by + the encoder. When omitted, 448 is used. + +- The "format" option specifies the output format type. It's either + S16_LE or S16_BE. As default, S16_LE is used. + +An example using the secondary card, 44.1kHz, 4 channels, output +bitrate 256kbps and output format S16_BE looks like below: + + pcm.myout { + type a52 + card 1 + rate 44100 + channels 4 + bitrate 256 + format S16_BE + } + + +The plugin reads always S16 format (i.e. native-endian) as input, so +you'd need plug layer appropriately to covert it. -- cgit