ConvertStream.c

/* [<][>]
[^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following functions.
  1. main
  2. convert_stream
  3. parse_chunk
  4. usage

/* Convert a tyStream file into separate mpeg2 audio and video stream.
 * 
 * Gary Steele <gsteele@mit.edu>, 21 June 2001
 *
 * Adapted from Playstream.c by D18C7DB Jan 2001
 * 
 * released under the Gnu General Public License
 *
 * Thinking:
 *
 * ExtractStream could not extract proper video and audio stream from
 * my TiVo (Philips HDR112, software version 1.3.0), although it was
 * able to extract the raw tyStream with no errors using the -s
 * option.
 *
 * Extracting the tyStream and converting it to mpeg data is really
 * two separate steps (read_chunk, parse_chunk).
 *
 * This simple code is desinged to do only the second operation.
 *
 * The only significant modification is that some of the "records"/
 * subchunks extracted in parrse_chunk were found not to actually be
 * part of the video and audio streams.
 *
 * The resulting m2a file plays with no errors with mpg123, and the
 * resulting m2v file with no errors using the libmpeg2 library
 * (mplayer, or mpeg2dec, see linuxvideo.org). The m2v file still does
 * not play properly using the libsmpeg 0.4.3 library (plaympeg, gtv):
 * gets the video size, frame rate, and time right, but displays only
 * a black screen.
 *
 * Multiplex these together into an MPEG2 Program stream using mplex
 * with the -f 3 (mpeg2) format option:
 *
 * mplex -f 3 -o test.mpg test.m2a test.m2v
 *
 * The resulting mpg file will play properly with mplayer including
 * nicely synchronized audio and video streams.
 *
 * Untested using a tyStream from TiVO software version 2.0
 *
 * Many thanks to the authors of PlayStream.c and
 * ExtractStream.c.
 *
 *  version 0.1: 21 June 2001 initial release
 *  */

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/fs.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 PERMS 0666

// Global variabls

int audio_offset = 0;
int video_offset = 0;
int verbose=0;

// Function declarations

void convert_stream(int stream_in, int video_out, int audio_out);
static void usage(void);
static void parse_chunk(unsigned char buf[CHUNK_SIZE], int video_out, int audio_out);

// Main

int main(int argc, char *argv[]) {
/* [<][>][^][v][top][bottom][index][help] */

  int audio_out, video_out, stream_in;

  if (argc < 4) usage();

  if (argc > 4) {
    if (argv[4][0]=='-') {
      if (argv[4][1]=='v') verbose=1;
      else usage();
    }
    else usage();
  }
  

  if (verbose) printf( "Opening file: %s\n", argv[1]);
  stream_in = open(argv[1], O_RDONLY);
  if (stream_in == -1) {
    printf("Failed to open input file %s\n", argv[1]);
    exit(1);
  }

  if (verbose) printf("Opening file: %s\n", argv[2]);
  audio_out = creat(argv[2], PERMS);
  if (audio_out == -1) {
    printf("Failed to open output file %s\n", argv[2]);
    exit(1);
  } 
  
  if (verbose) printf( "Opening file: %s\n", argv[3]);
  video_out = creat(argv[3], PERMS);
  if (video_out == -1) {
    printf("Failed to open output file %s\n", argv[3]);
    exit(1);
  }


  convert_stream(stream_in, video_out, audio_out);

  close(stream_in);
  close(video_out);
  close(audio_out);
}

void convert_stream(int stream_in, int video_out, int audio_out) {
/* [<][>][^][v][top][bottom][index][help] */
  unsigned char buf[CHUNK_SIZE];
  unsigned long index, i;
  int n;
  
  while ( n = read(stream_in, buf, CHUNK_SIZE) > 0 ) {
    //    dbug(printf( "%i bytes read from input\n", n));
    parse_chunk(buf, video_out, audio_out);
  }
}

static void parse_chunk(unsigned char buf[CHUNK_SIZE], int video_out, int audio_out) {
/* [<][>][^][v][top][bottom][index][help] */
  int num_recs = buf[0];
  int i,j,chksum, ret;
  unsigned char *p;
  int total_misc=0;
  int total_av=0;
  long int ofs=0;

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

  if (verbose) {
    int j = 0;
    int l = 4;
    int k = 4 + (num_recs<<4);
    printf("Chunk output:\n");
    for (i=0; i<l; i++)  // the packet header bit, tells us how many records
      printf(" %02x", buf[i]);
    printf("\n");
    for (i=l; i < k; i++) {  // the record header, tell us what's in the record
      printf(" %02x", buf[i]); // and how big it is, anything else?
      if (j == 15) { printf("\n"); j=0; }
      else j++;
    }
    printf("\n");
    for (i=k ; i<k+200; i++) {
      printf(" %02x", buf[i]);
      if (j == 15) {
        printf("\n");
        j=0;
      }
      else j++;
    }
    printf("\n");
  }

  /* each 128k chunk starts with N 16 byte record headers that tell you
     what sorts of things are in the chunk */
  p = &buf[4];
  ofs = 4 + (num_recs<<4);

  for (i=0; i<num_recs; i++) {
    unsigned long size = (p[0]<<8 | p[1])<<4 | (p[2]>>4);
    unsigned int type = p[3];
    int k;

    chksum=0;
    for (j=0; j<8; j++) chksum += p[j];
    if (chksum != CHECKSUM) {
      if (verbose) printf("bad checksum, skipping block.\n");
      return;
    }
    if (verbose) printf("\nRecord %x: type %02x size %-x\n", i, type, size);

    p += 16;

    if (type == VIDEO_ID) {
      int j = 0; 
      if (verbose) {
        printf("Writing record to video at offset %x\n", video_offset);
        printf("Contents:\n");
        for (k=0 ; k<size && k<100 ; k++) {
          printf(" %02x", buf[ofs+k]);
          if (j == 15) { printf("\n"); j=0; }
          else j++;
        }
      }
      // There seems to be some records that have size "10" that are not actually 
      // part of the stream: these records are causing errors in mpeg2dec of video
      // stream and mpg123 of audio stream. Identified it by matching the offset of
      // the offending bits found by mpg123 to the record that was put into the 
      // m2a file at that offset. These are 16 bytes, and contain the video/audio
      // id at the fourth byte of the record, and are found at the start of a series 
      // of audio records, or video records.
      if (buf[ofs+3] == VIDEO_ID){
        if (verbose) printf("Looks like a bad record, rejecting\n"); 
      }
      else write(video_out, &buf[ofs], size);
      
      video_offset += size;
      ofs += size;
    }
    if (type == AUDIO_ID) {
      int j = 0;
      if (verbose) {
        printf("Writing record to audio at offset %x\n", audio_offset);
        printf("Contents:\n");
        for (k=0 ; k<size && k<100 ; k++) {
          printf(" %02x", buf[ofs+k]);
          if (j == 15) { printf("\n"); j=0; }
          else j++;
        }
      }
      if (buf[ofs+3] == AUDIO_ID) {
        if (verbose) printf("Looks like a bad record, rejecting\n");
      }
      else write(audio_out, &buf[ofs], size);
      audio_offset += size;
      ofs += size;
    }
    if ((type==1) || (type==2)) {
      total_misc += size;
    } else {
      total_av+= size;
    }
    if (verbose) printf("\n");
  }
  if (verbose) printf("Misc = %x, A/V = %x\n"
                    "-------------------\n\n", total_misc, total_av);
}
         

static void usage(void) {
/* [<][>][^][v][top][bottom][index][help] */
  printf("
usage: ConvertStream <tyStream> <audio file> <video file> [-v]

ConvertStream takes a TiVo tyStream as an input, outputs the MPEG2
video stream to <video file>, and the MPEG2 audio stream to <audio
file>. Use the -v flag for verboseness.\n\n");
  exit(0);
}





/* [<][>][^][v][top][bottom][index][help] */