diff --git a/Makefile b/Makefile index 0c9eabc806..54d354ffc4 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ include config.mak SOURCES_AUDIO_INPUT-$(ALSA) += stream/ai_alsa1x.c SOURCES_AUDIO_INPUT-$(OSS) += stream/ai_oss.c +SOURCES_AUDIO_INPUT-$(SNDIO) += stream/ai_sndio.c SOURCES-$(AUDIO_INPUT) += $(SOURCES_AUDIO_INPUT-yes) SOURCES-$(CDDA) += stream/stream_cdda.c \ stream/cdinfo.c @@ -111,6 +112,7 @@ SOURCES-$(OSS) += audio/out/ao_oss.c SOURCES-$(PULSE) += audio/out/ao_pulse.c SOURCES-$(PORTAUDIO) += audio/out/ao_portaudio.c SOURCES-$(RSOUND) += audio/out/ao_rsound.c +SOURCES-$(SNDIO) += audio/out/ao_sndio.c SOURCES-$(VDPAU) += video/vdpau.c video/out/vo_vdpau.c SOURCES-$(VDA) += video/decode/vda.c SOURCES-$(VDPAU_DEC) += video/decode/vdpau.c diff --git a/audio/out/ao.c b/audio/out/ao.c index 1c7cfcbcae..566ab0fea9 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -35,6 +35,7 @@ extern const struct ao_driver audio_out_oss; extern const struct ao_driver audio_out_coreaudio; extern const struct ao_driver audio_out_rsound; +extern const struct ao_driver audio_out_sndio; extern const struct ao_driver audio_out_pulse; extern const struct ao_driver audio_out_jack; extern const struct ao_driver audio_out_openal; @@ -55,6 +56,9 @@ static const struct ao_driver * const audio_out_drivers[] = { #ifdef CONFIG_PULSE &audio_out_pulse, #endif +#ifdef CONFIG_SNDIO + &audio_out_sndio, +#endif #ifdef CONFIG_ALSA &audio_out_alsa, #endif diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c new file mode 100644 index 0000000000..0a1f9f3038 --- /dev/null +++ b/audio/out/ao_sndio.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2008 Alexandre Ratchov + * Copyright (c) 2013 Christian Neukirchen + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "mpvcore/m_option.h" +#include "mpvcore/mp_msg.h" + +#include "audio/format.h" +#include "ao.h" + +struct priv { + struct sio_hdl *hdl; + struct sio_par par; + int delay; + int vol; + int havevol; +#define SILENCE_NMAX 0x1000 + char silence[SILENCE_NMAX]; + struct pollfd *pfd; + char *dev; +}; + +/* + * misc parameters (volume, etc...) + */ +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + ao_control_vol_t *vol = arg; + + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + vol->left = vol->right = p->vol * 100 / SIO_MAXVOL; + break; + case AOCONTROL_SET_VOLUME: + if (!p->havevol) + return CONTROL_FALSE; + sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100); + break; + default: + return CONTROL_UNKNOWN; + } + return CONTROL_OK; +} + +/* + * call-back invoked to notify of the hardware position + */ +static void movecb(void *addr, int delta) +{ + struct priv *p = addr; + p->delay -= delta * (int)(p->par.bps * p->par.pchan); +} + +/* + * call-back invoked to notify about volume changes + */ +static void volcb(void *addr, unsigned newvol) +{ + struct priv *p = addr; + p->vol = newvol; +} + +static const struct mp_chmap sndio_layouts[MP_NUM_CHANNELS + 1] = { + {0}, // empty + {1, {MP_SPEAKER_ID_FL}}, // mono + MP_CHMAP2(FL, FR), // stereo + {0}, // 2.1 + MP_CHMAP4(FL, FR, BL, BR), // 4.0 + {0}, // 5.0 + MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1 + {0}, // 6.1 + MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1 + /* above is the fixed channel assignment for sndio, since we need to fill + all channels and cannot insert silence, not all layouts are supported. */ +}; + +/* + * open device and setup parameters + * return: 0=success -1=fail + */ +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + + struct af_to_par { + int format, bits, sig, le; + } static const af_to_par[] = { + {AF_FORMAT_U8, 8, 0, 0}, + {AF_FORMAT_S8, 8, 1, 0}, + {AF_FORMAT_U16_LE, 16, 0, 1}, + {AF_FORMAT_U16_BE, 16, 0, 0}, + {AF_FORMAT_S16_LE, 16, 1, 1}, + {AF_FORMAT_S16_BE, 16, 1, 0}, + {AF_FORMAT_U24_LE, 16, 0, 1}, + {AF_FORMAT_U24_BE, 24, 0, 0}, + {AF_FORMAT_S24_LE, 24, 1, 1}, + {AF_FORMAT_S24_BE, 24, 1, 0}, + {AF_FORMAT_U32_LE, 32, 0, 1}, + {AF_FORMAT_U32_BE, 32, 0, 0}, + {AF_FORMAT_S32_LE, 32, 1, 1}, + {AF_FORMAT_S32_BE, 32, 1, 0} + }, *ap; + int i; + + p->hdl = sio_open(p->dev, SIO_PLAY, 0); + if (p->hdl == NULL) { + MP_ERR(ao, "can't open sndio %s\n", p->dev); + goto error; + } + sio_initpar(&p->par); + for (i = 0, ap = af_to_par;; i++, ap++) { + if (i == sizeof(af_to_par) / sizeof(struct af_to_par)) { + MP_VERBOSE(ao, "unsupported format\n"); + p->par.bits = 16; + p->par.sig = 1; + p->par.le = SIO_LE_NATIVE; + break; + } + if (ap->format == ao->format) { + p->par.bits = ap->bits; + p->par.sig = ap->sig; + if (ap->bits > 8) + p->par.le = ap->le; + if (ap->bits != SIO_BPS(ap->bits)) + p->par.bps = ap->bits / 8; + break; + } + } + p->par.rate = ao->samplerate; + + struct mp_chmap_sel sel = {0}; + for (int n = 0; n < MP_NUM_CHANNELS+1; n++) + mp_chmap_sel_add_map(&sel, &sndio_layouts[n]); + + if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) + goto error; + + p->par.pchan = ao->channels.num; + p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */ + p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */ + if (!sio_setpar(p->hdl, &p->par)) { + MP_ERR(ao, "couldn't set params\n"); + goto error; + } + if (!sio_getpar(p->hdl, &p->par)) { + MP_ERR(ao, "couldn't get params\n"); + goto error; + } + if (p->par.bits == 8 && p->par.bps == 1) { + ao->format = p->par.sig ? AF_FORMAT_S8 : AF_FORMAT_U8; + } else if (p->par.bits == 16 && p->par.bps == 2) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S16_LE : AF_FORMAT_S16_BE) : + (p->par.le ? AF_FORMAT_U16_LE : AF_FORMAT_U16_BE); + } else if ((p->par.bits == 24 || p->par.msb) && p->par.bps == 3) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S24_LE : AF_FORMAT_S24_BE) : + (p->par.le ? AF_FORMAT_U24_LE : AF_FORMAT_U24_BE); + } else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4) { + ao->format = p->par.sig ? + (p->par.le ? AF_FORMAT_S32_LE : AF_FORMAT_S32_BE) : + (p->par.le ? AF_FORMAT_U32_LE : AF_FORMAT_U32_BE); + } else { + MP_ERR(ao, "couldn't set format\n"); + goto error; + } + + ao->bps = p->par.bps * p->par.pchan * p->par.rate; + ao->no_persistent_volume = true; + p->havevol = sio_onvol(p->hdl, volcb, p); + sio_onmove(p->hdl, movecb, p); + p->delay = 0; + if (!sio_start(p->hdl)) + MP_ERR(ao, "init: couldn't start\n"); + + p->pfd = calloc (sio_nfds(p->hdl), sizeof (struct pollfd)); + if (!p->pfd) + goto error; + + return 0; + +error: + if (p->hdl) + sio_close(p->hdl); + + return -1; +} + +/* + * close device + */ +static void uninit(struct ao *ao, bool immed) +{ + struct priv *p = ao->priv; + + if (p->hdl) + sio_close(p->hdl); + + free(p->pfd); +} + +/* + * stop playing and empty buffers (for seeking/pause) + */ +static void reset(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (!sio_stop(p->hdl)) + MP_ERR(ao, "reset: couldn't stop\n"); + p->delay = 0; + if (!sio_start(p->hdl)) + MP_ERR(ao, "reset: couldn't start\n"); +} + +/* + * play given number of bytes until sio_write() blocks + */ +static int play(struct ao *ao, void *data, int len, int flags) +{ + struct priv *p = ao->priv; + int n; + + n = sio_write(p->hdl, data, len); + p->delay += n; + if (flags & AOPLAY_FINAL_CHUNK) + reset(ao); + return n; +} + +/* + * how many bytes can be played without blocking + */ +static int get_space(struct ao *ao) +{ + struct priv *p = ao->priv; + int n; + + /* + * call poll() and sio_revents(), so the + * delay counter is updated + */ + n = sio_pollfd(p->hdl, p->pfd, POLLOUT); + while (poll(p->pfd, n, 0) < 0 && errno == EINTR) + ; /* nothing */ + sio_revents(p->hdl, p->pfd); + + return p->par.bufsz * p->par.pchan * p->par.bps - p->delay; +} + +/* + * return: delay in seconds between first and last sample in buffer + */ +static float get_delay(struct ao *ao) +{ + struct priv *p = ao->priv; + return (float)p->delay / (p->par.bps * p->par.pchan * p->par.rate); +} + +/* + * stop playing, keep buffers (for pause) + */ +static void audio_pause(struct ao *ao) +{ + reset(ao); +} + +/* + * resume playing, after audio_pause() + */ +static void audio_resume(struct ao *ao) +{ + struct priv *p = ao->priv; + int n, count, todo; + + /* + * we want to start with buffers full, because mplayer uses + * get_space() pointer as clock, which would cause video to + * accelerate while buffers are filled. + */ + todo = p->par.bufsz * p->par.pchan * p->par.bps; + while (todo > 0) { + count = todo; + if (count > SILENCE_NMAX) + count = SILENCE_NMAX; + n = sio_write(p->hdl, p->silence, count); + if (n == 0) + break; + todo -= n; + p->delay += n; + } +} + +#define OPT_BASE_STRUCT struct priv + +const struct ao_driver audio_out_sndio = { + .info = &(const struct ao_info) { + "sndio audio output", + "sndio", + "Alexandre Ratchov , Christian Neukirchen ", + "under development" + }, + .init = init, + .uninit = uninit, + .control = control, + .get_space = get_space, + .play = play, + .get_delay = get_delay, + .pause = audio_pause, + .resume = audio_resume, + .reset = reset, + .priv_size = sizeof(struct priv), + .options = (const struct m_option[]) { + OPT_STRING("device", dev, 0, OPTDEF_STR(SIO_DEVANY)), + {0} + }, +}; diff --git a/configure b/configure index 32525f9de9..2c41e05a34 100755 --- a/configure +++ b/configure @@ -364,6 +364,7 @@ Audio output: --disable-alsa disable ALSA audio output [autodetect] --disable-ossaudio disable OSS audio output [autodetect] --disable-rsound disable RSound audio output [autodetect] + --disable-sndio disable sndio audio output [autodetect] --disable-pulse disable Pulseaudio audio output [autodetect] --disable-portaudio disable PortAudio audio output [autodetect] --disable-jack disable JACK audio output [autodetect] @@ -459,6 +460,7 @@ _lcms2=auto _xinerama=auto _vm=auto _xf86keysym=auto +_sndio=auto _alsa=auto _select=yes _radio=no @@ -625,6 +627,8 @@ for ac_option do --disable-ossaudio) _ossaudio=no ;; --enable-rsound) _rsound=yes ;; --disable-rsound) _rsound=no ;; + --enable-sndio) _sndio=yes ;; + --disable-sndio) _sndio=no ;; --enable-pulse) _pulse=yes ;; --disable-pulse) _pulse=no ;; --enable-portaudio) _portaudio=yes ;; @@ -2323,6 +2327,23 @@ else fi +echocheck "sndio" +if test "$_sndio" = auto ; then + _sndio=no + statement_check sndio.h 'struct sio_par par; sio_initpar(&par);' -lsndio && _sndio=yes +fi +echores "$_sndio" + +if test "$_sndio" = yes ; then + def_sndio='#define CONFIG_SNDIO 1' + aomodules="sndio $_aomodules" + libs_mplayer="$libs_mplayer -lsndio" +else + def_sndio='#undef CONFIG_SNDIO' + noaomodules="sndio $_noaomodules" +fi + + echocheck "pulse" if test "$_pulse" = auto ; then _pulse=no @@ -3290,6 +3311,7 @@ PVR = $_pvr RADIO=$_radio RADIO_CAPTURE=$_radio_capture RSOUND = $_rsound +SNDIO = $_sndio STREAM_CACHE = $_stream_cache TV = $_tv TV_V4L2 = $_tv_v4l2 @@ -3426,6 +3448,7 @@ $def_ossaudio_devmixer $def_pulse $def_portaudio $def_rsound +$def_sndio $def_ladspa $def_libbs2b diff --git a/stream/ai_sndio.c b/stream/ai_sndio.c new file mode 100644 index 0000000000..b486bb9210 --- /dev/null +++ b/stream/ai_sndio.c @@ -0,0 +1,49 @@ +#include +#include + +#include "config.h" + +#include +#include "audio_in.h" +#include "mpvcore/mp_msg.h" + +int ai_sndio_setup(audio_in_t *ai) +{ + struct sio_par par; + + sio_initpar(&par); + + par.bits = 16; + par.sig = 1; + par.le = 1; + par.rchan = ai->req_channels; + par.rate = ai->req_samplerate; + par.appbufsz = ai->req_samplerate; /* 1 sec */ + + if (!sio_setpar(ai->sndio.hdl, &par) || !sio_getpar(ai->sndio.hdl, &par)) { + mp_msg(MSGT_TV, MSGL_ERR, "could not configure sndio audio"); + return -1; + } + + ai->channels = par.rchan; + ai->samplerate = par.rate; + ai->samplesize = par.bits; + ai->bytes_per_sample = par.bps; + ai->blocksize = par.round * par.bps; + + return 0; +} + +int ai_sndio_init(audio_in_t *ai) +{ + int err; + + if ((ai->sndio.hdl = sio_open(ai->sndio.device, SIO_REC, 0)) == NULL) { + mp_msg(MSGT_TV, MSGL_ERR, "could not open sndio audio"); + return -1; + } + + err = ai_sndio_setup(ai); + + return err; +} diff --git a/stream/audio_in.c b/stream/audio_in.c index cc54e87800..420311e848 100644 --- a/stream/audio_in.c +++ b/stream/audio_in.c @@ -52,6 +52,12 @@ int audio_in_init(audio_in_t *ai, int type) ai->oss.audio_fd = -1; ai->oss.device = strdup("/dev/dsp"); return 0; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + ai->sndio.hdl = NULL; + ai->sndio.device = strdup("default"); + return 0; #endif default: return -1; @@ -73,6 +79,12 @@ int audio_in_setup(audio_in_t *ai) if (ai_oss_init(ai) < 0) return -1; ai->setup = 1; return 0; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + if (ai_sndio_init(ai) < 0) return -1; + ai->setup = 1; + return 0; #endif default: return -1; @@ -95,6 +107,13 @@ int audio_in_set_samplerate(audio_in_t *ai, int rate) if (!ai->setup) return 0; if (ai_oss_set_samplerate(ai) < 0) return -1; return ai->samplerate; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + ai->req_samplerate = rate; + if (!ai->setup) return 0; + if (ai_sndio_setup(ai) < 0) return -1; + return ai->samplerate; #endif default: return -1; @@ -117,6 +136,13 @@ int audio_in_set_channels(audio_in_t *ai, int channels) if (!ai->setup) return 0; if (ai_oss_set_channels(ai) < 0) return -1; return ai->channels; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + ai->req_channels = channels; + if (!ai->setup) return 0; + if (ai_sndio_setup(ai) < 0) return -1; + return ai->channels; #endif default: return -1; @@ -145,6 +171,12 @@ int audio_in_set_device(audio_in_t *ai, char *device) free(ai->oss.device); ai->oss.device = strdup(device); return 0; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + if (ai->sndio.device) free(ai->sndio.device); + ai->sndio.device = strdup(device); + return 0; #endif default: return -1; @@ -170,6 +202,13 @@ int audio_in_uninit(audio_in_t *ai) close(ai->oss.audio_fd); ai->setup = 0; return 0; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + if (ai->sndio.hdl) + sio_close(ai->sndio.hdl); + ai->setup = 0; + return 0; #endif } } @@ -186,6 +225,12 @@ int audio_in_start_capture(audio_in_t *ai) #ifdef CONFIG_OSS_AUDIO case AUDIO_IN_OSS: return 0; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + if (!sio_start(ai->sndio.hdl)) + return -1; + return 0; #endif default: return -1; @@ -220,6 +265,20 @@ int audio_in_read_chunk(audio_in_t *ai, unsigned char *buffer) #ifdef CONFIG_OSS_AUDIO case AUDIO_IN_OSS: ret = read(ai->oss.audio_fd, buffer, ai->blocksize); + if (ret != ai->blocksize) { + if (ret < 0) { + mp_msg(MSGT_TV, MSGL_ERR, "\nError reading audio: %s\n", strerror(errno)); + + } else { + mp_msg(MSGT_TV, MSGL_ERR, "\nNot enough audio samples!\n"); + } + return -1; + } + return ret; +#endif +#ifdef CONFIG_SNDIO + case AUDIO_IN_SNDIO: + ret = sio_read(ai->sndio.hdl, buffer, ai->blocksize); if (ret != ai->blocksize) { if (ret < 0) { mp_tmsg(MSGT_TV, MSGL_ERR, "\nError reading audio: %s\n", strerror(errno)); diff --git a/stream/audio_in.h b/stream/audio_in.h index 31688e7192..2f42685c7c 100644 --- a/stream/audio_in.h +++ b/stream/audio_in.h @@ -21,6 +21,7 @@ #define AUDIO_IN_ALSA 1 #define AUDIO_IN_OSS 2 +#define AUDIO_IN_SNDIO 3 #include "config.h" @@ -45,6 +46,16 @@ typedef struct { } ai_oss_t; #endif +#ifdef CONFIG_SNDIO +#include + +typedef struct { + char *device; + + struct sio_hdl *hdl; +} ai_sndio_t; +#endif + typedef struct { int type; @@ -67,6 +78,9 @@ typedef struct #ifdef CONFIG_OSS_AUDIO ai_oss_t oss; #endif +#ifdef CONFIG_SNDIO + ai_sndio_t sndio; +#endif } audio_in_t; int audio_in_init(audio_in_t *ai, int type); @@ -90,4 +104,9 @@ int ai_oss_set_channels(audio_in_t *ai); int ai_oss_init(audio_in_t *ai); #endif +#ifdef CONFIG_SNDIO +int ai_sndio_setup(audio_in_t *ai); +int ai_sndio_init(audio_in_t *ai); +#endif + #endif /* MPLAYER_AUDIO_IN_H */