/*
*  ximp3  A simple mp3 player
*
*  Copyright (C) 2001 Mats Peterson
*
*  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; see the file COPYING.  If not, write to
*  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
*  Boston, MA 02111-1307, USA.
*
*  Please send any comments/bug reports to
*  [email protected]  (Mats Peterson)
*/

/*
*  Audio output thread
*/

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include "xingmp3.h"
#include "ximp3.h"
#include "init.h"
#include "audio.h"
#include "info.h"


static void err_exit(void)
{
   exit(1);
}


static void out_stats(int frames, int in_bytes, int out_bytes)
{
   double sec;
   int unplayed;

   if (ioctl(v->audio_fd, SNDCTL_DSP_GETODELAY, &unplayed) == -1) {
       perror("SNDCTL_DSP_GETODELAY");
       err_exit();
   }
   sec = (double)(out_bytes - unplayed) / (double)v->bytes_sec;

   if (v->remote)
       printf("@F %d %.2f\n", frames, sec);
   else {
       printf("\r Frames %6d   Time %02d:%02d.%02d   "
               "Bytes In %6d   Bytes Out %6d",
               frames, (int)(sec / 60), (int)sec % 60,
               (int)(sec * 100) % 100, in_bytes, out_bytes - unplayed);
       fflush(stdout);
   }
}


static int read_buffer(void)
{
   static char *p = NULL;
   static int toread = sizeof(PLAYBUF);
   fd_set readfds;
   struct timeval timeout;
   int n;

   if (v->n_playbufs == v->tot_playbufs) {
       if (! v->playing)
           v->playing = 1;
       return 0;
   }

/* read a full buffer */
   do {
       FD_ZERO(&readfds);
       FD_SET(v->pfds[0], &readfds);
       timeout.tv_sec = 0;
       timeout.tv_usec = 0;
       if (! (n = select(v->pfds[0] + 1, &readfds, NULL, NULL, &timeout)))
           return 0;
       if (n < 0) {
           perror("select");
           err_exit();
       }
       if (! p)
           p = (char *)&(v->playbufs[v->addbuf]);
       if ((n = read(v->pfds[0], p, toread)) < 0) {
           perror("pipe read");
           err_exit();
       }
       p += n;
       toread -= n;
   } while (toread);

   v->n_playbufs++;
/* if last buffer, play remaining buffers now */
   if (v->playbufs[v->addbuf].size == 0) {
       v->playing = 1;
       goto set_p;
   }
   v->addbuf = (v->addbuf == (v->tot_playbufs - 1)) ? 0 : v->addbuf + 1;

set_p:
   p = (char *)&(v->playbufs[v->addbuf]);
   toread = sizeof(PLAYBUF);
   return 1;
}


static int play_buffer(void)
{
   static char *p = NULL;
   static int towrite;
   fd_set writefds;
   struct timeval timeout;
   static int out_bytes = 0;
   audio_buf_info info;
   int n;

   if (! v->playing)
       return 1;

/* last buffer */
   if (v->playbufs[v->playbuf].size == 0) {
       if (v->verbose || v->remote) {
           int now_bytes = 0;
           while (1) {
               if (ioctl(v->audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) {
                   perror("SNDCTL_DSP_GETOSPACE");
                   err_exit();
               }
               if ((info.bytes - now_bytes) >= PCM_BUFBYTES) {
                   now_bytes = info.bytes;
                   out_stats(v->playbufs[v->playbuf].frames,
                           v->playbufs[v->playbuf].in_bytes, out_bytes);
               }
               /* Break if audio buffer empty */
               if (info.bytes == v->audio_bufsize) {
                   out_stats(v->playbufs[v->playbuf].frames,
                           v->playbufs[v->playbuf].in_bytes, out_bytes);
                   break;
               }
               usleep(1000);
           }
       }
       close(v->audio_fd);
       if (v->verbose && (! v->remote))
           printf("\n");
       v->n_playbufs = 0;
       v->playing = 0;
       out_bytes = 0;
       p = NULL;
       return 0;
   }

/* write a full buffer */
   do {
       FD_ZERO(&writefds);
       FD_SET(v->audio_fd, &writefds);
       timeout.tv_sec = 0;
       timeout.tv_usec = 0;
       if (! (n = select(v->audio_fd + 1, NULL, &writefds, NULL, &timeout)))
           return 1;
       if (n < 0) {
           perror("select");
           err_exit();
       }
       if (! p) {
           p = v->playbufs[v->playbuf].pcm;
           towrite = v->playbufs[v->playbuf].size;
       }
       if ((n = write(v->audio_fd, p, towrite)) < 0) {
           fprintf(stderr, "DSP write error");
           err_exit();
       }
       p += n;
       towrite -= n;
   } while (towrite);

   out_bytes += v->playbufs[v->playbuf].size;
   if (v->verbose || v->remote) {
       out_stats(v->playbufs[v->playbuf].frames,
               v->playbufs[v->playbuf].in_bytes, out_bytes);
   }
   v->playbuf = (v->playbuf == (v->tot_playbufs - 1)) ? 0 : v->playbuf + 1;
   v->n_playbufs--;
   if (! v->n_playbufs) {
       v->playing = 0;
       goto null_p;
   }
   p = v->playbufs[v->playbuf].pcm;
   towrite = v->playbufs[v->playbuf].size;
   return 1;

null_p:
   p = NULL;
   return 1;
}


void * audio_thread(void *arg)
{
   while (1) {
       while (read_buffer());
       if (! play_buffer())
           return NULL;
       usleep(1000);
   }
}