2
* Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
4
* This file is part of libdvdnav, a DVD navigation library.
6
* libdvdnav is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* libdvdnav is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License along
17
* with libdvdnav; if not, write to the Free Software Foundation, Inc.,
18
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32
#include "dvdnav/dvdnav.h"
33
#include <dvdread/nav_types.h>
34
#include <dvdread/ifo_types.h>
36
#include "vm/decoder.h"
38
#include "dvdnav_internal.h"
44
/* Searching API calls */
46
/* Scan the ADMAP for a particular block number. */
47
/* Return placed in vobu. */
48
/* Returns error status */
49
/* FIXME: Maybe need to handle seeking outside current cell. */
50
static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, int next, uint32_t *vobu) {
51
vobu_admap_t *admap = NULL;
54
fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block);
58
/* Search through the VOBU_ADMAP for the nearest VOBU
59
* to the target block */
63
admap = this->vm->vmgi->menu_vobu_admap;
66
admap = this->vm->vtsi->menu_vobu_admap;
69
admap = this->vm->vtsi->vts_vobu_admap;
72
fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n");
76
uint32_t vobu_start, next_vobu = 0;
77
int admap_entries = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)/VOBU_ADMAP_SIZE;
79
/* Search through ADMAP for best sector */
80
vobu_start = SRI_END_OF_CELL;
81
/* FIXME: Implement a faster search algorithm */
82
while(address < admap_entries) {
83
next_vobu = admap->vobu_start_sectors[address];
85
/* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */
87
if(vobu_start <= seekto_block && next_vobu > seekto_block)
89
vobu_start = next_vobu;
92
*vobu = next ? next_vobu : vobu_start;
93
return DVDNAV_STATUS_OK;
95
fprintf(MSG_OUT, "libdvdnav: admap not located\n");
96
return DVDNAV_STATUS_ERR;
99
/* FIXME: right now, this function does not use the time tables but interpolates
100
only the cell times */
101
dvdnav_status_t dvdnav_time_search(dvdnav_t *this,
104
uint64_t target = time;
106
uint32_t first_cell_nr, last_cell_nr, cell_nr;
108
cell_playback_t *cell;
111
if(this->position_current.still != 0) {
112
printerr("Cannot seek in a still frame.");
113
return DVDNAV_STATUS_ERR;
116
pthread_mutex_lock(&this->vm_lock);
117
state = &(this->vm->state);
119
printerr("No current PGC.");
120
pthread_mutex_unlock(&this->vm_lock);
121
return DVDNAV_STATUS_ERR;
125
this->cur_cell_time = 0;
126
if (this->pgc_based) {
128
last_cell_nr = state->pgc->nr_of_cells;
130
/* Find start cell of program. */
131
first_cell_nr = state->pgc->program_map[state->pgN-1];
132
/* Find end cell of program */
133
if(state->pgN < state->pgc->nr_of_programs)
134
last_cell_nr = state->pgc->program_map[state->pgN] - 1;
136
last_cell_nr = state->pgc->nr_of_cells;
140
for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
141
cell = &(state->pgc->cell_playback[cell_nr-1]);
142
if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL)
144
length = dvdnav_convert_time(&cell->playback_time);
145
if (target >= length) {
148
/* FIXME: there must be a better way than interpolation */
149
target = target * (cell->last_sector - cell->first_sector + 1) / length;
150
target += cell->first_sector;
160
fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
161
cell_nr, first_cell_nr, last_cell_nr);
163
if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) == DVDNAV_STATUS_OK) {
164
uint32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
166
if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
168
fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
169
state->cellN, state->blockN, target, vobu, start);
171
this->vm->hop_channel += HOP_SEEK;
172
pthread_mutex_unlock(&this->vm_lock);
173
return DVDNAV_STATUS_OK;
178
fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
179
printerr("Error when seeking.");
180
pthread_mutex_unlock(&this->vm_lock);
181
return DVDNAV_STATUS_ERR;
184
dvdnav_status_t dvdnav_sector_search(dvdnav_t *this,
185
uint64_t offset, int32_t origin) {
187
uint32_t current_pos;
189
uint32_t cur_cell_nr;
191
uint32_t first_cell_nr, last_cell_nr, cell_nr;
194
cell_playback_t *cell;
196
dvdnav_status_t result;
198
if(this->position_current.still != 0) {
199
printerr("Cannot seek in a still frame.");
200
return DVDNAV_STATUS_ERR;
203
result = dvdnav_get_position(this, &target, &length);
205
return DVDNAV_STATUS_ERR;
208
pthread_mutex_lock(&this->vm_lock);
209
state = &(this->vm->state);
211
printerr("No current PGC.");
212
pthread_mutex_unlock(&this->vm_lock);
213
return DVDNAV_STATUS_ERR;
216
fprintf(MSG_OUT, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length);
217
fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
220
current_pos = target;
221
cur_sector = this->vobu.vobu_start + this->vobu.blockN;
222
cur_cell_nr = state->cellN;
226
if(offset >= length) {
227
printerr("Request to seek behind end.");
228
pthread_mutex_unlock(&this->vm_lock);
229
return DVDNAV_STATUS_ERR;
234
if(target + offset >= length) {
235
printerr("Request to seek behind end.");
236
pthread_mutex_unlock(&this->vm_lock);
237
return DVDNAV_STATUS_ERR;
242
if(length < offset) {
243
printerr("Request to seek before start.");
244
pthread_mutex_unlock(&this->vm_lock);
245
return DVDNAV_STATUS_ERR;
247
target = length - offset;
251
printerr("Illegal seek mode.");
252
pthread_mutex_unlock(&this->vm_lock);
253
return DVDNAV_STATUS_ERR;
255
forward = target > current_pos;
257
this->cur_cell_time = 0;
258
if (this->pgc_based) {
260
last_cell_nr = state->pgc->nr_of_cells;
262
/* Find start cell of program. */
263
first_cell_nr = state->pgc->program_map[state->pgN-1];
264
/* Find end cell of program */
265
if(state->pgN < state->pgc->nr_of_programs)
266
last_cell_nr = state->pgc->program_map[state->pgN] - 1;
268
last_cell_nr = state->pgc->nr_of_cells;
272
for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
273
cell = &(state->pgc->cell_playback[cell_nr-1]);
274
if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL)
276
length = cell->last_sector - cell->first_sector + 1;
277
if (target >= length) {
280
/* convert the target sector from Cell-relative to absolute physical sector */
281
target += cell->first_sector;
282
if (forward && (cell_nr == cur_cell_nr)) {
284
/* if we are seeking forward from the current position, make sure
285
* we move to a new position that is after our current position.
286
* simply truncating to the vobu will go backwards */
287
if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) != DVDNAV_STATUS_OK)
289
if (vobu <= cur_sector) {
290
if (dvdnav_scan_admap(this, state->domain, target, 1, &vobu) != DVDNAV_STATUS_OK)
292
if (vobu > cell->last_sector) {
293
if (cell_nr == last_cell_nr)
296
cell = &(state->pgc->cell_playback[cell_nr-1]);
297
target = cell->first_sector;
311
fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
312
cell_nr, first_cell_nr, last_cell_nr);
314
if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) == DVDNAV_STATUS_OK) {
315
int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
317
if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
319
fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
320
state->cellN, state->blockN, target, vobu, start);
322
this->vm->hop_channel += HOP_SEEK;
323
pthread_mutex_unlock(&this->vm_lock);
324
return DVDNAV_STATUS_OK;
329
fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
330
fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", target);
331
printerr("Error when seeking.");
332
pthread_mutex_unlock(&this->vm_lock);
333
return DVDNAV_STATUS_ERR;
336
dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) {
337
int32_t title, old_part;
339
if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK)
340
return dvdnav_part_play(this, title, part);
341
return DVDNAV_STATUS_ERR;
344
dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) {
345
pthread_mutex_lock(&this->vm_lock);
346
if(!this->vm->state.pgc) {
347
printerr("No current PGC.");
348
pthread_mutex_unlock(&this->vm_lock);
349
return DVDNAV_STATUS_ERR;
353
fprintf(MSG_OUT, "libdvdnav: previous chapter\n");
355
if (!vm_jump_prev_pg(this->vm)) {
356
fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n");
357
printerr("Skip to previous chapter failed.");
358
pthread_mutex_unlock(&this->vm_lock);
359
return DVDNAV_STATUS_ERR;
361
this->cur_cell_time = 0;
362
this->position_current.still = 0;
363
this->vm->hop_channel++;
365
fprintf(MSG_OUT, "libdvdnav: previous chapter done\n");
367
pthread_mutex_unlock(&this->vm_lock);
369
return DVDNAV_STATUS_OK;
372
dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) {
373
pthread_mutex_lock(&this->vm_lock);
374
if(!this->vm->state.pgc) {
375
printerr("No current PGC.");
376
pthread_mutex_unlock(&this->vm_lock);
377
return DVDNAV_STATUS_ERR;
381
fprintf(MSG_OUT, "libdvdnav: top chapter\n");
383
if (!vm_jump_top_pg(this->vm)) {
384
fprintf(MSG_OUT, "libdvdnav: top chapter failed.\n");
385
printerr("Skip to top chapter failed.");
386
pthread_mutex_unlock(&this->vm_lock);
387
return DVDNAV_STATUS_ERR;
389
this->cur_cell_time = 0;
390
this->position_current.still = 0;
391
this->vm->hop_channel++;
393
fprintf(MSG_OUT, "libdvdnav: top chapter done\n");
395
pthread_mutex_unlock(&this->vm_lock);
397
return DVDNAV_STATUS_OK;
400
dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) {
403
pthread_mutex_lock(&this->vm_lock);
404
if(!this->vm->state.pgc) {
405
printerr("No current PGC.");
410
fprintf(MSG_OUT, "libdvdnav: next chapter\n");
412
/* make a copy of current VM and try to navigate the copy to the next PG */
413
try_vm = vm_new_copy(this->vm);
414
if (try_vm == NULL) {
415
printerr("Unable to copy the VM.");
419
if (!vm_jump_next_pg(try_vm) || try_vm->stopped) {
420
vm_free_copy(try_vm);
421
/* next_pg failed, try to jump at least to the next cell */
422
try_vm = vm_new_copy(this->vm);
423
if (try_vm == NULL) {
424
printerr("Unable to copy the VM.");
427
vm_get_next_cell(try_vm);
428
if (try_vm->stopped) {
429
vm_free_copy(try_vm);
430
fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n");
431
printerr("Skip to next chapter failed.");
435
this->cur_cell_time = 0;
436
/* merge changes on success */
437
vm_merge(this->vm, try_vm);
438
vm_free_copy(try_vm);
439
this->position_current.still = 0;
440
this->vm->hop_channel++;
442
fprintf(MSG_OUT, "libdvdnav: next chapter done\n");
444
pthread_mutex_unlock(&this->vm_lock);
446
return DVDNAV_STATUS_OK;
449
pthread_mutex_unlock(&this->vm_lock);
450
return DVDNAV_STATUS_ERR;
453
dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) {
456
pthread_mutex_lock(&this->vm_lock);
457
if(!this->vm->state.pgc) {
458
printerr("No current PGC.");
462
this->cur_cell_time = 0;
463
/* make a copy of current VM and try to navigate the copy to the menu */
464
try_vm = vm_new_copy(this->vm);
465
if (try_vm == NULL) {
466
printerr("Unable to copy VM.");
470
if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) {
472
if (vm_jump_resume(try_vm) && !try_vm->stopped) {
473
/* merge changes on success */
474
vm_merge(this->vm, try_vm);
475
vm_free_copy(try_vm);
476
this->position_current.still = 0;
477
this->vm->hop_channel++;
478
pthread_mutex_unlock(&this->vm_lock);
479
return DVDNAV_STATUS_OK;
482
if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root;
484
if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) {
485
/* merge changes on success */
486
vm_merge(this->vm, try_vm);
487
vm_free_copy(try_vm);
488
this->position_current.still = 0;
489
this->vm->hop_channel++;
490
pthread_mutex_unlock(&this->vm_lock);
491
return DVDNAV_STATUS_OK;
493
vm_free_copy(try_vm);
494
printerr("No such menu or menu not reachable.");
499
pthread_mutex_unlock(&this->vm_lock);
500
return DVDNAV_STATUS_ERR;
503
dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos,
506
int32_t cell_nr, first_cell_nr, last_cell_nr;
507
cell_playback_t *cell;
511
printerr("Virtual DVD machine not started.");
512
return DVDNAV_STATUS_ERR;
515
pthread_mutex_lock(&this->vm_lock);
516
state = &(this->vm->state);
517
if(!state->pgc || this->vm->stopped) {
518
printerr("No current PGC.");
519
pthread_mutex_unlock(&this->vm_lock);
520
return DVDNAV_STATUS_ERR;
522
if (this->position_current.hop_channel != this->vm->hop_channel ||
523
this->position_current.domain != state->domain ||
524
this->position_current.vts != state->vtsN ||
525
this->position_current.cell_restart != state->cell_restart) {
526
printerr("New position not yet determined.");
527
pthread_mutex_unlock(&this->vm_lock);
528
return DVDNAV_STATUS_ERR;
531
/* Get current sector */
532
cur_sector = this->vobu.vobu_start + this->vobu.blockN;
534
if (this->pgc_based) {
536
last_cell_nr = state->pgc->nr_of_cells;
538
/* Find start cell of program. */
539
first_cell_nr = state->pgc->program_map[state->pgN-1];
540
/* Find end cell of program */
541
if(state->pgN < state->pgc->nr_of_programs)
542
last_cell_nr = state->pgc->program_map[state->pgN] - 1;
544
last_cell_nr = state->pgc->nr_of_cells;
549
for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) {
550
cell = &(state->pgc->cell_playback[cell_nr-1]);
551
if (cell_nr == state->cellN) {
552
/* the current sector is in this cell,
553
* pos is length of PG up to here + sector's offset in this cell */
554
*pos = *len + cur_sector - cell->first_sector;
556
*len += cell->last_sector - cell->first_sector + 1;
559
assert((signed)*pos != -1);
561
pthread_mutex_unlock(&this->vm_lock);
563
return DVDNAV_STATUS_OK;
566
dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this,
570
uint32_t first_cell_nr;
571
uint32_t last_cell_nr;
572
cell_playback_t *first_cell;
573
cell_playback_t *last_cell;
576
state = &(this->vm->state);
578
printerr("No current PGC.");
579
return DVDNAV_STATUS_ERR;
582
/* Get current sector */
583
cur_sector = this->vobu.vobu_start + this->vobu.blockN;
585
/* Now find first and last cells in title. */
586
first_cell_nr = state->pgc->program_map[0];
587
first_cell = &(state->pgc->cell_playback[first_cell_nr-1]);
588
last_cell_nr = state->pgc->nr_of_cells;
589
last_cell = &(state->pgc->cell_playback[last_cell_nr-1]);
591
*pos = cur_sector - first_cell->first_sector;
592
*len = last_cell->last_sector - first_cell->first_sector;
594
return DVDNAV_STATUS_OK;
597
uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration) {
600
title_info_t *ptitle = NULL;
601
ptt_info_t *ptt = NULL;
602
ifo_handle_t *ifo = NULL;
604
cell_playback_t *cell;
605
uint64_t length, *tmp=NULL;
609
pthread_mutex_lock(&this->vm_lock);
610
if(!this->vm->vmgi) {
611
printerr("Bad VM state or missing VTSI.");
615
/* don't report an error but be nice */
619
ifo = vm_get_title_ifo(this->vm, title);
620
if(!ifo || !ifo->vts_pgcit) {
621
printerr("Couldn't open IFO for chosen title, exit.");
626
ptitle = &this->vm->vmgi->tt_srpt->title[title-1];
627
parts = ptitle->nr_of_ptts;
628
ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt;
630
tmp = calloc(1, sizeof(uint64_t)*parts);
635
for(i=0; i<parts; i++) {
636
uint32_t cellnr, endcellnr;
637
if (ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte >= ifo->vts_pgcit->last_byte) {
638
printerr("PGC start out of bounds");
641
pgc = ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc;
643
printerr("PGC missing.");
646
if(ptt[i].pgn > pgc->nr_of_programs) {
647
printerr("WRONG part number.");
651
if (pgc->nr_of_cells == 0) {
652
printerr("Number of cells cannot be 0");
655
if ((cellnr = pgc->program_map[ptt[i].pgn-1]) == 0) {
656
printerr("Cell new row cannot be 0");
660
if(ptt[i].pgn < pgc->nr_of_programs)
661
endcellnr = pgc->program_map[ptt[i].pgn];
666
cell = &pgc->cell_playback[cellnr-1];
667
if(!(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK &&
668
cell->block_mode != BLOCK_MODE_FIRST_CELL
671
tmp[i] = length + dvdnav_convert_time(&cell->playback_time);
675
} while(cellnr < endcellnr);
684
pthread_mutex_unlock(&this->vm_lock);