|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
buffer.c: output buffer
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
copyright 1997-2015 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 Oliver Fromme
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
I (ThOr) am reviewing this file at about the same daytime as Oliver's timestamp here:
|
|
Packit |
c32a2d |
Mon Apr 14 03:53:18 MET DST 1997
|
|
Packit |
c32a2d |
- dammed night coders;-)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
This has been heavily reworked to be barely recognizable for the creation of
|
|
Packit |
c32a2d |
libout123. There is more structure in the communication, as is necessary if
|
|
Packit |
c32a2d |
the libout123 functionality is offered via some API to unknown client
|
|
Packit |
c32a2d |
programs instead of being used from mpg123 alone. The basic idea is the same,
|
|
Packit |
c32a2d |
the xfermem part only sligthly modified for more synchronization, as I sensed
|
|
Packit |
c32a2d |
potential deadlocks. --ThOr
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Communication to the buffer is normally via xfermem_putcmd() and blocking
|
|
Packit |
c32a2d |
on a response, relying on the buffer process periodically checking for
|
|
Packit |
c32a2d |
pending commands.
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
For more immediate concerns, you can send SIGINT. The only result is that this
|
|
Packit |
c32a2d |
interrupts a current device writing operation and causes the buffer to wait
|
|
Packit |
c32a2d |
for a following command.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Needed for kill() from signal.h. */
|
|
Packit |
c32a2d |
#define _POSIX_SOURCE
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#include "buffer.h"
|
|
Packit |
c32a2d |
#include "out123_int.h"
|
|
Packit |
c32a2d |
#include "xfermem.h"
|
|
Packit |
c32a2d |
#include <errno.h>
|
|
Packit |
c32a2d |
#include "debug.h"
|
|
Packit |
c32a2d |
#ifdef HAVE_SYS_WAIT_H
|
|
Packit |
c32a2d |
#include <sys/wait.h>
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
#ifdef HAVE_SIGNAL_H
|
|
Packit |
c32a2d |
#include <signal.h>
|
|
Packit |
c32a2d |
#else
|
|
Packit |
c32a2d |
#ifdef HAVE_SYS_SIGNAL_H
|
|
Packit |
c32a2d |
#include <sys/signal.h>
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#define BUF_CMD_OPEN XF_CMD_CUSTOM1
|
|
Packit |
c32a2d |
#define BUF_CMD_CLOSE XF_CMD_CUSTOM2
|
|
Packit |
c32a2d |
#define BUF_CMD_START XF_CMD_CUSTOM3
|
|
Packit |
c32a2d |
#define BUF_CMD_STOP XF_CMD_CUSTOM4
|
|
Packit |
c32a2d |
#define BUF_CMD_AUDIOCAP XF_CMD_CUSTOM5
|
|
Packit |
c32a2d |
#define BUF_CMD_PARAM XF_CMD_CUSTOM6
|
|
Packit |
c32a2d |
#define BUF_CMD_NDRAIN XF_CMD_CUSTOM7
|
|
Packit |
c32a2d |
#define BUF_CMD_AUDIOFMT XF_CMD_CUSTOM8
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* TODO: Dynamically allocate that to allow multiple instances. */
|
|
Packit |
c32a2d |
int outburst = 32768;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* This is static and global for the forked buffer process.
|
|
Packit |
c32a2d |
Another forked buffer process will have its on value. */
|
|
Packit |
c32a2d |
static int intflag = FALSE;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void catch_interrupt (void)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
intflag = TRUE;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int read_record(out123_handle *ao
|
|
Packit |
c32a2d |
, int who, void **buf, byte *prebuf, int *preoff, int presize, size_t *recsize);
|
|
Packit |
c32a2d |
static int buffer_loop(out123_handle *ao);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static void catch_child(void)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* Disabled for now. We do not really need that.
|
|
Packit |
c32a2d |
Rather get return status in a controlled way in buffer_exit(). */
|
|
Packit |
c32a2d |
/* while (waitpid(-1, NULL, WNOHANG) > 0); */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Functions called from the controlling process.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Start a buffer process. */
|
|
Packit |
c32a2d |
int buffer_init(out123_handle *ao, size_t bytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
buffer_exit(ao);
|
|
Packit |
c32a2d |
if(bytes < outburst) bytes = 2*outburst;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#ifdef DONT_CATCH_SIGNALS
|
|
Packit |
c32a2d |
#error I really need to catch signals here!
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
xfermem_init(&ao->buffermem, bytes, 0, 0);
|
|
Packit |
c32a2d |
/* Is catch_child() really useful? buffer_exit() does waitpid().
|
|
Packit |
c32a2d |
And if buffer_exit() is not called, the main process might be
|
|
Packit |
c32a2d |
killed off and not be able to run a signal handler anyway. */
|
|
Packit |
c32a2d |
catchsignal(SIGCHLD, catch_child);
|
|
Packit |
c32a2d |
switch((ao->buffer_pid = fork()))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case -1: /* error */
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error("cannot fork!");
|
|
Packit |
c32a2d |
goto buffer_init_bad;
|
|
Packit |
c32a2d |
case 0: /* child */
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int ret;
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Ensure the normal default value for buffer_pid to be able
|
|
Packit |
c32a2d |
to call normal out123 routines from the buffer proess.
|
|
Packit |
c32a2d |
One could keep it at zero and even use this for detecting the
|
|
Packit |
c32a2d |
buffer process and do special stuff for that. But the point
|
|
Packit |
c32a2d |
is that there shouldn't be special stuff.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
ao->buffer_pid = -1;
|
|
Packit |
c32a2d |
/* Not preparing audio output anymore, that comes later. */
|
|
Packit |
c32a2d |
xfermem_init_reader(ao->buffermem);
|
|
Packit |
c32a2d |
ret = buffer_loop(ao); /* Here the work happens. */
|
|
Packit |
c32a2d |
xfermem_done_reader(ao->buffermem);
|
|
Packit |
c32a2d |
xfermem_done(ao->buffermem);
|
|
Packit |
c32a2d |
/* Proper cleanup of output handle, including out123_close(). */
|
|
Packit |
c32a2d |
out123_del(ao);
|
|
Packit |
c32a2d |
exit(ret);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
default: /* parent */
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int cmd;
|
|
Packit |
c32a2d |
xfermem_init_writer(ao->buffermem);
|
|
Packit |
c32a2d |
debug("waiting for inital pong from buffer process");
|
|
Packit |
c32a2d |
if( (cmd=xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE))
|
|
Packit |
c32a2d |
!= XF_CMD_PONG )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error2("Got %i instead of expected initial response %i. Killing rogue buffer process."
|
|
Packit |
c32a2d |
, cmd, XF_CMD_PONG);
|
|
Packit |
c32a2d |
kill(ao->buffer_pid, SIGKILL);
|
|
Packit |
c32a2d |
buffer_exit(ao);
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
buffer_init_bad:
|
|
Packit |
c32a2d |
if(ao->buffermem)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_done(ao->buffermem);
|
|
Packit |
c32a2d |
ao->buffermem = NULL;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* End a buffer process. */
|
|
Packit |
c32a2d |
void buffer_exit(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int status = 0;
|
|
Packit |
c32a2d |
if(ao->buffer_pid == -1) return;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug("ending buffer");
|
|
Packit |
c32a2d |
buffer_stop(ao); /* Puts buffer into waiting-for-command mode. */
|
|
Packit |
c32a2d |
buffer_end(ao); /* Gives command to end operation. */
|
|
Packit |
c32a2d |
xfermem_done_writer(ao->buffermem);
|
|
Packit |
c32a2d |
waitpid(ao->buffer_pid, &status, 0);
|
|
Packit |
c32a2d |
xfermem_done(ao->buffermem);
|
|
Packit |
c32a2d |
ao->buffermem = NULL;
|
|
Packit |
c32a2d |
ao->buffer_pid = -1;
|
|
Packit |
c32a2d |
if(WIFEXITED(status))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int ret = WEXITSTATUS(status);
|
|
Packit |
c32a2d |
if(ret && !AOQUIET)
|
|
Packit |
c32a2d |
error1("Buffer process isses arose, non-zero return value %i.", ret);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else if(!AOQUIET)
|
|
Packit |
c32a2d |
error("Buffer process did not exit normally.");
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Communication from writer to reader (buffer process).
|
|
Packit |
c32a2d |
Remember: The ao struct here is the writer's instance.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
static int buffer_cmd_finish(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* Only if buffer returns XF_CMD_OK we got lucky. Otherwise, we expect
|
|
Packit |
c32a2d |
the buffer to deliver a reason right after XF_CMD_ERROR. */
|
|
Packit |
c32a2d |
switch(xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
case XF_CMD_OK: return 0;
|
|
Packit |
c32a2d |
case XF_CMD_ERROR:
|
|
Packit |
c32a2d |
if(!GOOD_READVAL(ao->buffermem->fd[XF_WRITER], ao->errcode))
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
default:
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int buffer_sync_param(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_PARAM) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Calling an external serialization routine to avoid forgetting
|
|
Packit |
c32a2d |
any fresh parameters here. */
|
|
Packit |
c32a2d |
if(write_parameters(ao, XF_WRITER))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return buffer_cmd_finish(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int buffer_open(out123_handle *ao, const char* driver, const char* device)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_OPEN) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Passing over driver and device name. */
|
|
Packit |
c32a2d |
if( xfer_write_string(ao, XF_WRITER, driver)
|
|
Packit |
c32a2d |
|| xfer_write_string(ao, XF_WRITER, device) )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(buffer_cmd_finish(ao) == 0)
|
|
Packit |
c32a2d |
/* Retrieve driver and device name. */
|
|
Packit |
c32a2d |
return ( xfer_read_string(ao, XF_WRITER, &ao->driver)
|
|
Packit |
c32a2d |
|| xfer_read_string(ao, XF_WRITER, &ao->device)
|
|
Packit |
c32a2d |
|| xfer_read_string(ao, XF_WRITER, &ao->realname) );
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int buffer_encodings(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_AUDIOCAP) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Now shoving over the parameters for opening the device. */
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_WRITEVAL(writerfd, ao->channels)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, ao->rate)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(buffer_cmd_finish(ao) == 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int encodings;
|
|
Packit |
c32a2d |
/* If all good, the answer can be read how. */
|
|
Packit |
c32a2d |
if(!GOOD_READVAL(writerfd, encodings))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else return encodings;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int buffer_formats( out123_handle *ao, const long *rates, int ratecount
|
|
Packit |
c32a2d |
, int minchannels, int maxchannels
|
|
Packit |
c32a2d |
, struct mpg123_fmt **fmtlist )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
size_t ratesize;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug("buffer_formats");
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_AUDIOFMT) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
ratesize = ratecount*sizeof(rates);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_WRITEVAL(writerfd, maxchannels)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, minchannels)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, ratesize)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEBUF(writerfd, rates, ratesize)
|
|
Packit |
c32a2d |
){
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(buffer_cmd_finish(ao) == 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int fmtcount;
|
|
Packit |
c32a2d |
size_t fmtsize;
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_READVAL(writerfd, fmtcount)
|
|
Packit |
c32a2d |
|| read_record(ao, XF_WRITER, (void**)fmtlist, NULL, NULL, 0, &fmtsize)
|
|
Packit |
c32a2d |
){
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
} else
|
|
Packit |
c32a2d |
return fmtsize/sizeof(struct mpg123_fmt);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int buffer_start(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_START) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Now shoving over the parameters for opening the device. */
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_WRITEVAL(writerfd, ao->format)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, ao->channels)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, ao->rate)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return buffer_cmd_finish(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#define BUFFER_SIMPLE_CONTROL(name, cmd) \
|
|
Packit |
c32a2d |
void name(out123_handle *ao) \
|
|
Packit |
c32a2d |
{ \
|
|
Packit |
c32a2d |
xfermem_putcmd(ao->buffermem->fd[XF_WRITER], cmd); \
|
|
Packit |
c32a2d |
xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE); \
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_stop, BUF_CMD_STOP)
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_continue, XF_CMD_CONTINUE)
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_ignore_lowmem, XF_CMD_IGNLOW)
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_drain, XF_CMD_DRAIN)
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_end, XF_CMD_TERMINATE)
|
|
Packit |
c32a2d |
BUFFER_SIMPLE_CONTROL(buffer_close, BUF_CMD_CLOSE)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
#define BUFFER_SIGNAL_CONTROL(name, cmd) \
|
|
Packit |
c32a2d |
void name(out123_handle *ao) \
|
|
Packit |
c32a2d |
{ \
|
|
Packit |
c32a2d |
kill(ao->buffer_pid, SIGINT); \
|
|
Packit |
c32a2d |
xfermem_putcmd(ao->buffermem->fd[XF_WRITER], cmd); \
|
|
Packit |
c32a2d |
xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE); \
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
BUFFER_SIGNAL_CONTROL(buffer_pause, XF_CMD_PAUSE)
|
|
Packit |
c32a2d |
BUFFER_SIGNAL_CONTROL(buffer_drop, XF_CMD_DROP)
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
size_t buffer_fill(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
return xfermem_get_usedspace(ao->buffermem);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
void buffer_ndrain(out123_handle *ao, size_t bytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t oldfill;
|
|
Packit |
c32a2d |
int writerfd = ao->buffermem->fd[XF_WRITER];
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
oldfill = buffer_fill(ao);
|
|
Packit |
c32a2d |
if(xfermem_putcmd(writerfd, BUF_CMD_NDRAIN) != 1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Now shoving over the parameters for opening the device. */
|
|
Packit |
c32a2d |
if( !GOOD_WRITEVAL(writerfd, bytes)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEVAL(writerfd, oldfill) )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
buffer_cmd_finish(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* The workhorse: Send data to the buffer with some synchronization and even
|
|
Packit |
c32a2d |
error checking. */
|
|
Packit |
c32a2d |
size_t buffer_write(out123_handle *ao, void *buffer, size_t bytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Writing the whole buffer in one piece is no good as that means
|
|
Packit |
c32a2d |
waiting for the buffer being empty. That is called a buffer underrun.
|
|
Packit |
c32a2d |
We want to refill the buffer before that happens. So, what is sane?
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
size_t written = 0;
|
|
Packit |
c32a2d |
size_t max_piece = ao->buffermem->size / 2;
|
|
Packit |
c32a2d |
while(bytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t count_piece = bytes > max_piece
|
|
Packit |
c32a2d |
? max_piece
|
|
Packit |
c32a2d |
: bytes;
|
|
Packit |
c32a2d |
int ret = xfermem_write(ao->buffermem
|
|
Packit |
c32a2d |
, (char*)buffer+written, count_piece);
|
|
Packit |
c32a2d |
if(ret)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1("writing to buffer memory failed (%i)", ret);
|
|
Packit |
c32a2d |
if(ret == XF_CMD_ERROR)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* Buffer tells me that it has an error waiting. */
|
|
Packit |
c32a2d |
if(!GOOD_READVAL(ao->buffermem->fd[XF_WRITER], ao->errcode))
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
bytes -= count_piece;
|
|
Packit |
c32a2d |
written += count_piece;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return written;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Code for the buffer process itself.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
buffer loop:
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
1. normal operation: get data, feed to audio device
|
|
Packit |
c32a2d |
(if device open and alive, if data there, if no other command pending)
|
|
Packit |
c32a2d |
2. command response: pause/unpause, open module/device, query caps
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
One command at a time, synchronized ... writer process blocks, waiting for
|
|
Packit |
c32a2d |
response.
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
Fill buffer to that value when starting playback from stopped state or after
|
|
Packit |
c32a2d |
experiencing a serious underrun.
|
|
Packit |
c32a2d |
One might also define intermediate preload to recover from underruns. Earlier
|
|
Packit |
c32a2d |
code used 1/8 of the buffer.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
static size_t preload_size(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t preload = 0;
|
|
Packit |
c32a2d |
txfermem *xf = ao->buffermem;
|
|
Packit |
c32a2d |
/* Fill configured part of buffer on first run before starting to play.
|
|
Packit |
c32a2d |
* Live mp3 streams constantly approach buffer underrun otherwise. [dk]
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
if(ao->preload > 0.) preload = (size_t)(ao->preload*xf->size);
|
|
Packit |
c32a2d |
if(preload > xf->size/2) preload = xf->size/2;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
return preload;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Play one piece of audio from the buffer after settling preload etc.
|
|
Packit |
c32a2d |
On error, the device is closed and this naturally stops playback
|
|
Packit |
c32a2d |
as that depends on ao->state == play_live.
|
|
Packit |
c32a2d |
This plays _at_ _most_ the given amount of bytes, usually less. */
|
|
Packit |
c32a2d |
static void buffer_play(out123_handle *ao, size_t bytes)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t written;
|
|
Packit |
c32a2d |
txfermem *xf = ao->buffermem;
|
|
Packit |
c32a2d |
/* Settle amount of bytes accessible in one block. */
|
|
Packit |
c32a2d |
if (bytes > xf->size - xf->readindex)
|
|
Packit |
c32a2d |
bytes = xf->size - xf->readindex;
|
|
Packit |
c32a2d |
/* Not more than configured output block. */
|
|
Packit |
c32a2d |
if (bytes > outburst)
|
|
Packit |
c32a2d |
bytes = outburst;
|
|
Packit |
c32a2d |
/* The output can only take multiples of framesize. */
|
|
Packit |
c32a2d |
bytes -= bytes % ao->framesize;
|
|
Packit |
c32a2d |
/* Actual work by out123_play to ensure logic like automatic continue. */
|
|
Packit |
c32a2d |
written = out123_play(ao, (unsigned char*)xf->data+xf->readindex, bytes);
|
|
Packit |
c32a2d |
/* Advance read pointer by the amount of written bytes. */
|
|
Packit |
c32a2d |
xf->readindex = (xf->readindex + written) % xf->size;
|
|
Packit |
c32a2d |
/* Detect a fatal error by proxy. */
|
|
Packit |
c32a2d |
if(ao->errcode == OUT123_DEV_PLAY)
|
|
Packit |
c32a2d |
out123_close(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Now I'm getting really paranoid: Helper to skip bytes from command
|
|
Packit |
c32a2d |
channel if we cannot allocate enough memory to hold the data. */
|
|
Packit |
c32a2d |
static void skip_bytes(int fd, size_t count)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
while(count)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char buf[1024];
|
|
Packit |
c32a2d |
if(!unintr_read(fd, buf, (count < sizeof(buf) ? count : sizeof(buf))))
|
|
Packit |
c32a2d |
return;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Write a string to command channel.
|
|
Packit |
c32a2d |
Return 0 on success, set ao->errcode on issues. */
|
|
Packit |
c32a2d |
int xfer_write_string(out123_handle *ao, int who, const char *buf)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
txfermem *xf = ao->buffermem;
|
|
Packit |
c32a2d |
int my_fd = xf->fd[who];
|
|
Packit |
c32a2d |
size_t len;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* A NULL string is passed als zero bytes. */
|
|
Packit |
c32a2d |
len = buf ? (strlen(buf)+1) : 0;
|
|
Packit |
c32a2d |
if( !GOOD_WRITEVAL(my_fd, len)
|
|
Packit |
c32a2d |
|| !GOOD_WRITEBUF(my_fd, buf, len) )
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
int xfer_read_string(out123_handle *ao, int who, char **buf)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* ao->errcode set in read_record() */
|
|
Packit |
c32a2d |
return read_record(ao, who, (void**)buf, NULL, NULL, 0, NULL)
|
|
Packit |
c32a2d |
? -1 /* read_record could return 2, normalize to -1 */
|
|
Packit |
c32a2d |
: 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Read a value from command channel with prebuffer.
|
|
Packit |
c32a2d |
This assumes responsible use and avoids needless checking of input.
|
|
Packit |
c32a2d |
And, yes, it modifies the preoff argument!
|
|
Packit |
c32a2d |
Returns 0 on success, modifies prebuffer fill. */
|
|
Packit |
c32a2d |
int read_buf(int fd, void *addr, size_t size, byte *prebuf, int *preoff, int presize)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t need = size;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(prebuf)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int have = presize - *preoff;
|
|
Packit |
c32a2d |
if(have > need)
|
|
Packit |
c32a2d |
have = need;
|
|
Packit |
c32a2d |
memcpy(addr, prebuf+*preoff, have);
|
|
Packit |
c32a2d |
*preoff += have;
|
|
Packit |
c32a2d |
addr = (char*)addr+have;
|
|
Packit |
c32a2d |
need -= have;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(need)
|
|
Packit |
c32a2d |
return !GOOD_READBUF(fd, addr, need);
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* Read a record of unspecified type from command channel.
|
|
Packit |
c32a2d |
Return 0 on success, set ao->errcode on issues. */
|
|
Packit |
c32a2d |
static int read_record(out123_handle *ao
|
|
Packit |
c32a2d |
, int who, void **buf, byte *prebuf, int *preoff, int presize
|
|
Packit |
c32a2d |
, size_t *reclen)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
txfermem *xf = ao->buffermem;
|
|
Packit |
c32a2d |
int my_fd = xf->fd[who];
|
|
Packit |
c32a2d |
size_t len;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(*buf)
|
|
Packit |
c32a2d |
free(*buf);
|
|
Packit |
c32a2d |
*buf = NULL;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(read_buf(my_fd, &len, sizeof(len), prebuf, preoff, presize))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(reclen)
|
|
Packit |
c32a2d |
*reclen = len;
|
|
Packit |
c32a2d |
/* If there is an insane length of given, that shall be handled. */
|
|
Packit |
c32a2d |
if(len && !(*buf = malloc(len)))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_DOOM;
|
|
Packit |
c32a2d |
skip_bytes(my_fd, len);
|
|
Packit |
c32a2d |
return -1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
if(read_buf(my_fd, *buf, len, prebuf, preoff, presize))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
ao->errcode = OUT123_BUFFER_ERROR;
|
|
Packit |
c32a2d |
free(*buf);
|
|
Packit |
c32a2d |
*buf = NULL;
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
/* The main loop, returns 0 when no issue occured. */
|
|
Packit |
c32a2d |
int buffer_loop(out123_handle *ao)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
txfermem *xf = ao->buffermem;
|
|
Packit |
c32a2d |
int my_fd = xf->fd[XF_READER];
|
|
Packit |
c32a2d |
int preloading = FALSE;
|
|
Packit |
c32a2d |
int draining = FALSE;
|
|
Packit |
c32a2d |
/* The buffer loop maintains a playback state that can differ from
|
|
Packit |
c32a2d |
the underlying device's. During prebuffering, the device is paused,
|
|
Packit |
c32a2d |
but we are playing (as soon as enough data is there, the device is,
|
|
Packit |
c32a2d |
too). */
|
|
Packit |
c32a2d |
enum playstate mystate = ao->state;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
ao->flags &= ~OUT123_KEEP_PLAYING; /* No need for that here. */
|
|
Packit |
c32a2d |
/* Be prepared to use SIGINT for communication. */
|
|
Packit |
c32a2d |
catchsignal (SIGINT, catch_interrupt);
|
|
Packit |
c32a2d |
/* sigprocmask (SIG_SETMASK, oldsigset, NULL); */
|
|
Packit |
c32a2d |
/* Say hello to the writer. */
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_PONG);
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug1("buffer with preload %g", ao->preload);
|
|
Packit |
c32a2d |
while(1)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* If a device is opened and playing, it is our first duty to keep it playing. */
|
|
Packit |
c32a2d |
if(mystate == play_live)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t bytes = xfermem_get_usedspace(xf);
|
|
Packit |
c32a2d |
debug4( "Play or preload? Got %"SIZE_P" B / %"SIZE_P" B (%i,%i)."
|
|
Packit |
c32a2d |
, (size_p)bytes, (size_p)preload_size(ao), preloading, draining );
|
|
Packit |
c32a2d |
if(preloading)
|
|
Packit |
c32a2d |
preloading = (bytes < preload_size(ao));
|
|
Packit |
c32a2d |
if(!preloading)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!draining && bytes < outburst)
|
|
Packit |
c32a2d |
preloading = TRUE;
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
buffer_play(ao, bytes);
|
|
Packit |
c32a2d |
mystate = ao->state; /* Maybe changed, must be in sync now. */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Be nice and pause the device on preloading. */
|
|
Packit |
c32a2d |
if(preloading && ao->state == play_live)
|
|
Packit |
c32a2d |
out123_pause(ao);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
/* Now always check for a pending command, in a blocking way if there is
|
|
Packit |
c32a2d |
no playback. */
|
|
Packit |
c32a2d |
debug2("Buffer cmd? (Interruped: %i) (mystate=%i)", intflag, (int)mystate);
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
The writer only ever signals before sending a command and also waiting
|
|
Packit |
c32a2d |
for a response. So, the right place to reset the flag is any time
|
|
Packit |
c32a2d |
before giving the response. But let's ensure two things:
|
|
Packit |
c32a2d |
1. The flag really is only cleared when a command response is given.
|
|
Packit |
c32a2d |
2. Command parsing does not stop until a command demanding a response
|
|
Packit |
c32a2d |
was handled.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
do
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
/* Getting a whole block of commands to efficiently process those
|
|
Packit |
c32a2d |
XF_CMD_DATA messages. */
|
|
Packit |
c32a2d |
byte cmd[100];
|
|
Packit |
c32a2d |
int cmdcount;
|
|
Packit |
c32a2d |
int i;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
cmdcount = xfermem_getcmds( my_fd
|
|
Packit |
c32a2d |
, (preloading || intflag || (mystate != play_live))
|
|
Packit |
c32a2d |
, cmd
|
|
Packit |
c32a2d |
, sizeof(cmd) );
|
|
Packit |
c32a2d |
if(cmdcount < 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1("Reading a command set returned %i, my link is broken.", cmdcount);
|
|
Packit |
c32a2d |
return 1;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
#ifdef DEBUG
|
|
Packit |
c32a2d |
for(i=0; i
|
|
Packit |
c32a2d |
debug2("cmd[%i]=%u", i, cmd[i]);
|
|
Packit |
c32a2d |
#endif
|
|
Packit |
c32a2d |
/*
|
|
Packit |
c32a2d |
These actions should rely heavily on calling the normal out123
|
|
Packit |
c32a2d |
API functions, just with some parameter passing and error checking
|
|
Packit |
c32a2d |
wrapped around. If there is much code here, it is wrong.
|
|
Packit |
c32a2d |
*/
|
|
Packit |
c32a2d |
for(i=0; i
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
#define GOOD_READVAL_BUF(fd, val) \
|
|
Packit |
c32a2d |
!read_buf(my_fd, &val, sizeof(val), cmd, &i, cmdcount)
|
|
Packit |
c32a2d |
case XF_CMD_DATA:
|
|
Packit |
c32a2d |
debug("got new data");
|
|
Packit |
c32a2d |
/* Other states should not happen. */
|
|
Packit |
c32a2d |
if(mystate == play_paused)
|
|
Packit |
c32a2d |
mystate = play_live;
|
|
Packit |
c32a2d |
/* When new data arrives, we are obviously not draining. */
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_PING:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
/* Expecting ping-pong only while playing! Otherwise, the writer
|
|
Packit |
c32a2d |
could get stuck waiting for free space forever. */
|
|
Packit |
c32a2d |
if(mystate == play_live)
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_PONG);
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
if(ao->errcode == OUT123_OK)
|
|
Packit |
c32a2d |
ao->errcode = OUT123_NOT_LIVE;
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_PARAM:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
/* If that does not work, communication is broken anyway and
|
|
Packit |
c32a2d |
writer will notice soon enough. */
|
|
Packit |
c32a2d |
read_parameters(ao, XF_READER, cmd, &i, cmdcount);
|
|
Packit |
c32a2d |
ao->flags &= ~OUT123_KEEP_PLAYING; /* No need for that here. */
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_OPEN:
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
char *driver = NULL;
|
|
Packit |
c32a2d |
char *device = NULL;
|
|
Packit |
c32a2d |
int success;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
success = (
|
|
Packit |
c32a2d |
!read_record( ao, XF_READER, (void**)&driver
|
|
Packit |
c32a2d |
, cmd, &i, cmdcount, NULL )
|
|
Packit |
c32a2d |
&& !read_record( ao, XF_READER, (void**)&device
|
|
Packit |
c32a2d |
, cmd, &i, cmdcount, NULL )
|
|
Packit |
c32a2d |
&& !out123_open(ao, driver, device)
|
|
Packit |
c32a2d |
);
|
|
Packit |
c32a2d |
free(device);
|
|
Packit |
c32a2d |
free(driver);
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
if(success)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
if( xfer_write_string(ao, XF_READER, ao->driver)
|
|
Packit |
c32a2d |
|| xfer_write_string(ao, XF_READER, ao->device)
|
|
Packit |
c32a2d |
|| xfer_write_string(ao, XF_READER, ao->realname ) )
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
/* Again, no sense to bitch around about communication errors,
|
|
Packit |
c32a2d |
just quit. */
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_CLOSE:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
out123_close(ao);
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_AUDIOCAP:
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int encodings;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_READVAL_BUF(my_fd, ao->channels)
|
|
Packit |
c32a2d |
|| !GOOD_READVAL_BUF(my_fd, ao->rate)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
encodings = out123_encodings(ao, ao->rate, ao->channels);
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
if(encodings >= 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, encodings))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_AUDIOFMT:
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t blocksize;
|
|
Packit |
c32a2d |
long *rates = NULL;
|
|
Packit |
c32a2d |
int minchannels;
|
|
Packit |
c32a2d |
int maxchannels;
|
|
Packit |
c32a2d |
struct mpg123_fmt *fmtlist;
|
|
Packit |
c32a2d |
int fmtcount = -1;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_READVAL_BUF(my_fd, maxchannels)
|
|
Packit |
c32a2d |
|| !GOOD_READVAL_BUF(my_fd, minchannels)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
read_record( ao, XF_READER, (void**)&rates
|
|
Packit |
c32a2d |
, cmd, &i, cmdcount, &blocksize )
|
|
Packit |
c32a2d |
){
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
fmtcount = out123_formats( ao, rates
|
|
Packit |
c32a2d |
, (int)(blocksize/sizeof(*rates))
|
|
Packit |
c32a2d |
, minchannels, maxchannels, &fmtlist );
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
free(rates);
|
|
Packit |
c32a2d |
if(fmtcount >= 0)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
int success;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
blocksize = sizeof(*fmtlist)*fmtcount;
|
|
Packit |
c32a2d |
debug2("responding with %i formats (block: %"SIZE_P")"
|
|
Packit |
c32a2d |
, fmtcount, (size_p)blocksize);
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
success =
|
|
Packit |
c32a2d |
GOOD_WRITEVAL(my_fd, fmtcount)
|
|
Packit |
c32a2d |
&& GOOD_WRITEVAL(my_fd, blocksize)
|
|
Packit |
c32a2d |
&& GOOD_WRITEBUF(my_fd, fmtlist, blocksize);
|
|
Packit |
c32a2d |
free(fmtlist);
|
|
Packit |
c32a2d |
if(!success)
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
} else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_START:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_READVAL_BUF(my_fd, ao->format)
|
|
Packit |
c32a2d |
|| !GOOD_READVAL_BUF(my_fd, ao->channels)
|
|
Packit |
c32a2d |
|| !GOOD_READVAL_BUF(my_fd, ao->rate)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
if(!out123_start(ao, ao->rate, ao->channels, ao->format))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
out123_pause(ao); /* Be nice, start only on buffer_play(). */
|
|
Packit |
c32a2d |
mystate = play_live;
|
|
Packit |
c32a2d |
preloading = TRUE;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_ERROR);
|
|
Packit |
c32a2d |
if(!GOOD_WRITEVAL(my_fd, ao->errcode))
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_STOP:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
if(mystate == play_live)
|
|
Packit |
c32a2d |
{ /* Drain is implied! */
|
|
Packit |
c32a2d |
size_t bytes;
|
|
Packit |
c32a2d |
while((bytes = xfermem_get_usedspace(xf)))
|
|
Packit |
c32a2d |
buffer_play(ao, bytes);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
out123_stop(ao);
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_CONTINUE:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
debug("continuing");
|
|
Packit |
c32a2d |
mystate = play_live; /* We'll get errors reported later if that is not right. */
|
|
Packit |
c32a2d |
preloading = FALSE; /* It should continue without delay. */
|
|
Packit |
c32a2d |
draining = FALSE; /* But outburst should be cared for. */
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_IGNLOW:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
preloading = FALSE;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_DRAIN:
|
|
Packit |
c32a2d |
debug("buffer drain");
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
if(mystate == play_live)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t bytes;
|
|
Packit |
c32a2d |
while(
|
|
Packit |
c32a2d |
(bytes = xfermem_get_usedspace(xf))
|
|
Packit |
c32a2d |
&& bytes > ao->framesize
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
buffer_play(ao, bytes);
|
|
Packit |
c32a2d |
out123_drain(ao);
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case BUF_CMD_NDRAIN:
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t limit;
|
|
Packit |
c32a2d |
size_t oldfill;
|
|
Packit |
c32a2d |
|
|
Packit |
c32a2d |
debug("buffer ndrain");
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
/* Expect further calls to ndrain, avoid prebuffering. */
|
|
Packit |
c32a2d |
draining = TRUE;
|
|
Packit |
c32a2d |
preloading = FALSE;
|
|
Packit |
c32a2d |
if(
|
|
Packit |
c32a2d |
!GOOD_READVAL_BUF(my_fd, limit)
|
|
Packit |
c32a2d |
|| !GOOD_READVAL_BUF(my_fd, oldfill)
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
return 2;
|
|
Packit |
c32a2d |
if(mystate == play_live)
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
size_t bytes;
|
|
Packit |
c32a2d |
while(
|
|
Packit |
c32a2d |
(bytes = xfermem_get_usedspace(xf))
|
|
Packit |
c32a2d |
&& bytes > ao->framesize
|
|
Packit |
c32a2d |
&& oldfill >= bytes /* paranoia, overflow would handle it anyway */
|
|
Packit |
c32a2d |
&& (oldfill-bytes) < limit
|
|
Packit |
c32a2d |
)
|
|
Packit |
c32a2d |
buffer_play(ao, bytes > limit ? limit : bytes);
|
|
Packit |
c32a2d |
/* Only drain hardware if the end was reached. */
|
|
Packit |
c32a2d |
if(!xfermem_get_usedspace(xf))
|
|
Packit |
c32a2d |
{
|
|
Packit |
c32a2d |
out123_drain(ao);
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
debug2( "buffer drained %"SIZE_P" / %"SIZE_P
|
|
Packit |
c32a2d |
, oldfill-bytes, limit );
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
else
|
|
Packit |
c32a2d |
debug("drain without playback ... not good");
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_TERMINATE:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
/* Will that response always reach the writer? Well, at worst,
|
|
Packit |
c32a2d |
it's an ignored error on xfermem_getcmd(). */
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
return 0;
|
|
Packit |
c32a2d |
case XF_CMD_PAUSE:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
out123_pause(ao);
|
|
Packit |
c32a2d |
mystate = ao->state;
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
case XF_CMD_DROP:
|
|
Packit |
c32a2d |
intflag = FALSE;
|
|
Packit |
c32a2d |
draining = FALSE;
|
|
Packit |
c32a2d |
xf->readindex = xf->freeindex;
|
|
Packit |
c32a2d |
out123_drop(ao);
|
|
Packit |
c32a2d |
xfermem_putcmd(my_fd, XF_CMD_OK);
|
|
Packit |
c32a2d |
break;
|
|
Packit |
c32a2d |
default:
|
|
Packit |
c32a2d |
if(!AOQUIET)
|
|
Packit |
c32a2d |
error1("Unknown command %u encountered. Confused Suicide!", cmd[i]);
|
|
Packit |
c32a2d |
return 1;
|
|
Packit |
c32a2d |
#undef GOOD_READVAL_BUF
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
} /* Ensure that an interrupt-giving command has been received. */
|
|
Packit |
c32a2d |
while(intflag);
|
|
Packit |
c32a2d |
if(intflag && !AOQUIET)
|
|
Packit |
c32a2d |
error("buffer: The intflag should not be set anymore.");
|
|
Packit |
c32a2d |
intflag = FALSE; /* Any possible harm by _not_ ensuring that the flag is cleared here? */
|
|
Packit |
c32a2d |
}
|
|
Packit |
c32a2d |
}
|