/* * 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: searching.c 1135 2008-09-06 21:55:51Z rathann $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "dvd_types.h" #include #include #include "remap.h" #include "vm/decoder.h" #include "vm/vm.h" #include "dvdnav.h" #include "dvdnav_internal.h" /* #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, uint32_t *vobu) { vobu_admap_t *admap = NULL; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block); #endif *vobu = -1; /* Search through the VOBU_ADMAP for the nearest VOBU * to the target block */ switch(domain) { case FP_DOMAIN: case VMGM_DOMAIN: admap = this->vm->vmgi->menu_vobu_admap; break; case VTSM_DOMAIN: admap = this->vm->vtsi->menu_vobu_admap; break; case VTS_DOMAIN: admap = this->vm->vtsi->vts_vobu_admap; break; default: fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n"); } if(admap) { uint32_t address = 0; uint32_t vobu_start, next_vobu; int 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) break; vobu_start = next_vobu; address++; } *vobu = vobu_start; return DVDNAV_STATUS_OK; } fprintf(MSG_OUT, "libdvdnav: admap not located\n"); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_time_search(dvdnav_t *this, uint64_t time) { uint32_t target; uint64_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found = 0; 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; } if((state->pgc->prohibited_ops.title_or_time_play == 1) || (this->pci.pci_gi.vobu_uop_ctl.title_or_time_play == 1 )){ printerr("operation forbidden."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } /* setup what cells we should be working within */ 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; } /* FIXME: using time map is not going to work unless we are pgc_based */ /* we'd need to recalculate the time to be relative to full pgc first*/ if(!this->pgc_based) { #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: time_search - not pgc based\n"); #endif goto timemapdone; } if(!this->vm->vtsi->vts_tmapt){ /* no time map for this program chain */ #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: time_search - no time map for this program chain\n"); #endif goto timemapdone; } if(this->vm->vtsi->vts_tmapt->nr_of_tmaps < state->pgcN){ /* to few time maps for this program chain */ #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: time_search - to few time maps for this program chain\n"); #endif goto timemapdone; } /* get the tmpat corresponding to the pgc */ vts_tmap_t *tmap = &(this->vm->vtsi->vts_tmapt->tmap[state->pgcN-1]); if(tmap->tmu == 0){ /* no time unit for this time map */ #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: time_search - no time unit for this time map\n"); #endif goto timemapdone; } /* time is in pts (90khz clock), get the number of tmu's that represent */ /* first entry defines at time tmu not time zero */ int entry = time / tmap->tmu / 90000 - 1; if(entry > tmap->nr_of_entries) entry = tmap->nr_of_entries -1; if(entry > 0) { /* get the table entry, disregarding marking of discontinuity */ target = tmap->map_ent[entry] & 0x7fffffff; } else { /* start from first vobunit */ target = state->pgc->cell_playback[first_cell_nr-1].first_sector;; } /* if we have an additional entry we can interpolate next position */ /* allowed only if next entry isn't discontinious */ if( entry < tmap->nr_of_entries - 1) { const uint32_t target2 = tmap->map_ent[entry+1]; const uint64_t timeunit = tmap->tmu*90000; if( !( target2 & 0x80000000) ) { length = target2 - target; target += (uint32_t) (length * ( time - (entry+1)*timeunit ) / timeunit); } } found = 1; timemapdone: 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(found) { if (target >= cell->first_sector && target <= cell->last_sector) break; } else { length = dvdnav_convert_time(&cell->playback_time); if (time >= length) { time -= length; } else { /* FIXME: there must be a better way than interpolation */ target = time * (cell->last_sector - cell->first_sector + 1) / length; target += cell->first_sector; #ifdef LOG_DEBUG if( cell->first_sector > target || target > cell->last_sector ) fprintf(MSG_OUT, "libdvdnav: time_search - sector is not within cell min:%u, max:%u, cur:%u\n", cell->first_sector, cell->last_sector, target); #endif found = 1; break; } } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, &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 fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); printerr("Error when seeking."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } dvdnav_status_t dvdnav_sector_search(dvdnav_t *this, uint64_t offset, int32_t origin) { uint32_t target = 0; uint32_t length = 0; uint32_t first_cell_nr, last_cell_nr, cell_nr; int32_t found; 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 fprintf(MSG_OUT, "libdvdnav: seeking to offset=%llu pos=%u length=%u\n", offset, target, length); fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN); #endif 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(target + offset >= length) { printerr("Request to seek behind end."); 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 occured */ printerr("Illegal seek mode."); 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 = 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; found = 1; break; } } if(found) { uint32_t vobu; #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", cell_nr, first_cell_nr, last_cell_nr); #endif if (dvdnav_scan_admap(this, state->domain, target, &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 fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , state->cellN, state->blockN, target, vobu, start); #endif this->vm->hop_channel += HOP_SEEK; pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } } } fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", 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 fprintf(MSG_OUT, "libdvdnav: previous chapter\n"); #endif if (!vm_jump_prev_pg(this->vm)) { fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n"); 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 fprintf(MSG_OUT, "libdvdnav: previous chapter done\n"); #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 fprintf(MSG_OUT, "libdvdnav: top chapter\n"); #endif if (!vm_jump_top_pg(this->vm)) { fprintf(MSG_OUT, "libdvdnav: top chapter failed.\n"); 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 fprintf(MSG_OUT, "libdvdnav: top chapter done\n"); #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."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } #ifdef LOG_DEBUG fprintf(MSG_OUT, "libdvdnav: next chapter\n"); #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 (!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); vm_get_next_cell(try_vm); if (try_vm->stopped) { vm_free_copy(try_vm); fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n"); printerr("Skip to next chapter failed."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } } 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 fprintf(MSG_OUT, "libdvdnav: next chapter done\n"); #endif pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } 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."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } 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 ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) { /* 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."); 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; 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_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; } assert((signed)*pos != -1); pthread_mutex_unlock(&this->vm_lock); 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; cell_playback_t *first_cell; cell_playback_t *last_cell; dvd_state_t *state; state = &(this->vm->state); if(!state->pgc) { printerr("No current PGC."); 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; title_info_t *ptitle = NULL; ptt_info_t *ptt = NULL; ifo_handle_t *ifo; pgc_t *pgc; 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."); goto fail; } ptitle = &this->vm->vmgi->tt_srpt->title[title-1]; parts = ptitle->nr_of_ptts; ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt; tmp = calloc(1, sizeof(uint64_t)*parts); if(!tmp) goto fail; length = 0; for(i=0; ivts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc; if(ptt[i].pgn > pgc->nr_of_programs) { printerr("WRONG part number."); goto fail; } cellnr = pgc->program_map[ptt[i].pgn-1]; 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); retval = parts; *times = tmp; fail: pthread_mutex_unlock(&this->vm_lock); if(!retval && tmp) free(tmp); return retval; } dvdnav_status_t dvdnav_get_state(dvdnav_t *this, dvd_state_t *save_state) { if(!this || !this->vm) return DVDNAV_STATUS_ERR; pthread_mutex_lock(&this->vm_lock); if( !vm_get_state(this->vm, save_state) ) { printerr("Failed to get vm state."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; } dvdnav_status_t dvdnav_set_state(dvdnav_t *this, dvd_state_t *save_state) { if(!this || !this->vm) { printerr("Passed a NULL pointer."); return DVDNAV_STATUS_ERR; } if(!this->started) { printerr("Virtual DVD machine not started."); return DVDNAV_STATUS_ERR; } pthread_mutex_lock(&this->vm_lock); /* reset the dvdnav state */ memset(&this->pci,0,sizeof(this->pci)); memset(&this->dsi,0,sizeof(this->dsi)); this->last_cmd_nav_lbn = SRI_END_OF_CELL; /* Set initial values of flags */ this->position_current.still = 0; this->skip_still = 0; this->sync_wait = 0; this->sync_wait_skip = 0; this->spu_clut_changed = 0; /* set the state. this will also start the vm on that state */ /* means the next read block should be comming from that new */ /* state */ if( !vm_set_state(this->vm, save_state) ) { printerr("Failed to set vm state."); pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_ERR; } pthread_mutex_unlock(&this->vm_lock); return DVDNAV_STATUS_OK; }