summaryrefslogtreecommitdiffstats
path: root/a52
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2006-04-06 17:53:07 +0200
committerTakashi Iwai <tiwai@suse.de>2006-04-06 17:53:07 +0200
commit2559a56209c6729574b8cb16c5be63e1cc2f1c04 (patch)
tree054f6917bb3add9e6e34742fbc3c4f2807055d59 /a52
parentbc0c2843db4dea70251e8c5b4a9272ce40caa7f2 (diff)
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.
Diffstat (limited to 'a52')
-rw-r--r--a52/Makefile.am9
-rw-r--r--a52/pcm_a52.c638
2 files changed, 647 insertions, 0 deletions
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 <tiwai@suse.de>
+ *
+ * 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 <stdio.h>
+#include <string.h>
+#define __USE_XOPEN
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+#include <ffmpeg/avcodec.h>
+
+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);