|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
nas: audio output via NAS
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
copyright ?-2016 by the mpg123 project - free software under the terms of the LGPL 2.1
|
|
Packit |
c32a2d |
see COPYING and AUTHORS files in distribution or http://mpg123.org
|
|
Packit |
c32a2d |
initially written by Martin Denn
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#include "out123_int.h"
|
|
Packit |
c32a2d |
#include <fcntl.h>
|
|
Packit |
c32a2d |
#include <audio/audiolib.h>
|
|
Packit |
c32a2d |
#include <audio/soundlib.h>
|
|
Packit |
c32a2d |
#include "debug.h"
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
typedef struct
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
AuServer *aud;
|
|
Packit |
c32a2d |
AuFlowID flow;
|
|
Packit |
c32a2d |
AuDeviceAttributes *da;
|
|
Packit |
c32a2d |
int numDevices;
|
|
Packit |
c32a2d |
char *buf;
|
|
Packit |
c32a2d |
AuUint32 buf_size;
|
|
Packit |
c32a2d |
AuUint32 buf_cnt;
|
|
Packit |
c32a2d |
AuBool data_sent;
|
|
Packit |
c32a2d |
AuBool finished;
|
|
Packit |
c32a2d |
} InfoRec, *InfoPtr;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* seconds */
|
|
Packit |
c32a2d |
#define NAS_SOUND_PORT_DURATION (ao->device_buffer > 0. ? ao->device_buffer : 5)
|
|
Packit |
c32a2d |
#define NAS_SOUND_LOW_WATER_MARK 25 /* percent */
|
|
Packit |
c32a2d |
#define NAS_MAX_FORMAT 10 /* currently, there are 7 supported formats */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* FIXME: stick this inside userptr inside out123_handle instead */
|
|
Packit |
c32a2d |
static InfoRec info;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* NAS specific routines */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void nas_sendData(AuServer *aud, InfoPtr i, AuUint32 numBytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if (numBytes < i->buf_cnt) {
|
|
Packit |
c32a2d |
AuWriteElement(aud, i->flow, 0, numBytes, i->buf, AuFalse, NULL);
|
|
Packit |
c32a2d |
memmove(i->buf, i->buf + numBytes, i->buf_cnt - numBytes);
|
|
Packit |
c32a2d |
i->buf_cnt = i->buf_cnt - numBytes;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else {
|
|
Packit |
c32a2d |
AuWriteElement(aud, i->flow, 0, i->buf_cnt, i->buf,
|
|
Packit |
c32a2d |
(numBytes > i->buf_cnt), NULL);
|
|
Packit |
c32a2d |
i->buf_cnt = 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
i->data_sent = AuTrue;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static AuBool nas_eventHandler(AuServer *aud, AuEvent *ev, AuEventHandlerRec *handler)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
InfoPtr i = (InfoPtr) handler->data;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
switch (ev->type)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case AuEventTypeMonitorNotify:
|
|
Packit |
c32a2d |
i->finished = AuTrue;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuEventTypeElementNotify:
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
switch (event->kind)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case AuElementNotifyKindLowWater:
|
|
Packit |
c32a2d |
nas_sendData(aud, i, event->num_bytes);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuElementNotifyKindState:
|
|
Packit |
c32a2d |
switch (event->cur_state)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case AuStatePause:
|
|
Packit |
c32a2d |
if (event->reason != AuReasonUser)
|
|
Packit |
c32a2d |
nas_sendData(aud, i, event->num_bytes);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuStateStop:
|
|
Packit |
c32a2d |
i->finished = AuTrue;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return AuTrue;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* 0 on error */
|
|
Packit |
c32a2d |
static int nas_createFlow(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
AuDeviceID device = AuNone;
|
|
Packit |
c32a2d |
AuElement elements[2];
|
|
Packit |
c32a2d |
unsigned char format;
|
|
Packit |
c32a2d |
AuUint32 buf_samples;
|
|
Packit |
c32a2d |
int i;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
switch(ao->format) {
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_16:
|
|
Packit |
c32a2d |
default:
|
|
Packit |
c32a2d |
if (((char) *(short *)"x")=='x') /* ugly, but painless */
|
|
Packit |
c32a2d |
format = AuFormatLinearSigned16LSB; /* little endian */
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
format = AuFormatLinearSigned16MSB; /* big endian */
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_UNSIGNED_8:
|
|
Packit |
c32a2d |
format = AuFormatLinearUnsigned8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_SIGNED_8:
|
|
Packit |
c32a2d |
format = AuFormatLinearSigned8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case MPG123_ENC_ULAW_8:
|
|
Packit |
c32a2d |
format = AuFormatULAW8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* look for an output device */
|
|
Packit |
c32a2d |
for (i = 0; i < AuServerNumDevices(info.aud); i++)
|
|
Packit |
c32a2d |
if (((AuDeviceKind(AuServerDevice(info.aud, i)) ==
|
|
Packit |
c32a2d |
AuComponentKindPhysicalOutput) &&
|
|
Packit |
c32a2d |
AuDeviceNumTracks(AuServerDevice(info.aud, i))
|
|
Packit |
c32a2d |
== ao->channels )) {
|
|
Packit |
c32a2d |
device = AuDeviceIdentifier(AuServerDevice(info.aud, i));
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if (device == AuNone) {
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1( "Couldn't find an output device providing %d channels."
|
|
Packit |
c32a2d |
, ao->channels );
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* set gain */
|
|
Packit |
c32a2d |
if(ao->gain >= 0) {
|
|
Packit |
c32a2d |
info.da = AuGetDeviceAttributes(info.aud, device, NULL);
|
|
Packit |
c32a2d |
if ((info.da)!=NULL) {
|
|
Packit |
c32a2d |
AuDeviceGain(info.da) = AuFixedPointFromSum(ao->gain, 0);
|
|
Packit |
c32a2d |
AuSetDeviceAttributes(info.aud, AuDeviceIdentifier(info.da),
|
|
Packit |
c32a2d |
AuCompDeviceGainMask, info.da, NULL);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else if(!AOQUIET)
|
|
Packit |
c32a2d |
error("audio/gain: setable Volume/PCM-Level not supported");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if (!(info.flow = AuCreateFlow(info.aud, NULL))) {
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error("Couldn't create flow");
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
buf_samples = ao->rate * NAS_SOUND_PORT_DURATION;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
AuMakeElementImportClient(&elements[0], /* element */
|
|
Packit |
c32a2d |
(unsigned short) ao->rate,
|
|
Packit |
c32a2d |
/* rate */
|
|
Packit |
c32a2d |
format, /* format */
|
|
Packit |
c32a2d |
ao->channels, /* channels */
|
|
Packit |
c32a2d |
AuTrue, /* ??? */
|
|
Packit |
c32a2d |
buf_samples, /* max samples */
|
|
Packit |
c32a2d |
(AuUint32) (buf_samples / 100
|
|
Packit |
c32a2d |
* NAS_SOUND_LOW_WATER_MARK),
|
|
Packit |
c32a2d |
/* low water mark */
|
|
Packit |
c32a2d |
0, /* num actions */
|
|
Packit |
c32a2d |
NULL); /* actions */
|
|
Packit |
c32a2d |
AuMakeElementExportDevice(&elements[1], /* element */
|
|
Packit |
c32a2d |
0, /* input */
|
|
Packit |
c32a2d |
device, /* device */
|
|
Packit |
c32a2d |
(unsigned short) ao->rate,
|
|
Packit |
c32a2d |
/* rate */
|
|
Packit |
c32a2d |
AuUnlimitedSamples, /* num samples */
|
|
Packit |
c32a2d |
0, /* num actions */
|
|
Packit |
c32a2d |
NULL); /* actions */
|
|
Packit |
c32a2d |
AuSetElements(info.aud, /* Au server */
|
|
Packit |
c32a2d |
info.flow, /* flow ID */
|
|
Packit |
c32a2d |
AuTrue, /* clocked */
|
|
Packit |
c32a2d |
2, /* num elements */
|
|
Packit |
c32a2d |
elements, /* elements */
|
|
Packit |
c32a2d |
NULL); /* return status */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
AuRegisterEventHandler(info.aud, /* Au server */
|
|
Packit |
c32a2d |
AuEventHandlerIDMask, /* value mask */
|
|
Packit |
c32a2d |
0, /* type */
|
|
Packit |
c32a2d |
info.flow, /* id */
|
|
Packit |
c32a2d |
nas_eventHandler, /* callback */
|
|
Packit |
c32a2d |
(AuPointer) &info;; /* data */
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
info.buf_size = buf_samples * ao->channels * AuSizeofFormat(format);
|
|
Packit |
c32a2d |
info.buf = (char *) malloc(info.buf_size);
|
|
Packit |
c32a2d |
if (info.buf == NULL) {
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1("Unable to allocate input/output buffer of size %ld",
|
|
Packit |
c32a2d |
(long)info.buf_size);
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
info.buf_cnt = 0;
|
|
Packit |
c32a2d |
info.data_sent = AuFalse;
|
|
Packit |
c32a2d |
info.finished = AuFalse;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
AuStartFlow(info.aud, /* Au server */
|
|
Packit |
c32a2d |
info.flow, /* id */
|
|
Packit |
c32a2d |
NULL); /* status */
|
|
Packit |
c32a2d |
return 1; /* success */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void flush_nas(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
AuEvent ev;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
while ((!info.data_sent) && (!info.finished)) {
|
|
Packit |
c32a2d |
AuNextEvent(info.aud, AuTrue, &ev;;
|
|
Packit |
c32a2d |
AuDispatchEvent(info.aud, &ev;;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
info.data_sent = AuFalse;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* returning -1 on error, 0 on success... */
|
|
Packit |
c32a2d |
static int open_nas(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!ao) return -1;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if (!(info.aud = AuOpenServer(ao->device, 0, NULL, 0, NULL, NULL)))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if (ao->device==NULL)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error("could not open default NAS server");
|
|
Packit |
c32a2d |
} else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1("could not open NAS server %s\n", ao->device);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
info.buf_size = 0;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int get_formats_nas(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int i, j, k, ret;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
ret=0;
|
|
Packit |
c32a2d |
j = AuServerNumFormats(info.aud);
|
|
Packit |
c32a2d |
for (i=0; i
|
|
Packit |
c32a2d |
k=AuServerFormat(info.aud,i);
|
|
Packit |
c32a2d |
switch (k)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case AuFormatULAW8:
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_ULAW_8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuFormatLinearUnsigned8:
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_UNSIGNED_8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuFormatLinearSigned8:
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_8;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case AuFormatLinearSigned16LSB:
|
|
Packit |
c32a2d |
ret |= MPG123_ENC_SIGNED_16;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return ret;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int write_nas(out123_handle *ao,unsigned char *buf,int len)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int buf_cnt = 0;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if (info.buf_size == 0)
|
|
Packit |
c32a2d |
if(!nas_createFlow(ao)) return -1;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
while ((info.buf_cnt + (len - buf_cnt)) > info.buf_size) {
|
|
Packit |
c32a2d |
memcpy(info.buf + info.buf_cnt,
|
|
Packit |
c32a2d |
buf + buf_cnt,
|
|
Packit |
c32a2d |
(info.buf_size - info.buf_cnt));
|
|
Packit |
c32a2d |
buf_cnt += (info.buf_size - info.buf_cnt);
|
|
Packit |
c32a2d |
info.buf_cnt += (info.buf_size - info.buf_cnt);
|
|
Packit |
c32a2d |
flush_nas(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
memcpy(info.buf + info.buf_cnt,
|
|
Packit |
c32a2d |
buf + buf_cnt,
|
|
Packit |
c32a2d |
(len - buf_cnt));
|
|
Packit |
c32a2d |
info.buf_cnt += (len - buf_cnt);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return len;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int close_nas(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if (info.aud == NULL) {
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if (info.buf_size == 0) {
|
|
Packit |
c32a2d |
/* Au server opened, but not yet initialized */
|
|
Packit |
c32a2d |
AuCloseServer(info.aud);
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
while (!info.finished) {
|
|
Packit |
c32a2d |
flush_nas(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
AuCloseServer(info.aud);
|
|
Packit |
c32a2d |
free(info.buf);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int init_nas(out123_handle* ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if (ao==NULL) return -1;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Set callbacks */
|
|
Packit |
c32a2d |
ao->open = open_nas;
|
|
Packit |
c32a2d |
ao->flush = flush_nas;
|
|
Packit |
c32a2d |
ao->write = write_nas;
|
|
Packit |
c32a2d |
ao->get_formats = get_formats_nas;
|
|
Packit |
c32a2d |
ao->close = close_nas;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Success */
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Module information data structure
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
mpg123_module_t mpg123_output_module_info = {
|
|
Packit |
c32a2d |
/* api_version */ MPG123_MODULE_API_VERSION,
|
|
Packit |
c32a2d |
/* name */ "nas",
|
|
Packit |
c32a2d |
/* description */ "Output audio using NAS (Network Audio System)",
|
|
Packit |
c32a2d |
/* revision */ "$Rev:$",
|
|
Packit |
c32a2d |
/* handle */ NULL,
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* init_output */ init_nas,
|
|
Packit |
c32a2d |
};
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|