/*****************************************************************************/

/*
 *      rtbuf.c  --  Linux realtime audio output.
 *
 *      Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *        Swiss Federal Institute of Technology (ETH), Electronics Lab
 *
 *      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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *  This is the Linux realtime sound output driver
 */

/*****************************************************************************/

#include "rtbuf.h"
#ifdef LINUX_REALTIME
 
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <sched.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/user.h>
      
#include "amp.h"
#include "transform.h"
#include "audio.h"
#include "getbits.h"
#include "layer3.h"

static int audio_fd;
static struct audio_buf_info abinfo;
static caddr_t abuf;
static unsigned int abuf_wptr;
static unsigned int abuf_sz;

#define IBUFSZ 65520

typedef volatile int atomic_t;

static struct ibuf {
	atomic_t rdptr;
	atomic_t wrptr;
	unsigned char buf[IBUFSZ];
} *ibuf;

#define atomic_set(x,y) x = y
#define atomic_read(x) (x)

static pid_t bufproc;

static int snd_eof;

/* --------------------------------------------------------------------- */

static int ld(unsigned int i)
{
	int ret = 0;
	
	if (!i)
		return -1;
	if (i >= 0x10000LU) {
		i >>= 16;
		ret += 16;
	}
	if (i >= 0x100LU) {
		i >>= 8;
		ret += 8;
	}
	if (i >= 0x10LU) {
		i >>= 4;
		ret += 4;
	}
	if (i >= 0x4LU) {
		i >>= 2;
		ret += 2;
	}
	return (i >= 2) ? ret + 1 : ret;
}

/* --------------------------------------------------------------------- */

void get_input(unsigned char *bp, int bytes)
{
	unsigned int rd, wr, rem, u;
	int stat;
	pid_t ret;

	rd = atomic_read(ibuf->rdptr);
	wr = atomic_read(ibuf->wrptr);
	rem = (IBUFSZ+wr-rd) % IBUFSZ;
	if (bytes > rem) {
		ret = wait4(bufproc, &stat, WNOHANG, NULL);
		if (ret != (pid_t)-1 && ret == bufproc) {
			bufproc = (pid_t) -1;
#if 0
			printf("Terminating");
#endif
		} else 
			fprintf(stderr, "get_input: Underrun");
		snd_eof = 1;
		memset(bp, 0, bytes);
		return;
	}
	u = IBUFSZ-rd;
	if (bytes >= u) {
		memcpy(bp, ibuf->buf+rd, u);
		bp += u;
		rd = 0;
		bytes -= u;
	}
	if (bytes > 0) {
		memcpy(bp, ibuf->buf+rd, bytes);
		rd += bytes;
	}
	atomic_set(ibuf->rdptr, rd);
}

/* --------------------------------------------------------------------- */

static void child(char *file)
{
	int fd, i;
	unsigned int rd, wr, free;

	/*
	 * release superuser priority
	 */
	setreuid(getuid(), getuid());	
	/*
	 * open file
	 */
	if ((fd = open(file, O_RDONLY, 0)) < 0) {
		fprintf(stderr, "Cannot open file \"%s\"\n", file);
		return;
	}
	for (;;) {
		rd = atomic_read(ibuf->rdptr);
		wr = atomic_read(ibuf->wrptr);
		free = (IBUFSZ + rd - wr - 1) % IBUFSZ;
		if (free <= 0) {
			usleep(100000L);
		} else {
			if (free > 4096)
				free = 4096;
			i = read(fd, ibuf->buf+wr, 
				 (wr + free > IBUFSZ) ? IBUFSZ-wr : free);
			if (i == -1) {
				perror("read");
				close(fd);
				return;
			}
			if (i == 0) 
				break;
			wr += i;
			if (wr >= IBUFSZ)
				wr = 0;
			atomic_set(ibuf->wrptr, wr);
#if 0
			fprintf(stderr, "buffer fill: wr: %d rd: %d\n", wr, rd);
			fflush(stderr);
#endif
		}
	}
	close(fd);
}

/* --------------------------------------------------------------------- */

static int decoder_process(void)
{
	int apar, i;
	struct count_info cinfo;
	struct sched_param schp;
	struct fd_set wmask;
	struct timeval tm;
	struct AUDIO_HEADER mpegheader;
	unsigned int frame_cnt = 1;
	unsigned int nfrags = 0;
	uid_t my_uid = getuid();
	
	/*
	 * set the process to realtime privs
	 */
        memset(&schp, 0, sizeof(schp));
	schp.sched_priority = sched_get_priority_min(SCHED_RR);
#if 1
	if (sched_setscheduler(0, SCHED_RR, &schp) != 0) {
		perror("sched_setscheduler");
		return -1;
	}
#endif
	/*
	 * release superuser priority
	 */
	setreuid(my_uid, my_uid);	
	/*
	 * open the audio device
	 * Don't use O_WRONLY, as the following mmap will fail with permission error!
	 */
	if ((audio_fd = open("/dev/dsp", O_RDWR, 0)) < 0) {
		perror("open /dev/dsp");
		return -1;
	}
	/*
	 * wait for MPEG buffer to fill
	 */
	while (atomic_read(ibuf->wrptr) < IBUFSZ/2) {
		if (wait4(bufproc, &i, WNOHANG, NULL) == bufproc) 
			return -1;
		usleep(100000L);
	}

	/* this code changed to work with new audio.c 
	if (processHeader(&mpegheader, 0))
		return -1;
	*/
	if (gethdr(&mpegheader)!=0) return -1;
	if (mpegheader.protection_bit==0) getcrc();

	/*
	 * configure audio
	 */
	apar = AFMT_S16_LE;
	if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_SETFMT");
		return -1;
	}
	if (apar != AFMT_S16_LE) {
		fprintf(stderr, "16 bit format not supported\n");
		return -1;
	}
	apar = (mpegheader.mode != 3);
	if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_STEREO");
		return -1;
	}
	if (apar != (mpegheader.mode != 3)) {	
		fprintf(stderr, "%s not supported\n", 
			(mpegheader.mode != 3) ? "stereo" : "mono");
		return -1;
	}
	apar = t_sampling_frequency[mpegheader.ID][mpegheader.sampling_frequency];
	if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_SPEED");
		return -1;
	}
	if (apar != t_sampling_frequency[mpegheader.ID][mpegheader.sampling_frequency]) {
		fprintf(stderr, "Warning: requested sampling rate %d does not "
			"match effective %d\n", 
			t_sampling_frequency[mpegheader.ID][mpegheader.sampling_frequency],
			apar);
	}
	/* volume A_SET_VOLUME */
	/*
	 * determine if the driver is capable to support our access method
	 */
	if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_GETCAPS");
		return -1;
	}
	if (!(apar & DSP_CAP_TRIGGER) || !(apar & DSP_CAP_MMAP)) {
		fprintf(stderr, "Sound driver does not support mmap and/or trigger\n");
		return -1;
	}
	/*
	 * set fragment sizes
	 */
	apar = 0xffff000d;
	if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_SETFRAGMENT");
		return -1;
	}
	if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &abinfo) == -1) {
		perror("ioctl: SNDCTL_DSP_GETOSPACE");
		return -1;
	}
	abuf_sz = abinfo.fragstotal * abinfo.fragsize; 
#if 0
	fprintf(stderr, "OSS: # fragments: %d  fragment size: %d  total buffer size: %d\n",
		abinfo.fragstotal, abinfo.fragsize, abuf_sz);
#endif
	/*
	 * mmap buffer
	 * BSD people attention: you may need to uncomment the PROT_READ
	 * feedback welcome: sailer@ife.ee.ethz.ch
	 */
	if ((abuf = mmap(NULL, abuf_sz, PROT_WRITE /* | PROT_READ*/, MAP_FILE | MAP_SHARED, 
	                 audio_fd, 0)) == (caddr_t)-1) {
		perror("mmap");
		return -1;
	}
	memset(abuf, 0, abinfo.fragstotal * abinfo.fragsize);
	/* 
	 * prefill buffer
	 */
	abuf_wptr = 0;
	snd_eof = 0;
	if (layer3_frame(&mpegheader, 0)) {
		warn(" error. blip.\n");
		return -1;
	}
	cinfo.ptr = 0;
	/*
	 * start audio device
	 */
	apar = 0;
	if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_SETTRIGGER");
		return -1;
	}
	apar = PCM_ENABLE_OUTPUT;
	if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &apar) == -1) {
		perror("ioctl: SNDCTL_DSP_SETTRIGGER");
		return -1;
	}
	/*
	 * decoder loop
	 */	
	while (!snd_eof) {
		while (!snd_eof && (i = (abuf_sz + cinfo.ptr - abuf_wptr - 1) % abuf_sz) >= (2*sizeof sample_buffer)) {
			if (gethdr(&mpegheader)!=0) {
				snd_eof = 1;
				break;
			}
			if (mpegheader.protection_bit==0) getcrc();
			statusDisplay(&mpegheader, frame_cnt);   
			if (layer3_frame(&mpegheader, frame_cnt)) {
				warn(" error. blip.\n");
				return -1;
			} 
			frame_cnt++;
		}
		FD_ZERO(&wmask);
		FD_SET(audio_fd, &wmask);
		tm.tv_sec = 10;
		tm.tv_usec = 0;
		i = select(audio_fd+1, NULL, &wmask, NULL, &tm);
		if (i < 0) {
			perror("select");
			return -1;
		}
		if (i == 0) {
			fprintf(stderr, "Sound output: timeout\n");
			return -1;
		}
		if (ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &cinfo) == -1) {
			perror("ioctl: SNDCTL_DSP_GETOPTR");
			return -1;
		}
		nfrags += cinfo.blocks;
#if 0
		fprintf(stderr, "# of frags: %d ptr: %d\n", nfrags, cinfo.ptr);
		fflush(stderr);
#endif
	}
	return 0;
}

/* --------------------------------------------------------------------- */

int rt_play(char *file)
{
	pid_t playproc;
	int i;

	/*
	 * this fork enables us to run both the buffering
	 * process and the decoding/play process to run at
	 * user privileges while retaining superuser privileges
	 * for subsequent files. This should minimize security
	 * problems
	 */
	switch (playproc = fork()) {
	case -1:
		perror("fork");
		return -1;

	case 0:
		break;

	default:
#if 0
		fprintf(stderr, "Play task PID: %d\n", playproc);
		fflush(stderr);
#endif
		wait4(playproc, &i, 0, NULL);
		return i;
	}
	/*
	 * allocate the communication buffer between the
	 * prefetch and the decode processes
	 */
#if 0
	ibuf = (struct ibuf *)mmap(NULL, sizeof(struct ibuf), PROT_READ|PROT_WRITE,
	                           MAP_SHARED|MAP_ANON, -1, 0);
	if (ibuf == (struct ibuf *)-1) {
		perror("mmap: shared anonymous memory");
		exit(-1);
	}
#else
        if ((i = shmget(IPC_PRIVATE, /*SHMLBA*/sizeof(struct ibuf), 
			IPC_CREAT | IPC_EXCL | 0600)) < 0) {
		perror("shmget");
		exit(-1);
	}
        if ((ibuf = (struct ibuf *)shmat(i, NULL, 0)) == (struct ibuf *)-1) {
		perror("shmat");
		exit(-1);
	}
        if (shmctl(i, IPC_RMID, NULL)) {
		perror("shmctl");
		exit(-1);
	}
#endif
	atomic_set(ibuf->rdptr, 0);
	atomic_set(ibuf->wrptr, 0);
	/*
	 * lock memory down
	 */
	if (mlockall(MCL_CURRENT) == -1) 
		perror("mlockall");
	/*
	 * fork the prefetch process
	 */
	switch (bufproc = fork()) {
	case 0:
		child(file);
		exit(0);
	
	case -1:
		perror("fork");
		if (munlockall() == -1)
			perror("munlockall");
		exit(-1);
		
	default:
#if 0
		fprintf(stderr, "Buffering task PID: %d\n", bufproc);
		fflush(stderr);
#endif
		audio_fd = -1;
		decoder_process();
		if (audio_fd != -1)
			close(audio_fd);
		if (bufproc != (pid_t)-1) {
			if (kill(bufproc, SIGTERM) == -1)
				perror("kill");
			if (wait4(bufproc, NULL, 0, NULL) == (pid_t)-1)
				perror("wait4");
		}
		break;
	}
	if (munlockall() == -1)
		perror("munlockall");
	exit(0);
}

/* --------------------------------------------------------------------- */

void rt_printout(short *sbuf, int ln)
{
	int rem = abuf_sz - abuf_wptr;
	
	if (ln >= rem) {
		memcpy((void *)(abuf + abuf_wptr), sbuf, rem);
		(unsigned char *)sbuf += rem;
		abuf_wptr = 0;
		ln -= rem;
	}
	if (ln > 0) {
		memcpy((void *)(abuf + abuf_wptr), sbuf, ln);
		abuf_wptr += ln;
	}
}
	
/* --------------------------------------------------------------------- */
#endif /* LINUX_REALTIME */
