aboutsummaryrefslogtreecommitdiffhomepage
path: root/libao2
diff options
context:
space:
mode:
authorGravatar wm4 <wm4@mplayer2.org>2012-04-27 11:26:04 +0200
committerGravatar Uoti Urpala <uau@mplayer2.org>2012-05-06 17:57:44 +0300
commitcd21ce3779d40e36ac2b49811679e30cc07ed357 (patch)
tree99a9e52c82ffbe0712d713050edd4668123b65b7 /libao2
parentbb908027178fe8bfd7d6e3fc255dea8c5051cd4a (diff)
ao_portaudio: add new PortAudio audio output driver
This AO has potential to be useful on platforms other than Linux. On Windows in particular, PortAudio can make use of newer/better audio APIs like WASAPI, instead of DirectSound. As an implementation choice, the PortAudio callback API was used. The blocking API might be a better match for mplayer's requirements, but caused severe problems on Linux/ALSA (possibly PortAudio bugs).
Diffstat (limited to 'libao2')
-rw-r--r--libao2/ao_portaudio.c431
-rw-r--r--libao2/audio_out.c4
2 files changed, 435 insertions, 0 deletions
diff --git a/libao2/ao_portaudio.c b/libao2/ao_portaudio.c
new file mode 100644
index 0000000000..c8275f0b38
--- /dev/null
+++ b/libao2/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/avutil.h>
+#include <portaudio.h>
+
+#include "config.h"
+#include "subopt-helper.h"
+#include "libaf/af_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: mplayer -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/libao2/audio_out.c b/libao2/audio_out.c
index 268c17d749..d9a81b93b6 100644
--- a/libao2/audio_out.c
+++ b/libao2/audio_out.c
@@ -55,6 +55,7 @@ extern const struct ao_driver audio_out_v4l2;
extern const struct ao_driver audio_out_mpegpes;
extern const struct ao_driver audio_out_pcm;
extern const struct ao_driver audio_out_pss;
+extern const struct ao_driver audio_out_portaudio;
static const struct ao_driver * const audio_out_drivers[] = {
// native:
@@ -82,6 +83,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#ifdef CONFIG_OSS_AUDIO
&audio_out_oss,
#endif
+#ifdef CONFIG_PORTAUDIO
+ &audio_out_portaudio,
+#endif
#ifdef CONFIG_SGI_AUDIO
&audio_out_sgi,
#endif