#include #include #include #include #include #include "ifo.h" #include "mpeg3private.h" #include "mpeg3protos.h" typedef struct { double start_byte; double end_byte; // Used in final table int program; // Used in cell play info int cell_type; // Used in cell addresses int vob_id; int cell_id; } mpeg3ifo_cell_t; typedef struct { mpeg3ifo_cell_t *cells; long total_cells; long cells_allocated; } mpeg3ifo_celltable_t; #define CADDR_HDR_LEN 8 typedef struct { u_short num : 16; // Number of Video Objects u_short unknown : 16; // don't know u_int len : 32; // length of table } cell_addr_hdr_t; typedef struct { u_int foo : 16; // ??? u_int num : 16; // number of subchannels } audio_hdr_t; #define AUDIO_HDR_LEN 4 typedef struct { u_short id : 16; // Language u_short : 16; // don't know u_int start : 32; // Start of unit } pgci_sub_t; #define PGCI_SUB_LEN 8 #define PGCI_COLOR_LEN 4 #define OFF_PTT mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xC8) #define OFF_TITLE_PGCI mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xCC) #define OFF_MENU_PGCI mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xD0) #define OFF_TMT mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xD4) #define OFF_MENU_CELL_ADDR mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xD8) #define OFF_MENU_VOBU_ADDR_MAP mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xDC) #define OFF_TITLE_CELL_ADDR mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xE0) #define OFF_TITLE_VOBU_ADDR_MAP mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xE4) #define OFF_VMG_TSP mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xC4) #define OFF_VMG_MENU_PGCI mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xC8) #define OFF_VMG_TMT mpeg3_ifo_get4bytes (ifo->data[ID_MAT] + 0xD0) #define bswap_16(x) ((((x) << 8) & 0xff00) | \ (((x) >> 8) & 0x00ff)) #define bswap_32(x) ((((x) << 24) & 0xff000000) | \ (((x) << 8) & 0x00ff0000) | \ (((x) >> 8) & 0x0000ff00) | \ (((x) >> 24) & 0x000000ff)) inline u_int mpeg3_ifo_get4bytes(u_char *buf) { return bswap_32 (*((u_int32_t *)buf)); } inline u_int mpeg3_ifo_get2bytes(u_char *buf) { return bswap_16 (*((u_int16_t *)buf)); } int mpeg3_ifo_read(int fd, long pos, long count, unsigned char *data) { if((pos = lseek(fd, pos, SEEK_SET)) < 0) { perror("mpeg3_ifo_read"); return -1; } return read(fd, data, count); } int mpeg3_ifo_vts(ifo_t *ifo) { if(!strncmp(ifo->data[ID_MAT], "DVDVIDEO-VTS", 12)) return 0; return -1; } int mpeg3_ifo_vmg(ifo_t *ifo) { if(!strncmp(ifo->data[ID_MAT], "DVDVIDEO-VMG", 12)) return 0; return -1; } int mpeg3_ifo_table(ifo_t *ifo, unsigned long offset, unsigned long tbl_id) { unsigned char *data; unsigned long len = 0; int i; u_int32_t *ptr; if(!offset) return -1; data = (u_char *)calloc(1, DVD_VIDEO_LB_LEN); if(mpeg3_ifo_read(ifo->fd, ifo->pos + offset * DVD_VIDEO_LB_LEN, DVD_VIDEO_LB_LEN, data) <= 0) { perror("mpeg3_ifo_table"); return -1; } switch(tbl_id) { case ID_TITLE_VOBU_ADDR_MAP: case ID_MENU_VOBU_ADDR_MAP: len = mpeg3_ifo_get4bytes(data) + 1; break; default: { ifo_hdr_t *hdr = (ifo_hdr_t *)data; len = bswap_32(hdr->len) + 1; } } if(len > DVD_VIDEO_LB_LEN) { data = (u_char *)realloc((void *)data, len); mpeg3_ifo_read(ifo->fd, ifo->pos + offset * DVD_VIDEO_LB_LEN, len, data); } ifo->data[tbl_id] = data; ptr = (u_int32_t*)data; len /= 4; if(tbl_id == ID_TMT) for (i = 0; i < len; i++) ptr[i] = bswap_32(ptr[i]); return 0; } ifo_t* mpeg3_ifo_open(int fd, long pos) { ifo_t *ifo; ifo = (ifo_t *)calloc(sizeof(ifo_t), 1); ifo->data[ID_MAT] = (char *)calloc(DVD_VIDEO_LB_LEN, 1); ifo->pos = pos; ifo->fd = fd; if(mpeg3_ifo_read(fd, pos, DVD_VIDEO_LB_LEN, ifo->data[ID_MAT]) < 0) { perror("mpeg3_ifo_open"); free(ifo->data[ID_MAT]); free(ifo); return NULL; } ifo->num_menu_vobs = mpeg3_ifo_get4bytes(ifo->data[ID_MAT] + 0xC0); ifo->vob_start = mpeg3_ifo_get4bytes(ifo->data[ID_MAT] + 0xC4); #ifdef DEBUG printf ("num of vobs: %x vob_start %x\n", ifo->num_menu_vobs, ifo->vob_start); #endif if(!mpeg3_ifo_vts(ifo)) { mpeg3_ifo_table(ifo, OFF_PTT, ID_PTT); mpeg3_ifo_table(ifo, OFF_TITLE_PGCI, ID_TITLE_PGCI); mpeg3_ifo_table(ifo, OFF_MENU_PGCI, ID_MENU_PGCI); mpeg3_ifo_table(ifo, OFF_TMT, ID_TMT); mpeg3_ifo_table(ifo, OFF_MENU_CELL_ADDR, ID_MENU_CELL_ADDR); mpeg3_ifo_table(ifo, OFF_MENU_VOBU_ADDR_MAP, ID_MENU_VOBU_ADDR_MAP); mpeg3_ifo_table(ifo, OFF_TITLE_CELL_ADDR, ID_TITLE_CELL_ADDR); mpeg3_ifo_table(ifo, OFF_TITLE_VOBU_ADDR_MAP, ID_TITLE_VOBU_ADDR_MAP); } else if(!mpeg3_ifo_vmg(ifo)) { mpeg3_ifo_table(ifo, OFF_VMG_TSP, ID_TSP); mpeg3_ifo_table(ifo, OFF_VMG_MENU_PGCI, ID_MENU_PGCI); mpeg3_ifo_table(ifo, OFF_VMG_TMT, ID_TMT); mpeg3_ifo_table(ifo, OFF_TITLE_CELL_ADDR, ID_TITLE_CELL_ADDR); mpeg3_ifo_table(ifo, OFF_TITLE_VOBU_ADDR_MAP, ID_TITLE_VOBU_ADDR_MAP); } return ifo; } int mpeg3_ifo_close(ifo_t *ifo) { if(ifo->data[ID_MAT]) free(ifo->data[ID_MAT]); if(ifo->data[ID_PTT]) free(ifo->data[ID_PTT]); if(ifo->data[ID_TITLE_PGCI]) free(ifo->data[ID_TITLE_PGCI]); if(ifo->data[ID_MENU_PGCI]) free(ifo->data[ID_MENU_PGCI]); if(ifo->data[ID_TMT]) free(ifo->data[ID_TMT]); if(ifo->data[ID_MENU_CELL_ADDR]) free(ifo->data[ID_MENU_CELL_ADDR]); if(ifo->data[ID_MENU_VOBU_ADDR_MAP]) free(ifo->data[ID_MENU_VOBU_ADDR_MAP]); if(ifo->data[ID_TITLE_CELL_ADDR]) free(ifo->data[ID_TITLE_CELL_ADDR]); if(ifo->data[ID_TITLE_VOBU_ADDR_MAP]) free(ifo->data[ID_TITLE_VOBU_ADDR_MAP]); free(ifo); return 0; } int mpeg3_ifo_audio(char *_hdr, char **ptr) { audio_hdr_t *hdr = (audio_hdr_t *)_hdr; if(!_hdr) return -1; *ptr = _hdr + AUDIO_HDR_LEN; return bswap_16(hdr->num); } int mpeg3_ifo_pgci(ifo_hdr_t *hdr, int title, char **ptr) { pgci_sub_t *pgci_sub; *ptr = (char *) hdr; if(!*ptr) return -1; if(title > hdr->num) return -1; *ptr += IFO_HDR_LEN; pgci_sub = (pgci_sub_t *)*ptr + title; *ptr = (char *)hdr + bswap_32(pgci_sub->start); return 0; } int mpeg3_ifo_program_map(char *pgc, char **ptr) { int num; *ptr = pgc; if (!pgc) return -1; *ptr += 2; num = **ptr; *ptr += 10; *ptr += 8 * 2; // AUDIO *ptr += 32 * 4; // SUBPICTURE *ptr += 8; *ptr += 16 * PGCI_COLOR_LEN; // CLUT *ptr += 2; *ptr = mpeg3_ifo_get2bytes(*ptr) + pgc; return num; } u_int mpeg3_ifo_get_cellplayinfo(u_char *pgc, u_char **ptr) { u_int num; *ptr = pgc; if (!pgc) return -1; *ptr += 3; num = **ptr; *ptr += 9; *ptr += 8 * 2; // AUDIO *ptr += 32 * 4; // SUBPICTURE *ptr += 8; *ptr += 16 * PGCI_COLOR_LEN; // CLUT *ptr += 4; *ptr = mpeg3_ifo_get2bytes(*ptr) + pgc; return num; } void mpeg3_get_ifo_playlist(mpeg3_t *file, mpeg3_demuxer_t *demuxer) { DIR *dirstream; char directory[MPEG3_STRLEN]; char filename[MPEG3_STRLEN]; char complete_path[MPEG3_STRLEN]; char title_path[MPEG3_STRLEN]; char vob_prefix[MPEG3_STRLEN]; struct dirent *new_filename; char *ptr; double total_bytes = 0; // Get titles matching ifo file mpeg3io_complete_path(complete_path, file->fs->path); mpeg3io_get_directory(directory, complete_path); mpeg3io_get_filename(filename, complete_path); strncpy(vob_prefix, filename, 6); dirstream = opendir(directory); while(new_filename = readdir(dirstream)) { if(!strncasecmp(new_filename->d_name, vob_prefix, 6)) { ptr = strrchr(new_filename->d_name, '.'); if(ptr && !strncasecmp(ptr, ".vob", 4)) { // Got a title if(atol(&new_filename->d_name[7]) > 0) { mpeg3_title_t *title; mpeg3io_joinpath(title_path, directory, new_filename->d_name); title = demuxer->titles[demuxer->total_titles++] = mpeg3_new_title(file, title_path); title->total_bytes = mpeg3io_path_total_bytes(title_path); total_bytes += title->total_bytes; //printf("%s\n", title_path); } } } } } // IFO parsing void mpeg3_get_ifo_header(mpeg3_demuxer_t *demuxer, ifo_t *ifo) { int i; // Video header demuxer->vstream_table[0] = 1; // Audio header if(!mpeg3_ifo_vts(ifo)) { ifo_audio_t *audio; int result = 0; // Doesn't detect number of tracks. int atracks = mpeg3_ifo_audio(ifo->data[ID_MAT] + IFO_OFFSET_AUDIO, (char**)&audio); int atracks_empirical = 0; // Construct audio stream id's #define TEST_START 0x600000 #define TEST_LEN 0x100000 mpeg3demux_open_title(demuxer, 0); mpeg3demux_seek_byte(demuxer, TEST_START); while(!result && !mpeg3demux_eof(demuxer) && mpeg3demux_tell(demuxer) < TEST_START + TEST_LEN) { result = mpeg3_read_next_packet(demuxer); } mpeg3demux_seek_byte(demuxer, 0); for(i = 0; i < MPEG3_MAX_STREAMS; i++) { //printf("%x %d\n", i, demuxer->astream_table[i]); if(demuxer->astream_table[i]) atracks_empirical++; } // Doesn't detect PCM audio or total number of tracks /* * if(atracks && !atracks_empirical) * for(i = 0; i < atracks; i++) * { * int audio_mode = AUDIO_AC3; * switch(audio->coding_mode) * { * case 0: audio_mode = AUDIO_AC3; break; * case 1: audio_mode = AUDIO_MPEG; break; * case 2: audio_mode = AUDIO_MPEG; break; * case 3: audio_mode = AUDIO_PCM; break; * } * if(!demuxer->astream_table[i + 0x80]) demuxer->astream_table[i + 0x80] = audio_mode; * } */ } else if(!mpeg3_ifo_vmg(ifo)) { } } mpeg3ifo_cell_t* mpeg3_append_cell(mpeg3ifo_celltable_t *table) { if(!table->cells || table->total_cells >= table->cells_allocated) { long new_allocation; mpeg3ifo_cell_t *new_cells; new_allocation = table->cells_allocated ? table->cells_allocated * 2 : 64; new_cells = calloc(1, sizeof(mpeg3ifo_cell_t) * new_allocation); if(table->cells) { memcpy(new_cells, table->cells, sizeof(mpeg3ifo_cell_t) * table->total_cells); free(table->cells); } table->cells = new_cells; table->cells_allocated = new_allocation; } return &table->cells[table->total_cells++]; } void mpeg3_delete_celltable(mpeg3ifo_celltable_t *table) { if(table->cells) free(table->cells); free(table); } void mpeg3_ifo_cellplayinfo(ifo_t *ifo, mpeg3ifo_celltable_t *cells) { int i, j; char *cell_hdr, *cell_hdr_start, *cell_info; ifo_hdr_t *hdr = (ifo_hdr_t*)ifo->data[ID_TITLE_PGCI]; int program_chains = bswap_16(hdr->num); long total_cells; //printf("mpeg3_ifo_cellplayinfo\n"); for(j = 0; j < program_chains; j++) { // Cell header mpeg3_ifo_pgci(hdr, j, &cell_hdr); cell_hdr_start = cell_hdr; // Unknown cell_hdr += 2; // Num programs cell_hdr += 2; // Chain Time cell_hdr += 4; // Unknown cell_hdr += 4; // Subaudio streams for(i = 0; i < 8; i++) cell_hdr += 2; // Subpictures for(i = 0; i < 32; i++) cell_hdr += 4; // Unknown for(i = 0; i < 8; i++) cell_hdr++; // Skip CLUT // Skip PGC commands // Program map if(mpeg3_ifo_program_map(cell_hdr_start, &cell_hdr)) ; // Cell Positions if(total_cells = mpeg3_ifo_get_cellplayinfo(cell_hdr_start, (unsigned char**)&cell_hdr)) { //printf("mpeg3_ifo_cellplayinfo %d %d\n", j, total_cells); cell_info = cell_hdr; for(i = 0; i < total_cells; i++) { ifo_pgci_cell_addr_t *cell_addr = (ifo_pgci_cell_addr_t *)cell_info; long start_byte = bswap_32(cell_addr->vobu_start); long end_byte = bswap_32(cell_addr->vobu_last_end); int cell_type = cell_addr->chain_info; if(!cells->total_cells && start_byte > 0) start_byte = 0; if(!cells->total_cells || end_byte >= cells->cells[cells->total_cells - 1].end_byte) { mpeg3ifo_cell_t *cell = mpeg3_append_cell(cells); cell->start_byte = start_byte; cell->end_byte = end_byte; cell->cell_type = cell_type; //printf("mpeg3_ifo_cellplayinfo start: %llx end: %llx type: %x\n", // (int64_t)cell->start_byte * 0x800, (int64_t)cell->end_byte * 0x800, cell->cell_type); } cell_info += PGCI_CELL_ADDR_LEN; } } } } void mpeg3_ifo_celladdresses(ifo_t *ifo, mpeg3ifo_celltable_t *cell_addresses) { int i; char *ptr = ifo->data[ID_TITLE_CELL_ADDR]; int total_addresses; cell_addr_hdr_t *cell_addr_hdr = (cell_addr_hdr_t*)ptr; ifo_cell_addr_t *cell_addr = (ifo_cell_addr_t*)(ptr + CADDR_HDR_LEN); int done = 0; //printf("mpeg3_ifo_celladdresses\n"); if(total_addresses = bswap_32(cell_addr_hdr->len) / sizeof(ifo_cell_addr_t)) { for(i = 0; i < total_addresses; i++) { mpeg3ifo_cell_t *cell; cell = mpeg3_append_cell(cell_addresses); cell->start_byte = (double)bswap_32(cell_addr->start); cell->end_byte = (double)bswap_32(cell_addr->end); cell->vob_id = bswap_16(cell_addr->vob_id); cell->cell_id = cell_addr->cell_id; /* * printf("mpeg3_ifo_celladdresses vob id: %x cell id: %x start: %ld end: %ld\n", * bswap_16(cell_addr->vob_id), cell_addr->cell_id, (long)cell->start_byte, (long)cell->end_byte); */ cell_addr++; } } // Sort addresses by address instead of vob id done = 0; while(!done) { done = 1; for(i = 0; i < total_addresses - 1; i++) { mpeg3ifo_cell_t *cell1, *cell2; cell1 = &cell_addresses->cells[i]; cell2 = &cell_addresses->cells[i + 1]; if(cell1->start_byte > cell2->start_byte) { mpeg3ifo_cell_t temp = *cell1; *cell1 = *cell2; *cell2 = temp; done = 0; break; } } } for(i = 0; i < total_addresses; i++) { mpeg3ifo_cell_t *cell = &cell_addresses->cells[i]; //printf("mpeg3_ifo_celladdresses vob id: %x cell id: %x start: %ld end: %ld\n", // cell->vob_id, cell->cell_id, (long)cell->start_byte, (long)cell->end_byte); } } void mpeg3_ifo_finaltable(mpeg3ifo_celltable_t *final_cells, mpeg3ifo_celltable_t *cells, mpeg3ifo_celltable_t *cell_addresses) { int input_cell = 0, current_address = 0; int output_cell = 0; int done; int i, j; int current_vobid; // Start and end bytes of programs long program_start_byte[256], program_end_byte[256]; final_cells->total_cells = 0; final_cells->cells_allocated = cell_addresses->total_cells; final_cells->cells = calloc(1, sizeof(mpeg3ifo_cell_t) * final_cells->cells_allocated); // Assign programs to cells current_vobid = -1; for(i = cell_addresses->total_cells - 1; i >= 0; i--) { mpeg3ifo_cell_t *input = &cell_addresses->cells[i]; mpeg3ifo_cell_t *output = &final_cells->cells[i]; if(current_vobid < 0) current_vobid = input->vob_id; *output = *input; // Reduce current vobid if(input->vob_id < current_vobid) current_vobid = input->vob_id; else // Get the current program number if(input->vob_id > current_vobid) { int current_program = input->vob_id - current_vobid; output->program = current_program; // Get the last interleave by brute force for(j = i; j < cell_addresses->total_cells && cell_addresses->cells[i].cell_id == cell_addresses->cells[j].cell_id; j++) { int new_program = final_cells->cells[j].vob_id - current_vobid; if(new_program <= current_program) final_cells->cells[j].program = new_program; } } final_cells->total_cells++; } // Expand byte position and remove duplicates for(i = 0; i < final_cells->total_cells; i++) { if(i < final_cells->total_cells - 1 && final_cells->cells[i].start_byte == final_cells->cells[i + 1].start_byte) { for(j = i; j < final_cells->total_cells - 1; j++) final_cells->cells[j] = final_cells->cells[j + 1]; final_cells->total_cells--; } final_cells->cells[i].start_byte *= (double)2048; final_cells->cells[i].end_byte *= (double)2048; } return; // Debug printf("mpeg3_ifo_finaltable\n"); for(i = 0; i < final_cells->total_cells; i++) { printf(" vob id: %x cell id: %x start: %llx end: %llx program: %x\n", final_cells->cells[i].vob_id, final_cells->cells[i].cell_id, (int64_t)final_cells->cells[i].start_byte, (int64_t)final_cells->cells[i].end_byte, final_cells->cells[i].program); } } /* Read the title information from a ifo */ int mpeg3demux_read_ifo(mpeg3_t *file, mpeg3_demuxer_t *demuxer, int read_timecodes) { double last_ifo_byte = 0, first_ifo_byte = 0; mpeg3ifo_celltable_t *cells, *cell_addresses, *final_cells; int current_title = 0, current_cell = 0; int i; ifo_t *ifo; int fd = mpeg3io_get_fd(file->fs); double title_start_byte = 0; int result; //printf("mpeg3demux_read_ifo 1\n"); if(!(ifo = mpeg3_ifo_open(fd, 0))) { fprintf(stderr, "mpeg3demux_read_ifo: Error decoding ifo.\n"); return 1; } demuxer->packet_size = 2048; demuxer->read_all = 1; cells = calloc(1, sizeof(mpeg3ifo_celltable_t)); cell_addresses = calloc(1, sizeof(mpeg3ifo_celltable_t)); final_cells = calloc(1, sizeof(mpeg3ifo_celltable_t)); mpeg3_get_ifo_playlist(file, demuxer); mpeg3_get_ifo_header(demuxer, ifo); mpeg3_ifo_cellplayinfo(ifo, cells); mpeg3_ifo_celladdresses(ifo, cell_addresses); mpeg3_ifo_finaltable(final_cells, cells, cell_addresses); //printf("mpeg3demux_read_ifp 2\n"); // Assign timecodes to titles while(final_cells && current_cell < final_cells->total_cells) { mpeg3_title_t *title; mpeg3ifo_cell_t *cell; double cell_start, cell_end; title = demuxer->titles[current_title]; cell = &final_cells->cells[current_cell]; cell_start = cell->start_byte; cell_end = cell->end_byte; //printf("mpeg3demux_read_ifo 1 %d %llx %llx %d\n", current_cell, (int64_t)cell->start_byte, (int64_t)cell->end_byte, cell->program); while(cell_start < cell_end) { double length = cell_end - cell_start; if(cell_start + length - title_start_byte > title->total_bytes) length = title->total_bytes - cell_start + title_start_byte; if(length) { mpeg3_new_timecode(title, (long)(cell_start - title_start_byte), 0, (long)(cell_start - title_start_byte + length), 0, cell->program); cell_start += length; } // Advance title if(cell_start - title_start_byte >= title->total_bytes && current_title < demuxer->total_titles - 1) { title_start_byte += title->total_bytes; title = demuxer->titles[++current_title]; } } current_cell++; } // Look up time values for the timecodes // Should only be used for building a TOC if(read_timecodes) { for(current_title = 0; current_title < demuxer->total_titles; current_title++) { mpeg3_title_t *title = demuxer->titles[current_title]; mpeg3demux_open_title(demuxer, current_title); for(i = 0; i < title->timecode_table_size; i++) { mpeg3demux_timecode_t *timecode = &title->timecode_table[i]; mpeg3io_seek(title->fs, timecode->start_byte); mpeg3_read_next_packet(demuxer); timecode->start_time = demuxer->time; mpeg3io_seek(title->fs, timecode->end_byte); if(timecode->end_byte >= title->total_bytes) mpeg3_read_prev_packet(demuxer); else mpeg3_read_next_packet(demuxer); timecode->end_time = demuxer->time; } } mpeg3demux_open_title(demuxer, 0); } //for(i = 0; i < demuxer->total_titles; i++) mpeg3_dump_title(demuxer->titles[i]); mpeg3_delete_celltable(cells); mpeg3_delete_celltable(cell_addresses); mpeg3_delete_celltable(final_cells); mpeg3_ifo_close(ifo); mpeg3demux_assign_programs(demuxer); return 0; } int mpeg3_read_ifo(mpeg3_t *file, int read_timecodes) { file->is_program_stream = 1; mpeg3demux_read_ifo(file, file->demuxer, read_timecodes); return 0; }