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