summaryrefslogtreecommitdiffstats
path: root/audio/out
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2012-11-05 17:02:04 +0100
committerwm4 <wm4@nowhere>2012-11-12 20:06:14 +0100
commitd4bdd0473d6f43132257c9fb3848d829755167a3 (patch)
tree8021c2f7da1841393c8c832105e20cd527826d6c /audio/out
parentbd48deba77bd5582c5829d6fe73a7d2571088aba (diff)
downloadmpv-d4bdd0473d6f43132257c9fb3848d829755167a3.tar.bz2
mpv-d4bdd0473d6f43132257c9fb3848d829755167a3.tar.xz
Rename directories, move files (step 1 of 2) (does not compile)
Tis drops the silly lib prefixes, and attempts to organize the tree in a more logical way. Make the top-level directory less cluttered as well. Renames the following directories: libaf -> audio/filter libao2 -> audio/out libvo -> video/out libmpdemux -> demux Split libmpcodecs: vf* -> video/filter vd*, dec_video.* -> video/decode mp_image*, img_format*, ... -> video/ ad*, dec_audio.* -> audio/decode libaf/format.* is moved to audio/ - this is similar to how mp_image.* is located in video/. Move most top-level .c/.h files to core. (talloc.c/.h is left on top- level, because it's external.) Park some of the more annoying files in compat/. Some of these are relicts from the time mplayer used ffmpeg internals. sub/ is not split, because it's too much of a mess (subtitle code is mixed with OSD display and rendering). Maybe the organization of core is not ideal: it mixes playback core (like mplayer.c) and utility helpers (like bstr.c/h). Should the need arise, the playback core will be moved somewhere else, while core contains all helper and common code.
Diffstat (limited to 'audio/out')
-rw-r--r--audio/out/ao.c294
-rw-r--r--audio/out/ao.h140
-rw-r--r--audio/out/ao_alsa.c868
-rw-r--r--audio/out/ao_coreaudio.c1283
-rw-r--r--audio/out/ao_dsound.c648
-rw-r--r--audio/out/ao_jack.c361
-rw-r--r--audio/out/ao_lavc.c621
-rw-r--r--audio/out/ao_null.c129
-rw-r--r--audio/out/ao_openal.c280
-rw-r--r--audio/out/ao_oss.c560
-rw-r--r--audio/out/ao_pcm.c256
-rw-r--r--audio/out/ao_portaudio.c431
-rw-r--r--audio/out/ao_pulse.c554
-rw-r--r--audio/out/ao_rsound.c214
-rw-r--r--audio/out/audio_out_internal.h65
15 files changed, 6704 insertions, 0 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
new file mode 100644
index 0000000000..ab8e60b753
--- /dev/null
+++ b/audio/out/ao.c
@@ -0,0 +1,294 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "talloc.h"
+
+#include "config.h"
+#include "audio_out.h"
+
+#include "mp_msg.h"
+
+// there are some globals:
+struct ao *global_ao;
+char *ao_subdevice = NULL;
+
+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_pulse;
+extern const struct ao_driver audio_out_jack;
+extern const struct ao_driver audio_out_openal;
+extern const struct ao_driver audio_out_null;
+extern const struct ao_driver audio_out_alsa;
+extern const struct ao_driver audio_out_dsound;
+extern const struct ao_driver audio_out_pcm;
+extern const struct ao_driver audio_out_pss;
+extern const struct ao_driver audio_out_lavc;
+extern const struct ao_driver audio_out_portaudio;
+
+static const struct ao_driver * const audio_out_drivers[] = {
+// native:
+#ifdef CONFIG_COREAUDIO
+ &audio_out_coreaudio,
+#endif
+#ifdef CONFIG_PULSE
+ &audio_out_pulse,
+#endif
+#ifdef CONFIG_ALSA
+ &audio_out_alsa,
+#endif
+#ifdef CONFIG_OSS_AUDIO
+ &audio_out_oss,
+#endif
+#ifdef CONFIG_PORTAUDIO
+ &audio_out_portaudio,
+#endif
+#ifdef CONFIG_DSOUND
+ &audio_out_dsound,
+#endif
+ // wrappers:
+#ifdef CONFIG_JACK
+ &audio_out_jack,
+#endif
+#ifdef CONFIG_OPENAL
+ &audio_out_openal,
+#endif
+ &audio_out_null,
+ // should not be auto-selected:
+ &audio_out_pcm,
+#ifdef CONFIG_ENCODING
+ &audio_out_lavc,
+#endif
+#ifdef CONFIG_RSOUND
+ &audio_out_rsound,
+#endif
+ NULL
+};
+
+void list_audio_out(void)
+{
+ int i=0;
+ mp_tmsg(MSGT_AO, MSGL_INFO, "Available audio output drivers:\n");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AUDIO_OUTPUTS\n");
+ while (audio_out_drivers[i]) {
+ const ao_info_t *info = audio_out_drivers[i++]->info;
+ mp_msg(MSGT_GLOBAL, MSGL_INFO, "\t%s\t%s\n", info->short_name,
+ info->name);
+ }
+ mp_msg(MSGT_GLOBAL, MSGL_INFO,"\n");
+}
+
+struct ao *ao_create(struct MPOpts *opts, struct input_ctx *input)
+{
+ struct ao *r = talloc(NULL, struct ao);
+ *r = (struct ao){.outburst = 512, .buffersize = -1,
+ .opts = opts, .input_ctx = input };
+ return r;
+}
+
+void ao_init(struct ao *ao, char **ao_list)
+{
+ /* Caller adding child blocks is not supported as we may call
+ * talloc_free_children() to clean up after failed open attempts.
+ */
+ assert(talloc_total_blocks(ao) == 1);
+ struct ao backup = *ao;
+
+ if (!ao_list)
+ goto try_defaults;
+
+ // first try the preferred drivers, with their optional subdevice param:
+ while (*ao_list) {
+ char *ao_name = *ao_list;
+ if (!*ao_name)
+ goto try_defaults; // empty entry means try defaults
+ int ao_len;
+ char *params = strchr(ao_name, ':');
+ if (params) {
+ ao_len = params - ao_name;
+ params++;
+ } else
+ ao_len = strlen(ao_name);
+
+ mp_tmsg(MSGT_AO, MSGL_V,
+ "Trying preferred audio driver '%.*s', options '%s'\n",
+ ao_len, ao_name, params ? params : "[none]");
+
+ const struct ao_driver *audio_out = NULL;
+ for (int i = 0; audio_out_drivers[i]; i++) {
+ audio_out = audio_out_drivers[i];
+ if (!strncmp(audio_out->info->short_name, ao_name, ao_len))
+ break;
+ audio_out = NULL;
+ }
+ if (audio_out) {
+ // name matches, try it
+ ao->driver = audio_out;
+ if (audio_out->init(ao, params) >= 0) {
+ ao->driver = audio_out;
+ ao->initialized = true;
+ return;
+ }
+ mp_tmsg(MSGT_AO, MSGL_WARN,
+ "Failed to initialize audio driver '%s'\n", ao_name);
+ talloc_free_children(ao);
+ *ao = backup;
+ } else
+ mp_tmsg(MSGT_AO, MSGL_WARN, "No such audio driver '%.*s'\n",
+ ao_len, ao_name);
+ ++ao_list;
+ }
+ return;
+
+ try_defaults:
+ mp_tmsg(MSGT_AO, MSGL_V, "Trying every known audio driver...\n");
+
+ // now try the rest...
+ for (int i = 0; audio_out_drivers[i]; i++) {
+ const struct ao_driver *audio_out = audio_out_drivers[i];
+ ao->driver = audio_out;
+ ao->probing = true;
+ if (audio_out->init(ao, NULL) >= 0) {
+ ao->probing = false;
+ ao->initialized = true;
+ ao->driver = audio_out;
+ return;
+ }
+ talloc_free_children(ao);
+ *ao = backup;
+ }
+ return;
+}
+
+void ao_uninit(struct ao *ao, bool cut_audio)
+{
+ assert(ao->buffer.len >= ao->buffer_playable_size);
+ ao->buffer.len = ao->buffer_playable_size;
+ if (ao->initialized)
+ ao->driver->uninit(ao, cut_audio);
+ if (!cut_audio && ao->buffer.len)
+ mp_msg(MSGT_AO, MSGL_WARN, "Audio output truncated at end.\n");
+ talloc_free(ao);
+}
+
+int ao_play(struct ao *ao, void *data, int len, int flags)
+{
+ return ao->driver->play(ao, data, len, flags);
+}
+
+int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ if (ao->driver->control)
+ return ao->driver->control(ao, cmd, arg);
+ return CONTROL_UNKNOWN;
+}
+
+double ao_get_delay(struct ao *ao)
+{
+ if (!ao->driver->get_delay) {
+ assert(ao->untimed);
+ return 0;
+ }
+ return ao->driver->get_delay(ao);
+}
+
+int ao_get_space(struct ao *ao)
+{
+ return ao->driver->get_space(ao);
+}
+
+void ao_reset(struct ao *ao)
+{
+ ao->buffer.len = 0;
+ ao->buffer_playable_size = 0;
+ if (ao->driver->reset)
+ ao->driver->reset(ao);
+}
+
+void ao_pause(struct ao *ao)
+{
+ if (ao->driver->pause)
+ ao->driver->pause(ao);
+}
+
+void ao_resume(struct ao *ao)
+{
+ if (ao->driver->resume)
+ ao->driver->resume(ao);
+}
+
+
+
+int old_ao_init(struct ao *ao, char *params)
+{
+ assert(!global_ao);
+ global_ao = ao;
+ ao_subdevice = params ? talloc_strdup(ao, params) : NULL;
+ if (ao->driver->old_functions->init(ao->samplerate, ao->channels,
+ ao->format, 0) == 0) {
+ global_ao = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+void old_ao_uninit(struct ao *ao, bool cut_audio)
+{
+ ao->driver->old_functions->uninit(cut_audio);
+ global_ao = NULL;
+}
+
+int old_ao_play(struct ao *ao, void *data, int len, int flags)
+{
+ return ao->driver->old_functions->play(data, len, flags);
+}
+
+int old_ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ return ao->driver->old_functions->control(cmd, arg);
+}
+
+float old_ao_get_delay(struct ao *ao)
+{
+ return ao->driver->old_functions->get_delay();
+}
+
+int old_ao_get_space(struct ao *ao)
+{
+ return ao->driver->old_functions->get_space();
+}
+
+void old_ao_reset(struct ao *ao)
+{
+ ao->driver->old_functions->reset();
+}
+
+void old_ao_pause(struct ao *ao)
+{
+ ao->driver->old_functions->pause();
+}
+
+void old_ao_resume(struct ao *ao)
+{
+ ao->driver->old_functions->resume();
+}
diff --git a/audio/out/ao.h b/audio/out/ao.h
new file mode 100644
index 0000000000..9e172fd06c
--- /dev/null
+++ b/audio/out/ao.h
@@ -0,0 +1,140 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_AUDIO_OUT_H
+#define MPLAYER_AUDIO_OUT_H
+
+#include <stdbool.h>
+
+#include "bstr.h"
+
+#define CONTROL_OK 1
+#define CONTROL_TRUE 1
+#define CONTROL_FALSE 0
+#define CONTROL_UNKNOWN -1
+#define CONTROL_ERROR -2
+#define CONTROL_NA -3
+
+enum aocontrol {
+ // _VOLUME commands take struct ao_control_vol pointer for input/output.
+ // If there's only one volume, SET should use average of left/right.
+ AOCONTROL_GET_VOLUME,
+ AOCONTROL_SET_VOLUME,
+ // _MUTE commands take a pointer to bool
+ AOCONTROL_GET_MUTE,
+ AOCONTROL_SET_MUTE,
+};
+
+#define AOPLAY_FINAL_CHUNK 1
+
+typedef struct ao_control_vol {
+ float left;
+ float right;
+} ao_control_vol_t;
+
+typedef struct ao_info {
+ /* driver name ("Matrox Millennium G200/G400" */
+ const char *name;
+ /* short name (for config strings) ("mga") */
+ const char *short_name;
+ /* author ("Aaron Holtzman <aholtzma@ess.engr.uvic.ca>") */
+ const char *author;
+ /* any additional comments */
+ const char *comment;
+} ao_info_t;
+
+/* interface towards mplayer and */
+typedef struct ao_old_functions {
+ int (*control)(int cmd, void *arg);
+ int (*init)(int rate, int channels, int format, int flags);
+ void (*uninit)(int immed);
+ void (*reset)(void);
+ int (*get_space)(void);
+ int (*play)(void *data, int len, int flags);
+ float (*get_delay)(void);
+ void (*pause)(void);
+ void (*resume)(void);
+} ao_functions_t;
+
+struct ao;
+
+struct ao_driver {
+ bool is_new;
+ const struct ao_info *info;
+ const struct ao_old_functions *old_functions;
+ int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
+ int (*init)(struct ao *ao, char *params);
+ void (*uninit)(struct ao *ao, bool cut_audio);
+ void (*reset)(struct ao*ao);
+ int (*get_space)(struct ao *ao);
+ int (*play)(struct ao *ao, void *data, int len, int flags);
+ float (*get_delay)(struct ao *ao);
+ void (*pause)(struct ao *ao);
+ void (*resume)(struct ao *ao);
+};
+
+/* global data used by mplayer and plugins */
+struct ao {
+ int samplerate;
+ int channels;
+ int format;
+ int bps;
+ int outburst;
+ int buffersize;
+ int brokenpts;
+ double pts;
+ struct bstr buffer;
+ int buffer_playable_size;
+ bool probing;
+ bool initialized;
+ bool untimed;
+ bool no_persistent_volume;
+ bool per_application_mixer;
+ const struct ao_driver *driver;
+ void *priv;
+ struct encode_lavc_context *encode_lavc_ctx;
+ struct MPOpts *opts;
+ struct input_ctx *input_ctx;
+};
+
+extern char *ao_subdevice;
+
+void list_audio_out(void);
+
+struct ao *ao_create(struct MPOpts *opts, struct input_ctx *input);
+void ao_init(struct ao *ao, char **ao_list);
+void ao_uninit(struct ao *ao, bool cut_audio);
+int ao_play(struct ao *ao, void *data, int len, int flags);
+int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
+double ao_get_delay(struct ao *ao);
+int ao_get_space(struct ao *ao);
+void ao_reset(struct ao *ao);
+void ao_pause(struct ao *ao);
+void ao_resume(struct ao *ao);
+
+int old_ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
+int old_ao_init(struct ao *ao, char *params);
+void old_ao_uninit(struct ao *ao, bool cut_audio);
+void old_ao_reset(struct ao*ao);
+int old_ao_get_space(struct ao *ao);
+int old_ao_play(struct ao *ao, void *data, int len, int flags);
+float old_ao_get_delay(struct ao *ao);
+void old_ao_pause(struct ao *ao);
+void old_ao_resume(struct ao *ao);
+
+#endif /* MPLAYER_AUDIO_OUT_H */
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
new file mode 100644
index 0000000000..27119112cb
--- /dev/null
+++ b/audio/out/ao_alsa.c
@@ -0,0 +1,868 @@
+/*
+ * ALSA 0.9.x-1.x audio output driver
+ *
+ * Copyright (C) 2004 Alex Beregszaszi
+ *
+ * modified for real ALSA 0.9.0 support by Zsolt Barat <joy@streamminister.de>
+ * additional AC-3 passthrough support by Andy Lo A Foe <andy@alsaplayer.org>
+ * 08/22/2002 iec958-init rewritten and merged with common init, zsolt
+ * 04/13/2004 merged with ao_alsa1.x, fixes provided by Jindrich Makovicka
+ * 04/25/2004 printfs converted to mp_msg, Zsolt.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+#include <alloca.h>
+
+#include "config.h"
+#include "subopt-helper.h"
+#include "mixer.h"
+#include "mp_msg.h"
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+#include <alsa/asoundlib.h>
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "libaf/format.h"
+
+static const ao_info_t info =
+{
+ "ALSA-0.9.x-1.x audio output",
+ "alsa",
+ "Alex Beregszaszi, Zsolt Barat <joy@streamminister.de>",
+ "under development"
+};
+
+LIBAO_EXTERN(alsa)
+
+static snd_pcm_t *alsa_handler;
+static snd_pcm_format_t alsa_format;
+
+#define BUFFER_TIME 500000 // 0.5 s
+#define FRAGCOUNT 16
+
+static size_t bytes_per_sample;
+
+static int alsa_can_pause;
+static snd_pcm_sframes_t prepause_frames;
+
+#define ALSA_DEVICE_SIZE 256
+
+static void alsa_error_handler(const char *file, int line, const char *function,
+ int err, const char *format, ...)
+{
+ char tmp[0xc00];
+ va_list va;
+
+ va_start(va, format);
+ vsnprintf(tmp, sizeof tmp, format, va);
+ va_end(va);
+
+ if (err)
+ mp_msg(MSGT_AO, MSGL_ERR, "[AO_ALSA] alsa-lib: %s:%i:(%s) %s: %s\n",
+ file, line, function, tmp, snd_strerror(err));
+ else
+ mp_msg(MSGT_AO, MSGL_ERR, "[AO_ALSA] alsa-lib: %s:%i:(%s) %s\n",
+ file, line, function, tmp);
+}
+
+/* to set/get/query special features/parameters */
+static int control(int cmd, void *arg)
+{
+ switch(cmd) {
+ case AOCONTROL_GET_MUTE:
+ case AOCONTROL_SET_MUTE:
+ case AOCONTROL_GET_VOLUME:
+ case AOCONTROL_SET_VOLUME:
+ {
+ int err;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid;
+
+ char *mix_name = "Master";
+ char *card = "default";
+ int mix_index = 0;
+
+ long pmin, pmax;
+ long get_vol, set_vol;
+ float f_multi;
+
+ if(AF_FORMAT_IS_AC3(ao_data.format) || AF_FORMAT_IS_IEC61937(ao_data.format))
+ return CONTROL_TRUE;
+
+ if(mixer_channel) {
+ char *test_mix_index;
+
+ mix_name = strdup(mixer_channel);
+ if ((test_mix_index = strchr(mix_name, ','))){
+ *test_mix_index = 0;
+ test_mix_index++;
+ mix_index = strtol(test_mix_index, &test_mix_index, 0);
+
+ if (*test_mix_index){
+ mp_tmsg(MSGT_AO,MSGL_ERR,
+ "[AO_ALSA] Invalid mixer index. Defaulting to 0.\n");
+ mix_index = 0 ;
+ }
+ }
+ }
+ if(mixer_device) card = mixer_device;
+
+ //allocate simple id
+ snd_mixer_selem_id_alloca(&sid);
+
+ //sets simple-mixer index and name
+ snd_mixer_selem_id_set_index(sid, mix_index);
+ snd_mixer_selem_id_set_name(sid, mix_name);
+
+ if (mixer_channel) {
+ free(mix_name);
+ mix_name = NULL;
+ }
+
+ if ((err = snd_mixer_open(&handle, 0)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer open error: %s\n", snd_strerror(err));
+ return CONTROL_ERROR;
+ }
+
+ if ((err = snd_mixer_attach(handle, card)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer attach %s error: %s\n",
+ card, snd_strerror(err));
+ snd_mixer_close(handle);
+ return CONTROL_ERROR;
+ }
+
+ if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer register error: %s\n", snd_strerror(err));
+ snd_mixer_close(handle);
+ return CONTROL_ERROR;
+ }
+ err = snd_mixer_load(handle);
+ if (err < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Mixer load error: %s\n", snd_strerror(err));
+ snd_mixer_close(handle);
+ return CONTROL_ERROR;
+ }
+
+ elem = snd_mixer_find_selem(handle, sid);
+ if (!elem) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to find simple control '%s',%i.\n",
+ snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+ snd_mixer_close(handle);
+ return CONTROL_ERROR;
+ }
+
+ snd_mixer_selem_get_playback_volume_range(elem,&pmin,&pmax);
+ f_multi = (100 / (float)(pmax - pmin));
+
+ switch (cmd) {
+ case AOCONTROL_SET_VOLUME: {
+ ao_control_vol_t *vol = arg;
+ set_vol = vol->left / f_multi + pmin + 0.5;
+
+ //setting channels
+ if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, set_vol)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Error setting left channel, %s\n",
+ snd_strerror(err));
+ goto mixer_error;
+ }
+ mp_msg(MSGT_AO,MSGL_DBG2,"left=%li, ", set_vol);
+
+ set_vol = vol->right / f_multi + pmin + 0.5;
+
+ if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, set_vol)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Error setting right channel, %s\n",
+ snd_strerror(err));
+ goto mixer_error;
+ }
+ mp_msg(MSGT_AO,MSGL_DBG2,"right=%li, pmin=%li, pmax=%li, mult=%f\n",
+ set_vol, pmin, pmax, f_multi);
+ break;
+ }
+ case AOCONTROL_GET_VOLUME: {
+ ao_control_vol_t *vol = arg;
+ snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &get_vol);
+ vol->left = (get_vol - pmin) * f_multi;
+ snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &get_vol);
+ vol->right = (get_vol - pmin) * f_multi;
+ mp_msg(MSGT_AO,MSGL_DBG2,"left=%f, right=%f\n",vol->left,vol->right);
+ break;
+ }
+ case AOCONTROL_SET_MUTE: {
+ bool *mute = arg;
+ if (!snd_mixer_selem_has_playback_switch(elem))
+ goto mixer_error;
+ if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
+ snd_mixer_selem_set_playback_switch(
+ elem, SND_MIXER_SCHN_FRONT_RIGHT, !*mute);
+ }
+ snd_mixer_selem_set_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT,
+ !*mute);
+ break;
+ }
+ case AOCONTROL_GET_MUTE: {
+ bool *mute = arg;
+ if (!snd_mixer_selem_has_playback_switch(elem))
+ goto mixer_error;
+ int tmp = 1;
+ snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT,
+ &tmp);
+ *mute = !tmp;
+ if (!snd_mixer_selem_has_playback_switch_joined(elem)) {
+ snd_mixer_selem_get_playback_switch(
+ elem, SND_MIXER_SCHN_FRONT_RIGHT, &tmp);
+ *mute &= !tmp;
+ }
+ break;
+ }
+ }
+ snd_mixer_close(handle);
+ return CONTROL_OK;
+ mixer_error:
+ snd_mixer_close(handle);
+ return CONTROL_ERROR;
+ }
+
+ } //end switch
+ return CONTROL_UNKNOWN;
+}
+
+static void parse_device (char *dest, const char *src, int len)
+{
+ char *tmp;
+ memmove(dest, src, len);
+ dest[len] = 0;
+ while ((tmp = strrchr(dest, '.')))
+ tmp[0] = ',';
+ while ((tmp = strrchr(dest, '=')))
+ tmp[0] = ':';
+}
+
+static void print_help (void)
+{
+ mp_tmsg (MSGT_AO, MSGL_FATAL,
+ "\n[AO_ALSA] -ao alsa commandline help:\n"\
+ "[AO_ALSA] Example: mpv -ao alsa:device=hw=0.3\n"\
+ "[AO_ALSA] Sets first card fourth hardware device.\n\n"\
+ "[AO_ALSA] Options:\n"\
+ "[AO_ALSA] noblock\n"\
+ "[AO_ALSA] Opens device in non-blocking mode.\n"\
+ "[AO_ALSA] device=<device-name>\n"\
+ "[AO_ALSA] Sets device (change , to . and : to =)\n");
+}
+
+static int str_maxlen(void *strp) {
+ strarg_t *str = strp;
+ return str->len <= ALSA_DEVICE_SIZE;
+}
+
+static int try_open_device(const char *device, int open_mode, int try_ac3)
+{
+ int err, len;
+ char *ac3_device, *args;
+
+ if (try_ac3) {
+ /* to set the non-audio bit, use AES0=6 */
+ len = strlen(device);
+ ac3_device = malloc(len + 7 + 1);
+ if (!ac3_device)
+ return -ENOMEM;
+ strcpy(ac3_device, device);
+ args = strchr(ac3_device, ':');
+ if (!args) {
+ /* no existing parameters: add it behind device name */
+ strcat(ac3_device, ":AES0=6");
+ } else {
+ do
+ ++args;
+ while (isspace(*args));
+ if (*args == '\0') {
+ /* ":" but no parameters */
+ strcat(ac3_device, "AES0=6");
+ } else if (*args != '{') {
+ /* a simple list of parameters: add it at the end of the list */
+ strcat(ac3_device, ",AES0=6");
+ } else {
+ /* parameters in config syntax: add it inside the { } block */
+ do
+ --len;
+ while (len > 0 && isspace(ac3_device[len]));
+ if (ac3_device[len] == '}')
+ strcpy(ac3_device + len, " AES0=6}");
+ }
+ }
+ err = snd_pcm_open(&alsa_handler, ac3_device, SND_PCM_STREAM_PLAYBACK,
+ open_mode);
+ free(ac3_device);
+ if (!err)
+ return 0;
+ }
+ return snd_pcm_open(&alsa_handler, device, SND_PCM_STREAM_PLAYBACK,
+ open_mode);
+}
+
+/*
+ open & setup audio device
+ return: 1=success 0=fail
+*/
+static int init(int rate_hz, int channels, int format, int flags)
+{
+ int err;
+ int block;
+ strarg_t device;
+ snd_pcm_uframes_t chunk_size;
+ snd_pcm_uframes_t bufsize;
+ snd_pcm_uframes_t boundary;
+ const opt_t subopts[] = {
+ {"block", OPT_ARG_BOOL, &block, NULL},
+ {"device", OPT_ARG_STR, &device, str_maxlen},
+ {NULL}
+ };
+
+ char alsa_device[ALSA_DEVICE_SIZE + 1];
+ // make sure alsa_device is null-terminated even when using strncpy etc.
+ memset(alsa_device, 0, ALSA_DEVICE_SIZE + 1);
+
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: requested format: %d Hz, %d channels, %x\n", rate_hz,
+ channels, format);
+ alsa_handler = NULL;
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: using ALSA %s\n", snd_asoundlib_version());
+
+ prepause_frames = 0;
+
+ snd_lib_error_set_handler(alsa_error_handler);
+
+ ao_data.samplerate = rate_hz;
+ ao_data.format = format;
+ ao_data.channels = channels;
+
+ switch (format)
+ {
+ case AF_FORMAT_S8:
+ alsa_format = SND_PCM_FORMAT_S8;
+ break;
+ case AF_FORMAT_U8:
+ alsa_format = SND_PCM_FORMAT_U8;
+ break;
+ case AF_FORMAT_U16_LE:
+ alsa_format = SND_PCM_FORMAT_U16_LE;
+ break;
+ case AF_FORMAT_U16_BE:
+ alsa_format = SND_PCM_FORMAT_U16_BE;
+ break;
+ case AF_FORMAT_AC3_LE:
+ case AF_FORMAT_S16_LE:
+ case AF_FORMAT_IEC61937_LE:
+ alsa_format = SND_PCM_FORMAT_S16_LE;
+ break;
+ case AF_FORMAT_AC3_BE:
+ case AF_FORMAT_S16_BE:
+ case AF_FORMAT_IEC61937_BE:
+ alsa_format = SND_PCM_FORMAT_S16_BE;
+ break;
+ case AF_FORMAT_U32_LE:
+ alsa_format = SND_PCM_FORMAT_U32_LE;
+ break;
+ case AF_FORMAT_U32_BE:
+ alsa_format = SND_PCM_FORMAT_U32_BE;
+ break;
+ case AF_FORMAT_S32_LE:
+ alsa_format = SND_PCM_FORMAT_S32_LE;
+ break;
+ case AF_FORMAT_S32_BE:
+ alsa_format = SND_PCM_FORMAT_S32_BE;
+ break;
+ case AF_FORMAT_U24_LE:
+ alsa_format = SND_PCM_FORMAT_U24_3LE;
+ break;
+ case AF_FORMAT_U24_BE:
+ alsa_format = SND_PCM_FORMAT_U24_3BE;
+ break;
+ case AF_FORMAT_S24_LE:
+ alsa_format = SND_PCM_FORMAT_S24_3LE;
+ break;
+ case AF_FORMAT_S24_BE:
+ alsa_format = SND_PCM_FORMAT_S24_3BE;
+ break;
+ case AF_FORMAT_FLOAT_LE:
+ alsa_format = SND_PCM_FORMAT_FLOAT_LE;
+ break;
+ case AF_FORMAT_FLOAT_BE:
+ alsa_format = SND_PCM_FORMAT_FLOAT_BE;
+ break;
+ case AF_FORMAT_MU_LAW:
+ alsa_format = SND_PCM_FORMAT_MU_LAW;
+ break;
+ case AF_FORMAT_A_LAW:
+ alsa_format = SND_PCM_FORMAT_A_LAW;
+ break;
+
+ default:
+ alsa_format = SND_PCM_FORMAT_MPEG; //? default should be -1
+ break;
+ }
+
+ //subdevice parsing
+ // set defaults
+ block = 1;
+ /* switch for spdif
+ * sets opening sequence for SPDIF
+ * sets also the playback and other switches 'on the fly'
+ * while opening the abstract alias for the spdif subdevice
+ * 'iec958'
+ */
+ if (AF_FORMAT_IS_AC3(format) || AF_FORMAT_IS_IEC61937(format)) {
+ device.str = "iec958";
+ mp_msg(MSGT_AO,MSGL_V,"alsa-spdif-init: playing AC3/iec61937/iec958, %i channels\n", channels);
+ }
+ else
+ /* in any case for multichannel playback we should select
+ * appropriate device
+ */
+ switch (channels) {
+ case 1:
+ case 2:
+ device.str = "default";
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: setup for 1/2 channel(s)\n");
+ break;
+ case 4:
+ if (alsa_format == SND_PCM_FORMAT_FLOAT_LE)
+ // hack - use the converter plugin
+ device.str = "plug:surround40";
+ else
+ device.str = "surround40";
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround40\n");
+ break;
+ case 6:
+ if (alsa_format == SND_PCM_FORMAT_FLOAT_LE)
+ device.str = "plug:surround51";
+ else
+ device.str = "surround51";
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround51\n");
+ break;
+ case 8:
+ if (alsa_format == SND_PCM_FORMAT_FLOAT_LE)
+ device.str = "plug:surround71";
+ else
+ device.str = "surround71";
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: device set to surround71\n");
+ break;
+ default:
+ device.str = "default";
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] %d channels are not supported.\n",channels);
+ }
+ device.len = strlen(device.str);
+ if (subopt_parse(ao_subdevice, subopts) != 0) {
+ print_help();
+ return 0;
+ }
+ parse_device(alsa_device, device.str, device.len);
+
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: using device %s\n", alsa_device);
+
+ alsa_can_pause = 1;
+
+ if (!alsa_handler) {
+ int open_mode = block ? 0 : SND_PCM_NONBLOCK;
+ int isac3 = AF_FORMAT_IS_AC3(format) || AF_FORMAT_IS_IEC61937(format);
+ //modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
+ if ((err = try_open_device(alsa_device, open_mode, isac3)) < 0)
+ {
+ if (err != -EBUSY && !block) {
+ mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Open in nonblock-mode failed, trying to open in block-mode.\n");
+ if ((err = try_open_device(alsa_device, 0, isac3)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Playback open error: %s\n", snd_strerror(err));
+ return 0;
+ }
+ } else {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Playback open error: %s\n", snd_strerror(err));
+ return 0;
+ }
+ }
+
+ if ((err = snd_pcm_nonblock(alsa_handler, 0)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AL_ALSA] Error setting block-mode %s.\n", snd_strerror(err));
+ } else {
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: pcm opened in blocking mode\n");
+ }
+
+ snd_pcm_hw_params_t *alsa_hwparams;
+ snd_pcm_sw_params_t *alsa_swparams;
+
+ snd_pcm_hw_params_alloca(&alsa_hwparams);
+ snd_pcm_sw_params_alloca(&alsa_swparams);
+
+ // setting hw-parameters
+ if ((err = snd_pcm_hw_params_any(alsa_handler, alsa_hwparams)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get initial parameters: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ err = snd_pcm_hw_params_set_access(alsa_handler, alsa_hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set access type: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ /* workaround for nonsupported formats
+ sets default format to S16_LE if the given formats aren't supported */
+ if ((err = snd_pcm_hw_params_test_format(alsa_handler, alsa_hwparams,
+ alsa_format)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_INFO,
+ "[AO_ALSA] Format %s is not supported by hardware, trying default.\n", af_fmt2str_short(format));
+ alsa_format = SND_PCM_FORMAT_S16_LE;
+ if (AF_FORMAT_IS_AC3(ao_data.format))
+ ao_data.format = AF_FORMAT_AC3_LE;
+ else if (AF_FORMAT_IS_IEC61937(ao_data.format))
+ ao_data.format = AF_FORMAT_IEC61937_LE;
+ else
+ ao_data.format = AF_FORMAT_S16_LE;
+ }
+
+ if ((err = snd_pcm_hw_params_set_format(alsa_handler, alsa_hwparams,
+ alsa_format)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set format: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ if ((err = snd_pcm_hw_params_set_channels_near(alsa_handler, alsa_hwparams,
+ &ao_data.channels)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set channels: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ /* workaround for buggy rate plugin (should be fixed in ALSA 1.0.11)
+ prefer our own resampler, since that allows users to choose the resampler,
+ even per file if desired */
+ if ((err = snd_pcm_hw_params_set_rate_resample(alsa_handler, alsa_hwparams,
+ 0)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to disable resampling: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ if ((err = snd_pcm_hw_params_set_rate_near(alsa_handler, alsa_hwparams,
+ &ao_data.samplerate, NULL)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set samplerate-2: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ bytes_per_sample = af_fmt2bits(ao_data.format) / 8;
+ bytes_per_sample *= ao_data.channels;
+ ao_data.bps = ao_data.samplerate * bytes_per_sample;
+
+ if ((err = snd_pcm_hw_params_set_buffer_time_near(alsa_handler, alsa_hwparams,
+ &(unsigned int){BUFFER_TIME}, NULL)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set buffer time near: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ if ((err = snd_pcm_hw_params_set_periods_near(alsa_handler, alsa_hwparams,
+ &(unsigned int){FRAGCOUNT}, NULL)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set periods: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+
+ /* finally install hardware parameters */
+ if ((err = snd_pcm_hw_params(alsa_handler, alsa_hwparams)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set hw-parameters: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ // end setting hw-params
+
+
+ // gets buffersize for control
+ if ((err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get buffersize: %s\n", snd_strerror(err));
+ return 0;
+ }
+ else {
+ ao_data.buffersize = bufsize * bytes_per_sample;
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: got buffersize=%i\n", ao_data.buffersize);
+ }
+
+ if ((err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO ALSA] Unable to get period size: %s\n", snd_strerror(err));
+ return 0;
+ } else {
+ mp_msg(MSGT_AO,MSGL_V,"alsa-init: got period size %li\n", chunk_size);
+ }
+ ao_data.outburst = chunk_size * bytes_per_sample;
+
+ /* setting software parameters */
+ if ((err = snd_pcm_sw_params_current(alsa_handler, alsa_swparams)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get sw-parameters: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ if ((err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get boundary: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ /* start playing when one period has been written */
+ if ((err = snd_pcm_sw_params_set_start_threshold(alsa_handler, alsa_swparams, chunk_size)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set start threshold: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ /* disable underrun reporting */
+ if ((err = snd_pcm_sw_params_set_stop_threshold(alsa_handler, alsa_swparams, boundary)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set stop threshold: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ /* play silence when there is an underrun */
+ if ((err = snd_pcm_sw_params_set_silence_size(alsa_handler, alsa_swparams, boundary)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to set silence size: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ if ((err = snd_pcm_sw_params(alsa_handler, alsa_swparams)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Unable to get sw-parameters: %s\n",
+ snd_strerror(err));
+ return 0;
+ }
+ /* end setting sw-params */
+
+ alsa_can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
+
+ mp_msg(MSGT_AO,MSGL_V,"alsa: %d Hz/%d channels/%d bpf/%d bytes buffer/%s\n",
+ ao_data.samplerate, ao_data.channels, (int)bytes_per_sample, ao_data.buffersize,
+ snd_pcm_format_description(alsa_format));
+
+ } // end switch alsa_handler (spdif)
+ return 1;
+} // end init
+
+
+/* close audio device */
+static void uninit(int immed)
+{
+
+ if (alsa_handler) {
+ int err;
+
+ if (!immed)
+ snd_pcm_drain(alsa_handler);
+
+ if ((err = snd_pcm_close(alsa_handler)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm close error: %s\n", snd_strerror(err));
+ return;
+ }
+ else {
+ alsa_handler = NULL;
+ mp_msg(MSGT_AO,MSGL_V,"alsa-uninit: pcm closed\n");
+ }
+ }
+ else {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] No handler defined!\n");
+ }
+}
+
+static void audio_pause(void)
+{
+ int err;
+
+ if (alsa_can_pause) {
+ if ((err = snd_pcm_pause(alsa_handler, 1)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm pause error: %s\n", snd_strerror(err));
+ return;
+ }
+ mp_msg(MSGT_AO,MSGL_V,"alsa-pause: pause supported by hardware\n");
+ } else {
+ if (snd_pcm_delay(alsa_handler, &prepause_frames) < 0
+ || prepause_frames < 0)
+ prepause_frames = 0;
+
+ if ((err = snd_pcm_drop(alsa_handler)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm drop error: %s\n", snd_strerror(err));
+ return;
+ }
+ }
+}
+
+static void audio_resume(void)
+{
+ int err;
+
+ if (snd_pcm_state(alsa_handler) == SND_PCM_STATE_SUSPENDED) {
+ mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Pcm in suspend mode, trying to resume.\n");
+ while ((err = snd_pcm_resume(alsa_handler)) == -EAGAIN) sleep(1);
+ }
+ if (alsa_can_pause) {
+ if ((err = snd_pcm_pause(alsa_handler, 0)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm resume error: %s\n", snd_strerror(err));
+ return;
+ }
+ mp_msg(MSGT_AO,MSGL_V,"alsa-resume: resume supported by hardware\n");
+ } else {
+ if ((err = snd_pcm_prepare(alsa_handler)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err));
+ return;
+ }
+ if (prepause_frames) {
+ void *silence = calloc(prepause_frames, bytes_per_sample);
+ play(silence, prepause_frames * bytes_per_sample, 0);
+ free(silence);
+ }
+ }
+}
+
+/* stop playing and empty buffers (for seeking/pause) */
+static void reset(void)
+{
+ int err;
+
+ prepause_frames = 0;
+ if ((err = snd_pcm_drop(alsa_handler)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err));
+ return;
+ }
+ if ((err = snd_pcm_prepare(alsa_handler)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(err));
+ return;
+ }
+ return;
+}
+
+/*
+ plays 'len' bytes of 'data'
+ returns: number of bytes played
+ modified last at 29.06.02 by jp
+ thanxs for marius <marius@rospot.com> for giving us the light ;)
+*/
+
+static int play(void* data, int len, int flags)
+{
+ int num_frames;
+ snd_pcm_sframes_t res = 0;
+ if (!(flags & AOPLAY_FINAL_CHUNK))
+ len = len / ao_data.outburst * ao_data.outburst;
+ num_frames = len / bytes_per_sample;
+
+ //mp_msg(MSGT_AO,MSGL_ERR,"alsa-play: frames=%i, len=%i\n",num_frames,len);
+
+ if (!alsa_handler) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Device configuration error.");
+ return 0;
+ }
+
+ if (num_frames == 0)
+ return 0;
+
+ do {
+ res = snd_pcm_writei(alsa_handler, data, num_frames);
+
+ if (res == -EINTR) {
+ /* nothing to do */
+ res = 0;
+ }
+ else if (res == -ESTRPIPE) { /* suspend */
+ mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Pcm in suspend mode, trying to resume.\n");
+ while ((res = snd_pcm_resume(alsa_handler)) == -EAGAIN)
+ sleep(1);
+ }
+ if (res < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Write error: %s\n", snd_strerror(res));
+ mp_tmsg(MSGT_AO,MSGL_INFO,"[AO_ALSA] Trying to reset soundcard.\n");
+ if ((res = snd_pcm_prepare(alsa_handler)) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] pcm prepare error: %s\n", snd_strerror(res));
+ return 0;
+ break;
+ }
+ }
+ } while (res == 0);
+
+ return res < 0 ? res : res * bytes_per_sample;
+}
+
+/* how many byes are free in the buffer */
+static int get_space(void)
+{
+ snd_pcm_status_t *status;
+ int ret;
+
+ snd_pcm_status_alloca(&status);
+
+ if ((ret = snd_pcm_status(alsa_handler, status)) < 0)
+ {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO_ALSA] Cannot get pcm status: %s\n", snd_strerror(ret));
+ return 0;
+ }
+
+ unsigned space = snd_pcm_status_get_avail(status) * bytes_per_sample;
+ if (space > ao_data.buffersize) // Buffer underrun?
+ space = ao_data.buffersize;
+ return space;
+}
+
+/* delay in seconds between first and last sample in buffer */
+static float get_delay(void)
+{
+ if (alsa_handler) {
+ snd_pcm_sframes_t delay;
+
+ if (snd_pcm_delay(alsa_handler, &delay) < 0)
+ return 0;
+
+ if (delay < 0) {
+ /* underrun - move the application pointer forward to catch up */
+ snd_pcm_forward(alsa_handler, -delay);
+ delay = 0;
+ }
+ return (float)delay / (float)ao_data.samplerate;
+ } else {
+ return 0;
+ }
+}
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
new file mode 100644
index 0000000000..146cfd2a22
--- /dev/null
+++ b/audio/out/ao_coreaudio.c
@@ -0,0 +1,1283 @@
+/*
+ * CoreAudio audio output driver for Mac OS X
+ *
+ * original copyright (C) Timothy J. Wood - Aug 2000
+ * ported to MPlayer libao2 by Dan Christiansen
+ *
+ * The S/PDIF part of the code is based on the auhal audio output
+ * module from VideoLAN:
+ * Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * along with MPlayer; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * The MacOS X CoreAudio framework doesn't mesh as simply as some
+ * simpler frameworks do. This is due to the fact that CoreAudio pulls
+ * audio samples rather than having them pushed at it (which is nice
+ * when you are wanting to do good buffering of audio).
+ *
+ * AC-3 and MPEG audio passthrough is possible, but has never been tested
+ * due to lack of a soundcard that supports it.
+ */
+
+#include <CoreServices/CoreServices.h>
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioToolbox.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "mp_msg.h"
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "libaf/format.h"
+#include "osdep/timer.h"
+#include "libavutil/fifo.h"
+#include "subopt-helper.h"
+
+static const ao_info_t info =
+ {
+ "Darwin/Mac OS X native audio output",
+ "coreaudio",
+ "Timothy J. Wood & Dan Christiansen & Chris Roccati",
+ ""
+ };
+
+LIBAO_EXTERN(coreaudio)
+
+/* Prefix for all mp_msg() calls */
+#define ao_msg(a, b, c...) mp_msg(a, b, "AO: [coreaudio] " c)
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED <= 1040
+/* AudioDeviceIOProcID does not exist in Mac OS X 10.4. We can emulate
+ * this by using AudioDeviceAddIOProc() and AudioDeviceRemoveIOProc(). */
+#define AudioDeviceIOProcID AudioDeviceIOProc
+#define AudioDeviceDestroyIOProcID AudioDeviceRemoveIOProc
+static OSStatus AudioDeviceCreateIOProcID(AudioDeviceID dev,
+ AudioDeviceIOProc proc,
+ void *data,
+ AudioDeviceIOProcID *procid)
+{
+ *procid = proc;
+ return AudioDeviceAddIOProc(dev, proc, data);
+}
+#endif
+
+typedef struct ao_coreaudio_s
+{
+ AudioDeviceID i_selected_dev; /* Keeps DeviceID of the selected device. */
+ int b_supports_digital; /* Does the currently selected device support digital mode? */
+ int b_digital; /* Are we running in digital mode? */
+ int b_muted; /* Are we muted in digital mode? */
+
+ AudioDeviceIOProcID renderCallback; /* Render callback used for SPDIF */
+
+ /* AudioUnit */
+ AudioUnit theOutputUnit;
+
+ /* CoreAudio SPDIF mode specific */
+ pid_t i_hog_pid; /* Keeps the pid of our hog status. */
+ AudioStreamID i_stream_id; /* The StreamID that has a cac3 streamformat */
+ int i_stream_index; /* The index of i_stream_id in an AudioBufferList */
+ AudioStreamBasicDescription stream_format;/* The format we changed the stream to */
+ AudioStreamBasicDescription sfmt_revert; /* The original format of the stream */
+ int b_revert; /* Whether we need to revert the stream format */
+ int b_changed_mixing; /* Whether we need to set the mixing mode back */
+ int b_stream_format_changed; /* Flag for main thread to reset stream's format to digital and reset buffer */
+
+ /* Original common part */
+ int packetSize;
+ int paused;
+
+ /* Ring-buffer */
+ AVFifoBuffer *buffer;
+ unsigned int buffer_len; ///< must always be num_chunks * chunk_size
+ unsigned int num_chunks;
+ unsigned int chunk_size;
+} ao_coreaudio_t;
+
+static ao_coreaudio_t *ao = NULL;
+
+/**
+ * \brief add data to ringbuffer
+ */
+static int write_buffer(unsigned char* data, int len){
+ int free = ao->buffer_len - av_fifo_size(ao->buffer);
+ if (len > free) len = free;
+ return av_fifo_generic_write(ao->buffer, data, len, NULL);
+}
+
+/**
+ * \brief remove data from ringbuffer
+ */
+static int read_buffer(unsigned char* data,int len){
+ int buffered = av_fifo_size(ao->buffer);
+ if (len > buffered) len = buffered;
+ if (data)
+ av_fifo_generic_read(ao->buffer, data, len, NULL);
+ else
+ av_fifo_drain(ao->buffer, len);
+ return len;
+}
+
+static OSStatus theRenderProc(void *inRefCon,
+ AudioUnitRenderActionFlags *inActionFlags,
+ const AudioTimeStamp *inTimeStamp,
+ UInt32 inBusNumber, UInt32 inNumFrames,
+ AudioBufferList *ioData)
+{
+int amt=av_fifo_size(ao->buffer);
+int req=(inNumFrames)*ao->packetSize;
+
+ if(amt>req)
+ amt=req;
+
+ if(amt)
+ read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt);
+ else audio_pause();
+ ioData->mBuffers[0].mDataByteSize = amt;
+
+ return noErr;
+}
+
+static int control(int cmd,void *arg){
+ao_control_vol_t *control_vol;
+OSStatus err;
+Float32 vol;
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME:
+ control_vol = (ao_control_vol_t*)arg;
+ if (ao->b_digital) {
+ // Digital output has no volume adjust.
+ int vol = ao->b_muted ? 0 : 100;
+ *control_vol = (ao_control_vol_t) {
+ .left = vol, .right = vol,
+ };
+ return CONTROL_TRUE;
+ }
+ err = AudioUnitGetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &vol);
+
+ if(err==0) {
+ // printf("GET VOL=%f\n", vol);
+ control_vol->left=control_vol->right=vol*100.0/4.0;
+ return CONTROL_TRUE;
+ }
+ else {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not get HAL output volume: [%4.4s]\n", (char *)&err);
+ return CONTROL_FALSE;
+ }
+
+ case AOCONTROL_SET_VOLUME:
+ control_vol = (ao_control_vol_t*)arg;
+
+ if (ao->b_digital) {
+ // Digital output can not set volume. Here we have to return true
+ // to make mixer forget it. Else mixer will add a soft filter,
+ // that's not we expected and the filter not support ac3 stream
+ // will cause mplayer die.
+
+ // Although not support set volume, but at least we support mute.
+ // MPlayer set mute by set volume to zero, we handle it.
+ if (control_vol->left == 0 && control_vol->right == 0)
+ ao->b_muted = 1;
+ else
+ ao->b_muted = 0;
+ return CONTROL_TRUE;
+ }
+
+ vol=(control_vol->left+control_vol->right)*4.0/200.0;
+ err = AudioUnitSetParameter(ao->theOutputUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, vol, 0);
+ if(err==0) {
+ // printf("SET VOL=%f\n", vol);
+ return CONTROL_TRUE;
+ }
+ else {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not set HAL output volume: [%4.4s]\n", (char *)&err);
+ return CONTROL_FALSE;
+ }
+ /* Everything is currently unimplemented */
+ default:
+ return CONTROL_FALSE;
+ }
+
+}
+
+
+static void print_format(int lev, const char* str, const AudioStreamBasicDescription *f){
+ uint32_t flags=(uint32_t) f->mFormatFlags;
+ ao_msg(MSGT_AO,lev, "%s %7.1fHz %"PRIu32"bit [%c%c%c%c][%"PRIu32"][%"PRIu32"][%"PRIu32"][%"PRIu32"][%"PRIu32"] %s %s %s%s%s%s\n",
+ str, f->mSampleRate, f->mBitsPerChannel,
+ (int)(f->mFormatID & 0xff000000) >> 24,
+ (int)(f->mFormatID & 0x00ff0000) >> 16,
+ (int)(f->mFormatID & 0x0000ff00) >> 8,
+ (int)(f->mFormatID & 0x000000ff) >> 0,
+ f->mFormatFlags, f->mBytesPerPacket,
+ f->mFramesPerPacket, f->mBytesPerFrame,
+ f->mChannelsPerFrame,
+ (flags&kAudioFormatFlagIsFloat) ? "float" : "int",
+ (flags&kAudioFormatFlagIsBigEndian) ? "BE" : "LE",
+ (flags&kAudioFormatFlagIsSignedInteger) ? "S" : "U",
+ (flags&kAudioFormatFlagIsPacked) ? " packed" : "",
+ (flags&kAudioFormatFlagIsAlignedHigh) ? " aligned" : "",
+ (flags&kAudioFormatFlagIsNonInterleaved) ? " ni" : "" );
+}
+
+static OSStatus GetAudioProperty(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ UInt32 outSize, void *outData)
+{
+ AudioObjectPropertyAddress property_address;
+
+ property_address.mSelector = selector;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ return AudioObjectGetPropertyData(id, &property_address, 0, NULL, &outSize, outData);
+}
+
+static UInt32 GetAudioPropertyArray(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ AudioObjectPropertyScope scope,
+ void **outData)
+{
+ OSStatus err;
+ AudioObjectPropertyAddress property_address;
+ UInt32 i_param_size;
+
+ property_address.mSelector = selector;
+ property_address.mScope = scope;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ err = AudioObjectGetPropertyDataSize(id, &property_address, 0, NULL, &i_param_size);
+
+ if (err != noErr)
+ return 0;
+
+ *outData = malloc(i_param_size);
+
+
+ err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &i_param_size, *outData);
+
+ if (err != noErr) {
+ free(*outData);
+ return 0;
+ }
+
+ return i_param_size;
+}
+
+static UInt32 GetGlobalAudioPropertyArray(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ void **outData)
+{
+ return GetAudioPropertyArray(id, selector, kAudioObjectPropertyScopeGlobal, outData);
+}
+
+static OSStatus GetAudioPropertyString(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ char **outData)
+{
+ OSStatus err;
+ AudioObjectPropertyAddress property_address;
+ UInt32 i_param_size;
+ CFStringRef string;
+ CFIndex string_length;
+
+ property_address.mSelector = selector;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ i_param_size = sizeof(CFStringRef);
+ err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &i_param_size, &string);
+ if (err != noErr)
+ return err;
+
+ string_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string),
+ kCFStringEncodingASCII);
+ *outData = malloc(string_length + 1);
+ CFStringGetCString(string, *outData, string_length + 1, kCFStringEncodingASCII);
+
+ CFRelease(string);
+
+ return err;
+}
+
+static OSStatus SetAudioProperty(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ UInt32 inDataSize, void *inData)
+{
+ AudioObjectPropertyAddress property_address;
+
+ property_address.mSelector = selector;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ return AudioObjectSetPropertyData(id, &property_address, 0, NULL, inDataSize, inData);
+}
+
+static Boolean IsAudioPropertySettable(AudioObjectID id,
+ AudioObjectPropertySelector selector,
+ Boolean *outData)
+{
+ AudioObjectPropertyAddress property_address;
+
+ property_address.mSelector = selector;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ return AudioObjectIsPropertySettable(id, &property_address, outData);
+}
+
+static int AudioDeviceSupportsDigital( AudioDeviceID i_dev_id );
+static int AudioStreamSupportsDigital( AudioStreamID i_stream_id );
+static int OpenSPDIF(void);
+static int AudioStreamChangeFormat( AudioStreamID i_stream_id, AudioStreamBasicDescription change_format );
+static OSStatus RenderCallbackSPDIF( AudioDeviceID inDevice,
+ const AudioTimeStamp * inNow,
+ const void * inInputData,
+ const AudioTimeStamp * inInputTime,
+ AudioBufferList * outOutputData,
+ const AudioTimeStamp * inOutputTime,
+ void * threadGlobals );
+static OSStatus StreamListener( AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void *inClientData );
+static OSStatus DeviceListener( AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void *inClientData );
+
+static void print_help(void)
+{
+ OSStatus err;
+ UInt32 i_param_size;
+ int num_devices;
+ AudioDeviceID *devids;
+ char *device_name;
+
+ mp_msg(MSGT_AO, MSGL_FATAL,
+ "\n-ao coreaudio commandline help:\n"
+ "Example: mpv -ao coreaudio:device_id=266\n"
+ " open Core Audio with output device ID 266.\n"
+ "\nOptions:\n"
+ " device_id\n"
+ " ID of output device to use (0 = default device)\n"
+ " help\n"
+ " This help including list of available devices.\n"
+ "\n"
+ "Available output devices:\n");
+
+ i_param_size = GetGlobalAudioPropertyArray(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, (void **)&devids);
+
+ if (!i_param_size) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "Failed to get list of output devices.\n");
+ return;
+ }
+
+ num_devices = i_param_size / sizeof(AudioDeviceID);
+
+ for (int i = 0; i < num_devices; ++i) {
+ err = GetAudioPropertyString(devids[i], kAudioObjectPropertyName, &device_name);
+
+ if (err == noErr) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "%s (id: %"PRIu32")\n", device_name, devids[i]);
+ free(device_name);
+ } else
+ mp_msg(MSGT_AO, MSGL_FATAL, "Unknown (id: %"PRIu32")\n", devids[i]);
+ }
+
+ mp_msg(MSGT_AO, MSGL_FATAL, "\n");
+
+ free(devids);
+}
+
+static int init(int rate,int channels,int format,int flags)
+{
+AudioStreamBasicDescription inDesc;
+ComponentDescription desc;
+Component comp;
+AURenderCallbackStruct renderCallback;
+OSStatus err;
+UInt32 size, maxFrames, b_alive;
+char *psz_name;
+AudioDeviceID devid_def = 0;
+int device_id, display_help = 0;
+
+ const opt_t subopts[] = {
+ {"device_id", OPT_ARG_INT, &device_id, NULL},
+ {"help", OPT_ARG_BOOL, &display_help, NULL},
+ {NULL}
+ };
+
+ // set defaults
+ device_id = 0;
+
+ if (subopt_parse(ao_subdevice, subopts) != 0 || display_help) {
+ print_help();
+ if (!display_help)
+ return 0;
+ }
+
+ ao_msg(MSGT_AO,MSGL_V, "init([%dHz][%dch][%s][%d])\n", rate, channels, af_fmt2str_short(format), flags);
+
+ ao = calloc(1, sizeof(ao_coreaudio_t));
+
+ ao->i_selected_dev = 0;
+ ao->b_supports_digital = 0;
+ ao->b_digital = 0;
+ ao->b_muted = 0;
+ ao->b_stream_format_changed = 0;
+ ao->i_hog_pid = -1;
+ ao->i_stream_id = 0;
+ ao->i_stream_index = -1;
+ ao->b_revert = 0;
+ ao->b_changed_mixing = 0;
+
+ global_ao->per_application_mixer = true;
+ global_ao->no_persistent_volume = true;
+
+ if (device_id == 0) {
+ /* Find the ID of the default Device. */
+ err = GetAudioProperty(kAudioObjectSystemObject,
+ kAudioHardwarePropertyDefaultOutputDevice,
+ sizeof(UInt32), &devid_def);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not get default audio device: [%4.4s]\n", (char *)&err);
+ goto err_out;
+ }
+ } else {
+ devid_def = device_id;
+ }
+
+ /* Retrieve the name of the device. */
+ err = GetAudioPropertyString(devid_def,
+ kAudioObjectPropertyName,
+ &psz_name);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not get default audio device name: [%4.4s]\n", (char *)&err);
+ goto err_out;
+ }
+
+ ao_msg(MSGT_AO,MSGL_V, "got audio output device ID: %"PRIu32" Name: %s\n", devid_def, psz_name );
+
+ /* Probe whether device support S/PDIF stream output if input is AC3 stream. */
+ if (AF_FORMAT_IS_AC3(format)) {
+ if (AudioDeviceSupportsDigital(devid_def))
+ {
+ ao->b_supports_digital = 1;
+ }
+ ao_msg(MSGT_AO, MSGL_V,
+ "probe default audio output device about support for digital s/pdif output: %d\n",
+ ao->b_supports_digital );
+ }
+
+ free(psz_name);
+
+ // Save selected device id
+ ao->i_selected_dev = devid_def;
+
+ // Build Description for the input format
+ inDesc.mSampleRate=rate;
+ inDesc.mFormatID=ao->b_supports_digital ? kAudioFormat60958AC3 : kAudioFormatLinearPCM;
+ inDesc.mChannelsPerFrame=channels;
+ inDesc.mBitsPerChannel=af_fmt2bits(format);
+
+ if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) {
+ // float
+ inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked;
+ }
+ else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) {
+ // signed int
+ inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
+ }
+ else {
+ // unsigned int
+ inDesc.mFormatFlags = kAudioFormatFlagIsPacked;
+ }
+ if ((format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
+ inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
+
+ inDesc.mFramesPerPacket = 1;
+ ao->packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*channels*(inDesc.mBitsPerChannel/8);
+ print_format(MSGL_V, "source:",&inDesc);
+
+ if (ao->b_supports_digital)
+ {
+ b_alive = 1;
+ err = GetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertyDeviceIsAlive,
+ sizeof(UInt32), &b_alive);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "could not check whether device is alive: [%4.4s]\n", (char *)&err);
+ if (!b_alive)
+ ao_msg(MSGT_AO, MSGL_WARN, "device is not alive\n" );
+
+ /* S/PDIF output need device in HogMode. */
+ err = GetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertyHogMode,
+ sizeof(pid_t), &ao->i_hog_pid);
+ if (err != noErr)
+ {
+ /* This is not a fatal error. Some drivers simply don't support this property. */
+ ao_msg(MSGT_AO, MSGL_WARN, "could not check whether device is hogged: [%4.4s]\n",
+ (char *)&err);
+ ao->i_hog_pid = -1;
+ }
+
+ if (ao->i_hog_pid != -1 && ao->i_hog_pid != getpid())
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "Selected audio device is exclusively in use by another program.\n" );
+ goto err_out;
+ }
+ ao->stream_format = inDesc;
+ return OpenSPDIF();
+ }
+
+ /* original analog output code */
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = (device_id == 0) ? kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's
+ if (comp == NULL) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Unable to find Output Unit component\n");
+ goto err_out;
+ }
+
+ err = OpenAComponent(comp, &(ao->theOutputUnit)); //gains access to the services provided by the component
+ if (err) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Unable to open Output Unit component: [%4.4s]\n", (char *)&err);
+ goto err_out;
+ }
+
+ // Initialize AudioUnit
+ err = AudioUnitInitialize(ao->theOutputUnit);
+ if (err) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Unable to initialize Output Unit component: [%4.4s]\n", (char *)&err);
+ goto err_out1;
+ }
+
+ size = sizeof(AudioStreamBasicDescription);
+ err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size);
+
+ if (err) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the input format: [%4.4s]\n", (char *)&err);
+ goto err_out2;
+ }
+
+ size = sizeof(UInt32);
+ err = AudioUnitGetProperty(ao->theOutputUnit, kAudioDevicePropertyBufferSize, kAudioUnitScope_Input, 0, &maxFrames, &size);
+
+ if (err)
+ {
+ ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty returned [%4.4s] when getting kAudioDevicePropertyBufferSize\n", (char *)&err);
+ goto err_out2;
+ }
+
+ //Set the Current Device to the Default Output Unit.
+ err = AudioUnitSetProperty(ao->theOutputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &ao->i_selected_dev, sizeof(ao->i_selected_dev));
+
+ ao->chunk_size = maxFrames;//*inDesc.mBytesPerFrame;
+
+ ao_data.samplerate = inDesc.mSampleRate;
+ ao_data.channels = inDesc.mChannelsPerFrame;
+ ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame;
+ ao_data.outburst = ao->chunk_size;
+ ao_data.buffersize = ao_data.bps;
+
+ ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size;
+ ao->buffer_len = ao->num_chunks * ao->chunk_size;
+ ao->buffer = av_fifo_alloc(ao->buffer_len);
+
+ ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len);
+
+ renderCallback.inputProc = theRenderProc;
+ renderCallback.inputProcRefCon = 0;
+ err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct));
+ if (err) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the render callback: [%4.4s]\n", (char *)&err);
+ goto err_out2;
+ }
+
+ reset();
+
+ return CONTROL_OK;
+
+err_out2:
+ AudioUnitUninitialize(ao->theOutputUnit);
+err_out1:
+ CloseComponent(ao->theOutputUnit);
+err_out:
+ av_fifo_free(ao->buffer);
+ free(ao);
+ ao = NULL;
+ return CONTROL_FALSE;
+}
+
+/*****************************************************************************
+ * Setup a encoded digital stream (SPDIF)
+ *****************************************************************************/
+static int OpenSPDIF(void)
+{
+ OSStatus err = noErr;
+ UInt32 i_param_size, b_mix = 0;
+ Boolean b_writeable = 0;
+ AudioStreamID *p_streams = NULL;
+ int i, i_streams = 0;
+ AudioObjectPropertyAddress property_address;
+
+ /* Start doing the SPDIF setup process. */
+ ao->b_digital = 1;
+
+ /* Hog the device. */
+ ao->i_hog_pid = getpid() ;
+
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertyHogMode,
+ sizeof(ao->i_hog_pid), &ao->i_hog_pid);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "failed to set hogmode: [%4.4s]\n", (char *)&err);
+ ao->i_hog_pid = -1;
+ goto err_out;
+ }
+
+ property_address.mSelector = kAudioDevicePropertySupportsMixing;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ /* Set mixable to false if we are allowed to. */
+ if (AudioObjectHasProperty(ao->i_selected_dev, &property_address)) {
+ /* Set mixable to false if we are allowed to. */
+ err = IsAudioPropertySettable(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ &b_writeable);
+ err = GetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ sizeof(UInt32), &b_mix);
+ if (err == noErr && b_writeable)
+ {
+ b_mix = 0;
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ sizeof(UInt32), &b_mix);
+ ao->b_changed_mixing = 1;
+ }
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n", (char *)&err);
+ goto err_out;
+ }
+ }
+
+ /* Get a list of all the streams on this device. */
+ i_param_size = GetAudioPropertyArray(ao->i_selected_dev,
+ kAudioDevicePropertyStreams,
+ kAudioDevicePropertyScopeOutput,
+ (void **)&p_streams);
+
+ if (!i_param_size) {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not get number of streams.\n");
+ goto err_out;
+ }
+
+ i_streams = i_param_size / sizeof(AudioStreamID);
+
+ ao_msg(MSGT_AO, MSGL_V, "current device stream number: %d\n", i_streams);
+
+ for (i = 0; i < i_streams && ao->i_stream_index < 0; ++i)
+ {
+ /* Find a stream with a cac3 stream. */
+ AudioStreamRangedDescription *p_format_list = NULL;
+ int i_formats = 0, j = 0, b_digital = 0;
+
+ i_param_size = GetGlobalAudioPropertyArray(p_streams[i],
+ kAudioStreamPropertyAvailablePhysicalFormats,
+ (void **)&p_format_list);
+
+ if (!i_param_size) {
+ ao_msg(MSGT_AO, MSGL_WARN,
+ "Could not get number of stream formats.\n");
+ continue;
+ }
+
+ i_formats = i_param_size / sizeof(AudioStreamRangedDescription);
+
+ /* Check if one of the supported formats is a digital format. */
+ for (j = 0; j < i_formats; ++j)
+ {
+ if (p_format_list[j].mFormat.mFormatID == 'IAC3' ||
+ p_format_list[j].mFormat.mFormatID == 'iac3' ||
+ p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 ||
+ p_format_list[j].mFormat.mFormatID == kAudioFormatAC3)
+ {
+ b_digital = 1;
+ break;
+ }
+ }
+
+ if (b_digital)
+ {
+ /* If this stream supports a digital (cac3) format, then set it. */
+ int i_requested_rate_format = -1;
+ int i_current_rate_format = -1;
+ int i_backup_rate_format = -1;
+
+ ao->i_stream_id = p_streams[i];
+ ao->i_stream_index = i;
+
+ if (ao->b_revert == 0)
+ {
+ /* Retrieve the original format of this stream first if not done so already. */
+ err = GetAudioProperty(ao->i_stream_id,
+ kAudioStreamPropertyPhysicalFormat,
+ sizeof(ao->sfmt_revert), &ao->sfmt_revert);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN,
+ "Could not retrieve the original stream format: [%4.4s]\n",
+ (char *)&err);
+ free(p_format_list);
+ continue;
+ }
+ ao->b_revert = 1;
+ }
+
+ for (j = 0; j < i_formats; ++j)
+ if (p_format_list[j].mFormat.mFormatID == 'IAC3' ||
+ p_format_list[j].mFormat.mFormatID == 'iac3' ||
+ p_format_list[j].mFormat.mFormatID == kAudioFormat60958AC3 ||
+ p_format_list[j].mFormat.mFormatID == kAudioFormatAC3)
+ {
+ if (p_format_list[j].mFormat.mSampleRate == ao->stream_format.mSampleRate)
+ {
+ i_requested_rate_format = j;
+ break;
+ }
+ if (p_format_list[j].mFormat.mSampleRate == ao->sfmt_revert.mSampleRate)
+ i_current_rate_format = j;
+ else if (i_backup_rate_format < 0 || p_format_list[j].mFormat.mSampleRate > p_format_list[i_backup_rate_format].mFormat.mSampleRate)
+ i_backup_rate_format = j;
+ }
+
+ if (i_requested_rate_format >= 0) /* We prefer to output at the samplerate of the original audio. */
+ ao->stream_format = p_format_list[i_requested_rate_format].mFormat;
+ else if (i_current_rate_format >= 0) /* If not possible, we will try to use the current samplerate of the device. */
+ ao->stream_format = p_format_list[i_current_rate_format].mFormat;
+ else ao->stream_format = p_format_list[i_backup_rate_format].mFormat; /* And if we have to, any digital format will be just fine (highest rate possible). */
+ }
+ free(p_format_list);
+ }
+ free(p_streams);
+
+ if (ao->i_stream_index < 0)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN,
+ "Cannot find any digital output stream format when OpenSPDIF().\n");
+ goto err_out;
+ }
+
+ print_format(MSGL_V, "original stream format:", &ao->sfmt_revert);
+
+ if (!AudioStreamChangeFormat(ao->i_stream_id, ao->stream_format))
+ goto err_out;
+
+ property_address.mSelector = kAudioDevicePropertyDeviceHasChanged;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ err = AudioObjectAddPropertyListener(ao->i_selected_dev,
+ &property_address,
+ DeviceListener,
+ NULL);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceAddPropertyListener for kAudioDevicePropertyDeviceHasChanged failed: [%4.4s]\n", (char *)&err);
+
+
+ /* FIXME: If output stream is not native byte-order, we need change endian somewhere. */
+ /* Although there's no such case reported. */
+#if BYTE_ORDER == BIG_ENDIAN
+ if (!(ao->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian))
+#else
+ /* tell mplayer that we need a byteswap on AC3 streams, */
+ if (ao->stream_format.mFormatID & kAudioFormat60958AC3)
+ ao_data.format = AF_FORMAT_AC3_LE;
+
+ if (ao->stream_format.mFormatFlags & kAudioFormatFlagIsBigEndian)
+#endif
+ ao_msg(MSGT_AO, MSGL_WARN,
+ "Output stream has non-native byte order, digital output may fail.\n");
+
+ /* For ac3/dts, just use packet size 6144 bytes as chunk size. */
+ ao->chunk_size = ao->stream_format.mBytesPerPacket;
+
+ ao_data.samplerate = ao->stream_format.mSampleRate;
+ ao_data.channels = ao->stream_format.mChannelsPerFrame;
+ ao_data.bps = ao_data.samplerate * (ao->stream_format.mBytesPerPacket/ao->stream_format.mFramesPerPacket);
+ ao_data.outburst = ao->chunk_size;
+ ao_data.buffersize = ao_data.bps;
+
+ ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size;
+ ao->buffer_len = ao->num_chunks * ao->chunk_size;
+ ao->buffer = av_fifo_alloc(ao->buffer_len);
+
+ ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len);
+
+
+ /* Create IOProc callback. */
+ err = AudioDeviceCreateIOProcID(ao->i_selected_dev,
+ (AudioDeviceIOProc)RenderCallbackSPDIF,
+ (void *)ao,
+ &ao->renderCallback);
+
+ if (err != noErr || ao->renderCallback == NULL)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceAddIOProc failed: [%4.4s]\n", (char *)&err);
+ goto err_out1;
+ }
+
+ reset();
+
+ return CONTROL_TRUE;
+
+err_out1:
+ if (ao->b_revert)
+ AudioStreamChangeFormat(ao->i_stream_id, ao->sfmt_revert);
+err_out:
+ if (ao->b_changed_mixing && ao->sfmt_revert.mFormatID != kAudioFormat60958AC3)
+ {
+ int b_mix = 1;
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ sizeof(int), &b_mix);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n",
+ (char *)&err);
+ }
+ if (ao->i_hog_pid == getpid())
+ {
+ ao->i_hog_pid = -1;
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertyHogMode,
+ sizeof(ao->i_hog_pid), &ao->i_hog_pid);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "Could not release hogmode: [%4.4s]\n",
+ (char *)&err);
+ }
+ av_fifo_free(ao->buffer);
+ free(ao);
+ ao = NULL;
+ return CONTROL_FALSE;
+}
+
+/*****************************************************************************
+ * AudioDeviceSupportsDigital: Check i_dev_id for digital stream support.
+ *****************************************************************************/
+static int AudioDeviceSupportsDigital( AudioDeviceID i_dev_id )
+{
+ UInt32 i_param_size = 0;
+ AudioStreamID *p_streams = NULL;
+ int i = 0, i_streams = 0;
+ int b_return = CONTROL_FALSE;
+
+ /* Retrieve all the output streams. */
+ i_param_size = GetAudioPropertyArray(i_dev_id,
+ kAudioDevicePropertyStreams,
+ kAudioDevicePropertyScopeOutput,
+ (void **)&p_streams);
+
+ if (!i_param_size) {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not get number of streams.\n");
+ return CONTROL_FALSE;
+ }
+
+ i_streams = i_param_size / sizeof(AudioStreamID);
+
+ for (i = 0; i < i_streams; ++i)
+ {
+ if (AudioStreamSupportsDigital(p_streams[i]))
+ b_return = CONTROL_OK;
+ }
+
+ free(p_streams);
+ return b_return;
+}
+
+/*****************************************************************************
+ * AudioStreamSupportsDigital: Check i_stream_id for digital stream support.
+ *****************************************************************************/
+static int AudioStreamSupportsDigital( AudioStreamID i_stream_id )
+{
+ UInt32 i_param_size;
+ AudioStreamRangedDescription *p_format_list = NULL;
+ int i, i_formats, b_return = CONTROL_FALSE;
+
+ /* Retrieve all the stream formats supported by each output stream. */
+ i_param_size = GetGlobalAudioPropertyArray(i_stream_id,
+ kAudioStreamPropertyAvailablePhysicalFormats,
+ (void **)&p_format_list);
+
+ if (!i_param_size) {
+ ao_msg(MSGT_AO, MSGL_WARN, "Could not get number of stream formats.\n");
+ return CONTROL_FALSE;
+ }
+
+ i_formats = i_param_size / sizeof(AudioStreamRangedDescription);
+
+ for (i = 0; i < i_formats; ++i)
+ {
+ print_format(MSGL_V, "supported format:", &(p_format_list[i].mFormat));
+
+ if (p_format_list[i].mFormat.mFormatID == 'IAC3' ||
+ p_format_list[i].mFormat.mFormatID == 'iac3' ||
+ p_format_list[i].mFormat.mFormatID == kAudioFormat60958AC3 ||
+ p_format_list[i].mFormat.mFormatID == kAudioFormatAC3)
+ b_return = CONTROL_OK;
+ }
+
+ free(p_format_list);
+ return b_return;
+}
+
+/*****************************************************************************
+ * AudioStreamChangeFormat: Change i_stream_id to change_format
+ *****************************************************************************/
+static int AudioStreamChangeFormat( AudioStreamID i_stream_id, AudioStreamBasicDescription change_format )
+{
+ OSStatus err = noErr;
+ int i;
+ AudioObjectPropertyAddress property_address;
+
+ static volatile int stream_format_changed;
+ stream_format_changed = 0;
+
+ print_format(MSGL_V, "setting stream format:", &change_format);
+
+ /* Install the callback. */
+ property_address.mSelector = kAudioStreamPropertyPhysicalFormat;
+ property_address.mScope = kAudioObjectPropertyScopeGlobal;
+ property_address.mElement = kAudioObjectPropertyElementMaster;
+
+ err = AudioObjectAddPropertyListener(i_stream_id,
+ &property_address,
+ StreamListener,
+ (void *)&stream_format_changed);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioStreamAddPropertyListener failed: [%4.4s]\n", (char *)&err);
+ return CONTROL_FALSE;
+ }
+
+ /* Change the format. */
+ err = SetAudioProperty(i_stream_id,
+ kAudioStreamPropertyPhysicalFormat,
+ sizeof(AudioStreamBasicDescription), &change_format);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "could not set the stream format: [%4.4s]\n", (char *)&err);
+ return CONTROL_FALSE;
+ }
+
+ /* The AudioStreamSetProperty is not only asynchronious,
+ * it is also not Atomic, in its behaviour.
+ * Therefore we check 5 times before we really give up.
+ * FIXME: failing isn't actually implemented yet. */
+ for (i = 0; i < 5; ++i)
+ {
+ AudioStreamBasicDescription actual_format;
+ int j;
+ for (j = 0; !stream_format_changed && j < 50; ++j)
+ usec_sleep(10000);
+ if (stream_format_changed)
+ stream_format_changed = 0;
+ else
+ ao_msg(MSGT_AO, MSGL_V, "reached timeout\n" );
+
+ err = GetAudioProperty(i_stream_id,
+ kAudioStreamPropertyPhysicalFormat,
+ sizeof(AudioStreamBasicDescription), &actual_format);
+
+ print_format(MSGL_V, "actual format in use:", &actual_format);
+ if (actual_format.mSampleRate == change_format.mSampleRate &&
+ actual_format.mFormatID == change_format.mFormatID &&
+ actual_format.mFramesPerPacket == change_format.mFramesPerPacket)
+ {
+ /* The right format is now active. */
+ break;
+ }
+ /* We need to check again. */
+ }
+
+ /* Removing the property listener. */
+ err = AudioObjectRemovePropertyListener(i_stream_id,
+ &property_address,
+ StreamListener,
+ (void *)&stream_format_changed);
+ if (err != noErr)
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioStreamRemovePropertyListener failed: [%4.4s]\n", (char *)&err);
+ return CONTROL_FALSE;
+ }
+
+ return CONTROL_TRUE;
+}
+
+/*****************************************************************************
+ * RenderCallbackSPDIF: callback for SPDIF audio output
+ *****************************************************************************/
+static OSStatus RenderCallbackSPDIF( AudioDeviceID inDevice,
+ const AudioTimeStamp * inNow,
+ const void * inInputData,
+ const AudioTimeStamp * inInputTime,
+ AudioBufferList * outOutputData,
+ const AudioTimeStamp * inOutputTime,
+ void * threadGlobals )
+{
+ int amt = av_fifo_size(ao->buffer);
+ int req = outOutputData->mBuffers[ao->i_stream_index].mDataByteSize;
+
+ if (amt > req)
+ amt = req;
+ if (amt)
+ read_buffer(ao->b_muted ? NULL : (unsigned char *)outOutputData->mBuffers[ao->i_stream_index].mData, amt);
+
+ return noErr;
+}
+
+
+static int play(void* output_samples,int num_bytes,int flags)
+{
+ int wrote, b_digital;
+ SInt32 exit_reason;
+
+ // Check whether we need to reset the digital output stream.
+ if (ao->b_digital && ao->b_stream_format_changed)
+ {
+ ao->b_stream_format_changed = 0;
+ b_digital = AudioStreamSupportsDigital(ao->i_stream_id);
+ if (b_digital)
+ {
+ /* Current stream supports digital format output, let's set it. */
+ ao_msg(MSGT_AO, MSGL_V,
+ "Detected current stream supports digital, try to restore digital output...\n");
+
+ if (!AudioStreamChangeFormat(ao->i_stream_id, ao->stream_format))
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "Restoring digital output failed.\n");
+ }
+ else
+ {
+ ao_msg(MSGT_AO, MSGL_WARN, "Restoring digital output succeeded.\n");
+ reset();
+ }
+ }
+ else
+ ao_msg(MSGT_AO, MSGL_V, "Detected current stream does not support digital.\n");
+ }
+
+ wrote=write_buffer(output_samples, num_bytes);
+ audio_resume();
+
+ do {
+ exit_reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, true);
+ } while (exit_reason == kCFRunLoopRunHandledSource);
+
+ return wrote;
+}
+
+/* set variables and buffer to initial state */
+static void reset(void)
+{
+ audio_pause();
+ av_fifo_reset(ao->buffer);
+}
+
+
+/* return available space */
+static int get_space(void)
+{
+ return ao->buffer_len - av_fifo_size(ao->buffer);
+}
+
+
+/* return delay until audio is played */
+static float get_delay(void)
+{
+ // inaccurate, should also contain the data buffered e.g. by the OS
+ return (float)av_fifo_size(ao->buffer)/(float)ao_data.bps;
+}
+
+
+/* unload plugin and deregister from coreaudio */
+static void uninit(int immed)
+{
+ OSStatus err = noErr;
+
+ if (!immed) {
+ long long timeleft=(1000000LL*av_fifo_size(ao->buffer))/ao_data.bps;
+ ao_msg(MSGT_AO,MSGL_DBG2, "%d bytes left @%d bps (%d usec)\n", av_fifo_size(ao->buffer), ao_data.bps, (int)timeleft);
+ usec_sleep((int)timeleft);
+ }
+
+ if (!ao->b_digital) {
+ AudioOutputUnitStop(ao->theOutputUnit);
+ AudioUnitUninitialize(ao->theOutputUnit);
+ CloseComponent(ao->theOutputUnit);
+ }
+ else {
+ /* Stop device. */
+ err = AudioDeviceStop(ao->i_selected_dev, ao->renderCallback);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStop failed: [%4.4s]\n", (char *)&err);
+
+ /* Remove IOProc callback. */
+ err = AudioDeviceDestroyIOProcID(ao->i_selected_dev, ao->renderCallback);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceRemoveIOProc failed: [%4.4s]\n", (char *)&err);
+
+ if (ao->b_revert)
+ AudioStreamChangeFormat(ao->i_stream_id, ao->sfmt_revert);
+
+ if (ao->b_changed_mixing && ao->sfmt_revert.mFormatID != kAudioFormat60958AC3)
+ {
+ UInt32 b_mix;
+ Boolean b_writeable = 0;
+ /* Revert mixable to true if we are allowed to. */
+ err = IsAudioPropertySettable(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ &b_writeable);
+ err = GetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ sizeof(UInt32), &b_mix);
+ if (err == noErr && b_writeable)
+ {
+ b_mix = 1;
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertySupportsMixing,
+ sizeof(UInt32), &b_mix);
+ }
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "failed to set mixmode: [%4.4s]\n", (char *)&err);
+ }
+ if (ao->i_hog_pid == getpid())
+ {
+ ao->i_hog_pid = -1;
+ err = SetAudioProperty(ao->i_selected_dev,
+ kAudioDevicePropertyHogMode,
+ sizeof(ao->i_hog_pid), &ao->i_hog_pid);
+ if (err != noErr) ao_msg(MSGT_AO, MSGL_WARN, "Could not release hogmode: [%4.4s]\n", (char *)&err);
+ }
+ }
+
+ av_fifo_free(ao->buffer);
+ free(ao);
+ ao = NULL;
+}
+
+
+/* stop playing, keep buffers (for pause) */
+static void audio_pause(void)
+{
+ OSErr err=noErr;
+
+ /* Stop callback. */
+ if (!ao->b_digital)
+ {
+ err=AudioOutputUnitStop(ao->theOutputUnit);
+ if (err != noErr)
+ ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned [%4.4s]\n", (char *)&err);
+ }
+ else
+ {
+ err = AudioDeviceStop(ao->i_selected_dev, ao->renderCallback);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStop failed: [%4.4s]\n", (char *)&err);
+ }
+ ao->paused = 1;
+}
+
+
+/* resume playing, after audio_pause() */
+static void audio_resume(void)
+{
+ OSErr err=noErr;
+
+ if (!ao->paused)
+ return;
+
+ /* Start callback. */
+ if (!ao->b_digital)
+ {
+ err = AudioOutputUnitStart(ao->theOutputUnit);
+ if (err != noErr)
+ ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned [%4.4s]\n", (char *)&err);
+ }
+ else
+ {
+ err = AudioDeviceStart(ao->i_selected_dev, ao->renderCallback);
+ if (err != noErr)
+ ao_msg(MSGT_AO, MSGL_WARN, "AudioDeviceStart failed: [%4.4s]\n", (char *)&err);
+ }
+ ao->paused = 0;
+}
+
+/*****************************************************************************
+ * StreamListener
+ *****************************************************************************/
+static OSStatus StreamListener( AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void *inClientData )
+{
+ for (int i=0; i < inNumberAddresses; ++i)
+ {
+ if (inAddresses[i].mSelector == kAudioStreamPropertyPhysicalFormat) {
+ ao_msg(MSGT_AO, MSGL_WARN, "got notify kAudioStreamPropertyPhysicalFormat changed.\n");
+ if (inClientData)
+ *(volatile int *)inClientData = 1;
+ break;
+ }
+ }
+ return noErr;
+}
+
+static OSStatus DeviceListener( AudioObjectID inObjectID,
+ UInt32 inNumberAddresses,
+ const AudioObjectPropertyAddress inAddresses[],
+ void *inClientData )
+{
+ for (int i=0; i < inNumberAddresses; ++i)
+ {
+ if (inAddresses[i].mSelector == kAudioDevicePropertyDeviceHasChanged) {
+ ao_msg(MSGT_AO, MSGL_WARN, "got notify kAudioDevicePropertyDeviceHasChanged.\n");
+ ao->b_stream_format_changed = 1;
+ break;
+ }
+ }
+ return noErr;
+}
diff --git a/audio/out/ao_dsound.c b/audio/out/ao_dsound.c
new file mode 100644
index 0000000000..f2f44dd401
--- /dev/null
+++ b/audio/out/ao_dsound.c
@@ -0,0 +1,648 @@
+/*
+ * Windows DirectSound interface
+ *
+ * Copyright (c) 2004 Gabor Szecsi <deje@miki.hu>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+\todo verify/extend multichannel support
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+#define DIRECTSOUND_VERSION 0x0600
+#include <dsound.h>
+#include <math.h>
+
+#include "config.h"
+#include "libaf/format.h"
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "mp_msg.h"
+#include "osdep/timer.h"
+#include "subopt-helper.h"
+
+
+static const ao_info_t info =
+{
+ "Windows DirectSound audio output",
+ "dsound",
+ "Gabor Szecsi <deje@miki.hu>",
+ ""
+};
+
+LIBAO_EXTERN(dsound)
+
+/**
+\todo use the definitions from the win32 api headers when they define these
+*/
+#define WAVE_FORMAT_IEEE_FLOAT 0x0003
+#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
+#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
+
+static const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x1,0x0000,0x0010, {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}};
+
+#define SPEAKER_FRONT_LEFT 0x1
+#define SPEAKER_FRONT_RIGHT 0x2
+#define SPEAKER_FRONT_CENTER 0x4
+#define SPEAKER_LOW_FREQUENCY 0x8
+#define SPEAKER_BACK_LEFT 0x10
+#define SPEAKER_BACK_RIGHT 0x20
+#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
+#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
+#define SPEAKER_BACK_CENTER 0x100
+#define SPEAKER_SIDE_LEFT 0x200
+#define SPEAKER_SIDE_RIGHT 0x400
+#define SPEAKER_TOP_CENTER 0x800
+#define SPEAKER_TOP_FRONT_LEFT 0x1000
+#define SPEAKER_TOP_FRONT_CENTER 0x2000
+#define SPEAKER_TOP_FRONT_RIGHT 0x4000
+#define SPEAKER_TOP_BACK_LEFT 0x8000
+#define SPEAKER_TOP_BACK_CENTER 0x10000
+#define SPEAKER_TOP_BACK_RIGHT 0x20000
+#define SPEAKER_RESERVED 0x80000000
+
+#if 0
+#define DSSPEAKER_HEADPHONE 0x00000001
+#define DSSPEAKER_MONO 0x00000002
+#define DSSPEAKER_QUAD 0x00000003
+#define DSSPEAKER_STEREO 0x00000004
+#define DSSPEAKER_SURROUND 0x00000005
+#define DSSPEAKER_5POINT1 0x00000006
+#endif
+
+#ifndef _WAVEFORMATEXTENSIBLE_
+typedef struct {
+ WAVEFORMATEX Format;
+ union {
+ WORD wValidBitsPerSample; /* bits of precision */
+ WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */
+ WORD wReserved; /* If neither applies, set to zero. */
+ } Samples;
+ DWORD dwChannelMask; /* which channels are */
+ /* present in stream */
+ GUID SubFormat;
+} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
+#endif
+
+static const int channel_mask[] = {
+ SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY,
+ SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
+ SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY,
+ SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY
+};
+
+static HINSTANCE hdsound_dll = NULL; ///handle to the dll
+static LPDIRECTSOUND hds = NULL; ///direct sound object
+static LPDIRECTSOUNDBUFFER hdspribuf = NULL; ///primary direct sound buffer
+static LPDIRECTSOUNDBUFFER hdsbuf = NULL; ///secondary direct sound buffer (stream buffer)
+static int buffer_size = 0; ///size in bytes of the direct sound buffer
+static int write_offset = 0; ///offset of the write cursor in the direct sound buffer
+static int min_free_space = 0; ///if the free space is below this value get_space() will return 0
+ ///there will always be at least this amout of free space to prevent
+ ///get_space() from returning wrong values when buffer is 100% full.
+ ///will be replaced with nBlockAlign in init()
+static int underrun_check = 0; ///0 or last reported free space (underrun detection)
+static int device_num = 0; ///wanted device number
+static GUID device; ///guid of the device
+static int audio_volume;
+
+/***************************************************************************************/
+
+/**
+\brief output error message
+\param err error code
+\return string with the error message
+*/
+static char * dserr2str(int err)
+{
+ switch (err) {
+ case DS_OK: return "DS_OK";
+ case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
+ case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
+ case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
+ case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
+ case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
+ case DSERR_GENERIC: return "DSERR_GENERIC";
+ case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
+ case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
+ case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
+ case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
+ case DSERR_NODRIVER: return "DSERR_NODRIVER";
+ case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
+ case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
+ case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
+ case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
+ case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
+ case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
+ case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
+ default: return "unknown";
+ }
+}
+
+/**
+\brief uninitialize direct sound
+*/
+static void UninitDirectSound(void)
+{
+ // finally release the DirectSound object
+ if (hds) {
+ IDirectSound_Release(hds);
+ hds = NULL;
+ }
+ // free DSOUND.DLL
+ if (hdsound_dll) {
+ FreeLibrary(hdsound_dll);
+ hdsound_dll = NULL;
+ }
+ mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound uninitialized\n");
+}
+
+/**
+\brief print the commandline help
+*/
+static void print_help(void)
+{
+ mp_msg(MSGT_AO, MSGL_FATAL,
+ "\n-ao dsound commandline help:\n"
+ "Example: mpv -ao dsound:device=1\n"
+ " sets 1st device\n"
+ "\nOptions:\n"
+ " device=<device-number>\n"
+ " Sets device number, use -v to get a list\n");
+}
+
+
+/**
+\brief enumerate direct sound devices
+\return TRUE to continue with the enumeration
+*/
+static BOOL CALLBACK DirectSoundEnum(LPGUID guid,LPCSTR desc,LPCSTR module,LPVOID context)
+{
+ int* device_index=context;
+ mp_msg(MSGT_AO, MSGL_V,"%i %s ",*device_index,desc);
+ if(device_num==*device_index){
+ mp_msg(MSGT_AO, MSGL_V,"<--");
+ if(guid){
+ memcpy(&device,guid,sizeof(GUID));
+ }
+ }
+ mp_msg(MSGT_AO, MSGL_V,"\n");
+ (*device_index)++;
+ return TRUE;
+}
+
+
+/**
+\brief initilize direct sound
+\return 0 if error, 1 if ok
+*/
+static int InitDirectSound(void)
+{
+ DSCAPS dscaps;
+
+ // initialize directsound
+ HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
+ HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID);
+ int device_index=0;
+ const opt_t subopts[] = {
+ {"device", OPT_ARG_INT, &device_num,NULL},
+ {NULL}
+ };
+ if (subopt_parse(ao_subdevice, subopts) != 0) {
+ print_help();
+ return 0;
+ }
+
+ hdsound_dll = LoadLibrary("DSOUND.DLL");
+ if (hdsound_dll == NULL) {
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot load DSOUND.DLL\n");
+ return 0;
+ }
+ OurDirectSoundCreate = (void*)GetProcAddress(hdsound_dll, "DirectSoundCreate");
+ OurDirectSoundEnumerate = (void*)GetProcAddress(hdsound_dll, "DirectSoundEnumerateA");
+
+ if (OurDirectSoundCreate == NULL || OurDirectSoundEnumerate == NULL) {
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: GetProcAddress FAILED\n");
+ FreeLibrary(hdsound_dll);
+ return 0;
+ }
+
+ // Enumerate all directsound devices
+ mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Output Devices:\n");
+ OurDirectSoundEnumerate(DirectSoundEnum,&device_index);
+
+ // Create the direct sound object
+ if FAILED(OurDirectSoundCreate((device_num)?&device:NULL, &hds, NULL )) {
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create a DirectSound device\n");
+ FreeLibrary(hdsound_dll);
+ return 0;
+ }
+
+ /* Set DirectSound Cooperative level, ie what control we want over Windows
+ * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
+ * settings of the primary buffer, but also that only the sound of our
+ * application will be hearable when it will have the focus.
+ * !!! (this is not really working as intended yet because to set the
+ * cooperative level you need the window handle of your application, and
+ * I don't know of any easy way to get it. Especially since we might play
+ * sound without any video, and so what window handle should we use ???
+ * The hack for now is to use the Desktop window handle - it seems to be
+ * working */
+ if (IDirectSound_SetCooperativeLevel(hds, GetDesktopWindow(), DSSCL_EXCLUSIVE)) {
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot set direct sound cooperative level\n");
+ IDirectSound_Release(hds);
+ FreeLibrary(hdsound_dll);
+ return 0;
+ }
+ mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound initialized\n");
+
+ memset(&dscaps, 0, sizeof(DSCAPS));
+ dscaps.dwSize = sizeof(DSCAPS);
+ if (DS_OK == IDirectSound_GetCaps(hds, &dscaps)) {
+ if (dscaps.dwFlags & DSCAPS_EMULDRIVER) mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound is emulated, waveOut may give better performance\n");
+ } else {
+ mp_msg(MSGT_AO, MSGL_V, "ao_dsound: cannot get device capabilities\n");
+ }
+
+ return 1;
+}
+
+/**
+\brief destroy the direct sound buffer
+*/
+static void DestroyBuffer(void)
+{
+ if (hdsbuf) {
+ IDirectSoundBuffer_Release(hdsbuf);
+ hdsbuf = NULL;
+ }
+ if (hdspribuf) {
+ IDirectSoundBuffer_Release(hdspribuf);
+ hdspribuf = NULL;
+ }
+}
+
+/**
+\brief fill sound buffer
+\param data pointer to the sound data to copy
+\param len length of the data to copy in bytes
+\return number of copyed bytes
+*/
+static int write_buffer(unsigned char *data, int len)
+{
+ HRESULT res;
+ LPVOID lpvPtr1;
+ DWORD dwBytes1;
+ LPVOID lpvPtr2;
+ DWORD dwBytes2;
+
+ underrun_check = 0;
+
+ // Lock the buffer
+ res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ // If the buffer was lost, restore and retry lock.
+ if (DSERR_BUFFERLOST == res)
+ {
+ IDirectSoundBuffer_Restore(hdsbuf);
+ res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
+ }
+
+
+ if (SUCCEEDED(res))
+ {
+ if( (ao_data.channels == 6) && !AF_FORMAT_IS_AC3(ao_data.format) ) {
+ // reorder channels while writing to pointers.
+ // it's this easy because buffer size and len are always
+ // aligned to multiples of channels*bytespersample
+ // there's probably some room for speed improvements here
+ const int chantable[6] = {0, 1, 4, 5, 2, 3}; // reorder "matrix"
+ int i, j;
+ int numsamp,sampsize;
+
+ sampsize = af_fmt2bits(ao_data.format)>>3; // bytes per sample
+ numsamp = dwBytes1 / (ao_data.channels * sampsize); // number of samples for each channel in this buffer
+
+ for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) {
+ memcpy((char*)lpvPtr1+(i*ao_data.channels*sampsize)+(chantable[j]*sampsize),data+(i*ao_data.channels*sampsize)+(j*sampsize),sampsize);
+ }
+
+ if (NULL != lpvPtr2 )
+ {
+ numsamp = dwBytes2 / (ao_data.channels * sampsize);
+ for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) {
+ memcpy((char*)lpvPtr2+(i*ao_data.channels*sampsize)+(chantable[j]*sampsize),data+dwBytes1+(i*ao_data.channels*sampsize)+(j*sampsize),sampsize);
+ }
+ }
+
+ write_offset+=dwBytes1+dwBytes2;
+ if(write_offset>=buffer_size)write_offset=dwBytes2;
+ } else {
+ // Write to pointers without reordering.
+ memcpy(lpvPtr1,data,dwBytes1);
+ if (NULL != lpvPtr2 )memcpy(lpvPtr2,data+dwBytes1,dwBytes2);
+ write_offset+=dwBytes1+dwBytes2;
+ if(write_offset>=buffer_size)write_offset=dwBytes2;
+ }
+
+ // Release the data back to DirectSound.
+ res = IDirectSoundBuffer_Unlock(hdsbuf,lpvPtr1,dwBytes1,lpvPtr2,dwBytes2);
+ if (SUCCEEDED(res))
+ {
+ // Success.
+ DWORD status;
+ IDirectSoundBuffer_GetStatus(hdsbuf, &status);
+ if (!(status & DSBSTATUS_PLAYING)){
+ res = IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING);
+ }
+ return dwBytes1+dwBytes2;
+ }
+ }
+ // Lock, Unlock, or Restore failed.
+ return 0;
+}
+
+/***************************************************************************************/
+
+/**
+\brief handle control commands
+\param cmd command
+\param arg argument
+\return CONTROL_OK or -1 in case the command can't be handled
+*/
+static int control(int cmd, void *arg)
+{
+ DWORD volume;
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME: {
+ ao_control_vol_t* vol = (ao_control_vol_t*)arg;
+ vol->left = vol->right = audio_volume;
+ return CONTROL_OK;
+ }
+ case AOCONTROL_SET_VOLUME: {
+ ao_control_vol_t* vol = (ao_control_vol_t*)arg;
+ volume = audio_volume = vol->right;
+ if (volume < 1)
+ volume = 1;
+ volume = (DWORD)(log10(volume) * 5000.0) - 10000;
+ IDirectSoundBuffer_SetVolume(hdsbuf, volume);
+ return CONTROL_OK;
+ }
+ }
+ return -1;
+}
+
+/**
+\brief setup sound device
+\param rate samplerate
+\param channels number of channels
+\param format format
+\param flags unused
+\return 1=success 0=fail
+*/
+static int init(int rate, int channels, int format, int flags)
+{
+ int res;
+ if (!InitDirectSound()) return 0;
+
+ global_ao->no_persistent_volume = true;
+ audio_volume = 100;
+
+ // ok, now create the buffers
+ WAVEFORMATEXTENSIBLE wformat;
+ DSBUFFERDESC dsbpridesc;
+ DSBUFFERDESC dsbdesc;
+
+ //check if the channel count and format is supported in general
+ if (channels > 6) {
+ UninitDirectSound();
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: 8 channel audio not yet supported\n");
+ return 0;
+ }
+
+ if (AF_FORMAT_IS_AC3(format))
+ format = AF_FORMAT_AC3_NE;
+ switch(format){
+ case AF_FORMAT_AC3_NE:
+ case AF_FORMAT_S24_LE:
+ case AF_FORMAT_S16_LE:
+ case AF_FORMAT_U8:
+ break;
+ default:
+ mp_msg(MSGT_AO, MSGL_V,"ao_dsound: format %s not supported defaulting to Signed 16-bit Little-Endian\n",af_fmt2str_short(format));
+ format=AF_FORMAT_S16_LE;
+ }
+ //fill global ao_data
+ ao_data.channels = channels;
+ ao_data.samplerate = rate;
+ ao_data.format = format;
+ ao_data.bps = channels * rate * (af_fmt2bits(format)>>3);
+ if(ao_data.buffersize==-1) ao_data.buffersize = ao_data.bps; // space for 1 sec
+ mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Samplerate:%iHz Channels:%i Format:%s\n", rate, channels, af_fmt2str_short(format));
+ mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Buffersize:%d bytes (%d msec)\n", ao_data.buffersize, ao_data.buffersize / ao_data.bps * 1000);
+
+ //fill waveformatex
+ ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
+ wformat.Format.cbSize = (channels > 2) ? sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX) : 0;
+ wformat.Format.nChannels = channels;
+ wformat.Format.nSamplesPerSec = rate;
+ if (AF_FORMAT_IS_AC3(format)) {
+ wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
+ wformat.Format.wBitsPerSample = 16;
+ wformat.Format.nBlockAlign = 4;
+ } else {
+ wformat.Format.wFormatTag = (channels > 2) ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
+ wformat.Format.wBitsPerSample = af_fmt2bits(format);
+ wformat.Format.nBlockAlign = wformat.Format.nChannels * (wformat.Format.wBitsPerSample >> 3);
+ }
+
+ // fill in primary sound buffer descriptor
+ memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC));
+ dsbpridesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
+ dsbpridesc.dwBufferBytes = 0;
+ dsbpridesc.lpwfxFormat = NULL;
+
+
+ // fill in the secondary sound buffer (=stream buffer) descriptor
+ memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
+ dsbdesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
+ | DSBCAPS_GLOBALFOCUS /** Allows background playing */
+ | DSBCAPS_CTRLVOLUME; /** volume control enabled */
+
+ if (channels > 2) {
+ wformat.dwChannelMask = channel_mask[channels - 3];
+ wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ wformat.Samples.wValidBitsPerSample = wformat.Format.wBitsPerSample;
+ // Needed for 5.1 on emu101k - shit soundblaster
+ dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
+ }
+ wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec * wformat.Format.nBlockAlign;
+
+ dsbdesc.dwBufferBytes = ao_data.buffersize;
+ dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat;
+ buffer_size = dsbdesc.dwBufferBytes;
+ write_offset = 0;
+ min_free_space = wformat.Format.nBlockAlign;
+ ao_data.outburst = wformat.Format.nBlockAlign * 512;
+
+ // create primary buffer and set its format
+
+ res = IDirectSound_CreateSoundBuffer( hds, &dsbpridesc, &hdspribuf, NULL );
+ if ( res != DS_OK ) {
+ UninitDirectSound();
+ mp_msg(MSGT_AO, MSGL_ERR,"ao_dsound: cannot create primary buffer (%s)\n", dserr2str(res));
+ return 0;
+ }
+ res = IDirectSoundBuffer_SetFormat( hdspribuf, (WAVEFORMATEX *)&wformat );
+ if ( res != DS_OK ) mp_msg(MSGT_AO, MSGL_WARN,"ao_dsound: cannot set primary buffer format (%s), using standard setting (bad quality)", dserr2str(res));
+
+ mp_msg(MSGT_AO, MSGL_V, "ao_dsound: primary buffer created\n");
+
+ // now create the stream buffer
+
+ res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL);
+ if (res != DS_OK) {
+ if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) {
+ // Try without DSBCAPS_LOCHARDWARE
+ dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
+ res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL);
+ }
+ if (res != DS_OK) {
+ UninitDirectSound();
+ mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create secondary (stream)buffer (%s)\n", dserr2str(res));
+ return 0;
+ }
+ }
+ mp_msg(MSGT_AO, MSGL_V, "ao_dsound: secondary (stream)buffer created\n");
+ return 1;
+}
+
+
+
+/**
+\brief stop playing and empty buffers (for seeking/pause)
+*/
+static void reset(void)
+{
+ IDirectSoundBuffer_Stop(hdsbuf);
+ // reset directsound buffer
+ IDirectSoundBuffer_SetCurrentPosition(hdsbuf, 0);
+ write_offset=0;
+ underrun_check=0;
+}
+
+/**
+\brief stop playing, keep buffers (for pause)
+*/
+static void audio_pause(void)
+{
+ IDirectSoundBuffer_Stop(hdsbuf);
+}
+
+/**
+\brief resume playing, after audio_pause()
+*/
+static void audio_resume(void)
+{
+ IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING);
+}
+
+/**
+\brief close audio device
+\param immed stop playback immediately
+*/
+static void uninit(int immed)
+{
+ if (!immed)
+ usec_sleep(get_delay() * 1000000);
+ reset();
+
+ DestroyBuffer();
+ UninitDirectSound();
+}
+
+// return exact number of free (safe to write) bytes
+static int check_free_buffer_size(void)
+{
+ int space;
+ DWORD play_offset;
+ IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL);
+ space=buffer_size-(write_offset-play_offset);
+ // | | <-- const --> | | |
+ // buffer start play_cursor write_cursor write_offset buffer end
+ // play_cursor is the actual postion of the play cursor
+ // write_cursor is the position after which it is assumed to be save to write data
+ // write_offset is the postion where we actually write the data to
+ if(space > buffer_size)space -= buffer_size; // write_offset < play_offset
+ // Check for buffer underruns. An underrun happens if DirectSound
+ // started to play old data beyond the current write_offset. Detect this
+ // by checking whether the free space shrinks, even though no data was
+ // written (i.e. no write_buffer). Doesn't always work, but the only
+ // reason we need this is to deal with the situation when playback ends,
+ // and the buffer is only half-filled.
+ if (space < underrun_check) {
+ // there's no useful data in the buffers
+ space = buffer_size;
+ reset();
+ }
+ underrun_check = space;
+ return space;
+}
+
+/**
+\brief find out how many bytes can be written into the audio buffer without
+\return free space in bytes, has to return 0 if the buffer is almost full
+*/
+static int get_space(void)
+{
+ int space = check_free_buffer_size();
+ if(space < min_free_space)return 0;
+ return space-min_free_space;
+}
+
+/**
+\brief play 'len' bytes of 'data'
+\param data pointer to the data to play
+\param len size in bytes of the data buffer, gets rounded down to outburst*n
+\param flags currently unused
+\return number of played bytes
+*/
+static int play(void* data, int len, int flags)
+{
+ int space = check_free_buffer_size();
+ if(space < len) len = space;
+
+ if (!(flags & AOPLAY_FINAL_CHUNK))
+ len = (len / ao_data.outburst) * ao_data.outburst;
+ return write_buffer(data, len);
+}
+
+/**
+\brief get the delay between the first and last sample in the buffer
+\return delay in seconds
+*/
+static float get_delay(void)
+{
+ int space = check_free_buffer_size();
+ return (float)(buffer_size - space) / (float)ao_data.bps;
+}
diff --git a/audio/out/ao_jack.c b/audio/out/ao_jack.c
new file mode 100644
index 0000000000..b30f99a14e
--- /dev/null
+++ b/audio/out/ao_jack.c
@@ -0,0 +1,361 @@
+/*
+ * JACK audio output driver for MPlayer
+ *
+ * Copyleft 2001 by Felix Bünemann (atmosfear@users.sf.net)
+ * and Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de)
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * along with MPlayer; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "mp_msg.h"
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "libaf/format.h"
+#include "osdep/timer.h"
+#include "subopt-helper.h"
+
+#include "libavutil/fifo.h"
+
+#include <jack/jack.h>
+
+static const ao_info_t info =
+{
+ "JACK audio output",
+ "jack",
+ "Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>",
+ "based on ao_sdl.c"
+};
+
+LIBAO_EXTERN(jack)
+
+//! maximum number of channels supported, avoids lots of mallocs
+#define MAX_CHANS 8
+static jack_port_t *ports[MAX_CHANS];
+static int num_ports; ///< Number of used ports == number of channels
+static jack_client_t *client;
+static float jack_latency;
+static int estimate;
+static volatile int paused = 0; ///< set if paused
+static volatile int underrun = 0; ///< signals if an underrun occured
+
+static volatile float callback_interval = 0;
+static volatile float callback_time = 0;
+
+//! size of one chunk, if this is too small MPlayer will start to "stutter"
+//! after a short time of playback
+#define CHUNK_SIZE (16 * 1024)
+//! number of "virtual" chunks the buffer consists of
+#define NUM_CHUNKS 8
+#define BUFFSIZE (NUM_CHUNKS * CHUNK_SIZE)
+
+//! buffer for audio data
+static AVFifoBuffer *buffer;
+
+/**
+ * \brief insert len bytes into buffer
+ * \param data data to insert
+ * \param len length of data
+ * \return number of bytes inserted into buffer
+ *
+ * If there is not enough room, the buffer is filled up
+ */
+static int write_buffer(unsigned char* data, int len) {
+ int free = av_fifo_space(buffer);
+ if (len > free) len = free;
+ return av_fifo_generic_write(buffer, data, len, NULL);
+}
+
+static void silence(float **bufs, int cnt, int num_bufs);
+
+struct deinterleave {
+ float **bufs;
+ int num_bufs;
+ int cur_buf;
+ int pos;
+};
+
+static void deinterleave(void *info, void *src, int len) {
+ struct deinterleave *di = info;
+ float *s = src;
+ int i;
+ len /= sizeof(float);
+ for (i = 0; i < len; i++) {
+ di->bufs[di->cur_buf++][di->pos] = s[i];
+ if (di->cur_buf >= di->num_bufs) {
+ di->cur_buf = 0;
+ di->pos++;
+ }
+ }
+}
+
+/**
+ * \brief read data from buffer and splitting it into channels
+ * \param bufs num_bufs float buffers, each will contain the data of one channel
+ * \param cnt number of samples to read per channel
+ * \param num_bufs number of channels to split the data into
+ * \return number of samples read per channel, equals cnt unless there was too
+ * little data in the buffer
+ *
+ * Assumes the data in the buffer is of type float, the number of bytes
+ * read is res * num_bufs * sizeof(float), where res is the return value.
+ * If there is not enough data in the buffer remaining parts will be filled
+ * with silence.
+ */
+static int read_buffer(float **bufs, int cnt, int num_bufs) {
+ struct deinterleave di = {bufs, num_bufs, 0, 0};
+ int buffered = av_fifo_size(buffer);
+ if (cnt * sizeof(float) * num_bufs > buffered) {
+ silence(bufs, cnt, num_bufs);
+ cnt = buffered / sizeof(float) / num_bufs;
+ }
+ av_fifo_generic_read(buffer, &di, cnt * num_bufs * sizeof(float), deinterleave);
+ return cnt;
+}
+
+// end ring buffer stuff
+
+static int control(int cmd, void *arg) {
+ return CONTROL_UNKNOWN;
+}
+
+/**
+ * \brief fill the buffers with silence
+ * \param bufs num_bufs float buffers, each will contain the data of one channel
+ * \param cnt number of samples in each buffer
+ * \param num_bufs number of buffers
+ */
+static void silence(float **bufs, int cnt, int num_bufs) {
+ int i;
+ for (i = 0; i < num_bufs; i++)
+ memset(bufs[i], 0, cnt * sizeof(float));
+}
+
+/**
+ * \brief JACK Callback function
+ * \param nframes number of frames to fill into buffers
+ * \param arg unused
+ * \return currently always 0
+ *
+ * Write silence into buffers if paused or an underrun occured
+ */
+static int outputaudio(jack_nframes_t nframes, void *arg) {
+ float *bufs[MAX_CHANS];
+ int i;
+ for (i = 0; i < num_ports; i++)
+ bufs[i] = jack_port_get_buffer(ports[i], nframes);
+ if (paused || underrun)
+ silence(bufs, nframes, num_ports);
+ else
+ if (read_buffer(bufs, nframes, num_ports) < nframes)
+ underrun = 1;
+ if (estimate) {
+ float now = (float)GetTimer() / 1000000.0;
+ float diff = callback_time + callback_interval - now;
+ if ((diff > -0.002) && (diff < 0.002))
+ callback_time += callback_interval;
+ else
+ callback_time = now;
+ callback_interval = (float)nframes / (float)ao_data.samplerate;
+ }
+ return 0;
+}
+
+/**
+ * \brief print suboption usage help
+ */
+static void print_help (void)
+{
+ mp_msg (MSGT_AO, MSGL_FATAL,
+ "\n-ao jack commandline help:\n"
+ "Example: mpv -ao jack:port=myout\n"
+ " connects mpv to the jack ports named myout\n"
+ "\nOptions:\n"
+ " port=<port name>\n"
+ " Connects to the given ports instead of the default physical ones\n"
+ " name=<client name>\n"
+ " Client name to pass to JACK\n"
+ " estimate\n"
+ " Estimates the amount of data in buffers (experimental)\n"
+ " autostart\n"
+ " Automatically start JACK server if necessary\n"
+ );
+}
+
+static int init(int rate, int channels, int format, int flags) {
+ const char **matching_ports = NULL;
+ char *port_name = NULL;
+ char *client_name = NULL;
+ int autostart = 0;
+ const opt_t subopts[] = {
+ {"port", OPT_ARG_MSTRZ, &port_name, NULL},
+ {"name", OPT_ARG_MSTRZ, &client_name, NULL},
+ {"estimate", OPT_ARG_BOOL, &estimate, NULL},
+ {"autostart", OPT_ARG_BOOL, &autostart, NULL},
+ {NULL}
+ };
+ jack_options_t open_options = JackUseExactName;
+ int port_flags = JackPortIsInput;
+ int i;
+ estimate = 1;
+ if (subopt_parse(ao_subdevice, subopts) != 0) {
+ print_help();
+ return 0;
+ }
+ if (channels > MAX_CHANS) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] Invalid number of channels: %i\n", channels);
+ goto err_out;
+ }
+ if (!client_name) {
+ client_name = malloc(40);
+ sprintf(client_name, "mpv [%d]", getpid());
+ }
+ if (!autostart)
+ open_options |= JackNoStartServer;
+ client = jack_client_open(client_name, open_options, NULL);
+ if (!client) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] cannot open server\n");
+ goto err_out;
+ }
+ buffer = av_fifo_alloc(BUFFSIZE);
+ jack_set_process_callback(client, outputaudio, 0);
+
+ // list matching ports
+ if (!port_name)
+ port_flags |= JackPortIsPhysical;
+ matching_ports = jack_get_ports(client, port_name, NULL, port_flags);
+ if (!matching_ports || !matching_ports[0]) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] no physical ports available\n");
+ goto err_out;
+ }
+ i = 1;
+ while (matching_ports[i]) i++;
+ if (channels > i) channels = i;
+ num_ports = channels;
+
+ // create out output ports
+ for (i = 0; i < num_ports; i++) {
+ char pname[30];
+ snprintf(pname, 30, "out_%d", i);
+ ports[i] = jack_port_register(client, pname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+ if (!ports[i]) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] not enough ports available\n");
+ goto err_out;
+ }
+ }
+ if (jack_activate(client)) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] activate failed\n");
+ goto err_out;
+ }
+ for (i = 0; i < num_ports; i++) {
+ if (jack_connect(client, jack_port_name(ports[i]), matching_ports[i])) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] connecting failed\n");
+ goto err_out;
+ }
+ }
+ rate = jack_get_sample_rate(client);
+ jack_latency = (float)(jack_port_get_total_latency(client, ports[0]) +
+ jack_get_buffer_size(client)) / (float)rate;
+ callback_interval = 0;
+
+ ao_data.channels = channels;
+ ao_data.samplerate = rate;
+ ao_data.format = AF_FORMAT_FLOAT_NE;
+ ao_data.bps = channels * rate * sizeof(float);
+ ao_data.buffersize = CHUNK_SIZE * NUM_CHUNKS;
+ ao_data.outburst = CHUNK_SIZE;
+ free(matching_ports);
+ free(port_name);
+ free(client_name);
+ return 1;
+
+err_out:
+ free(matching_ports);
+ free(port_name);
+ free(client_name);
+ if (client)
+ jack_client_close(client);
+ av_fifo_free(buffer);
+ buffer = NULL;
+ return 0;
+}
+
+// close audio device
+static void uninit(int immed) {
+ if (!immed)
+ usec_sleep(get_delay() * 1000 * 1000);
+ // HACK, make sure jack doesn't loop-output dirty buffers
+ reset();
+ usec_sleep(100 * 1000);
+ jack_client_close(client);
+ av_fifo_free(buffer);
+ buffer = NULL;
+}
+
+/**
+ * \brief stop playing and empty buffers (for seeking/pause)
+ */
+static void reset(void) {
+ paused = 1;
+ av_fifo_reset(buffer);
+ paused = 0;
+}
+
+/**
+ * \brief stop playing, keep buffers (for pause)
+ */
+static void audio_pause(void) {
+ paused = 1;
+}
+
+/**
+ * \brief resume playing, after audio_pause()
+ */
+static void audio_resume(void) {
+ paused = 0;
+}
+
+static int get_space(void) {
+ return av_fifo_space(buffer);
+}
+
+/**
+ * \brief write data into buffer and reset underrun flag
+ */
+static int play(void *data, int len, int flags) {
+ if (!(flags & AOPLAY_FINAL_CHUNK))
+ len -= len % ao_data.outburst;
+ underrun = 0;
+ return write_buffer(data, len);
+}
+
+static float get_delay(void) {
+ int buffered = av_fifo_size(buffer); // could be less
+ float in_jack = jack_latency;
+ if (estimate && callback_interval > 0) {
+ float elapsed = (float)GetTimer() / 1000000.0 - callback_time;
+ in_jack += callback_interval - elapsed;
+ if (in_jack < 0) in_jack = 0;
+ }
+ return (float)buffered / (float)ao_data.bps + in_jack;
+}
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
new file mode 100644
index 0000000000..ef76db2717
--- /dev/null
+++ b/audio/out/ao_lavc.c
@@ -0,0 +1,621 @@
+/*
+ * audio encoding using libavformat
+ * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org>
+ * NOTE: this file is partially based on ao_pcm.c by Atmosfear
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libavutil/common.h>
+#include <libavutil/audioconvert.h>
+
+#include "config.h"
+#include "options.h"
+#include "mpcommon.h"
+#include "fmt-conversion.h"
+#include "libaf/format.h"
+#include "libaf/reorder_ch.h"
+#include "talloc.h"
+#include "audio_out.h"
+#include "mp_msg.h"
+
+#include "encode_lavc.h"
+
+static const char *sample_padding_signed = "\x00\x00\x00\x00";
+static const char *sample_padding_u8 = "\x80";
+static const char *sample_padding_float = "\x00\x00\x00\x00";
+
+struct priv {
+ uint8_t *buffer;
+ size_t buffer_size;
+ AVStream *stream;
+ int pcmhack;
+ int aframesize;
+ int aframecount;
+ int offset;
+ int offset_left;
+ int64_t savepts;
+ int framecount;
+ int64_t lastpts;
+ int sample_size;
+ const void *sample_padding;
+ double expected_next_pts;
+
+ AVRational worst_time_base;
+ int worst_time_base_is_stream;
+};
+
+// open & setup audio device
+static int init(struct ao *ao, char *params)
+{
+ struct priv *ac = talloc_zero(ao, struct priv);
+ const enum AVSampleFormat *sampleformat;
+ AVCodec *codec;
+
+ if (!encode_lavc_available(ao->encode_lavc_ctx)) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR,
+ "ao-lavc: the option -o (output file) must be specified\n");
+ return -1;
+ }
+
+ if (ac->stream) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: rejecting reinitialization\n");
+ return -1;
+ }
+
+ ac->stream = encode_lavc_alloc_stream(ao->encode_lavc_ctx,
+ AVMEDIA_TYPE_AUDIO);
+
+ if (!ac->stream) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: could not get a new audio stream\n");
+ return -1;
+ }
+
+ codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
+
+ // ac->stream->time_base.num = 1;
+ // ac->stream->time_base.den = ao->samplerate;
+ // doing this breaks mpeg2ts in ffmpeg
+ // which doesn't properly force the time base to be 90000
+ // furthermore, ffmpeg.c doesn't do this either and works
+
+ ac->stream->codec->time_base.num = 1;
+ ac->stream->codec->time_base.den = ao->samplerate;
+
+ ac->stream->codec->sample_rate = ao->samplerate;
+ ac->stream->codec->channels = ao->channels;
+
+ ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
+
+ {
+ // first check if the selected format is somewhere in the list of
+ // supported formats by the codec
+ for (sampleformat = codec->sample_fmts;
+ sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
+ ++sampleformat) {
+ switch (*sampleformat) {
+ case AV_SAMPLE_FMT_U8:
+ if (ao->format == AF_FORMAT_U8)
+ goto out_search;
+ break;
+ case AV_SAMPLE_FMT_S16:
+ if (ao->format == AF_FORMAT_S16_BE)
+ goto out_search;
+ if (ao->format == AF_FORMAT_S16_LE)
+ goto out_search;
+ break;
+ case AV_SAMPLE_FMT_S32:
+ if (ao->format == AF_FORMAT_S32_BE)
+ goto out_search;
+ if (ao->format == AF_FORMAT_S32_LE)
+ goto out_search;
+ break;
+ case AV_SAMPLE_FMT_FLT:
+ if (ao->format == AF_FORMAT_FLOAT_BE)
+ goto out_search;
+ if (ao->format == AF_FORMAT_FLOAT_LE)
+ goto out_search;
+ break;
+ default:
+ break;
+ }
+ }
+out_search:
+ ;
+ }
+
+ if (!sampleformat || *sampleformat == AV_SAMPLE_FMT_NONE) {
+ // if the selected format is not supported, we have to pick the first
+ // one we CAN support
+ // note: not needing to select endianness here, as the switch() below
+ // does that anyway for us
+ for (sampleformat = codec->sample_fmts;
+ sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
+ ++sampleformat) {
+ switch (*sampleformat) {
+ case AV_SAMPLE_FMT_U8:
+ ao->format = AF_FORMAT_U8;
+ goto out_takefirst;
+ case AV_SAMPLE_FMT_S16:
+ ao->format = AF_FORMAT_S16_NE;
+ goto out_takefirst;
+ case AV_SAMPLE_FMT_S32:
+ ao->format = AF_FORMAT_S32_NE;
+ goto out_takefirst;
+ case AV_SAMPLE_FMT_FLT:
+ ao->format = AF_FORMAT_FLOAT_NE;
+ goto out_takefirst;
+ default:
+ break;
+ }
+ }
+out_takefirst:
+ ;
+ }
+
+ switch (ao->format) {
+ // now that we have chosen a format, set up the fields for it, boldly
+ // switching endianness if needed (mplayer code will convert for us
+ // anyway, but ffmpeg always expects native endianness)
+ case AF_FORMAT_U8:
+ ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_U8;
+ ac->sample_size = 1;
+ ac->sample_padding = sample_padding_u8;
+ ao->format = AF_FORMAT_U8;
+ break;
+ default:
+ case AF_FORMAT_S16_BE:
+ case AF_FORMAT_S16_LE:
+ ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S16;
+ ac->sample_size = 2;
+ ac->sample_padding = sample_padding_signed;
+ ao->format = AF_FORMAT_S16_NE;
+ break;
+ case AF_FORMAT_S32_BE:
+ case AF_FORMAT_S32_LE:
+ ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S32;
+ ac->sample_size = 4;
+ ac->sample_padding = sample_padding_signed;
+ ao->format = AF_FORMAT_S32_NE;
+ break;
+ case AF_FORMAT_FLOAT_BE:
+ case AF_FORMAT_FLOAT_LE:
+ ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_FLT;
+ ac->sample_size = 4;
+ ac->sample_padding = sample_padding_float;
+ ao->format = AF_FORMAT_FLOAT_NE;
+ break;
+ }
+
+ ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
+
+ switch (ao->channels) {
+ case 1:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_MONO;
+ break;
+ case 2:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_STEREO;
+ break;
+ /* someone please check if these are what mplayer normally assumes
+ case 3:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_SURROUND;
+ break;
+ case 4:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_2_2;
+ break;
+ */
+ case 5:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT0;
+ break;
+ case 6:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_5POINT1;
+ break;
+ case 8:
+ ac->stream->codec->channel_layout = AV_CH_LAYOUT_7POINT1;
+ break;
+ default:
+ mp_msg(MSGT_ENCODE, MSGL_ERR,
+ "ao-lavc: unknown channel layout; hoping for the best\n");
+ break;
+ }
+
+ if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0)
+ return -1;
+
+ ac->pcmhack = 0;
+ if (ac->stream->codec->frame_size <= 1)
+ ac->pcmhack = av_get_bits_per_sample(ac->stream->codec->codec_id) / 8;
+
+ if (ac->pcmhack) {
+ ac->aframesize = 16384; // "enough"
+ ac->buffer_size = ac->aframesize * ac->pcmhack * ao->channels * 2 + 200;
+ } else {
+ ac->aframesize = ac->stream->codec->frame_size;
+ ac->buffer_size = ac->aframesize * ac->sample_size * ao->channels * 2 +
+ 200;
+ }
+ if (ac->buffer_size < FF_MIN_BUFFER_SIZE)
+ ac->buffer_size = FF_MIN_BUFFER_SIZE;
+ ac->buffer = talloc_size(ac, ac->buffer_size);
+
+ // enough frames for at least 0.25 seconds
+ ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
+ // but at least one!
+ ac->framecount = FFMAX(ac->framecount, 1);
+
+ ac->savepts = MP_NOPTS_VALUE;
+ ac->lastpts = MP_NOPTS_VALUE;
+ ac->offset = ac->stream->codec->sample_rate *
+ encode_lavc_getoffset(ao->encode_lavc_ctx, ac->stream);
+ ac->offset_left = ac->offset;
+
+ //fill_ao_data:
+ ao->outburst = ac->aframesize * ac->sample_size * ao->channels *
+ ac->framecount;
+ ao->buffersize = ao->outburst * 2;
+ ao->bps = ao->channels * ao->samplerate * ac->sample_size;
+ ao->untimed = true;
+ ao->priv = ac;
+
+ return 0;
+}
+
+static void fill_with_padding(void *buf, int cnt, int sz, const void *padding)
+{
+ int i;
+ if (sz == 1) {
+ memset(buf, cnt, *(char *)padding);
+ return;
+ }
+ for (i = 0; i < cnt; ++i)
+ memcpy((char *) buf + i * sz, padding, sz);
+}
+
+// close audio device
+static int encode(struct ao *ao, double apts, void *data);
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *ac = ao->priv;
+ struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
+
+ if (!encode_lavc_start(ectx)) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "ao-lavc: not even ready to encode audio at end -> dropped");
+ return;
+ }
+
+ if (ac->buffer) {
+ double pts = ao->pts + ac->offset / (double) ao->samplerate;
+ if (ao->buffer.len > 0) {
+ void *paddingbuf = talloc_size(ao,
+ ac->aframesize * ao->channels * ac->sample_size);
+ memcpy(paddingbuf, ao->buffer.start, ao->buffer.len);
+ fill_with_padding((char *) paddingbuf + ao->buffer.len,
+ (ac->aframesize * ao->channels * ac->sample_size
+ - ao->buffer.len) / ac->sample_size,
+ ac->sample_size, ac->sample_padding);
+ encode(ao, pts, paddingbuf);
+ pts += ac->aframesize / (double) ao->samplerate;
+ talloc_free(paddingbuf);
+ ao->buffer.len = 0;
+ }
+ while (encode(ao, pts, NULL) > 0) ;
+ }
+
+ ao->priv = NULL;
+}
+
+// return: how many bytes can be played without blocking
+static int get_space(struct ao *ao)
+{
+ return ao->outburst;
+}
+
+// must get exactly ac->aframesize amount of data
+static int encode(struct ao *ao, double apts, void *data)
+{
+ AVFrame *frame;
+ AVPacket packet;
+ struct priv *ac = ao->priv;
+ struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
+ double realapts = ac->aframecount * (double) ac->aframesize /
+ ao->samplerate;
+ int status, gotpacket;
+
+ ac->aframecount++;
+ if (data && (ao->channels == 5 || ao->channels == 6 || ao->channels == 8)) {
+ reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT,
+ AF_CHANNEL_LAYOUT_LAVC_DEFAULT,
+ ao->channels,
+ ac->aframesize * ao->channels, ac->sample_size);
+ }
+
+ if (data)
+ ectx->audio_pts_offset = realapts - apts;
+
+ av_init_packet(&packet);
+ packet.data = ac->buffer;
+ packet.size = ac->buffer_size;
+ if(data)
+ {
+ frame = avcodec_alloc_frame();
+ frame->nb_samples = ac->aframesize;
+ if(avcodec_fill_audio_frame(frame, ao->channels, ac->stream->codec->sample_fmt, data, ac->aframesize * ao->channels * ac->sample_size, 1))
+ {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error filling\n");
+ return -1;
+ }
+
+ if (ectx->options->rawts || ectx->options->copyts) {
+ // real audio pts
+ frame->pts = floor(apts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
+ } else {
+ // audio playback time
+ frame->pts = floor(realapts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
+ }
+
+ int64_t frame_pts = av_rescale_q(frame->pts, ac->stream->codec->time_base, ac->worst_time_base);
+ if (ac->lastpts != MP_NOPTS_VALUE && frame_pts <= ac->lastpts) {
+ // this indicates broken video
+ // (video pts failing to increase fast enough to match audio)
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: audio frame pts went backwards "
+ "(%d <- %d), autofixed\n", (int)frame->pts,
+ (int)ac->lastpts);
+ frame_pts = ac->lastpts + 1;
+ frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->stream->codec->time_base);
+ }
+ ac->lastpts = frame_pts;
+
+ frame->quality = ac->stream->codec->global_quality;
+ status = avcodec_encode_audio2(ac->stream->codec, &packet, frame, &gotpacket);
+
+ if (!status) {
+ if (ac->savepts == MP_NOPTS_VALUE)
+ ac->savepts = frame->pts;
+ }
+
+ avcodec_free_frame(&frame);
+ }
+ else
+ {
+ status = avcodec_encode_audio2(ac->stream->codec, &packet, NULL, &gotpacket);
+ }
+
+ if(status)
+ {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error encoding\n");
+ return -1;
+ }
+
+ if(!gotpacket)
+ return 0;
+
+ mp_msg(MSGT_ENCODE, MSGL_DBG2,
+ "ao-lavc: got pts %f (playback time: %f); out size: %d\n",
+ apts, realapts, packet.size);
+
+ encode_lavc_write_stats(ao->encode_lavc_ctx, ac->stream);
+
+ packet.stream_index = ac->stream->index;
+
+ // Do we need this at all? Better be safe than sorry...
+ if (packet.pts == AV_NOPTS_VALUE) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: encoder lost pts, why?\n");
+ if (ac->savepts != MP_NOPTS_VALUE)
+ packet.pts = ac->savepts;
+ }
+
+ if (packet.pts != AV_NOPTS_VALUE)
+ packet.pts = av_rescale_q(packet.pts, ac->stream->codec->time_base,
+ ac->stream->time_base);
+
+ if (packet.dts != AV_NOPTS_VALUE)
+ packet.dts = av_rescale_q(packet.dts, ac->stream->codec->time_base,
+ ac->stream->time_base);
+
+ if(packet.duration > 0)
+ packet.duration = av_rescale_q(packet.duration, ac->stream->codec->time_base,
+ ac->stream->time_base);
+
+ ac->savepts = MP_NOPTS_VALUE;
+
+ if (encode_lavc_write_frame(ao->encode_lavc_ctx, &packet) < 0) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "ao-lavc: error writing at %f %f/%f\n",
+ realapts, (double) ac->stream->time_base.num,
+ (double) ac->stream->time_base.den);
+ return -1;
+ }
+
+ return packet.size;
+}
+
+// plays 'len' bytes of 'data'
+// it should round it down to outburst*n
+// return: number of bytes played
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *ac = ao->priv;
+ struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
+ int bufpos = 0;
+ int64_t ptsoffset;
+ void *paddingbuf = NULL;
+ double nextpts;
+ double pts = ao->pts;
+ double outpts;
+
+ len /= ac->sample_size * ao->channels;
+
+ if (!encode_lavc_start(ectx)) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "ao-lavc: not ready yet for encoding audio\n");
+ return 0;
+ }
+ if (pts == MP_NOPTS_VALUE) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: frame without pts, please report; synthesizing pts instead\n");
+ // synthesize pts from previous expected next pts
+ pts = ac->expected_next_pts;
+ }
+
+ if (ac->worst_time_base.den == 0) {
+ //if (ac->stream->codec->time_base.num / ac->stream->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
+ if (ac->stream->codec->time_base.num * (double) ac->stream->time_base.den >=
+ ac->stream->time_base.num * (double) ac->stream->codec->time_base.den) {
+ mp_msg(MSGT_ENCODE, MSGL_V, "ao-lavc: NOTE: using codec time base "
+ "(%d/%d) for pts adjustment; the stream base (%d/%d) is "
+ "not worse.\n", (int)ac->stream->codec->time_base.num,
+ (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num,
+ (int)ac->stream->time_base.den);
+ ac->worst_time_base = ac->stream->codec->time_base;
+ ac->worst_time_base_is_stream = 0;
+ } else {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: NOTE: not using codec time "
+ "base (%d/%d) for pts adjustment; the stream base (%d/%d) "
+ "is worse.\n", (int)ac->stream->codec->time_base.num,
+ (int)ac->stream->codec->time_base.den, (int)ac->stream->time_base.num,
+ (int)ac->stream->time_base.den);
+ ac->worst_time_base = ac->stream->time_base;
+ ac->worst_time_base_is_stream = 1;
+ }
+
+ // NOTE: we use the following "axiom" of av_rescale_q:
+ // if time base A is worse than time base B, then
+ // av_rescale_q(av_rescale_q(x, A, B), B, A) == x
+ // this can be proven as long as av_rescale_q rounds to nearest, which
+ // it currently does
+
+ // av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
+ // and:
+ // av_rescale_q(av_rescale_q(x, A, B), B, A) * A
+ // == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
+ // == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
+ //
+ // assume this fails. Then there is a value of x*A, for which the
+ // nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
+ // Absurd, as this range MUST contain at least one multiple of B.
+ }
+
+ ptsoffset = ac->offset;
+ // this basically just edits ao->apts for syncing purposes
+
+ if (ectx->options->copyts || ectx->options->rawts) {
+ // we do not send time sync data to the video side,
+ // but we always need the exact pts, even if zero
+ } else {
+ // here we must "simulate" the pts editing
+ // 1. if we have to skip stuff, we skip it
+ // 2. if we have to add samples, we add them
+ // 3. we must still adjust ptsoffset appropriately for AV sync!
+ // invariant:
+ // if no partial skipping is done, the first frame gets ao->apts passed as pts!
+
+ if (ac->offset_left < 0) {
+ if (ac->offset_left <= -len) {
+ // skip whole frame
+ ac->offset_left += len;
+ return len * ac->sample_size * ao->channels;
+ } else {
+ // skip part of this frame, buffer/encode the rest
+ bufpos -= ac->offset_left;
+ ptsoffset += ac->offset_left;
+ ac->offset_left = 0;
+ }
+ } else if (ac->offset_left > 0) {
+ // make a temporary buffer, filled with zeroes at the start
+ // (don't worry, only happens once)
+
+ paddingbuf = talloc_size(ac, ac->sample_size * ao->channels *
+ (ac->offset_left + len));
+ fill_with_padding(paddingbuf, ac->offset_left, ac->sample_size,
+ ac->sample_padding);
+ data = (char *) paddingbuf + ac->sample_size * ao->channels *
+ ac->offset_left;
+ bufpos -= ac->offset_left; // yes, negative!
+ ptsoffset += ac->offset_left;
+ ac->offset_left = 0;
+
+ // now adjust the bufpos so the final value of bufpos is positive!
+ /*
+ int cnt = (len - bufpos) / ac->aframesize;
+ int finalbufpos = bufpos + cnt * ac->aframesize;
+ */
+ int finalbufpos = len - (len - bufpos) % ac->aframesize;
+ if (finalbufpos < 0) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ao-lavc: cannot attain the "
+ "exact requested audio sync; shifting by %d frames\n",
+ -finalbufpos);
+ bufpos -= finalbufpos;
+ }
+ }
+ }
+
+ if (!ectx->options->rawts && ectx->options->copyts) {
+ // fix the discontinuity pts offset
+ nextpts = pts + ptsoffset / (double) ao->samplerate;
+ if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
+ }
+ else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "ao-lavc: detected an unexpected discontinuity (pts jumped by "
+ "%f seconds)\n",
+ nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
+ }
+
+ outpts = pts + ectx->discontinuity_pts_offset;
+ }
+ else
+ outpts = pts;
+
+ while (len - bufpos >= ac->aframesize) {
+ encode(ao,
+ outpts + (bufpos + ptsoffset) / (double) ao->samplerate + encode_lavc_getoffset(ectx, ac->stream),
+ (char *) data + ac->sample_size * bufpos * ao->channels);
+ bufpos += ac->aframesize;
+ }
+
+ talloc_free(paddingbuf);
+
+ // calculate expected pts of next audio frame
+ ac->expected_next_pts = pts + (bufpos + ptsoffset) / (double) ao->samplerate;
+
+ if (!ectx->options->rawts && ectx->options->copyts) {
+ // set next allowed output pts value
+ nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset;
+ if (nextpts > ectx->next_in_pts)
+ ectx->next_in_pts = nextpts;
+ }
+
+ return bufpos * ac->sample_size * ao->channels;
+}
+
+const struct ao_driver audio_out_lavc = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "audio encoding using libavcodec",
+ "lavc",
+ "Rudolf Polzer <divVerent@xonotic.org>",
+ ""
+ },
+ .init = init,
+ .uninit = uninit,
+ .get_space = get_space,
+ .play = play,
+};
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
new file mode 100644
index 0000000000..87f11a51b6
--- /dev/null
+++ b/audio/out/ao_null.c
@@ -0,0 +1,129 @@
+/*
+ * null audio output driver
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "talloc.h"
+
+#include "config.h"
+#include "osdep/timer.h"
+#include "libaf/format.h"
+#include "audio_out.h"
+
+struct priv {
+ unsigned last_time;
+ float buffered_bytes;
+};
+
+static void drain(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ unsigned now = GetTimer();
+ priv->buffered_bytes -= (now - priv->last_time) / 1e6 * ao->bps;
+ if (priv->buffered_bytes < 0)
+ priv->buffered_bytes = 0;
+ priv->last_time = now;
+}
+
+static int init(struct ao *ao, char *params)
+{
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+ int samplesize = af_fmt2bits(ao->format) / 8;
+ ao->outburst = 256 * ao->channels * samplesize;
+ // A "buffer" for about 0.2 seconds of audio
+ ao->buffersize = (int)(ao->samplerate * 0.2 / 256 + 1) * ao->outburst;
+ ao->bps = ao->channels * ao->samplerate * samplesize;
+ priv->last_time = GetTimer();
+
+ return 0;
+}
+
+// close audio device
+static void uninit(struct ao *ao, bool cut_audio)
+{
+}
+
+// stop playing and empty buffers (for seeking/pause)
+static void reset(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ priv->buffered_bytes = 0;
+}
+
+// stop playing, keep buffers (for pause)
+static void pause(struct ao *ao)
+{
+ // for now, just call reset();
+ reset(ao);
+}
+
+// resume playing, after audio_pause()
+static void resume(struct ao *ao)
+{
+}
+
+static int get_space(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ drain(ao);
+ return ao->buffersize - priv->buffered_bytes;
+}
+
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+
+ int maxbursts = (ao->buffersize - priv->buffered_bytes) / ao->outburst;
+ int playbursts = len / ao->outburst;
+ int bursts = playbursts > maxbursts ? maxbursts : playbursts;
+ priv->buffered_bytes += bursts * ao->outburst;
+ return bursts * ao->outburst;
+}
+
+static float get_delay(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ drain(ao);
+ return priv->buffered_bytes / ao->bps;
+}
+
+const struct ao_driver audio_out_null = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "Null audio output",
+ "null",
+ "Tobias Diedrich <ranma+mplayer@tdiedrich.de>",
+ "",
+ },
+ .init = init,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+};
+
diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c
new file mode 100644
index 0000000000..e5a40a769d
--- /dev/null
+++ b/audio/out/ao_openal.c
@@ -0,0 +1,280 @@
+/*
+ * OpenAL audio output driver for MPlayer
+ *
+ * Copyleft 2006 by Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de)
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * along with MPlayer; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#ifdef OPENAL_AL_H
+#include <OpenAL/alc.h>
+#include <OpenAL/al.h>
+#include <OpenAL/alext.h>
+#else
+#include <AL/alc.h>
+#include <AL/al.h>
+#include <AL/alext.h>
+#endif
+
+#include "mp_msg.h"
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+#include "libaf/format.h"
+#include "osdep/timer.h"
+#include "subopt-helper.h"
+
+static const ao_info_t info =
+{
+ "OpenAL audio output",
+ "openal",
+ "Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>",
+ ""
+};
+
+LIBAO_EXTERN(openal)
+
+#define MAX_CHANS 8
+#define NUM_BUF 128
+#define CHUNK_SIZE 512
+static ALuint buffers[MAX_CHANS][NUM_BUF];
+static ALuint sources[MAX_CHANS];
+
+static int cur_buf[MAX_CHANS];
+static int unqueue_buf[MAX_CHANS];
+static int16_t *tmpbuf;
+
+
+static int control(int cmd, void *arg) {
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME:
+ case AOCONTROL_SET_VOLUME: {
+ ALfloat volume;
+ ao_control_vol_t *vol = (ao_control_vol_t *)arg;
+ if (cmd == AOCONTROL_SET_VOLUME) {
+ volume = (vol->left + vol->right) / 200.0;
+ alListenerf(AL_GAIN, volume);
+ }
+ alGetListenerf(AL_GAIN, &volume);
+ vol->left = vol->right = volume * 100;
+ return CONTROL_TRUE;
+ }
+ }
+ return CONTROL_UNKNOWN;
+}
+
+/**
+ * \brief print suboption usage help
+ */
+static void print_help(void) {
+ mp_msg(MSGT_AO, MSGL_FATAL,
+ "\n-ao openal commandline help:\n"
+ "Example: mpv -ao openal:device=subdevice\n"
+ "\nOptions:\n"
+ " device=subdevice\n"
+ " Audio device OpenAL should use. Devices can be listed\n"
+ " with -ao openal:device=help\n"
+ );
+}
+
+static void list_devices(void) {
+ if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_TRUE) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "Device listing not supported.\n");
+ return;
+ }
+ const char *list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+ mp_msg(MSGT_AO, MSGL_FATAL, "OpenAL devices:\n");
+ while (list && *list) {
+ mp_msg(MSGT_AO, MSGL_FATAL, " '%s'\n", list);
+ list = list + strlen(list) + 1;
+ }
+}
+
+static int init(int rate, int channels, int format, int flags) {
+ float position[3] = {0, 0, 0};
+ float direction[6] = {0, 0, 1, 0, -1, 0};
+ float sppos[MAX_CHANS][3] = {
+ {-1, 0, 0.5}, {1, 0, 0.5},
+ {-1, 0, -1}, {1, 0, -1},
+ {0, 0, 1}, {0, 0, 0.1},
+ {-1, 0, 0}, {1, 0, 0},
+ };
+ ALCdevice *dev = NULL;
+ ALCcontext *ctx = NULL;
+ ALCint freq = 0;
+ ALCint attribs[] = {ALC_FREQUENCY, rate, 0, 0};
+ int i;
+ char *device = NULL;
+ const opt_t subopts[] = {
+ {"device", OPT_ARG_MSTRZ, &device, NULL},
+ {NULL}
+ };
+ global_ao->no_persistent_volume = true;
+ if (subopt_parse(ao_subdevice, subopts) != 0) {
+ print_help();
+ return 0;
+ }
+ if (device && strcmp(device, "help") == 0) {
+ list_devices();
+ goto err_out;
+ }
+ if (channels > MAX_CHANS) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[OpenAL] Invalid number of channels: %i\n", channels);
+ goto err_out;
+ }
+ dev = alcOpenDevice(device);
+ if (!dev) {
+ mp_msg(MSGT_AO, MSGL_FATAL, "[OpenAL] could not open device\n");
+ goto err_out;
+ }
+ ctx = alcCreateContext(dev, attribs);
+ alcMakeContextCurrent(ctx);
+ alListenerfv(AL_POSITION, position);
+ alListenerfv(AL_ORIENTATION, direction);
+ alGenSources(channels, sources);
+ for (i = 0; i < channels; i++) {
+ cur_buf[i] = 0;
+ unqueue_buf[i] = 0;
+ alGenBuffers(NUM_BUF, buffers[i]);
+ alSourcefv(sources[i], AL_POSITION, sppos[i]);
+ alSource3f(sources[i], AL_VELOCITY, 0, 0, 0);
+ }
+ if (channels == 1)
+ alSource3f(sources[0], AL_POSITION, 0, 0, 1);
+ ao_data.channels = channels;
+ alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq);
+ if (alcGetError(dev) == ALC_NO_ERROR && freq)
+ rate = freq;
+ ao_data.samplerate = rate;
+ ao_data.format = AF_FORMAT_S16_NE;
+ ao_data.bps = channels * rate * 2;
+ ao_data.buffersize = CHUNK_SIZE * NUM_BUF;
+ ao_data.outburst = channels * CHUNK_SIZE;
+ tmpbuf = malloc(CHUNK_SIZE);
+ free(device);
+ return 1;
+
+err_out:
+ free(device);
+ return 0;
+}
+
+// close audio device
+static void uninit(int immed) {
+ ALCcontext *ctx = alcGetCurrentContext();
+ ALCdevice *dev = alcGetContextsDevice(ctx);
+ free(tmpbuf);
+ if (!immed) {
+ ALint state;
+ alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
+ while (state == AL_PLAYING) {
+ usec_sleep(10000);
+ alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
+ }
+ }
+ reset();
+ alcMakeContextCurrent(NULL);
+ alcDestroyContext(ctx);
+ alcCloseDevice(dev);
+}
+
+static void unqueue_buffers(void) {
+ ALint p;
+ int s;
+ for (s = 0; s < ao_data.channels; s++) {
+ int till_wrap = NUM_BUF - unqueue_buf[s];
+ alGetSourcei(sources[s], AL_BUFFERS_PROCESSED, &p);
+ if (p >= till_wrap) {
+ alSourceUnqueueBuffers(sources[s], till_wrap, &buffers[s][unqueue_buf[s]]);
+ unqueue_buf[s] = 0;
+ p -= till_wrap;
+ }
+ if (p) {
+ alSourceUnqueueBuffers(sources[s], p, &buffers[s][unqueue_buf[s]]);
+ unqueue_buf[s] += p;
+ }
+ }
+}
+
+/**
+ * \brief stop playing and empty buffers (for seeking/pause)
+ */
+static void reset(void) {
+ alSourceStopv(ao_data.channels, sources);
+ unqueue_buffers();
+}
+
+/**
+ * \brief stop playing, keep buffers (for pause)
+ */
+static void audio_pause(void) {
+ alSourcePausev(ao_data.channels, sources);
+}
+
+/**
+ * \brief resume playing, after audio_pause()
+ */
+static void audio_resume(void) {
+ alSourcePlayv(ao_data.channels, sources);
+}
+
+static int get_space(void) {
+ ALint queued;
+ unqueue_buffers();
+ alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
+ queued = NUM_BUF - queued - 3;
+ if (queued < 0) return 0;
+ return queued * CHUNK_SIZE * ao_data.channels;
+}
+
+/**
+ * \brief write data into buffer and reset underrun flag
+ */
+static int play(void *data, int len, int flags) {
+ ALint state;
+ int i, j, k;
+ int ch;
+ int16_t *d = data;
+ len /= ao_data.channels * CHUNK_SIZE;
+ for (i = 0; i < len; i++) {
+ for (ch = 0; ch < ao_data.channels; ch++) {
+ for (j = 0, k = ch; j < CHUNK_SIZE / 2; j++, k += ao_data.channels)
+ tmpbuf[j] = d[k];
+ alBufferData(buffers[ch][cur_buf[ch]], AL_FORMAT_MONO16, tmpbuf,
+ CHUNK_SIZE, ao_data.samplerate);
+ alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]);
+ cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF;
+ }
+ d += ao_data.channels * CHUNK_SIZE / 2;
+ }
+ alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
+ if (state != AL_PLAYING) // checked here in case of an underrun
+ alSourcePlayv(ao_data.channels, sources);
+ return len * ao_data.channels * CHUNK_SIZE;
+}
+
+static float get_delay(void) {
+ ALint queued;
+ unqueue_buffers();
+ alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
+ return queued * CHUNK_SIZE / 2 / (float)ao_data.samplerate;
+}
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
new file mode 100644
index 0000000000..9d4dde4837
--- /dev/null
+++ b/audio/out/ao_oss.c
@@ -0,0 +1,560 @@
+/*
+ * OSS audio output driver
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "config.h"
+#include "mp_msg.h"
+#include "mixer.h"
+
+#ifdef HAVE_SYS_SOUNDCARD_H
+#include <sys/soundcard.h>
+#else
+#ifdef HAVE_SOUNDCARD_H
+#include <soundcard.h>
+#endif
+#endif
+
+#include "libaf/format.h"
+
+#include "audio_out.h"
+#include "audio_out_internal.h"
+
+static const ao_info_t info =
+{
+ "OSS/ioctl audio output",
+ "oss",
+ "A'rpi",
+ ""
+};
+
+/* Support for >2 output channels added 2001-11-25 - Steve Davies <steve@daviesfam.org> */
+
+LIBAO_EXTERN(oss)
+
+static int format2oss(int format)
+{
+ switch(format)
+ {
+ case AF_FORMAT_U8: return AFMT_U8;
+ case AF_FORMAT_S8: return AFMT_S8;
+ case AF_FORMAT_U16_LE: return AFMT_U16_LE;
+ case AF_FORMAT_U16_BE: return AFMT_U16_BE;
+ case AF_FORMAT_S16_LE: return AFMT_S16_LE;
+ case AF_FORMAT_S16_BE: return AFMT_S16_BE;
+#ifdef AFMT_S24_PACKED
+ case AF_FORMAT_S24_LE: return AFMT_S24_PACKED;
+#endif
+#ifdef AFMT_U32_LE
+ case AF_FORMAT_U32_LE: return AFMT_U32_LE;
+#endif
+#ifdef AFMT_U32_BE
+ case AF_FORMAT_U32_BE: return AFMT_U32_BE;
+#endif
+#ifdef AFMT_S32_LE
+ case AF_FORMAT_S32_LE: return AFMT_S32_LE;
+#endif
+#ifdef AFMT_S32_BE
+ case AF_FORMAT_S32_BE: return AFMT_S32_BE;
+#endif
+#ifdef AFMT_FLOAT
+ case AF_FORMAT_FLOAT_NE: return AFMT_FLOAT;
+#endif
+ // SPECIALS
+ case AF_FORMAT_MU_LAW: return AFMT_MU_LAW;
+ case AF_FORMAT_A_LAW: return AFMT_A_LAW;
+ case AF_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM;
+#ifdef AFMT_MPEG
+ case AF_FORMAT_MPEG2: return AFMT_MPEG;
+#endif
+#ifdef AFMT_AC3
+ case AF_FORMAT_AC3_NE: return AFMT_AC3;
+#endif
+ }
+ mp_msg(MSGT_AO, MSGL_V, "OSS: Unknown/not supported internal format: %s\n", af_fmt2str_short(format));
+ return -1;
+}
+
+static int oss2format(int format)
+{
+ switch(format)
+ {
+ case AFMT_U8: return AF_FORMAT_U8;
+ case AFMT_S8: return AF_FORMAT_S8;
+ case AFMT_U16_LE: return AF_FORMAT_U16_LE;
+ case AFMT_U16_BE: return AF_FORMAT_U16_BE;
+ case AFMT_S16_LE: return AF_FORMAT_S16_LE;
+ case AFMT_S16_BE: return AF_FORMAT_S16_BE;
+#ifdef AFMT_S24_PACKED
+ case AFMT_S24_PACKED: return AF_FORMAT_S24_LE;
+#endif
+#ifdef AFMT_U32_LE
+ case AFMT_U32_LE: return AF_FORMAT_U32_LE;
+#endif
+#ifdef AFMT_U32_BE
+ case AFMT_U32_BE: return AF_FORMAT_U32_BE;
+#endif
+#ifdef AFMT_S32_LE
+ case AFMT_S32_LE: return AF_FORMAT_S32_LE;
+#endif
+#ifdef AFMT_S32_BE
+ case AFMT_S32_BE: return AF_FORMAT_S32_BE;
+#endif
+#ifdef AFMT_FLOAT
+ case AFMT_FLOAT: return AF_FORMAT_FLOAT_NE;
+#endif
+ // SPECIALS
+ case AFMT_MU_LAW: return AF_FORMAT_MU_LAW;
+ case AFMT_A_LAW: return AF_FORMAT_A_LAW;
+ case AFMT_IMA_ADPCM: return AF_FORMAT_IMA_ADPCM;
+#ifdef AFMT_MPEG
+ case AFMT_MPEG: return AF_FORMAT_MPEG2;
+#endif
+#ifdef AFMT_AC3
+ case AFMT_AC3: return AF_FORMAT_AC3_NE;
+#endif
+ }
+ mp_tmsg(MSGT_GLOBAL,MSGL_ERR,"[AO OSS] Unknown/Unsupported OSS format: %x.\n", format);
+ return -1;
+}
+
+static char *dsp=PATH_DEV_DSP;
+static audio_buf_info zz;
+static int audio_fd=-1;
+static int prepause_space;
+
+static const char *oss_mixer_device = PATH_DEV_MIXER;
+static int oss_mixer_channel = SOUND_MIXER_PCM;
+
+#ifdef SNDCTL_DSP_GETPLAYVOL
+static int volume_oss4(ao_control_vol_t *vol, int cmd) {
+ int v;
+
+ if (audio_fd < 0)
+ return CONTROL_ERROR;
+
+ if (cmd == AOCONTROL_GET_VOLUME) {
+ if (ioctl(audio_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1)
+ return CONTROL_ERROR;
+ vol->right = (v & 0xff00) >> 8;
+ vol->left = v & 0x00ff;
+ return CONTROL_OK;
+ } else if (cmd == AOCONTROL_SET_VOLUME) {
+ v = ((int) vol->right << 8) | (int) vol->left;
+ if (ioctl(audio_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1)
+ return CONTROL_ERROR;
+ return CONTROL_OK;
+ } else
+ return CONTROL_UNKNOWN;
+}
+#endif
+
+// to set/get/query special features/parameters
+static int control(int cmd,void *arg){
+ switch(cmd){
+ case AOCONTROL_GET_VOLUME:
+ case AOCONTROL_SET_VOLUME:
+ {
+ ao_control_vol_t *vol = (ao_control_vol_t *)arg;
+ int fd, v, devs;
+
+#ifdef SNDCTL_DSP_GETPLAYVOL
+ // Try OSS4 first
+ if (volume_oss4(vol, cmd) == CONTROL_OK)
+ return CONTROL_OK;
+#endif
+
+ if(AF_FORMAT_IS_AC3(ao_data.format))
+ return CONTROL_TRUE;
+
+ if ((fd = open(oss_mixer_device, O_RDONLY)) != -1)
+ {
+ ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
+ if (devs & (1 << oss_mixer_channel))
+ {
+ if (cmd == AOCONTROL_GET_VOLUME)
+ {
+ ioctl(fd, MIXER_READ(oss_mixer_channel), &v);
+ vol->right = (v & 0xFF00) >> 8;
+ vol->left = v & 0x00FF;
+ }
+ else
+ {
+ v = ((int)vol->right << 8) | (int)vol->left;
+ ioctl(fd, MIXER_WRITE(oss_mixer_channel), &v);
+ }
+ }
+ else
+ {
+ close(fd);
+ return CONTROL_ERROR;
+ }
+ close(fd);
+ return CONTROL_OK;
+ }
+ }
+ return CONTROL_ERROR;
+ }
+ return CONTROL_UNKNOWN;
+}
+
+// open & setup audio device
+// return: 1=success 0=fail
+static int init(int rate,int channels,int format,int flags){
+ char *mixer_channels [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
+ int oss_format;
+ char *mdev = mixer_device, *mchan = mixer_channel;
+
+ mp_msg(MSGT_AO,MSGL_V,"ao2: %d Hz %d chans %s\n",rate,channels,
+ af_fmt2str_short(format));
+
+ if (ao_subdevice) {
+ char *m,*c;
+ m = strchr(ao_subdevice,':');
+ if(m) {
+ c = strchr(m+1,':');
+ if(c) {
+ mchan = c+1;
+ c[0] = '\0';
+ }
+ mdev = m+1;
+ m[0] = '\0';
+ }
+ dsp = ao_subdevice;
+ }
+
+ if(mdev)
+ oss_mixer_device=mdev;
+ else
+ oss_mixer_device=PATH_DEV_MIXER;
+
+ if(mchan){
+ int fd, devs, i;
+
+ if ((fd = open(oss_mixer_device, O_RDONLY)) == -1){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't open mixer device %s: %s\n",
+ oss_mixer_device, strerror(errno));
+ }else{
+ ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
+ close(fd);
+
+ for (i=0; i<SOUND_MIXER_NRDEVICES; i++){
+ if(!strcasecmp(mixer_channels[i], mchan)){
+ if(!(devs & (1 << i))){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Audio card mixer does not have channel '%s', using default.\n",mchan);
+ i = SOUND_MIXER_NRDEVICES+1;
+ break;
+ }
+ oss_mixer_channel = i;
+ break;
+ }
+ }
+ if(i==SOUND_MIXER_NRDEVICES){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Audio card mixer does not have channel '%s', using default.\n",mchan);
+ }
+ }
+ } else
+ oss_mixer_channel = SOUND_MIXER_PCM;
+
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' dsp device\n", dsp);
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' mixer device\n", oss_mixer_device);
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: using '%s' mixer device\n", mixer_channels[oss_mixer_channel]);
+
+#ifdef __linux__
+ audio_fd=open(dsp, O_WRONLY | O_NONBLOCK);
+#else
+ audio_fd=open(dsp, O_WRONLY);
+#endif
+ if(audio_fd<0){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't open audio device %s: %s\n", dsp, strerror(errno));
+ return 0;
+ }
+
+#ifdef __linux__
+ /* Remove the non-blocking flag */
+ if(fcntl(audio_fd, F_SETFL, 0) < 0) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Can't make file descriptor blocking: %s\n", strerror(errno));
+ return 0;
+ }
+#endif
+
+#if defined(FD_CLOEXEC) && defined(F_SETFD)
+ fcntl(audio_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ if(AF_FORMAT_IS_AC3(format)) {
+ ao_data.samplerate=rate;
+ ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate);
+ }
+
+ac3_retry:
+ if (AF_FORMAT_IS_AC3(format))
+ format = AF_FORMAT_AC3_NE;
+ ao_data.format=format;
+ oss_format=format2oss(format);
+ if (oss_format == -1) {
+#if BYTE_ORDER == BIG_ENDIAN
+ oss_format=AFMT_S16_BE;
+#else
+ oss_format=AFMT_S16_LE;
+#endif
+ format=AF_FORMAT_S16_NE;
+ }
+ if( ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format)<0 ||
+ oss_format != format2oss(format)) {
+ mp_tmsg(MSGT_AO,MSGL_WARN, "[AO OSS] Can't set audio device %s to %s output, trying %s...\n", dsp,
+ af_fmt2str_short(format), af_fmt2str_short(AF_FORMAT_S16_NE) );
+ format=AF_FORMAT_S16_NE;
+ goto ac3_retry;
+ }
+#if 0
+ if(oss_format!=format2oss(format))
+ mp_msg(MSGT_AO,MSGL_WARN,"WARNING! Your soundcard does NOT support %s sample format! Broken audio or bad playback speed are possible! Try with '-af format'\n",audio_out_format_name(format));
+#endif
+
+ ao_data.format = oss2format(oss_format);
+ if (ao_data.format == -1) return 0;
+
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: sample format: %s (requested: %s)\n",
+ af_fmt2str_short(ao_data.format), af_fmt2str_short(format));
+
+ ao_data.channels = channels;
+ if(!AF_FORMAT_IS_AC3(format)) {
+ // We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
+ if (ao_data.channels > 2) {
+ if ( ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ao_data.channels) == -1 ||
+ ao_data.channels != channels ) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Failed to set audio device to %d channels.\n", channels);
+ return 0;
+ }
+ }
+ else {
+ int c = ao_data.channels-1;
+ if (ioctl (audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS] audio_setup: Failed to set audio device to %d channels.\n", ao_data.channels);
+ return 0;
+ }
+ ao_data.channels=c+1;
+ }
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: using %d channels (requested: %d)\n", ao_data.channels, channels);
+ // set rate
+ ao_data.samplerate=rate;
+ ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate);
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: using %d Hz samplerate (requested: %d)\n",ao_data.samplerate,rate);
+ }
+
+ if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)==-1){
+ int r=0;
+ mp_tmsg(MSGT_AO,MSGL_WARN,"[AO OSS] audio_setup: driver doesn't support SNDCTL_DSP_GETOSPACE :-(\n");
+ if(ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &r)==-1){
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: %d bytes/frag (config.h)\n",ao_data.outburst);
+ } else {
+ ao_data.outburst=r;
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: %d bytes/frag (GETBLKSIZE)\n",ao_data.outburst);
+ }
+ } else {
+ mp_msg(MSGT_AO,MSGL_V,"audio_setup: frags: %3d/%d (%d bytes/frag) free: %6d\n",
+ zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes);
+ if(ao_data.buffersize==-1) ao_data.buffersize=zz.bytes;
+ ao_data.outburst=zz.fragsize;
+ }
+
+ if(ao_data.buffersize==-1){
+ // Measuring buffer size:
+ void* data;
+ ao_data.buffersize=0;
+#ifdef HAVE_AUDIO_SELECT
+ data=malloc(ao_data.outburst); memset(data,0,ao_data.outburst);
+ while(ao_data.buffersize<0x40000){
+ fd_set rfds;
+ struct timeval tv;
+ FD_ZERO(&rfds); FD_SET(audio_fd,&rfds);
+ tv.tv_sec=0; tv.tv_usec = 0;
+ if(!select(audio_fd+1, NULL, &rfds, NULL, &tv)) break;
+ write(audio_fd,data,ao_data.outburst);
+ ao_data.buffersize+=ao_data.outburst;
+ }
+ free(data);
+ if(ao_data.buffersize==0){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS]\n *** Your audio driver DOES NOT support select() ***\n Recompile mpv with #undef HAVE_AUDIO_SELECT in config.h !\n\n");
+ return 0;
+ }
+#endif
+ }
+
+ ao_data.bps=ao_data.channels;
+ switch (ao_data.format & AF_FORMAT_BITS_MASK) {
+ case AF_FORMAT_8BIT:
+ break;
+ case AF_FORMAT_16BIT:
+ ao_data.bps*=2;
+ break;
+ case AF_FORMAT_24BIT:
+ ao_data.bps*=3;
+ break;
+ case AF_FORMAT_32BIT:
+ ao_data.bps*=4;
+ break;
+ }
+
+ ao_data.outburst-=ao_data.outburst % ao_data.bps; // round down
+ ao_data.bps*=ao_data.samplerate;
+
+ return 1;
+}
+
+// close audio device
+static void uninit(int immed){
+ if(audio_fd == -1) return;
+#ifdef SNDCTL_DSP_SYNC
+ // to get the buffer played
+ if (!immed)
+ ioctl(audio_fd, SNDCTL_DSP_SYNC, NULL);
+#endif
+#ifdef SNDCTL_DSP_RESET
+ if (immed)
+ ioctl(audio_fd, SNDCTL_DSP_RESET, NULL);
+#endif
+ close(audio_fd);
+ audio_fd = -1;
+}
+
+// stop playing and empty buffers (for seeking/pause)
+static void reset(void){
+ int oss_format;
+ uninit(1);
+ audio_fd=open(dsp, O_WRONLY);
+ if(audio_fd < 0){
+ mp_tmsg(MSGT_AO,MSGL_ERR,"[AO OSS]\nFatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE *** %s\n", strerror(errno));
+ return;
+ }
+
+#if defined(FD_CLOEXEC) && defined(F_SETFD)
+ fcntl(audio_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ oss_format = format2oss(ao_data.format);
+ if(AF_FORMAT_IS_AC3(ao_data.format))
+ ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate);
+ ioctl (audio_fd, SNDCTL_DSP_SETFMT, &oss_format);
+ if(!AF_FORMAT_IS_AC3(ao_data.format)) {
+ if (ao_data.channels > 2)
+ ioctl (audio_fd, SNDCTL_DSP_CHANNELS, &ao_data.channels);
+ else {
+ int c = ao_data.channels-1;
+ ioctl (audio_fd, SNDCTL_DSP_STEREO, &c);
+ }
+ ioctl (audio_fd, SNDCTL_DSP_SPEED, &ao_data.samplerate);
+ }
+}
+
+// stop playing, keep buffers (for pause)
+static void audio_pause(void)
+{
+ prepause_space = get_space();
+ uninit(1);
+}
+
+// resume playing, after audio_pause()
+static void audio_resume(void)
+{
+ int fillcnt;
+ reset();
+ fillcnt = get_space() - prepause_space;
+ if (fillcnt > 0 && !(ao_data.format & AF_FORMAT_SPECIAL_MASK)) {
+ void *silence = calloc(fillcnt, 1);
+ play(silence, fillcnt, 0);
+ free(silence);
+ }
+}
+
+
+// return: how many bytes can be played without blocking
+static int get_space(void){
+ int playsize=ao_data.outburst;
+
+#ifdef SNDCTL_DSP_GETOSPACE
+ if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)!=-1){
+ // calculate exact buffer space:
+ playsize = zz.fragments*zz.fragsize;
+ return playsize;
+ }
+#endif
+
+ // check buffer
+#ifdef HAVE_AUDIO_SELECT
+ { fd_set rfds;
+ struct timeval tv;
+ FD_ZERO(&rfds);
+ FD_SET(audio_fd, &rfds);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if(!select(audio_fd+1, NULL, &rfds, NULL, &tv)) return 0; // not block!
+ }
+#endif
+
+ return ao_data.outburst;
+}
+
+// plays 'len' bytes of 'data'
+// it should round it down to outburst*n
+// return: number of bytes played
+static int play(void* data,int len,int flags){
+ if(len==0)
+ return len;
+ if(len>ao_data.outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
+ len/=ao_data.outburst;
+ len*=ao_data.outburst;
+ }
+ len=write(audio_fd,data,len);
+ return len;
+}
+
+static int audio_delay_method=2;
+
+// return: delay in seconds between first and last sample in buffer
+static float get_delay(void){
+ /* Calculate how many bytes/second is sent out */
+ if(audio_delay_method==2){
+#ifdef SNDCTL_DSP_GETODELAY
+ int r=0;
+ if(ioctl(audio_fd, SNDCTL_DSP_GETODELAY, &r)!=-1)
+ return ((float)r)/(float)ao_data.bps;
+#endif
+ audio_delay_method=1; // fallback if not supported
+ }
+ if(audio_delay_method==1){
+ // SNDCTL_DSP_GETOSPACE
+ if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz)!=-1)
+ return ((float)(ao_data.buffersize-zz.bytes))/(float)ao_data.bps;
+ audio_delay_method=0; // fallback if not supported
+ }
+ return ((float)ao_data.buffersize)/(float)ao_data.bps;
+}
diff --git a/audio/out/ao_pcm.c b/audio/out/ao_pcm.c
new file mode 100644
index 0000000000..0b1c527e89
--- /dev/null
+++ b/audio/out/ao_pcm.c
@@ -0,0 +1,256 @@
+/*
+ * PCM audio output driver
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libavutil/common.h>
+
+#include "talloc.h"
+
+#include "subopt-helper.h"
+#include "libaf/format.h"
+#include "libaf/reorder_ch.h"
+#include "audio_out.h"
+#include "mp_msg.h"
+
+#ifdef __MINGW32__
+// for GetFileType to detect pipes
+#include <windows.h>
+#include <io.h>
+#endif
+
+struct priv {
+ char *outputfilename;
+ int waveheader;
+ uint64_t data_length;
+ FILE *fp;
+};
+
+#define WAV_ID_RIFF 0x46464952 /* "RIFF" */
+#define WAV_ID_WAVE 0x45564157 /* "WAVE" */
+#define WAV_ID_FMT 0x20746d66 /* "fmt " */
+#define WAV_ID_DATA 0x61746164 /* "data" */
+#define WAV_ID_PCM 0x0001
+#define WAV_ID_FLOAT_PCM 0x0003
+#define WAV_ID_FORMAT_EXTENSIBLE 0xfffe
+
+static void fput16le(uint16_t val, FILE *fp)
+{
+ uint8_t bytes[2] = {val, val >> 8};
+ fwrite(bytes, 1, 2, fp);
+}
+
+static void fput32le(uint32_t val, FILE *fp)
+{
+ uint8_t bytes[4] = {val, val >> 8, val >> 16, val >> 24};
+ fwrite(bytes, 1, 4, fp);
+}
+
+static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
+{
+ bool use_waveex = ao->channels >= 5 && ao->channels <= 8;
+ uint16_t fmt = ao->format == AF_FORMAT_FLOAT_LE ?
+ WAV_ID_FLOAT_PCM : WAV_ID_PCM;
+ uint32_t fmt_chunk_size = use_waveex ? 40 : 16;
+ int bits = af_fmt2bits(ao->format);
+
+ // Master RIFF chunk
+ fput32le(WAV_ID_RIFF, fp);
+ // RIFF chunk size: 'WAVE' + 'fmt ' + 4 + fmt_chunk_size +
+ // data chunk hdr (8) + data length
+ fput32le(12 + fmt_chunk_size + 8 + data_length, fp);
+ fput32le(WAV_ID_WAVE, fp);
+
+ // Format chunk
+ fput32le(WAV_ID_FMT, fp);
+ fput32le(fmt_chunk_size, fp);
+ fput16le(use_waveex ? WAV_ID_FORMAT_EXTENSIBLE : fmt, fp);
+ fput16le(ao->channels, fp);
+ fput32le(ao->samplerate, fp);
+ fput32le(ao->bps, fp);
+ fput16le(ao->channels * (bits / 8), fp);
+ fput16le(bits, fp);
+
+ if (use_waveex) {
+ // Extension chunk
+ fput16le(22, fp);
+ fput16le(bits, fp);
+ switch (ao->channels) {
+ case 5:
+ fput32le(0x0607, fp); // L R C Lb Rb
+ break;
+ case 6:
+ fput32le(0x060f, fp); // L R C Lb Rb LFE
+ break;
+ case 7:
+ fput32le(0x0727, fp); // L R C Cb Ls Rs LFE
+ break;
+ case 8:
+ fput32le(0x063f, fp); // L R C Lb Rb Ls Rs LFE
+ break;
+ }
+ // 2 bytes format + 14 bytes guid
+ fput32le(fmt, fp);
+ fput32le(0x00100000, fp);
+ fput32le(0xAA000080, fp);
+ fput32le(0x719B3800, fp);
+ }
+
+ // Data chunk
+ fput32le(WAV_ID_DATA, fp);
+ fput32le(data_length, fp);
+}
+
+static int init(struct ao *ao, char *params)
+{
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+
+ int fast = 0;
+ const opt_t subopts[] = {
+ {"waveheader", OPT_ARG_BOOL, &priv->waveheader, NULL},
+ {"file", OPT_ARG_MSTRZ, &priv->outputfilename, NULL},
+ {"fast", OPT_ARG_BOOL, &fast, NULL},
+ {NULL}
+ };
+ // set defaults
+ priv->waveheader = 1;
+
+ if (subopt_parse(params, subopts) != 0)
+ return -1;
+
+ if (fast)
+ mp_msg(MSGT_AO, MSGL_WARN,
+ "[AO PCM] Suboption \"fast\" is deprecated.\n"
+ "[AO PCM] Use -novideo, or -benchmark if you want "
+ "faster playback with video.\n");
+ if (!priv->outputfilename)
+ priv->outputfilename =
+ strdup(priv->waveheader ? "audiodump.wav" : "audiodump.pcm");
+ if (priv->waveheader) {
+ // WAV files must have one of the following formats
+
+ switch (ao->format) {
+ case AF_FORMAT_U8:
+ case AF_FORMAT_S16_LE:
+ case AF_FORMAT_S24_LE:
+ case AF_FORMAT_S32_LE:
+ case AF_FORMAT_FLOAT_LE:
+ case AF_FORMAT_AC3_BE:
+ case AF_FORMAT_AC3_LE:
+ break;
+ default:
+ ao->format = AF_FORMAT_S16_LE;
+ break;
+ }
+ }
+
+ ao->outburst = 65536;
+ ao->bps = ao->channels * ao->samplerate * (af_fmt2bits(ao->format) / 8);
+
+ mp_tmsg(MSGT_AO, MSGL_INFO, "[AO PCM] File: %s (%s)\n"
+ "PCM: Samplerate: %d Hz Channels: %d Format: %s\n",
+ priv->outputfilename,
+ priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
+ ao->channels, af_fmt2str_short(ao->format));
+ mp_tmsg(MSGT_AO, MSGL_INFO,
+ "[AO PCM] Info: Faster dumping is achieved with -novideo\n"
+ "[AO PCM] Info: To write WAVE files use -ao pcm:waveheader (default).\n");
+
+ priv->fp = fopen(priv->outputfilename, "wb");
+ if (!priv->fp) {
+ mp_tmsg(MSGT_AO, MSGL_ERR, "[AO PCM] Failed to open %s for writing!\n",
+ priv->outputfilename);
+ return -1;
+ }
+ if (priv->waveheader) // Reserve space for wave header
+ write_wave_header(ao, priv->fp, 0x7ffff000);
+ ao->untimed = true;
+
+ return 0;
+}
+
+// close audio device
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *priv = ao->priv;
+
+ if (priv->waveheader) { // Rewrite wave header
+ bool broken_seek = false;
+#ifdef __MINGW32__
+ // Windows, in its usual idiocy "emulates" seeks on pipes so it always
+ // looks like they work. So we have to detect them brute-force.
+ broken_seek = FILE_TYPE_DISK !=
+ GetFileType((HANDLE)_get_osfhandle(_fileno(priv->fp)));
+#endif
+ if (broken_seek || fseek(priv->fp, 0, SEEK_SET) != 0)
+ mp_msg(MSGT_AO, MSGL_ERR, "Could not seek to start, "
+ "WAV size headers not updated!\n");
+ else {
+ if (priv->data_length > 0xfffff000) {
+ mp_msg(MSGT_AO, MSGL_ERR, "File larger than allowed for "
+ "WAV files, may play truncated!\n");
+ priv->data_length = 0xfffff000;
+ }
+ write_wave_header(ao, priv->fp, priv->data_length);
+ }
+ }
+ fclose(priv->fp);
+ free(priv->outputfilename);
+}
+
+static int get_space(struct ao *ao)
+{
+ return ao->outburst;
+}
+
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+
+ if (ao->channels == 5 || ao->channels == 6 || ao->channels == 8) {
+ int frame_size = af_fmt2bits(ao->format) / 8;
+ len -= len % (frame_size * ao->channels);
+ reorder_channel_nch(data, AF_CHANNEL_LAYOUT_MPLAYER_DEFAULT,
+ AF_CHANNEL_LAYOUT_WAVEEX_DEFAULT,
+ ao->channels, len / frame_size, frame_size);
+ }
+ fwrite(data, len, 1, priv->fp);
+ priv->data_length += len;
+ return len;
+}
+
+const struct ao_driver audio_out_pcm = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "RAW PCM/WAVE file writer audio output",
+ "pcm",
+ "Atmosfear",
+ "",
+ },
+ .init = init,
+ .uninit = uninit,
+ .get_space = get_space,
+ .play = play,
+};
diff --git a/audio/out/ao_portaudio.c b/audio/out/ao_portaudio.c
new file mode 100644
index 0000000000..36b08f8288
--- /dev/null
+++ b/audio/out/ao_portaudio.c
@@ -0,0 +1,431 @@
+/*
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include <libavutil/common.h>
+#include <portaudio.h>
+
+#include "config.h"
+#include "subopt-helper.h"
+#include "libaf/format.h"
+#include "mp_msg.h"
+#include "audio_out.h"
+
+struct priv {
+ PaStream *stream;
+ int framelen;
+
+ pthread_mutex_t ring_mutex;
+
+ // protected by ring_mutex
+ unsigned char *ring;
+ int ring_size; // max size of the ring
+ int read_pos; // points to first byte that can be read
+ int read_len; // number of bytes that can be read
+ double play_time; // time when last packet returned to PA is on speaker
+ // 0 is N/A (0 is not a valid PA time value)
+ int play_silence; // play this many bytes of silence, before real data
+ bool play_remaining;// play what's left in the buffer, then stop stream
+};
+
+struct format_map {
+ int mp_format;
+ PaSampleFormat pa_format;
+};
+
+static const struct format_map format_maps[] = {
+ // first entry is the default format
+ {AF_FORMAT_S16_NE, paInt16},
+ {AF_FORMAT_S24_NE, paInt24},
+ {AF_FORMAT_S32_NE, paInt32},
+ {AF_FORMAT_S8, paInt8},
+ {AF_FORMAT_U8, paUInt8},
+ {AF_FORMAT_FLOAT_NE, paFloat32},
+ {AF_FORMAT_UNKNOWN, 0}
+};
+
+static void print_help(void)
+{
+ mp_msg(MSGT_AO, MSGL_FATAL,
+ "\n-ao portaudio commandline help:\n"
+ "Example: mpv -ao portaudio:device=subdevice\n"
+ "\nOptions:\n"
+ " device=subdevice\n"
+ " Audio device PortAudio should use. Devices can be listed\n"
+ " with -ao portaudio:device=help\n"
+ " The subdevice can be passed as index, or as complete name.\n");
+}
+
+static bool check_pa_ret(int ret)
+{
+ if (ret < 0) {
+ mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] %s\n",
+ Pa_GetErrorText(ret));
+ if (ret == paUnanticipatedHostError) {
+ const PaHostErrorInfo* hosterr = Pa_GetLastHostErrorInfo();
+ mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] Host error: %s\n",
+ hosterr->errorText);
+ }
+ return false;
+ }
+ return true;
+}
+
+// Amount of bytes that contain audio of the given duration, aligned to frames.
+static int seconds_to_bytes(struct ao *ao, double duration_seconds)
+{
+ struct priv *priv = ao->priv;
+
+ int bytes = duration_seconds * ao->bps;
+ if (bytes % priv->framelen)
+ bytes += priv->framelen - (bytes % priv->framelen);
+ return bytes;
+}
+
+static int to_int(const char *s, int return_on_error)
+{
+ char *endptr;
+ int res = strtol(s, &endptr, 10);
+ return (s[0] && !endptr[0]) ? res : return_on_error;
+}
+
+static int find_device(struct ao *ao, const char *name)
+{
+ int help = strcmp(name, "help") == 0;
+ int count = Pa_GetDeviceCount();
+ check_pa_ret(count);
+ int found = paNoDevice;
+ int index = to_int(name, -1);
+ if (help)
+ mp_msg(MSGT_AO, MSGL_INFO, "PortAudio devices:\n");
+ for (int n = 0; n < count; n++) {
+ const PaDeviceInfo* info = Pa_GetDeviceInfo(n);
+ if (help) {
+ if (info->maxOutputChannels < 1)
+ continue;
+ mp_msg(MSGT_AO, MSGL_INFO, " %d '%s', %d channels, latency: %.2f "
+ "ms, sample rate: %.0f\n", n, info->name,
+ info->maxOutputChannels,
+ info->defaultHighOutputLatency * 1000,
+ info->defaultSampleRate);
+ }
+ if (strcmp(name, info->name) == 0 || n == index) {
+ found = n;
+ break;
+ }
+ }
+ if (found == paNoDevice && !help)
+ mp_msg(MSGT_AO, MSGL_FATAL, "[portaudio] Device '%s' not found!\n",
+ name);
+ return found;
+}
+
+static int ring_write(struct ao *ao, unsigned char *data, int len)
+{
+ struct priv *priv = ao->priv;
+
+ int free = priv->ring_size - priv->read_len;
+ int write_pos = (priv->read_pos + priv->read_len) % priv->ring_size;
+ int write_len = FFMIN(len, free);
+ int len1 = FFMIN(priv->ring_size - write_pos, write_len);
+ int len2 = write_len - len1;
+
+ memcpy(priv->ring + write_pos, data, len1);
+ memcpy(priv->ring, data + len1, len2);
+
+ priv->read_len += write_len;
+
+ return write_len;
+}
+
+static int ring_read(struct ao *ao, unsigned char *data, int len)
+{
+ struct priv *priv = ao->priv;
+
+ int read_len = FFMIN(len, priv->read_len);
+ int len1 = FFMIN(priv->ring_size - priv->read_pos, read_len);
+ int len2 = read_len - len1;
+
+ memcpy(data, priv->ring + priv->read_pos, len1);
+ memcpy(data + len1, priv->ring, len2);
+
+ priv->read_len -= read_len;
+ priv->read_pos = (priv->read_pos + read_len) % priv->ring_size;
+
+ return read_len;
+}
+
+static void fill_silence(unsigned char *ptr, int len)
+{
+ memset(ptr, 0, len);
+}
+
+static int stream_callback(const void *input,
+ void *output_v,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo *timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData)
+{
+ struct ao *ao = userData;
+ struct priv *priv = ao->priv;
+ int res = paContinue;
+ unsigned char *output = output_v;
+ int len_bytes = frameCount * priv->framelen;
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ // NOTE: PA + ALSA in dmix mode seems to pretend that there is no latency
+ // (outputBufferDacTime == currentTime)
+ priv->play_time = timeInfo->outputBufferDacTime
+ + len_bytes / (float)ao->bps;
+
+ if (priv->play_silence > 0) {
+ int bytes = FFMIN(priv->play_silence, len_bytes);
+ fill_silence(output, bytes);
+ priv->play_silence -= bytes;
+ len_bytes -= bytes;
+ output += bytes;
+ }
+ int read = ring_read(ao, output, len_bytes);
+ len_bytes -= read;
+ output += read;
+
+ if (len_bytes > 0) {
+ if (priv->play_remaining) {
+ res = paComplete;
+ priv->play_remaining = false;
+ } else {
+ mp_msg(MSGT_AO, MSGL_ERR, "[portaudio] Buffer underflow!\n");
+ }
+ fill_silence(output, len_bytes);
+ }
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+
+ return res;
+}
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *priv = ao->priv;
+
+ if (priv->stream) {
+ if (!cut_audio && Pa_IsStreamActive(priv->stream) == 1) {
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ priv->play_remaining = true;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+
+ check_pa_ret(Pa_StopStream(priv->stream));
+ }
+ check_pa_ret(Pa_CloseStream(priv->stream));
+ }
+
+ pthread_mutex_destroy(&priv->ring_mutex);
+ Pa_Terminate();
+}
+
+static int init(struct ao *ao, char *params)
+{
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+
+ if (!check_pa_ret(Pa_Initialize()))
+ return -1;
+
+ pthread_mutex_init(&priv->ring_mutex, NULL);
+
+ char *device = NULL;
+ const opt_t subopts[] = {
+ {"device", OPT_ARG_MSTRZ, &device, NULL},
+ {NULL}
+ };
+ if (subopt_parse(params, subopts) != 0) {
+ print_help();
+ goto error_exit;
+ }
+
+ int pa_device = Pa_GetDefaultOutputDevice();
+ if (device)
+ pa_device = find_device(ao, device);
+ if (pa_device == paNoDevice)
+ goto error_exit;
+
+ PaStreamParameters sp = {
+ .device = pa_device,
+ .channelCount = ao->channels,
+ .suggestedLatency
+ = Pa_GetDeviceInfo(pa_device)->defaultHighOutputLatency,
+ };
+
+ const struct format_map *fmt = format_maps;
+ while (fmt->pa_format) {
+ if (fmt->mp_format == ao->format) {
+ PaStreamParameters test = sp;
+ test.sampleFormat = fmt->pa_format;
+ if (Pa_IsFormatSupported(NULL, &test, ao->samplerate) == paNoError)
+ break;
+ }
+ fmt++;
+ }
+ if (!fmt->pa_format) {
+ mp_msg(MSGT_AO, MSGL_V,
+ "[portaudio] Unsupported format, using default.\n");
+ fmt = format_maps;
+ }
+
+ ao->format = fmt->mp_format;
+ sp.sampleFormat = fmt->pa_format;
+ priv->framelen = ao->channels * (af_fmt2bits(ao->format) / 8);
+ ao->bps = ao->samplerate * priv->framelen;
+
+ if (!check_pa_ret(Pa_IsFormatSupported(NULL, &sp, ao->samplerate)))
+ goto error_exit;
+ if (!check_pa_ret(Pa_OpenStream(&priv->stream, NULL, &sp, ao->samplerate,
+ paFramesPerBufferUnspecified, paNoFlag,
+ stream_callback, ao)))
+ goto error_exit;
+
+ priv->ring_size = seconds_to_bytes(ao, 0.5);
+ priv->ring = talloc_zero_size(priv, priv->ring_size);
+
+ free(device);
+ return 0;
+
+error_exit:
+ uninit(ao, true);
+ free(device);
+ return -1;
+}
+
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ int write_len = ring_write(ao, data, len);
+ if (flags & AOPLAY_FINAL_CHUNK)
+ priv->play_remaining = true;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+
+ if (Pa_IsStreamStopped(priv->stream) == 1)
+ check_pa_ret(Pa_StartStream(priv->stream));
+
+ return write_len;
+}
+
+static int get_space(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ int free = priv->ring_size - priv->read_len;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+
+ return free;
+}
+
+static float get_delay(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ double stream_time = Pa_GetStreamTime(priv->stream);
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ float frame_time = priv->play_time ? priv->play_time - stream_time : 0;
+ float buffer_latency = (priv->read_len + priv->play_silence)
+ / (float)ao->bps;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+
+ return buffer_latency + frame_time;
+}
+
+static void reset(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ if (Pa_IsStreamStopped(priv->stream) != 1)
+ check_pa_ret(Pa_AbortStream(priv->stream));
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ priv->read_len = 0;
+ priv->read_pos = 0;
+ priv->play_remaining = false;
+ priv->play_time = 0;
+ priv->play_silence = 0;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+}
+
+static void pause(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ check_pa_ret(Pa_AbortStream(priv->stream));
+
+ double stream_time = Pa_GetStreamTime(priv->stream);
+
+ pthread_mutex_lock(&priv->ring_mutex);
+
+ // When playback resumes, replace the lost audio (due to dropping the
+ // portaudio/driver/hardware internal buffers) with silence.
+ float frame_time = priv->play_time ? priv->play_time - stream_time : 0;
+ priv->play_silence += seconds_to_bytes(ao, FFMAX(frame_time, 0));
+ priv->play_time = 0;
+
+ pthread_mutex_unlock(&priv->ring_mutex);
+}
+
+static void resume(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+
+ check_pa_ret(Pa_StartStream(priv->stream));
+}
+
+const struct ao_driver audio_out_portaudio = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "PortAudio",
+ "portaudio",
+ "wm4",
+ "",
+ },
+ .init = init,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+};
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
new file mode 100644
index 0000000000..1d2ebc5281
--- /dev/null
+++ b/audio/out/ao_pulse.c
@@ -0,0 +1,554 @@
+/*
+ * PulseAudio audio output driver.
+ * Copyright (C) 2006 Lennart Poettering
+ * Copyright (C) 2007 Reimar Doeffinger
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "config.h"
+#include "libaf/format.h"
+#include "mp_msg.h"
+#include "audio_out.h"
+#include "input/input.h"
+
+#define PULSE_CLIENT_NAME "mpv"
+
+#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_UI_MAX)
+#define VOL_MP2PA(v) ((v) * PA_VOLUME_UI_MAX / 100)
+
+struct priv {
+ // PulseAudio playback stream object
+ struct pa_stream *stream;
+
+ // PulseAudio connection context
+ struct pa_context *context;
+
+ // Main event loop object
+ struct pa_threaded_mainloop *mainloop;
+
+ // temporary during control()
+ struct pa_sink_input_info pi;
+
+ bool broken_pause;
+ int retval;
+};
+
+#define GENERIC_ERR_MSG(ctx, str) \
+ mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] "str": %s\n", \
+ pa_strerror(pa_context_errno(ctx)))
+
+static void context_state_cb(pa_context *c, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+ break;
+ }
+}
+
+static void stream_state_cb(pa_stream *s, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+ break;
+ }
+}
+
+static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ mp_input_wakeup(ao->input_ctx);
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+}
+
+static void stream_latency_update_cb(pa_stream *s, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+}
+
+static void success_cb(pa_stream *s, int success, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ priv->retval = success;
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param op operation to wait for
+ * \return 1 if operation has finished normally (DONE state), 0 otherwise
+ */
+static int waitop(struct priv *priv, pa_operation *op)
+{
+ if (!op) {
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return 0;
+ }
+ pa_operation_state_t state = pa_operation_get_state(op);
+ while (state == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(priv->mainloop);
+ state = pa_operation_get_state(op);
+ }
+ pa_operation_unref(op);
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return state == PA_OPERATION_DONE;
+}
+
+static const struct format_map {
+ int mp_format;
+ pa_sample_format_t pa_format;
+} format_maps[] = {
+ {AF_FORMAT_S16_LE, PA_SAMPLE_S16LE},
+ {AF_FORMAT_S16_BE, PA_SAMPLE_S16BE},
+ {AF_FORMAT_S32_LE, PA_SAMPLE_S32LE},
+ {AF_FORMAT_S32_BE, PA_SAMPLE_S32BE},
+ {AF_FORMAT_FLOAT_LE, PA_SAMPLE_FLOAT32LE},
+ {AF_FORMAT_FLOAT_BE, PA_SAMPLE_FLOAT32BE},
+ {AF_FORMAT_U8, PA_SAMPLE_U8},
+ {AF_FORMAT_MU_LAW, PA_SAMPLE_ULAW},
+ {AF_FORMAT_A_LAW, PA_SAMPLE_ALAW},
+ {AF_FORMAT_UNKNOWN, 0}
+};
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *priv = ao->priv;
+ if (priv->stream && !cut_audio) {
+ pa_threaded_mainloop_lock(priv->mainloop);
+ waitop(priv, pa_stream_drain(priv->stream, success_cb, ao));
+ }
+
+ if (priv->mainloop)
+ pa_threaded_mainloop_stop(priv->mainloop);
+
+ if (priv->stream) {
+ pa_stream_disconnect(priv->stream);
+ pa_stream_unref(priv->stream);
+ priv->stream = NULL;
+ }
+
+ if (priv->context) {
+ pa_context_disconnect(priv->context);
+ pa_context_unref(priv->context);
+ priv->context = NULL;
+ }
+
+ if (priv->mainloop) {
+ pa_threaded_mainloop_free(priv->mainloop);
+ priv->mainloop = NULL;
+ }
+}
+
+static int init(struct ao *ao, char *params)
+{
+ struct pa_sample_spec ss;
+ struct pa_channel_map map;
+ char *devarg = NULL;
+ char *host = NULL;
+ char *sink = NULL;
+ const char *version = pa_get_library_version();
+
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+
+ ao->per_application_mixer = true;
+
+ if (params) {
+ devarg = strdup(params);
+ sink = strchr(devarg, ':');
+ if (sink)
+ *sink++ = 0;
+ if (devarg[0])
+ host = devarg;
+ }
+
+ priv->broken_pause = false;
+ /* not sure which versions are affected, assume 0.9.11* to 0.9.14*
+ * known bad: 0.9.14, 0.9.13
+ * known good: 0.9.9, 0.9.10, 0.9.15
+ * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer
+ * hangs somewhen. */
+ if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1'
+ && version[5] <= '4') {
+ mp_msg(MSGT_AO, MSGL_WARN,
+ "[pulse] working around probably broken pause functionality,\n"
+ " see http://www.pulseaudio.org/ticket/440\n");
+ priv->broken_pause = true;
+ }
+
+ ss.channels = ao->channels;
+ ss.rate = ao->samplerate;
+
+ const struct format_map *fmt_map = format_maps;
+ while (fmt_map->mp_format != ao->format) {
+ if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
+ mp_msg(MSGT_AO, MSGL_V,
+ "AO: [pulse] Unsupported format, using default\n");
+ fmt_map = format_maps;
+ break;
+ }
+ fmt_map++;
+ }
+ ao->format = fmt_map->mp_format;
+ ss.format = fmt_map->pa_format;
+
+ if (!pa_sample_spec_valid(&ss)) {
+ mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Invalid sample spec\n");
+ goto fail;
+ }
+
+ pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA);
+ ao->bps = pa_bytes_per_second(&ss);
+
+ if (!(priv->mainloop = pa_threaded_mainloop_new())) {
+ mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate main loop\n");
+ goto fail;
+ }
+
+ if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api(
+ priv->mainloop), PULSE_CLIENT_NAME))) {
+ mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate context\n");
+ goto fail;
+ }
+
+ pa_context_set_state_callback(priv->context, context_state_cb, ao);
+
+ if (pa_context_connect(priv->context, host, 0, NULL) < 0)
+ goto fail;
+
+ pa_threaded_mainloop_lock(priv->mainloop);
+
+ if (pa_threaded_mainloop_start(priv->mainloop) < 0)
+ goto unlock_and_fail;
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(priv->mainloop);
+
+ if (pa_context_get_state(priv->context) != PA_CONTEXT_READY)
+ goto unlock_and_fail;
+
+ if (!(priv->stream = pa_stream_new(priv->context, "audio stream", &ss,
+ &map)))
+ goto unlock_and_fail;
+
+ pa_stream_set_state_callback(priv->stream, stream_state_cb, ao);
+ pa_stream_set_write_callback(priv->stream, stream_request_cb, ao);
+ pa_stream_set_latency_update_callback(priv->stream,
+ stream_latency_update_cb, ao);
+ pa_buffer_attr bufattr = {
+ .maxlength = -1,
+ .tlength = pa_usec_to_bytes(1000000, &ss),
+ .prebuf = -1,
+ .minreq = -1,
+ .fragsize = -1,
+ };
+ if (pa_stream_connect_playback(priv->stream, sink, &bufattr,
+ PA_STREAM_NOT_MONOTONIC, NULL, NULL) < 0)
+ goto unlock_and_fail;
+
+ /* Wait until the stream is ready */
+ pa_threaded_mainloop_wait(priv->mainloop);
+
+ if (pa_stream_get_state(priv->stream) != PA_STREAM_READY)
+ goto unlock_and_fail;
+
+ pa_threaded_mainloop_unlock(priv->mainloop);
+
+ free(devarg);
+ return 0;
+
+unlock_and_fail:
+
+ if (priv->mainloop)
+ pa_threaded_mainloop_unlock(priv->mainloop);
+
+fail:
+ if (priv->context) {
+ if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED
+ && ao->probing))
+ GENERIC_ERR_MSG(priv->context, "Init failed");
+ }
+ free(devarg);
+ uninit(ao, true);
+ return -1;
+}
+
+static void cork(struct ao *ao, bool pause)
+{
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_lock(priv->mainloop);
+ priv->retval = 0;
+ if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) ||
+ !priv->retval)
+ GENERIC_ERR_MSG(priv->context, "pa_stream_cork() failed");
+}
+
+// Play the specified data to the pulseaudio server
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_lock(priv->mainloop);
+ if (pa_stream_write(priv->stream, data, len, NULL, 0,
+ PA_SEEK_RELATIVE) < 0) {
+ GENERIC_ERR_MSG(priv->context, "pa_stream_write() failed");
+ len = -1;
+ }
+ if (flags & AOPLAY_FINAL_CHUNK) {
+ // Force start in case the stream was too short for prebuf
+ pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL);
+ pa_operation_unref(op);
+ }
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return len;
+}
+
+// Reset the audio stream, i.e. flush the playback buffer on the server side
+static void reset(struct ao *ao)
+{
+ // pa_stream_flush() works badly if not corked
+ cork(ao, true);
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_lock(priv->mainloop);
+ priv->retval = 0;
+ if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) ||
+ !priv->retval)
+ GENERIC_ERR_MSG(priv->context, "pa_stream_flush() failed");
+ cork(ao, false);
+}
+
+// Pause the audio stream by corking it on the server
+static void pause(struct ao *ao)
+{
+ cork(ao, true);
+}
+
+// Resume the audio stream by uncorking it on the server
+static void resume(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ /* Without this, certain versions will cause an infinite hang because
+ * pa_stream_writable_size returns 0 always.
+ * Note that this workaround causes A-V desync after pause. */
+ if (priv->broken_pause)
+ reset(ao);
+ cork(ao, false);
+}
+
+// Return number of bytes that may be written to the server without blocking
+static int get_space(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_lock(priv->mainloop);
+ size_t space = pa_stream_writable_size(priv->stream);
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return space;
+}
+
+// Return the current latency in seconds
+static float get_delay(struct ao *ao)
+{
+ /* This code basically does what pa_stream_get_latency() _should_
+ * do, but doesn't due to multiple known bugs in PulseAudio (at
+ * PulseAudio version 2.1). In particular, the timing interpolation
+ * mode (PA_STREAM_INTERPOLATE_TIMING) can return completely bogus
+ * values, and the non-interpolating code has a bug causing too
+ * large results at end of stream (so a stream never seems to finish).
+ * This code can still return wrong values in some cases due to known
+ * PulseAudio bugs that can not be worked around on the client side.
+ *
+ * We always query the server for latest timing info. This may take
+ * too long to work well with remote audio servers, but at least
+ * this should be enough to fix the normal local playback case.
+ */
+ struct priv *priv = ao->priv;
+ pa_threaded_mainloop_lock(priv->mainloop);
+ if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) {
+ GENERIC_ERR_MSG(priv->context, "pa_stream_update_timing_info() failed");
+ return 0;
+ }
+ pa_threaded_mainloop_lock(priv->mainloop);
+ const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream);
+ if (!ti) {
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ GENERIC_ERR_MSG(priv->context, "pa_stream_get_timing_info() failed");
+ return 0;
+ }
+ const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream);
+ if (!ss) {
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ GENERIC_ERR_MSG(priv->context, "pa_stream_get_sample_spec() failed");
+ return 0;
+ }
+ // data left in PulseAudio's main buffers (not written to sink yet)
+ int64_t latency = pa_bytes_to_usec(ti->write_index - ti->read_index, ss);
+ // since this info may be from a while ago, playback has progressed since
+ latency -= ti->transport_usec;
+ // data already moved from buffers to sink, but not played yet
+ int64_t sink_latency = ti->sink_usec;
+ if (!ti->playing)
+ /* At the end of a stream, part of the data "left" in the sink may
+ * be padding silence after the end; that should be subtracted to
+ * get the amount of real audio from our stream. This adjustment
+ * is missing from Pulseaudio's own get_latency calculations
+ * (as of PulseAudio 2.1). */
+ sink_latency -= pa_bytes_to_usec(ti->since_underrun, ss);
+ if (sink_latency > 0)
+ latency += sink_latency;
+ if (latency < 0)
+ latency = 0;
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return latency / 1e6;
+}
+
+/* A callback function that is called when the
+ * pa_context_get_sink_input_info() operation completes. Saves the
+ * volume field of the specified structure to the global variable volume.
+ */
+static void info_func(struct pa_context *c, const struct pa_sink_input_info *i,
+ int is_last, void *userdata)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+ if (is_last < 0) {
+ GENERIC_ERR_MSG(priv->context, "Failed to get sink input info");
+ return;
+ }
+ if (!i)
+ return;
+ priv->pi = *i;
+ pa_threaded_mainloop_signal(priv->mainloop, 0);
+}
+
+static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ struct priv *priv = ao->priv;
+ switch (cmd) {
+ case AOCONTROL_GET_MUTE:
+ case AOCONTROL_GET_VOLUME: {
+ uint32_t devidx = pa_stream_get_index(priv->stream);
+ pa_threaded_mainloop_lock(priv->mainloop);
+ if (!waitop(priv, pa_context_get_sink_input_info(priv->context, devidx,
+ info_func, ao))) {
+ GENERIC_ERR_MSG(priv->context,
+ "pa_stream_get_sink_input_info() failed");
+ return CONTROL_ERROR;
+ }
+ // Warning: some information in pi might be unaccessible, because
+ // we naively copied the struct, without updating pointers etc.
+ // Pointers might point to invalid data, accessors might fail.
+ if (cmd == AOCONTROL_GET_VOLUME) {
+ ao_control_vol_t *vol = arg;
+ if (priv->pi.volume.channels != 2)
+ vol->left = vol->right =
+ VOL_PA2MP(pa_cvolume_avg(&priv->pi.volume));
+ else {
+ vol->left = VOL_PA2MP(priv->pi.volume.values[0]);
+ vol->right = VOL_PA2MP(priv->pi.volume.values[1]);
+ }
+ } else if (cmd == AOCONTROL_GET_MUTE) {
+ bool *mute = arg;
+ *mute = priv->pi.mute;
+ }
+ return CONTROL_OK;
+ }
+
+ case AOCONTROL_SET_MUTE:
+ case AOCONTROL_SET_VOLUME: {
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(priv->mainloop);
+ uint32_t stream_index = pa_stream_get_index(priv->stream);
+ if (cmd == AOCONTROL_SET_VOLUME) {
+ const ao_control_vol_t *vol = arg;
+ struct pa_cvolume volume;
+
+ pa_cvolume_reset(&volume, ao->channels);
+ if (volume.channels != 2)
+ pa_cvolume_set(&volume, volume.channels, VOL_MP2PA(vol->left));
+ else {
+ volume.values[0] = VOL_MP2PA(vol->left);
+ volume.values[1] = VOL_MP2PA(vol->right);
+ }
+ o = pa_context_set_sink_input_volume(priv->context, stream_index,
+ &volume, NULL, NULL);
+ if (!o) {
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ GENERIC_ERR_MSG(priv->context,
+ "pa_context_set_sink_input_volume() failed");
+ return CONTROL_ERROR;
+ }
+ } else if (cmd == AOCONTROL_SET_MUTE) {
+ const bool *mute = arg;
+ o = pa_context_set_sink_input_mute(priv->context, stream_index,
+ *mute, NULL, NULL);
+ if (!o) {
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ GENERIC_ERR_MSG(priv->context,
+ "pa_context_set_sink_input_mute() failed");
+ return CONTROL_ERROR;
+ }
+ } else
+ abort();
+ /* We don't wait for completion here */
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(priv->mainloop);
+ return CONTROL_OK;
+ }
+ default:
+ return CONTROL_UNKNOWN;
+ }
+}
+
+const struct ao_driver audio_out_pulse = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "PulseAudio audio output",
+ "pulse",
+ "Lennart Poettering",
+ "",
+ },
+ .control = control,
+ .init = init,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+};
diff --git a/audio/out/ao_rsound.c b/audio/out/ao_rsound.c
new file mode 100644
index 0000000000..8232aad865
--- /dev/null
+++ b/audio/out/ao_rsound.c
@@ -0,0 +1,214 @@
+/*
+ * RSound audio output driver
+ *
+ * Copyright (C) 2011 Hans-Kristian Arntzen
+ *
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <rsound.h>
+
+#include "talloc.h"
+
+#include "subopt-helper.h"
+#include "osdep/timer.h"
+#include "libaf/format.h"
+#include "audio_out.h"
+
+struct priv {
+ rsound_t *rd;
+};
+
+static int set_format(struct ao *ao)
+{
+ int rsd_format;
+
+ switch (ao->format) {
+ case AF_FORMAT_U8:
+ rsd_format = RSD_U8;
+ break;
+ case AF_FORMAT_S8:
+ rsd_format = RSD_S8;
+ break;
+ case AF_FORMAT_S16_LE:
+ rsd_format = RSD_S16_LE;
+ break;
+ case AF_FORMAT_S16_BE:
+ rsd_format = RSD_S16_BE;
+ break;
+ case AF_FORMAT_U16_LE:
+ rsd_format = RSD_U16_LE;
+ break;
+ case AF_FORMAT_U16_BE:
+ rsd_format = RSD_U16_BE;
+ break;
+ case AF_FORMAT_S24_LE:
+ case AF_FORMAT_S24_BE:
+ case AF_FORMAT_U24_LE:
+ case AF_FORMAT_U24_BE:
+ rsd_format = RSD_S32_LE;
+ ao->format = AF_FORMAT_S32_LE;
+ break;
+ case AF_FORMAT_S32_LE:
+ rsd_format = RSD_S32_LE;
+ break;
+ case AF_FORMAT_S32_BE:
+ rsd_format = RSD_S32_BE;
+ break;
+ case AF_FORMAT_U32_LE:
+ rsd_format = RSD_U32_LE;
+ break;
+ case AF_FORMAT_U32_BE:
+ rsd_format = RSD_U32_BE;
+ break;
+ case AF_FORMAT_A_LAW:
+ rsd_format = RSD_ALAW;
+ break;
+ case AF_FORMAT_MU_LAW:
+ rsd_format = RSD_MULAW;
+ break;
+ default:
+ rsd_format = RSD_S16_LE;
+ ao->format = AF_FORMAT_S16_LE;
+ }
+
+ return rsd_format;
+}
+
+static int init(struct ao *ao, char *params)
+{
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+
+ char *host = NULL;
+ char *port = NULL;
+
+ const opt_t subopts[] = {
+ {"host", OPT_ARG_MSTRZ, &host, NULL},
+ {"port", OPT_ARG_MSTRZ, &port, NULL},
+ {NULL}
+ };
+
+ if (subopt_parse(params, subopts) != 0)
+ return -1;
+
+ if (rsd_init(&priv->rd) < 0) {
+ free(host);
+ free(port);
+ return -1;
+ }
+
+ if (host) {
+ rsd_set_param(priv->rd, RSD_HOST, host);
+ free(host);
+ }
+
+ if (port) {
+ rsd_set_param(priv->rd, RSD_PORT, port);
+ free(port);
+ }
+
+ rsd_set_param(priv->rd, RSD_SAMPLERATE, &ao->samplerate);
+ rsd_set_param(priv->rd, RSD_CHANNELS, &ao->channels);
+
+ int rsd_format = set_format(ao);
+ rsd_set_param(priv->rd, RSD_FORMAT, &rsd_format);
+
+ if (rsd_start(priv->rd) < 0) {
+ rsd_free(priv->rd);
+ return -1;
+ }
+
+ ao->bps = ao->channels * ao->samplerate * af_fmt2bits(ao->format) / 8;
+
+ return 0;
+}
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *priv = ao->priv;
+ /* The API does not provide a direct way to explicitly wait until
+ * the last byte has been played server-side as this cannot be
+ * guaranteed by backend drivers, so we approximate this behavior.
+ */
+ if (!cut_audio)
+ usec_sleep(rsd_delay_ms(priv->rd) * 1000);
+
+ rsd_stop(priv->rd);
+ rsd_free(priv->rd);
+}
+
+static void reset(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ rsd_stop(priv->rd);
+ rsd_start(priv->rd);
+}
+
+static void audio_pause(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ rsd_pause(priv->rd, 1);
+}
+
+static void audio_resume(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ rsd_pause(priv->rd, 0);
+}
+
+static int get_space(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ return rsd_get_avail(priv->rd);
+}
+
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+ return rsd_write(priv->rd, data, len);
+}
+
+static float get_delay(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ return rsd_delay_ms(priv->rd) / 1000.0;
+}
+
+const struct ao_driver audio_out_rsound = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ .name = "RSound output driver",
+ .short_name = "rsound",
+ .author = "Hans-Kristian Arntzen",
+ .comment = "",
+ },
+ .init = init,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = audio_pause,
+ .resume = audio_resume,
+};
+
diff --git a/audio/out/audio_out_internal.h b/audio/out/audio_out_internal.h
new file mode 100644
index 0000000000..215428fb0e
--- /dev/null
+++ b/audio/out/audio_out_internal.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_AUDIO_OUT_INTERNAL_H
+#define MPLAYER_AUDIO_OUT_INTERNAL_H
+
+#include "options.h"
+
+// prototypes:
+//static ao_info_t info;
+static int control(int cmd, void *arg);
+static int init(int rate,int channels,int format,int flags);
+static void uninit(int immed);
+static void reset(void);
+static int get_space(void);
+static int play(void* data,int len,int flags);
+static float get_delay(void);
+static void audio_pause(void);
+static void audio_resume(void);
+
+extern struct ao *global_ao;
+#define ao_data (*global_ao)
+#define mixer_channel (global_ao->opts->mixer_channel)
+#define mixer_device (global_ao->opts->mixer_device)
+
+#define LIBAO_EXTERN(x) const struct ao_driver audio_out_##x = { \
+ .info = &info, \
+ .control = old_ao_control, \
+ .init = old_ao_init, \
+ .uninit = old_ao_uninit, \
+ .reset = old_ao_reset, \
+ .get_space = old_ao_get_space, \
+ .play = old_ao_play, \
+ .get_delay = old_ao_get_delay, \
+ .pause = old_ao_pause, \
+ .resume = old_ao_resume, \
+ .old_functions = &(const struct ao_old_functions) { \
+ .control = control, \
+ .init = init, \
+ .uninit = uninit, \
+ .reset = reset, \
+ .get_space = get_space, \
+ .play = play, \
+ .get_delay = get_delay, \
+ .pause = audio_pause, \
+ .resume = audio_resume, \
+ }, \
+};
+
+#endif /* MPLAYER_AUDIO_OUT_INTERNAL_H */