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
, 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
;
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
;
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
, &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
) {
188 uint32_t first_cell_nr
, last_cell_nr
, cell_nr
;
190 cell_playback_t
*cell
;
192 dvdnav_status_t result
;
194 if(this->position_current
.still
!= 0) {
195 printerr("Cannot seek in a still frame.");
196 return DVDNAV_STATUS_ERR
;
199 result
= dvdnav_get_position(this, &target
, &length
);
201 return DVDNAV_STATUS_ERR
;
204 pthread_mutex_lock(&this->vm_lock
);
205 state
= &(this->vm
->state
);
207 printerr("No current PGC.");
208 pthread_mutex_unlock(&this->vm_lock
);
209 return DVDNAV_STATUS_ERR
;
212 fprintf(MSG_OUT
, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset
, target
, length
);
213 fprintf(MSG_OUT
, "libdvdnav: Before cellN=%u blockN=%u\n", state
->cellN
, state
->blockN
);
218 if(offset
>= length
) {
219 printerr("Request to seek behind end.");
220 pthread_mutex_unlock(&this->vm_lock
);
221 return DVDNAV_STATUS_ERR
;
226 if(target
+ offset
>= length
) {
227 printerr("Request to seek behind end.");
228 pthread_mutex_unlock(&this->vm_lock
);
229 return DVDNAV_STATUS_ERR
;
234 if(length
< offset
) {
235 printerr("Request to seek before start.");
236 pthread_mutex_unlock(&this->vm_lock
);
237 return DVDNAV_STATUS_ERR
;
239 target
= length
- offset
;
243 printerr("Illegal seek mode.");
244 pthread_mutex_unlock(&this->vm_lock
);
245 return DVDNAV_STATUS_ERR
;
248 this->cur_cell_time
= 0;
249 if (this->pgc_based
) {
251 last_cell_nr
= state
->pgc
->nr_of_cells
;
253 /* Find start cell of program. */
254 first_cell_nr
= state
->pgc
->program_map
[state
->pgN
-1];
255 /* Find end cell of program */
256 if(state
->pgN
< state
->pgc
->nr_of_programs
)
257 last_cell_nr
= state
->pgc
->program_map
[state
->pgN
] - 1;
259 last_cell_nr
= state
->pgc
->nr_of_cells
;
263 for(cell_nr
= first_cell_nr
; (cell_nr
<= last_cell_nr
) && !found
; cell_nr
++) {
264 cell
= &(state
->pgc
->cell_playback
[cell_nr
-1]);
265 if(cell
->block_type
== BLOCK_TYPE_ANGLE_BLOCK
&& cell
->block_mode
!= BLOCK_MODE_FIRST_CELL
)
267 length
= cell
->last_sector
- cell
->first_sector
+ 1;
268 if (target
>= length
) {
271 /* convert the target sector from Cell-relative to absolute physical sector */
272 target
+= cell
->first_sector
;
281 fprintf(MSG_OUT
, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
282 cell_nr
, first_cell_nr
, last_cell_nr
);
284 if (dvdnav_scan_admap(this, state
->domain
, target
, &vobu
) == DVDNAV_STATUS_OK
) {
285 int32_t start
= state
->pgc
->cell_playback
[cell_nr
-1].first_sector
;
287 if (vm_jump_cell_block(this->vm
, cell_nr
, vobu
- start
)) {
289 fprintf(MSG_OUT
, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
290 state
->cellN
, state
->blockN
, target
, vobu
, start
);
292 this->vm
->hop_channel
+= HOP_SEEK
;
293 pthread_mutex_unlock(&this->vm_lock
);
294 return DVDNAV_STATUS_OK
;
299 fprintf(MSG_OUT
, "libdvdnav: Error when seeking\n");
300 fprintf(MSG_OUT
, "libdvdnav: FIXME: Implement seeking to location %u\n", target
);
301 printerr("Error when seeking.");
302 pthread_mutex_unlock(&this->vm_lock
);
303 return DVDNAV_STATUS_ERR
;
306 dvdnav_status_t
dvdnav_part_search(dvdnav_t
*this, int32_t part
) {
307 int32_t title
, old_part
;
309 if (dvdnav_current_title_info(this, &title
, &old_part
) == DVDNAV_STATUS_OK
)
310 return dvdnav_part_play(this, title
, part
);
311 return DVDNAV_STATUS_ERR
;
314 dvdnav_status_t
dvdnav_prev_pg_search(dvdnav_t
*this) {
315 pthread_mutex_lock(&this->vm_lock
);
316 if(!this->vm
->state
.pgc
) {
317 printerr("No current PGC.");
318 pthread_mutex_unlock(&this->vm_lock
);
319 return DVDNAV_STATUS_ERR
;
323 fprintf(MSG_OUT
, "libdvdnav: previous chapter\n");
325 if (!vm_jump_prev_pg(this->vm
)) {
326 fprintf(MSG_OUT
, "libdvdnav: previous chapter failed.\n");
327 printerr("Skip to previous chapter failed.");
328 pthread_mutex_unlock(&this->vm_lock
);
329 return DVDNAV_STATUS_ERR
;
331 this->cur_cell_time
= 0;
332 this->position_current
.still
= 0;
333 this->vm
->hop_channel
++;
335 fprintf(MSG_OUT
, "libdvdnav: previous chapter done\n");
337 pthread_mutex_unlock(&this->vm_lock
);
339 return DVDNAV_STATUS_OK
;
342 dvdnav_status_t
dvdnav_top_pg_search(dvdnav_t
*this) {
343 pthread_mutex_lock(&this->vm_lock
);
344 if(!this->vm
->state
.pgc
) {
345 printerr("No current PGC.");
346 pthread_mutex_unlock(&this->vm_lock
);
347 return DVDNAV_STATUS_ERR
;
351 fprintf(MSG_OUT
, "libdvdnav: top chapter\n");
353 if (!vm_jump_top_pg(this->vm
)) {
354 fprintf(MSG_OUT
, "libdvdnav: top chapter failed.\n");
355 printerr("Skip to top chapter failed.");
356 pthread_mutex_unlock(&this->vm_lock
);
357 return DVDNAV_STATUS_ERR
;
359 this->cur_cell_time
= 0;
360 this->position_current
.still
= 0;
361 this->vm
->hop_channel
++;
363 fprintf(MSG_OUT
, "libdvdnav: top chapter done\n");
365 pthread_mutex_unlock(&this->vm_lock
);
367 return DVDNAV_STATUS_OK
;
370 dvdnav_status_t
dvdnav_next_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: next chapter\n");
383 /* make a copy of current VM and try to navigate the copy to the next PG */
384 try_vm
= vm_new_copy(this->vm
);
385 if (!vm_jump_next_pg(try_vm
) || try_vm
->stopped
) {
386 vm_free_copy(try_vm
);
387 /* next_pg failed, try to jump at least to the next cell */
388 try_vm
= vm_new_copy(this->vm
);
389 vm_get_next_cell(try_vm
);
390 if (try_vm
->stopped
) {
391 vm_free_copy(try_vm
);
392 fprintf(MSG_OUT
, "libdvdnav: next chapter failed.\n");
393 printerr("Skip to next chapter failed.");
394 pthread_mutex_unlock(&this->vm_lock
);
395 return DVDNAV_STATUS_ERR
;
398 this->cur_cell_time
= 0;
399 /* merge changes on success */
400 vm_merge(this->vm
, try_vm
);
401 vm_free_copy(try_vm
);
402 this->position_current
.still
= 0;
403 this->vm
->hop_channel
++;
405 fprintf(MSG_OUT
, "libdvdnav: next chapter done\n");
407 pthread_mutex_unlock(&this->vm_lock
);
409 return DVDNAV_STATUS_OK
;
412 dvdnav_status_t
dvdnav_menu_call(dvdnav_t
*this, DVDMenuID_t menu
) {
415 pthread_mutex_lock(&this->vm_lock
);
416 if(!this->vm
->state
.pgc
) {
417 printerr("No current PGC.");
418 pthread_mutex_unlock(&this->vm_lock
);
419 return DVDNAV_STATUS_ERR
;
422 this->cur_cell_time
= 0;
423 /* make a copy of current VM and try to navigate the copy to the menu */
424 try_vm
= vm_new_copy(this->vm
);
425 if ( (menu
== DVD_MENU_Escape
) && (this->vm
->state
.domain
!= VTS_DOMAIN
)) {
427 if (vm_jump_resume(try_vm
) && !try_vm
->stopped
) {
428 /* merge changes on success */
429 vm_merge(this->vm
, try_vm
);
430 vm_free_copy(try_vm
);
431 this->position_current
.still
= 0;
432 this->vm
->hop_channel
++;
433 pthread_mutex_unlock(&this->vm_lock
);
434 return DVDNAV_STATUS_OK
;
437 if (menu
== DVD_MENU_Escape
) menu
= DVD_MENU_Root
;
439 if (vm_jump_menu(try_vm
, menu
) && !try_vm
->stopped
) {
440 /* merge changes on success */
441 vm_merge(this->vm
, try_vm
);
442 vm_free_copy(try_vm
);
443 this->position_current
.still
= 0;
444 this->vm
->hop_channel
++;
445 pthread_mutex_unlock(&this->vm_lock
);
446 return DVDNAV_STATUS_OK
;
448 vm_free_copy(try_vm
);
449 printerr("No such menu or menu not reachable.");
450 pthread_mutex_unlock(&this->vm_lock
);
451 return DVDNAV_STATUS_ERR
;
455 dvdnav_status_t
dvdnav_get_position(dvdnav_t
*this, uint32_t *pos
,
458 int32_t cell_nr
, first_cell_nr
, last_cell_nr
;
459 cell_playback_t
*cell
;
463 printerr("Virtual DVD machine not started.");
464 return DVDNAV_STATUS_ERR
;
467 pthread_mutex_lock(&this->vm_lock
);
468 state
= &(this->vm
->state
);
469 if(!state
->pgc
|| this->vm
->stopped
) {
470 printerr("No current PGC.");
471 pthread_mutex_unlock(&this->vm_lock
);
472 return DVDNAV_STATUS_ERR
;
474 if (this->position_current
.hop_channel
!= this->vm
->hop_channel
||
475 this->position_current
.domain
!= state
->domain
||
476 this->position_current
.vts
!= state
->vtsN
||
477 this->position_current
.cell_restart
!= state
->cell_restart
) {
478 printerr("New position not yet determined.");
479 pthread_mutex_unlock(&this->vm_lock
);
480 return DVDNAV_STATUS_ERR
;
483 /* Get current sector */
484 cur_sector
= this->vobu
.vobu_start
+ this->vobu
.blockN
;
486 if (this->pgc_based
) {
488 last_cell_nr
= state
->pgc
->nr_of_cells
;
490 /* Find start cell of program. */
491 first_cell_nr
= state
->pgc
->program_map
[state
->pgN
-1];
492 /* Find end cell of program */
493 if(state
->pgN
< state
->pgc
->nr_of_programs
)
494 last_cell_nr
= state
->pgc
->program_map
[state
->pgN
] - 1;
496 last_cell_nr
= state
->pgc
->nr_of_cells
;
501 for (cell_nr
= first_cell_nr
; cell_nr
<= last_cell_nr
; cell_nr
++) {
502 cell
= &(state
->pgc
->cell_playback
[cell_nr
-1]);
503 if (cell_nr
== state
->cellN
) {
504 /* the current sector is in this cell,
505 * pos is length of PG up to here + sector's offset in this cell */
506 *pos
= *len
+ cur_sector
- cell
->first_sector
;
508 *len
+= cell
->last_sector
- cell
->first_sector
+ 1;
511 assert((signed)*pos
!= -1);
513 pthread_mutex_unlock(&this->vm_lock
);
515 return DVDNAV_STATUS_OK
;
518 dvdnav_status_t
dvdnav_get_position_in_title(dvdnav_t
*this,
522 uint32_t first_cell_nr
;
523 uint32_t last_cell_nr
;
524 cell_playback_t
*first_cell
;
525 cell_playback_t
*last_cell
;
528 state
= &(this->vm
->state
);
530 printerr("No current PGC.");
531 return DVDNAV_STATUS_ERR
;
534 /* Get current sector */
535 cur_sector
= this->vobu
.vobu_start
+ this->vobu
.blockN
;
537 /* Now find first and last cells in title. */
538 first_cell_nr
= state
->pgc
->program_map
[0];
539 first_cell
= &(state
->pgc
->cell_playback
[first_cell_nr
-1]);
540 last_cell_nr
= state
->pgc
->nr_of_cells
;
541 last_cell
= &(state
->pgc
->cell_playback
[last_cell_nr
-1]);
543 *pos
= cur_sector
- first_cell
->first_sector
;
544 *len
= last_cell
->last_sector
- first_cell
->first_sector
;
546 return DVDNAV_STATUS_OK
;
549 uint32_t dvdnav_describe_title_chapters(dvdnav_t
*this, int32_t title
, uint64_t **times
, uint64_t *duration
) {
552 title_info_t
*ptitle
= NULL
;
553 ptt_info_t
*ptt
= NULL
;
554 ifo_handle_t
*ifo
= NULL
;
556 cell_playback_t
*cell
;
557 uint64_t length
, *tmp
=NULL
;
561 pthread_mutex_lock(&this->vm_lock
);
562 if(!this->vm
->vmgi
) {
563 printerr("Bad VM state or missing VTSI.");
567 /* don't report an error but be nice */
571 ifo
= vm_get_title_ifo(this->vm
, title
);
572 if(!ifo
|| !ifo
->vts_pgcit
) {
573 printerr("Couldn't open IFO for chosen title, exit.");
578 ptitle
= &this->vm
->vmgi
->tt_srpt
->title
[title
-1];
579 parts
= ptitle
->nr_of_ptts
;
580 ptt
= ifo
->vts_ptt_srpt
->title
[ptitle
->vts_ttn
-1].ptt
;
582 tmp
= calloc(1, sizeof(uint64_t)*parts
);
587 for(i
=0; i
<parts
; i
++) {
588 uint32_t cellnr
, endcellnr
;
589 pgc
= ifo
->vts_pgcit
->pgci_srp
[ptt
[i
].pgcn
-1].pgc
;
590 if(ptt
[i
].pgn
> pgc
->nr_of_programs
) {
591 printerr("WRONG part number.");
595 cellnr
= pgc
->program_map
[ptt
[i
].pgn
-1];
596 if(ptt
[i
].pgn
< pgc
->nr_of_programs
)
597 endcellnr
= pgc
->program_map
[ptt
[i
].pgn
];
602 cell
= &pgc
->cell_playback
[cellnr
-1];
603 if(!(cell
->block_type
== BLOCK_TYPE_ANGLE_BLOCK
&&
604 cell
->block_mode
!= BLOCK_MODE_FIRST_CELL
607 tmp
[i
] = length
+ dvdnav_convert_time(&cell
->playback_time
);
611 } while(cellnr
< endcellnr
);
620 pthread_mutex_unlock(&this->vm_lock
);