diff options
author | Martijn Kaijser <machine.sanctum@gmail.com> | 2013-08-01 13:16:00 -0700 |
---|---|---|
committer | Martijn Kaijser <machine.sanctum@gmail.com> | 2013-08-01 13:16:00 -0700 |
commit | f2757569ff24ba46e88639faad8a7f5830978510 (patch) | |
tree | e90294d202bc676aa40b8ad5d5ca218720c94127 /lib | |
parent | da4921e697e209f07f392ae2429d67fe464dcef4 (diff) | |
parent | 88fd7aec411ca0deb27dd2d51d64bedeb10057ab (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.h | 8 | ||||
-rw-r--r-- | lib/libdvd/libdvdnav/src/dvdnav_internal.h | 39 | ||||
-rw-r--r-- | lib/libdvd/libdvdnav/src/searching.c | 591 | ||||
-rw-r--r-- | lib/libdvd/patches/libdvdnav-accurate_seek.diff | 689 |
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; ++} |