/* * Copyright (C) 2004-2005 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA * * * xine audio output plugin using DirectX * * Implementation: * - this version contains service thread which starts and stops playback * according to the data availability * - it uses the ring buffer offered by DirectSound API * - formula for volume level is deduced according to authors ears :-) * * Hacker notes: * - always lock the mutex before calling audio_* functions * * Authors: * - Frantisek Dvorak * * Inspiration: * - mplayer for workarounding -lguid idea * - DirectX 7 documentation * * License: * - dual GPL/LGPL (LGPL for non xine-specific part) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #define LOG_MODULE "audio_directx2_out" #define LOG_VERBOSE /* #define LOG */ #include "xine_internal.h" #include "audio_out.h" #define AO_OUT_DIRECTX2_IFACE_VERSION 8 /* * buffer size in miliseconds * (one second takes 11-192 KB) */ #define BUFFER_MS 1000 /* * number of parts in the buffer, * one is always locked for playing */ #define PARTS 3 /* * base power factor for volume remapping */ #define FACTOR 60.0 /* experiments */ /*#define EXACT_WAIT*/ /*#define EXACT_SLEEP*/ /*#define PANIC_OVERRUN*/ #define STATUS_START 0 #define STATUS_WAIT 1 #define STATUS_RUNNING 2 #define PRIdword "lu" #define PRIsizet "u" typedef struct { audio_driver_class_t driver_class; xine_t *xine; } dx2_class_t; typedef struct { ao_driver_t ao_driver; dx2_class_t *class; LPDIRECTSOUND ds; /* DirectSound device */ LPDIRECTSOUNDBUFFER dsbuffer; /* DirectSound buffer */ DSBPOSITIONNOTIFY events[PARTS]; /* position events */ LPDIRECTSOUNDNOTIFY notify; /* notify interface */ size_t buffer_size; /* size of the buffer */ size_t part_size; /* second half of buffer */ size_t read_size; /* size of prepared data */ uint32_t bits; uint32_t rate; uint32_t frame_size; uint32_t capabilities; int channels; int volume; int muted; int status; /* current status of the driver */ int paused; /* paused mode */ int finished; /* driver finished */ int failed; /* don't open modal dialog again */ int count; /* current free part number */ pthread_t buffer_service; /* service thread for operating with DSB */ pthread_cond_t data_cond; /* signals on data */ pthread_mutex_t data_mutex; /* data lock */ } dx2_driver_t; /***************************************************************************** * DirectDraw GUIDs. * Defining them here allows us to get rid of the dxguid library during * the linking stage. *****************************************************************************/ static const GUID IID_IDirectSoundNotify = { 0xB0210783, 0x89CD, 0x11D0, {0xAF, 0x08, 0x00, 0xA0, 0xC9, 0x25, 0xCD, 0x16} }; static int buffer_ready(dx2_driver_t *this); /* popup a dialog with error */ static void XINE_FORMAT_PRINTF(1, 2) error_message(const char *fmt, ...) { char message[256]; va_list ap; va_start(ap, fmt); vsnprintf(message, sizeof(message), fmt, ap); va_end(ap); MessageBox(0, message, _("Error"), MB_ICONERROR | MB_OK | MB_APPLMODAL); } /* description of given error */ static char *dsound_strerror(HRESULT err) { switch(err) { case DS_OK: return _("success"); #ifdef DSERR_ACCESSDENIED case DSERR_ACCESSDENIED: return _("access denied"); #endif case DSERR_ALLOCATED: return _("resource is already in use"); case DSERR_ALREADYINITIALIZED: return _("object was already initialized"); case DSERR_BADFORMAT: return _("specified wave format is not supported"); case DSERR_BUFFERLOST: return _("memory buffer has been lost and must be restored"); case DSERR_CONTROLUNAVAIL: return _("requested buffer control is not available"); case DSERR_GENERIC: return _("undetermined error inside DirectSound subsystem"); #ifdef DSERR_HWUNAVAIL case DSERR_HWUNAVAIL: return _("DirectSound hardware device is unavailable"); #endif case DSERR_INVALIDCALL: return _("function is not valid for the current state of the object"); case DSERR_INVALIDPARAM: return _("invalid parameter was passed"); case DSERR_NOAGGREGATION: return _("object doesn't support aggregation"); case DSERR_NODRIVER: return _("no sound driver available for use"); case DSERR_NOINTERFACE: return _("requested COM interface not available"); case DSERR_OTHERAPPHASPRIO: return _("another application has a higher priority level"); case DSERR_OUTOFMEMORY: return _("insufficient memory"); case DSERR_PRIOLEVELNEEDED: return _("low priority level for this function"); case DSERR_UNINITIALIZED: return _("DirectSound wasn't initialized"); case DSERR_UNSUPPORTED: return _("function is not supported"); default: return _("unknown error"); } } /* create direct sound object */ static LPDIRECTSOUND dsound_create() { LPDIRECTSOUND ds; if (DirectSoundCreate(NULL, &ds, NULL) != DS_OK) { error_message(_("Unable to create direct sound object.")); return NULL; } if (IDirectSound_SetCooperativeLevel(ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK) { IDirectSound_Release(ds); error_message(_("Could not set direct sound cooperative level.")); return NULL; } return ds; } /* destroy direct sound object */ static void dsound_destroy(LPDIRECTSOUND ds) { IDirectSound_Release(ds); } /* fill out wave format header */ static void dsound_fill_wfx(WAVEFORMATEX *wfx, uint32_t bits, uint32_t rate, int channels, size_t frame_size) { memset(wfx, 0, sizeof(wfx)); wfx->wFormatTag = WAVE_FORMAT_PCM; wfx->nChannels = channels; wfx->nSamplesPerSec = rate; wfx->wBitsPerSample = (WORD)bits; wfx->nBlockAlign = frame_size; wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; } /* fill out buffer description structure */ static void dsound_fill_desc(DSBUFFERDESC *desc, DWORD flags, DWORD buffer_size, WAVEFORMATEX *wfx) { memset(desc, 0, sizeof(DSBUFFERDESC)); desc->dwSize = sizeof(DSBUFFERDESC); desc->dwFlags = flags; desc->dwBufferBytes = buffer_size; desc->lpwfxFormat = wfx; } /* send exit signal to the audio thread */ static void audio_thread_exit(dx2_driver_t *this) { this->finished = 1; pthread_cond_signal(&this->data_cond); } /* check for error, log it and pop up dialog */ static void audio_error(dx2_driver_t *this, HRESULT err, char *msg) { xine_log(this->class->xine, XINE_LOG_MSG, LOG_MODULE ": %s: %s\n", msg, dsound_strerror(err)); if (!this->failed) { error_message("%s: %s", msg, dsound_strerror(err)); this->failed = 1; } if (this->status != STATUS_START) audio_thread_exit(this); } /* create direct sound buffer */ static int audio_create_buffers(dx2_driver_t *this) { DSBUFFERDESC desc; WAVEFORMATEX wfx; DWORD flags; HRESULT err; size_t buffer_size; buffer_size = this->rate * this->frame_size * BUFFER_MS / 1000; if (buffer_size > DSBSIZE_MAX) buffer_size = DSBSIZE_MAX; if (buffer_size < DSBSIZE_MIN) buffer_size = DSBSIZE_MIN; this->part_size = (buffer_size / PARTS / this->frame_size) * this->frame_size; if (!this->part_size) this->part_size = this->frame_size; this->buffer_size = this->part_size * PARTS; flags = DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY; dsound_fill_wfx(&wfx, this->bits, this->rate, this->channels, this->frame_size); dsound_fill_desc(&desc, flags, this->buffer_size, &wfx); if ((err = IDirectSound_CreateSoundBuffer(this->ds, &desc, &this->dsbuffer, NULL)) != DS_OK) { audio_error(this, err, _("Unable to create secondary direct sound buffer")); return 0; } lprintf("created direct sound buffer, size = %u, part = %u\n", this->buffer_size, this->part_size); return 1; } /* destroy the sound buffer */ static void audio_destroy_buffers(dx2_driver_t *this) { IDirectSoundBuffer_Release(this->dsbuffer); } /* create position events */ static int audio_create_events(dx2_driver_t *this) { HANDLE handle[PARTS]; HRESULT err; int i; for (i = 0; i < PARTS; i++) { handle[i] = CreateEvent(NULL, FALSE, FALSE, NULL); if (!handle[i]) { error_message(_("Unable to create buffer position events.")); return 0; } this->events[i].dwOffset = i * this->part_size; this->events[i].hEventNotify = handle[i]; } if ((err = IDirectSoundBuffer_QueryInterface(this->dsbuffer, &IID_IDirectSoundNotify, (void **)&this->notify)) != DS_OK) { audio_error(this, err, _("Unable to get notification interface")); return 0; } if ((err = IDirectSoundNotify_SetNotificationPositions(this->notify, PARTS, this->events)) != DS_OK) { audio_error(this, err, _("Unable to set notification positions")); IDirectSoundNotify_Release(this->notify); return 0; } return 1; } /* destroy notification interface */ static void audio_destroy_events(dx2_driver_t *this) { IDirectSoundNotify_Release(this->notify); } /* start playback */ static int audio_play(dx2_driver_t *this) { HRESULT err; if ((err = IDirectSoundBuffer_Play(this->dsbuffer, 0, 0, DSBPLAY_LOOPING)) != DS_OK) { audio_error(this, err, _("Couldn't play sound buffer")); return 0; } return 1; } /* stop playback */ static int audio_stop(dx2_driver_t *this) { HRESULT err; if ((err = IDirectSoundBuffer_Stop(this->dsbuffer)) != DS_OK) { audio_error(this, err, _("Couldn't stop sound buffer")); return 0; } return 1; } /* get current playback position in the ring buffer */ static int audio_tell(dx2_driver_t *this, size_t *pos) { DWORD err; DWORD play_pos; if ((err = IDirectSoundBuffer_GetCurrentPosition(this->dsbuffer, &play_pos, NULL)) != DS_OK) { audio_error(this, err, _("Can't get buffer position")); return 0; } *pos = play_pos; return 1; } /* set playback position in the ring buffer */ static int audio_seek(dx2_driver_t *this, size_t pos) { DWORD err; if ((err = IDirectSoundBuffer_SetCurrentPosition(this->dsbuffer, pos)) != DS_OK) { audio_error(this, err, _("Can't set buffer position")); return 0; } return 1; } /* flush audio buffers */ static int audio_flush(dx2_driver_t *this) { this->status = STATUS_WAIT; this->count = 0; this->read_size = 0; return audio_seek(this, 0); } /* * set the volume * * DirecSound can only lower the volume by software way. * Unit is dB, value is always negative or zero. */ static int audio_set_volume(dx2_driver_t *this, int volume) { HRESULT err; LONG value; value = DSBVOLUME_MIN * (pow(FACTOR, 1 - volume / 100.0) - 1) / (FACTOR - 1); if (value < DSBVOLUME_MIN) value = DSBVOLUME_MIN; else if (value > DSBVOLUME_MAX) value = DSBVOLUME_MAX; lprintf("Setting sound to %d%% (%ld dB)\n", volume, value); if ((err = IDirectSoundBuffer_SetVolume(this->dsbuffer, value) != DS_OK)) { audio_error(this, err, _("Can't set sound volume")); return 0; } return 1; } /* add given data into the ring buffer */ static int audio_fill(dx2_driver_t *this, char *data, size_t size) { DWORD size1, size2; void *ptr1, *ptr2; HRESULT err; /* lock a part of the buffer, begin position on free space */ err = IDirectSoundBuffer_Lock(this->dsbuffer, (this->count * this->part_size + this->read_size) % this->buffer_size, size, &ptr1, &size1, &ptr2, &size2, 0); /* try to restore the buffer, if necessary */ if (err == DSERR_BUFFERLOST) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": buffer lost, tryig to restore\n")); IDirectSoundBuffer_Restore(this->dsbuffer); err = IDirectSoundBuffer_Lock(this->dsbuffer, (this->count * this->part_size + this->read_size) % this->buffer_size, size, &ptr1, &size1, &ptr2, &size2, 0); } if (err != DS_OK) { audio_error(this, err, _("Couldn't lock direct sound buffer")); return 0; } _x_assert(size == size1 + size2); if (ptr1 && size1) xine_fast_memcpy(ptr1, data, size1); if (ptr2 && size2) xine_fast_memcpy(ptr2, data + size1, size2); this->read_size += size; if ((err = IDirectSoundBuffer_Unlock(this->dsbuffer, ptr1, size1, ptr2, size2)) != DS_OK) { audio_error(this, err, _("Couldn't unlock direct sound buffer")); return 0; } /* signal, if are waiting and need wake up */ if ((this->status == STATUS_WAIT) && buffer_ready(this)) { lprintf("buffer ready, waking up\n"); pthread_cond_signal(&this->data_cond); } return 1; } /* transform given mode the number of channels */ static int mode2channels(uint32_t mode) { int channels; switch(mode) { case AO_CAP_MODE_MONO: channels = 1; break; case AO_CAP_MODE_STEREO: channels = 2; break; case AO_CAP_MODE_4CHANNEL: channels = 4; break; case AO_CAP_MODE_5CHANNEL: channels = 5; break; case AO_CAP_MODE_5_1CHANNEL: channels = 6; break; default: return 0; } return channels; } /* test the capability on given buffer */ static int test_capability(LPDIRECTSOUNDBUFFER buffer, uint32_t bits, uint32_t rate, int mode) { WAVEFORMATEX wfx; int channels; channels = mode2channels(mode); if (!channels) return 0; dsound_fill_wfx(&wfx, bits, rate, channels, (bits >> 3) * channels); if (IDirectSoundBuffer_SetFormat(buffer, &wfx) != DS_OK) { lprintf("mode %d, bits %" PRIu32 " not supported\n", mode, bits); return 0; } lprintf("mode %d, bits %" PRIu32 " supported\n", mode, bits); return 1; } /* * test capabilities of driver before opening * * Passed only 8 bit and 16 bit with mono or stereo. */ static int test_capabilities(dx2_driver_t *this) { struct { uint32_t bits; uint32_t rate; uint32_t mode; uint32_t caps; } tests[] = { {8, 44100, AO_CAP_MODE_MONO, AO_CAP_8BITS | AO_CAP_MODE_MONO}, {8, 44100, AO_CAP_MODE_STEREO, AO_CAP_8BITS | AO_CAP_MODE_STEREO}, {16, 44100, AO_CAP_MODE_MONO, AO_CAP_16BITS | AO_CAP_MODE_MONO}, {16, 44100, AO_CAP_MODE_STEREO, AO_CAP_16BITS | AO_CAP_MODE_STEREO}, {16, 44100, AO_CAP_MODE_4CHANNEL, AO_CAP_16BITS | AO_CAP_MODE_4CHANNEL}, {16, 44100, AO_CAP_MODE_5CHANNEL, AO_CAP_16BITS | AO_CAP_MODE_5CHANNEL}, {16, 44100, AO_CAP_MODE_5_1CHANNEL, AO_CAP_16BITS | AO_CAP_MODE_5_1CHANNEL}, {24, 44100, AO_CAP_MODE_STEREO, AO_CAP_24BITS | AO_CAP_MODE_STEREO}, {32, 44100, AO_CAP_MODE_STEREO, AO_CAP_FLOAT32 | AO_CAP_MODE_STEREO}, {0, 0, 0, 0}, }; LPDIRECTSOUNDBUFFER buffer; DSBUFFERDESC desc; int i; /* create temporary primary sound buffer */ dsound_fill_desc(&desc, DSBCAPS_PRIMARYBUFFER, 0, NULL); if (IDirectSound_CreateSoundBuffer(this->ds, &desc, &buffer, NULL) != DS_OK) { error_message(_("Unable to create primary direct sound buffer.")); return 0; } /* test capabilities */ this->capabilities = 0; i = 0; while (tests[i].bits) { if (test_capability(buffer, tests[i].bits, tests[i].rate, tests[i].mode)) this->capabilities |= tests[i].caps; i++; } lprintf("result capabilities: 0x08%" PRIX32 "\n", this->capabilities); IDirectSoundBuffer_Release(buffer); return 1; } /* size of free space in the ring buffer */ static size_t buffer_free_size(dx2_driver_t *this) { size_t used_full_size; used_full_size = this->read_size + ((this->status != STATUS_WAIT) ? this->part_size : 0); _x_assert(used_full_size <= this->buffer_size); return this->buffer_size - used_full_size; } /* enough data in the ring buffer for playing next part? */ static int buffer_ready(dx2_driver_t *this) { return this->read_size >= this->part_size; } /* service thread working with direct sound buffer */ static void *buffer_service(void *data) { dx2_driver_t *this = (dx2_driver_t *)data; HANDLE handles[PARTS]; DWORD ret; int i; #if defined(EXACT_SLEEP) || defined(EXACT_WAIT) size_t play_pos, req_delay; #endif /* prepare empty buffer */ audio_flush(this); for (i = 0; i < PARTS; i++) handles[i] = this->events[i].hEventNotify; /* we live! */ pthread_mutex_lock(&this->data_mutex); pthread_cond_signal(&this->data_cond); pthread_mutex_unlock(&this->data_mutex); while (!this->finished) { pthread_mutex_lock(&this->data_mutex); if (!buffer_ready(this)) { if (!audio_stop(this)) goto fail; lprintf("no data (count=%d,free=%" PRIsizet ",avail=%" PRIsizet "), sleeping...\n", this->count, buffer_free_size(this), this->read_size); this->status = STATUS_WAIT; pthread_cond_wait(&this->data_cond, &this->data_mutex); lprintf("wake up (count=%d,free=%" PRIsizet "--,avail=%" PRIsizet ")\n", this->count, buffer_free_size(this), this->read_size); if (this->finished) goto finished; if (!audio_seek(this, this->count * this->part_size)) goto fail; if (!this->paused) { if (!audio_play(this)) goto fail; } this->status = STATUS_RUNNING; this->count = (this->count + 1) % PARTS; this->read_size -= this->part_size; pthread_mutex_unlock(&this->data_mutex); lprintf("wait for playback (newcount=%d,free=%" PRIsizet ",avail=%" PRIsizet ")\n", this->count, buffer_free_size(this), this->read_size); do { ret = WaitForMultipleObjects(PARTS, handles, FALSE, 250) - WAIT_OBJECT_0; if (this->finished) goto finished; } while (ret > PARTS); lprintf("playback started (newcount=%d,ev=%d,free=%" PRIsizet ",avail=%" PRIsizet ")\n", this->count, ret, buffer_free_size(this), this->read_size); _x_assert(ret == ((PARTS + this->count - 1) % PARTS)); } else { this->count = (this->count + 1) % PARTS; this->read_size -= this->part_size; pthread_mutex_unlock(&this->data_mutex); } lprintf("waiting for sound event(count=%d,free=%" PRIsizet ",avail=%" PRIsizet ")...\n", this->count, buffer_free_size(this), this->read_size); do { ret = WaitForMultipleObjects(PARTS, handles, FALSE, 250) - WAIT_OBJECT_0; if (this->finished) goto finished; } while (ret > PARTS); lprintf("end wait(ev=%" PRIdword ",count=%d,free=%" PRIsizet ",avail=%" PRIsizet ")\n", ret, this->count, buffer_free_size(this), this->read_size); #ifdef PANIC_OVERRUN _x_abort(ret == this->count); #else if (ret != this->count) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": play cursor overran, flushing buffers\n")); pthread_mutex_lock(&this->data_mutex); if (!audio_stop(this)) goto fail; if (!audio_flush(this)) goto fail; pthread_mutex_unlock(&this->data_mutex); } #endif #if defined(EXACT_SLEEP) || defined(EXACT_WAIT) /* ugly hack: wait for right time, + little over for sure */ pthread_mutex_lock(&this->data_mutex); if (!audio_tell(this, &play_pos)) goto fail; req_delay = (this->buffer_size + play_pos - this->count * this->part_size) % this->buffer_size; pthread_mutex_unlock(&this->data_mutex); if (req_delay > (this->buffer_size >> 1)) { long delay; delay = 1000 * (this->buffer_size - req_delay) / (this->frame_size * this->rate) + 1 + BUFFER_MS / PARTS / 4; xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": delayed by %ld msec\n"), delay); printf("should be delayed %ld msec\n", delay); #ifdef EXACT_SLEEP xine_usec_sleep(delay * 1000); #endif #ifdef EXACT_WAIT WaitForMultipleObjects(PARTS, handles, FALSE, delay); #endif } #endif } return NULL; fail: this->finished = 1; finished: pthread_mutex_unlock(&this->data_mutex); return NULL; } /* ---- driver functions ---- */ static uint32_t ao_dx2_get_capabilities(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; return this->capabilities; } static int ao_dx2_get_property(ao_driver_t *this_gen, int property) { dx2_driver_t *this = (dx2_driver_t *)this_gen; switch(property) { case AO_PROP_MIXER_VOL: case AO_PROP_PCM_VOL: return this->volume; case AO_PROP_MUTE_VOL: return this->muted; default: return 0; } } static int ao_dx2_set_property(ao_driver_t *this_gen, int property, int value) { dx2_driver_t *this = (dx2_driver_t *)this_gen; switch(property) { case AO_PROP_MIXER_VOL: case AO_PROP_PCM_VOL: lprintf("set volume to %d\n", value); pthread_mutex_lock(&this->data_mutex); if (!this->muted) { if (this->dsbuffer && !audio_set_volume(this, value)) return ~value; } this->volume = value; pthread_mutex_unlock(&this->data_mutex); break; case AO_PROP_MUTE_VOL: pthread_mutex_lock(&this->data_mutex); if (this->dsbuffer && !audio_set_volume(this, value ? 0 : this->volume)) return ~value; this->muted = value; pthread_mutex_unlock(&this->data_mutex); break; default: return ~value; } return value; } static int ao_dx2_open(ao_driver_t *this_gen, uint32_t bits, uint32_t rate, int mode) { dx2_driver_t *this = (dx2_driver_t *)this_gen; lprintf("bits=%" PRIu32 ", rate=%" PRIu32 ", mode=%d\n", bits, rate, mode); if (rate < DSBFREQUENCY_MIN) rate = DSBFREQUENCY_MIN; if (rate > DSBFREQUENCY_MAX) rate = DSBFREQUENCY_MAX; this->bits = bits; this->rate = rate; if ((this->channels = mode2channels(mode)) == 0) return 0; this->frame_size = (this->bits >> 3) * this->channels; this->paused = 0; this->finished = 0; this->status = STATUS_START; if (!audio_create_buffers(this)) return 0; if (!audio_create_events(this)) goto fail_buffers; if (!audio_set_volume(this, this->volume)) goto fail_events; if (pthread_cond_init(&this->data_cond, NULL) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't create pthread condition: %s\n"), strerror(errno)); goto fail_events; } if (pthread_mutex_init(&this->data_mutex, NULL) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't create pthread mutex: %s\n"), strerror(errno)); goto fail_cond; } /* creating the service thread and waiting for its signal */ pthread_mutex_lock(&this->data_mutex); if (pthread_create(&this->buffer_service, NULL, buffer_service, this) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't create buffer pthread: %s\n"), strerror(errno)); goto fail_mutex; } pthread_cond_wait(&this->data_cond, &this->data_mutex); pthread_mutex_unlock(&this->data_mutex); return rate; fail_mutex: pthread_mutex_unlock(&this->data_mutex); pthread_mutex_destroy(&this->data_mutex); fail_cond: pthread_cond_destroy(&this->data_cond); fail_events: audio_destroy_events(this); fail_buffers: audio_destroy_buffers(this); return 0; } static int ao_dx2_num_channels(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; lprintf("channels=%d\n", this->channels); return this->channels; } static int ao_dx2_bytes_per_frame(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; lprintf("frame_size=%d\n", this->frame_size); return this->frame_size; } static int ao_dx2_delay(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; int frames; size_t final_pos, play_pos; if (this->status != STATUS_RUNNING) return this->read_size / this->frame_size; pthread_mutex_lock(&this->data_mutex); if (!audio_tell(this, &play_pos)) { pthread_mutex_unlock(&this->data_mutex); return 0; } final_pos = this->read_size + (((PARTS + this->count - 1) % PARTS) + 1) * this->part_size - 1; frames = (this->buffer_size + final_pos - play_pos) % this->buffer_size / this->frame_size; pthread_mutex_unlock(&this->data_mutex); #ifdef LOG if ((rand() % 10) == 0) lprintf("frames=%d, play_pos=%" PRIdword ", block=%" PRIsizet "..%" PRIsizet "\n", frames, play_pos, final_pos - this->part_size + 1, final_pos); #endif return frames; } static int ao_dx2_write(ao_driver_t *this_gen, int16_t* audio_data, uint32_t num_samples) { dx2_driver_t *this = (dx2_driver_t *)this_gen; size_t input_size; /* used size of input data */ size_t free_size; /* size of the free space in the ring buffer */ size_t size; /* current block size */ size_t read_pos; /* position in the input */ input_size = this->frame_size * num_samples; read_pos = 0; while (input_size && !this->finished) { pthread_mutex_lock(&this->data_mutex); while (((free_size = buffer_free_size(this)) == 0) && !this->finished) { lprintf("buffer full, waiting\n"); pthread_mutex_unlock(&this->data_mutex); xine_usec_sleep(1000 * BUFFER_MS / PARTS / 5); pthread_mutex_lock(&this->data_mutex); } if (free_size >= input_size) size = input_size; else size = free_size; if (!audio_fill(this, ((char *)audio_data) + read_pos, size)) { audio_thread_exit(this); pthread_mutex_unlock(&this->data_mutex); return 0; } pthread_mutex_unlock(&this->data_mutex); read_pos += size; input_size -= size; } return 1; } static void ao_dx2_close(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; lprintf("close plugin\n"); pthread_mutex_lock(&this->data_mutex); audio_thread_exit(this); pthread_mutex_unlock(&this->data_mutex); if (pthread_join(this->buffer_service, NULL) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't destroy buffer pthread: %s\n"), strerror(errno)); return; } lprintf("pthread joined\n"); pthread_mutex_unlock(&this->data_mutex); if (pthread_cond_destroy(&this->data_cond) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't destroy pthread condition: %s\n"), strerror(errno)); } if (pthread_mutex_destroy(&this->data_mutex) != 0) { xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": can't destroy pthread mutex: %s\n"), strerror(errno)); } audio_destroy_events(this); audio_destroy_buffers(this); } static void ao_dx2_exit(ao_driver_t *this_gen) { dx2_driver_t *this = (dx2_driver_t *)this_gen; lprintf("exit instance\n"); dsound_destroy(this->ds); free(this); } /* * TODO: check */ static int ao_dx2_get_gap_tolerance(ao_driver_t *this_gen) { /* half of part of the buffer in pts (1 msec = 90 pts) */ return (90 * (BUFFER_MS / PARTS)) >> 1; } static int ao_dx2_control(ao_driver_t *this_gen, int cmd, ...) { dx2_driver_t *this = (dx2_driver_t *)this_gen; switch(cmd) { case AO_CTRL_PLAY_PAUSE: lprintf("control pause\n"); pthread_mutex_lock(&this->data_mutex); if (!this->paused) { audio_stop(this); this->paused = 1; } pthread_mutex_unlock(&this->data_mutex); break; case AO_CTRL_PLAY_RESUME: lprintf("control resume\n"); pthread_mutex_lock(&this->data_mutex); if (this->paused) { if (this->status != STATUS_WAIT) audio_play(this); this->paused = 0; } pthread_mutex_unlock(&this->data_mutex); break; case AO_CTRL_FLUSH_BUFFERS: lprintf("control flush\n"); pthread_mutex_lock(&this->data_mutex); audio_stop(this); audio_flush(this); pthread_mutex_unlock(&this->data_mutex); break; default: xine_log(this->class->xine, XINE_LOG_MSG, _(LOG_MODULE ": unknown control command %d\n"), cmd); } return 0; } /* ---- class functions ---- */ static ao_driver_t *open_plugin(audio_driver_class_t *class_gen, const void *data) { dx2_class_t *class = (dx2_class_t *)class_gen; dx2_driver_t *this; lprintf("open plugin called\n"); this = (dx2_driver_t *)xine_xmalloc(sizeof(dx2_driver_t)); if (!this) return NULL; this->class = class; this->ao_driver.get_capabilities = ao_dx2_get_capabilities; this->ao_driver.get_property = ao_dx2_get_property; this->ao_driver.set_property = ao_dx2_set_property; this->ao_driver.open = ao_dx2_open; this->ao_driver.num_channels = ao_dx2_num_channels; this->ao_driver.bytes_per_frame = ao_dx2_bytes_per_frame; this->ao_driver.delay = ao_dx2_delay; this->ao_driver.write = ao_dx2_write; this->ao_driver.close = ao_dx2_close; this->ao_driver.exit = ao_dx2_exit; this->ao_driver.get_gap_tolerance = ao_dx2_get_gap_tolerance; this->ao_driver.control = ao_dx2_control; this->volume = 100; this->muted = 0; this->failed = 0; if ((this->ds = dsound_create()) == NULL) { free(this); return NULL; } test_capabilities(this); return (ao_driver_t *)this; } static char* get_identifier(audio_driver_class_t *this_gen) { return "directx2"; } static char *get_description(audio_driver_class_t *this_gen) { return _("second xine audio output plugin using directx"); } static void dispose_class(audio_driver_class_t *this_gen) { free(this_gen); } static void *init_class(xine_t *xine, void *data) { dx2_class_t *this; lprintf("init class\n"); this = (dx2_class_t *)xine_xmalloc(sizeof(dx2_class_t)); if (!this) return NULL; this->driver_class.open_plugin = open_plugin; this->driver_class.get_identifier = get_identifier; this->driver_class.get_description = get_description; this->driver_class.dispose = dispose_class; this->xine = xine; return this; } static const ao_info_t ao_info_directx2 = { 10 }; const plugin_info_t xine_plugin_info[] EXPORTED = { { PLUGIN_AUDIO_OUT, AO_OUT_DIRECTX2_IFACE_VERSION, "directx2", XINE_VERSION_CODE, &ao_info_directx2, init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };