summaryrefslogtreecommitdiff
path: root/oss.c
diff options
context:
space:
mode:
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2003-11-13 01:46:15 +0000
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2003-11-13 01:46:15 +0000
commit275033239681346347766ac3712e3af792a0304f (patch)
tree5b5a392ab0c2d472577a297186ff7f74487e29b5 /oss.c
parentf115e911d70dedbbab90efe589b267bf53178f10 (diff)
downloadqemu-275033239681346347766ac3712e3af792a0304f.tar.gz
Soundblaster 16 support (malc)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@455 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'oss.c')
-rw-r--r--oss.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/oss.c b/oss.c
new file mode 100644
index 0000000000..4210799eb7
--- /dev/null
+++ b/oss.c
@@ -0,0 +1,512 @@
+/*
+ * QEMU OSS Audio output driver
+ *
+ * Copyright (c) 2003 Vassili Karpov (malc)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include "vl.h"
+
+/* http://www.df.lth.se/~john_e/gems/gem002d.html */
+/* http://www.multi-platforms.com/Tips/PopCount.htm */
+static inline uint32_t popcount (uint32_t u)
+{
+ u = ((u&0x55555555) + ((u>>1)&0x55555555));
+ u = ((u&0x33333333) + ((u>>2)&0x33333333));
+ u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f));
+ u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff));
+ u = ( u&0x0000ffff) + (u>>16);
+ return u;
+}
+
+static inline uint32_t lsbindex (uint32_t u)
+{
+ return popcount ((u&-u)-1);
+}
+
+#define MIN(a, b) ((a)>(b)?(b):(a))
+#define MAX(a, b) ((a)<(b)?(b):(a))
+
+#define DEREF(x) (void)x
+#define log(...) fprintf (stderr, "oss: " __VA_ARGS__)
+#define ERRFail(...) do { \
+ int _errno = errno; \
+ fprintf (stderr, "oss: " __VA_ARGS__); \
+ fprintf (stderr, "system error: %s\n", strerror (_errno)); \
+ abort (); \
+} while (0)
+#define Fail(...) do { \
+ fprintf (stderr, "oss: " __VA_ARGS__); \
+ fprintf (stderr, "\n"); \
+ abort (); \
+} while (0)
+
+#ifdef DEBUG_OSS
+#define lwarn(...) fprintf (stderr, "oss: " __VA_ARGS__)
+#define linfo(...) fprintf (stderr, "oss: " __VA_ARGS__)
+#define ldebug(...) fprintf (stderr, "oss: " __VA_ARGS__)
+#else
+#define lwarn(...)
+#define linfo(...)
+#define ldebug(...)
+#endif
+
+
+#define IOCTL(args) do { \
+ int ret = ioctl args; \
+ if (-1 == ret) { \
+ ERRFail (#args); \
+ } \
+ ldebug ("ioctl " #args " = %d\n", ret); \
+} while (0)
+
+static int audio_fd = -1;
+static int freq;
+static int conf_nfrags = 4;
+static int conf_fragsize;
+static int nfrags;
+static int fragsize;
+static int bufsize;
+static int nchannels;
+static int fmt;
+static int rpos;
+static int wpos;
+static int atom;
+static int live;
+static int leftover;
+static int bytes_per_second;
+static void *buf;
+static enum {DONT, DSP, TID} estimate = TID;
+
+static void (*copy_fn)(void *, void *, int);
+
+static void copy_no_conversion (void *dst, void *src, int size)
+{
+ memcpy (dst, src, size);
+}
+
+static void copy_u16_to_s16 (void *dst, void *src, int size)
+{
+ int i;
+ uint16_t *out, *in;
+
+ out = dst;
+ in = src;
+
+ for (i = 0; i < size / 2; i++) {
+ out[i] = in[i] + 0x8000;
+ }
+}
+
+static void pab (struct audio_buf_info *abinfo)
+{
+ DEREF (abinfo);
+
+ ldebug ("fragments %d, fragstotal %d, fragsize %d, bytes %d\n"
+ "rpos %d, wpos %d, live %d\n",
+ abinfo->fragments,
+ abinfo->fragstotal,
+ abinfo->fragsize,
+ abinfo->bytes,
+ rpos, wpos, live);
+}
+
+void AUD_reset (int rfreq, int rnchannels, audfmt_e rfmt)
+{
+ int fmt_;
+ int bits16;
+
+ if (-1 == audio_fd) {
+ AUD_open (rfreq, rnchannels, rfmt);
+ return;
+ }
+
+ switch (rfmt) {
+ case AUD_FMT_U8:
+ bits16 = 0;
+ fmt_ = AFMT_U8;
+ copy_fn = copy_no_conversion;
+ atom = 1;
+ break;
+
+ case AUD_FMT_S8:
+ Fail ("can not play 8bit signed");
+
+ case AUD_FMT_S16:
+ bits16 = 1;
+ fmt_ = AFMT_S16_LE;
+ copy_fn = copy_no_conversion;
+ atom = 2;
+ break;
+
+ case AUD_FMT_U16:
+ bits16 = 1;
+ fmt_ = AFMT_S16_LE;
+ copy_fn = copy_u16_to_s16;
+ atom = 2;
+ break;
+
+ default:
+ abort ();
+ }
+
+ if ((fmt_ == fmt) && (bits16 + 1 == nchannels) && (rfreq == freq))
+ return;
+ else {
+ AUD_open (rfreq, rnchannels, rfmt);
+ }
+}
+
+void AUD_open (int rfreq, int rnchannels, audfmt_e rfmt)
+{
+ int fmt_;
+ int mmmmssss;
+ struct audio_buf_info abinfo;
+ int _fmt;
+ int _freq;
+ int _nchannels;
+ int bits16;
+
+ bits16 = 0;
+
+ switch (rfmt) {
+ case AUD_FMT_U8:
+ bits16 = 0;
+ fmt_ = AFMT_U8;
+ copy_fn = copy_no_conversion;
+ atom = 1;
+ break;
+
+ case AUD_FMT_S8:
+ Fail ("can not play 8bit signed");
+
+ case AUD_FMT_S16:
+ bits16 = 1;
+ fmt_ = AFMT_S16_LE;
+ copy_fn = copy_no_conversion;
+ atom = 2;
+ break;
+
+ case AUD_FMT_U16:
+ bits16 = 1;
+ fmt_ = AFMT_S16_LE;
+ copy_fn = copy_u16_to_s16;
+ atom = 2;
+ break;
+
+ default:
+ abort ();
+ }
+
+ if (buf) {
+ free (buf);
+ buf = 0;
+ }
+
+ if (-1 != audio_fd)
+ close (audio_fd);
+
+ audio_fd = open ("/dev/dsp", O_WRONLY | O_NONBLOCK);
+ if (-1 == audio_fd) {
+ ERRFail ("can not open /dev/dsp");
+ }
+
+ _fmt = fmt_;
+ _freq = rfreq;
+ _nchannels = rnchannels;
+
+ IOCTL ((audio_fd, SNDCTL_DSP_RESET, 1));
+ IOCTL ((audio_fd, SNDCTL_DSP_SAMPLESIZE, &_fmt));
+ IOCTL ((audio_fd, SNDCTL_DSP_CHANNELS, &_nchannels));
+ IOCTL ((audio_fd, SNDCTL_DSP_SPEED, &_freq));
+ IOCTL ((audio_fd, SNDCTL_DSP_NONBLOCK));
+
+ /* from oss.pdf:
+
+ The argument to this call is an integer encoded as 0xMMMMSSSS (in
+ hex). The 16 least significant bits determine the fragment
+ size. The size is 2^SSSS. For examp le SSSS=0008 gives fragment
+ size of 256 bytes (2^8). The minimum is 16 bytes (SSSS=4) and the
+ maximum is total_buffer_size/2. Some devices or processor
+ architectures may require larger fragments - in this case the
+ requested fragment size is automatically increased.
+
+ So ahem... 4096 = 2^12, and grand total 0x0004000c
+ */
+
+ mmmmssss = (conf_nfrags << 16) | conf_fragsize;
+ IOCTL ((audio_fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss));
+
+ linfo ("_fmt = %d, fmt = %d\n"
+ "_channels = %d, rnchannels = %d\n"
+ "_freq = %d, freq = %d\n",
+ _fmt, fmt_,
+ _nchannels, rnchannels,
+ _freq, rfreq);
+
+ if (_fmt != fmt_) {
+ Fail ("format %d != %d", _fmt, fmt_);
+ }
+
+ if (_nchannels != rnchannels) {
+ Fail ("channels %d != %d", _nchannels, rnchannels);
+ }
+
+ if (_freq != rfreq) {
+ Fail ("freq %d != %d", _freq, rfreq);
+ }
+
+ IOCTL ((audio_fd, SNDCTL_DSP_GETOSPACE, &abinfo));
+
+ nfrags = abinfo.fragstotal;
+ fragsize = abinfo.fragsize;
+ freq = _freq;
+ fmt = _fmt;
+ nchannels = rnchannels;
+ atom <<= nchannels >> 1;
+ bufsize = nfrags * fragsize;
+
+ bytes_per_second = (freq << (nchannels >> 1)) << bits16;
+
+ linfo ("bytes per second %d\n", bytes_per_second);
+
+ linfo ("fragments %d, fragstotal %d, fragsize %d, bytes %d, bufsize %d\n",
+ abinfo.fragments,
+ abinfo.fragstotal,
+ abinfo.fragsize,
+ abinfo.bytes,
+ bufsize);
+
+ if (NULL == buf) {
+ buf = malloc (bufsize);
+ if (NULL == buf) {
+ abort ();
+ }
+ }
+
+ rpos = 0;
+ wpos = 0;
+ live = 0;
+}
+
+int AUD_write (void *in_buf, int size)
+{
+ int to_copy, temp;
+ uint8_t *in, *out;
+
+ to_copy = MIN (bufsize - live, size);
+
+ temp = to_copy;
+
+ in = in_buf;
+ out = buf;
+
+ while (temp) {
+ int copy;
+
+ copy = MIN (temp, bufsize - wpos);
+ copy_fn (out + wpos, in, copy);
+
+ wpos += copy;
+ if (wpos == bufsize) {
+ wpos = 0;
+ }
+
+ temp -= copy;
+ in += copy;
+ live += copy;
+ }
+
+ return to_copy;
+}
+
+void AUD_run (void)
+{
+ int res;
+ int bytes;
+ struct audio_buf_info abinfo;
+
+ if (0 == live)
+ return;
+
+ res = ioctl (audio_fd, SNDCTL_DSP_GETOSPACE, &abinfo);
+
+ if (-1 == res) {
+ int err;
+
+ err = errno;
+ lwarn ("SNDCTL_DSP_GETOSPACE failed with %s\n", strerror (err));
+ }
+
+ bytes = abinfo.bytes;
+ bytes = MIN (live, bytes);
+#if 0
+ bytes = (bytes / fragsize) * fragsize;
+#endif
+
+ while (bytes) {
+ int left, play, written;
+
+ left = bufsize - rpos;
+ play = MIN (left, bytes);
+ written = write (audio_fd, (void *) ((uint32_t) buf + rpos), play);
+
+ if (-1 == written) {
+ if (EAGAIN == errno || EINTR == errno) {
+ return;
+ }
+ else {
+ ERRFail ("write audio");
+ }
+ }
+
+ play = written;
+ live -= play;
+ rpos += play;
+ bytes -= play;
+
+ if (rpos == bufsize) {
+ rpos = 0;
+ }
+ }
+}
+
+static int get_dsp_bytes (void)
+{
+ int res;
+ struct count_info info;
+
+ res = ioctl (audio_fd, SNDCTL_DSP_GETOPTR, &info);
+ if (-1 == res) {
+ int err;
+
+ err = errno;
+ lwarn ("SNDCTL_DSP_GETOPTR failed with %s\n", strerror (err));
+ return -1;
+ }
+ else {
+ ldebug ("bytes %d\n", info.bytes);
+ return info.bytes;
+ }
+}
+
+void AUD_adjust_estimate (int _leftover)
+{
+ leftover = _leftover;
+}
+
+int AUD_get_free (void)
+{
+ int free, elapsed;
+
+ free = bufsize - live;
+
+ if (0 == free)
+ return 0;
+
+ elapsed = free;
+ switch (estimate) {
+ case DONT:
+ break;
+
+ case DSP:
+ {
+ static int old_bytes;
+ int bytes;
+
+ bytes = get_dsp_bytes ();
+ if (bytes <= 0)
+ return free;
+
+ elapsed = bytes - old_bytes;
+ old_bytes = bytes;
+ ldebug ("dsp elapsed %d bytes\n", elapsed);
+ break;
+ }
+
+ case TID:
+ {
+ static uint64_t old_ticks;
+ uint64_t ticks, delta;
+ uint64_t ua_elapsed;
+ uint64_t al_elapsed;
+
+ ticks = cpu_get_ticks ();
+ delta = ticks - old_ticks;
+ old_ticks = ticks;
+
+ ua_elapsed = (delta * bytes_per_second) / ticks_per_sec;
+ al_elapsed = ua_elapsed & ~3ULL;
+
+ ldebug ("tid elapsed %llu bytes\n", ua_elapsed);
+
+ if (al_elapsed > (uint64_t) INT_MAX)
+ elapsed = INT_MAX;
+ else
+ elapsed = al_elapsed;
+
+ elapsed += leftover;
+ }
+ }
+
+ if (elapsed > free) {
+ lwarn ("audio can not keep up elapsed %d free %d\n", elapsed, free);
+ return free;
+ }
+ else {
+ return elapsed;
+ }
+}
+
+int AUD_get_live (void)
+{
+ return live;
+}
+
+int AUD_get_buffer_size (void)
+{
+ return bufsize;
+}
+
+void AUD_init (void)
+{
+ int fsp;
+ int _fragsize = 4096;
+
+ DEREF (pab);
+
+ fsp = _fragsize;
+ if (0 != (fsp & (fsp - 1))) {
+ Fail ("fragment size %d is not power of 2", fsp);
+ }
+
+ conf_fragsize = lsbindex (fsp);
+}