aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMartijn Kaijser <machine.sanctum@gmail.com>2013-08-01 13:16:00 -0700
committerMartijn Kaijser <machine.sanctum@gmail.com>2013-08-01 13:16:00 -0700
commitf2757569ff24ba46e88639faad8a7f5830978510 (patch)
treee90294d202bc676aa40b8ad5d5ca218720c94127 /lib
parentda4921e697e209f07f392ae2429d67fe464dcef4 (diff)
parent88fd7aec411ca0deb27dd2d51d64bedeb10057ab (diff)
Merge pull request #2957 from jmbreuer/upstream-libdvdnav-seek
More accurate seeking with libdvdnav
Diffstat (limited to 'lib')
-rw-r--r--lib/libdvd/libdvdnav/src/dvdnav/dvdnav.h8
-rw-r--r--lib/libdvd/libdvdnav/src/dvdnav_internal.h39
-rw-r--r--lib/libdvd/libdvdnav/src/searching.c591
-rw-r--r--lib/libdvd/patches/libdvdnav-accurate_seek.diff689
4 files changed, 1327 insertions, 0 deletions
diff --git a/lib/libdvd/libdvdnav/src/dvdnav/dvdnav.h b/lib/libdvd/libdvdnav/src/dvdnav/dvdnav.h
index 359b951aad..506a2867d3 100644
--- a/lib/libdvd/libdvdnav/src/dvdnav/dvdnav.h
+++ b/lib/libdvd/libdvdnav/src/dvdnav/dvdnav.h
@@ -373,6 +373,14 @@ dvdnav_status_t dvdnav_sector_search(dvdnav_t *self,
int64_t dvdnav_get_current_time(dvdnav_t *self);
/*
+ * Find the nearest vobu and jump to it
+ *
+ * Alternative to dvdnav_time_search
+ */
+dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this,
+ uint64_t time_in_pts_ticks, int32_t mode);
+
+/*
* Stop playing the current position and start playback of the title
* from the specified timecode.
*
diff --git a/lib/libdvd/libdvdnav/src/dvdnav_internal.h b/lib/libdvd/libdvdnav/src/dvdnav_internal.h
index d64a5ba7cd..df26014d8d 100644
--- a/lib/libdvd/libdvdnav/src/dvdnav_internal.h
+++ b/lib/libdvd/libdvdnav/src/dvdnav_internal.h
@@ -125,6 +125,45 @@ typedef struct {
} ATTRIBUTE_PACKED spu_status_t;
#endif
+/*
+ * Describes a given time, and the closest sector, vobu and tmap index
+ */
+typedef struct {
+ uint64_t time;
+ uint32_t sector;
+ uint32_t vobu_idx;
+ int32_t tmap_idx;
+} dvdnav_pos_data_t;
+
+/*
+ * Encapsulates cell data
+ */
+typedef struct {
+ int32_t idx;
+ dvdnav_pos_data_t *bgn;
+ dvdnav_pos_data_t *end;
+} dvdnav_cell_data_t;
+
+/*
+ * Encapsulates common variables used by internal functions of jump_to_time
+ */
+typedef struct {
+ vobu_admap_t *admap;
+ int32_t admap_len;
+ vts_tmap_t *tmap;
+ int32_t tmap_len;
+ int32_t tmap_interval;
+} dvdnav_jump_args_t;
+
+/*
+ * Utility constants for jump_to_time
+ */
+#define TMAP_IDX_EDGE_BGN -1
+#define TMAP_IDX_EDGE_END -2
+#define JUMP_MODE_TIME_AFTER 1
+#define JUMP_MODE_TIME_DEFAULT 0
+#define JUMP_MODE_TIME_BEFORE -1
+
typedef struct dvdnav_vobu_s {
int32_t vobu_start; /* Logical Absolute. MAX needed is 0x300000 */
int32_t vobu_length;
diff --git a/lib/libdvd/libdvdnav/src/searching.c b/lib/libdvd/libdvdnav/src/searching.c
index a5e48fe7da..0115e2fa21 100644
--- a/lib/libdvd/libdvdnav/src/searching.c
+++ b/lib/libdvd/libdvdnav/src/searching.c
@@ -36,6 +36,7 @@
#include "vm/decoder.h"
#include "vm/vm.h"
#include "dvdnav_internal.h"
+#include <dvdread/ifo_read.h>
/*
#define LOG_DEBUG
@@ -805,3 +806,593 @@ dvdnav_status_t dvdnav_set_state(dvdnav_t *this, dvd_state_t *save_state)
pthread_mutex_unlock(&this->vm_lock);
return DVDNAV_STATUS_OK;
}
+
+
+
+/* Get an admap and admap_len */
+static vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state,
+ int32_t *admap_len) {
+ vobu_admap_t *admap = NULL;
+ switch(state->domain) {
+ case 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, "Unknown domain");
+ return NULL;
+ }
+ }
+ if (admap == NULL) return NULL;
+
+ *admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE;
+ if (*admap_len <= 0) {
+ fprintf(MSG_OUT, "admap_len <= 0");
+ return NULL;
+ }
+ return admap;
+}
+
+/* Get a tmap, tmap_len and tmap_interval */
+static vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state,
+ int32_t *tmap_len, int32_t *tmap_interval) {
+ int32_t vts_idx = 0;
+ domain_t domain;
+ ifo_handle_t *ifo = NULL;
+ vts_tmapt_t *tmapt = NULL;
+ uint16_t tmap_count = 0;
+ int32_t pgcN = 0;
+ vts_tmap_t *tmap = NULL;
+ int32_t result = 0;
+
+ vts_idx = state->vtsN;
+ domain = state->domain;
+ switch(domain) {
+ case FP_DOMAIN:
+ case VTSM_DOMAIN:
+ case VMGM_DOMAIN: {
+ ifo = this->vm->vmgi;
+ break;
+ }
+ case VTS_DOMAIN: {
+ ifo = this->vm->vtsi;
+ break;
+ }
+ default: {
+ fprintf(MSG_OUT, "unknown domain for tmap");
+ return NULL;
+ }
+ }
+ if (ifo == NULL) return NULL;
+ tmapt = ifo->vts_tmapt;
+ /* HACK: ifo->vts_tmapt is NULL b/c ifo_read.c never loads it
+ * load ifo->vts_tmapt directly*/
+ if (tmapt == NULL) {
+ result = ifoRead_VTS_TMAPT(ifo);
+ if (!result) {
+ return NULL;
+ }
+ tmapt = ifo->vts_tmapt;
+ if (tmapt == NULL) return NULL;
+ }
+
+ tmap_count = tmapt->nr_of_tmaps;
+ pgcN = state->pgcN - 1; /* -1 b/c pgcN is base1 */
+ if (pgcN < 0) {
+ fprintf(MSG_OUT, "pgcN < 0");
+ return NULL;
+ }
+
+ /* get tmap */
+ switch(domain) {
+ case FP_DOMAIN:
+ case VMGM_DOMAIN:
+ case VTSM_DOMAIN: {
+ if (tmap_count == 0) {
+ fprintf(MSG_OUT, "tmap_count == 0");
+ return NULL;
+ }
+ tmap = &tmapt->tmap[0]; /* ASSUME: vmgi only has one time map */
+ break;
+ }
+ case VTS_DOMAIN: {
+ if (pgcN >= tmap_count) {
+ fprintf(MSG_OUT, "pgcN >= tmap_count; pgcN=%i tmap_count=%i",
+ pgcN, tmap_count);
+ return NULL;
+ }
+ tmap = &tmapt->tmap[pgcN];
+ break;
+ }
+ }
+ if (tmap == NULL) return NULL;
+
+ /* tmap->tmu is in seconds; convert to millisecs */
+ *tmap_interval = tmap->tmu * 1000;
+ if (*tmap_interval == 0) {
+ fprintf(MSG_OUT, "tmap_interval == 0");
+ return NULL;
+ }
+ *tmap_len = tmap->nr_of_entries;
+ if (*tmap_len == 0) {
+ fprintf(MSG_OUT, "tmap_len == 0");
+ return NULL;
+ }
+ return tmap;
+}
+
+/* Get a sector from a tmap */
+static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len,
+ int32_t idx, uint32_t *sector) {
+ /* tmaps start at idx 0 which represents a sector at time 1 * tmap_interval
+ * this creates a "fake" tmap index at idx -1 for sector 0 */
+ if (idx == TMAP_IDX_EDGE_BGN) {
+ *sector = 0;
+ return 1;
+ }
+ if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) {
+ fprintf(MSG_OUT, "idx out of bounds idx=%i %i", idx, tmap_len);
+ return 0;
+ }
+ /* 0x7fffffff unsets discontinuity bit if present */
+ *sector = tmap->map_ent[idx] & 0x7fffffff;
+ return 1;
+}
+
+/* Do a binary search for earlier admap index near find_sector */
+static int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len,
+ uint32_t find_sector, uint32_t *vobu) {
+ int32_t adj = 1;
+ int32_t prv_pos = 0;
+ int32_t prv_len = admap_len;
+ int32_t cur_len = 0;
+ int32_t cur_idx = 0;
+ uint32_t cur_sector = 0;
+ while (1) {
+ cur_len = prv_len / 2;
+ /* need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) */
+ if (prv_len % 2 == 1) ++cur_len;
+ cur_idx = prv_pos + (cur_len * adj);
+ if (cur_idx < 0) cur_idx = 0;
+ else if (cur_idx >= admap_len) cur_idx = admap_len - 1;
+
+ cur_sector = admap->vobu_start_sectors[cur_idx];
+ if (find_sector < cur_sector) adj = -1;
+ else if (find_sector > cur_sector) adj = 1;
+ else if (find_sector == cur_sector) {
+ *vobu = cur_idx;
+ return 1;
+ }
+ if (cur_len == 1) {/* no smaller intervals left */
+ if (adj == -1) {/* last comparison was greater; take lesser */
+ cur_idx -= 1;
+ cur_sector = admap->vobu_start_sectors[cur_idx];
+ }
+ *vobu = cur_idx;
+ return 1;
+ }
+ prv_len = cur_len;
+ prv_pos = cur_idx;
+ }
+}
+
+/* Do a binary search for the earlier tmap entry near find_sector */
+static int32_t dvdnav_tmap_search(vts_tmap_t *tmap, uint32_t tmap_len,
+ uint32_t find_sector, int32_t *tmap_idx, uint32_t *sector) {
+ int32_t adj = 1;
+ int32_t prv_pos = 0;
+ int32_t prv_len = tmap_len;
+ int32_t result = 0;
+ int32_t cur_len = 0;
+ int32_t cur_idx = 0;
+ uint32_t cur_sector = 0;
+ while (1) {
+ cur_len = prv_len / 2;
+ /* need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) */
+ if (prv_len % 2 == 1) ++cur_len;
+ cur_idx = prv_pos + (cur_len * adj);
+ if (cur_idx < 0) cur_idx = 0;
+ else if (cur_idx >= tmap_len) cur_idx = tmap_len - 1;
+ cur_sector = 0;
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector);
+ if (!result) return 0;
+ if (find_sector < cur_sector) adj = -1;
+ else if (find_sector > cur_sector) adj = 1;
+ else if (find_sector == cur_sector) {
+ *tmap_idx = cur_idx;
+ *sector = cur_sector;
+ return 1;
+ }
+ if (cur_len == 1) {/* no smaller intervals left */
+ if (adj == -1) {/* last comparison was greater; take lesser */
+ if (cur_idx == 0) { /* fake tmap index for sector 0 */
+ cur_idx = TMAP_IDX_EDGE_BGN;
+ cur_sector = 0;
+ }
+ else {
+ cur_idx -= 1;
+ result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector);
+ if (!result) return 0;
+ }
+ }
+ *tmap_idx = cur_idx;
+ *sector = cur_sector;
+ return 1;
+ }
+ prv_len = cur_len;
+ prv_pos = cur_idx;
+ }
+}
+
+/* Find the cell for a given time */
+static int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state,
+ uint64_t find_val, dvdnav_cell_data_t *cell_data) {
+ uint32_t cells_len = 0;
+ uint32_t cells_bgn = 0;
+ uint32_t cells_end = 0;
+ uint32_t cell_idx = 0;
+ pgc_t *pgc = NULL;
+ int pgN = 0;
+ cell_playback_t *cell = NULL;
+ int found = 0;
+
+ pgc = state->pgc;
+ if (pgc == NULL) return 0;
+ cells_len = pgc->nr_of_cells;
+ if (cells_len == 0) {
+ fprintf(MSG_OUT, "cells_len == 0");
+ return 0;
+ }
+
+ /* get cells_bgn, cells_end */
+ if (this->pgc_based) {
+ cells_bgn = 1;
+ cells_end = cells_len;
+ }
+ else {
+ pgN = state->pgN;
+ cells_bgn = pgc->program_map[pgN - 1]; /* -1 b/c pgN is 1 based? */
+ if (pgN < pgc->nr_of_programs) {
+ cells_end = pgc->program_map[pgN] - 1;
+ }
+ else {
+ cells_end = cells_len;
+ }
+ }
+
+ /* search cells */
+ for (cell_idx = cells_bgn; cell_idx <= cells_end; cell_idx++) {
+ cell = &(pgc->cell_playback[cell_idx - 1]); /* -1 b/c cell is base1 */
+ /* if angle block, only consider first angleBlock
+ * (others are "redundant" for purpose of search) */
+ if ( cell->block_type == BLOCK_TYPE_ANGLE_BLOCK
+ && cell->block_mode != BLOCK_MODE_FIRST_CELL) {
+ continue;
+ }
+ cell_data->bgn->sector = cell->first_sector;
+ cell_data->end->sector = cell->last_sector;
+
+ /* 90 pts to ms */
+ cell_data->end->time += (dvdnav_convert_time(&cell->playback_time) / 90);
+ if ( find_val >= cell_data->bgn->time
+ && find_val <= cell_data->end->time) {
+ found = 1;
+ break;
+ }
+ cell_data->bgn->time = cell_data->end->time;
+ }
+
+ /* found cell: set var */
+ if (found) {
+ cell_data->idx = cell_idx;
+ }
+ else
+ fprintf(MSG_OUT, "cell not found; find=%"PRId64"", find_val);
+ return found;
+}
+
+/* Given two sectors and a fraction, calc the corresponding vobu */
+static int32_t dvdnav_admap_interpolate_vobu(dvdnav_jump_args_t *args,
+ dvdnav_pos_data_t *bgn, dvdnav_pos_data_t *end, uint32_t fraction,
+ uint32_t *jump_sector) {
+ int32_t result = 0;
+ uint32_t vobu_len = 0;
+ uint32_t vobu_adj = 0;
+ uint32_t vobu_idx = 0;
+
+ /* get bgn->vobu_idx */
+ result = dvdnav_admap_search(args->admap, args->admap_len,
+ bgn->sector, &bgn->vobu_idx);
+ if (!result) {
+ fprintf(MSG_OUT, "admap_interpolate: could not find sector_bgn");
+ return 0;
+ }
+
+ /* get end->vobu_idx */
+ result = dvdnav_admap_search(args->admap, args->admap_len,
+ end->sector, &end->vobu_idx);
+ if (!result) {
+ fprintf(MSG_OUT, "admap_interpolate: could not find sector_end");
+ return 0;
+ }
+
+ vobu_len = end->vobu_idx - bgn->vobu_idx;
+ /* +500 to round up else 74% of a 4 sec interval = 2 sec */
+ vobu_adj = ((fraction * vobu_len) + 500) / 1000;
+ /* HACK: need to add +1, or else will land too soon (not sure why) */
+ vobu_adj++;
+ vobu_idx = bgn->vobu_idx + vobu_adj;
+ if (vobu_idx >= args->admap_len) {
+ fprintf(MSG_OUT, "admap_interpolate: vobu_idx >= admap_len");
+ return 0;
+ }
+ *jump_sector = args->admap->vobu_start_sectors[vobu_idx];
+ return 1;
+}
+
+/* Given two tmap entries and a time, calc the time for the lo tmap entry */
+static int32_t dvdnav_tmap_calc_time_for_tmap_entry(dvdnav_jump_args_t *args,
+ dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi,
+ dvdnav_pos_data_t *pos, uint64_t *out_time) {
+ int32_t result = 0;
+ uint32_t vobu_pct = 0;
+ uint64_t time_adj = 0;
+
+ if (lo->sector == hi->sector) {
+ fprintf(MSG_OUT, "lo->sector == hi->sector: %i", lo->sector);
+ return 0;
+ }
+
+ /* get vobus corresponding to lo, hi, pos */
+ result = dvdnav_admap_search(args->admap, args->admap_len,
+ lo->sector, &lo->vobu_idx);
+ if (!result) {
+ fprintf(MSG_OUT, "lo->vobu: lo->sector=%i", lo->sector);
+ return 0;
+ }
+ result = dvdnav_admap_search(args->admap, args->admap_len,
+ hi->sector, &hi->vobu_idx);
+ if (!result) {
+ fprintf(MSG_OUT, "hi->vobu: hi->sector=%i", hi->sector);
+ return 0;
+ }
+ result = dvdnav_admap_search(args->admap, args->admap_len,
+ pos->sector, &pos->vobu_idx);
+ if (!result) {
+ fprintf(MSG_OUT, "pos->vobu: pos->sector=%i", pos->sector);
+ return 0;
+ }
+
+ /* calc position of cell relative to lo */
+ vobu_pct = ((pos->vobu_idx - lo->vobu_idx) * 1000)
+ / ( hi->vobu_idx - lo->vobu_idx);
+ if (vobu_pct < 0 || vobu_pct > 1000) {
+ fprintf(MSG_OUT, "vobu_pct must be between 0 and 1000");
+ return 0;
+ }
+
+ /* calc time of lo */
+ time_adj = (uint64_t)((args->tmap_interval * vobu_pct) / 1000);
+ *out_time = pos->time - time_adj;
+ return 1;
+}
+
+/* Find the tmap entries on either side of a given sector */
+static int32_t dvdnav_tmap_get_entries_for_sector(dvdnav_t *this,
+ dvd_state_t *state, dvdnav_jump_args_t *args,
+ dvdnav_cell_data_t *cell_data, uint32_t find_sector,
+ dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi) {
+ int32_t result = 0;
+
+ result = dvdnav_tmap_search(args->tmap, args->tmap_len, find_sector,
+ &lo->tmap_idx, &lo->sector);
+ if (!result) {
+ fprintf(MSG_OUT, "could not find lo idx: %i", find_sector);
+ return 0;
+ }
+
+ /* HACK: Most DVDs have a tmap that starts at sector 0
+ * However, some have initial dummy cells that are not seekable
+ * (restricted = y).
+ * These cells will throw off the tmap calcs when in the first playable cell.
+ * For now, assume that lo->sector is equal to the cell->bgn->sector
+ * Note that for most DVDs this will be 0
+ * (Since they will have no dummy cells and cell 1 will start at sector 0)
+ */
+ if (lo->tmap_idx == TMAP_IDX_EDGE_BGN) {
+ lo->sector = cell_data->bgn->sector;
+ }
+
+ if (lo->tmap_idx == args->tmap_len - 1) {
+ /* lo is last tmap entry; "fake" entry for one beyond
+ * and mark it with cell_end_sector */
+ hi->tmap_idx = TMAP_IDX_EDGE_END;
+ hi->sector = cell_data->end->sector;
+ }
+ else {
+ hi->tmap_idx = lo->tmap_idx + 1;
+ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
+ hi->tmap_idx, &hi->sector);
+ if (!result) {
+ fprintf(MSG_OUT, "could not find hi idx: %i", find_sector);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* Find the nearest vobu by using the tmap */
+static int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state,
+ dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data,
+ dvdnav_pos_data_t *jump) {
+ uint64_t seek_offset = 0;
+ uint32_t seek_idx = 0;
+ int32_t result = 0;
+ dvdnav_pos_data_t *cell_bgn_lo = NULL;
+ dvdnav_pos_data_t *cell_bgn_hi = NULL;
+ dvdnav_pos_data_t *jump_lo = NULL;
+ dvdnav_pos_data_t *jump_hi = NULL;
+
+ /* get tmap, tmap_len, tmap_interval */
+ args->tmap = dvdnav_tmap_get(this, state,
+ &args->tmap_len, &args->tmap_interval);
+ if (args->tmap == NULL) return 0;
+
+ /* get tmap entries on either side of cell_bgn */
+ cell_bgn_lo = &(dvdnav_pos_data_t){0};
+ cell_bgn_hi = &(dvdnav_pos_data_t){0};
+ result = dvdnav_tmap_get_entries_for_sector(this, state, args, cell_data,
+ cell_data->bgn->sector, cell_bgn_lo, cell_bgn_hi);
+ if (!result) return 0;
+
+ /* calc time of cell_bgn_lo */
+ result = dvdnav_tmap_calc_time_for_tmap_entry(args, cell_bgn_lo, cell_bgn_hi,
+ cell_data->bgn, &cell_bgn_lo->time);
+ if (!result) return 0;
+
+ /* calc time of jump_time relative to cell_bgn_lo */
+ seek_offset = jump->time - cell_bgn_lo->time;
+ seek_idx = (uint32_t)(seek_offset / args->tmap_interval);
+ uint32_t seek_remainder = seek_offset - (seek_idx * args->tmap_interval);
+ uint32_t seek_pct = (seek_remainder * 1000) / args->tmap_interval;
+
+ /* get tmap entries on either side of jump_time */
+ jump_lo = &(dvdnav_pos_data_t){0};
+ jump_hi = &(dvdnav_pos_data_t){0};
+
+ /* if seek_idx == 0, then tmap_indexes are the same, do not re-get
+ * also, note cell_bgn_lo will already have sector if TMAP_IDX_EDGE_BGN */
+ if (seek_idx == 0) {
+ jump_lo = cell_bgn_lo;
+ jump_hi = cell_bgn_hi;
+ }
+ else {
+ jump_lo->tmap_idx = (uint32_t)(cell_bgn_lo->tmap_idx + seek_idx);
+ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
+ jump_lo->tmap_idx, &jump_lo->sector);
+ if (!result) return 0;
+
+ /* +1 handled by dvdnav_tmap_get_entry */
+ jump_hi->tmap_idx = jump_lo->tmap_idx + 1;
+ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
+ jump_hi->tmap_idx, &jump_hi->sector);
+ if (!result) return 0;
+ }
+
+ /* interpolate sector */
+ result = dvdnav_admap_interpolate_vobu(args, jump_lo, jump_hi,
+ seek_pct, &jump->sector);
+
+ return result;
+}
+
+/* Find the nearest vobu by using the cell boundaries */
+static int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this,
+ dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data,
+ dvdnav_pos_data_t *jump) {
+ uint64_t jump_offset = 0;
+ uint64_t cell_len = 0;
+ uint32_t jump_pct = 0;
+ int32_t result = 0;
+
+ /* get jump_offset */
+ jump_offset = jump->time - cell_data->bgn->time;
+ if (jump_offset < 0) {
+ fprintf(MSG_OUT, "jump_offset < 0");
+ return 0;
+ }
+ cell_len = cell_data->end->time - cell_data->bgn->time;
+ if (cell_len < 0) {
+ fprintf(MSG_OUT, "cell_len < 0");
+ return 0;
+ }
+ jump_pct = (jump_offset * 1000) / cell_len;
+
+ /* get sector */
+ /* NOTE: end cell sector in VTS_PGC is last sector of cell
+ * this last sector is not the start of a VOBU
+ * +1 to get sector that is the start of a VOBU
+ * start of a VOBU is needed in order to index into admap */
+ cell_data->end->sector += 1;
+ result = dvdnav_admap_interpolate_vobu(args,
+ cell_data->bgn, cell_data->end, jump_pct, &jump->sector);
+ if (!result) {
+ fprintf(MSG_OUT, "find_by_admap.interpolate");
+ return 0;
+ }
+ return 1;
+}
+
+/* Jump to sector by time */
+/* NOTE: Mode is currently unimplemented. Only 0 should be passed. */
+/* 1 and -1 are for future implementation */
+/* 0: Default. Jump to a time which may be either <> time_in_pts_ticks */
+/* 1: After. Always jump to a time that is > time_in_pts_ticks */
+/* -1: Before. Always jump to a time that is < time_in_pts_ticks */
+dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this,
+ uint64_t time_in_pts_ticks, int32_t mode) {
+ if (mode != JUMP_MODE_TIME_DEFAULT) return DVDNAV_STATUS_ERR;
+ int32_t result = DVDNAV_STATUS_ERR;
+ dvd_state_t *state = NULL;
+ uint32_t sector_off = 0;
+ dvdnav_pos_data_t *jump = NULL;
+ dvdnav_cell_data_t *cell_data = NULL;
+ dvdnav_jump_args_t *args = NULL;
+
+ jump = &(dvdnav_pos_data_t){0};
+ /* convert time to milliseconds */
+ jump->time = time_in_pts_ticks / 90;
+
+ /* get variables that will be used across both functions */
+ state = &(this->vm->state);
+ if (state == NULL) goto exit;
+
+ /* get cell info */
+ cell_data = &(dvdnav_cell_data_t){0};
+ cell_data->bgn = &(dvdnav_pos_data_t){0};
+ cell_data->end = &(dvdnav_pos_data_t){0};
+ result = dvdnav_cell_find(this, state, jump->time, cell_data);
+ if (!result) goto exit;
+
+ /* get admap */
+ args = &(dvdnav_jump_args_t){0};
+ args->admap = dvdnav_admap_get(this, state, &args->admap_len);
+ if (args->admap == NULL) goto exit;
+
+ /* find sector */
+ result = dvdnav_find_vobu_by_tmap(this, state, args, cell_data, jump);
+ if (!result) {/* bad tmap; interpolate over cell */
+ result = dvdnav_find_vobu_by_cell_boundaries(this, args, cell_data, jump);
+ if (!result) {
+ goto exit;
+ }
+ }
+
+#ifdef LOG_DEBUG
+ fprintf(MSG_OUT, "libdvdnav: seeking to time=%lu\n", jump->time);
+ fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
+#endif
+
+ /* jump to sector */
+ sector_off = jump->sector - cell_data->bgn->sector;
+ this->cur_cell_time = 0;
+ if (vm_jump_cell_block(this->vm, cell_data->idx, sector_off)) {
+ pthread_mutex_lock(&this->vm_lock);
+ this->vm->hop_channel += HOP_SEEK;
+ pthread_mutex_unlock(&this->vm_lock);
+ result = DVDNAV_STATUS_OK;
+ }
+
+#ifdef LOG_DEBUG
+ fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u\n", state->cellN, state->blockN);
+#endif
+
+exit:
+ return result;
+}
diff --git a/lib/libdvd/patches/libdvdnav-accurate_seek.diff b/lib/libdvd/patches/libdvdnav-accurate_seek.diff
new file mode 100644
index 0000000000..efcda5f8d3
--- /dev/null
+++ b/lib/libdvd/patches/libdvdnav-accurate_seek.diff
@@ -0,0 +1,689 @@
+More accurate seeking with libdvdnav
+
+applied to current codebase from:
+ http://lists.mplayerhq.hu/pipermail/dvdnav-discuss/2012-December/001837.html
+
+full credit goes to gnosygnu, see
+ http://forum.videolan.org/viewtopic.php?f=32&t=76308&start=20#p316583
+ https://github.com/xbmc/xbmc/pull/2957#issuecomment-20855719
+
+related tickets:
+ http://trac.xbmc.org/ticket/12212
+ http://trac.xbmc.org/ticket/14493
+
+
+diff --git a/libdvdnav/src/dvdnav/dvdnav.h b/libdvdnav/src/dvdnav/dvdnav.h
+index 359b951..506a286 100644
+--- a/libdvdnav/src/dvdnav/dvdnav.h
++++ b/libdvdnav/src/dvdnav/dvdnav.h
+@@ -373,6 +373,14 @@ dvdnav_status_t dvdnav_sector_search(dvdnav_t *self,
+ int64_t dvdnav_get_current_time(dvdnav_t *self);
+
+ /*
++ * Find the nearest vobu and jump to it
++ *
++ * Alternative to dvdnav_time_search
++ */
++dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this,
++ uint64_t time_in_pts_ticks, int32_t mode);
++
++/*
+ * Stop playing the current position and start playback of the title
+ * from the specified timecode.
+ *
+diff --git a/libdvdnav/src/dvdnav_internal.h b/libdvdnav/src/dvdnav_internal.h
+index d64a5ba..df26014 100644
+--- a/libdvdnav/src/dvdnav_internal.h
++++ b/libdvdnav/src/dvdnav_internal.h
+@@ -125,6 +125,45 @@ typedef struct {
+ } ATTRIBUTE_PACKED spu_status_t;
+ #endif
+
++/*
++ * Describes a given time, and the closest sector, vobu and tmap index
++ */
++typedef struct {
++ uint64_t time;
++ uint32_t sector;
++ uint32_t vobu_idx;
++ int32_t tmap_idx;
++} dvdnav_pos_data_t;
++
++/*
++ * Encapsulates cell data
++ */
++typedef struct {
++ int32_t idx;
++ dvdnav_pos_data_t *bgn;
++ dvdnav_pos_data_t *end;
++} dvdnav_cell_data_t;
++
++/*
++ * Encapsulates common variables used by internal functions of jump_to_time
++ */
++typedef struct {
++ vobu_admap_t *admap;
++ int32_t admap_len;
++ vts_tmap_t *tmap;
++ int32_t tmap_len;
++ int32_t tmap_interval;
++} dvdnav_jump_args_t;
++
++/*
++ * Utility constants for jump_to_time
++ */
++#define TMAP_IDX_EDGE_BGN -1
++#define TMAP_IDX_EDGE_END -2
++#define JUMP_MODE_TIME_AFTER 1
++#define JUMP_MODE_TIME_DEFAULT 0
++#define JUMP_MODE_TIME_BEFORE -1
++
+ typedef struct dvdnav_vobu_s {
+ int32_t vobu_start; /* Logical Absolute. MAX needed is 0x300000 */
+ int32_t vobu_length;
+diff --git a/libdvdnav/src/searching.c b/libdvdnav/src/searching.c
+index a5e48fe..0115e2f 100644
+--- a/libdvdnav/src/searching.c
++++ b/libdvdnav/src/searching.c
+@@ -36,6 +36,7 @@
+ #include "vm/decoder.h"
+ #include "vm/vm.h"
+ #include "dvdnav_internal.h"
++#include <dvdread/ifo_read.h>
+
+ /*
+ #define LOG_DEBUG
+@@ -805,3 +806,593 @@ dvdnav_status_t dvdnav_set_state(dvdnav_t *this, dvd_state_t *save_state)
+ pthread_mutex_unlock(&this->vm_lock);
+ return DVDNAV_STATUS_OK;
+ }
++
++
++
++/* Get an admap and admap_len */
++static vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state,
++ int32_t *admap_len) {
++ vobu_admap_t *admap = NULL;
++ switch(state->domain) {
++ case 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, "Unknown domain");
++ return NULL;
++ }
++ }
++ if (admap == NULL) return NULL;
++
++ *admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE;
++ if (*admap_len <= 0) {
++ fprintf(MSG_OUT, "admap_len <= 0");
++ return NULL;
++ }
++ return admap;
++}
++
++/* Get a tmap, tmap_len and tmap_interval */
++static vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state,
++ int32_t *tmap_len, int32_t *tmap_interval) {
++ int32_t vts_idx = 0;
++ domain_t domain;
++ ifo_handle_t *ifo = NULL;
++ vts_tmapt_t *tmapt = NULL;
++ uint16_t tmap_count = 0;
++ int32_t pgcN = 0;
++ vts_tmap_t *tmap = NULL;
++ int32_t result = 0;
++
++ vts_idx = state->vtsN;
++ domain = state->domain;
++ switch(domain) {
++ case FP_DOMAIN:
++ case VTSM_DOMAIN:
++ case VMGM_DOMAIN: {
++ ifo = this->vm->vmgi;
++ break;
++ }
++ case VTS_DOMAIN: {
++ ifo = this->vm->vtsi;
++ break;
++ }
++ default: {
++ fprintf(MSG_OUT, "unknown domain for tmap");
++ return NULL;
++ }
++ }
++ if (ifo == NULL) return NULL;
++ tmapt = ifo->vts_tmapt;
++ /* HACK: ifo->vts_tmapt is NULL b/c ifo_read.c never loads it
++ * load ifo->vts_tmapt directly*/
++ if (tmapt == NULL) {
++ result = ifoRead_VTS_TMAPT(ifo);
++ if (!result) {
++ return NULL;
++ }
++ tmapt = ifo->vts_tmapt;
++ if (tmapt == NULL) return NULL;
++ }
++
++ tmap_count = tmapt->nr_of_tmaps;
++ pgcN = state->pgcN - 1; /* -1 b/c pgcN is base1 */
++ if (pgcN < 0) {
++ fprintf(MSG_OUT, "pgcN < 0");
++ return NULL;
++ }
++
++ /* get tmap */
++ switch(domain) {
++ case FP_DOMAIN:
++ case VMGM_DOMAIN:
++ case VTSM_DOMAIN: {
++ if (tmap_count == 0) {
++ fprintf(MSG_OUT, "tmap_count == 0");
++ return NULL;
++ }
++ tmap = &tmapt->tmap[0]; /* ASSUME: vmgi only has one time map */
++ break;
++ }
++ case VTS_DOMAIN: {
++ if (pgcN >= tmap_count) {
++ fprintf(MSG_OUT, "pgcN >= tmap_count; pgcN=%i tmap_count=%i",
++ pgcN, tmap_count);
++ return NULL;
++ }
++ tmap = &tmapt->tmap[pgcN];
++ break;
++ }
++ }
++ if (tmap == NULL) return NULL;
++
++ /* tmap->tmu is in seconds; convert to millisecs */
++ *tmap_interval = tmap->tmu * 1000;
++ if (*tmap_interval == 0) {
++ fprintf(MSG_OUT, "tmap_interval == 0");
++ return NULL;
++ }
++ *tmap_len = tmap->nr_of_entries;
++ if (*tmap_len == 0) {
++ fprintf(MSG_OUT, "tmap_len == 0");
++ return NULL;
++ }
++ return tmap;
++}
++
++/* Get a sector from a tmap */
++static int32_t dvdnav_tmap_get_entry(vts_tmap_t *tmap, uint16_t tmap_len,
++ int32_t idx, uint32_t *sector) {
++ /* tmaps start at idx 0 which represents a sector at time 1 * tmap_interval
++ * this creates a "fake" tmap index at idx -1 for sector 0 */
++ if (idx == TMAP_IDX_EDGE_BGN) {
++ *sector = 0;
++ return 1;
++ }
++ if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) {
++ fprintf(MSG_OUT, "idx out of bounds idx=%i %i", idx, tmap_len);
++ return 0;
++ }
++ /* 0x7fffffff unsets discontinuity bit if present */
++ *sector = tmap->map_ent[idx] & 0x7fffffff;
++ return 1;
++}
++
++/* Do a binary search for earlier admap index near find_sector */
++static int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len,
++ uint32_t find_sector, uint32_t *vobu) {
++ int32_t adj = 1;
++ int32_t prv_pos = 0;
++ int32_t prv_len = admap_len;
++ int32_t cur_len = 0;
++ int32_t cur_idx = 0;
++ uint32_t cur_sector = 0;
++ while (1) {
++ cur_len = prv_len / 2;
++ /* need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) */
++ if (prv_len % 2 == 1) ++cur_len;
++ cur_idx = prv_pos + (cur_len * adj);
++ if (cur_idx < 0) cur_idx = 0;
++ else if (cur_idx >= admap_len) cur_idx = admap_len - 1;
++
++ cur_sector = admap->vobu_start_sectors[cur_idx];
++ if (find_sector < cur_sector) adj = -1;
++ else if (find_sector > cur_sector) adj = 1;
++ else if (find_sector == cur_sector) {
++ *vobu = cur_idx;
++ return 1;
++ }
++ if (cur_len == 1) {/* no smaller intervals left */
++ if (adj == -1) {/* last comparison was greater; take lesser */
++ cur_idx -= 1;
++ cur_sector = admap->vobu_start_sectors[cur_idx];
++ }
++ *vobu = cur_idx;
++ return 1;
++ }
++ prv_len = cur_len;
++ prv_pos = cur_idx;
++ }
++}
++
++/* Do a binary search for the earlier tmap entry near find_sector */
++static int32_t dvdnav_tmap_search(vts_tmap_t *tmap, uint32_t tmap_len,
++ uint32_t find_sector, int32_t *tmap_idx, uint32_t *sector) {
++ int32_t adj = 1;
++ int32_t prv_pos = 0;
++ int32_t prv_len = tmap_len;
++ int32_t result = 0;
++ int32_t cur_len = 0;
++ int32_t cur_idx = 0;
++ uint32_t cur_sector = 0;
++ while (1) {
++ cur_len = prv_len / 2;
++ /* need to add 1 when prv_len == 3 (cur_len shoud go to 2, not 1) */
++ if (prv_len % 2 == 1) ++cur_len;
++ cur_idx = prv_pos + (cur_len * adj);
++ if (cur_idx < 0) cur_idx = 0;
++ else if (cur_idx >= tmap_len) cur_idx = tmap_len - 1;
++ cur_sector = 0;
++ result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector);
++ if (!result) return 0;
++ if (find_sector < cur_sector) adj = -1;
++ else if (find_sector > cur_sector) adj = 1;
++ else if (find_sector == cur_sector) {
++ *tmap_idx = cur_idx;
++ *sector = cur_sector;
++ return 1;
++ }
++ if (cur_len == 1) {/* no smaller intervals left */
++ if (adj == -1) {/* last comparison was greater; take lesser */
++ if (cur_idx == 0) { /* fake tmap index for sector 0 */
++ cur_idx = TMAP_IDX_EDGE_BGN;
++ cur_sector = 0;
++ }
++ else {
++ cur_idx -= 1;
++ result = dvdnav_tmap_get_entry(tmap, tmap_len, cur_idx, &cur_sector);
++ if (!result) return 0;
++ }
++ }
++ *tmap_idx = cur_idx;
++ *sector = cur_sector;
++ return 1;
++ }
++ prv_len = cur_len;
++ prv_pos = cur_idx;
++ }
++}
++
++/* Find the cell for a given time */
++static int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state,
++ uint64_t find_val, dvdnav_cell_data_t *cell_data) {
++ uint32_t cells_len = 0;
++ uint32_t cells_bgn = 0;
++ uint32_t cells_end = 0;
++ uint32_t cell_idx = 0;
++ pgc_t *pgc = NULL;
++ int pgN = 0;
++ cell_playback_t *cell = NULL;
++ int found = 0;
++
++ pgc = state->pgc;
++ if (pgc == NULL) return 0;
++ cells_len = pgc->nr_of_cells;
++ if (cells_len == 0) {
++ fprintf(MSG_OUT, "cells_len == 0");
++ return 0;
++ }
++
++ /* get cells_bgn, cells_end */
++ if (this->pgc_based) {
++ cells_bgn = 1;
++ cells_end = cells_len;
++ }
++ else {
++ pgN = state->pgN;
++ cells_bgn = pgc->program_map[pgN - 1]; /* -1 b/c pgN is 1 based? */
++ if (pgN < pgc->nr_of_programs) {
++ cells_end = pgc->program_map[pgN] - 1;
++ }
++ else {
++ cells_end = cells_len;
++ }
++ }
++
++ /* search cells */
++ for (cell_idx = cells_bgn; cell_idx <= cells_end; cell_idx++) {
++ cell = &(pgc->cell_playback[cell_idx - 1]); /* -1 b/c cell is base1 */
++ /* if angle block, only consider first angleBlock
++ * (others are "redundant" for purpose of search) */
++ if ( cell->block_type == BLOCK_TYPE_ANGLE_BLOCK
++ && cell->block_mode != BLOCK_MODE_FIRST_CELL) {
++ continue;
++ }
++ cell_data->bgn->sector = cell->first_sector;
++ cell_data->end->sector = cell->last_sector;
++
++ /* 90 pts to ms */
++ cell_data->end->time += (dvdnav_convert_time(&cell->playback_time) / 90);
++ if ( find_val >= cell_data->bgn->time
++ && find_val <= cell_data->end->time) {
++ found = 1;
++ break;
++ }
++ cell_data->bgn->time = cell_data->end->time;
++ }
++
++ /* found cell: set var */
++ if (found) {
++ cell_data->idx = cell_idx;
++ }
++ else
++ fprintf(MSG_OUT, "cell not found; find=%"PRId64"", find_val);
++ return found;
++}
++
++/* Given two sectors and a fraction, calc the corresponding vobu */
++static int32_t dvdnav_admap_interpolate_vobu(dvdnav_jump_args_t *args,
++ dvdnav_pos_data_t *bgn, dvdnav_pos_data_t *end, uint32_t fraction,
++ uint32_t *jump_sector) {
++ int32_t result = 0;
++ uint32_t vobu_len = 0;
++ uint32_t vobu_adj = 0;
++ uint32_t vobu_idx = 0;
++
++ /* get bgn->vobu_idx */
++ result = dvdnav_admap_search(args->admap, args->admap_len,
++ bgn->sector, &bgn->vobu_idx);
++ if (!result) {
++ fprintf(MSG_OUT, "admap_interpolate: could not find sector_bgn");
++ return 0;
++ }
++
++ /* get end->vobu_idx */
++ result = dvdnav_admap_search(args->admap, args->admap_len,
++ end->sector, &end->vobu_idx);
++ if (!result) {
++ fprintf(MSG_OUT, "admap_interpolate: could not find sector_end");
++ return 0;
++ }
++
++ vobu_len = end->vobu_idx - bgn->vobu_idx;
++ /* +500 to round up else 74% of a 4 sec interval = 2 sec */
++ vobu_adj = ((fraction * vobu_len) + 500) / 1000;
++ /* HACK: need to add +1, or else will land too soon (not sure why) */
++ vobu_adj++;
++ vobu_idx = bgn->vobu_idx + vobu_adj;
++ if (vobu_idx >= args->admap_len) {
++ fprintf(MSG_OUT, "admap_interpolate: vobu_idx >= admap_len");
++ return 0;
++ }
++ *jump_sector = args->admap->vobu_start_sectors[vobu_idx];
++ return 1;
++}
++
++/* Given two tmap entries and a time, calc the time for the lo tmap entry */
++static int32_t dvdnav_tmap_calc_time_for_tmap_entry(dvdnav_jump_args_t *args,
++ dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi,
++ dvdnav_pos_data_t *pos, uint64_t *out_time) {
++ int32_t result = 0;
++ uint32_t vobu_pct = 0;
++ uint64_t time_adj = 0;
++
++ if (lo->sector == hi->sector) {
++ fprintf(MSG_OUT, "lo->sector == hi->sector: %i", lo->sector);
++ return 0;
++ }
++
++ /* get vobus corresponding to lo, hi, pos */
++ result = dvdnav_admap_search(args->admap, args->admap_len,
++ lo->sector, &lo->vobu_idx);
++ if (!result) {
++ fprintf(MSG_OUT, "lo->vobu: lo->sector=%i", lo->sector);
++ return 0;
++ }
++ result = dvdnav_admap_search(args->admap, args->admap_len,
++ hi->sector, &hi->vobu_idx);
++ if (!result) {
++ fprintf(MSG_OUT, "hi->vobu: hi->sector=%i", hi->sector);
++ return 0;
++ }
++ result = dvdnav_admap_search(args->admap, args->admap_len,
++ pos->sector, &pos->vobu_idx);
++ if (!result) {
++ fprintf(MSG_OUT, "pos->vobu: pos->sector=%i", pos->sector);
++ return 0;
++ }
++
++ /* calc position of cell relative to lo */
++ vobu_pct = ((pos->vobu_idx - lo->vobu_idx) * 1000)
++ / ( hi->vobu_idx - lo->vobu_idx);
++ if (vobu_pct < 0 || vobu_pct > 1000) {
++ fprintf(MSG_OUT, "vobu_pct must be between 0 and 1000");
++ return 0;
++ }
++
++ /* calc time of lo */
++ time_adj = (uint64_t)((args->tmap_interval * vobu_pct) / 1000);
++ *out_time = pos->time - time_adj;
++ return 1;
++}
++
++/* Find the tmap entries on either side of a given sector */
++static int32_t dvdnav_tmap_get_entries_for_sector(dvdnav_t *this,
++ dvd_state_t *state, dvdnav_jump_args_t *args,
++ dvdnav_cell_data_t *cell_data, uint32_t find_sector,
++ dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi) {
++ int32_t result = 0;
++
++ result = dvdnav_tmap_search(args->tmap, args->tmap_len, find_sector,
++ &lo->tmap_idx, &lo->sector);
++ if (!result) {
++ fprintf(MSG_OUT, "could not find lo idx: %i", find_sector);
++ return 0;
++ }
++
++ /* HACK: Most DVDs have a tmap that starts at sector 0
++ * However, some have initial dummy cells that are not seekable
++ * (restricted = y).
++ * These cells will throw off the tmap calcs when in the first playable cell.
++ * For now, assume that lo->sector is equal to the cell->bgn->sector
++ * Note that for most DVDs this will be 0
++ * (Since they will have no dummy cells and cell 1 will start at sector 0)
++ */
++ if (lo->tmap_idx == TMAP_IDX_EDGE_BGN) {
++ lo->sector = cell_data->bgn->sector;
++ }
++
++ if (lo->tmap_idx == args->tmap_len - 1) {
++ /* lo is last tmap entry; "fake" entry for one beyond
++ * and mark it with cell_end_sector */
++ hi->tmap_idx = TMAP_IDX_EDGE_END;
++ hi->sector = cell_data->end->sector;
++ }
++ else {
++ hi->tmap_idx = lo->tmap_idx + 1;
++ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
++ hi->tmap_idx, &hi->sector);
++ if (!result) {
++ fprintf(MSG_OUT, "could not find hi idx: %i", find_sector);
++ return 0;
++ }
++ }
++ return 1;
++}
++
++/* Find the nearest vobu by using the tmap */
++static int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state,
++ dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data,
++ dvdnav_pos_data_t *jump) {
++ uint64_t seek_offset = 0;
++ uint32_t seek_idx = 0;
++ int32_t result = 0;
++ dvdnav_pos_data_t *cell_bgn_lo = NULL;
++ dvdnav_pos_data_t *cell_bgn_hi = NULL;
++ dvdnav_pos_data_t *jump_lo = NULL;
++ dvdnav_pos_data_t *jump_hi = NULL;
++
++ /* get tmap, tmap_len, tmap_interval */
++ args->tmap = dvdnav_tmap_get(this, state,
++ &args->tmap_len, &args->tmap_interval);
++ if (args->tmap == NULL) return 0;
++
++ /* get tmap entries on either side of cell_bgn */
++ cell_bgn_lo = &(dvdnav_pos_data_t){0};
++ cell_bgn_hi = &(dvdnav_pos_data_t){0};
++ result = dvdnav_tmap_get_entries_for_sector(this, state, args, cell_data,
++ cell_data->bgn->sector, cell_bgn_lo, cell_bgn_hi);
++ if (!result) return 0;
++
++ /* calc time of cell_bgn_lo */
++ result = dvdnav_tmap_calc_time_for_tmap_entry(args, cell_bgn_lo, cell_bgn_hi,
++ cell_data->bgn, &cell_bgn_lo->time);
++ if (!result) return 0;
++
++ /* calc time of jump_time relative to cell_bgn_lo */
++ seek_offset = jump->time - cell_bgn_lo->time;
++ seek_idx = (uint32_t)(seek_offset / args->tmap_interval);
++ uint32_t seek_remainder = seek_offset - (seek_idx * args->tmap_interval);
++ uint32_t seek_pct = (seek_remainder * 1000) / args->tmap_interval;
++
++ /* get tmap entries on either side of jump_time */
++ jump_lo = &(dvdnav_pos_data_t){0};
++ jump_hi = &(dvdnav_pos_data_t){0};
++
++ /* if seek_idx == 0, then tmap_indexes are the same, do not re-get
++ * also, note cell_bgn_lo will already have sector if TMAP_IDX_EDGE_BGN */
++ if (seek_idx == 0) {
++ jump_lo = cell_bgn_lo;
++ jump_hi = cell_bgn_hi;
++ }
++ else {
++ jump_lo->tmap_idx = (uint32_t)(cell_bgn_lo->tmap_idx + seek_idx);
++ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
++ jump_lo->tmap_idx, &jump_lo->sector);
++ if (!result) return 0;
++
++ /* +1 handled by dvdnav_tmap_get_entry */
++ jump_hi->tmap_idx = jump_lo->tmap_idx + 1;
++ result = dvdnav_tmap_get_entry(args->tmap, args->tmap_len,
++ jump_hi->tmap_idx, &jump_hi->sector);
++ if (!result) return 0;
++ }
++
++ /* interpolate sector */
++ result = dvdnav_admap_interpolate_vobu(args, jump_lo, jump_hi,
++ seek_pct, &jump->sector);
++
++ return result;
++}
++
++/* Find the nearest vobu by using the cell boundaries */
++static int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this,
++ dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data,
++ dvdnav_pos_data_t *jump) {
++ uint64_t jump_offset = 0;
++ uint64_t cell_len = 0;
++ uint32_t jump_pct = 0;
++ int32_t result = 0;
++
++ /* get jump_offset */
++ jump_offset = jump->time - cell_data->bgn->time;
++ if (jump_offset < 0) {
++ fprintf(MSG_OUT, "jump_offset < 0");
++ return 0;
++ }
++ cell_len = cell_data->end->time - cell_data->bgn->time;
++ if (cell_len < 0) {
++ fprintf(MSG_OUT, "cell_len < 0");
++ return 0;
++ }
++ jump_pct = (jump_offset * 1000) / cell_len;
++
++ /* get sector */
++ /* NOTE: end cell sector in VTS_PGC is last sector of cell
++ * this last sector is not the start of a VOBU
++ * +1 to get sector that is the start of a VOBU
++ * start of a VOBU is needed in order to index into admap */
++ cell_data->end->sector += 1;
++ result = dvdnav_admap_interpolate_vobu(args,
++ cell_data->bgn, cell_data->end, jump_pct, &jump->sector);
++ if (!result) {
++ fprintf(MSG_OUT, "find_by_admap.interpolate");
++ return 0;
++ }
++ return 1;
++}
++
++/* Jump to sector by time */
++/* NOTE: Mode is currently unimplemented. Only 0 should be passed. */
++/* 1 and -1 are for future implementation */
++/* 0: Default. Jump to a time which may be either <> time_in_pts_ticks */
++/* 1: After. Always jump to a time that is > time_in_pts_ticks */
++/* -1: Before. Always jump to a time that is < time_in_pts_ticks */
++dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this,
++ uint64_t time_in_pts_ticks, int32_t mode) {
++ if (mode != JUMP_MODE_TIME_DEFAULT) return DVDNAV_STATUS_ERR;
++ int32_t result = DVDNAV_STATUS_ERR;
++ dvd_state_t *state = NULL;
++ uint32_t sector_off = 0;
++ dvdnav_pos_data_t *jump = NULL;
++ dvdnav_cell_data_t *cell_data = NULL;
++ dvdnav_jump_args_t *args = NULL;
++
++ jump = &(dvdnav_pos_data_t){0};
++ /* convert time to milliseconds */
++ jump->time = time_in_pts_ticks / 90;
++
++ /* get variables that will be used across both functions */
++ state = &(this->vm->state);
++ if (state == NULL) goto exit;
++
++ /* get cell info */
++ cell_data = &(dvdnav_cell_data_t){0};
++ cell_data->bgn = &(dvdnav_pos_data_t){0};
++ cell_data->end = &(dvdnav_pos_data_t){0};
++ result = dvdnav_cell_find(this, state, jump->time, cell_data);
++ if (!result) goto exit;
++
++ /* get admap */
++ args = &(dvdnav_jump_args_t){0};
++ args->admap = dvdnav_admap_get(this, state, &args->admap_len);
++ if (args->admap == NULL) goto exit;
++
++ /* find sector */
++ result = dvdnav_find_vobu_by_tmap(this, state, args, cell_data, jump);
++ if (!result) {/* bad tmap; interpolate over cell */
++ result = dvdnav_find_vobu_by_cell_boundaries(this, args, cell_data, jump);
++ if (!result) {
++ goto exit;
++ }
++ }
++
++#ifdef LOG_DEBUG
++ fprintf(MSG_OUT, "libdvdnav: seeking to time=%lu\n", jump->time);
++ fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
++#endif
++
++ /* jump to sector */
++ sector_off = jump->sector - cell_data->bgn->sector;
++ this->cur_cell_time = 0;
++ if (vm_jump_cell_block(this->vm, cell_data->idx, sector_off)) {
++ pthread_mutex_lock(&this->vm_lock);
++ this->vm->hop_channel += HOP_SEEK;
++ pthread_mutex_unlock(&this->vm_lock);
++ result = DVDNAV_STATUS_OK;
++ }
++
++#ifdef LOG_DEBUG
++ fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u\n", state->cellN, state->blockN);
++#endif
++
++exit:
++ return result;
++}