/* * Copyright (C) 2000 Rich Wareham * * This file is part of libdvdnav, a DVD navigation library. * * libdvdnav 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. * * libdvdnav 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 libdvdnav; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "dvdnav/dvdnav.h" #include #include "vm/decoder.h" #include "vm/vm.h" #include "dvdnav_internal.h" #include "logger.h" #include /* #define LOG_DEBUG */ /* Searching API calls */ /* Scan the ADMAP for a particular block number. */ /* Return placed in vobu. */ /* Returns error status */ /* FIXME: Maybe need to handle seeking outside current cell. */ static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, int next, uint32_t *vobu) { vobu_admap_t *admap = NULL; #ifdef LOG_DEBUG Log3(this, "Seeking to target %u ...", seekto_block); #endif *vobu = -1; /* Search through the VOBU_ADMAP for the nearest VOBU * to the target block */ switch(domain) { case DVD_DOMAIN_FirstPlay: case DVD_DOMAIN_VMGM: admap = this->vm->vmgi->menu_vobu_admap; break; case DVD_DOMAIN_VTSMenu: admap = this->vm->vtsi->menu_vobu_admap; break; case DVD_DOMAIN_VTSTitle: admap = this->vm->vtsi->vts_vobu_admap; break; default: Log0(this, "Error: Unknown domain for seeking."); } if(admap) { uint32_t address = 0; uint32_t vobu_start, next_vobu = 0; uint32_t admap_entries = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)/VOBU_ADMAP_SIZE; /* Search through ADMAP for best sector */ vobu_start = SRI_END_OF_CELL; /* FIXME: Implement a faster search algorithm */ while(address < admap_entries) { next_vobu = admap->vobu_start_sectors[address]; /* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */ if(vobu_start <= seekto_block && next_vobu > seekto_block) { /* We expect the ADMAP to be sorted in order of increasing * sector number, but some discs store out-of-order values in * cells that would not normally be playable, cells which * themselves contain bad sectors. The straightforward linear * search would get "stuck" on those values, essentially * forcing playback of the bad sectors on any sector seek * anywhere within the title. To prevent this, scan the next * few entries to make sure they're all really later than the * current entry, and skip over the out-of-order entries if * not. */ uint32_t ooo_address = 0; uint32_t ooo_vobu = SRI_END_OF_CELL; uint32_t check; for( check = 1 ; check <= 32 && address+check < admap_entries ; check++ ) { uint32_t check_vobu = admap->vobu_start_sectors[address+check]; if (check_vobu < next_vobu && check_vobu < ooo_vobu) { ooo_address = address+check; ooo_vobu = check_vobu; } } if (ooo_vobu < next_vobu) { address = ooo_address; next_vobu = ooo_vobu; } else { break; } } vobu_start = next_vobu; address++; } *vobu = next ? next_vobu : vobu_start; return DVDNAV_STATUS_OK; } Log0(this, "admap not located"); return DVDNAV_STATUS_ERR; } /* FIXME: right now, this function does not use the time tables but interpolates only the cell times */ dvdnav_status_t dvdnav_time_search(dvdnav_t *this, uint64_t time) { uint64_t target = time; uint64_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found; const cell_playback_t *cell; dvd_state_t *state; if(this->position_current.still != 0) { printerr("Cannot seek in a still frame."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { /* Find start cell of program. */ first_cell_nr = state->pgc->program_map[state->pgN-1]; /* Find end cell of program */ if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } found = 0; for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) continue; length = dvdnav_convert_time(&cell->playback_time); if (target >= length) { target -= length; } else { /* FIXME: there must be a better way than interpolation */ target = target * (cell->last_sector - cell->first_sector + 1) / length; target += cell->first_sector; found = 1; break; } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG Log3(this, "Seeking to cell %i from choice of %i to %i", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) == DVDNAV_STATUS_OK) { uint32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { #ifdef LOG_DEBUG Log3(this, "After cellN=%u blockN=%u target=%x vobu=%x start=%x" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } Log0(this, "Error when seeking"); printerr("Error when seeking."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_sector_search(dvdnav_t *this, int64_t offset, int32_t origin) { uint32_t target = 0; uint32_t current_pos; uint32_t cur_sector; uint32_t cur_cell_nr; uint32_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found; int forward = 0; const cell_playback_t *cell; dvd_state_t *state; dvdnav_status_t result; if(this->position_current.still != 0) { printerr("Cannot seek in a still frame."); return DVDNAV_STATUS_ERR; } result = dvdnav_get_position(this, &target, &length); if(!result) { printerr("Cannot get current position."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG Log3(this, "seeking to offset=%lld pos=%u length=%u", offset, target, length); Log3(this, "Before cellN=%u blockN=%u", state->cellN, state->blockN); #endif current_pos = target; cur_sector = this->vobu.vobu_start + this->vobu.blockN; cur_cell_nr = state->cellN; switch(origin) { case SEEK_SET: if(offset >= length) { printerr("Request to seek behind end."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target = offset; break; case SEEK_CUR: if((signed)target + offset >= length) { printerr("Request to seek behind end."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } if((signed)target + offset < 0) { printerr("Request to seek before start."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target += offset; break; case SEEK_END: if(length < offset) { printerr("Request to seek before start."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } target = length - offset; break; default: /* Error occurred */ printerr("Illegal seek mode."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } forward = target > current_pos; this->cur_cell_time = 0; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { /* Find start cell of program. */ first_cell_nr = state->pgc->program_map[state->pgN-1]; /* Find end cell of program */ if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } found = 0; for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) continue; length = cell->last_sector - cell->first_sector + 1; if (target >= length) { target -= length; } else { /* convert the target sector from Cell-relative to absolute physical sector */ target += cell->first_sector; if (forward && (cell_nr == cur_cell_nr)) { uint32_t vobu; /* if we are seeking forward from the current position, make sure * we move to a new position that is after our current position. * simply truncating to the vobu will go backwards */ if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) != DVDNAV_STATUS_OK) break; if (vobu <= cur_sector) { if (dvdnav_scan_admap(this, state->domain, target, 1, &vobu) != DVDNAV_STATUS_OK) break; if (vobu > cell->last_sector) { if (cell_nr == last_cell_nr) break; cell_nr++; cell = &(state->pgc->cell_playback[cell_nr-1]); target = cell->first_sector; } else { target = vobu; } } } found = 1; break; } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG Log3(this, "Seeking to cell %i from choice of %i to %i", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) == DVDNAV_STATUS_OK) { int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { #ifdef LOG_DEBUG Log3(this, "After cellN=%u blockN=%u target=%x vobu=%x start=%x" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } Log0(this, "Error when seeking"); Log1(this, "FIXME: Implement seeking to location %u", target); printerr("Error when seeking."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) { int32_t title, old_part; if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK) return dvdnav_part_play(this, title, part); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) { pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG Log3(this, "previous chapter"); #endif if (!vm_jump_prev_pg(this->vm)) { Log0(this, "previous chapter failed."); printerr("Skip to previous chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG Log3(this, "previous chapter done"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) { pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG Log3(this, "top chapter"); #endif if (!vm_jump_top_pg(this->vm)) { Log0(this, "top chapter failed."); printerr("Skip to top chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } this->cur_cell_time = 0; this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG Log3(this, "top chapter done"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) { vm_t *try_vm; pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); goto fail; } #ifdef LOG_DEBUG Log3(this, "next chapter"); #endif /* make a copy of current VM and try to navigate the copy to the next PG */ try_vm = vm_new_copy(this->vm); if (try_vm == NULL) { printerr("Unable to copy the VM."); goto fail; } if (!vm_jump_next_pg(try_vm) || try_vm->stopped) { vm_free_copy(try_vm); /* next_pg failed, try to jump at least to the next cell */ try_vm = vm_new_copy(this->vm); if (try_vm == NULL) { printerr("Unable to copy the VM."); goto fail; } vm_get_next_cell(try_vm); if (try_vm->stopped) { vm_free_copy(try_vm); Log0(this, "next chapter failed."); printerr("Skip to next chapter failed."); goto fail; } } this->cur_cell_time = 0; /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; #ifdef LOG_DEBUG Log3(this, "next chapter done"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; fail: pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) { vm_t *try_vm; pthread_mutex_lock(&this->vm_lock); if(!this->vm->state.pgc) { printerr("No current PGC."); goto fail; } this->cur_cell_time = 0; /* make a copy of current VM and try to navigate the copy to the menu */ try_vm = vm_new_copy(this->vm); if (try_vm == NULL) { printerr("Unable to copy VM."); goto fail; } if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != DVD_DOMAIN_VTSTitle)) { /* Try resume */ if (vm_jump_resume(try_vm) && !try_vm->stopped) { /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root; if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) { /* merge changes on success */ vm_merge(this->vm, try_vm); vm_free_copy(try_vm); this->position_current.still = 0; this->vm->hop_channel++; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } else { vm_free_copy(try_vm); printerr("No such menu or menu not reachable."); goto fail; } fail: pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos, uint32_t *len) { uint32_t cur_sector; int32_t cell_nr, first_cell_nr, last_cell_nr; const cell_playback_t *cell; dvd_state_t *state; if(!this->started) { printerr("Virtual DVD machine not started."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); state = &(this->vm->state); if(!state->pgc || this->vm->stopped) { printerr("No current PGC."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } if (this->position_current.hop_channel != this->vm->hop_channel || this->position_current.domain != state->domain || this->position_current.vts != state->vtsN || this->position_current.cell_restart != state->cell_restart) { printerr("New position not yet determined."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } /* Get current sector */ cur_sector = this->vobu.vobu_start + this->vobu.blockN; if (this->pgc_based) { first_cell_nr = 1; last_cell_nr = state->pgc->nr_of_cells; } else { /* Find start cell of program. */ first_cell_nr = state->pgc->program_map[state->pgN-1]; /* Find end cell of program */ if(state->pgN < state->pgc->nr_of_programs) last_cell_nr = state->pgc->program_map[state->pgN] - 1; else last_cell_nr = state->pgc->nr_of_cells; } *pos = -1; *len = 0; for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) { cell = &(state->pgc->cell_playback[cell_nr-1]); if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) continue; if (cell_nr == state->cellN) { /* the current sector is in this cell, * pos is length of PG up to here + sector's offset in this cell */ *pos = *len + cur_sector - cell->first_sector; } *len += cell->last_sector - cell->first_sector + 1; } pthread_mutex_unlock(&this->vm_lock); if((signed)*pos == -1) return DVDNAV_STATUS_ERR; return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this, uint32_t *pos, uint32_t *len) { uint32_t cur_sector; uint32_t first_cell_nr; uint32_t last_cell_nr; const cell_playback_t *first_cell; const cell_playback_t *last_cell; dvd_state_t *state; state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); return DVDNAV_STATUS_ERR; } if (state->pgc->program_map == NULL) { printerr("Program map missing."); return DVDNAV_STATUS_ERR; } /* Get current sector */ cur_sector = this->vobu.vobu_start + this->vobu.blockN; /* Now find first and last cells in title. */ first_cell_nr = state->pgc->program_map[0]; first_cell = &(state->pgc->cell_playback[first_cell_nr-1]); last_cell_nr = state->pgc->nr_of_cells; last_cell = &(state->pgc->cell_playback[last_cell_nr-1]); *pos = cur_sector - first_cell->first_sector; *len = last_cell->last_sector - first_cell->first_sector; return DVDNAV_STATUS_OK; } uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration) { int32_t retval=0; uint16_t parts, i; const title_info_t *ptitle = NULL; const ptt_info_t *ptt = NULL; ifo_handle_t *ifo = NULL; const pgc_t *pgc; const cell_playback_t *cell; uint64_t length, *tmp=NULL; *times = NULL; *duration = 0; pthread_mutex_lock(&this->vm_lock); if(!this->vm->vmgi) { printerr("Bad VM state or missing VTSI."); goto fail; } if(!this->started) { /* don't report an error but be nice */ vm_start(this->vm); this->started = 1; } ifo = vm_get_title_ifo(this->vm, title); if(!ifo || !ifo->vts_pgcit) { printerr("Couldn't open IFO for chosen title, exit."); retval = 0; goto fail; } ptitle = &this->vm->vmgi->tt_srpt->title[title-1]; parts = ptitle->nr_of_ptts; if(ptitle->vts_ttn == 0) goto fail; ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt; tmp = calloc(1, sizeof(uint64_t)*parts); if(!tmp) goto fail; if(!ptt) { printerr("ptt NULL"); goto fail; } length = 0; for(i=0; i ifo->vts_pgcit->nr_of_pgci_srp) { printerr("PGCN out of bounds."); continue; } if (ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte >= ifo->vts_pgcit->last_byte) { printerr("PGC start out of bounds"); continue; } if (0 == ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte) { printerr("PGC start zero."); continue; } if (0 != (ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte & 1)) { printerr("PGC start unaligned."); continue; } if (0 != ((uintptr_t)(ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc) & 1)) { printerr("PGC pointer unaligned."); continue; } pgc = ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc; if (pgc == NULL) { printerr("PGC missing."); continue; } if (pgc->program_map == NULL) { printerr("Program map missing."); continue; } if(ptt[i].pgn == 0 || ptt[i].pgn > pgc->nr_of_programs) { printerr("WRONG part number."); goto fail; } if (pgc->nr_of_cells == 0) { printerr("Number of cells cannot be 0"); continue; } if ((cellnr = pgc->program_map[ptt[i].pgn-1]) == 0) { printerr("Cell new row cannot be 0"); continue; } if (pgc->cell_playback == NULL) { printerr("Cell missing"); continue; } if(ptt[i].pgn < pgc->nr_of_programs) endcellnr = pgc->program_map[ptt[i].pgn]; else endcellnr = 0; do { cell = &pgc->cell_playback[cellnr-1]; if(!(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL )) { tmp[i] = length + dvdnav_convert_time(&cell->playback_time); length = tmp[i]; } cellnr++; } while(cellnr < endcellnr); } *duration = length; vm_ifo_close(ifo); ifo = NULL; retval = parts; *times = tmp; fail: pthread_mutex_unlock(&this->vm_lock); if(!retval && ifo) vm_ifo_close(ifo); if(!retval && tmp) free(tmp); return retval; } /* Get an admap and admap_len */ static vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state, int32_t *admap_len) { vobu_admap_t *admap = NULL; switch(state->domain) { case DVD_DOMAIN_FirstPlay: case DVD_DOMAIN_VMGM: admap = this->vm->vmgi->menu_vobu_admap; break; case DVD_DOMAIN_VTSMenu: admap = this->vm->vtsi->menu_vobu_admap; break; case DVD_DOMAIN_VTSTitle: admap = this->vm->vtsi->vts_vobu_admap; break; default: { Log1(this, "Unknown domain"); return NULL; } } if (admap == NULL) return NULL; *admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE; if (*admap_len <= 0) { Log1(this, "admap_len <= 0"); return NULL; } return admap; } /* Get a tmap, tmap_len and tmap_interval */ static const vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state, int32_t *tmap_len, int32_t *tmap_interval) { int32_t domain; ifo_handle_t *ifo = NULL; vts_tmapt_t *tmapt = NULL; uint16_t tmap_count = 0; int32_t pgcN = 0; const vts_tmap_t *tmap = NULL; int32_t result = 0; domain = state->domain; switch(domain) { case DVD_DOMAIN_FirstPlay: case DVD_DOMAIN_VTSMenu: case DVD_DOMAIN_VMGM: { ifo = this->vm->vmgi; break; } case DVD_DOMAIN_VTSTitle: { ifo = this->vm->vtsi; break; } default: { Log1(this, "unknown domain for tmap"); return NULL; } } if (ifo == NULL) return NULL; tmapt = ifo->vts_tmapt; /* HACK: ifo->vts_tmapt is NULL b/c ifo_read.c never loads it * load ifo->vts_tmapt directly*/ if (tmapt == NULL) { result = ifoRead_VTS_TMAPT(ifo); if (!result) { return NULL; } tmapt = ifo->vts_tmapt; if (tmapt == NULL) return NULL; } tmap_count = tmapt->nr_of_tmaps; pgcN = state->pgcN - 1; /* -1 b/c pgcN is base1 */ if (pgcN < 0) { Log1(this, "pgcN < 0"); return NULL; } /* get tmap */ switch(domain) { case DVD_DOMAIN_FirstPlay: case DVD_DOMAIN_VMGM: case DVD_DOMAIN_VTSMenu: { if (tmap_count == 0) { Log1(this, "tmap_count == 0"); return NULL; } tmap = &tmapt->tmap[0]; /* ASSUME: vmgi only has one time map */ break; } case DVD_DOMAIN_VTSTitle: { if (pgcN >= tmap_count) { Log1(this, "pgcN >= tmap_count; pgcN=%i tmap_count=%i", pgcN, tmap_count); return NULL; } tmap = &tmapt->tmap[pgcN]; break; } } if (tmap == NULL) return NULL; /* tmap->tmu is in seconds; convert to millisecs */ *tmap_interval = tmap->tmu * 1000; if (*tmap_interval == 0) { Log1(this, "tmap_interval == 0"); return NULL; } *tmap_len = tmap->nr_of_entries; if (*tmap_len == 0) { Log1(this, "tmap_len == 0"); return NULL; } return tmap; } /* Get a sector from a tmap */ static int32_t dvdnav_tmap_get_entry(dvdnav_t *this, const vts_tmap_t *tmap, uint16_t tmap_len, int32_t idx, uint32_t *sector) { /* tmaps start at idx 0 which represents a sector at time 1 * tmap_interval * this creates a "fake" tmap index at idx -1 for sector 0 */ if (idx == TMAP_IDX_EDGE_BGN) { *sector = 0; return 1; } if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) { Log1(this, "idx out of bounds idx=%i %i", idx, tmap_len); return 0; } /* 0x7fffffff unsets discontinuity bit if present */ *sector = tmap->map_ent[idx] & 0x7fffffff; return 1; } /* Do a binary search for earlier admap index near find_sector */ static int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len, uint32_t find_sector, uint32_t *vobu) { int32_t adj = 1; int32_t prv_pos = 0; int32_t prv_len = admap_len; int32_t cur_len = 0; int32_t cur_idx = 0; uint32_t cur_sector = 0; while (1) { cur_len = prv_len / 2; /* need to add 1 when prv_len == 3 (cur_len should go to 2, not 1) */ if (prv_len % 2 == 1) ++cur_len; cur_idx = prv_pos + (cur_len * adj); if (cur_idx < 0) cur_idx = 0; else if (cur_idx >= (int32_t)admap_len) cur_idx = admap_len - 1; cur_sector = admap->vobu_start_sectors[cur_idx]; if (find_sector < cur_sector) adj = -1; else if (find_sector > cur_sector) adj = 1; else if (find_sector == cur_sector) { *vobu = cur_idx; return 1; } if (cur_len == 1) {/* no smaller intervals left */ if (adj == -1) {/* last comparison was greater; take lesser */ cur_idx -= 1; cur_sector = admap->vobu_start_sectors[cur_idx]; } *vobu = cur_idx; return 1; } prv_len = cur_len; prv_pos = cur_idx; } } /* Do a binary search for the earlier tmap entry near find_sector */ static int32_t dvdnav_tmap_search(dvdnav_t *this, const vts_tmap_t *tmap, uint32_t tmap_len, uint32_t find_sector, int32_t *tmap_idx, uint32_t *sector) { int32_t adj = 1; int32_t prv_pos = 0; int32_t prv_len = tmap_len; int32_t result = 0; int32_t cur_len = 0; int32_t cur_idx = 0; uint32_t cur_sector = 0; while (1) { cur_len = prv_len / 2; /* need to add 1 when prv_len == 3 (cur_len should go to 2, not 1) */ if (prv_len % 2 == 1) ++cur_len; cur_idx = prv_pos + (cur_len * adj); if (cur_idx < 0) cur_idx = 0; else if (cur_idx >= (int32_t)tmap_len) cur_idx = tmap_len - 1; cur_sector = 0; result = dvdnav_tmap_get_entry(this, tmap, tmap_len, cur_idx, &cur_sector); if (!result) return 0; if (find_sector < cur_sector) adj = -1; else if (find_sector > cur_sector) adj = 1; else if (find_sector == cur_sector) { *tmap_idx = cur_idx; *sector = cur_sector; return 1; } if (cur_len == 1) {/* no smaller intervals left */ if (adj == -1) {/* last comparison was greater; take lesser */ if (cur_idx == 0) { /* fake tmap index for sector 0 */ cur_idx = TMAP_IDX_EDGE_BGN; cur_sector = 0; } else { cur_idx -= 1; result = dvdnav_tmap_get_entry(this, tmap, tmap_len, cur_idx, &cur_sector); if (!result) return 0; } } *tmap_idx = cur_idx; *sector = cur_sector; return 1; } prv_len = cur_len; prv_pos = cur_idx; } } /* Find the cell for a given time */ static int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state, uint64_t find_val, dvdnav_cell_data_t *cell_data) { uint32_t cells_len = 0; uint32_t cells_bgn = 0; uint32_t cells_end = 0; uint32_t cell_idx = 0; const pgc_t *pgc = NULL; int pgN = 0; const cell_playback_t *cell = NULL; int found = 0; pgc = state->pgc; if (pgc == NULL) return 0; cells_len = pgc->nr_of_cells; if (cells_len == 0) { Log1(this, "cells_len == 0"); return 0; } /* get cells_bgn, cells_end */ if (this->pgc_based) { cells_bgn = 1; cells_end = cells_len; } else { pgN = state->pgN; cells_bgn = pgc->program_map[pgN - 1]; /* -1 b/c pgN is 1 based? */ if (pgN < pgc->nr_of_programs) { cells_end = pgc->program_map[pgN] - 1; } else { cells_end = cells_len; } } /* search cells */ for (cell_idx = cells_bgn; cell_idx <= cells_end; cell_idx++) { cell = &(pgc->cell_playback[cell_idx - 1]); /* -1 b/c cell is base1 */ /* if angle block, only consider first angleBlock * (others are "redundant" for purpose of search) */ if ( cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) { continue; } cell_data->bgn->sector = cell->first_sector; cell_data->end->sector = cell->last_sector; /* 90 pts to ms */ cell_data->end->time += (dvdnav_convert_time(&cell->playback_time) / 90); if ( find_val >= cell_data->bgn->time && find_val <= cell_data->end->time) { found = 1; break; } cell_data->bgn->time = cell_data->end->time; } /* found cell: set var */ if (found) { cell_data->idx = cell_idx; } else Log1(this, "cell not found; find=%"PRId64, find_val); return found; } /* Given two sectors and a fraction, calc the corresponding vobu */ static int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this, dvdnav_jump_args_t *args, dvdnav_pos_data_t *bgn, dvdnav_pos_data_t *end, uint32_t fraction, uint32_t *jump_sector) { int32_t result = 0; uint32_t vobu_len = 0; uint32_t vobu_adj = 0; uint32_t vobu_idx = 0; /* get bgn->vobu_idx */ result = dvdnav_admap_search(args->admap, args->admap_len, bgn->sector, &bgn->vobu_idx); if (!result) { Log1(this, "admap_interpolate: could not find sector_bgn"); return 0; } /* get end->vobu_idx */ result = dvdnav_admap_search(args->admap, args->admap_len, end->sector, &end->vobu_idx); if (!result) { Log1(this, "admap_interpolate: could not find sector_end"); return 0; } vobu_len = end->vobu_idx - bgn->vobu_idx; /* +500 to round up else 74% of a 4 sec interval = 2 sec */ vobu_adj = ((fraction * vobu_len) + 500) / 1000; /* HACK: need to add +1, or else will land too soon (not sure why) */ vobu_adj++; vobu_idx = bgn->vobu_idx + vobu_adj; if ((int32_t)vobu_idx >= args->admap_len) { Log1(this, "admap_interpolate: vobu_idx >= admap_len"); return 0; } *jump_sector = args->admap->vobu_start_sectors[vobu_idx]; return 1; } /* Given two tmap entries and a time, calc the time for the lo tmap entry */ static int32_t dvdnav_tmap_calc_time_for_tmap_entry(dvdnav_t *this, dvdnav_jump_args_t *args, dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi, dvdnav_pos_data_t *pos, uint64_t *out_time) { int32_t result = 0; int32_t vobu_pct = 0; uint64_t time_adj = 0; if (lo->sector == hi->sector) { Log1(this, "lo->sector == hi->sector: %i", lo->sector); return 0; } /* get vobus corresponding to lo, hi, pos */ result = dvdnav_admap_search(args->admap, args->admap_len, lo->sector, &lo->vobu_idx); if (!result) { Log1(this, "lo->vobu: lo->sector=%i", lo->sector); return 0; } result = dvdnav_admap_search(args->admap, args->admap_len, hi->sector, &hi->vobu_idx); if (!result) { Log1(this, "hi->vobu: hi->sector=%i", hi->sector); return 0; } result = dvdnav_admap_search(args->admap, args->admap_len, pos->sector, &pos->vobu_idx); if (!result) { Log1(this, "pos->vobu: pos->sector=%i", pos->sector); return 0; } /* calc position of cell relative to lo */ if (hi->vobu_idx == lo->vobu_idx) { /* We are at the very end - pos should also equal lo so force that * rather than hit the divide-by-zero. */ vobu_pct = 0; } else { vobu_pct = ((pos->vobu_idx - lo->vobu_idx) * 1000) / ( hi->vobu_idx - lo->vobu_idx); } if (vobu_pct < 0 || vobu_pct > 1000) { Log1(this, "vobu_pct must be between 0 and 1000"); return 0; } /* calc time of lo */ time_adj = ((uint64_t)args->tmap_interval * vobu_pct) / 1000; *out_time = pos->time - time_adj; return 1; } /* Find the tmap entries on either side of a given sector */ static int32_t dvdnav_tmap_get_entries_for_sector( dvdnav_t *this, dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data, uint32_t find_sector, dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi) { int32_t result = 0; result = dvdnav_tmap_search(this, args->tmap, args->tmap_len, find_sector, &lo->tmap_idx, &lo->sector); if (!result) { Log1(this, "could not find lo idx: %i", find_sector); return 0; } /* HACK: Most DVDs have a tmap that starts at sector 0 * However, some have initial dummy cells that are not seekable * (restricted = y). * These cells will throw off the tmap calcs when in the first playable cell. * For now, assume that lo->sector is equal to the cell->bgn->sector * Note that for most DVDs this will be 0 * (Since they will have no dummy cells and cell 1 will start at sector 0) */ if (lo->tmap_idx == TMAP_IDX_EDGE_BGN) { lo->sector = cell_data->bgn->sector; } if (lo->tmap_idx == args->tmap_len - 1) { /* lo is last tmap entry; "fake" entry for one beyond * and mark it with cell_end_sector */ hi->tmap_idx = TMAP_IDX_EDGE_END; hi->sector = cell_data->end->sector; } else { hi->tmap_idx = lo->tmap_idx + 1; result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, hi->tmap_idx, &hi->sector); if (!result) { Log1(this, "could not find hi idx: %i", find_sector); return 0; } } return 1; } /* Find the nearest vobu by using the tmap */ static int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state, dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data, dvdnav_pos_data_t *jump) { uint64_t seek_offset = 0; uint32_t seek_idx = 0; int32_t result = 0; dvdnav_pos_data_t *cell_bgn_lo = NULL; dvdnav_pos_data_t *cell_bgn_hi = NULL; dvdnav_pos_data_t *jump_lo = NULL; dvdnav_pos_data_t *jump_hi = NULL; /* get tmap, tmap_len, tmap_interval */ args->tmap = dvdnav_tmap_get(this, state, &args->tmap_len, &args->tmap_interval); if (args->tmap == NULL) return 0; /* get tmap entries on either side of cell_bgn */ cell_bgn_lo = &(dvdnav_pos_data_t){0}; cell_bgn_hi = &(dvdnav_pos_data_t){0}; result = dvdnav_tmap_get_entries_for_sector(this, args, cell_data, cell_data->bgn->sector, cell_bgn_lo, cell_bgn_hi); if (!result) return 0; /* calc time of cell_bgn_lo */ result = dvdnav_tmap_calc_time_for_tmap_entry(this, args, cell_bgn_lo, cell_bgn_hi, cell_data->bgn, &cell_bgn_lo->time); if (!result) return 0; /* calc time of jump_time relative to cell_bgn_lo */ seek_offset = jump->time - cell_bgn_lo->time; seek_idx = (uint32_t)(seek_offset / args->tmap_interval); uint32_t seek_remainder = seek_offset - (seek_idx * args->tmap_interval); uint32_t seek_pct = (seek_remainder * 1000) / args->tmap_interval; /* get tmap entries on either side of jump_time */ jump_lo = &(dvdnav_pos_data_t){0}; jump_hi = &(dvdnav_pos_data_t){0}; /* if seek_idx == 0, then tmap_indexes are the same, do not re-get * also, note cell_bgn_lo will already have sector if TMAP_IDX_EDGE_BGN */ if (seek_idx == 0) { jump_lo = cell_bgn_lo; jump_hi = cell_bgn_hi; } else { jump_lo->tmap_idx = (uint32_t)(cell_bgn_lo->tmap_idx + seek_idx); result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, jump_lo->tmap_idx, &jump_lo->sector); if (!result) return 0; /* +1 handled by dvdnav_tmap_get_entry */ jump_hi->tmap_idx = jump_lo->tmap_idx + 1; result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, jump_hi->tmap_idx, &jump_hi->sector); if (!result) return 0; } /* interpolate sector */ result = dvdnav_admap_interpolate_vobu(this, args, jump_lo, jump_hi, seek_pct, &jump->sector); return result; } /* Find the nearest vobu by using the cell boundaries */ static int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this, dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data, dvdnav_pos_data_t *jump) { int64_t jump_offset = 0; int64_t cell_len = 0; uint32_t jump_pct = 0; int32_t result = 0; /* get jump_offset */ jump_offset = jump->time - cell_data->bgn->time; if (jump_offset < 0) { Log1(this, "jump_offset < 0"); return 0; } cell_len = cell_data->end->time - cell_data->bgn->time; if (cell_len < 0) { Log1(this, "cell_len < 0"); return 0; } jump_pct = (jump_offset * 1000) / cell_len; /* get sector */ /* NOTE: end cell sector in VTS_PGC is last sector of cell * this last sector is not the start of a VOBU * +1 to get sector that is the start of a VOBU * start of a VOBU is needed in order to index into admap */ cell_data->end->sector += 1; result = dvdnav_admap_interpolate_vobu(this, args, cell_data->bgn, cell_data->end, jump_pct, &jump->sector); if (!result) { Log1(this, "find_by_admap.interpolate"); return 0; } return 1; } /* Jump to sector by time */ /* NOTE: Mode is currently unimplemented. Only 0 should be passed. */ /* 1 and -1 are for future implementation */ /* 0: Default. Jump to a time which may be either <> time_in_pts_ticks */ /* 1: After. Always jump to a time that is > time_in_pts_ticks */ /* -1: Before. Always jump to a time that is < time_in_pts_ticks */ dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, uint64_t time_in_pts_ticks, int32_t mode) { if (mode != JUMP_MODE_TIME_DEFAULT) return DVDNAV_STATUS_ERR; int32_t result = DVDNAV_STATUS_ERR; dvd_state_t *state = NULL; uint32_t sector_off = 0; dvdnav_pos_data_t *jump = NULL; dvdnav_cell_data_t *cell_data = NULL; dvdnav_jump_args_t *args = NULL; jump = &(dvdnav_pos_data_t){0}; /* convert time to milliseconds */ jump->time = time_in_pts_ticks / 90; /* get variables that will be used across both functions */ state = &(this->vm->state); if (state == NULL) goto exit; /* get cell info */ cell_data = &(dvdnav_cell_data_t){0}; cell_data->bgn = &(dvdnav_pos_data_t){0}; cell_data->end = &(dvdnav_pos_data_t){0}; result = dvdnav_cell_find(this, state, jump->time, cell_data); if (!result) goto exit; /* get admap */ args = &(dvdnav_jump_args_t){0}; args->admap = dvdnav_admap_get(this, state, &args->admap_len); if (args->admap == NULL) goto exit; /* find sector */ result = dvdnav_find_vobu_by_tmap(this, state, args, cell_data, jump); if (!result) {/* bad tmap; interpolate over cell */ result = dvdnav_find_vobu_by_cell_boundaries(this, args, cell_data, jump); if (!result) { goto exit; } } /* jump to sector */ sector_off = jump->sector - cell_data->bgn->sector; result = vm_jump_cell_block(this->vm, cell_data->idx, sector_off); pthread_mutex_lock(&this->vm_lock); this->cur_cell_time = 0; if (result) { /* vm_jump_cell_block was successful */ this->vm->hop_channel += HOP_SEEK; } pthread_mutex_unlock(&this->vm_lock); exit: return result; }