1 /*
2 * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
3 *
4 * This file is part of libdvdnav, a DVD navigation library.
5 *
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.
10 *
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.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <assert.h>
26 #include "dvdnav_internal.h"
27 #include "dvdnav.h"
28
29 /*
30 #define LOG_DEBUG
31 */
32
33 /* Searching API calls */
34
35 /* Scan the ADMAP for a particular block number. */
36 /* Return placed in vobu. */
37 /* Returns error status */
38 /* FIXME: Maybe need to handle seeking outside current cell. */
dvdnav_scan_admap(dvdnav_t * this,int32_t domain,uint32_t seekto_block,uint32_t * vobu)39 static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, uint32_t *vobu) {
40 vobu_admap_t *admap = NULL;
41
42 #ifdef LOG_DEBUG
43 fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block);
44 #endif
45 *vobu = -1;
46
47 /* Search through the VOBU_ADMAP for the nearest VOBU
48 * to the target block */
49 switch(domain) {
50 case FP_DOMAIN:
51 case VMGM_DOMAIN:
52 admap = this->vm->vmgi->menu_vobu_admap;
53 break;
54 case VTSM_DOMAIN:
55 admap = this->vm->vtsi->menu_vobu_admap;
56 break;
57 case VTS_DOMAIN:
58 admap = this->vm->vtsi->vts_vobu_admap;
59 break;
60 default:
61 fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n");
62 }
63 if(admap) {
64 uint32_t address = 0;
65 uint32_t vobu_start, next_vobu;
66 int32_t found = 0;
67
68 /* Search through ADMAP for best sector */
69 vobu_start = SRI_END_OF_CELL;
70 /* FIXME: Implement a faster search algorithm */
71 while((!found) && ((address<<2) < admap->last_byte)) {
72 next_vobu = admap->vobu_start_sectors[address];
73
74 /* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */
75
76 if(vobu_start <= seekto_block &&
77 next_vobu > seekto_block) {
78 found = 1;
79 } else {
80 vobu_start = next_vobu;
81 }
82 address ++;
83 }
84 if(found) {
85 *vobu = vobu_start;
86 return DVDNAV_STATUS_OK;
87 } else {
88 fprintf(MSG_OUT, "libdvdnav: Could not locate block\n");
89 return DVDNAV_STATUS_ERR;
90 }
91 }
92 fprintf(MSG_OUT, "libdvdnav: admap not located\n");
93 return DVDNAV_STATUS_ERR;
94 }
95
dvdnav_time_search(dvdnav_t * this,uint64_t time)96 dvdnav_status_t dvdnav_time_search(dvdnav_t *this,
97 uint64_t time) {
98
99 uint64_t target = time;
100 uint64_t length = 0;
101 uint32_t first_cell_nr, last_cell_nr, cell_nr;
102 int32_t found;
103 cell_playback_t *cell;
104 dvd_state_t *state;
105
106 if(this->position_current.still != 0) {
107 printerr("Cannot seek in a still frame.");
108 return DVDNAV_STATUS_ERR;
109 }
110
111 pthread_mutex_lock(&this->vm_lock);
112 state = &(this->vm->state);
113 if(!state->pgc) {
114 printerr("No current PGC.");
115 pthread_mutex_unlock(&this->vm_lock);
116 return DVDNAV_STATUS_ERR;
117 }
118
119
120 if (this->pgc_based) {
121 first_cell_nr = 1;
122 last_cell_nr = state->pgc->nr_of_cells;
123 } else {
124 /* Find start cell of program. */
125 first_cell_nr = state->pgc->program_map[state->pgN-1];
126 /* Find end cell of program */
127 if(state->pgN < state->pgc->nr_of_programs)
128 last_cell_nr = state->pgc->program_map[state->pgN] - 1;
129 else
130 last_cell_nr = state->pgc->nr_of_cells;
131 }
132
133 found = 0;
134 for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
135 cell = &(state->pgc->cell_playback[cell_nr-1]);
136 length = dvdnav_convert_time(&cell->playback_time);
137 if (target >= length) {
138 target -= length;
139 } else {
140 /* FIXME: there must be a better way than interpolation */
141 target = target * (cell->last_sector - cell->first_sector + 1) / length;
142 target += cell->first_sector;
143
144 found = 1;
145 break;
146 }
147 }
148
149 if(found) {
150 int32_t vobu;
151 #ifdef LOG_DEBUG
152 fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
153 cell_nr, first_cell_nr, last_cell_nr);
154 #endif
155 if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) {
156 int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
157
158 if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
159 #ifdef LOG_DEBUG
160 fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
161 state->cellN, state->blockN, target, vobu, start);
162 #endif
163 this->vm->hop_channel += HOP_SEEK;
164 pthread_mutex_unlock(&this->vm_lock);
165 return DVDNAV_STATUS_OK;
166 }
167 }
168 }
169
170 fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
171 printerr("Error when seeking.");
172 pthread_mutex_unlock(&this->vm_lock);
173 return DVDNAV_STATUS_ERR;
174 }
175
dvdnav_sector_search(dvdnav_t * this,uint64_t offset,int32_t origin)176 dvdnav_status_t dvdnav_sector_search(dvdnav_t *this,
177 uint64_t offset, int32_t origin) {
178 uint32_t target = 0;
179 uint32_t length = 0;
180 uint32_t first_cell_nr, last_cell_nr, cell_nr;
181 int32_t found;
182 cell_playback_t *cell;
183 dvd_state_t *state;
184 dvdnav_status_t result;
185
186 if(this->position_current.still != 0) {
187 printerr("Cannot seek in a still frame.");
188 return DVDNAV_STATUS_ERR;
189 }
190
191 result = dvdnav_get_position(this, &target, &length);
192 if(!result) {
193 return DVDNAV_STATUS_ERR;
194 }
195
196 pthread_mutex_lock(&this->vm_lock);
197 state = &(this->vm->state);
198 if(!state->pgc) {
199 printerr("No current PGC.");
200 pthread_mutex_unlock(&this->vm_lock);
201 return DVDNAV_STATUS_ERR;
202 }
203 #ifdef LOG_DEBUG
204 fprintf(MSG_OUT, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length);
205 fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
206 #endif
207
208 switch(origin) {
209 case SEEK_SET:
210 if(offset > length) {
211 printerr("Request to seek behind end.");
212 pthread_mutex_unlock(&this->vm_lock);
213 return DVDNAV_STATUS_ERR;
214 }
215 target = offset;
216 break;
217 case SEEK_CUR:
218 if(target + offset > length) {
219 printerr("Request to seek behind end.");
220 pthread_mutex_unlock(&this->vm_lock);
221 return DVDNAV_STATUS_ERR;
222 }
223 target += offset;
224 break;
225 case SEEK_END:
226 if (length < offset) {
227 printerr("Request to seek before start.");
228 pthread_mutex_unlock(&this->vm_lock);
229 return DVDNAV_STATUS_ERR;
230 }
231 target = length - offset;
232 break;
233 default:
234 /* Error occured */
235 printerr("Illegal seek mode.");
236 pthread_mutex_unlock(&this->vm_lock);
237 return DVDNAV_STATUS_ERR;
238 }
239
240 if (this->pgc_based) {
241 first_cell_nr = 1;
242 last_cell_nr = state->pgc->nr_of_cells;
243 } else {
244 /* Find start cell of program. */
245 first_cell_nr = state->pgc->program_map[state->pgN-1];
246 /* Find end cell of program */
247 if(state->pgN < state->pgc->nr_of_programs)
248 last_cell_nr = state->pgc->program_map[state->pgN] - 1;
249 else
250 last_cell_nr = state->pgc->nr_of_cells;
251 }
252
253 found = 0;
254 for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
255 cell = &(state->pgc->cell_playback[cell_nr-1]);
256 length = cell->last_sector - cell->first_sector + 1;
257 if (target >= length) {
258 target -= length;
259 } else {
260 /* convert the target sector from Cell-relative to absolute physical sector */
261 target += cell->first_sector;
262 found = 1;
263 break;
264 }
265 }
266
267 if(found) {
268 int32_t vobu;
269 #ifdef LOG_DEBUG
270 fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
271 cell_nr, first_cell_nr, last_cell_nr);
272 #endif
273 if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) {
274 int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
275
276 if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
277 #ifdef LOG_DEBUG
278 fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
279 state->cellN, state->blockN, target, vobu, start);
280 #endif
281 this->vm->hop_channel += HOP_SEEK;
282 pthread_mutex_unlock(&this->vm_lock);
283 return DVDNAV_STATUS_OK;
284 }
285 }
286 }
287
288 fprintf(MSG_OUT, "libdvdnav: Error when seeking\n");
289 fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", target);
290 printerr("Error when seeking.");
291 pthread_mutex_unlock(&this->vm_lock);
292 return DVDNAV_STATUS_ERR;
293 }
294
dvdnav_part_search(dvdnav_t * this,int32_t part)295 dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) {
296 int32_t title, old_part;
297
298 if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK)
299 return dvdnav_part_play(this, title, part);
300 return DVDNAV_STATUS_ERR;
301 }
302
dvdnav_prev_pg_search(dvdnav_t * this)303 dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) {
304
305 if(!this) {
306 printerr("Passed a NULL pointer.");
307 return DVDNAV_STATUS_ERR;
308 }
309
310 pthread_mutex_lock(&this->vm_lock);
311 if(!this->vm->state.pgc) {
312 printerr("No current PGC.");
313 pthread_mutex_unlock(&this->vm_lock);
314 return DVDNAV_STATUS_ERR;
315 }
316
317 #ifdef LOG_DEBUG
318 fprintf(MSG_OUT, "libdvdnav: previous chapter\n");
319 #endif
320 if (!vm_jump_prev_pg(this->vm)) {
321 fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n");
322 printerr("Skip to previous chapter failed.");
323 pthread_mutex_unlock(&this->vm_lock);
324 return DVDNAV_STATUS_ERR;
325 }
326 this->position_current.still = 0;
327 this->vm->hop_channel++;
328 #ifdef LOG_DEBUG
329 fprintf(MSG_OUT, "libdvdnav: previous chapter done\n");
330 #endif
331 pthread_mutex_unlock(&this->vm_lock);
332
333 return DVDNAV_STATUS_OK;
334 }
335
dvdnav_top_pg_search(dvdnav_t * this)336 dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) {
337
338 if(!this) {
339 printerr("Passed a NULL pointer.");
340 return DVDNAV_STATUS_ERR;
341 }
342
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;
348 }
349
350 #ifdef LOG_DEBUG
351 fprintf(MSG_OUT, "libdvdnav: top chapter\n");
352 #endif
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;
358 }
359 this->position_current.still = 0;
360 this->vm->hop_channel++;
361 #ifdef LOG_DEBUG
362 fprintf(MSG_OUT, "libdvdnav: top chapter done\n");
363 #endif
364 pthread_mutex_unlock(&this->vm_lock);
365
366 return DVDNAV_STATUS_OK;
367 }
368
dvdnav_next_pg_search(dvdnav_t * this)369 dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) {
370 vm_t *try_vm;
371
372 if(!this) {
373 printerr("Passed a NULL pointer.");
374 return DVDNAV_STATUS_ERR;
375 }
376
377 pthread_mutex_lock(&this->vm_lock);
378 if(!this->vm->state.pgc) {
379 printerr("No current PGC.");
380 pthread_mutex_unlock(&this->vm_lock);
381 return DVDNAV_STATUS_ERR;
382 }
383
384 #ifdef LOG_DEBUG
385 fprintf(MSG_OUT, "libdvdnav: next chapter\n");
386 #endif
387 /* make a copy of current VM and try to navigate the copy to the next PG */
388 try_vm = vm_new_copy(this->vm);
389 if (!vm_jump_next_pg(try_vm) || try_vm->stopped) {
390 vm_free_copy(try_vm);
391 /* next_pg failed, try to jump at least to the next cell */
392 try_vm = vm_new_copy(this->vm);
393 vm_get_next_cell(try_vm);
394 if (try_vm->stopped) {
395 vm_free_copy(try_vm);
396 fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n");
397 printerr("Skip to next chapter failed.");
398 pthread_mutex_unlock(&this->vm_lock);
399 return DVDNAV_STATUS_ERR;
400 }
401 }
402 /* merge changes on success */
403 vm_merge(this->vm, try_vm);
404 vm_free_copy(try_vm);
405 this->position_current.still = 0;
406 this->vm->hop_channel++;
407 #ifdef LOG_DEBUG
408 fprintf(MSG_OUT, "libdvdnav: next chapter done\n");
409 #endif
410 pthread_mutex_unlock(&this->vm_lock);
411
412 return DVDNAV_STATUS_OK;
413 }
414
dvdnav_menu_call(dvdnav_t * this,DVDMenuID_t menu)415 dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) {
416 vm_t *try_vm;
417
418 if(!this) {
419 printerr("Passed a NULL pointer.");
420 return DVDNAV_STATUS_ERR;
421 }
422
423 pthread_mutex_lock(&this->vm_lock);
424 if(!this->vm->state.pgc) {
425 printerr("No current PGC.");
426 pthread_mutex_unlock(&this->vm_lock);
427 return DVDNAV_STATUS_ERR;
428 }
429
430 /* make a copy of current VM and try to navigate the copy to the menu */
431 try_vm = vm_new_copy(this->vm);
432 if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) {
433 /* Try resume */
434 if (vm_jump_resume(try_vm) && !try_vm->stopped) {
435 /* merge changes on success */
436 vm_merge(this->vm, try_vm);
437 vm_free_copy(try_vm);
438 this->position_current.still = 0;
439 this->vm->hop_channel++;
440 pthread_mutex_unlock(&this->vm_lock);
441 return DVDNAV_STATUS_OK;
442 }
443 }
444 if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root;
445
446 if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) {
447 /* merge changes on success */
448 vm_merge(this->vm, try_vm);
449 vm_free_copy(try_vm);
450 this->position_current.still = 0;
451 this->vm->hop_channel++;
452 pthread_mutex_unlock(&this->vm_lock);
453 return DVDNAV_STATUS_OK;
454 } else {
455 vm_free_copy(try_vm);
456 printerr("No such menu or menu not reachable.");
457 pthread_mutex_unlock(&this->vm_lock);
458 return DVDNAV_STATUS_ERR;
459 }
460 }
461
dvdnav_get_position(dvdnav_t * this,uint32_t * pos,uint32_t * len)462 dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos,
463 uint32_t *len) {
464 uint32_t cur_sector;
465 int32_t cell_nr, first_cell_nr, last_cell_nr;
466 cell_playback_t *cell;
467 dvd_state_t *state;
468
469 if(!this || !pos || !len) {
470 printerr("Passed a NULL pointer.");
471 return DVDNAV_STATUS_ERR;
472 }
473 if(!this->started) {
474 printerr("Virtual DVD machine not started.");
475 return DVDNAV_STATUS_ERR;
476 }
477
478 pthread_mutex_lock(&this->vm_lock);
479 state = &(this->vm->state);
480 if(!state->pgc || this->vm->stopped) {
481 printerr("No current PGC.");
482 pthread_mutex_unlock(&this->vm_lock);
483 return DVDNAV_STATUS_ERR;
484 }
485 if (this->position_current.hop_channel != this->vm->hop_channel ||
486 this->position_current.domain != state->domain ||
487 this->position_current.vts != state->vtsN ||
488 this->position_current.cell_restart != state->cell_restart) {
489 printerr("New position not yet determined.");
490 pthread_mutex_unlock(&this->vm_lock);
491 return DVDNAV_STATUS_ERR;
492 }
493
494 /* Get current sector */
495 cur_sector = this->vobu.vobu_start + this->vobu.blockN;
496
497 if (this->pgc_based) {
498 first_cell_nr = 1;
499 last_cell_nr = state->pgc->nr_of_cells;
500 } else {
501 /* Find start cell of program. */
502 first_cell_nr = state->pgc->program_map[state->pgN-1];
503 /* Find end cell of program */
504 if(state->pgN < state->pgc->nr_of_programs)
505 last_cell_nr = state->pgc->program_map[state->pgN] - 1;
506 else
507 last_cell_nr = state->pgc->nr_of_cells;
508 }
509
510 *pos = -1;
511 *len = 0;
512 for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) {
513 cell = &(state->pgc->cell_playback[cell_nr-1]);
514 if (cell_nr == state->cellN) {
515 /* the current sector is in this cell,
516 * pos is length of PG up to here + sector's offset in this cell */
517 *pos = *len + cur_sector - cell->first_sector;
518 }
519 *len += cell->last_sector - cell->first_sector + 1;
520 }
521
522 assert((signed)*pos != -1);
523
524 pthread_mutex_unlock(&this->vm_lock);
525
526 return DVDNAV_STATUS_OK;
527 }
528
dvdnav_get_position_in_title(dvdnav_t * this,uint32_t * pos,uint32_t * len)529 dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this,
530 uint32_t *pos,
531 uint32_t *len) {
532 uint32_t cur_sector;
533 uint32_t first_cell_nr;
534 uint32_t last_cell_nr;
535 cell_playback_t *first_cell;
536 cell_playback_t *last_cell;
537 dvd_state_t *state;
538
539 if(!this || !pos || !len) {
540 printerr("Passed a NULL pointer.");
541 return DVDNAV_STATUS_ERR;
542 }
543
544 state = &(this->vm->state);
545 if(!state->pgc) {
546 printerr("No current PGC.");
547 return DVDNAV_STATUS_ERR;
548 }
549
550 /* Get current sector */
551 cur_sector = this->vobu.vobu_start + this->vobu.blockN;
552
553 /* Now find first and last cells in title. */
554 first_cell_nr = state->pgc->program_map[0];
555 first_cell = &(state->pgc->cell_playback[first_cell_nr-1]);
556 last_cell_nr = state->pgc->nr_of_cells;
557 last_cell = &(state->pgc->cell_playback[last_cell_nr-1]);
558
559 *pos = cur_sector - first_cell->first_sector;
560 *len = last_cell->last_sector - first_cell->first_sector;
561
562 return DVDNAV_STATUS_OK;
563 }
564