/*

    Small program for testing scheduling latencies.

    Copyright (C) 2002 Jussi Laako

*/


#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include <sched.h>
#include <pthread.h>


/* this is considered safe, loss should never happen (92.8 msecs) */
/*#define BUF_SIZE    8192*/
/* this should work in all systems without loss (23 msecs) */
/*#define BUF_SIZE    2048*/
/* this is heavier but shouldn't cause loss (5.8 msecs) */
/*#define BUF_SIZE    512*/
/* lowlatency systems are be able to do this without loss (2.9 msecs) */
#define BUF_SIZE    256

#define AUDIO_DEV   "/dev/dsp"
/*#define AUDIO_DEV   "/dev/dsp9"*/


volatile int run = 1;
volatile int eventcntr = 0;
signed short shared_buf[BUF_SIZE];
pthread_mutex_t mtx;
pthread_cond_t cond;


void set_scheduling ()
{
    uid_t current_uid;
    struct sched_param scheduler_params;

    current_uid = getuid();
    setuid(0);
    scheduler_params.sched_priority = sched_get_priority_min(SCHED_FIFO);
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, 
        &scheduler_params) != 0)
        perror("pthread_setschedparam()");
    setuid(current_uid);
}


void *producer_thread (void *param)
{
    int h = (int) param;
    signed short buf[BUF_SIZE];

    set_scheduling();

    while (run)
    {
        if (read(h, buf, sizeof(buf)) != (int) sizeof(buf))
            perror("read()");
        pthread_mutex_lock(&mtx);
        memcpy(shared_buf, buf, BUF_SIZE * sizeof(signed short));
        eventcntr++;
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mtx);
    }

    return NULL;
}


void *consumer_thread (void *param)
{
    int h = (int) param;
    int local_eventcntr;
    signed short buf[BUF_SIZE];

    set_scheduling();

    pthread_mutex_lock(&mtx);
    local_eventcntr = eventcntr;
    pthread_mutex_unlock(&mtx);
    while (run)
    {
        pthread_mutex_lock(&mtx);
        pthread_cond_wait(&cond, &mtx);
        memcpy(buf, shared_buf, BUF_SIZE * sizeof(signed short));
        local_eventcntr++;
        if (local_eventcntr != eventcntr)
        {
            printf("thread %u lost %i blocks\n", 
                pthread_self(),
                eventcntr - local_eventcntr);
            local_eventcntr = eventcntr;
        }
        pthread_mutex_unlock(&mtx);
        if (write(h, buf, sizeof(buf)) != (int) sizeof(buf))
            perror("write()");
    }
    return NULL;
}


void sig_handler (int signo)
{
    switch (signo)
    {
        case SIGINT:
            run = 0;
            break;
    }
}


int open_audio ()
{
    int devh;
    int fmt = AFMT_S16_NE;
    int chs = 2;
    int spd = 44100;
    int fragsize;
    int fragsetting;

    devh = open(AUDIO_DEV, O_RDONLY);
    if (devh < 0)
    {
        perror("open()");
        return -1;
    }

    if (ioctl(devh, SNDCTL_DSP_SETFMT, &fmt) < 0)
        perror("ioctl(SNDCTL_DSP_SETFMT)");
    if (ioctl(devh, SNDCTL_DSP_CHANNELS, &chs) < 0)
        perror("ioctl(SNDCTL_DSP_CHANNELS)");
    if (ioctl(devh, SNDCTL_DSP_SPEED, &spd) < 0)
        perror("ioctl(SNDCTL_DSP_SPEED)");

    fragsize = BUF_SIZE * sizeof(signed short);
    fragsetting = (0x7fff0000 | (fragsize & 0xffff));
    if (ioctl(devh, SNDCTL_DSP_SETFRAGMENT, &fragsetting) < 0)
        perror("ioctl(SNDCTL_DSP_SETFRAGMENT)");
    if (ioctl(devh, SNDCTL_DSP_GETBLKSIZE, &fragsize) < 0)
        perror("ioctl(SNDCTL_DSP_GETBLKSIZE)");
    printf("fragment size: %i\n", fragsize);
    if (fragsize > (BUF_SIZE * sizeof(signed short)))
        puts("warning, hardware fragment size larger than requested");

    return devh;
}


int open_null ()
{
    int devh;

    devh = open("/dev/null", O_WRONLY);
    if (devh < 0)
        perror("open()");

    return devh;
}


int main (int argc, char *argv[])
{
    int audio_h;
    int null_h1;
    int null_h2;
    int recv_sig;
    sigset_t sigset_wait;
    pthread_t tid_producer;
    pthread_t tid_consumer1;
    pthread_t tid_consumer2;

    signal(SIGINT, sig_handler);
    sigemptyset(&sigset_wait);
    sigaddset(&sigset_wait, SIGINT);

    audio_h = open_audio();
    if (audio_h < 0)
        return 1;

    null_h1 = open_null();
    if (null_h1 < 0)
        return 1;

    null_h2 = open_null();
    if (null_h2 < 0)
        return 1;

    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&tid_producer, NULL, producer_thread, (void *) audio_h);
    pthread_create(&tid_consumer1, NULL, consumer_thread, (void *) null_h1);
    pthread_create(&tid_consumer2, NULL, consumer_thread, (void *) null_h2);

    sigwait(&sigset_wait, &recv_sig);
    run = 0;

    pthread_join(tid_consumer1, NULL);
    pthread_join(tid_consumer2, NULL);
    pthread_join(tid_producer, NULL);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mtx);

    close(audio_h);
    close(null_h1);
    close(null_h2);

    return 0;
}
