summaryrefslogtreecommitdiffstats
path: root/stream/stream_dvdnav.c
blob: 039dd1940d9f02538c4360e699bc1d8b2ee397ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "mp_msg.h"
#include "osdep/timer.h"
#include "input/input.h"
#include "stream.h"
#include "libmpdemux/demuxer.h"
#include "stream_dvdnav.h"
#include "libvo/video_out.h"
#include "libavutil/common.h"
#include "spudec.h"
#include "m_option.h"
#include "m_struct.h"
#include "help_mp.h"

extern char *dvd_device;
extern int dvd_chapter;
extern int dvd_last_chapter;
extern int dvd_angle;
extern char *audio_lang, *dvdsub_lang;

static struct stream_priv_s {
  int track;
  char* device;
} stream_priv_dflts = {
  0,
  NULL
};

#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
/// URL definition
static m_option_t stream_opts_fields[] = {
  {"filename", 	ST_OFF(device), CONF_TYPE_STRING, 0, 0, 0, NULL },
  {"hostname", 	ST_OFF(track), CONF_TYPE_INT, 0, 0, 0, NULL},
  { NULL, NULL, 0, 0, 0, 0,  NULL }
};
static struct m_struct_st stream_opts = {
  "dvd",
  sizeof(struct stream_priv_s),
  &stream_priv_dflts,
  stream_opts_fields
};

int dvd_nav_still=0;            /* are we on a still picture? */
dvdnav_highlight_event_t dvd_nav_hl;
int dvd_nav_hl_on = 0;

static int seek(stream_t *s, off_t newpos);

static dvdnav_priv_t * new_dvdnav_stream(char * filename) {
  char * title_str;
  dvdnav_priv_t *priv;

  if (!filename)
    return NULL;

  if (!(priv=calloc(1,sizeof(dvdnav_priv_t))))
    return NULL;

  if (!(priv->filename=strdup(filename))) {
    free(priv);
    return NULL;
  }

  if(dvdnav_open(&(priv->dvdnav),priv->filename)!=DVDNAV_STATUS_OK)
  {
    free(priv->filename);
    free(priv);
    return NULL;
  }

  if (!priv->dvdnav) {
    free(priv);
    return NULL;
  }

  if(1)	//from vlc: if not used dvdnav from cvs will fail
  {
    int len, event;
    char buf[2048];

    dvdnav_get_next_block(priv->dvdnav,buf,&event,&len);
    dvdnav_sector_search(priv->dvdnav, 0, SEEK_SET);
  }

  /* turn off dvdnav caching */
  dvdnav_set_readahead_flag(priv->dvdnav, 0);
  if(dvdnav_set_PGC_positioning_flag(priv->dvdnav, 1) != DVDNAV_STATUS_OK)
    mp_msg(MSGT_OPEN,MSGL_ERR,"stream_dvdnav, failed to set PGC positioning\n");
#if 1
  /* report the title?! */
  if (dvdnav_get_title_string(priv->dvdnav,&title_str)==DVDNAV_STATUS_OK) {
    mp_msg(MSGT_IDENTIFY, MSGL_INFO,"Title: '%s'\n",title_str);
  }
#endif

  //dvdnav_event_clear(priv);

  return priv;
}

static void dvdnav_get_highlight (dvdnav_priv_t *priv, dvdnav_highlight_event_t *hlev, int display_mode) {
  pci_t *pnavpci = NULL;
  int btnum = -1;

  if (!priv || !priv->dvdnav || !hlev)
    return;

  pnavpci = dvdnav_get_current_nav_pci (priv->dvdnav);
  if (!pnavpci)
    return;

  dvdnav_get_current_highlight (priv->dvdnav, &(hlev->buttonN));
  hlev->display = display_mode; /* show */

  if (hlev->buttonN > 0 && pnavpci->hli.hl_gi.btn_ns > 0 && hlev->display) {
    for (btnum = 0; btnum < pnavpci->hli.hl_gi.btn_ns; btnum++) {
      btni_t *btni = &(pnavpci->hli.btnit[btnum]);

      if (hlev->buttonN == btnum + 1) {
        hlev->sx = FFMIN (btni->x_start, btni->x_end);
        hlev->ex = FFMAX (btni->x_start, btni->x_end);
        hlev->sy = FFMIN (btni->y_start, btni->y_end);
        hlev->ey = FFMAX (btni->y_start, btni->y_end);

        hlev->palette = (btni->btn_coln == 0) ? 0 :
          pnavpci->hli.btn_colit.btn_coli[btni->btn_coln - 1][0];
        dvd_nav_hl_on = 1;
        break;
      }
    }
  } else { /* hide button or no button */
    hlev->sx = hlev->ex = 0;
    hlev->sy = hlev->ey = 0;
    hlev->palette = hlev->buttonN = 0;
    dvd_nav_hl_on = 0;
  }
}

static int dvdnav_stream_read(dvdnav_priv_t * priv, unsigned char *buf, int *len) {
  int event = DVDNAV_NOP;

  if (!len) return -1;
  *len=-1;
  if (!priv) return -1;
  if (!buf) return -1;

  if (dvd_nav_still) {
    mp_msg(MSGT_OPEN,MSGL_V, "%s: got a stream_read while I should be asleep!\n",__FUNCTION__);
    *len=0;
    return -1;
  }

  if (dvdnav_get_next_block(priv->dvdnav,buf,&event,len)!=DVDNAV_STATUS_OK) {
    mp_msg(MSGT_OPEN,MSGL_V, "Error getting next block from DVD %d (%s)\n",event, dvdnav_err_to_string(priv->dvdnav) );
    *len=-1;
  }
  else if (event!=DVDNAV_BLOCK_OK) {
    // need to handle certain events internally (like skipping stills)
    switch (event) {
      case DVDNAV_STILL_FRAME: {
        dvdnav_still_event_t *still_event = (dvdnav_still_event_t*)(buf);
        //if (priv->started) dvd_nav_still=1;
        //else
          dvdnav_still_skip(priv->dvdnav); // don't let dvdnav stall on this image

        break;
      }
      case DVDNAV_HIGHLIGHT: {
        dvdnav_get_highlight (priv, &dvd_nav_hl, 1);
        break;
      }
      case DVDNAV_CELL_CHANGE: {
        dvdnav_cell_change_event_t *ev =  (dvdnav_cell_change_event_t*)buf;
        if(ev->pgc_length)
          priv->duration = ev->pgc_length/90;
        break;
      }
      case DVDNAV_WAIT:
        dvdnav_wait_skip(priv->dvdnav);
        break;
    }

    *len=0;
  }
  return event;
}

static void update_title_len(stream_t *stream) {
  dvdnav_priv_t *priv = stream->priv;
  dvdnav_status_t status;
  uint32_t pos = 0, len = 0;

  status = dvdnav_get_position(priv->dvdnav, &pos, &len);
  if(status == DVDNAV_STATUS_OK && len) {
    stream->end_pos = (off_t) len * 2048;
    stream->seek = seek;
  } else {
    stream->seek = NULL;
    stream->end_pos = 0;
  }
}


static int seek(stream_t *s, off_t newpos) {
  uint32_t pos = 0, len = 0, sector = 0;
  dvdnav_priv_t *priv = s->priv;

  if(s->end_pos && newpos > s->end_pos) 
     newpos = s->end_pos;
  sector = newpos / 2048ULL;
  if(dvdnav_sector_search(priv->dvdnav, (uint64_t) sector, SEEK_SET) != DVDNAV_STATUS_OK)
    goto fail;

  s->pos = newpos;

  return 1;

fail:
  mp_msg(MSGT_STREAM,MSGL_INFO,"dvdnav_stream, seeking to %"PRIu64" failed: %s\n", newpos, dvdnav_err_to_string(priv->dvdnav));

  return 1;
}

static void stream_dvdnav_close(stream_t *s) {
  dvdnav_priv_t *priv = s->priv;
  dvdnav_close(priv->dvdnav);
  priv->dvdnav = NULL;
  free(priv);
}


static int fill_buffer(stream_t *s, char *but, int len)
{
    int event;

    dvdnav_priv_t* priv=s->priv;
    len=0;
    if(!s->end_pos)
      update_title_len(s);
    while(!len) /* grab all event until DVDNAV_BLOCK_OK (len=2048), DVDNAV_STOP or DVDNAV_STILL_FRAME */
    {
      event=dvdnav_stream_read(priv, s->buffer, &len);
      if(event==-1 || len==-1)
      {
        mp_msg(MSGT_CPLAYER,MSGL_ERR, "DVDNAV stream read error!\n");
        return 0;
      }
      switch (event) {
        case DVDNAV_STOP: return len;
        case DVDNAV_BLOCK_OK: return len;
        case DVDNAV_VTS_CHANGE: {
          int tit = 0, part = 0;
          s->end_pos = 0;
          update_title_len(s);
          if(dvdnav_current_title_info(priv->dvdnav, &tit, &part) == DVDNAV_STATUS_OK) {
            mp_msg(MSGT_CPLAYER,MSGL_V, "\r\nDVDNAV, NEW TITLE %d\r\n", tit);
            dvdnav_get_highlight (priv, &dvd_nav_hl, 0);
            if(priv->title > 0 && tit != priv->title)
              return 0;
          }
          break;
        }
        case DVDNAV_CELL_CHANGE: {
          if(priv->title > 0 && dvd_last_chapter > 0) {
            int tit=0, part=0;
            if(dvdnav_current_title_info(priv->dvdnav, &tit, &part) == DVDNAV_STATUS_OK && part > dvd_last_chapter)
              return 0;
            }
        }
        break;
      }
  }
  mp_msg(MSGT_STREAM,MSGL_DBG2,"DVDNAV fill_buffer len: %d\n",len);
  return len;
}

static int control(stream_t *stream, int cmd, void* arg) {
  dvdnav_priv_t* priv=stream->priv;
  int tit, part;

  switch(cmd) 
  {
    case STREAM_CTRL_SEEK_TO_CHAPTER:
    {
      int chap = *((unsigned int *)arg)+1;

      if(chap < 1 || dvdnav_current_title_info(priv->dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
        break;
      if(dvdnav_part_play(priv->dvdnav, tit, chap) != DVDNAV_STATUS_OK)
        break;
      return 1;
    }
    case STREAM_CTRL_GET_NUM_CHAPTERS:
    {
      if(dvdnav_current_title_info(priv->dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
        break;
      if(dvdnav_get_number_of_parts(priv->dvdnav, tit, &part) != DVDNAV_STATUS_OK)
        break;
      if(!part)
        break;
      *((unsigned int *)arg) = part;
      return 1;
    }
    case STREAM_CTRL_GET_CURRENT_CHAPTER:
    {
      if(dvdnav_current_title_info(priv->dvdnav, &tit, &part) != DVDNAV_STATUS_OK)
        break;
      *((unsigned int *)arg) = part - 1;
      return 1;
    }
    case STREAM_CTRL_GET_TIME_LENGTH:
    {
      if(priv->duration)
      {
        *((double *)arg) = (double)priv->duration / 1000.0;
        return 1;
      }
      break;
    }
  }

  return STREAM_UNSUPORTED;
}

static int open_s(stream_t *stream,int mode, void* opts, int* file_format) {
  struct stream_priv_s* p = (struct stream_priv_s*)opts;
  char *filename;
  int event,len,tmplen=0;
  uint32_t pos, l2;
  dvdnav_priv_t *priv;
  dvdnav_status_t status;

  if(p->device) filename = p->device; 
  else if(dvd_device) filename= dvd_device; 
  else filename = DEFAULT_DVD_DEVICE;
  if(!(priv=new_dvdnav_stream(filename))) {
    mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,filename);
    return STREAM_UNSUPORTED;
  }

  if(p->track > 0) {
    if(dvd_chapter > 0 && dvd_last_chapter > 0 && dvd_chapter > dvd_last_chapter) {
      mp_msg(MSGT_OPEN,MSGL_FATAL,"dvdnav_stream, invalid chapter range: %d > %d\n", dvd_chapter, dvd_last_chapter);
      return STREAM_UNSUPORTED;
    }
    priv->title = p->track;
    if(dvdnav_title_play(priv->dvdnav, p->track) != DVDNAV_STATUS_OK) {
      mp_msg(MSGT_OPEN,MSGL_FATAL,"dvdnav_stream, couldn't select title %d, error '%s'\n", p->track, dvdnav_err_to_string(priv->dvdnav));
      return STREAM_UNSUPORTED;
    }
    if(dvd_chapter > 0)
      dvdnav_part_play(priv->dvdnav, p->track, dvd_chapter);
  } else if(p->track == -1)
    dvdnav_menu_call(priv->dvdnav, DVD_MENU_Root);
  else {
    mp_msg(MSGT_OPEN,MSGL_INFO,"dvdnav_stream, you didn't specify a track number (as in dvdnav://1), playing whole disc\n");
    dvdnav_menu_call(priv->dvdnav, DVD_MENU_Title);
  }
  if(dvd_angle > 1)
    dvdnav_angle_change(priv->dvdnav, dvd_angle);

  stream->sector_size = 2048;
  stream->flags = STREAM_READ | STREAM_SEEK;
  stream->fill_buffer = fill_buffer;
  stream->seek = seek;
  stream->control = control;
  stream->close = stream_dvdnav_close;
  stream->type = STREAMTYPE_DVDNAV;
  stream->priv=(void*)priv;
  *file_format = DEMUXER_TYPE_MPEG_PS;

  update_title_len(stream);
  if(!stream->pos)
    mp_msg(MSGT_OPEN,MSGL_ERR, "INIT ERROR: %d, couldn't get init pos %s\r\n", status, dvdnav_err_to_string(priv->dvdnav));

  mp_msg(MSGT_OPEN,MSGL_INFO, "Remember to disable MPlayer's cache when playing dvdnav:// streams (adding -nocache to your command line)\r\n");

  return STREAM_OK;
}


int mp_dvdnav_handle_input(stream_t *stream, int cmd, int *button) {
  dvdnav_priv_t * priv=(dvdnav_priv_t*)stream->priv;
  dvdnav_t *nav = priv->dvdnav;
  dvdnav_status_t status=DVDNAV_STATUS_ERR;
  pci_t *pci = dvdnav_get_current_nav_pci(nav);
  int reset = 0;

  if(cmd != MP_CMD_DVDNAV_SELECT && !pci)
    return 0;

  switch(cmd) {
    case MP_CMD_DVDNAV_UP:
      status = dvdnav_upper_button_select(nav, pci);
      break;
    case MP_CMD_DVDNAV_DOWN:
      status = dvdnav_lower_button_select(nav, pci);
      break;
    case MP_CMD_DVDNAV_LEFT:
      status = dvdnav_left_button_select(nav, pci);
      break;
    case MP_CMD_DVDNAV_RIGHT:
      status = dvdnav_right_button_select(nav, pci);
      break;
    case MP_CMD_DVDNAV_MENU:
      status = dvdnav_menu_call(nav,DVD_MENU_Root);
      reset = 1;
      break;
    case MP_CMD_DVDNAV_PREVMENU: {
      int title=0, part=0;

      dvdnav_current_title_info(nav, &title, &part);
      if(title) {
        if(dvdnav_menu_call(nav, DVD_MENU_Part) == DVDNAV_STATUS_OK
           || dvdnav_menu_call(nav, DVD_MENU_Title) == DVDNAV_STATUS_OK) {
          reset = 1;
          break;
        }
      }
      if(dvdnav_menu_call(nav, DVD_MENU_Root) == DVDNAV_STATUS_OK)
        reset = 1;
      }
      break;
    case MP_CMD_DVDNAV_SELECT:
      status = dvdnav_button_activate(nav, pci);
      if(status == DVDNAV_STATUS_OK) reset = 1;
      break;
    case MP_CMD_DVDNAV_MOUSECLICK:
      /*
        this is a workaround: in theory the simple dvdnav_lower_button_select()+dvdnav_button_activate()
        should be enough (and generally it is), but there are cases when the calls to dvdnav_lower_button_select()
        and friends fail! Hence we have to call dvdnav_mouse_activate(priv->mousex, priv->mousey) with
        the coodinates saved by mp_dvdnav_update_mouse_pos().
        This last call always works well
      */
      status = dvdnav_mouse_activate(nav, pci, priv->mousex, priv->mousey);
      break;
    default:
      mp_msg(MSGT_CPLAYER, MSGL_V, "Unknown DVDNAV cmd %d\n", cmd);
      break;
  }

  if(status == DVDNAV_STATUS_OK)
    dvdnav_get_current_highlight(nav, button);

  return reset;
}

void mp_dvdnav_update_mouse_pos(stream_t *stream, int32_t x, int32_t y, int* button) {
  dvdnav_priv_t * priv=(dvdnav_priv_t*)stream->priv;
  dvdnav_t *nav = priv->dvdnav;
  dvdnav_status_t status;
  pci_t *pci = dvdnav_get_current_nav_pci(nav);

  if(!pci) return;

  status = dvdnav_mouse_select(nav, pci, x, y);
  if(status == DVDNAV_STATUS_OK) dvdnav_get_current_highlight(nav, button);
  else *button = -1;
  priv->mousex = x;
  priv->mousey = y;
}

int dvdnav_sid_from_lang(stream_t *stream, unsigned char *language) {
  dvdnav_priv_t * priv=(dvdnav_priv_t*)stream->priv;
  uint8_t format, lg, k;
  uint16_t lang, lcode = (language[0] << 8) | (language[1]);

  for(k=0; k<32; k++) {
    lg = dvdnav_get_spu_logical_stream(priv->dvdnav, k);
    if(lg == 0xff) continue;
    lang = dvdnav_spu_stream_to_lang(priv->dvdnav, lg);
    if(lang != 0xFFFF && lang == lcode) {
      return k;
    }
  }
  return -1;
}

int dvdnav_number_of_subs(stream_t *stream) {
  dvdnav_priv_t * priv=(dvdnav_priv_t*)stream->priv;
  uint8_t lg, k, n=0;

  for(k=0; k<32; k++) {
    lg = dvdnav_get_spu_logical_stream(priv->dvdnav, k);
    if(lg == 0xff) continue;
    n++;
  }
  return n;
}


stream_info_t stream_info_dvdnav = {
  "DVDNAV stream",
  "null",
  "",
  "",
  open_s,
  { "dvdnav", NULL },
  &stream_opts,
  1 // Urls are an option string
};