/* * a dvd backup tool * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; * * * Some parts of this source code were taken from the following projects: * transcode by Dr. Thomas Oestreich * lsdvd by Chris Phillips * mplex by mjpeg-tools developers group * */ #include #include #include #include #include #include #include #include #include "streamdvd.h" #define VERSION "0.4" extern int dorequant( float ); extern int run_mplex( int, int *, int ); static struct global_job_values * gl; static struct streamblock *requant_buffer = NULL; /* * check if packet is part of a selected stream. if yes, return stream code, otherwise 0 */ int identify_stream( unsigned char *buffer, int *stream_list, int stream_count ) { uint8_t packet_type = buffer[17]; int i, ok = 0; if( (packet_type >= 0xE0) && (packet_type <= 0xEF) ) { ; } else if( packet_type == 0xBB ) { ; } else if( packet_type == 0xBE ) { ; } else if( packet_type == 0xBF ) { ; } else if( (packet_type >= 0xC0) && (packet_type <= 0xEF) ) { ; } else if( (packet_type >= 0xA0) && (packet_type <= 0xAF) ) { ; } else if( packet_type == 0xBD ) { packet_type = buffer[23+buffer[22]]; } if( ((!stream_count) || (stream_count == MAX_STREAMS)) && ((packet_type < 0xB0) || (packet_type >= 0xC0)) ) return(packet_type); for( i=0 ; i> 6) & 0x3; pes_header_data_length = buffer[ptr++]; switch(pts_dts_flags) { case 2: case 3: *pts = (buffer[ptr++] >> 1) & 7; *pts <<= 15; *pts |= (stream_read_int16(&buffer[ptr]) >> 1); ptr+=2; *pts <<= 15; *pts |= (stream_read_int16(&buffer[ptr]) >> 1); ptr+=2; pes_header_bytes += 5; has_pts_dts=1; break; default: has_pts_dts=0; *pts=0; break; } return(has_pts_dts); } /* * calculate a/v delay in ms, needed for sync muxing */ void get_av_delay ( void ) { double pts_diff = 0.; unsigned long v_pts, a_pts; int i = gl->last_block, stream_id, have_video = 0, have_audio = 0; unsigned char buffer[DVD_VIDEO_LB_LEN]; while( (!have_video) || (!have_audio) ) { if( !DVDReadBlocks(gl->vob, i, 1, buffer) ) { fprintf(stderr, "ERROR probing for av sync\n"); exit(1); } stream_id = identify_stream(buffer, gl->stream_list, gl->stream_count); if( stream_id ) { if( (!have_video) && ((stream_id & VIDEO_PREFIX)==VIDEO_PREFIX) ) { if( get_pts_dts(buffer+20, &v_pts) ) { have_video = 1; } } else if( (!have_audio) && (stream_id >= 0x80) && (stream_id < 0xD0) ) { if( get_pts_dts(buffer+20, &a_pts) ) { have_audio = 1; } } } i++; } pts_diff = ( (double)v_pts/90000. ) - ( (double)a_pts/90000. ); gl->av_delay = pts_diff * 1000.; fprintf(stderr, "AV Delay: %d ms\n", gl->av_delay); } /* * probe for available streams if no stream selection in commandline */ int probe_dvd ( int *list ) { int i, x, found, count = 0; unsigned char buffer[DVD_VIDEO_LB_LEN]; uint8_t packet_type; for( i=0 ; i<5120 ; i++ ) { if( !DVDReadBlocks(gl->vob, i, 1, buffer) ) { fprintf(stderr, "ERROR probing for streams\n"); exit(1); } found = 0; packet_type = buffer[17]; if( packet_type == 0xBD ) { packet_type = buffer[23+buffer[22]]; } if( ((packet_type >= 0xE0) && (packet_type <= 0xEF)) || ((packet_type >= 0xC0) && (packet_type <= 0xCF)) || ((packet_type >= 0xA0) && (packet_type <= 0xAF)) || ((packet_type >= 0x80) && (packet_type <= 0x8F)) ) { for( x=0 ; xlast_block; int stream_id, payload_offset, payload_len, i; struct streamblock *new_block, *tmp_block; unsigned char buffer[DVD_VIDEO_LB_LEN]; if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: DVD read\n"); while( (offset >= gl->start_block) && (offset <= gl->end_block) ) { if( !DVDReadBlocks(gl->vob, offset, 1, buffer) ) { fprintf(stderr, "ERROR reading block %d\n", offset); break; } stream_id = identify_stream(buffer, gl->stream_list, gl->stream_count); if( (stream_id & VIDEO_PREFIX) == VIDEO_PREFIX ) { if( (vid_cnt >= BLOCKS2READ) && (buffer[21] != 0) && (buffer[22] != 0) && (buffer[36] == 0xb3) ) { if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: DVD read stopped at next seq hdr\n"); break; } } if( stream_id ) { new_block = (struct streamblock *) malloc(sizeof(struct streamblock)); if( new_block == NULL ) { fprintf(stderr, "Out of memory\n"); exit(1); } payload_len = (buffer[18]<<8) | buffer[19]; if( (stream_id & VIDEO_PREFIX) == VIDEO_PREFIX ) { payload_offset = BLOCK_DATA_OFFSET; payload_len -= 3; payload_offset += buffer[22]; payload_len -= buffer[22]; vid_cnt++; } else { payload_offset = 27 + buffer[22]; payload_len = payload_len - 7 - buffer[22]; } memcpy( new_block->content, buffer+payload_offset, payload_len ); new_block->size = payload_len; new_block->offset = 0; new_block->id = stream_id; new_block->num = vid_cnt; new_block->next = NULL; new_block->previous = NULL; if( ((stream_id & VIDEO_PREFIX) == VIDEO_PREFIX) && gl->need_requant ) { if( requant_buffer == NULL ) { requant_buffer = new_block; } else { tmp_block = requant_buffer; while( tmp_block->next != NULL ) { tmp_block = tmp_block->next; } tmp_block->next = new_block; new_block->previous = tmp_block; } } else { for( i=0 ; istream_count ; i++ ) { if( gl->streams[i] == NULL ) { gl->streams[i] = new_block; break; } else if( gl->streams[i]->id == new_block->id ) { tmp_block = gl->streams[i]; while( tmp_block->next != NULL ) { tmp_block = tmp_block->next; } tmp_block->next = new_block; new_block->previous = tmp_block; break; } } } if( gl->debug_level > 4 ) fprintf(stderr, " Debug 4: added packet %d: %x, %d\n", new_block->num, new_block->id, new_block->size); } offset++; cnt ++; } if( gl->need_requant ) { dorequant(gl->requant_factor); } gl->last_block = offset; return(cnt); } /* * read count bytes from internal buffer * if internal buffer is empty, refill it */ int read_from_buffer ( unsigned char stream_id, unsigned char *buffer, int count, int do_refill ) { int done = 0, i, this_stream, toread; struct streamblock *this_block = NULL; if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: Buffer read: %d\n", count); if( gl->streams[0] == NULL ) { fprintf(stderr, "Starting input buffering\n"); if( read_from_dvd() > 0 ) { for( i=0 ; istream_count ; i++ ) { if( gl->streams[i] == NULL ) { gl->stream_count = i; fprintf(stderr, "Processing %d streams\n", gl->stream_count); break; } } } else { return(-1); } } for( i=0 ; istream_count ; i++ ) { if( gl->streams[i] == NULL ) { return(-1); } if( gl->streams[i]->id == stream_id ) { this_block = gl->streams[i]; this_stream = i; break; } } if( this_block == NULL ) return(-1); while( done < count ) { if( this_block->size == this_block->offset ) { if( this_block->next == NULL ) { if( !do_refill ) { return(done); } if( read_from_dvd() <= 0 ) { return(done); } } this_block = this_block->next; free(this_block->previous); gl->streams[this_stream] = this_block; } if( gl->debug_level > 4 ) fprintf(stderr, " Debug 6: reading %x from packet: %d\n", this_block->id, this_block->num); toread = this_block->size - this_block->offset; if( (count - done) < toread ) { toread = count - done; } memcpy( buffer, this_block->content + this_block->offset, toread); this_block->offset += toread; buffer += toread; done += toread; } if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: Buffer return: %d\n", done); return done; } /* * input buffer read function for requantizer */ int requant_read ( unsigned char *buffer, int count ) { int done = 0, toread; if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant read: %d\n", count); if( requant_buffer == NULL ) { if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant abort\n"); return(0); } while( done < count ) { if( requant_buffer->size == requant_buffer->offset ) { if( requant_buffer->next == NULL ) { free(requant_buffer); requant_buffer = NULL; if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant return empty: %d\n", done); return(done); } requant_buffer = requant_buffer->next; free(requant_buffer->previous); } if( gl->debug_level > 4 ) fprintf(stderr, " Debug 5: reading %x from packet: %d\n", requant_buffer->id, requant_buffer->num); toread = requant_buffer->size - requant_buffer->offset; if( (count - done) < toread ) { toread = count - done; } memcpy( buffer, requant_buffer->content + requant_buffer->offset, toread); requant_buffer->offset += toread; buffer += toread; done += toread; } if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant return: %d\n", done); return done; } /* * input buffer write function for requantizer */ int requant_write ( unsigned char *buffer, int count ) { struct streamblock *this_block = NULL, *new_block; int i, done = 0, towrite; if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant write: %d\n", count); for( i=0 ; istream_count ; i++ ) { if( gl->streams[i]->id == gl->video_id ) { this_block = gl->streams[i]; break; } } if( this_block == NULL ) { fprintf(stderr, "Error writing requantized stream\n"); exit(1); } while( (this_block != NULL) && (this_block->next != NULL) ) { this_block = this_block->next; } i = this_block->num + 1; while( done < count ) { new_block = (struct streamblock *) malloc(sizeof(struct streamblock)); if( new_block == NULL ) { fprintf(stderr, "Out of memory\n"); exit(1); } towrite = DVD_VIDEO_LB_LEN; if( (count - done) < towrite ) { towrite = count - done; } memcpy( new_block->content, buffer, towrite); new_block->size = towrite; new_block->offset = 0; new_block->id = gl->video_id; new_block->num = i; new_block->next = NULL; new_block->previous = NULL; this_block->next = new_block; new_block->previous = this_block; if( gl->debug_level > 4 ) fprintf(stderr, " Debug 5: writing to packet: %d\n", this_block->num); this_block = this_block->next; done += towrite; buffer += towrite; i++; } return(done); } /* * Extract the real start and stop blocks to read from the ifo structure * If start and stop chapters are 0, they'll be set to the right values (start=1, stop=lastchapter) */ int get_start_stop_blocks (dvd_reader_t *dvd, int title, uint8_t *ts_nr, int *chapstart, int *chapstop, uint32_t *startblock, uint32_t *lastblock ) { int ok = 0; ifo_handle_t *ifo_title; ifo_handle_t *ifo_main; vts_ptt_srpt_t *vts_ptt_srpt; tt_srpt_t *tt_srpt; pgc_t *pgc; int titleid = title -1, ttn, pgc_id, pgn, start_cell, end_cell; if( title == 0 ) { fprintf(stderr, "Invalid title number %d\n", title); return 0; } ifo_main = ifoOpen(dvd, 0); if( !ifo_main ) { fprintf(stderr, "Error opening main ifo file\n"); return(0); } if( titleid > ifo_main->tt_srpt->nr_of_srpts ) { fprintf(stderr, "Invalid title number %d\n", title); return(0); } tt_srpt = ifo_main->tt_srpt; *ts_nr = tt_srpt->title[ titleid ].title_set_nr; ifo_title = ifoOpen(dvd, tt_srpt->title[ titleid ].title_set_nr); if( !ifo_title ) { fprintf(stderr, "Error opening ifo file for title %d\n", title); ifoClose(ifo_main); return(0); } if( ifo_title->vtsi_mat ) { if( *chapstart == 0 ) *chapstart = 1; if( *chapstop == 0 ) *chapstop = tt_srpt->title[ titleid ].nr_of_ptts; ttn = tt_srpt->title[ titleid ].vts_ttn; vts_ptt_srpt = ifo_title->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgn; pgc = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc; start_cell = pgc->program_map[pgn - 1] - 1; *startblock = pgc->cell_playback[start_cell].first_sector; if( *chapstop == tt_srpt->title[ titleid ].nr_of_ptts ) { end_cell = pgc->nr_of_cells -1; *lastblock = pgc->cell_playback[end_cell].last_sector; } else { pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgn; pgc = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc; end_cell = pgc->program_map[pgn - 1] - 2; *lastblock = pgc->cell_playback[end_cell].last_sector; } if( gl->debug_level > 2) fprintf(stderr, "startcell %d, stopcell %d\n", start_cell+1, end_cell+1); ok = 1; } ifoClose(ifo_title); ifoClose(ifo_main); return(ok); } void usage (char *progname, int exitcode) { fprintf(stderr, "usage: %s -i device -t title [-c chapter|chapter range] [-s stream(s)] [-f factor] [-d level] [-v]\n", progname); fprintf(stderr, " device : dvd device or directory containing dvd structure\n"); fprintf(stderr, " title : title number\n"); fprintf(stderr, " chapter: chapter number or range (default: all chapter)\n"); fprintf(stderr, " streams: video/audio/subtitle stream(s) (default: all streams)\n"); fprintf(stderr, " factor : requantize factor (default: 1)\n"); fprintf(stderr, " level : debug level (default: 0)\n"); fprintf(stderr, "\n"); fprintf(stderr, " example:\n"); fprintf(stderr, " Use /dev/dvd to read chapter 3 out of title 1 with all video/audio/subtitle streams without requantizing\n"); fprintf(stderr, " %s -i /dev/dvd -t 1 -c 3\n\n", progname); fprintf(stderr, " Use directory /mnt/movie to read complete title 2 with first video and audio stream, requantizing factor 1.5\n"); fprintf(stderr, " %s -i /mnt/movie -t 2 -f 1.5 -s 0xe0,0x80\n\n", progname); fprintf(stderr, " Use /dev/dvd to read title 1, chapter 1 - 3, with first video, first 2 audio and first subtitle streams\n"); fprintf(stderr, " %s -i /dev/dvd -t 1 -c 1-3 -s 0xe0,0x80,0x81,0x20\n\n", progname); exit(exitcode); } void version ( void ) { fprintf(stderr, " streamdvd v%s - (C) 2003 by Reinhardt Wolf\n", VERSION); exit(0); } unsigned char get_video_id ( void ) { int i; unsigned char videoid = 0; for( i=0 ; istream_count ; i++ ) { if( (gl->stream_list[i] & VIDEO_PREFIX) == VIDEO_PREFIX ) { videoid = gl->stream_list[i]; break; } } if( (videoid == 0) && (gl->stream_count == MAX_STREAMS) ) videoid = VIDEO_PREFIX; return(videoid); } void init_streams ( void ) { int i; struct streamblock *new_block; for( i=0 ; istream_count ; i++ ) { new_block = (struct streamblock *) malloc(sizeof(struct streamblock)); if( new_block == NULL ) { fprintf(stderr, "Out of memory\n"); exit(1); } new_block->size = 0; new_block->offset = 0; new_block->id = gl->stream_list[i]; new_block->num = 0; new_block->next = NULL; new_block->previous = NULL; gl->streams[i] = new_block; } } int dummy_runmplex ( void ) { unsigned char buffer[BLOCKS2READ * 1024]; fprintf(stderr, "Bytes read: %d\n", read_from_buffer( gl->video_id, buffer, (BLOCKS2READ * 1024), 1) ); write( STDOUT_FILENO, buffer, (BLOCKS2READ * 1024) ); return(1); } int main(int argc, char *argv[]) { dvd_reader_t *dvd; char *opt_inputname = NULL; float opt_factor = 1.0; int opt_debuglevel = 0; int opt_title = 0; int opt_chapter_start = 0; int opt_chapter_stop = 0; int opt_streamlist[MAX_STREAMS]; int stream_count = 0; uint8_t ts_nr; int c, x; int *b; uint32_t start_block = 0, stop_block = 0; while( (c = getopt(argc, argv, "i:t:c:s:f:d:Ihv")) != -1 ) { switch(c) { case 'h': usage(argv[0], 0); break; case 'v': version(); break; case 'd': opt_debuglevel = atoi(optarg); break; case 'i': opt_inputname = optarg; break; case 't': opt_title = atoi(optarg); break; case 'f': opt_factor = atof(optarg); break; case 'c': x = sscanf(optarg,"%d-%d", &opt_chapter_start, &opt_chapter_stop); if( x != 2 ) { x = sscanf(optarg,"%d", &opt_chapter_start); if( x != 1 ) { fprintf(stderr, "invalid parameter for option -c\n"); exit(1); } opt_chapter_stop = opt_chapter_start; } break; case 's': b = opt_streamlist; if( (x = sscanf(optarg, "%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", &b[0],&b[1],&b[2],&b[3],&b[4],&b[5],&b[6],&b[7],&b[8],&b[9])) > 0 ) { stream_count = stream_count + x; } break; default: usage(argv[0], 1); } } if( (opt_inputname == NULL) || (opt_title == 0) ) usage(argv[0], 1); dvd = DVDOpen(opt_inputname); if( !dvd ) { fprintf(stderr, "ERROR: opening dvd at %s failed\n", opt_inputname); exit(1); } gl = (struct global_job_values *) malloc(sizeof(struct global_job_values)); if( gl == NULL ) { fprintf(stderr, "ERROR: no memory to create job\n"); DVDClose(dvd); exit(1); } if( get_start_stop_blocks(dvd, opt_title, &ts_nr, &opt_chapter_start, &opt_chapter_stop, &start_block, &stop_block) ) { gl->start_block = start_block; gl->last_block = start_block; gl->end_block = stop_block; gl->dvd = dvd; gl->debug_level = opt_debuglevel; gl->requant_factor = opt_factor; gl->av_delay = 0; gl->vob = DVDOpenFile(gl->dvd, ts_nr, DVD_READ_TITLE_VOBS); if( !gl->vob ) { fprintf(stderr, "ERROR: opening vobs for title %d failed\n", ts_nr); DVDClose(dvd); exit(1); } if( !stream_count ) stream_count = probe_dvd(opt_streamlist); gl->streams = (struct streamblock **) calloc( stream_count, sizeof(struct streamblock *) ); if( gl->streams == NULL ) { fprintf(stderr, "Out of memory\n"); exit(1); } gl->stream_count = stream_count; gl->stream_list = opt_streamlist; init_streams(); gl->video_id = get_video_id(); if( !gl->video_id ) { fprintf(stderr, "ERROR: no video stream found\n"); exit(1); } if( opt_factor > 1.0 ) { gl->requant_factor = opt_factor; gl->need_requant = 1; } else { gl->need_requant = 0; } fprintf(stderr, "Selected streams: "); for( x=0 ; xstream_count, gl->stream_list, gl->av_delay); DVDCloseFile(gl->vob); } DVDClose(dvd); return 0; }