diff options
Diffstat (limited to 'libao2/ao_nas.c')
-rw-r--r-- | libao2/ao_nas.c | 646 |
1 files changed, 0 insertions, 646 deletions
diff --git a/libao2/ao_nas.c b/libao2/ao_nas.c deleted file mode 100644 index d3274df9a5..0000000000 --- a/libao2/ao_nas.c +++ /dev/null @@ -1,646 +0,0 @@ -/* - * NAS audio output driver - * - * copyright (c) 2001 Tobias Diedrich <ranma@gmx.at> - * - * Based on the libaudiooss parts rewritten by me, which were - * originally based on the NAS output plugin for XMMS. - * - * XMMS plugin by Willem Monsuwe - * adapted for libaudiooss by Jon Trulson - * further modified by Erik Inge Bolsø - * largely rewritten and used for this ao driver by Tobias Diedrich - * - * 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. - */ - -/* - * Theory of operation: - * - * The NAS consists of two parts, a server daemon and a client. - * We setup the server to use a buffer of size bytes_per_second - * with a low watermark of buffer_size - NAS_FRAG_SIZE. - * Upon starting the flow the server will generate a buffer underrun - * event and the event handler will fill the buffer for the first time. - * Now the server will generate a lowwater event when the server buffer - * falls below the low watermark value. The event handler gets called - * again and refills the buffer by the number of bytes requested by the - * server (usually a multiple of 4096). To prevent stuttering on - * startup (start of playing, seeks, unpausing) the client buffer should - * be bigger than the server buffer. (For debugging we also do some - * accounting of what we think how much of the server buffer is filled) - */ - -#include <unistd.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <pthread.h> -#include <limits.h> -#include <audio/audiolib.h> - -#include "config.h" -#include "mp_msg.h" - -#include "audio_out.h" -#include "audio_out_internal.h" -#include "libaf/af_format.h" - -/* NAS_FRAG_SIZE must be a power-of-two value */ -#define NAS_FRAG_SIZE 4096 - -static const char * const nas_event_types[] = { - "Undefined", - "Undefined", - "ElementNotify", - "GrabNotify", - "MonitorNotify", - "BucketNotify", - "DeviceNotify" -}; - -static const char * const nas_elementnotify_kinds[] = { - "LowWater", - "HighWater", - "State", - "Unknown" -}; - -static const char * const nas_states[] = { - "Stop", - "Start", - "Pause", - "Any" -}; - -static const char * const nas_reasons[] = { - "User", - "Underrun", - "Overrun", - "EOF", - "Watermark", - "Hardware", - "Any" -}; - -static const char* nas_reason(unsigned int reason) -{ - if (reason > 6) reason = 6; - return nas_reasons[reason]; -} - -static const char* nas_elementnotify_kind(unsigned int kind) -{ - if (kind > 2) kind = 3; - return nas_elementnotify_kinds[kind]; -} - -static const char* nas_event_type(unsigned int type) { - if (type > 6) type = 0; - return nas_event_types[type]; -} - -static const char* nas_state(unsigned int state) { - if (state>3) state = 3; - return nas_states[state]; -} - -static const ao_info_t info = -{ - "NAS audio output", - "nas", - "Tobias Diedrich <ranma+mplayer@tdiedrich.de>", - "" -}; - -struct ao_nas_data { - AuServer *aud; - AuFlowID flow; - AuDeviceID dev; - AuFixedPoint gain; - - unsigned int state; - int expect_underrun; - - char *client_buffer; - char *server_buffer; - unsigned int client_buffer_size; - unsigned int client_buffer_used; - unsigned int server_buffer_size; - unsigned int server_buffer_used; - pthread_mutex_t buffer_mutex; - - pthread_t event_thread; - int stop_thread; -}; - -static struct ao_nas_data *nas_data; - -LIBAO_EXTERN(nas) - -static void nas_print_error(AuServer *aud, const char *prefix, AuStatus as) -{ - char s[100]; - AuGetErrorText(aud, as, s, 100); - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: %s: returned status %d (%s)\n", prefix, as, s); -} - -static int nas_readBuffer(struct ao_nas_data *nas_data, unsigned int num) -{ - AuStatus as; - - pthread_mutex_lock(&nas_data->buffer_mutex); - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: nas_readBuffer(): num=%d client=%d/%d server=%d/%d\n", - num, - nas_data->client_buffer_used, nas_data->client_buffer_size, - nas_data->server_buffer_used, nas_data->server_buffer_size); - - if (nas_data->client_buffer_used == 0) { - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: buffer is empty, nothing read.\n"); - pthread_mutex_unlock(&nas_data->buffer_mutex); - return 0; - } - if (num > nas_data->client_buffer_used) - num = nas_data->client_buffer_used; - - /* - * It is not appropriate to call AuWriteElement() here because the - * buffer is locked and delays writing to the network will cause - * other threads to block waiting for buffer_mutex. Instead the - * data is copied to "server_buffer" and written to the network - * outside of the locked section of code. - * - * (Note: Rather than these two buffers, a single circular buffer - * could eliminate the memcpy/memmove steps.) - */ - /* make sure we don't overflow the buffer */ - if (num > nas_data->server_buffer_size) - num = nas_data->server_buffer_size; - memcpy(nas_data->server_buffer, nas_data->client_buffer, num); - - nas_data->client_buffer_used -= num; - nas_data->server_buffer_used += num; - memmove(nas_data->client_buffer, nas_data->client_buffer + num, nas_data->client_buffer_used); - pthread_mutex_unlock(&nas_data->buffer_mutex); - - /* - * Now write the new buffer to the network. - */ - AuWriteElement(nas_data->aud, nas_data->flow, 0, num, nas_data->server_buffer, AuFalse, &as); - if (as != AuSuccess) - nas_print_error(nas_data->aud, "nas_readBuffer(): AuWriteElement", as); - - return num; -} - -static int nas_writeBuffer(struct ao_nas_data *nas_data, void *data, unsigned int len) -{ - pthread_mutex_lock(&nas_data->buffer_mutex); - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: nas_writeBuffer(): len=%d client=%d/%d server=%d/%d\n", - len, nas_data->client_buffer_used, nas_data->client_buffer_size, - nas_data->server_buffer_used, nas_data->server_buffer_size); - - /* make sure we don't overflow the buffer */ - if (len > nas_data->client_buffer_size - nas_data->client_buffer_used) - len = nas_data->client_buffer_size - nas_data->client_buffer_used; - memcpy(nas_data->client_buffer + nas_data->client_buffer_used, data, len); - nas_data->client_buffer_used += len; - - pthread_mutex_unlock(&nas_data->buffer_mutex); - - return len; -} - -static int nas_empty_event_queue(struct ao_nas_data *nas_data) -{ - AuEvent ev; - int result = 0; - - while (AuScanForTypedEvent(nas_data->aud, AuEventsQueuedAfterFlush, - AuTrue, AuEventTypeElementNotify, &ev)) { - AuDispatchEvent(nas_data->aud, &ev); - result = 1; - } - return result; -} - -static void *nas_event_thread_start(void *data) -{ - struct ao_nas_data *nas_data = data; - - do { - mp_msg(MSGT_AO, MSGL_DBG2, - "ao_nas: event thread heartbeat (state=%s)\n", - nas_state(nas_data->state)); - nas_empty_event_queue(nas_data); - usleep(1000); - } while (!nas_data->stop_thread); - - return NULL; -} - -static AuBool nas_error_handler(AuServer* aud, AuErrorEvent* ev) -{ - char s[100]; - AuGetErrorText(aud, ev->error_code, s, 100); - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: error [%s]\n" - "error_code: %d\n" - "request_code: %d\n" - "minor_code: %d\n", - s, - ev->error_code, - ev->request_code, - ev->minor_code); - - return AuTrue; -} - -static AuBool nas_event_handler(AuServer *aud, AuEvent *ev, AuEventHandlerRec *hnd) -{ - AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev; - struct ao_nas_data *nas_data = hnd->data; - - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: event_handler(): type %s kind %s state %s->%s reason %s numbytes %d expect_underrun %d\n", - nas_event_type(event->type), - nas_elementnotify_kind(event->kind), - nas_state(event->prev_state), - nas_state(event->cur_state), - nas_reason(event->reason), - (int)event->num_bytes, - nas_data->expect_underrun); - - if (event->num_bytes > INT_MAX) { - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: num_bytes > 2GB, server buggy?\n"); - } - - if (event->num_bytes > nas_data->server_buffer_used) - event->num_bytes = nas_data->server_buffer_used; - nas_data->server_buffer_used -= event->num_bytes; - - switch (event->reason) { - case AuReasonWatermark: - nas_readBuffer(nas_data, event->num_bytes); - break; - case AuReasonUnderrun: - // buffer underrun -> refill buffer - nas_data->server_buffer_used = 0; - if (nas_data->expect_underrun) { - nas_data->expect_underrun = 0; - } else { - static int hint = 1; - mp_msg(MSGT_AO, MSGL_WARN, - "ao_nas: Buffer underrun.\n"); - if (hint) { - hint = 0; - mp_msg(MSGT_AO, MSGL_HINT, - "Possible reasons are:\n" - "1) Network congestion.\n" - "2) Your NAS server is too slow.\n" - "Try renicing your nasd to e.g. -15.\n"); - } - } - if (nas_readBuffer(nas_data, - nas_data->server_buffer_size - - nas_data->server_buffer_used) != 0) { - event->cur_state = AuStateStart; - break; - } - mp_msg(MSGT_AO, MSGL_DBG2, - "ao_nas: Can't refill buffer, stopping flow.\n"); - AuStopFlow(aud, nas_data->flow, NULL); - break; - default: - break; - } - nas_data->state=event->cur_state; - return AuTrue; -} - -static AuDeviceID nas_find_device(AuServer *aud, int nch) -{ - int i; - for (i = 0; i < AuServerNumDevices(aud); i++) { - AuDeviceAttributes *dev = AuServerDevice(aud, i); - if ((AuDeviceKind(dev) == AuComponentKindPhysicalOutput) && - AuDeviceNumTracks(dev) == nch) { - return AuDeviceIdentifier(dev); - } - } - return AuNone; -} - -static unsigned int nas_aformat_to_auformat(unsigned int *format) -{ - switch (*format) { - case AF_FORMAT_U8: - return AuFormatLinearUnsigned8; - case AF_FORMAT_S8: - return AuFormatLinearSigned8; - case AF_FORMAT_U16_LE: - return AuFormatLinearUnsigned16LSB; - case AF_FORMAT_U16_BE: - return AuFormatLinearUnsigned16MSB; - case AF_FORMAT_S16_LE: - return AuFormatLinearSigned16LSB; - case AF_FORMAT_S16_BE: - return AuFormatLinearSigned16MSB; - case AF_FORMAT_MU_LAW: - return AuFormatULAW8; - default: - *format=AF_FORMAT_S16_NE; - return nas_aformat_to_auformat(format); - } -} - -// to set/get/query special features/parameters -static int control(int cmd, void *arg) -{ - AuElementParameters aep; - AuStatus as; - int retval = CONTROL_UNKNOWN; - - ao_control_vol_t *vol = (ao_control_vol_t *)arg; - - switch (cmd) { - case AOCONTROL_GET_VOLUME: - - vol->right = (float)nas_data->gain/AU_FIXED_POINT_SCALE*50; - vol->left = vol->right; - - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: AOCONTROL_GET_VOLUME: %.2f\n", vol->right); - retval = CONTROL_OK; - break; - - case AOCONTROL_SET_VOLUME: - /* - * kn: we should have vol->left == vol->right but i don't - * know if something can change it outside of ao_nas - * so i take the mean of both values. - */ - nas_data->gain = AU_FIXED_POINT_SCALE*((vol->left+vol->right)/2)/50; - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: AOCONTROL_SET_VOLUME: %.2f\n", (vol->left+vol->right)/2); - - aep.parameters[AuParmsMultiplyConstantConstant]=nas_data->gain; - aep.flow = nas_data->flow; - aep.element_num = 1; - aep.num_parameters = AuParmsMultiplyConstant; - - AuSetElementParameters(nas_data->aud, 1, &aep, &as); - if (as != AuSuccess) { - nas_print_error(nas_data->aud, - "control(): AuSetElementParameters", as); - retval = CONTROL_ERROR; - } else retval = CONTROL_OK; - break; - }; - - return retval; -} - -// open & setup audio device -// return: 1=success 0=fail -static int init(int rate,int channels,int format,int flags) -{ - AuElement elms[3]; - AuStatus as; - unsigned char auformat = nas_aformat_to_auformat(&format); - int bytes_per_sample = channels * AuSizeofFormat(auformat); - int buffer_size; - char *server; - - (void)flags; /* shut up 'unused parameter' warning */ - - global_ao->no_persistent_volume = true; - - nas_data=malloc(sizeof(struct ao_nas_data)); - memset(nas_data, 0, sizeof(struct ao_nas_data)); - - mp_msg(MSGT_AO, MSGL_V, "ao2: %d Hz %d chans %s\n",rate,channels, - af_fmt2str_short(format)); - - ao_data.format = format; - ao_data.samplerate = rate; - ao_data.channels = channels; - ao_data.outburst = NAS_FRAG_SIZE; - ao_data.bps = rate * bytes_per_sample; - buffer_size = ao_data.bps; /* buffer 1 second */ - /* - * round up to multiple of NAS_FRAG_SIZE - * divide by 3 first because of 2:1 split - */ - buffer_size = (buffer_size/3 + NAS_FRAG_SIZE-1) & ~(NAS_FRAG_SIZE-1); - ao_data.buffersize = buffer_size*3; - - nas_data->client_buffer_size = buffer_size*2; - nas_data->client_buffer = malloc(nas_data->client_buffer_size); - nas_data->server_buffer_size = buffer_size; - nas_data->server_buffer = malloc(nas_data->server_buffer_size); - - if (!bytes_per_sample) { - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: init(): Zero bytes per sample -> nosound\n"); - return 0; - } - - if (!(server = getenv("AUDIOSERVER")) && - !(server = getenv("DISPLAY"))) { - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: init(): AUDIOSERVER environment variable not set -> nosound\n"); - return 0; - } - - mp_msg(MSGT_AO, MSGL_V, "ao_nas: init(): Using audioserver %s\n", server); - - nas_data->aud = AuOpenServer(server, 0, NULL, 0, NULL, NULL); - if (!nas_data->aud) { - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: init(): Can't open nas audio server -> nosound\n"); - return 0; - } - - while (channels>0) { - nas_data->dev = nas_find_device(nas_data->aud, channels); - if (nas_data->dev != AuNone && - ((nas_data->flow = AuCreateFlow(nas_data->aud, NULL)) != 0)) - break; - channels--; - } - - if (nas_data->flow == 0) { - mp_msg(MSGT_AO, MSGL_ERR, "ao_nas: init(): Can't find a suitable output device -> nosound\n"); - AuCloseServer(nas_data->aud); - nas_data->aud = 0; - return 0; - } - - AuMakeElementImportClient(elms, rate, auformat, channels, AuTrue, - buffer_size / bytes_per_sample, - (buffer_size - NAS_FRAG_SIZE) / - bytes_per_sample, 0, NULL); - nas_data->gain = AuFixedPointFromFraction(1, 1); - AuMakeElementMultiplyConstant(elms+1, 0, nas_data->gain); - AuMakeElementExportDevice(elms+2, 1, nas_data->dev, rate, - AuUnlimitedSamples, 0, NULL); - AuSetElements(nas_data->aud, nas_data->flow, AuTrue, sizeof(elms)/sizeof(*elms), elms, &as); - if (as != AuSuccess) { - nas_print_error(nas_data->aud, "init(): AuSetElements", as); - AuCloseServer(nas_data->aud); - nas_data->aud = 0; - return 0; - } - AuRegisterEventHandler(nas_data->aud, AuEventHandlerIDMask | - AuEventHandlerTypeMask, - AuEventTypeElementNotify, nas_data->flow, - nas_event_handler, (AuPointer) nas_data); - AuSetErrorHandler(nas_data->aud, nas_error_handler); - nas_data->state=AuStateStop; - nas_data->expect_underrun=0; - - pthread_mutex_init(&nas_data->buffer_mutex, NULL); - pthread_create(&nas_data->event_thread, NULL, &nas_event_thread_start, nas_data); - - return 1; -} - -// close audio device -static void uninit(int immed){ - - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: uninit()\n"); - - nas_data->expect_underrun = 1; - if (!immed) - while (nas_data->state != AuStateStop) usleep(1000); - nas_data->stop_thread = 1; - pthread_join(nas_data->event_thread, NULL); - AuCloseServer(nas_data->aud); - nas_data->aud = 0; - free(nas_data->client_buffer); - free(nas_data->server_buffer); -} - -// stop playing and empty buffers (for seeking/pause) -static void reset(void){ - AuStatus as; - - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: reset()\n"); - - pthread_mutex_lock(&nas_data->buffer_mutex); - nas_data->client_buffer_used = 0; - pthread_mutex_unlock(&nas_data->buffer_mutex); - while (nas_data->state != AuStateStop) { - AuStopFlow(nas_data->aud, nas_data->flow, &as); - if (as != AuSuccess) - nas_print_error(nas_data->aud, "reset(): AuStopFlow", as); - usleep(1000); - } -} - -// stop playing, keep buffers (for pause) -static void audio_pause(void) -{ - AuStatus as; - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: audio_pause()\n"); - - AuStopFlow(nas_data->aud, nas_data->flow, &as); -} - -// resume playing, after audio_pause() -static void audio_resume(void) -{ - AuStatus as; - - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: audio_resume()\n"); - - AuStartFlow(nas_data->aud, nas_data->flow, &as); - if (as != AuSuccess) - nas_print_error(nas_data->aud, - "play(): AuStartFlow", as); -} - - -// return: how many bytes can be played without blocking -static int get_space(void) -{ - int result; - - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: get_space()\n"); - - pthread_mutex_lock(&nas_data->buffer_mutex); - result = nas_data->client_buffer_size - nas_data->client_buffer_used; - pthread_mutex_unlock(&nas_data->buffer_mutex); - - return result; -} - -// 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) -{ - int written, maxbursts = 0, playbursts = 0; - AuStatus as; - - mp_msg(MSGT_AO, MSGL_DBG3, - "ao_nas: play(%p, %d, %d)\n", - data, len, flags); - - if (len == 0) - return 0; - - if (!(flags & AOPLAY_FINAL_CHUNK)) { - pthread_mutex_lock(&nas_data->buffer_mutex); - maxbursts = (nas_data->client_buffer_size - - nas_data->client_buffer_used) / ao_data.outburst; - playbursts = len / ao_data.outburst; - len = (playbursts > maxbursts ? maxbursts : playbursts) * - ao_data.outburst; - pthread_mutex_unlock(&nas_data->buffer_mutex); - } - - /* - * If AOPLAY_FINAL_CHUNK is set, we did not actually check len fits - * into the available buffer space, but mplayer.c shouldn't give us - * more to play than we report to it by get_space(), so this should be - * fine. - */ - written = nas_writeBuffer(nas_data, data, len); - - if (nas_data->state != AuStateStart && - (maxbursts == playbursts || - flags & AOPLAY_FINAL_CHUNK)) { - mp_msg(MSGT_AO, MSGL_DBG2, "ao_nas: play(): Starting flow.\n"); - nas_data->expect_underrun = 1; - AuStartFlow(nas_data->aud, nas_data->flow, &as); - if (as != AuSuccess) - nas_print_error(nas_data->aud, "play(): AuStartFlow", as); - } - - return written; -} - -// return: delay in seconds between first and last sample in buffer -static float get_delay(void) -{ - float result; - - mp_msg(MSGT_AO, MSGL_DBG3, "ao_nas: get_delay()\n"); - - pthread_mutex_lock(&nas_data->buffer_mutex); - result = ((float)(nas_data->client_buffer_used + - nas_data->server_buffer_used)) / - (float)ao_data.bps; - pthread_mutex_unlock(&nas_data->buffer_mutex); - - return result; -} |