owntone-server/src/laudio_oss4.c

428 lines
8.7 KiB
C

/*
* Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include "conffile.h"
#include "logger.h"
#include "player.h"
#include "laudio.h"
struct pcm_packet
{
uint8_t samples[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
uint64_t rtptime;
size_t offset;
struct pcm_packet *next;
};
static uint64_t pcm_pos;
static uint64_t pcm_start_pos;
static int pcm_buf_threshold;
static int pcm_retry;
static struct pcm_packet *pcm_pkt_head;
static struct pcm_packet *pcm_pkt_tail;
static char *card_name;
static int oss_fd;
static enum laudio_state pcm_status;
static laudio_status_cb status_cb;
static void
update_status(enum laudio_state status)
{
pcm_status = status;
status_cb(status);
}
static void
laudio_oss4_write(uint8_t *buf, uint64_t rtptime)
{
struct pcm_packet *pkt;
int scratch;
int nsamp;
int ret;
pkt = (struct pcm_packet *)malloc(sizeof(struct pcm_packet));
if (!pkt)
{
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for PCM pkt\n");
update_status(LAUDIO_FAILED);
return;
}
memcpy(pkt->samples, buf, sizeof(pkt->samples));
pkt->rtptime = rtptime;
pkt->offset = 0;
pkt->next = NULL;
if (pcm_pkt_tail)
{
pcm_pkt_tail->next = pkt;
pcm_pkt_tail = pkt;
}
else
{
pcm_pkt_head = pkt;
pcm_pkt_tail = pkt;
}
if (pcm_pos < pcm_pkt_head->rtptime)
{
pcm_pos += AIRTUNES_V2_PACKET_SAMPLES;
return;
}
else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos))
{
/* Start audio output */
scratch = PCM_ENABLE_OUTPUT;
ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not enable output: %s\n", strerror(errno));
update_status(LAUDIO_FAILED);
return;
}
update_status(LAUDIO_RUNNING);
}
pkt = pcm_pkt_head;
while (pkt)
{
nsamp = write(oss_fd, pkt->samples + pkt->offset, sizeof(pkt->samples) - pkt->offset);
if (nsamp < 0)
{
if (errno == EAGAIN)
{
pcm_retry++;
if (pcm_retry < 10)
return;
}
DPRINTF(E_LOG, L_LAUDIO, "Write error: %s\n", strerror(errno));
update_status(LAUDIO_FAILED);
return;
}
pcm_retry = 0;
pkt->offset += nsamp;
nsamp = BTOS(nsamp);
pcm_pos += nsamp;
if (pkt->offset == sizeof(pkt->samples))
{
pcm_pkt_head = pkt->next;
if (pkt == pcm_pkt_tail)
pcm_pkt_tail = NULL;
free(pkt);
pkt = pcm_pkt_head;
}
/* Don't let the buffer fill up too much */
if (nsamp == AIRTUNES_V2_PACKET_SAMPLES)
break;
}
}
static uint64_t
laudio_oss4_get_pos(void)
{
int delay;
int ret;
ret = ioctl(oss_fd, SNDCTL_DSP_GETODELAY, &delay);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not obtain output delay: %s\n", strerror(errno));
return pcm_pos;
}
return pcm_pos - BTOS(delay);
}
static void
laudio_oss4_set_volume(int vol)
{
int oss_vol;
int ret;
vol = vol & 0xff;
oss_vol = vol | (vol << 8);
ret = ioctl(oss_fd, SNDCTL_DSP_SETPLAYVOL, &oss_vol);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set volume: %s\n", strerror(errno));
return;
}
DPRINTF(E_DBG, L_LAUDIO, "Setting PCM volume to %d (real: %d)\n", vol, (oss_vol & 0xff));
}
static int
laudio_oss4_start(uint64_t cur_pos, uint64_t next_pkt)
{
int scratch;
int ret;
DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES);
/* Make pcm_pos the rtptime of the packet containing cur_pos */
pcm_pos = next_pkt;
while (pcm_pos > cur_pos)
pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES;
pcm_start_pos = next_pkt + pcm_buf_threshold;
/* FIXME check for OSS - Compensate threshold, as it's taken into account by snd_pcm_delay() */
pcm_pos += pcm_buf_threshold;
DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos);
pcm_pkt_head = NULL;
pcm_pkt_tail = NULL;
pcm_retry = 0;
scratch = 0;
ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno));
return -1;
}
update_status(LAUDIO_STARTED);
return 0;
}
static void
laudio_oss4_stop(void)
{
struct pcm_packet *pkt;
int ret;
update_status(LAUDIO_STOPPING);
ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
if (ret < 0)
DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno));
for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
{
pcm_pkt_head = pkt->next;
free(pkt);
}
pcm_pkt_head = NULL;
pcm_pkt_tail = NULL;
update_status(LAUDIO_OPEN);
}
static int
laudio_oss4_open(void)
{
audio_buf_info bi;
oss_sysinfo si;
int scratch;
int ret;
oss_fd = open(card_name, O_RDWR | O_NONBLOCK);
if (oss_fd < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno));
return -1;
}
ret = ioctl(oss_fd, SNDCTL_SYSINFO, &si);
if ((ret < 0) || (si.versionnum < 0x040000))
{
DPRINTF(E_LOG, L_LAUDIO, "Your OSS version (%s) is unavailable or too old; version 4.0.0+ is required\n", si.version);
goto out_fail;
}
scratch = 0;
ret = ioctl(oss_fd, SNDCTL_DSP_SETTRIGGER, &scratch);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Could not set trigger: %s\n", strerror(errno));
goto out_fail;
}
scratch = AFMT_S16_LE;
errno = 0;
ret = ioctl(oss_fd, SNDCTL_DSP_SETFMT, &scratch);
if ((ret < 0) || (scratch != AFMT_S16_LE))
{
if (errno)
DPRINTF(E_LOG, L_LAUDIO, "Could not set sample format (S16 LE): %s\n", strerror(errno));
else
DPRINTF(E_LOG, L_LAUDIO, "Sample format S16 LE not supported\n");
goto out_fail;
}
scratch = 2;
errno = 0;
ret = ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &scratch);
if ((ret < 0) || (scratch != 2))
{
if (errno)
DPRINTF(E_LOG, L_LAUDIO, "Could not set stereo: %s\n", strerror(errno));
else
DPRINTF(E_LOG, L_LAUDIO, "Stereo not supported\n");
goto out_fail;
}
scratch = 44100;
errno = 0;
ret = ioctl(oss_fd, SNDCTL_DSP_SPEED, &scratch);
if ((ret < 0) || (scratch != 44100))
{
if (errno)
DPRINTF(E_LOG, L_LAUDIO, "Could not set speed (44100): %s\n", strerror(errno));
else
DPRINTF(E_LOG, L_LAUDIO, "Sample rate 44100 not supported\n");
goto out_fail;
}
ret = ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &bi);
if (ret < 0)
{
DPRINTF(E_LOG, L_LAUDIO, "Couldn't get output buffer status: %s\n", strerror(errno));
goto out_fail;
}
pcm_buf_threshold = (BTOS(bi.bytes) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES;
update_status(LAUDIO_OPEN);
return 0;
out_fail:
close(oss_fd);
oss_fd = -1;
return -1;
}
static void
laudio_oss4_close(void)
{
struct pcm_packet *pkt;
int ret;
ret = ioctl(oss_fd, SNDCTL_DSP_HALT_OUTPUT, NULL);
if (ret < 0)
DPRINTF(E_LOG, L_LAUDIO, "Failed to halt output: %s\n", strerror(errno));
close(oss_fd);
oss_fd = -1;
for (pkt = pcm_pkt_head; pcm_pkt_head; pkt = pcm_pkt_head)
{
pcm_pkt_head = pkt->next;
free(pkt);
}
pcm_pkt_head = NULL;
pcm_pkt_tail = NULL;
update_status(LAUDIO_CLOSED);
}
static int
laudio_oss4_init(laudio_status_cb cb, cfg_t *cfg_audio)
{
status_cb = cb;
card_name = cfg_getstr(cfg_audio, "card");
return 0;
}
static void
laudio_oss4_deinit(void)
{
/* EMPTY */
}
audio_output audio_oss4 = {
.name = "oss4",
.init = &laudio_oss4_init,
.deinit = &laudio_oss4_deinit,
.start = &laudio_oss4_start,
.stop = &laudio_oss4_stop,
.open = &laudio_oss4_open,
.close = &laudio_oss4_close,
.pos = &laudio_oss4_get_pos,
.write = &laudio_oss4_write,
.volume = &laudio_oss4_set_volume,
};