Code: Select all
Entries = tmap_idx sector time
0 711 4
1 1776 8
2 2790 12
Code: Select all
Entries = vobu sector
0 0
1 43
etc.
9 711
10 831
11 948
12 1080
13 1213
14 1349
15 1493
16 1642
17 1776
Code: Select all
/*
* Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
*
* 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 <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
//#include "dvd_types.h"
#include "dvdnav/dvdnav.h"
#include <dvdread/nav_types.h>
#include <dvdread/ifo_types.h>
#include "remap.h"
#include "vm/decoder.h"
#include "vm/vm.h"
//#include "dvdnav.h"
#include "dvdnav_internal.h"
/*
#define LOG_DEBUG
*/
#include <dvdread/ifo_read.h>
#define RESULT_FALSE 0
#define RESULT_TRUE 1
#define CELL_FIND_SECTOR 1
#define CELL_FIND_TIME 2
/*
proc:
check_null
in:
@val: pointer to check
@val_name: name of pointer
out:
RESULT_TRUE if null
RESULT_FALSE if not-null
notes:
this proc...
- returns true if null
calling procs can then handle null refs accordingly
EX: if (check_null(&ptr)) {return;}
- assists logging
*/
static int32_t check_null(void *val, const char *val_name) {
char msg_str[255];
if (val) {
strcpy (msg_str, " ");
strcat (msg_str, val_name);
strcat (msg_str, "=y");
fprintf(MSG_OUT, msg_str);
return RESULT_FALSE;
}
else {
strcpy (msg_str, "\nlibdvdnav.time_search: ERR: NULL REF: ");
strcat (msg_str, val_name);
fprintf(MSG_OUT, msg_str);
return RESULT_TRUE;
}
}
/*
proc:
dvdnav_tmap_get_entry
in:
@tmap: tmap instance
@entry_count: used for bounds check
@entry_idx: index to lookup
@out_sector: sector val
out:
RESULT_TRUE if found
RESULT_FALSE if not
notes:
this proc...
- does bounds checking
- gets a sector from a tmap
- unsets discontinuity bit (if present)
*/
static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t entry_count, uint32_t entry_idx, uint32_t *out_sector) {
if (entry_idx >= entry_count) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: entry_idx >= entry_count");
return RESULT_FALSE;
}
int32_t sector = tmap->map_ent[entry_idx];
if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set
sector &= ~(1 << 31); // unset bit
}
if (sector < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: sector < 0; discontinuity?");
return RESULT_FALSE;
}
*out_sector = sector;
return RESULT_TRUE;
}
/*
proc:
dvdnav_cell_find
in:
@this: dvdnav instance
@find_mode:
- CELL_FIND_SECTOR: find cell that has this sector
- CELL_FIND_TIME: find cell that has this time
@find_val: value to find
@out_cell_idx: index of found cell
@out_sector_bgn: sector_bgn of found cell; needed when CELL_FIND_TIME
@out_sector_end: sector_end of found cell; needed when CELL_FIND_TIME
@out_time_bgn: time_bgn of found cell; needed when CELL_FIND_SECTOR
@out_time_end: time_end of found cell; needed when CELL_FIND_SECTOR
out:
RESULT_TRUE if found
RESULT_FALSE if not
notes:
this proc finds a cell, according to the find_args
the @find_mode/@find_val design is sloppy
however, there is specific cell traversal logic that I did not want to duplicate
*/
static int32_t dvdnav_cell_find(dvdnav_t *this, int32_t find_mode, uint64_t find_val, int32_t *out_cell_idx, uint32_t *out_sector_bgn, uint32_t *out_sector_end, uint64_t *out_time_bgn, uint64_t *out_time_end) {
// output msg
if (find_mode == CELL_FIND_SECTOR)
fprintf(MSG_OUT, " find_mode=sector");
else if (find_mode == CELL_FIND_TIME)
fprintf(MSG_OUT, " find_mode=time");
fprintf(MSG_OUT, " find_val=%"PRId64, find_val);
// get state
dvd_state_t *state = &(this->vm->state);
if (check_null(&state, "state")) return RESULT_FALSE;
// get pgc
pgc_t *pgc = state->pgc;
if (check_null(&pgc, "pgc")) return RESULT_FALSE;
// get cell_count
uint32_t cell_count = pgc->nr_of_cells;
if (cell_count == 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: cell_count == 0");
return RESULT_FALSE;
}
// get cell_bgn, cell_end
uint32_t cell_bgn, cell_end;
if (this->pgc_based) {
fprintf(MSG_OUT, " pgc_based=y");
cell_bgn = 1;
cell_end = cell_count;
}
else {
int pgN = state->pgN;
fprintf(MSG_OUT, " pgN=%i", pgN);
cell_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based?
int program_count = pgc->nr_of_programs;
fprintf(MSG_OUT, " program_count=%i", program_count);
if (pgN < program_count) {
fprintf(MSG_OUT, " pgN < program_count=y");
cell_end = pgc->program_map[pgN] - 1;
}
else {
fprintf(MSG_OUT, " pgN >= program_count=y");
cell_end = cell_count;
}
}
fprintf(MSG_OUT, " cell_bgn=%i", cell_bgn);
fprintf(MSG_OUT, " cell_end=%i", cell_end);
// search cells
uint32_t cell_idx, sector_bgn = 0, sector_end = 0;
uint64_t time_bgn = 0, time_end = 0;
cell_playback_t *cell;
int located = RESULT_FALSE;
for (cell_idx = cell_bgn; cell_idx <= cell_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;
}
sector_bgn = cell->first_sector;
sector_end = cell->last_sector;
time_end += (dvdnav_convert_time(&cell->playback_time) / 90); // 90: pts to ms
if (find_mode == CELL_FIND_SECTOR) {
if (find_val >= sector_bgn && find_val <= sector_end) {
located = RESULT_TRUE;
break;
}
}
else if (find_mode == CELL_FIND_TIME) {
if (find_val >= time_bgn && find_val <= time_end) {
located = RESULT_TRUE;
break;
}
}
time_bgn = time_end;
}
// found cell: set *out vars
if (located == RESULT_TRUE) {
fprintf(MSG_OUT, " found cell; cell_idx=%i", cell_idx);
fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn);
fprintf(MSG_OUT, " sector_end=%i", sector_end);
fprintf(MSG_OUT, " time_bgn=%"PRId64, time_bgn);
fprintf(MSG_OUT, " time_end=%"PRId64, time_end);
*out_cell_idx = cell_idx;
*out_sector_bgn = sector_bgn;
*out_sector_end = sector_end;
*out_time_bgn = time_bgn;
*out_time_end = time_end;
}
else
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: cell not found");
return located;
}
/*
proc:
dvdnav_tmap_is_valid
in:
@this: dvdnav instance
@tmap: tmap instance
@entry_count: # of entries in tmap; assumed to be valid
@time_interval: time (in seconds) separating entries; assumed to be valid
out:
RESULT_TRUE if valid
RESULT_FALSE if not
notes:
this proc currently validates a time map according to two criteria
1. checks if 0 entries
2. checks entry0 to make certain it is valid in context of its cell
this may be easier explained by example
entry0 lists sector 4500
time_interval is 4
this means that time of 4 seconds should be in a cell with
- a sector_bgn < 4500
- a sector_end > 4500
so, iterate over the cells to find cell for 4 secs
@cell_idx for 4 secs is 8: it has time_bgn of 3 secs and time_end of 5 secs
however, it has a sector_bgn of 3000 and a sector_end of 4000
since this contradicts the time_map, the time_map is considered not valid
*/
static int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap, uint32_t entry_count, uint32_t time_interval) {
// tmap with 0 entries are invalid
if (entry_count == 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_entry_count == 0");
return RESULT_FALSE;
}
// get cell corresponding to entry0
int32_t cell_idx;
uint32_t sector_bgn = 0, sector_end = 0;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_interval, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_valid cell_find");
fprintf(MSG_OUT, " time=%i", time_interval);
return RESULT_FALSE;
}
// get sector for entry 0
uint32_t entry0_sector = 0;
result = dvdnav_tmap_get_entry(tmap, entry_count, 0, &entry0_sector);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_valid tmap_get_sector");
fprintf(MSG_OUT, " idx=%i", 0);
return RESULT_FALSE;
}
// check entry0_sector is between corresponding cell_sectors
if (entry0_sector >= sector_bgn && entry0_sector <= sector_end) {
fprintf(MSG_OUT, " tmap_valid=y");
return RESULT_TRUE;
}
else {
fprintf(MSG_OUT, " tmap_valid=n");
fprintf(MSG_OUT, " entry0_sector=%i", entry0_sector);
fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn);
fprintf(MSG_OUT, " sector_end=%i", sector_end);
return RESULT_FALSE;
}
}
/*
proc:
dvdnav_tmap_find_by_time
in:
@this: dvdnav instance
@time_in_ms: time to find
@out_tmap_idx: index of found entry
@out_time_interval: time_interval in map
@out_sector_bgn: sector of found entry
@out_sector_end: sector of next extry
out:
RESULT_TRUE if found
RESULT_FALSE if not
todo:
error handling for ifo retrieval
notes:
this proc scans the time_map to find the entry for @time_in_ms
*/
static int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms, uint32_t *out_tmap_idx, uint32_t *out_time_interval, uint32_t *out_sector_bgn, uint32_t *out_sector_end) {
fprintf(MSG_OUT, " dvdnav_tmap_find_by_time time_in_ms=%i", time_in_ms);
// get state
dvd_state_t *state = &(this->vm->state);
if (check_null(&state, "state")) return RESULT_FALSE;
// get domain
domain_t domain = state->domain;
fprintf(MSG_OUT, " domain=%i", domain);
// get vts_idx
int32_t vts_idx = state->vtsN; // int
fprintf(MSG_OUT, " vts_idx=%i", vts_idx);
// get ifo
// TODO: error handling for ifo retrieval
ifo_handle_t *ifo = NULL;
switch(domain) {
case FP_DOMAIN:
case VTSM_DOMAIN:
case VMGM_DOMAIN: {
// NOTE: ifo = this->vm->vmgi does not work
ifo = ifoOpenVMGI(this->vm->dvd);
fprintf(MSG_OUT, " ifo=non-title");
break;
}
case VTS_DOMAIN: {
// NOTE: ifo = this->vm->vtsi doesn't work
ifo = ifoOpen(this->vm->dvd, vts_idx);
fprintf(MSG_OUT, " ifo=title");
break;
}
default: {// NOTE: will not check out-of-range domain again
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: Unknown domain");
return RESULT_FALSE;
}
}
if (check_null(&ifo, "ifo")) return RESULT_FALSE;
// get tmapt
vts_tmapt_t *tmapt = ifo->vts_tmapt;
if (check_null(&tmapt, "tmapt")) return RESULT_FALSE;
// get tmap_count
uint16_t tmap_count = tmapt->nr_of_tmaps;
fprintf(MSG_OUT, " tmap_count=%i", tmap_count);
// get pgcN; -1 b/c pgcN is base1
int32_t pgcN = (state->pgcN) - 1; // int
fprintf(MSG_OUT, " pgcN=%i", pgcN);
if (pgcN < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: pgcN < 0");
return RESULT_FALSE;
}
// get tmap
vts_tmap_t *tmap = NULL;
switch(domain) {
case FP_DOMAIN:
case VMGM_DOMAIN:
case VTSM_DOMAIN: {
if (tmap_count == 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap_count == 0");
return RESULT_FALSE;
}
tmap = &(tmapt->tmap[0]); // ASSUME: vmgi only has one time map
fprintf(MSG_OUT, " tmap=non-title");
break;
}
case VTS_DOMAIN: {
if (pgcN >= tmap_count) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: pgcN >= tmap_count");
return RESULT_FALSE;
}
tmap = &(tmapt->tmap[pgcN]);
fprintf(MSG_OUT, " tmap=title");
break;
}
}
if (check_null(&tmap, "tmap")) return RESULT_FALSE;
// get time_interval; tmap->tmu is in seconds
uint32_t time_interval = (tmap->tmu) * 1000; // * 1000 converts to millisecs
fprintf(MSG_OUT, " time_interval=%i", time_interval);
if (time_interval == 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_interval == 0");
return RESULT_FALSE;
}
// check tmap is valid
if (!dvdnav_tmap_is_valid(this, tmap, time_in_ms, time_interval)) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: tmap not valid");
return RESULT_FALSE;
}
// get entry_count
uint16_t entry_count = tmap->nr_of_entries;
fprintf(MSG_OUT, " entry_count=%i", entry_count);
// get tmap_idx, tmap_idx_end: -1 b/c tmap is base1
// EX: idx time
// 0 4
// 1 8
uint32_t tmap_idx_bgn = (time_in_ms / time_interval) - 1;// -1 for base1
fprintf(MSG_OUT, " tmap_idx_bgn=%i", tmap_idx_bgn);
int32_t tmap_idx_end = tmap_idx_bgn + 1;
fprintf(MSG_OUT, " tmap_idx_end=%i", tmap_idx_end);
// get sector_bgn, sector_end
uint32_t sector_bgn, sector_end;
int32_t result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_bgn, §or_bgn);
fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn);
if (!result) return result;
result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_end, §or_end);
fprintf(MSG_OUT, " sector_end=%i", sector_end);
if (!result) return result;
// set *out vars
*out_tmap_idx = tmap_idx_bgn;
*out_time_interval = time_interval;
*out_sector_bgn = sector_bgn;
*out_sector_end = sector_end;
fprintf(MSG_OUT, " found tmap_idx=%i", tmap_idx_bgn);
fprintf(MSG_OUT, " time_interval=%i", time_interval);
fprintf(MSG_OUT, " sector_bgn=%i", sector_bgn);
fprintf(MSG_OUT, " sector_end=%i", sector_end);
return RESULT_TRUE;
}
/*
proc:
dvdnav_admap_find_by_sector
in:
@admap: admap instance
@entry_count: total # of entries in table
@sector_to_find: sector to find in table
@out_admap_idx: index for corresponding sector
out:
RESULT_TRUE if found
RESULT_FALSE if not
notes:
this proc uses a binary search to find @sector_to_find
note that an admap usually has an entry_count = # of seconds * 2
for a 2 hour film, entry_count will be 14k (2 * 60 * 60 * 2)
theoretical max is 64k entries
note that other methods in searching.c will iterate over the admap one at a time
they should be diverted to use this method (though performance gains will be negligible)
*/
static int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count, uint32_t sector_to_find, uint32_t *out_admap_idx) {
fprintf(MSG_OUT, " sector_to_find=%i", sector_to_find);
int32_t interval = entry_count;
int32_t multiplier = 1;
int32_t idx = 0;
int32_t tries = 0; // safeguard against infinite loop
int32_t sector_cur = 0;
while (1) {
interval = (interval + 1) / 2; // +1 to prevent fractional truncation; EX: 3 / 2 should be 2, not 1
if (interval == 0 || tries++ > 32) {
if (interval == 0)
fprintf(MSG_OUT, "\nlibdvdnav.time_search: WARN: binary search failed; zero interval; sector_to_find=%i", sector_to_find);
else
fprintf(MSG_OUT, "\nlibdvdnav.time_search: WARN: binary search failed; infinite loop; sector_to_find=%i", sector_to_find);
*out_admap_idx = idx;
fprintf(MSG_OUT, " admap_idx=%i", *out_admap_idx);
fprintf(MSG_OUT, " admap_find_tries=%i", tries);
return RESULT_FALSE;
}
idx += interval * multiplier;
sector_cur = admap->vobu_start_sectors[idx];
if (sector_cur == sector_to_find) {
*out_admap_idx = idx;
fprintf(MSG_OUT, " admap_idx=%i", *out_admap_idx);
fprintf(MSG_OUT, " admap_find_tries=%i", tries);
return RESULT_TRUE;
}
multiplier = sector_cur > sector_to_find ? -1 : 1;
}
}
/*
proc:
dvdnav_admap_interpolate_vobu
in:
@this: dvdnav instance
@sector_bgn: start sector of range
@sector_end: end sector of range
@fraction: fraction to interpolate
@vobu_adj: arbitrary value to add (fudge factor)
@out_sector: result of interpolation
out:
RESULT_TRUE if successful
RESULT_FALSE if not
notes:
this proc uses the admap to find the vobu that is the interpolated value of @sector_bgn, @sector_end and @fraction
again, an example may be easier to explain
@sector_bgn = 1000
@sector_end = 2000
@fraction = .25
@admap says @sector_bgn is vobu 10
@admap says @sector_end is vobu 14
there are 4 vobus between 14 and 10
so, requested vobu is 11 (1/4 of 4)
@admap says voub 11 is @sector 1125
*/
static int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this, uint32_t sector_bgn, uint32_t sector_end, double fraction, int32_t vobu_adj, uint32_t *out_sector) {
// get state
dvd_state_t *state = &(this->vm->state);
if (check_null(&state, "state")) return RESULT_FALSE;
// get domain
domain_t domain = state->domain;
fprintf(MSG_OUT, " domain=%i", domain);
// get admap
vobu_admap_t *admap = NULL;
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;
}
if (check_null(&admap, "admap")) return RESULT_FALSE;
// get vobu_count
int32_t vobu_count = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE;
fprintf(MSG_OUT, " vobu_count=%i", vobu_count);
if (vobu_count <= 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: vobu_count <= 0");
return RESULT_FALSE;
}
// get vobu_bgn
int32_t result;
uint32_t vobu_bgn;
result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_bgn, &vobu_bgn);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find sector_bgn");
return RESULT_FALSE;
}
// get vobu_end
uint32_t vobu_end;
result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_end, &vobu_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find sector_end");
return RESULT_FALSE;
}
// get vobu_dif
uint32_t vobu_dif = vobu_end - vobu_bgn;
fprintf(MSG_OUT, " vobu_dif=%i", vobu_dif);
// get vobu_off
uint32_t vobu_off = (fraction * ((double)vobu_dif + .5)); // +.5 to round up else .74 * 4 = 2
fprintf(MSG_OUT, " vobu_off=%i", vobu_off);
// adjust vobu_off
if (vobu_adj != 0) {
vobu_off += vobu_adj;
fprintf(MSG_OUT, " vobu_off + vobu_adj=%i", vobu_off);
}
// get sector
if (vobu_off >= vobu_count) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: vobu_off >= vobu_count");
return RESULT_FALSE;
}
*out_sector = admap->vobu_start_sectors[vobu_bgn + vobu_off];
fprintf(MSG_OUT, " vobu_out_sector=%i", *out_sector);
return RESULT_TRUE;
}
/*
proc:
dvdnav_find_sector_by_tmap
in:
@this: dvd_nav instance
@time_in_ms: time to find
@out_sector: sector found
out:
RESULT_TRUE if found
RESULT_FALSE if not
hacks:
+1 to vobu to adjust for time (is this a bounds issue?)
notes:
this proc finds the @sector from a given @time. Its approach is...
1. look at VTS_TMAPTI to find...
@time_interval: time precision of tmap (commonly 4 seconds?)
@sector_bgn: sector before @time
@sector_end: sector after @time
2. iterate over VTS_VOBU_ADMAP to find...
@vobu_bgn: vobu index corresponding to @sector_bgn
@vobu_end: vobu index corresponding to @sector_end
NOTE: VTS_VOBU_ADMAP will give precision to n seconds,
but n is only defined in the VOB PCI packet (vobu_s_ptm, vobu_e_ptm)
it will be .5 seconds (roughly) but it may vary
3. interpolate @sector using data from (1) and (2)
this proc will be accurate if...
1. VTS_TMAPTI is not corrupt
2. VTS_VOBU_ADMAP is not corrupt
NOTE: ifo_read.c reads VTS_TMAPTI and VTS_VOBU_ADMAP
- it will probably not be wrong, but look there for bugs
- if corrupt, the more likely source is either..
(a) original DVD may be mastered badly
(b) ifo may be altered manually by other programs
3. vobus are the same duration between two entries in VTS_TMAPTI
vobus seem to be the same time, though this is an assumption
even if false, then imprecision will be no more than @time_interval
EX:
find @sector for @time of 5 seconds
1. VTS_TMAPTI
@time_interval = 4
Entries = tmap_idx sector time
0 711 4
1 1776 8
2 2790 12
since 5 seconds is between 4 and 8, @time is between tmap_idx 0 and 1
@sector_bgn = 711
@sector_end = 1776
2. VTS_VOBU_ADMAP
Entries = vobu sector
0 0
1 43
etc.
9 711
10 831
11 948
12 1080
13 1213
14 1349
15 1493
16 1642
17 1776
@vobu_bgn = 9
@vobu_end = 17
3. interpolate
there are 8 vobus between 9 and 17
5 is 1/4 of the way between 4 and 8
1/4 of the way between 9 and 17 is 11
so, @sector = 948
*/
static int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) {
// get tmap data
uint32_t tmap_idx; uint32_t time_interval;
uint32_t sector_bgn, sector_end;
int32_t result = dvdnav_tmap_find_by_time(this, time_in_ms, &tmap_idx, &time_interval, §or_bgn, §or_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: could not find time in tmap");
return RESULT_FALSE;
}
// get time_in_ms_bgn
// NOTE: +1 b/c tmap is 0 based array, with 1 * time_interval
// EX: idx sector time calc
// 0 711 4 (idx + 1) * time_interval -> 4
// 1 1776 8 (idx + 1) * time_interval -> 8
uint32_t time_in_ms_bgn = (tmap_idx + 1) * time_interval;
fprintf(MSG_OUT, " time_in_ms_bgn2=%i", time_in_ms_bgn);
// get time_in_ms_dif
int32_t time_in_ms_dif = time_in_ms - time_in_ms_bgn;
fprintf(MSG_OUT, " time_in_ms_dif=%i", time_in_ms_dif);
if (time_in_ms_dif < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0");
return RESULT_FALSE;
}
// get time_pct
double time_pct = (double)time_in_ms_dif / time_interval; // time_interval checked previously to be > 0
fprintf(MSG_OUT, " time_pct=%f", time_pct);
// get sector
uint32_t sector = 0;
result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end, time_pct, 1, §or);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_tmap.interpolate");
return RESULT_FALSE;
}
fprintf(MSG_OUT, " sector=%i", sector);
// return
*out_sector = sector;
return RESULT_TRUE;
}
/*
proc:
dvdnav_find_sector_by_admap
in:
@this: dvd_nav instance
@time_in_ms: time to find
@out_sector: sector found
out:
RESULT_TRUE if found
RESULT_FALSE if not
hacks:
+1 to vobu to adjust for time (is this a bounds issue?)
assumes:
vmgi ifo only has one time map
notes:
this proc finds the @sector from a given @time. Its approach is...
1. look at cells to find...
@sector_bgn: start sector of cell
@sector_end: end sector of cell
@time_bgn: start time of cell
@time__end: end time of cell
2. iterate over VTS_VOBU_ADMAP to find...
@vobu_bgn: vobu index corresponding to @sector_bgn
@vobu_end: vobu index corresponding to @sector_end
3. interpolate @sector using data from (1) and (2)
refer to dvdnav_find_sector_by_tmap as logic is mostly the same
*/
static int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) {
// find cell_idx
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_admap.cell_find");
return RESULT_FALSE;
}
// get time_in_ms_dif
uint64_t time_in_ms_dif = time_in_ms - time_bgn;
fprintf(MSG_OUT, " time_in_ms_dif=%"PRId64, time_in_ms_dif);
if (time_in_ms_dif < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0");
return RESULT_FALSE;
}
// get time_len
uint64_t time_len = time_end - time_bgn;
fprintf(MSG_OUT, " time_len=%"PRId64, time_len);
if (time_len < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_len < 0");
return RESULT_FALSE;
}
// get time_pct
double time_pct = (double)time_in_ms_dif / time_len;
fprintf(MSG_OUT, " time_pct=%f", time_pct);
// get sector
uint32_t sector = 0;
sector_end++; // +1: cell sector in VTS_PGC is last sector of cell which is NOT a VOBU
result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end, time_pct, 1, §or);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_admap.interpolate");
return RESULT_FALSE;
}
fprintf(MSG_OUT, " sector=%i", sector);
// return
*out_sector = sector;
return RESULT_TRUE;
}
/*
proc:
dvdnav_find_sector_by_cells
in:
@this: dvd_nav instance
@time_in_ms: time to find
@out_sector: sector found
out:
RESULT_TRUE if found
RESULT_FALSE if not
notes:
this proc finds the @sector from a given @time. Its approach is...
1. look at cells to find...
@sector_bgn: start sector of cell
@sector_end: end sector of cell
@time_bgn: start time of cell
@time__end: end time of cell
2. interpolate @sector using data from (1)
*/
static int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms, uint32_t *out_sector) {
// find cell_idx
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: find_by_cells.cell_find");
return RESULT_FALSE;
}
// get time_in_ms_dif
uint64_t time_in_ms_dif = time_in_ms - time_bgn;
fprintf(MSG_OUT, " time_in_ms_dif=%"PRId64, time_in_ms_dif);
if (time_in_ms_dif < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_in_ms_dif < 0");
return RESULT_FALSE;
}
// get time_dif
uint64_t time_dif = time_end - time_bgn;
fprintf(MSG_OUT, " time_dif=%"PRId64, time_dif);
if (time_dif < 0) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: time_dif < 0");
return RESULT_FALSE;
}
// get time_pct
double time_pct = (double)time_in_ms_dif / time_dif;
fprintf(MSG_OUT, " time_pct=%f", time_pct);
// get sector_dif
uint32_t sector_dif = sector_end - sector_bgn;
fprintf(MSG_OUT, " sector_dif=%i", sector_dif);
// get sector_off
uint32_t sector_off = sector_bgn + sector_off;
fprintf(MSG_OUT, " sector_off=%i", sector_off);
// return
*out_sector = sector_bgn + sector_off;
return RESULT_TRUE;
}
/*
proc:
dvdnav_jump_to_sector
in:
@this: dvd_nav instance
@sector: sector to jump to
out:
RESULT_TRUE if successful
RESULT_FALSE if not
notes:
this proc will cause the vm to go to the @sector
*/
static int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector) {
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
// find cell_idx
result = dvdnav_cell_find(this, CELL_FIND_SECTOR, sector, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
fprintf(MSG_OUT, "\nlibdvdnav.time_search: ERR: jump_to_sector.cell_find");
return RESULT_FALSE;
}
// get sector_offset within cell; EX: sector=4100; cell begins at 4000; sector_offset = 100;
uint32_t sector_off = sector - sector_bgn;
fprintf(MSG_OUT, " sector_off=%i", sector_off);
// jump to cell/sector
this->cur_cell_time = 0;
if (vm_jump_cell_block(this->vm, cell_idx, sector_off)) {
pthread_mutex_lock(&this->vm_lock);
this->vm->hop_channel += HOP_SEEK;
pthread_mutex_unlock(&this->vm_lock);
return RESULT_TRUE;
}
return RESULT_FALSE;
}
/*
proc:
dvdnav_jump_to_sector_by_time
in:
@this: dvd_nav instance
@time_in_pts_ticks: time in presentation time stamp ticks
out:
RESULT_TRUE if successful
RESULT_FALSE if not
notes:
this proc tries to jump to a sector by time.
it tries to find a sector by calling each of these procs in succession
- dvdnav_find_sector_by_tmap
- dvdnav_find_sector_by_admap (if tmap fails)
- dvdnav_find_sector_by_cells (if tmap and admap fails)
if it gets a sector, it then calls dvdnav_jump_to_sector
*/
static int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, uint32_t time_in_pts_ticks) {
int32_t result = RESULT_FALSE;
fprintf(MSG_OUT, "\n*** time_in_pts_ticks=%i", time_in_pts_ticks);
// get time_in_ms
uint64_t time_in_ms = time_in_pts_ticks / 90;
fprintf(MSG_OUT, " time_in_ms=%"PRId64, time_in_ms);
// get_sector
uint32_t sector = 0;
result = dvdnav_find_sector_by_tmap(this, time_in_ms, §or);
if (!result) {
result = dvdnav_find_sector_by_admap(this, time_in_ms, §or);
if (!result) {
result = dvdnav_find_sector_by_cells(this, time_in_ms, §or);
if (!result) {
goto exit;
}
}
}
// jump to sector
result = dvdnav_jump_to_sector(this, sector);
if (!result) goto exit;
result = RESULT_TRUE;
exit:
return result;
}
/* 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;
}
/* Searching API calls */
/* 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;
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 {
first_cell_nr = state->pgc->program_map[state->pgN-1];
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 {
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
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) {
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=%lu 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; i<parts; i++) {
uint32_t cellnr, endcellnr;
pgc = ifo->vts_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;
}
Quite simple:My question for the VLC devs: what is the correct procedure for submitting a libdvdnav patch? Is mplayer the current custodians of the lib, and should I contact them? Do I then submit the VLC patch after the libdvdnav one gets integrated? (The VLC patch will look a lot like an earlier post, except it will call the new libdvdnav method: dvdnav_jump_to_sector_by_time)
Code: Select all
#include <dvdread/ifo_read.h>
#define RESULT_FALSE 0
#define RESULT_TRUE 1
#define CELL_FIND_SECTOR 1
#define CELL_FIND_TIME 2
/* Function prototypes */
int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this
, uint32_t time_in_pts_ticks);
/* Find functions */
int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector);
int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector);
int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector);
/* Sector functions */
int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector);
/* PG Cell functions */
int32_t dvdnav_cell_find(dvdnav_t *this
, int32_t find_mode, uint64_t find_val
, int32_t *out_cell_idx
, uint32_t *out_sector_bgn, uint32_t *out_sector_end
, uint64_t *out_time_bgn, uint64_t *out_time_end);
/* Time Map functions */
int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap
, uint32_t entry_count, uint32_t time_interval);
int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms
, uint32_t *out_tmap_idx, uint32_t *out_time_interval
, uint32_t *out_sector_bgn, uint32_t *out_sector_end);
/* Address Map functions */
int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count
, uint32_t sector_to_find, uint32_t *out_admap_idx);
int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this
, uint32_t sector_bgn, uint32_t sector_end, double fraction
, int32_t vobu_adj, uint32_t *out_sector);
Code: Select all
/*
This proc tries to jump to a specific sector
by calling these procs in the following order:
- dvdnav_find_sector_by_tmap
- dvdnav_find_sector_by_admap (if tmap fails)
- dvdnav_find_sector_by_cells (if tmap and admap fail)
If it gets a sector, it then calls dvdnav_jump_to_sector
*/
dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this
, uint32_t time_in_pts_ticks) {
dvdnav_status_t return_value = DVDNAV_STATUS_ERR;
// get sector
uint64_t time_in_ms = time_in_pts_ticks / 90;
uint32_t sector = 0;
int32_t result = dvdnav_find_sector_by_tmap(this, time_in_ms, §or);
if (!result) {
result = dvdnav_find_sector_by_admap(this, time_in_ms, §or);
if (!result) {
result = dvdnav_find_sector_by_cells(this, time_in_ms, §or);
if (!result) {
return_value = DVDNAV_STATUS_ERR;
goto exit;
}
}
}
// jump to sector
result = dvdnav_jump_to_sector(this, sector);
return_value = result ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
exit:
return return_value;
}
/*
This proc uses VTS_TMAPTI to find the @sector from a given @time.
1. uses VTS_TMAPTI to find:
@time_interval: time precision of tmap (commonly 4 seconds)
@sector_bgn: sector before @time
@sector_end: sector after @time
2. uses VTS_VOBU_ADMAP to find...
@vobu_bgn: vobu index corresponding to @sector_bgn
@vobu_end: vobu index corresponding to @sector_end
3. interpolate @sector using data from (1) and (2)
*/
int32_t dvdnav_find_sector_by_tmap(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector) {
// get VTS_TMAPTI data
uint32_t tmap_idx; uint32_t time_interval;
uint32_t sector_bgn, sector_end;
int32_t result = dvdnav_tmap_find_by_time(this, time_in_ms, &tmap_idx
, &time_interval, §or_bgn, §or_end);
if (!result) {
printerr("could not find time in tmap");
return RESULT_FALSE;
}
uint32_t time_in_ms_bgn
= (tmap_idx + 1) * time_interval; // +1 b/c tmap is 0-based
int32_t time_in_ms_dif = time_in_ms - time_in_ms_bgn;
if (time_in_ms_dif < 0) {
printerr("time_in_ms_dif cannot be < 0");
return RESULT_FALSE;
}
double time_pct = (double)time_in_ms_dif / time_interval;
// get sector
uint32_t sector = 0;
result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end
, time_pct, 1, §or);
if (!result) {
printerr("could not find time in admap");
return RESULT_FALSE;
}
*out_sector = sector;
return RESULT_TRUE;
}
/*
this proc VTS_VOBU_ADMAP to find the @sector from a given @time
1. look at cells to find...
@sector_bgn: start sector of cell
@sector_end: end sector of cell
@time_bgn: start time of cell
@time_end: end time of cell
2. iterate over VTS_VOBU_ADMAP to find...
@vobu_bgn: vobu index corresponding to @sector_bgn
@vobu_end: vobu index corresponding to @sector_end
3. interpolate @sector using data from (1) and (2)
*/
int32_t dvdnav_find_sector_by_admap(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector) {
// get VTS_VOBU_ADMAP data
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms
, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
printerr("could not find time in admap");
return RESULT_FALSE;
}
uint64_t time_in_ms_dif = time_in_ms - time_bgn;
if (time_in_ms_dif < 0) {
printerr("time_in_ms_dif cannot be < 0");
return RESULT_FALSE;
}
uint64_t time_len = time_end - time_bgn;
if (time_len < 0) {
printerr("time_len cannot be < 0");
return RESULT_FALSE;
}
double time_pct = (double)time_in_ms_dif / time_len;
// get sector
uint32_t sector = 0;
sector_end++; // +1: @sector_end is last sector of cell which is NOT a VOBU
result = dvdnav_admap_interpolate_vobu(this, sector_bgn, sector_end
, time_pct, 1, §or);
if (!result) {
printerr("could not find time in admap");
return RESULT_FALSE;
}
*out_sector = sector;
return RESULT_TRUE;
}
/*
this proc finds the @sector from a given @time. Its approach is...
1. look at cells to find...
@sector_bgn: start sector of cell
@sector_end: end sector of cell
@time_bgn: start time of cell
@time__end: end time of cell
2. interpolate @sector using data from (1)
*/
int32_t dvdnav_find_sector_by_cells(dvdnav_t *this, uint64_t time_in_ms
, uint32_t *out_sector) {
// get cell data
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_in_ms
, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
printerr("find_by_cells.cell_find");
return RESULT_FALSE;
}
uint64_t time_in_ms_dif = time_in_ms - time_bgn;
if (time_in_ms_dif < 0) {
printerr("time_in_ms_dif < 0");
return RESULT_FALSE;
}
uint64_t time_dif = time_end - time_bgn;
if (time_dif < 0) {
printerr("time_dif < 0");
return RESULT_FALSE;
}
// get sector
uint32_t sector_dif = sector_end - sector_bgn;
uint32_t sector_off = sector_bgn + sector_dif;
*out_sector = sector_bgn + sector_off;
return RESULT_TRUE;
}
int32_t dvdnav_jump_to_sector(dvdnav_t *this, uint32_t sector) {
int32_t cell_idx;
uint32_t sector_bgn, sector_end;
uint64_t time_bgn, time_end;
int32_t result;
// find cell_idx/sector_offset
result = dvdnav_cell_find(this, CELL_FIND_SECTOR, sector, &cell_idx
, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
printerr("could not jump_to_sector");
return RESULT_FALSE;
}
uint32_t sector_off = sector - sector_bgn;
// jump to cell/sector
this->cur_cell_time = 0;
if (vm_jump_cell_block(this->vm, cell_idx, sector_off)) {
pthread_mutex_lock(&this->vm_lock);
this->vm->hop_channel += HOP_SEEK;
pthread_mutex_unlock(&this->vm_lock);
return RESULT_TRUE;
}
return RESULT_FALSE;
}
/*
this proc...
- does bounds checking
- gets a sector from a tmap
- unsets discontinuity bit (if present)
*/
int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t entry_count
, uint32_t entry_idx, uint32_t *out_sector) {
if (entry_idx >= entry_count) {
// printerr("entry_idx >= entry_count");
return RESULT_FALSE;
}
int32_t sector = tmap->map_ent[entry_idx];
if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set
sector &= ~(1 << 31); // unset bit
}
if (sector < 0) {
// printerr("sector < 0; discontinuity?");
return RESULT_FALSE;
}
*out_sector = sector;
return RESULT_TRUE;
}
/*
this proc finds a cell, according to the find_args
*/
int32_t dvdnav_cell_find(dvdnav_t *this
, int32_t find_mode, uint64_t find_val
, int32_t *out_cell_idx
, uint32_t *out_sector_bgn, uint32_t *out_sector_end
, uint64_t *out_time_bgn, uint64_t *out_time_end) {
// get state
dvd_state_t *state = &(this->vm->state);
pgc_t *pgc = state->pgc;
uint32_t cell_count = pgc->nr_of_cells;
if (cell_count == 0) {
printerr("cell_count == 0");
return RESULT_FALSE;
}
// get cell_bgn, cell_end
uint32_t cell_bgn, cell_end;
if (this->pgc_based) {
cell_bgn = 1;
cell_end = cell_count;
}
else {
int pgN = state->pgN;
cell_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based?
int program_count = pgc->nr_of_programs;
if (pgN < program_count) {
cell_end = pgc->program_map[pgN] - 1;
}
else {
cell_end = cell_count;
}
}
// search cells
uint32_t cell_idx, sector_bgn = 0, sector_end = 0;
uint64_t time_bgn = 0, time_end = 0;
cell_playback_t *cell;
int located = RESULT_FALSE;
for (cell_idx = cell_bgn; cell_idx <= cell_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;
}
sector_bgn = cell->first_sector;
sector_end = cell->last_sector;
// 90: pts to ms
time_end += (dvdnav_convert_time(&cell->playback_time) / 90);
if (find_mode == CELL_FIND_SECTOR) {
if (find_val >= sector_bgn && find_val <= sector_end) {
located = RESULT_TRUE;
break;
}
}
else if (find_mode == CELL_FIND_TIME) {
if (find_val >= time_bgn && find_val <= time_end) {
located = RESULT_TRUE;
break;
}
}
time_bgn = time_end;
}
// found cell: set *out vars
if (located == RESULT_TRUE) {
*out_cell_idx = cell_idx;
*out_sector_bgn = sector_bgn;
*out_sector_end = sector_end;
*out_time_bgn = time_bgn;
*out_time_end = time_end;
}
else
printerr("cell not found");
return located;
}
/*
this proc currently validates a time map according to two criteria
1. checks if 0 entries
2. checks entry0 to make certain it is valid in context of its cell
this may be easier explained by example
entry0 lists sector 4500
time_interval is 4
this means that time of 4 seconds should be in a cell with
- a sector_bgn < 4500
- a sector_end > 4500
so, iterate over the cells to find cell for 4 secs
@cell_idx for 4 secs is 8
it has time_bgn of 3 secs and time_end of 5 secs
however, it has a sector_bgn of 3000 and a sector_end of 4000
since this contradicts the time_map, the time_map is not valid
*/
int32_t dvdnav_tmap_is_valid(dvdnav_t *this, vts_tmap_t *tmap
, uint32_t entry_count, uint32_t time_interval) {
// tmap with 0 entries are invalid
if (entry_count == 0) {
printerr("tmap_entry_count == 0");
return RESULT_FALSE;
}
// get cell corresponding to entry0
int32_t cell_idx;
uint32_t sector_bgn = 0, sector_end = 0;
uint64_t time_bgn, time_end;
int32_t result;
result = dvdnav_cell_find(this, CELL_FIND_TIME, time_interval
, &cell_idx, §or_bgn, §or_end, &time_bgn, &time_end);
if (!result) {
printerr("could not find cell in tmap");
return RESULT_FALSE;
}
// get sector for entry 0
uint32_t entry0_sector = 0;
result = dvdnav_tmap_get_entry(tmap, entry_count, 0, &entry0_sector);
if (!result) {
printerr("could not find sector in tmap");
return RESULT_FALSE;
}
// check entry0_sector is between corresponding cell_sectors
if (entry0_sector >= sector_bgn && entry0_sector <= sector_end) {
return RESULT_TRUE;
}
else {
return RESULT_FALSE;
}
}
/*
this proc scans the time_map to find the entry for @time_in_ms
*/
int32_t dvdnav_tmap_find_by_time(dvdnav_t *this, uint32_t time_in_ms
, uint32_t *out_tmap_idx, uint32_t *out_time_interval
, uint32_t *out_sector_bgn, uint32_t *out_sector_end) {
// get state
dvd_state_t *state = &(this->vm->state);
domain_t domain = state->domain;
int32_t vts_idx = state->vtsN; // int
// get ifo
ifo_handle_t *ifo = NULL;
switch(domain) {
case FP_DOMAIN:
case VTSM_DOMAIN:
case VMGM_DOMAIN: {
// NOTE: ifo = this->vm->vmgi did not work
ifo = ifoOpenVMGI(this->vm->dvd);
break;
}
case VTS_DOMAIN: {
// NOTE: ifo = this->vm->vtsi did not work
ifo = ifoOpen(this->vm->dvd, vts_idx);
break;
}
default: {// NOTE: will not check out-of-range domain again
printerr("unknown domain");
return RESULT_FALSE;
}
}
// get tmapt
vts_tmapt_t *tmapt = ifo->vts_tmapt;
uint16_t tmap_count = tmapt->nr_of_tmaps;
// get pgcN; -1 b/c pgcN is base1
int32_t pgcN = (state->pgcN) - 1; // int
if (pgcN < 0) {
printerr("pgcN < 0");
return RESULT_FALSE;
}
// get tmap
vts_tmap_t *tmap = NULL;
switch(domain) {
case FP_DOMAIN:
case VMGM_DOMAIN:
case VTSM_DOMAIN: {
if (tmap_count == 0) {
printerr("tmap_count == 0");
return RESULT_FALSE;
}
tmap = &(tmapt->tmap[0]); // ASSUME: vmgi only has one time map
break;
}
case VTS_DOMAIN: {
if (pgcN >= tmap_count) {
return RESULT_FALSE;
}
tmap = &(tmapt->tmap[pgcN]);
break;
}
}
// get time_interval; tmap->tmu is in seconds
uint32_t time_interval = (tmap->tmu) * 1000; // * 1000 converts to millisecs
if (time_interval == 0) {
printerr(" time_interval == 0");
return RESULT_FALSE;
}
// check tmap is valid
if (!dvdnav_tmap_is_valid(this, tmap, time_in_ms, time_interval)) {
printerr("tmap not valid");
return RESULT_FALSE;
}
uint16_t entry_count = tmap->nr_of_entries;
// get tmap_idx, tmap_idx_end: -1 b/c tmap is base1
// EX: idx time
// 0 4
// 1 8
uint32_t tmap_idx_bgn = (time_in_ms / time_interval) - 1;// -1 for base1
int32_t tmap_idx_end = tmap_idx_bgn + 1;
// get sector_bgn, sector_end
uint32_t sector_bgn, sector_end;
int32_t result = dvdnav_tmap_get_entry(tmap
, entry_count, tmap_idx_bgn, §or_bgn);
if (!result) return result;
result = dvdnav_tmap_get_entry(tmap, entry_count, tmap_idx_end, §or_end);
if (!result) return result;
// set *out vars
*out_tmap_idx = tmap_idx_bgn;
*out_time_interval = time_interval;
*out_sector_bgn = sector_bgn;
*out_sector_end = sector_end;
return RESULT_TRUE;
}
/*
this proc uses a binary search to find @sector_to_find
note that an admap usually has an entry_count = # of seconds * 2
for a 2 hour film, entry_count will be 14k (2 * 60 * 60 * 2)
theoretical max is 64k entries
note that other procs in searching.c iterates over the admap one at a time
they can use this proc (though performance gains will be negligible)
*/
int dvdnav_admap_find_by_sector(vobu_admap_t *admap, uint32_t entry_count
, uint32_t sector_to_find, uint32_t *out_admap_idx) {
int32_t interval = entry_count;
int32_t multiplier = 1;
int32_t idx = 0;
int32_t tries = 0; // safeguard against infinite loop
int32_t sector_cur = 0;
while (1) {
// +1 to prevent fractional truncation; EX: 3 / 2 should be 2, not 1
interval = (interval + 1) / 2;
if (interval == 0 || tries++ > 32) {
if (interval == 0)
fprintf(MSG_OUT, "binary search failed; zero interval");
else
fprintf(MSG_OUT, "binary search failed; infinite loop");
*out_admap_idx = idx;
return RESULT_FALSE;
}
idx += interval * multiplier;
sector_cur = admap->vobu_start_sectors[idx];
if (sector_cur == sector_to_find) {
*out_admap_idx = idx;
return RESULT_TRUE;
}
multiplier = sector_cur > sector_to_find ? -1 : 1;
}
}
/*
this proc uses the admap to find the vobu
that is the interpolated value of @sector_bgn, @sector_end and @fraction
again, an example may be easier to explain
@sector_bgn = 1000
@sector_end = 2000
@fraction = .25
@admap says @sector_bgn is vobu 10
@admap says @sector_end is vobu 14
there are 4 vobus between 14 and 10
so, requested vobu is 11 (1/4 of 4)
@admap says voub 11 is @sector 1125
*/
int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this
, uint32_t sector_bgn, uint32_t sector_end, double fraction
, int32_t vobu_adj, uint32_t *out_sector) {
dvd_state_t *state = &(this->vm->state);
domain_t domain = state->domain;
// get admap
vobu_admap_t *admap = NULL;
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;
}
// get vobu_count
int32_t vobu_count = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)
/ VOBU_ADMAP_SIZE;
if (vobu_count <= 0) {
printerr("vobu_count <= 0");
return RESULT_FALSE;
}
// get vobu_bgn
int32_t result;
uint32_t vobu_bgn;
result = dvdnav_admap_find_by_sector(admap, vobu_count
, sector_bgn, &vobu_bgn);
if (!result) {
printerr("could not find sector_bgn");
return RESULT_FALSE;
}
// get vobu_end
uint32_t vobu_end;
result = dvdnav_admap_find_by_sector(admap, vobu_count, sector_end
, &vobu_end);
if (!result) {
printerr("could not find sector_end");
return RESULT_FALSE;
}
// get vobu_dif
uint32_t vobu_dif = vobu_end - vobu_bgn;
// get vobu_off; +.5 to round up else .74 * 4 = 2
uint32_t vobu_off = (fraction * ((double)vobu_dif + .5));
// adjust vobu_off
if (vobu_adj != 0) {
vobu_off += vobu_adj;
}
// get sector
if (vobu_off >= vobu_count) {
printerr("vobu_off >= vobu_count");
return RESULT_FALSE;
}
*out_sector = admap->vobu_start_sectors[vobu_bgn + vobu_off];
return RESULT_TRUE;
}
Code: Select all
int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, uint32_t time_in_pts_ticks);
Code: Select all
case DEMUX_SET_POSITION:
case DEMUX_GET_POSITION:
case DEMUX_GET_TIME:
case DEMUX_GET_LENGTH:
{
int64_t timeInNavUnits, timeInSeconds, titleInSeconds;
titleInSeconds = p_sys->i_pgc_length;
timeInNavUnits = dvdnav_get_current_time( p_sys->dvdnav );
timeInSeconds = (timeInNavUnits / 90) * 1000; // see dvdnav_convert_time in dvdnav.c
if ( titleInSeconds == 0 )
return VLC_EGENERIC;
switch( i_query )
{
case DEMUX_GET_POSITION:
*va_arg( args, double* ) = (double)timeInSeconds / (double)titleInSeconds;
return VLC_SUCCESS;
case DEMUX_SET_POSITION: {
double rate = va_arg( args, double );
timeInSeconds = rate * titleInSeconds;
timeInNavUnits = (timeInSeconds / 1000) * 90;
dvdnav_jump_to_sector_by_time( p_sys->dvdnav, timeInNavUnits );
break;
}
case DEMUX_GET_TIME:
if( p_sys->i_pgc_length > 0 )
{
*va_arg( args, int64_t * ) = timeInSeconds;
return VLC_SUCCESS;
}
break;
case DEMUX_GET_LENGTH:
if( p_sys->i_pgc_length > 0 )
{
*va_arg( args, int64_t * ) = titleInSeconds;
return VLC_SUCCESS;
}
break;
}
return VLC_EGENERIC;
}
There are a few DVD wrappers, out there...Yep.Out of curiosity, did you want me to try dvd vs dvdsimple because one uses dvdnav and the other uses dvdplay?
Okay. I will give them another try later this week/next week. I didn't want to come across as pushy, especially in a project for which I have had no involvement. My other thoughts are...So it would not hurt to try to push harder on the dvdnav mailing list.
If there's sufficient documentation they might be all right with it.
Okay. I will give them another try later this week/next week. I didn't want to come across as pushy, especially in a project for which I have had no involvement. My other thoughts are...
I will post some status later on. If any libdvdnav developers happen to read this, please feel free to provide other pointers/suggestions.
- searching.c is currently 18.9KB. The proposed change would make it 37.0KB. Granted, I put in a lot of comments and logging, but even if I strip them away, it would be something like +9KB. That would be a code growth of 50% in a file with 12 or so functions -- just to handle one new function. I can see why any developer would balk at integrating the new "fix" from an unknown developer.
Unfortunately I believe this complexity is necessary. Time seek is not an easy task and I think that is why the original developer left it as a FIXME.- My approach does not handle all DVDs. As I detail earlier, it uses the .IFO files only. I think the "ideal" approach would be to look at the info in the .VOB files (the VOB PCI pak). However, this is not a trivial project. I tried to do as much afterwards, and the project rapidly became even more complicated than my +9KB solution.
That said, I think the current fix will work for 90+% of DVDs. The remaining % will fall back to a less precise variation of seek-time-by-sector-implementation, which may be close enough for most users.
Please re-send. The project is now alive again.I emailed the DVDNav mailing list because 90% of the change needs to be made in libdvdnav.
Okay. Sorry for the delay, but I ended up rewriting a good part of the code. The new version now handles those outlier dvds I've encountered (strange time maps: I won't bore anyone with the details).Please re-send. The project is now alive again.
Thanks for the link. I looked at the code, and it looks like we basically have the same approach. Kovensky's code is much shorter than mine: 60 lines versus 600. As an example, Kovensky gets a time_sector with this:Also of note to you might be https://github.com/Kovensky/mplayer-kov ... dvd.c#L551 (how mplayer does stream seeking)
Code: Select all
tmap_sector = vts_tmapt->tmap[i].map_ent[j] & 0x7fffffff;
Code: Select all
static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len, uint32_t idx, uint32_t *out_sector) {
if (idx < 0 || idx >= tmap_len)
return RESULT_FALSE;
int32_t sector = tmap->map_ent[idx];
if ((sector & (1 << 31)) != 0) { // if discontinuity bit is set
sector &= ~(1 << 31); // unset bit
}
if (sector < 0)
return RESULT_FALSE;
*out_sector = sector;
return RESULT_TRUE;
}
I looked at the link, but couldn't get to the source file (http://www.ratdvd.dk/downloads/libdvdnav.zip). It's probably the same variation as abovehttp://sourceforge.net/tracker/?func=de ... tid=402476
Code: Select all
Hi all. I've written a patch for jumping to a time in searching.c. I emailed something similar to this group a year ago, but there didn't seem to be much interest then. I am emailing again because there is some revived interest in the VLC community. (See http://forum.videolan.org/viewtopic.php?f=32&t=76308&start=20#p315221)
Currently, dvdnav_time_search is marked "FIXME: right now, this function does not use the time tables but interpolates only the cell times". This method is usually inaccurate: it interpolates using sectors, but one sector does not equal one fixed unit of time. For example, if there is a 10:00 cell and the cell is 1000 sectors long, it assumes that 5:00 is at sector 500. Jumping to sector 500 may actually jump instead to 5:12.
My patch jumps to a time using the time map and the vobu address map. I placed it in a new function called "dvdnav_jump_to_sector" because I wanted to keep backward compatibility with the existing "dvdnav_time_search" -- in case anyone wanted to keep using this method. I provide more details on my approach below. I will include the patch itself in the next email.
Please note that most of my work is based on reverse-engineering the IFO files. I am not a DVD application developer, so my approach may be flawed or may not follow DVD specification.
Also, please note that Java/C# is my primary language. I learned C for this task, and have not used it for anything else since then. My apologies in advance if I do something terribly stupid.
Finally, please note that my patch does not change or delete any existing code. However, it does add a lot of code: 650+ lines. I realize that such length makes it harder and less desirable to review. I do think the approach needs to have this complexity, especially to handle some outlier DVDs I've encountered.
I appreciate any feedback or review. At the very least, I hope others will find this useful.
Thanks.
-------
Details
-------
My patch introduces a new function called "dvdnav_jump_to_sector". It finds a sector for a given time and jumps to it.
In libdvdnav/src/dvdnav/dvdnav.h, it adds 1 line. (see dvdnav.h.patch)
In libdvdnav/src/searching.c, it adds 650+ lines. (see searching.c.patch.zip)
No other lines are modified or deleted.
From a broad perspective, it works like this:
(1) Find the two time map entries around a given time
(2) Get the vobus of these two time map entries
(3) Get the sector of the jump_time through interpolation of the vobus
For clarity's sake, I will repeat the above with fabricated data
(1) Find the two time map entries around a given time
Given time 56...
Time map index 10 is identified as 55 seconds
56 seconds must be between entries 10 and 11
(2) Get the vobus of these two time map entries
Time entry 10 is at sector 193 which is vobu 405
Time entry 11 is at sector 303 which is vobu 455
(3) Get the sector of the jump_time through interpolation of the vobus
56 is 20% of the way between 55 and 60
415 is 20% of the way between 405 and 455
320 is the corresponding sector of vobu 415
Note that step (1) actually requires more sub-steps
(1a) Find the cell for a given time
(1b) Find the tmap entries around the cell's start sector
(1c) Find the vobus for these tmap entries and cell start
(1d) Interpolate the time of earlier tmap entry
(1e) Find the tmap entries around jump_time
(2) Get the vobus of these two time map entries
(3) Get the sector of the jump_time through interpolation of the vobus
Again, here is the above with some fabricated data.
I try to represent the tables visually below. It was written with a fixed-width font.
My apologies if it does not show up formatted in the email.
(1a) Find the cell for a given time
Given time 56...
---- -------- -------- ---------- ----------
cell bgn_time end_time bgn_sector end_sector
---- -------- -------- ---------- ----------
1 0 28 0 99
---> 2 29 600 100 1000
3 601 900 1001 1400
...
**** Cell 2 encloses 56.
(1b) Find the tmap entries around the cell's start sector
---- ------
tmap sector
---- ------
...
3 71
---> 4 88
---> 5 108
6 121
...
**** tmap entries 4 and 5 enclose cell start sector of 100.
(1c) Find the vobus for these tmap entries and cell start
---- ------
vobu sector
---- ------
...
---> 200 88
...
---> 208 100
209 102
---> 210 108
...
**** 200, 208 and 210 are the relevant vobus
(1d) Interpolate the time of earlier tmap entry
208 is 80% of the way between 200 and 210
4 secs is 80% of 5 secs
25 is the time-point when subtracting 4 from 29 (cell_bgn)
vobu 200 is at 25 seconds
**** 25 secs is the calculated time of tmap idx 4
(1e) Find the tmap entries around jump_time
---- ---- ------
tmap time sector
---- ---- ------
4 25
5 30
6 35
7 40
8 45
9 50
---> 10 55 193
---> 11 60 303
...
**** 56 must be between tmap entries 10 and 11
(2) Get the vobus of these two time map entries
---- ------
vobu sector
---- ------
...
---> 405 193
...
415 320
...
---> 455 503
...
**** 405 and 445 are the relevant vobus
(3) Get the sector of the jump_time through interpolation of the vobus
56 is 20% of the way between 55 and 60
415 is 20% of the way between 405 and 455
320 is the corresponding sector of vobu 415
**** 320 is the closest vobu sector for 56 secs
-----------
Other notes
-----------
(1b) has a hack to handle cells that start at discontinuity entries
- Basically if a cell starts in a discontinuity entry, then the sector/vobu data may not be good for interpolation
For example, using the same data above
---- ---- ------ ----
tmap time sector vobu
---- ---- ------ ----
4 25 88 200 <- flagged with discontinuity bit
5 30 108 210
6 35 140 218
Since there is a discontinuity occurring at tmap entry 4, the sectors between 88 and 108 may not represent 5 seconds of data (It usually does, but not always)
To handle this, the proc looks forward to the next tmap entry and uses the vobu difference there to infer the vobu of the lo tmap
With data, this means
Look at tmap entry 6. It has a vobu of 218
There are 8 vobus between 218 and 210
Assume that tmap entry 10 starts at 202 (not 200)
This works in practice on a handful of DVDs I've encountered, but the logic will probably not hold for all.
There is a small percentage of DVDs that have issues
- Time map was missing (no entries)
For these DVDs, I wrote dvdnav_find_vobu_by_cell_boundaries. This function uses the same logic as tmap, but uses the cell_boundaries only. It is not as accurate as a tmap, but it is close. It is more accurate than raw sector interpolation. Roughly speaking, it is accurate 50% of the time, and the other other 50% of the time, it is off by 1 to 3 seconds.
- Time map has non-consecutive entries. For example, a DVD with a time interval of 5 seconds had entries at tmap index 99 that was not 500 seconds in playback, but 521 seconds (or some other non-intuitive number).
For these DVDs, steps (1a) - (1d) were written. If we could assume that tmap index 99 was always at time 500 seconds, then we could skip directly to (1e).
----------------
Function summary
----------------
Entry function:
- dvdnav_jump_to_sector_by_time
Find the nearest vobu and jump to it
Core function:
- dvdnav_find_vobu_by_tmap
Find the nearest vobu by using the tmap
Fallback function:
- dvdnav_find_vobu_by_cell_boundaries
Find the nearest vobu by using the cell boundaries
Utility functions:
- dvdnav_tmap_get_entries_for_sector
Find the tmap entries on either side of a given sector
- dvdnav_tmap_calc_time_for_tmap_entry
Given two tmap entries and a known time, calc the time for the lo tmap entry
- dvdnav_admap_interpolate_vobu
Given two sectors and a fraction, calc the corresponding vobu
- dvdnav_cell_find
Given a sector/time/idx find the cell that encloses it
- dvdnav_tmap_search
Do binary search for the earlier tmap entry near find_sector
- dvdnav_admap_search
Do a binary search for earlier admap index near find_sector
- dvdnav_tmap_get_entry
Get a sector from a tmap
Misc functions (separated for readability):
- dvdnav_tmap_get
Get a tmap, tmap_len and tmap_interval
- dvdnav_admap_get
Get an admap and admap_len
Log/Debug functions
- is_null
Check if pointer is null, and log it if it is
Code: Select all
Index: src/searching.c
===================================================================
--- src/searching.c (revision 1243)
+++ src/searching.c (working copy)
@@ -36,6 +36,14 @@
#include "vm/decoder.h"
#include "vm/vm.h"
#include "dvdnav_internal.h"
+#include <dvdread/ifo_read.h>
+#define RESULT_FALSE 0
+#define RESULT_TRUE 1
+#define CELL_FIND_SECTOR 1
+#define CELL_FIND_TIME 2
+#define CELL_FIND_INDEX 3
+#define TMAP_IDX_EDGE_BGN -1
+#define TMAP_IDX_EDGE_END -2
/*
#define LOG_DEBUG
@@ -654,3 +662,674 @@
free(tmp);
return retval;
}
+/*
+Check if pointer is null, and log it if it is
+*/
+int32_t is_null(void *val, const char *val_name) {
+ if (val == NULL) {
+ fprintf(MSG_OUT, "ERR:NULL REF: %s\n", val_name);
+ return RESULT_TRUE;
+ }
+ else {
+ return RESULT_FALSE;
+ }
+}
+/*
+Get an admap and admap_len
+*/
+vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state
+ , int32_t *out_admap_len) {
+ vobu_admap_t *admap = NULL;
+ domain_t domain = state->domain;
+ 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, "ERR:Unknown domain\n");
+ return NULL;
+ }
+ }
+ if (is_null(admap, "admap")) return NULL;
+
+ *out_admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE;
+ if (*out_admap_len <= 0) {
+ fprintf(MSG_OUT, "ERR:admap_len <= 0\n");
+ return NULL;
+ }
+ return admap;
+}
+/*
+Get a tmap, tmap_len and tmap_interval
+*/
+vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state
+ , int32_t *out_tmap_len, int32_t *out_tmap_interval) {
+ int32_t vts_idx = state->vtsN;
+
+ domain_t domain = state->domain;
+ ifo_handle_t *ifo = NULL;
+ switch(domain) {
+ case FP_DOMAIN:
+ case VTSM_DOMAIN:
+ case VMGM_DOMAIN: {
+ // NOTE: ifo = this->vm->vmgi seems to be always null
+ ifo = this->vm->vmgi;
+ break;
+ }
+ case VTS_DOMAIN: {
+ // NOTE: ifo = this->vm->vtsi doesn't work
+ ifo = ifoOpen(this->vm->dvd, vts_idx);
+ break;
+ }
+ default: {
+ fprintf(MSG_OUT, "ERR:unknown domain for tmap\n");
+ return NULL;
+ }
+ }
+ if (is_null(ifo, "ifo")) return NULL;
+
+ vts_tmapt_t *tmapt = ifo->vts_tmapt;
+ if (is_null(tmapt, "tmapt")) return NULL;
+
+ uint16_t tmap_count = tmapt->nr_of_tmaps;
+
+ // get pgcN; -1 b/c pgcN is base1
+ int32_t pgcN = state->pgcN - 1;
+ if (pgcN < 0) {
+ fprintf(MSG_OUT, "ERR:pgcN < 0\n");
+ return NULL;
+ }
+
+ // get tmap
+ vts_tmap_t *tmap = NULL;
+ switch(domain) {
+ case FP_DOMAIN:
+ case VMGM_DOMAIN:
+ case VTSM_DOMAIN: {
+ if (tmap_count == 0) {
+ fprintf(MSG_OUT, "ERR:tmap_count == 0\n");
+ return NULL;
+ }
+ tmap = &tmapt->tmap[0]; // ASSUME: vmgi only has one time map
+ break;
+ }
+ case VTS_DOMAIN: {
+ if (pgcN >= tmap_count) {
+ fprintf(MSG_OUT, "ERR:pgcN >= tmap_count; pgcN=%i tmap_count=%i\n"
+ , pgcN, tmap_count);
+ return NULL;
+ }
+ tmap = &tmapt->tmap[pgcN];
+ break;
+ }
+ }
+ if (is_null(tmap, "tmap")) return NULL;
+
+ // get tmap_interval; tmap->tmu is in seconds; convert to millisecs
+ *out_tmap_interval = tmap->tmu * 1000;
+ if (*out_tmap_interval == 0) {
+ fprintf(MSG_OUT, "ERR:tmap_interval == 0\n");
+ return NULL;
+ }
+ // get tmap_len
+ *out_tmap_len = tmap->nr_of_entries;
+ if (*out_tmap_len == 0) {
+ fprintf(MSG_OUT, "ERR:tmap_len == 0\n");
+ return NULL;
+ }
+ return tmap;
+}
+/*
+Get a sector from a tmap
+*/
+int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len
+ , int32_t idx, uint32_t *out_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) {
+ *out_sector = 0;
+ return RESULT_TRUE;
+ }
+ if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) {
+ fprintf(MSG_OUT, "ERR:idx out of bounds idx=%i %i\n", idx, tmap_len);
+ return RESULT_FALSE;
+ }
+ // 0x7fffffff unsets discontinuity bit if present
+ *out_sector = tmap->map_ent[idx] & 0x7fffffff;
+ return RESULT_TRUE;
+}
+/*
+Do a binary search for earlier admap index near find_sector
+*/
+int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len
+ , uint32_t find_sector, uint32_t *out_vobu) {
+ int32_t adj = 1, prv_pos = 0, prv_len = admap_len;
+ while (1) {
+ int32_t cur_len = prv_len / 2;
+ // need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1)
+ if (prv_len % 2 == 1) ++cur_len;
+ int32_t cur_idx = prv_pos + (cur_len * adj);
+ if (cur_idx < 0) cur_idx = 0;
+ else if (cur_idx >= admap_len) cur_idx = admap_len - 1;
+
+ uint32_t 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) {
+ *out_vobu = cur_idx;
+ return RESULT_TRUE;
+ }
+ 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];
+ }
+ *out_vobu = cur_idx;
+ return RESULT_TRUE;
+ }
+ prv_len = cur_len;
+ prv_pos = cur_idx;
+ }
+}
+/*
+Do a binary search for the earlier tmap entry near find_sector
+*/
+int32_t dvdnav_tmap_search(vts_tmap_t *tmap, uint32_t tmap_len
+ , uint32_t find_sector, int32_t *out_tmap, uint32_t *out_sector) {
+ int32_t adj = 1; int32_t prv_pos = 0; int32_t prv_len = tmap_len;
+ int32_t result = RESULT_FALSE;
+ while (1) {
+ int32_t cur_len = prv_len / 2;
+ // need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1)
+ if (prv_len % 2 == 1) ++cur_len;
+ int32_t cur_idx = prv_pos + (cur_len * adj);
+ if (cur_idx < 0) cur_idx = 0;
+ else if (cur_idx >= tmap_len) cur_idx = tmap_len - 1;
+ uint32_t cur_sector = 0;
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector);
+ if (!result) return RESULT_FALSE;
+ if (find_sector < cur_sector) adj = -1;
+ else if (find_sector > cur_sector) adj = 1;
+ else if (find_sector == cur_sector) {
+ *out_tmap = cur_idx;
+ *out_sector = cur_sector;
+ return RESULT_TRUE;
+ }
+ 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(tmap, tmap_len, cur_idx, &cur_sector);
+ if (!result) return RESULT_FALSE;
+ }
+ }
+ *out_tmap = cur_idx;
+ *out_sector = cur_sector;
+ return RESULT_TRUE;
+ }
+ prv_len = cur_len;
+ prv_pos = cur_idx;
+ }
+}
+
+/*
+Given a sector/time/idx find the cell that encloses it
+*/
+int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state
+ , int32_t find_mode, uint64_t find_val
+ , int32_t *out_cell_idx
+ , uint64_t *out_bgn_time, uint32_t *out_bgn_sector
+ , uint64_t *out_end_time, uint32_t *out_end_sector) {
+ pgc_t *pgc = state->pgc;
+ if (is_null(pgc, "pgc")) return RESULT_FALSE;
+
+ // get cells_len
+ uint32_t cells_len = pgc->nr_of_cells;
+ if (cells_len == 0) {
+ fprintf(MSG_OUT, "ERR:cells_len == 0\n");
+ return RESULT_FALSE;
+ }
+
+ // get cells_bgn, cells_end
+ uint32_t cells_bgn, cells_end;
+ if (this->pgc_based) {
+ cells_bgn = 1;
+ cells_end = cells_len;
+ }
+ else {
+ int pgN = state->pgN;
+ cells_bgn = pgc->program_map[pgN - 1]; // -1 b/c pgN is 1 based?
+ int program_count = pgc->nr_of_programs;
+ if (pgN < program_count) {
+ cells_end = pgc->program_map[pgN] - 1;
+ }
+ else {
+ cells_end = cells_len;
+ }
+ }
+
+ // search cells
+ uint32_t cell_idx, sector_bgn = 0, sector_end = 0;
+ uint64_t time_bgn = 0, time_end = 0;
+ cell_playback_t *cell;
+ int found = RESULT_FALSE;
+ 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;
+ }
+ sector_bgn = cell->first_sector;
+ sector_end = cell->last_sector;
+ time_end += (dvdnav_convert_time(&cell->playback_time) / 90);// 90 pts to ms
+ if (find_mode == CELL_FIND_SECTOR) {
+ if (find_val >= sector_bgn && find_val <= sector_end) {
+ found = RESULT_TRUE;
+ break;
+ }
+ }
+ else if (find_mode == CELL_FIND_TIME) {
+ if (find_val >= time_bgn && find_val <= time_end) {
+ found = RESULT_TRUE;
+ break;
+ }
+ }
+ else if (find_mode == CELL_FIND_INDEX) {
+ if (find_val == cell_idx) {
+ found = RESULT_TRUE;
+ break;
+ }
+ }
+ time_bgn = time_end;
+ }
+
+ // found cell: set *out vars
+ if (found) {
+ *out_cell_idx = cell_idx;
+ *out_bgn_sector = sector_bgn;
+ *out_end_sector = sector_end;
+ *out_bgn_time = time_bgn;
+ *out_end_time = time_end;
+ }
+ else
+ fprintf(MSG_OUT, "ERR:cell not found\n");
+ return found;
+}
+/*
+Given two sectors and a fraction, calc the corresponding vobu
+*/
+int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this
+ , vobu_admap_t *admap, int32_t admap_len
+ , uint32_t sector_bgn, uint32_t sector_end, double fraction
+ , uint32_t *out_jump_sector) {
+
+ // get vobu_bgn
+ int32_t result;
+ uint32_t vobu_bgn;
+ result = dvdnav_admap_search(admap, admap_len, sector_bgn, &vobu_bgn);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR admap_interpolate: could not find sector_bgn");
+ return RESULT_FALSE;
+ }
+
+ // get vobu_end
+ uint32_t vobu_end;
+ result = dvdnav_admap_search(admap, admap_len, sector_end, &vobu_end);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR admap_interpolate: could not find sector_end");
+ return RESULT_FALSE;
+ }
+
+ // get vobu_dif
+ uint32_t vobu_dif = vobu_end - vobu_bgn;
+
+ // get vobu_off; +.5 to round up else 74% of a 4 sec interval = 2 sec
+ uint32_t vobu_off = (fraction * ((double)vobu_dif + .5));
+ // HACK: need to add +1, or else will land too soon (not sure why)
+ vobu_off++;
+ // get sector
+ if (vobu_off >= admap_len) {
+ fprintf(MSG_OUT, "ERR admap_interpolate: vobu_off >= admap_len");
+ return RESULT_FALSE;
+ }
+
+ int32_t vobu_idx = vobu_bgn + vobu_off;
+ *out_jump_sector = admap->vobu_start_sectors[vobu_idx];
+ return RESULT_TRUE;
+}
+/*
+Given two tmap entries and a known time, calc the time for the lo tmap entry
+*/
+int32_t dvdnav_tmap_calc_time_for_tmap_entry(vobu_admap_t *admap
+ , uint32_t admap_len, int32_t tmap_interval
+ , uint32_t cell_sector, uint32_t lo_sector, uint32_t hi_sector
+ , uint64_t cell_time, uint64_t *out_lo_time
+ ) {
+ if (lo_sector == hi_sector) {
+ fprintf(MSG_OUT, "ERR:lo_sector == hi_sector: %i\n", lo_sector);
+ return RESULT_FALSE;
+ }
+
+ // get vobus corresponding to lo, hi, cell
+ int32_t result = RESULT_FALSE;
+ uint32_t lo_vobu;
+ result = dvdnav_admap_search(admap, admap_len, lo_sector, &lo_vobu);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR lo_vobu: lo_sector=%i", lo_sector);
+ return RESULT_FALSE;
+ }
+ uint32_t hi_vobu;
+ result = dvdnav_admap_search(admap, admap_len, hi_sector, &hi_vobu);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR hi_vobu: hi_sector=%i", hi_sector);
+ return RESULT_FALSE;
+ }
+ uint32_t cell_vobu;
+ result = dvdnav_admap_search(admap, admap_len, cell_sector, &cell_vobu);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR cell_vobu: cell_sector=%i", cell_sector);
+ return RESULT_FALSE;
+ }
+
+ // calc position of cell relative to lo
+ double vobu_pct = (double)(cell_vobu - lo_vobu)
+ / (double)(hi_vobu - lo_vobu);
+ if (vobu_pct < 0 || vobu_pct > 1) {
+ fprintf(MSG_OUT, "ERR vobu_pct must be between 0 and 1");
+ return RESULT_FALSE;
+ }
+
+ // calc time of lo
+ uint64_t time_adj = (uint64_t)(tmap_interval * vobu_pct);
+ *out_lo_time = cell_time - time_adj;
+ return RESULT_TRUE;
+}
+/*
+Find the tmap entries on either side of a given sector
+*/
+int32_t dvdnav_tmap_get_entries_for_sector(dvdnav_t *this, dvd_state_t *state
+ , vobu_admap_t *admap, int32_t admap_len, vts_tmap_t *tmap, uint32_t tmap_len
+ , int32_t cell_idx, uint32_t cell_end_sector, uint32_t find_sector
+ , int32_t *lo_tmap, uint32_t *lo_sector
+ , int32_t *hi_tmap, uint32_t *hi_sector
+ ) {
+ int32_t result = RESULT_FALSE;
+ result = dvdnav_tmap_search(tmap, tmap_len, find_sector, lo_tmap, lo_sector);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:could not find lo idx: %i\n", find_sector);
+ return RESULT_FALSE;
+ }
+
+ uint32_t out_sector = 0;
+ // lo is last tmap entry; "fake" entry for one beyond
+ // and mark it with cell_end_sector
+ if (*lo_tmap == tmap_len - 1) {
+ *hi_tmap = TMAP_IDX_EDGE_END;
+ *hi_sector = cell_end_sector;
+ }
+ else {
+ *hi_tmap = *lo_tmap + 1;
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, *hi_tmap, &out_sector);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:could not find hi sector: %i\n", *hi_tmap);
+ return RESULT_FALSE;
+ }
+ *hi_sector = out_sector;
+ }
+ // HACK: handle cells that start at discontinuity entries
+ // first check if discontinuity applies
+ int32_t discontinuity = RESULT_FALSE;
+ if (*lo_tmap == TMAP_IDX_EDGE_BGN) {
+ int32_t out_cell_idx;
+ // get cell 1
+ uint64_t cell_1_bgn_time; uint32_t cell_1_bgn_sector;
+ uint64_t cell_1_end_time; uint32_t cell_1_end_sector;
+ result = dvdnav_cell_find(this, state, CELL_FIND_INDEX
+ , 1, &out_cell_idx, &cell_1_bgn_time, &cell_1_bgn_sector
+ , &cell_1_end_time, &cell_1_end_sector
+ );
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:could not retrieve cell 1\n");
+ return RESULT_FALSE;
+ }
+ // if cell 1 does not start at sector 0 then assume discontinuity
+ // NOTE: most DVDs should start at 0
+ if (cell_1_bgn_sector != 0) {
+ discontinuity = RESULT_TRUE;
+ }
+ }
+ else {
+ if ((tmap->map_ent[*lo_tmap] & (1 << 31)) != 0) {
+ discontinuity = RESULT_TRUE;
+ }
+ }
+ if (discontinuity) {
+ // HACK: since there is either
+ // (a) a non-zero sector start at cell_1
+ // (b) a discontinuity entry
+ // the vobu at lo_tmap is suspect. In order to find a vobu, assume that
+ // lo_tmap to hi_tmap is separated by the same number of vobus as
+ // hi_tmap to hi_tmap + 1
+ // This is a hack. It works in practice but there must be a better way....
+
+ // first get the vobu for tmap_2
+ uint32_t tmap_2_sector = 0, tmap_2_vobu;
+ result = dvdnav_tmap_get_entry(tmap, tmap_len
+ , *hi_tmap + 1, &tmap_2_sector);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:no tmap_2_sector: %i\n"
+ , *hi_tmap + 1);
+ return RESULT_FALSE;
+ }
+ result = dvdnav_admap_search(admap, admap_len
+ , tmap_2_sector, &tmap_2_vobu);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:no tmap_2_vobu: %i\n", tmap_2_vobu);
+ return RESULT_FALSE;
+ }
+
+ // now get the vobu for tmap_1
+ uint32_t tmap_1_vobu;
+ result = dvdnav_admap_search(admap, admap_len, *hi_sector, &tmap_1_vobu);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:no find tmap_1_vobu: %i\n", tmap_1_vobu);
+ return RESULT_FALSE;
+ }
+
+ // now calc the vobu for lo_tmap
+ uint32_t vobu_diff = tmap_2_vobu - tmap_1_vobu;
+ uint32_t tmap_neg1_vobu = tmap_1_vobu - vobu_diff;
+ if (tmap_neg1_vobu < 0 || tmap_neg1_vobu >= admap_len) {
+ fprintf(MSG_OUT, "ERR:tmap_neg1_vobu: %i\n", tmap_neg1_vobu);
+ return RESULT_FALSE;
+ }
+ *lo_sector = admap->vobu_start_sectors[tmap_neg1_vobu];
+ }
+ if (!(find_sector >= *lo_sector && find_sector <= *hi_sector)) {
+ fprintf(MSG_OUT, "ERR:find_sector not between lo/hi\n");
+ return RESULT_FALSE;
+ }
+ return RESULT_TRUE;
+}
+
+/*
+Find the nearest vobu by using the tmap
+*/
+int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state
+ , vobu_admap_t *admap, int32_t admap_len
+ , int32_t cell_idx
+ , uint64_t cell_bgn_time, uint32_t cell_bgn_sector
+ , uint32_t cell_end_sector
+ , uint64_t time_in_ms, uint32_t *out_jump_sector) {
+ // get tmap, tmap_len, tmap_interval
+ int32_t tmap_len, tmap_interval;
+ vts_tmap_t *tmap = dvdnav_tmap_get(this, state
+ , &tmap_len, &tmap_interval);
+ if (is_null(tmap, "tmap")) return RESULT_FALSE;
+
+ // get tmap entries on either side of cell_bgn
+ int32_t cell_bgn_lo_tmap; uint32_t cell_bgn_lo_sector;
+ int32_t cell_bgn_hi_tmap; uint32_t cell_bgn_hi_sector;
+ int32_t result = RESULT_FALSE;
+ result = dvdnav_tmap_get_entries_for_sector(this, state, admap, admap_len
+ , tmap, tmap_len, cell_idx, cell_end_sector, cell_bgn_sector
+ , &cell_bgn_lo_tmap, &cell_bgn_lo_sector
+ , &cell_bgn_hi_tmap, &cell_bgn_hi_sector
+ );
+ if (!result) return RESULT_FALSE;
+
+ // calc time of cell_bgn_lo
+ uint64_t cell_bgn_lo_time;
+ result = dvdnav_tmap_calc_time_for_tmap_entry(admap, admap_len, tmap_interval
+ , cell_bgn_sector, cell_bgn_lo_sector, cell_bgn_hi_sector
+ , cell_bgn_time, &cell_bgn_lo_time
+ );
+ if (!result) return RESULT_FALSE;
+
+ // calc tmap_time of jump_time relative to cell_bgn_lo
+ uint64_t seek_offset = time_in_ms - cell_bgn_lo_time;
+ uint32_t seek_idx, jump_lo_sector, jump_hi_sector;
+ seek_idx = (uint32_t)(seek_offset / tmap_interval);
+ uint32_t seek_remainder = seek_offset - (seek_idx * tmap_interval);
+ double seek_pct = (double)seek_remainder / (double)tmap_interval;
+
+ // get tmap entries on either side of jump_time
+ uint32_t jump_lo_idx = (uint32_t)(cell_bgn_lo_tmap + seek_idx);
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, jump_lo_idx, &jump_lo_sector);
+ if (!result) return RESULT_FALSE;
+
+ uint32_t jump_hi_idx = jump_lo_idx + 1; // +1 handled by get_entry
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, jump_hi_idx, &jump_hi_sector);
+ if (!result) return RESULT_FALSE;
+
+ // interpolate sector
+ result = dvdnav_admap_interpolate_vobu(this, admap, admap_len
+ , jump_lo_sector, jump_hi_sector, seek_pct, out_jump_sector);
+
+ if (!result) return RESULT_FALSE;
+ return RESULT_TRUE;
+}
+/*
+Find the nearest vobu by using the cell boundaries
+*/
+int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this
+ , vobu_admap_t *admap, int32_t admap_len
+ , uint64_t cell_bgn_time, uint32_t cell_bgn_sector
+ , uint64_t cell_end_time, uint32_t cell_end_sector
+ , uint64_t time_in_ms
+ , uint32_t *out_jump_sector
+ ) {
+ // get jump_offset
+ uint64_t jump_offset = time_in_ms - cell_bgn_time;
+ if (jump_offset < 0) {
+ fprintf(MSG_OUT, "ERR:jump_offset < 0\n");
+ return RESULT_FALSE;
+ }
+
+ // get cell_len
+ uint64_t cell_len = cell_end_time - cell_bgn_time;
+ if (cell_len < 0) {
+ fprintf(MSG_OUT, "ERR:cell_len < 0\n");
+ return RESULT_FALSE;
+ }
+
+ // get jump_pct
+ double jump_pct = (double)jump_offset / 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
+ cell_end_sector += 1;
+ int32_t result = RESULT_FALSE;
+ result = dvdnav_admap_interpolate_vobu(this, admap, admap_len
+ , cell_bgn_sector, cell_end_sector, jump_pct, out_jump_sector);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:find_by_admap.interpolate\n");
+ return RESULT_FALSE;
+ }
+ return RESULT_TRUE;
+}
+/*
+Find the nearest vobu and jump to it
+*/
+int32_t dvdnav_jump_to_sector_by_time(dvdnav_t *this
+ , uint32_t time_in_pts_ticks) {
+ int32_t result = RESULT_FALSE;
+
+ // convert time to milliseconds
+ uint64_t time_in_ms = time_in_pts_ticks / 90;
+
+ // get variables that will be used across both functions
+ dvd_state_t *state = &(this->vm->state);
+ if (is_null(state, "state")) goto exit;
+
+ // get cell info
+ int32_t cell_idx;
+ uint64_t cell_bgn_time; uint32_t cell_bgn_sector;
+ uint64_t cell_end_time; uint32_t cell_end_sector;
+ result = dvdnav_cell_find(this, state, CELL_FIND_TIME, time_in_ms
+ , &cell_idx
+ , &cell_bgn_time, &cell_bgn_sector
+ , &cell_end_time, &cell_end_sector);
+ if (!result) goto exit;
+
+ // get admap
+ int32_t admap_len;
+ vobu_admap_t *admap = dvdnav_admap_get(this, state, &admap_len);
+ if (is_null(admap, "admap")) goto exit;
+
+ // find sector
+ uint32_t jump_sector;
+ result = dvdnav_find_vobu_by_tmap(this, state, admap, admap_len
+ , cell_idx, cell_bgn_time, cell_bgn_sector, cell_end_sector
+ , time_in_ms, &jump_sector);
+ if (!result) {// bad tmap; interpolate over cell
+ result = dvdnav_find_vobu_by_cell_boundaries(this, admap, admap_len
+ , cell_bgn_time, cell_bgn_sector
+ , cell_end_time, cell_end_sector
+ , time_in_ms, &jump_sector);
+ if (!result) {
+ goto exit;
+ }
+ }
+
+ // may need to reget time when jump goes to diff cell (occurs near time 0)
+ if ( jump_sector < cell_bgn_sector
+ || jump_sector > cell_end_sector) {
+ result = dvdnav_cell_find(this, state, CELL_FIND_SECTOR, jump_sector
+ , &cell_idx
+ , &cell_bgn_time, &cell_bgn_sector
+ , &cell_end_time, &cell_end_sector);
+ if (!result) {
+ fprintf(MSG_OUT, "ERR:unable to find cell for %i\n", jump_sector);
+ goto exit;
+ }
+ }
+
+ // jump to sector
+ uint32_t sector_off = jump_sector - cell_bgn_sector;
+ this->cur_cell_time = 0;
+ if (vm_jump_cell_block(this->vm, cell_idx, sector_off)) {
+ pthread_mutex_lock(&this->vm_lock);
+ this->vm->hop_channel += HOP_SEEK;
+ pthread_mutex_unlock(&this->vm_lock);
+ result = RESULT_TRUE;
+ }
+
+exit:
+ return result;
+}
Return to “Development around libVLC”
Users browsing this forum: No registered users and 21 guests