/* * 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 4096static 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/parametersstatic 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=failstatic 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 */ 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 devicestatic 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 blockingstatic 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 playedstatic 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 bufferstatic 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;}