/* * Copyright (C) 2010 Julien BLACHE * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #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); } void laudio_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; } } uint64_t laudio_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); } void laudio_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)); } int laudio_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; } void laudio_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); } int laudio_open(void) { audio_buf_info bi; 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; } 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; } void laudio_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); } int laudio_init(laudio_status_cb cb) { oss_sysinfo si; int ret; status_cb = cb; pcm_status = LAUDIO_CLOSED; card_name = cfg_getstr(cfg_getsec(cfg, "audio"), "card"); oss_fd = open(card_name, O_RDWR); if (oss_fd < 0) { DPRINTF(E_FATAL, L_LAUDIO, "Could not open sound device: %s\n", strerror(errno)); return -1; } ret = ioctl(oss_fd, SNDCTL_SYSINFO, &si); close(oss_fd); oss_fd = -1; if (ret < 0) { DPRINTF(E_FATAL, L_LAUDIO, "Could not check OSS version: %s\n", strerror(errno)); return -1; } if (si.versionnum < 0x040000) { DPRINTF(E_FATAL, L_LAUDIO, "Your OSS version (%s) is too old; version 4.0.0+ is required\n", si.version); return -1; } return 0; } void laudio_deinit(void) { /* EMPTY */ }