diff options
Diffstat (limited to 'audio/out')
-rw-r--r-- | audio/out/ao.c | 294 | ||||
-rw-r--r-- | audio/out/ao.h | 140 | ||||
-rw-r--r-- | audio/out/ao_alsa.c | 868 | ||||
-rw-r--r-- | audio/out/ao_coreaudio.c | 1283 | ||||
-rw-r--r-- | audio/out/ao_dsound.c | 648 | ||||
-rw-r--r-- | audio/out/ao_jack.c | 361 | ||||
-rw-r--r-- | audio/out/ao_lavc.c | 621 | ||||
-rw-r--r-- | audio/out/ao_null.c | 129 | ||||
-rw-r--r-- | audio/out/ao_openal.c | 280 | ||||
-rw-r--r-- | audio/out/ao_oss.c | 560 | ||||
-rw-r--r-- | audio/out/ao_pcm.c | 256 | ||||
-rw-r--r-- | audio/out/ao_portaudio.c | 431 | ||||
-rw-r--r-- | audio/out/ao_pulse.c | 554 | ||||
-rw-r--r-- | audio/out/ao_rsound.c | 214 | ||||
-rw-r--r-- | audio/out/audio_out_internal.h | 65 |
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 */ |