/* Play a tyStream given its FSID
 * Derived from PlayStream.c written by D18C7DB Jan 2001
 * released under the Gnu General Public License
 *
 *  v0.1 2001 June 6  Initial release for TiVo 2.0.1.
 *  v0.2 2001 June 13 Sequence restart fix, 
 *                    empty start block fix ("!foundfirstframe")
 *                    buffer flush at end.
 *                    increment p properly by SizeOffset.
 *
 */

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/fs.h>
#include <sys/time.h>
#include <unistd.h>
#include <asm/unistd.h>

#define VIDEO_ID        0xE0
#define AUDIO_ID        0xC0
#define CHECKSUM        0x3FC
#define MAGIC_SIGNATURE 0x91231ebc
#define CHUNK_SIZE     (0x20000)
#define BUFFER_SIZE     0x2000
#define VREADSIZ        (0x1000)        // the known readers read length, video
#define VBIGBUFSIZ      (VREADSIZ*320)  // must be a multiple of VREADSIZ 
#define AREADSIZ        (0x1000)        // the known readers read length, audio
#define ABIGBUFSIZ      (AREADSIZ*40)   // must be a multiple of AREADSIZ 
#define min(a,b) ((a) <= (b) ? (a) : (b))

char vbigbuf[VBIGBUFSIZ];  // I/O buffers
char abigbuf[ABIGBUFSIZ];

int aend = 0;   // next index to memcpy into
int astart = 0; // next index to write from
int aempty = 1; // when start catches up to end
int vend = 0;   // next index to memcpy into
int vstart = 0; // next index to write from
int vempty = 1; // when start catches up to end

static int out_video = -1;
static int out_audio = -1;

struct timeval timeout = {0, 0};

int verbose=0;
int dostdout=0;
int doPes=0;

_syscall5(int , _llseek, 
	  int, fd, 
	  unsigned long , offset_high,
	  unsigned long , offset_low,
	  loff_t *, result,
	  unsigned int, origin)

static loff_t llseek(int fd, loff_t offset, unsigned origin) {
  loff_t ofs=0;
  int ret;

  ret = _llseek(fd, offset>>32, offset&0xffffffff, &ofs, origin);
  if (ret == 0) return ofs;
  return ret;
}

static struct {
  char *dev;
  unsigned long sectors;
  int fd;
} devs[] = {
  {"/dev/hda10", 0, -1},
  {"/dev/hda11", 0, -1},
  {"/dev/hdb2", 0, -1},
  {"/dev/hdb3", 0, -1},
  {NULL, 0, -1}
};

void dump_data(unsigned char *data, unsigned long length) {
  unsigned long i, j, blocks, remainder, padding;

  blocks=length&0xfffffff0;
  remainder=length&0xf;
  padding=16-remainder;

  i=0;
  j=0;
  while(i < blocks) {
    for(j=0; j<16; j++) fprintf(stderr,"%02x ", data[i+j]);
    fprintf(stderr,"\t");
    for(j=0; j<16; j++) if ( (data[i+j]>31) && (data[i+j]<127) ) fprintf(stderr,"%c", data[i+j]); else fprintf(stderr,".");
    fprintf(stderr,"\n");
    i+=16;
  }
  for(j=i; j<length;  j++) fprintf(stderr,"%02x ", data[i+j]);
  for(j=0; j<padding; j++) fprintf(stderr," ");
  for(j=i; j<length;  j++) if ( (data[i+j]>31) && (data[i+j]<127) ) fprintf(stderr,"%c", data[i+j]); else fprintf(stderr,".");
  fprintf(stderr,"\n");
}

static void read_chunk(char buf[CHUNK_SIZE], long ofs, int out_dump) {
  int i;
  unsigned long start=0;
  for (i=0; devs[i].dev; i++) {
    if (devs[i].fd == -1) {
      devs[i].fd = open(devs[i].dev, O_RDONLY);
      if (devs[i].fd == -1) {
        fprintf(stderr,"Failed to open %s\n", devs[i].dev);
        exit(1);
      }
      ioctl(devs[i].fd, BLKGETSIZE, &devs[i].sectors);
      devs[i].sectors &= ~255;
      fprintf(stderr,"%s has 0x%08x sectors\n", devs[i].dev, devs[i].sectors);
    }
    if (ofs < start + devs[i].sectors) break;
    start += devs[i].sectors;
  }
  if (!devs[i].dev) {
    fprintf(stderr,"Failed to map sector %d\n", ofs);
    exit(1);
  }
  if (verbose) fprintf(stderr,"Mapped %x to %s/%x, seeking to %llx\n", ofs, devs[i].dev, ofs-start, (((loff_t)(ofs - start)) << 9) );

  if (llseek(devs[i].fd, (((loff_t)(ofs - start)) << 9), SEEK_SET) != (((loff_t)(ofs - start)) << 9) ) {
    fprintf(stderr,"Seek failed\n");
    exit(1);
  }
  read(devs[i].fd, buf, CHUNK_SIZE);
  if (out_dump > 0) write(out_dump, buf, CHUNK_SIZE);
  if (*(int *)buf != 0xf5467abd && dostdout)  write(STDOUT_FILENO, buf, CHUNK_SIZE);
}
#if 0 // This is an attempt to make PS streams
static int checksum(char *p) {
#if 0
  int chksum=0;
  int k;
  for (k=0; k<8; k++) chksum += p[k];
#endif
  return 1/*chksum == CHECKSUM*/;
}

static unsigned long one_type(unsigned char *buf,
			      int fd, 
			      unsigned char type, 
			      unsigned char **p, 
			      unsigned int num_recs,
			      int *i,
			      unsigned char *pes) {
  unsigned long ofs = 0;
  int size,total,j;

  do {
    total = 0;
    for (j=*i; j<num_recs && (*p)[3] == type; j++) {
      if (!checksum(*p)) {
	fprintf(stderr,"bad checksum %x, skipping rest of block.\n");
	*i = num_recs;
	break;
      }
      size = ((*p)[0]<<12 | (*p)[1])<<4 | ((*p)[2]>>4);
      if (size == 0x10) {
	(*i)++;
	(*p) += 16;
	break;
      } else if (total + size < (unsigned int)0x0fff5) {
	total += size;
	(*i)++;
	(*p) += 16;
      } else break;
    }
    if (total > 0) {
      int tot = total + 10;
      pes[4] = tot >> 8;
      pes[5] = tot & 0xff;
      write(fd, pes, 0x10);
      write(fd, &buf[ofs], total);
      ofs += total;
      if (verbose) fprintf(stderr,"%x[%08x]\n", type, total);
    } 
    if (size == 0x10) {
      if (buf[ofs+3] == type) 
	for (j=0; j<0x10; j++) pes[j] = buf[ofs+j];
      ofs += 0x10;
      size = 0; // to keep from re-entering this predicate if at and of series
    }
  } while (*i < num_recs && (*p)[3] == type);
  return ofs;
}

static void parse_chunk(int out_audio, int out_video, unsigned char buf[CHUNK_SIZE]) {
  /* each 128k chunk starts with N 16 byte records that tell you
     what sorts of things are in the chunk */
  int num_recs = buf[0];
  long unsigned int ofs = 4 + (num_recs<<4);
  unsigned char *p = &buf[4];
  static unsigned char v_pes[16];
  static unsigned char a_pes[16];
  int i = *(int *)buf;
  
  if (i == 0xf5467abd) {
    fprintf(stderr,"Bad chunk, skipping\n");
    return;
  }

  if (verbose) {
    fprintf(stderr,"Records=%x\n", num_recs);
  }

  for (i=0; i<num_recs;) {
    unsigned int type = p[3];

    if (type == VIDEO_ID) {
      ofs += one_type(&buf[ofs], out_video, VIDEO_ID, 
		      &p, num_recs, &i, v_pes);
    } else if (type == AUDIO_ID) {
      ofs += one_type(&buf[ofs], out_audio, AUDIO_ID, 
		      &p, num_recs, &i, a_pes);
    } else if (!checksum(p)) {
      fprintf(stderr,"bad checksum, skipping rest of block.\n");
      return;
    } else {
      i++;
      p += 16;
    } 
  }
}
#else

struct TyStreamHeader
{
  int type;
  int  PESHeader;
  int UnRecognized;
  int size;
  char Ts[16];
};

static void parse_chunk(int out_audio,int out_video, unsigned char
buf[CHUNK_SIZE], unsigned long TyStreamNumber)
{
  struct TyStreamHeader TyStream[0xff];

  int num_recs = (int)buf[0];
  int j;
  long int p=0;
  int i,k;
  int UnRecognized = 0; // future use
  static int foundfirstframe = 0;
  int StartingRecord = -1;
  int hCount = 0;
  static long int PredictSequence = 0;
  static int expect_more = 0;

  // Dummy in the init values (found in a normal TiVo packet)
  // since we may not find a PES header before the data.
  static unsigned char v_pes[16] = {0x00,0x00,0x01,0xe0,
				    0x00,0x00,0x8C,0x80,
				    0x07,0x21,0x00,0x13,
				    0xc3,0x83,0xff,0xff};
  static unsigned char a_pes[16] = {0x00,0x00,0x01,0xc0,
				    0x00,0x00,0x8C,0x80,
				    0x07,0x21,0x00,0x11,
				    0xe8,0xdb,0xff,0xff};
  

  if (*(int *)buf == 0xf5467abd) {
    fprintf(stderr,"Bad chunk, skipping\n");
    return;
  }
  
  if (verbose) {
    fprintf(stderr,"Records = %d\n",num_recs);
  }
  
  p = 4;
  j = 0;
  
  for (i=0; i<num_recs; i++) {

    p = i * 16 + 4;
    if (buf[p+3] ==0xC0 || buf[p+3] == 0xE0) {// filter out the unknown packets
      int PESHeader;
      
      if ((buf[p+1] == 0x01) &&
          (buf[p+2] == 0x03 || buf[p+2] == 0x06))
	PESHeader = 1;
      else PESHeader = 0;
	
      if (!foundfirstframe) {
	if (buf[p+3] == 0xE0) {
	  if (buf[p+2] == 0x8C) {// We've just passed the start of a Video header
	    foundfirstframe = 1;
	    StartingRecord = j - UnRecognized - 1; // set at the last Video header
	    hCount -= UnRecognized;
	  } else {
	    if (buf[p+5] < 0x20 && buf[p+2] < 0x50 && !PESHeader)
	      hCount++;
	  }
	}
      }      
      if (PESHeader || buf[p+5] >=0x20) { // collect Headers or Data
	memcpy(TyStream[j].Ts, (void *)&buf[p], 16);
	TyStream[j].type = buf[p+3];
	TyStream[j].UnRecognized = UnRecognized; // for future use
	TyStream[j].PESHeader = PESHeader;
	if (PESHeader) {
	  TyStream[j].size = 0x10; // PES Header size is constant
	}
	else
	  TyStream[j].size = buf[p] << 12 | buf[p+1] << 4 | buf[p+2] >> 4;
	j++;
	UnRecognized = 0;
      } else { // for future use - still need to know what these small packets are
	UnRecognized++;
	memcpy(TyStream[j].Ts, (void *)&buf[p], 16);
	TyStream[j].type = buf[p+3];
	TyStream[j].UnRecognized = UnRecognized;
	TyStream[j].PESHeader = 0;
	TyStream[j].size = buf[p] << 12 | buf[p+1] << 4 | buf[p+2] >> 4;
	j++;
      }
    }
  }      
  if (!foundfirstframe) {
    fprintf(stderr,"No first frame, skipping block\n");
    return;
  }
  for (i=StartingRecord; i<j; i++) {
    if (!TyStream[i].PESHeader) { // Header
      if (TyStream[i].type == VIDEO_ID) {
	unsigned long int TempSequence = TyStream[i].Ts[5] << 16;	
	unsigned short Tmp = TyStream[i].Ts[6] << 8;	
	TempSequence += Tmp;		  // this code is ugly
	Tmp = TyStream[i].Ts[7] << 8; // because my compiler	
	TempSequence += Tmp / 256;    // gave wrong answers 
	
	if (!PredictSequence || 
	    (TempSequence & 0xF0F0F0) == 0x205040) // start/restart
	  /*	if (!PredictSequence || 
	    TempSequence == 0x205040  || 
	    TempSequence == 0x205048,0x205640) // start/restart
	  */
	  PredictSequence = TempSequence;

	if (TempSequence!=PredictSequence && TyStream[i].Ts[5] >=0x20) {
	  fprintf(stderr,"Sequence number bogus %x!=%x at record %d of %d\n",PredictSequence,TempSequence,i,j);
	  j = i - 1;
	  break;
	}

	if (TyStream[i].Ts[5] >= 0x20 || // data
	    TyStream[i].Ts[2] == 0x42 || // marker
	    TyStream[i].Ts[2] == 0x8c)	 // frame beginning
	  PredictSequence += TyStream[i].size;
      }
    }
  }

  p = num_recs * 16 + 4; 
  if (verbose) fprintf(stderr,"Number of records to be processed: %d\n",j); 
  for (i=0; i<j; i++) {
    int SizeOffset = 0;
    if (i == StartingRecord && foundfirstframe) { // skip incomplete Video blocks
      while (buf[p] != 0 || buf[p+1] != 0 || buf[p+2] != 1) {
	p--;          // I don't know if this is correct
	SizeOffset++; // It makes bad data work ok
        if (SizeOffset > 100) { // arbitrary bail
	  fprintf(stderr,"Can't find first frame :(\n");
	  exit(1);
	}
      }
    }    
    if (TyStream[i].PESHeader) { // Header
      if (buf[p] != 0x00 && buf[p + 1] != 0x00 &&
	  buf[p + 2] != 0x01 && buf[p + 3] != TyStream[i].type) {
	
	fprintf(stderr,"Failure 0x%x:%ld:%d\n",p,TyStreamNumber,i);
	return;
      }
      if (verbose) {
	fprintf(stderr,"PES Header 0x%x:%ld:%d 0x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n",p,TyStreamNumber,buf[p + 0],buf[p + 1],buf[p + 2],buf[p + 3],buf[p + 4],buf[p + 5],buf[p + 6],buf[p + 7],buf[p + 8],buf[p + 9],buf[p + 10],buf[p + 11],buf[p + 12],buf[p + 13],buf[p + 14],buf[p + 15]);
      }
      if (doPes) {
	if (TyStream[i].type == VIDEO_ID) {
	  memcpy(v_pes, TyStream[i].Ts, 16);
	} else if (buf[p+3] == AUDIO_ID) {
	  memcpy(a_pes, TyStream[i].Ts, 16);
	}
      }
      //previousPES = 1; // future use
    } else {
      if (TyStream[i].type == VIDEO_ID) {
	if (i >= StartingRecord) {
	  if (verbose) {
	    fprintf(stderr,"Video Output 0x%x:%ld:%d:0x%x\n",p,TyStreamNumber,i,TyStream[i].size);
	  }
          if (doPes) {
	    // + 10 is to adjust for rest of PES header
            // v_pes[4] = (TyStream[i].size + SizeOffset + 10) >> 8;
            // v_pes[5] = (TyStream[i].size + SizeOffset + 10) & 0xff;
            // fwrite(v_pes, 1, 0x10, out_video);
          }

	  {
	    size_t  writesize = TyStream[i].size + SizeOffset;
	    struct timeval *to = &timeout;
	    long temp = p;
	    int docopy;
	    int maxf;
	    if (verbose) fprintf(stderr,"Video Bufferring 0x%x bytes\n",writesize);
	    while (writesize) {
	      while (1) {
		fd_set  writefds;
		FD_ZERO(&writefds);
		maxf = 0;
		docopy = (vend > vstart || vempty) ? (vend - vstart) : 
		  (VBIGBUFSIZ - vstart);
		if (docopy >= VREADSIZ) {
		  FD_SET(out_video, &writefds);
		  maxf = out_video + 1;
		}
		docopy = (aend > astart || aempty) ? (aend - astart) : 
		  (ABIGBUFSIZ - astart);
		if (docopy >= AREADSIZ) {
		  FD_SET(out_audio, &writefds);
		  if (out_audio >= maxf) maxf = out_audio + 1;
		}
		if (maxf > 0 && select(maxf, NULL, &writefds, NULL, to) > 0) {
		  to = &timeout;
		  if (FD_ISSET(out_video, &writefds)) {
  		    vstart = (vstart + write(out_video, &vbigbuf[vstart], VREADSIZ)) % VBIGBUFSIZ;
		    vempty = vstart == vend;
		  }
		  if (FD_ISSET(out_audio, &writefds)) {
		    astart = (astart + write(out_audio, &abigbuf[astart], AREADSIZ)) % ABIGBUFSIZ;
		    aempty = astart == aend;
		  }
		} else break;
	      }
	      docopy = (vend > vstart || vempty) ? 
		min(VBIGBUFSIZ-vend,writesize) : min(vstart-vend,writesize);
	      memcpy(&vbigbuf[vend], &buf[temp], docopy);
	      vempty = 0;
	      writesize -= docopy;
	      temp += docopy;
	      vend = (vend + docopy) % VBIGBUFSIZ;
	      if (writesize > 0) { // circular buffer, back to beginning?
		docopy = vend > vstart ? 
		  min(VBIGBUFSIZ-vend,writesize) : min(vstart-vend,writesize);
	        memcpy(&vbigbuf[vend], &buf[temp], docopy);
	        writesize -= docopy;
		temp += docopy;
		vend = (vend + docopy) % VBIGBUFSIZ;
	      }
	      if (writesize > 0) {
		to = NULL; // wait for an event
		if (verbose) fprintf(stderr,"Filled video, Waiting: writesiz 0x%x:0x%x vbuf 0x%x abuf 0x%x\n\n",
			writesize,VBIGBUFSIZ - vstart,
			vend >= vstart ? (vend - vstart) : 
			(VBIGBUFSIZ - vstart),
			aend >= astart ? (aend - astart) : 
			(ABIGBUFSIZ - astart));
	      }
	    }
	  }
	}
      } else if (TyStream[i].type == AUDIO_ID) {
	if (foundfirstframe && hCount) { // try and sync the audio and video
	  hCount--;
	} else {
	  if (verbose) {
	    fprintf(stderr,"Audio Output 0x%x:%ld:%d:0x%x\n",p,TyStreamNumber,i,TyStream[i].size);
	  }
	  
	  if (!expect_more && 
	      buf[p] != 0xFF && 
	      buf[p + 1] != 0xFD &&
	      buf[p + 2] != 0xA8) {
	    if (TyStream[i].size != 0) fprintf(stderr,"audio failure 0x%x:%d:0x%x 0x%x:0x%x:0x%x\n",p,i,TyStream[i].size,buf[p],buf[p+1],buf[p+2]);
	  } else {
	    if (doPes) {
	      // + 10 is to adjust for rest of PES header
	      //a_pes[4] = (TyStream[i].size + SizeOffset + 10) >> 8;
	      //a_pes[5] = (TyStream[i].size + SizeOffset + 10) & 0xff;
	      //fwrite(a_pes, 1, 0x10, out_audio);
	    }
	    if (buf[p] == 0xFF && buf[p + 1] == 0xFD &&
		buf[p+2] == 0xA8 && TyStream[i].size < 0x360) expect_more = 1;
	    else expect_more = 0;

	    {
	      size_t  writesize = TyStream[i].size + SizeOffset;
	      struct timeval *to = &timeout;
	      long temp = p;
	      int docopy;
	      int maxf;
	      if (verbose) fprintf(stderr,"Audio Bufferring 0x%x bytes\n",writesize);
	      while (writesize) {
		while (1) {
		  fd_set  writefds;
		  FD_ZERO(&writefds);
		  maxf = 0;
		  if (((vend > vstart || vempty) ? (vend - vstart) : 
		       (VBIGBUFSIZ - vstart)) >= VREADSIZ) {
		    FD_SET(out_video, &writefds);
		    maxf = out_video + 1;
		  }
		  if (((aend > astart || aempty) ? (aend - astart) : 
		       (ABIGBUFSIZ - astart)) >= AREADSIZ) {
		    FD_SET(out_audio, &writefds);
		    if (out_audio >= maxf) maxf = out_audio + 1;
		  }
		  if (maxf > 0 && select(maxf, NULL, &writefds, NULL, to) > 0) {
		    to = &timeout;
		    if (FD_ISSET(out_video, &writefds)) {
		      vstart = (vstart + write(out_video, &vbigbuf[vstart], VREADSIZ)) % VBIGBUFSIZ;
		      vempty = (vstart == vend);
		    }
		    if (FD_ISSET(out_audio, &writefds)) {
		      astart = (astart + write(out_audio, &abigbuf[astart], AREADSIZ)) % ABIGBUFSIZ;
		      aempty = (astart == aend);
		    }
		  } else break;
		}
		docopy = (aend > astart || aempty) ? 
		  min(ABIGBUFSIZ-aend,writesize) : min(astart-aend,writesize);
		memcpy(&abigbuf[aend], &buf[temp], docopy);
		aempty = 0;
		writesize -= docopy;
		temp += docopy;
		aend = (aend + docopy) % ABIGBUFSIZ;
		if (writesize > 0) {
		  docopy = aend > astart ? 
		    min(ABIGBUFSIZ-aend,writesize) : min(astart-aend,writesize);
		  memcpy(&abigbuf[aend], &buf[temp], docopy);
		  writesize -= docopy;
		  temp += docopy;
		  aend = (aend + docopy) % ABIGBUFSIZ;
		}
		if (writesize > 0) {
		  to = NULL; // wait for an event	      }
		  if (verbose) fprintf(stderr,"Filled audio, Waiting: vbuf 0x%x abuf 0x%x\n\n",
			  vend >= vstart ? (vend - vstart) : 
			  (VBIGBUFSIZ - vstart),
			  aend >= astart ? (aend - astart) : 
			  (ABIGBUFSIZ - astart));
		}
	      }
	    }
	  } 
	}
      } 
    }
    p += TyStream[i].size + SizeOffset;
  }
}

#endif

int get_tyStream_sectors(int fd, unsigned char *buffer, unsigned long fsid) {
  loff_t nsector=0;
  unsigned long i, count, sector;

  fprintf(stderr,"Attempting to locate tyStream with fsid %d...\n", fsid);
  nsector = ( ( (loff_t)(((fsid * 0x20db2) & 0x3ffff) + 0x462) ) << 9 );
  for (i=0; i<2; i++) {
    if(llseek(fd, nsector, SEEK_SET) < 0 || 
       read(fd, buffer, BUFFER_SIZE) != BUFFER_SIZE) {
      fprintf(stderr,"Failed to lseek/read from disk.\n");
      return(0);
    }
    
    if ( *((unsigned long *)(buffer + 0x2c)) != MAGIC_SIGNATURE ) {
      fprintf(stderr,"Sector fails signature check.\n");
      nsector += 0x400; 
      continue;
    }
    
    sector = *((unsigned long *)(buffer));
    if ((fsid) != (sector)) {
      fprintf(stderr,"Sector FSID passed (%lx) failed to match the FSID value in the sector (%lx).\n",  fsid, sector);
      nsector += 0x400;
    } else break;
  }
  if (i == 2) {
    dump_data(buffer,512);
    return 0;
  }
  if (buffer[0x28] != 2) {
    fprintf(stderr,"fsid value does not reference a tyStream.\n");
    dump_data(buffer,512);
    return(0);
  }
  fprintf(stderr,"...tyStream located, sector map follows:\n");
  fprintf(stderr,"Start    Length\n");
  for(i = 0; i < *((unsigned long *)(buffer + 0x38)); i++) {
    fprintf(stderr,"%08x %08x\n", *((unsigned long *)(buffer + (i<<3) + 0x3c)), *((unsigned long *)(buffer + (i<<3) + 0x40)) );
  }
  return(1);
}

int sector_scan(int fd, unsigned char *buffer, unsigned long fsid) {
  loff_t nsector=0;
  unsigned long i, count, sector;
  long temp;

  fprintf(stderr,"sector:fsid\t   Sig?\tTyStream?\n");
  for(fsid=0; ;fsid++) {
    nsector = (loff_t)(fsid << 9 );
    llseek(fd, nsector, SEEK_SET);
    if(read(fd, buffer, 256) != 256) {
      fprintf(stderr,"Failed to read from disk.\n");
      return(0);
    }
    temp = nsector;
    fprintf(stderr, "%.8d:0x%.8x\t%s\t  %s\n",
        temp,
        *((unsigned long *)(buffer)),
        (( *((unsigned long *)(buffer + 0x2c)) != MAGIC_SIGNATURE ) ? "no" : "yes"),
        ((buffer[0x28] != 2) ? "no" : "yes"));
  }
}

void media_play(unsigned char *sector_list) {
  unsigned char buf[CHUNK_SIZE];
  unsigned long block, i, index;
  signed long count;
  static int out_dump = -1;
  char *fname;
  
  if (out_video == -1) {
    if ((fname=getenv("VIDEO_OUT")) == NULL) fname = "/dev/mpeg0v";
    out_video = open(fname, O_WRONLY | O_CREAT /*| O_NONBLOCK*/);
    if (out_video < 0) {
      fprintf(stderr,"Failed to connect to Video codec.\n");
      exit(1);
    }
  }

  if (out_audio == -1) { 
    if ((fname=getenv("AUDIO_OUT")) == NULL) {
      if ((fname=getenv("VIDEO_OUT")) != NULL) {
        out_audio = out_video;
        goto gotit;
      } else {
        fname = "/dev/mpeg0a";
      }
    }
    out_audio = open(fname, O_WRONLY | O_CREAT);
gotit:
    if (out_audio < 0) {
      fprintf(stderr,"Failed to connect to Audio codec.\n");
      exit(1);
    }
  }

  if (out_dump == -1) { 
    if ((fname=getenv("DUMP_OUT")) != NULL) {
      out_dump = open(fname, O_WRONLY | O_CREAT);
      if (out_dump == -1) {
        fprintf(stderr,"Failed to open dump file\n");
        exit(1);
      }
    }
  }

  for(i = 0; i < *((unsigned long *)(sector_list + 0x38)); i++) {
    block = *( (unsigned long *) (sector_list + (i<<3) + 0x3c) );
    count = *( (unsigned long *) (sector_list + (i<<3) + 0x40) );
    fprintf(stderr,"Block: %x Count: %d Total blocks %ld\n",block,count,*((unsigned long *)(sector_list + 0x38)));
    index=0;
    while ( index <= count ) {
      fprintf(stderr,"Playing block %08lx", block+index);
      read_chunk(buf, block+index, out_dump);
      if (!dostdout) parse_chunk(out_audio, out_video, buf, index);
      if (verbose) fprintf(stderr,"\n"); else fprintf(stderr,"\r");
      index += 0x100;
      if (verbose) fprintf(stderr,"index %d count %d\n",index,count);
    }
  }
}

static void usage(void) {
  fprintf(stderr,"
ExtractStream [-v] [-s] [-d] <fsid>...

ExtractStream takes valid <fsid> numbers of the streams you want to play,
extracts the sectors allocated to that stream and attempts to play the
audio/video information on those sectors through the TiVo codecs.

-v Verbose output (stderr) 

-s Dump raw tyStream data to stdout

-d Dump sector scan info to stderr (no mpeg dumped at all)

-p Include PES headers with data (does not work -- use mplex)

If -s is not set, and AUDIO_OUT or VIDEO_OUT environmental variables
are set, then mpeg output will go to these files instead of the
codecs.  If only  VIDEO_OUT is set, then both audio and video are 
written to the file (note PS streams not yet working).

If the DUMP_OUT environmental variable is set, then the raw tyStream 
is copied to the named file.  This is in addition to writing to
stdout if -s is set, or to the files named in VIDEO_OUT or AUDIO_OUT
if they're set.

 ");
  exit(0);
}

int main(int argc, char *argv[]) {
  int fd;
  unsigned char *sector_list;
  unsigned char *blkfile;
  unsigned long fsid;
  int i = 1;
  int sectorscan = 0;
  if (argc < 2) usage();

  verbose=0; dostdout=0;
  while (argv[i][0] == '-') {
    if (argv[i][1] == 'v' || argv[i][1] == 'V') verbose=1; 
    else if (argv[i][1] == 's' || argv[i][1] == 'S') dostdout=1; 
    else if (argv[i][1] == 'd' || argv[i][1] == 'D') sectorscan=1;
    else if (argv[i][1] == 'p' || argv[i][1] == 'P') doPes = 1;
    else usage();
    i++;
  } 

  fd = open("/dev/hda10", O_RDONLY);
  if (fd == -1) {
    fprintf(stderr,"Failed to open /dev/hda10\n");
    exit(1);
  }
  
  sector_list = (unsigned char *)malloc(CHUNK_SIZE);
  if (sector_list == NULL) {
    fprintf(stderr,"Failed to allocate memory.\n");
    exit(1);
  }
  timeout.tv_sec = 0;
  timeout.tv_usec = 0;
  if (!sectorscan) {
    while (i < argc && sscanf(argv[i++], "%ld", &fsid) > 0)
      if (get_tyStream_sectors(fd, sector_list, fsid))  
	media_play(sector_list);
  } else {
    sector_scan(fd, sector_list, 0);
  }

  fprintf(stderr, "Flushing buffers\n");
  //i = 0;
  while (!aempty || !vempty) {
    struct timeval *to = NULL;
    int acopy, vcopy;
    int maxf;
    fd_set  writefds;
    FD_ZERO(&writefds);
    maxf = 0;
    if ((vcopy = ((vend > vstart || vempty) ? (vend - vstart) : 
		 (VBIGBUFSIZ - vstart))) != 0) {
      FD_SET(out_video, &writefds);
      maxf = out_video + 1;
    } else close(out_video);
    if ((acopy = ((aend > astart || aempty) ? (aend - astart) : 
		 (ABIGBUFSIZ - astart))) != 0) {
      FD_SET(out_audio, &writefds);
      if (out_audio >= maxf) maxf = out_audio + 1;
    } else close(out_audio);
#if 0
vcopy == 0) {
      to = &timeout;
      timeout.tv_usec = 50;
      i++;
      if (i > 100) {
        fprintf(stderr, "Can't get rid of all A/V data -- leaving\n");
        break; // got to leave if either are zero, since mplex might be reading video.
      }
    }
#endif 
    if (verbose) fprintf(stderr, "Still left to write: audio %d, video %d\n",acopy,vcopy);
    if (select(maxf, NULL, &writefds, NULL, to) > 0) {
      if (FD_ISSET(out_video, &writefds)) {
	vstart = (vstart + write(out_video, &vbigbuf[vstart], min(VREADSIZ,vcopy))) % VBIGBUFSIZ;
	vempty = vstart == vend;
      }
      if (FD_ISSET(out_audio, &writefds)) {
	astart = (astart + write(out_audio, &abigbuf[astart], min(AREADSIZ,acopy))) % ABIGBUFSIZ;
	aempty = astart == aend;
      }
    }  
  }
  free(sector_list);
  close(fd);
}

