/*
* This file is part of DisOrder.
* Copyright (C) 2007 Richard Kettlewell
* Portions copyright (C) 2007 Ross Younger
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/** @file clients/playrtp-oss.c
* @brief RTP player - OSS and empeg support
*/
#include "common.h"
#if HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
#include
#include
#if !EMPEG_HOST
#include
#endif
#include
#include
#include
#include
#include
#include "mem.h"
#include "log.h"
#include "vector.h"
#include "heap.h"
#include "syscalls.h"
#include "playrtp.h"
/** @brief /dev/dsp (or whatever) */
static int playrtp_oss_fd = -1;
/** @brief Audio buffer */
static char *playrtp_oss_buffer;
/** @brief Size of @ref playrtp_oss_buffer in bytes */
static int playrtp_oss_bufsize;
/** @brief Number of bytes used in @ref playrtp_oss_buffer */
static int playrtp_oss_bufused;
/** @brief Open and configure the OSS audio device */
static void playrtp_oss_enable(void) {
if(playrtp_oss_fd == -1) {
#if EMPEG_HOST
/* empeg audio driver only knows /dev/audio, only supports the equivalent
* of AFMT_S16_NE, has a fixed buffer size, and does not support the
* SNDCTL_ ioctls. */
if(!device)
device = "/dev/audio";
if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
fatal(errno, "error opening %s", device);
playrtp_oss_bufsize = 4608;
#else
int rate = 44100, stereo = 1, format = AFMT_S16_BE;
if(!device) {
if(access("/dev/dsp", W_OK) == 0)
device = "/dev/dsp";
else if(access("/dev/audio", W_OK) == 0)
device = "/dev/audio";
else
fatal(0, "cannot determine default audio device");
}
if((playrtp_oss_fd = open(device, O_WRONLY)) < 0)
fatal(errno, "error opening %s", device);
if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SETFMT, &format) < 0)
fatal(errno, "ioctl SNDCTL_DSP_SETFMT");
if(ioctl(playrtp_oss_fd, SNDCTL_DSP_STEREO, &stereo) < 0)
fatal(errno, "ioctl SNDCTL_DSP_STEREO");
if(ioctl(playrtp_oss_fd, SNDCTL_DSP_SPEED, &rate) < 0)
fatal(errno, "ioctl SNDCTL_DSP_SPEED");
if(rate != 44100)
error(0, "asking for 44100Hz, got %dHz", rate);
if(ioctl(playrtp_oss_fd, SNDCTL_DSP_GETBLKSIZE, &playrtp_oss_bufsize) < 0)
fatal(errno, "ioctl SNDCTL_DSP_GETBLKSIZE");
info("OSS buffer size %d", playrtp_oss_bufsize);
#endif
playrtp_oss_buffer = xmalloc(playrtp_oss_bufsize);
playrtp_oss_bufused = 0;
nonblock(playrtp_oss_fd);
}
}
/** @brief Flush the OSS output buffer
* @return 0 on success, non-0 on error
*/
static int playrtp_oss_flush(void) {
int nbyteswritten;
if(!playrtp_oss_bufused)
return 0; /* nothing to do */
/* 0 out the unused portion of the buffer */
memset(playrtp_oss_buffer + playrtp_oss_bufused, 0,
playrtp_oss_bufsize - playrtp_oss_bufused);
#if EMPEG_HOST
/* empeg audio driver insists on native-endian samples */
{
uint16_t *ptr,
*const limit = (uint16_t *)(playrtp_oss_buffer + playrtp_oss_bufused);
for(ptr = (uint16_t *)playrtp_oss_buffer; ptr < limit; ++ptr)
*ptr = ntohs(*ptr);
}
#endif
for(;;) {
nbyteswritten = write(playrtp_oss_fd,
playrtp_oss_buffer, playrtp_oss_bufsize);
if(nbyteswritten < 0) {
switch(errno) {
case EINTR:
break; /* try again */
case EAGAIN:
return 0; /* try later */
default:
error(errno, "error writing to %s", device);
return -1;
}
} else {
if(nbyteswritten < playrtp_oss_bufsize)
error(0, "%s: short write (%d/%d)",
device, nbyteswritten, playrtp_oss_bufsize);
if(dump_buffer) {
int count;
const int16_t *sp = (const int16_t *)playrtp_oss_buffer;
for(count = 0; count < playrtp_oss_bufsize; count += sizeof(int16_t)) {
dump_buffer[dump_index++] = (int16_t)ntohs(*sp++);
dump_index %= dump_size;
}
}
playrtp_oss_bufused = 0;
return 0;
}
}
}
/** @brief Wait until the audio device can accept more data */
static void playrtp_oss_wait(void) {
struct pollfd fds[1];
int n;
do {
fds[0].fd = playrtp_oss_fd;
fds[0].events = POLLOUT;
while((n = poll(fds, 1, -1)) < 0 && errno == EINTR)
;
if(n < 0)
fatal(errno, "calling poll");
} while(!(fds[0].revents & (POLLOUT|POLLERR)));
}
/** @brief Close the OSS output device
* @param hard If nonzero, drop pending data
*/
static void playrtp_oss_disable(int hard) {
if(hard) {
#if !EMPEG_HOST
/* No SNDCTL_DSP_ ioctls on empeg */
if(ioctl(playrtp_oss_fd, SNDCTL_DSP_RESET, 0) < 0)
error(errno, "ioctl SNDCTL_DSP_RESET");
#endif
} else
playrtp_oss_flush();
xclose(playrtp_oss_fd);
playrtp_oss_fd = -1;
free(playrtp_oss_buffer);
playrtp_oss_buffer = 0;
}
/** @brief Write samples to OSS output device
* @param data Pointer to sample data
* @param samples Number of samples
* @return 0 on success, non-0 on error
*/
static int playrtp_oss_write(const char *data, size_t samples) {
long bytes = samples * sizeof(int16_t);
while(bytes > 0) {
int n = playrtp_oss_bufsize - playrtp_oss_bufused;
if(n > bytes)
n = bytes;
memcpy(playrtp_oss_buffer + playrtp_oss_bufused, data, n);
bytes -= n;
data += n;
playrtp_oss_bufused += n;
if(playrtp_oss_bufused == playrtp_oss_bufsize)
if(playrtp_oss_flush())
return -1;
}
next_timestamp += samples;
return 0;
}
/** @brief Play some data from packet @p p
*
* @p p is assumed to contain @ref next_timestamp.
*/
static int playrtp_oss_play(const struct packet *p) {
return playrtp_oss_write
((const char *)(p->samples_raw + next_timestamp - p->timestamp),
(p->timestamp + p->nsamples) - next_timestamp);
}
/** @brief Play some silence before packet @p p
*
* @p p is assumed to be entirely before @ref next_timestamp.
*/
static int playrtp_oss_infill(const struct packet *p) {
static const char zeros[INFILL_SAMPLES * sizeof(int16_t)];
size_t samples_available = INFILL_SAMPLES;
if(p && samples_available > p->timestamp - next_timestamp)
samples_available = p->timestamp - next_timestamp;
return playrtp_oss_write(zeros, samples_available);
}
/** @brief OSS backend for playrtp */
void playrtp_oss(void) {
int escape;
const struct packet *p;
pthread_mutex_lock(&lock);
for(;;) {
/* Wait for the buffer to fill up a bit */
playrtp_fill_buffer();
playrtp_oss_enable();
escape = 0;
info("Playing...");
/* Keep playing until the buffer empties out, we get an error */
while((nsamples >= minbuffer
|| (nsamples > 0
&& contains(pheap_first(&packets), next_timestamp)))
&& !escape) {
/* Wait until we can play more */
pthread_mutex_unlock(&lock);
playrtp_oss_wait();
pthread_mutex_lock(&lock);
/* Device is ready for more data, find something to play */
p = playrtp_next_packet();
/* Play it or play some silence */
if(contains(p, next_timestamp))
escape = playrtp_oss_play(p);
else
escape = playrtp_oss_infill(p);
}
active = 0;
/* We stop playing for a bit until the buffer re-fills */
pthread_mutex_unlock(&lock);
playrtp_oss_disable(escape);
pthread_mutex_lock(&lock);
}
}
#endif
/*
Local Variables:
c-basic-offset:2
comment-column:40
fill-column:79
indent-tabs-mode:nil
End:
*/