/*
* This file is part of libbluray
* Copyright (C) 2009-2010 Obliter0n
* Copyright (C) 2009-2010 John Stebbins
* Copyright (C) 2010 hpi1
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include "bluray.h"
#include "register.h"
#include "util/macro.h"
#include "util/logging.h"
#include "util/strutl.h"
#include "bdnav/navigation.h"
#include "bdnav/index_parse.h"
#include "hdmv/hdmv_vm.h"
#include "file/file.h"
#ifdef DLOPEN_CRYPTO_LIBS
#include "file/dl.h"
#endif
#ifdef USING_BDJAVA
#include "bdj/bdj.h"
#endif
#ifndef DLOPEN_CRYPTO_LIBS
#include
#include
#endif
#include
#include
#include
typedef int (*fptr_int)();
typedef int32_t (*fptr_int32)();
typedef void* (*fptr_p_void)();
#define MAX_EVENTS 31 /* 2^n - 1 */
typedef struct bd_event_queue_s {
unsigned in; /* next free slot */
unsigned out; /* next event */
BD_EVENT ev[MAX_EVENTS];
} BD_EVENT_QUEUE;
typedef enum {
title_undef = 0,
title_hdmv,
title_bdj,
} BD_TITLE_TYPE;
typedef struct {
/* current clip */
NAV_CLIP *clip;
BD_FILE_H *fp;
uint64_t clip_size;
uint64_t clip_block_pos;
uint64_t clip_pos;
/* current aligned unit */
uint16_t int_buf_off;
} BD_STREAM;
struct bluray {
/* current disc */
char *device_path;
INDX_ROOT *index;
NAV_TITLE_LIST *title_list;
/* current playlist */
NAV_TITLE *title;
uint32_t title_idx;
uint64_t s_pos;
/* streams */
BD_STREAM st0; /* main path */
/* buffer for bd_read(): current aligned unit of main stream (st0) */
uint8_t int_buf[6144];
/* seamless angle change request */
int seamless_angle_change;
uint32_t angle_change_pkt;
uint32_t angle_change_time;
unsigned request_angle;
/* chapter tracking */
uint32_t next_chapter_start;
/* aacs */
#ifdef DLOPEN_CRYPTO_LIBS
void *h_libaacs; // library handle
#endif
void *aacs;
fptr_int libaacs_decrypt_unit;
/* BD+ */
#ifdef DLOPEN_CRYPTO_LIBS
void *h_libbdplus; // library handle
#endif
void *bdplus;
fptr_int32 bdplus_seek;
fptr_int32 bdplus_fixup;
/* player state */
BD_REGISTERS *regs; // player registers
BD_EVENT_QUEUE *event_queue; // navigation mode event queue
BD_TITLE_TYPE title_type; // type of current title (in navigation mode)
HDMV_VM *hdmv_vm;
uint8_t hdmv_suspended;
void *bdjava;
};
#ifdef DLOPEN_CRYPTO_LIBS
# define DL_CALL(lib,func,param,...) \
do { \
fptr_p_void fptr = dl_dlsym(lib, #func); \
if (fptr) { \
fptr(param, ##__VA_ARGS__); \
} \
} while (0)
#else
# define DL_CALL(lib,func,param,...) \
func (param, ##__VA_ARGS__)
#endif
/*
* Navigation mode event queue
*/
static void _init_event_queue(BLURAY *bd)
{
if (!bd->event_queue) {
bd->event_queue = calloc(1, sizeof(struct bd_event_queue_s));
} else {
memset(bd->event_queue, 0, sizeof(struct bd_event_queue_s));
}
}
static int _get_event(BLURAY *bd, BD_EVENT *ev)
{
struct bd_event_queue_s *eq = bd->event_queue;
if (eq) {
if (eq->in != eq->out) {
*ev = eq->ev[eq->out];
eq->out = (eq->out + 1) & MAX_EVENTS;
return 1;
}
}
ev->event = BD_EVENT_NONE;
return 0;
}
static int _queue_event(BLURAY *bd, BD_EVENT ev)
{
struct bd_event_queue_s *eq = bd->event_queue;
if (eq) {
unsigned new_in = (eq->in + 1) & MAX_EVENTS;
if (new_in != eq->out) {
eq->ev[eq->in] = ev;
eq->in = new_in;
return 0;
}
DEBUG(DBG_BLURAY|DBG_CRIT, "_queue_event(%d, %d): queue overflow !\n", ev.event, ev.param);
}
return -1;
}
/*
* clip access (BD_STREAM)
*/
static void _close_m2ts(BD_STREAM *st)
{
if (st->fp != NULL) {
file_close(st->fp);
st->fp = NULL;
}
}
static int _open_m2ts(BLURAY *bd, BD_STREAM *st)
{
char *f_name;
_close_m2ts(st);
f_name = str_printf("%s" DIR_SEP "BDMV" DIR_SEP "STREAM" DIR_SEP "%s",
bd->device_path, st->clip->name);
st->clip_pos = (uint64_t)st->clip->start_pkt * 192;
st->clip_block_pos = (st->clip_pos / 6144) * 6144;
if ((st->fp = file_open(f_name, "rb"))) {
file_seek(st->fp, 0, SEEK_END);
if ((st->clip_size = file_tell(st->fp))) {
file_seek(st->fp, st->clip_block_pos, SEEK_SET);
st->int_buf_off = 6144;
X_FREE(f_name);
if (bd->bdplus) {
DL_CALL(bd->h_libbdplus, bdplus_set_title,
bd->bdplus, st->clip->clip_id);
}
if (bd->aacs) {
uint32_t title = bd_psr_read(bd->regs, PSR_TITLE_NUMBER);
DL_CALL(bd->h_libaacs, aacs_select_title,
bd->aacs, title);
}
if (st == &bd->st0)
bd_psr_write(bd->regs, PSR_PLAYITEM, st->clip->ref);
return 1;
}
DEBUG(DBG_BLURAY, "Clip %s empty! (%p)\n", f_name, bd);
}
DEBUG(DBG_BLURAY | DBG_CRIT, "Unable to open clip %s! (%p)\n",
f_name, bd);
X_FREE(f_name);
return 0;
}
static int _read_block(BLURAY *bd, BD_STREAM *st, uint8_t *buf)
{
const int len = 6144;
if (st->fp) {
DEBUG(DBG_BLURAY, "Reading unit [%d bytes] at %"PRIu64"... (%p)\n",
len, st->clip_block_pos, bd);
if (len + st->clip_block_pos <= st->clip_size) {
int read_len;
if ((read_len = file_read(st->fp, buf, len))) {
if (read_len != len)
DEBUG(DBG_BLURAY | DBG_CRIT, "Read %d bytes at %"PRIu64" ; requested %d ! (%p)\n", read_len, st->clip_block_pos, len, bd);
if (bd->libaacs_decrypt_unit) {
if (!bd->libaacs_decrypt_unit(bd->aacs, buf)) {
DEBUG(DBG_BLURAY, "Unable decrypt unit! (%p)\n", bd);
return 0;
} // decrypt
} // aacs
st->clip_block_pos += len;
// bdplus fixup, if required.
if (bd->bdplus_fixup && bd->bdplus) {
int32_t numFixes;
numFixes = bd->bdplus_fixup(bd->bdplus, len, buf);
#if 1
if (numFixes) {
DEBUG(DBG_BDPLUS,
"BDPLUS did %u fixups\n", numFixes);
}
#endif
}
DEBUG(DBG_BLURAY, "Read unit OK! (%p)\n", bd);
return 1;
}
DEBUG(DBG_BLURAY | DBG_CRIT, "Read %d bytes at %"PRIu64" failed ! (%p)\n", len, st->clip_block_pos, bd);
return 0;
}
DEBUG(DBG_BLURAY | DBG_CRIT, "Read past EOF ! (%p)\n", bd);
return 0;
}
DEBUG(DBG_BLURAY, "No valid title selected! (%p)\n", bd);
return 0;
}
static int64_t _seek_stream(BLURAY *bd, BD_STREAM *st,
NAV_CLIP *clip, uint32_t clip_pkt)
{
if (!clip)
return -1;
if (!st->fp || clip->ref != st->clip->ref) {
// The position is in a new clip
st->clip = clip;
if (!_open_m2ts(bd, st)) {
return -1;
}
}
st->clip_pos = (uint64_t)clip_pkt * 192;
st->clip_block_pos = (st->clip_pos / 6144) * 6144;
file_seek(st->fp, st->clip_block_pos, SEEK_SET);
st->int_buf_off = 6144;
return st->clip_pos;
}
/*
* open / close
*/
static void _libaacs_close(BLURAY *bd)
{
if (bd->aacs) {
DL_CALL(bd->h_libaacs, aacs_close, bd->aacs);
bd->aacs = NULL;
}
#ifdef DLOPEN_CRYPTO_LIBS
if (bd->h_libaacs) {
dl_dlclose(bd->h_libaacs);
bd->h_libaacs = NULL;
}
#endif
bd->libaacs_decrypt_unit = NULL;
}
static int _libaacs_open(BLURAY *bd, const char *keyfile_path)
{
_libaacs_close(bd);
#ifdef DLOPEN_CRYPTO_LIBS
if ((bd->h_libaacs = dl_dlopen("libaacs", "0"))) {
DEBUG(DBG_BLURAY, "Downloaded libaacs (%p)\n", bd->h_libaacs);
fptr_p_void fptr = dl_dlsym(bd->h_libaacs, "aacs_open");
bd->libaacs_decrypt_unit = dl_dlsym(bd->h_libaacs, "aacs_decrypt_unit");
if (fptr && bd->libaacs_decrypt_unit) {
if ((bd->aacs = fptr(bd->device_path, keyfile_path))) {
DEBUG(DBG_BLURAY, "Opened libaacs (%p)\n", bd->aacs);
return 1;
}
DEBUG(DBG_BLURAY, "aacs_open() failed!\n");
} else {
DEBUG(DBG_BLURAY, "libaacs dlsym failed!\n");
}
dl_dlclose(bd->h_libaacs);
bd->h_libaacs = NULL;
} else {
DEBUG(DBG_BLURAY, "libaacs not found!\n");
}
#else
DEBUG(DBG_BLURAY, "Using libaacs via normal linking\n");
bd->libaacs_decrypt_unit = &aacs_decrypt_unit;
if ((bd->aacs = aacs_open(bd->device_path, keyfile_path))) {
DEBUG(DBG_BLURAY, "Opened libaacs (%p)\n", bd->aacs);
return 1;
}
DEBUG(DBG_BLURAY, "aacs_open() failed!\n");
#endif
bd->libaacs_decrypt_unit = NULL;
return 0;
}
static void _libbdplus_close(BLURAY *bd)
{
if (bd->bdplus) {
DL_CALL(bd->h_libbdplus, bdplus_free, bd->bdplus);
bd->bdplus = NULL;
}
#ifdef DLOPEN_CRYPTO_LIBS
if (bd->h_libbdplus) {
dl_dlclose(bd->h_libbdplus);
bd->h_libbdplus = NULL;
}
#endif
bd->bdplus_seek = NULL;
bd->bdplus_fixup = NULL;
}
static void _libbdplus_open(BLURAY *bd, const char *keyfile_path)
{
_libbdplus_close(bd);
// Take a quick stab to see if we want/need bdplus
// we should fix this, and add various string functions.
uint8_t vid[16] = {
0xC5,0x43,0xEF,0x2A,0x15,0x0E,0x50,0xC4,0xE2,0xCA,
0x71,0x65,0xB1,0x7C,0xA7,0xCB}; // FIXME
BD_FILE_H *fd;
char *tmp = NULL;
tmp = str_printf("%s/BDSVM/00000.svm", bd->device_path);
if ((fd = file_open(tmp, "rb"))) {
file_close(fd);
DEBUG(DBG_BDPLUS, "attempting to load libbdplus\n");
#ifdef DLOPEN_CRYPTO_LIBS
if ((bd->h_libbdplus = dl_dlopen("libbdplus", "0"))) {
DEBUG(DBG_BLURAY, "Downloaded libbdplus (%p)\n", bd->h_libbdplus);
fptr_p_void bdplus_init = dl_dlsym(bd->h_libbdplus, "bdplus_init");
//bdplus_t *bdplus_init(path,configfile_path,*vid );
if (bdplus_init)
bd->bdplus = bdplus_init(bd->device_path, keyfile_path, vid);
if (bd->bdplus) {
// Since we will call these functions a lot, we assign them
// now.
bd->bdplus_seek = dl_dlsym(bd->h_libbdplus, "bdplus_seek");
bd->bdplus_fixup = dl_dlsym(bd->h_libbdplus, "bdplus_fixup");
} else {
dl_dlclose(bd->h_libbdplus);
bd->h_libbdplus = NULL;
}
}
#else
DEBUG(DBG_BLURAY,"Using libbdplus via normal linking\n");
bd->bdplus = bdplus_init(bd->device_path, keyfile_path, vid);
// Since we will call these functions a lot, we assign them
// now.
bd->bdplus_seek = &bdplus_seek;
bd->bdplus_fixup = &bdplus_fixup;
#endif
} // file_open
X_FREE(tmp);
}
static int _index_open(BLURAY *bd)
{
char *file;
file = str_printf("%s/BDMV/index.bdmv", bd->device_path);
bd->index = indx_parse(file);
X_FREE(file);
return !!bd->index;
}
BLURAY *bd_open(const char* device_path, const char* keyfile_path)
{
BLURAY *bd = calloc(1, sizeof(BLURAY));
if (device_path) {
bd->device_path = (char*)malloc(strlen(device_path) + 1);
strcpy(bd->device_path, device_path);
_libaacs_open(bd, keyfile_path);
_libbdplus_open(bd, keyfile_path);
_index_open(bd);
bd->regs = bd_registers_init();
DEBUG(DBG_BLURAY, "BLURAY initialized! (%p)\n", bd);
} else {
X_FREE(bd);
DEBUG(DBG_BLURAY | DBG_CRIT, "No device path provided!\n");
}
return bd;
}
void bd_close(BLURAY *bd)
{
bd_stop_bdj(bd);
_libaacs_close(bd);
_libbdplus_close(bd);
_close_m2ts(&bd->st0);
if (bd->title_list != NULL) {
nav_free_title_list(bd->title_list);
}
if (bd->title != NULL) {
nav_title_close(bd->title);
}
hdmv_vm_free(bd->hdmv_vm);
indx_free(bd->index);
bd_registers_free(bd->regs);
X_FREE(bd->event_queue);
X_FREE(bd->device_path);
DEBUG(DBG_BLURAY, "BLURAY destroyed! (%p)\n", bd);
X_FREE(bd);
}
/*
* seeking and current position
*/
static int64_t _seek_internal(BLURAY *bd,
NAV_CLIP *clip, uint32_t title_pkt, uint32_t clip_pkt)
{
if (_seek_stream(bd, &bd->st0, clip, clip_pkt) >= 0) {
/* update title position */
bd->s_pos = (uint64_t)title_pkt * 192;
/* chapter tracking */
uint32_t current_chapter = bd_get_current_chapter(bd);
bd->next_chapter_start = bd_chapter_pos(bd, current_chapter + 1);
bd_psr_write(bd->regs, PSR_CHAPTER, current_chapter + 1);
DEBUG(DBG_BLURAY, "Seek to %"PRIu64" (%p)\n",
bd->s_pos, bd);
if (bd->bdplus_seek && bd->bdplus) {
bd->bdplus_seek(bd->bdplus, bd->st0.clip_block_pos);
}
}
return bd->s_pos;
}
/* _change_angle() should be used only before call to _seek_internal() ! */
static void _change_angle(BLURAY *bd)
{
if (bd->seamless_angle_change) {
bd->st0.clip = nav_set_angle(bd->title, bd->st0.clip, bd->request_angle);
bd->seamless_angle_change = 0;
bd_psr_write(bd->regs, PSR_ANGLE_NUMBER, bd->title->angle + 1);
/* force re-opening .m2ts file in _seek_internal() */
_close_m2ts(&bd->st0);
}
}
int64_t bd_seek_time(BLURAY *bd, uint64_t tick)
{
uint32_t clip_pkt, out_pkt;
NAV_CLIP *clip;
tick /= 2;
if (tick < bd->title->duration) {
_change_angle(bd);
// Find the closest access unit to the requested position
clip = nav_time_search(bd->title, tick, &clip_pkt, &out_pkt);
return _seek_internal(bd, clip, out_pkt, clip_pkt);
}
return bd->s_pos;
}
uint64_t bd_tell_time(BLURAY *bd)
{
uint32_t clip_pkt = 0, out_pkt = 0, out_time = 0;
if (bd && bd->title) {
nav_packet_search(bd->title, bd->s_pos / 192, &clip_pkt, &out_pkt, &out_time);
}
return ((uint64_t)out_time) * 2;
}
int64_t bd_seek_chapter(BLURAY *bd, unsigned chapter)
{
uint32_t clip_pkt, out_pkt;
NAV_CLIP *clip;
if (chapter < bd->title->chap_list.count) {
_change_angle(bd);
// Find the closest access unit to the requested position
clip = nav_chapter_search(bd->title, chapter, &clip_pkt, &out_pkt);
return _seek_internal(bd, clip, out_pkt, clip_pkt);
}
return bd->s_pos;
}
int64_t bd_chapter_pos(BLURAY *bd, unsigned chapter)
{
uint32_t clip_pkt, out_pkt;
if (chapter < bd->title->chap_list.count) {
// Find the closest access unit to the requested position
nav_chapter_search(bd->title, chapter, &clip_pkt, &out_pkt);
return (int64_t)out_pkt * 192;
}
return -1;
}
uint32_t bd_get_current_chapter(BLURAY *bd)
{
return nav_chapter_get_current(bd->st0.clip, bd->st0.clip_pos / 192);
}
int64_t bd_seek_mark(BLURAY *bd, unsigned mark)
{
uint32_t clip_pkt, out_pkt;
NAV_CLIP *clip;
if (mark < bd->title->mark_list.count) {
_change_angle(bd);
// Find the closest access unit to the requested position
clip = nav_mark_search(bd->title, mark, &clip_pkt, &out_pkt);
return _seek_internal(bd, clip, out_pkt, clip_pkt);
}
return bd->s_pos;
}
int64_t bd_seek(BLURAY *bd, uint64_t pos)
{
uint32_t pkt, clip_pkt, out_pkt, out_time;
NAV_CLIP *clip;
if (pos < (uint64_t)bd->title->packets * 192) {
pkt = pos / 192;
_change_angle(bd);
// Find the closest access unit to the requested position
clip = nav_packet_search(bd->title, pkt, &clip_pkt, &out_pkt, &out_time);
return _seek_internal(bd, clip, out_pkt, clip_pkt);
}
return bd->s_pos;
}
uint64_t bd_get_title_size(BLURAY *bd)
{
if (bd && bd->title) {
return (uint64_t)bd->title->packets * 192;
}
return UINT64_C(0);
}
uint64_t bd_tell(BLURAY *bd)
{
return bd ? bd->s_pos : INT64_C(0);
}
/*
* read
*/
static int64_t _clip_seek_time(BLURAY *bd, uint64_t tick)
{
uint32_t clip_pkt, out_pkt;
if (tick < bd->st0.clip->out_time) {
// Find the closest access unit to the requested position
nav_clip_time_search(bd->st0.clip, tick, &clip_pkt, &out_pkt);
return _seek_internal(bd, bd->st0.clip, out_pkt, clip_pkt);
}
return bd->s_pos;
}
int bd_read(BLURAY *bd, unsigned char *buf, int len)
{
BD_STREAM *st = &bd->st0;
int out_len;
if (st->fp) {
out_len = 0;
DEBUG(DBG_BLURAY, "Reading [%d bytes] at %"PRIu64"... (%p)\n", len, bd->s_pos, bd);
while (len > 0) {
uint32_t clip_pkt;
unsigned int size = len;
// Do we need to read more data?
clip_pkt = st->clip_pos / 192;
if (bd->seamless_angle_change) {
if (clip_pkt >= bd->angle_change_pkt) {
if (clip_pkt >= st->clip->end_pkt) {
st->clip = nav_next_clip(bd->title, st->clip);
if (!_open_m2ts(bd, st)) {
return -1;
}
bd->s_pos = st->clip->pos;
} else {
st->clip = nav_set_angle(bd->title, st->clip, bd->request_angle);
bd_psr_write(bd->regs, PSR_ANGLE_NUMBER, bd->title->angle + 1);
_clip_seek_time(bd, bd->angle_change_time);
}
bd->seamless_angle_change = 0;
} else {
uint64_t angle_pos;
angle_pos = bd->angle_change_pkt * 192;
if (angle_pos - st->clip_pos < size)
{
size = angle_pos - st->clip_pos;
}
}
}
if (st->int_buf_off == 6144 || clip_pkt >= st->clip->end_pkt) {
// Do we need to get the next clip?
if (st->clip == NULL) {
// We previously reached the last clip. Nothing
// else to read.
return 0;
}
if (clip_pkt >= st->clip->end_pkt) {
st->clip = nav_next_clip(bd->title, st->clip);
if (st->clip == NULL) {
DEBUG(DBG_BLURAY, "End of title (%p)\n", bd);
return out_len;
}
if (!_open_m2ts(bd, st)) {
return -1;
}
}
if (_read_block(bd, st, bd->int_buf)) {
st->int_buf_off = st->clip_pos % 6144;
} else {
return out_len;
}
}
if (size > (unsigned int)6144 - st->int_buf_off) {
size = 6144 - st->int_buf_off;
}
memcpy(buf, bd->int_buf + st->int_buf_off, size);
buf += size;
len -= size;
out_len += size;
st->clip_pos += size;
st->int_buf_off += size;
bd->s_pos += size;
}
/* chapter tracking */
if (bd->s_pos > bd->next_chapter_start) {
uint32_t current_chapter = bd_get_current_chapter(bd);
bd->next_chapter_start = bd_chapter_pos(bd, current_chapter + 1);
bd_psr_write(bd->regs, PSR_CHAPTER, current_chapter + 1);
}
DEBUG(DBG_BLURAY, "%d bytes read OK! (%p)\n", out_len, bd);
return out_len;
}
DEBUG(DBG_BLURAY, "No valid title selected! (%p)\n", bd);
return -1;
}
/*
* select title / angle
*/
static int _open_playlist(BLURAY *bd, const char *f_name)
{
if (bd->title) {
nav_title_close(bd->title);
}
bd->title = nav_title_open(bd->device_path, f_name);
if (bd->title == NULL) {
DEBUG(DBG_BLURAY | DBG_CRIT, "Unable to open title %s! (%p)\n",
f_name, bd);
return 0;
}
bd->seamless_angle_change = 0;
bd->s_pos = 0;
bd->next_chapter_start = bd_chapter_pos(bd, 1);
bd_psr_write(bd->regs, PSR_PLAYLIST, atoi(bd->title->name));
bd_psr_write(bd->regs, PSR_ANGLE_NUMBER, bd->title->angle + 1);
bd_psr_write(bd->regs, PSR_CHAPTER, 1);
// Get the initial clip of the playlist
bd->st0.clip = nav_next_clip(bd->title, NULL);
if (_open_m2ts(bd, &bd->st0)) {
DEBUG(DBG_BLURAY, "Title %s selected! (%p)\n", f_name, bd);
return 1;
}
return 0;
}
int bd_select_playlist(BLURAY *bd, uint32_t playlist)
{
char *f_name = str_printf("%05d.mpls", playlist);
int result;
if (bd->title_list) {
/* update current title */
unsigned i;
for (i = 0; i < bd->title_list->count; i++) {
if (playlist == bd->title_list->title_info[i].mpls_id) {
bd->title_idx = i;
break;
}
}
}
result = _open_playlist(bd, f_name);
X_FREE(f_name);
return result;
}
// Select a title for playback
// The title index is an index into the list
// established by bd_get_titles()
int bd_select_title(BLURAY *bd, uint32_t title_idx)
{
const char *f_name;
// Open the playlist
if (bd->title_list == NULL) {
DEBUG(DBG_BLURAY, "Title list not yet read! (%p)\n", bd);
return 0;
}
if (bd->title_list->count <= title_idx) {
DEBUG(DBG_BLURAY, "Invalid title index %d! (%p)\n", title_idx, bd);
return 0;
}
bd->title_idx = title_idx;
f_name = bd->title_list->title_info[title_idx].name;
return _open_playlist(bd, f_name);
}
uint32_t bd_get_current_title(BLURAY *bd)
{
return bd->title_idx;
}
int bd_select_angle(BLURAY *bd, unsigned angle)
{
unsigned orig_angle;
if (bd->title == NULL) {
DEBUG(DBG_BLURAY, "Title not yet selected! (%p)\n", bd);
return 0;
}
orig_angle = bd->title->angle;
bd->st0.clip = nav_set_angle(bd->title, bd->st0.clip, angle);
if (orig_angle == bd->title->angle) {
return 1;
}
bd_psr_write(bd->regs, PSR_ANGLE_NUMBER, bd->title->angle + 1);
if (!_open_m2ts(bd, &bd->st0)) {
DEBUG(DBG_BLURAY|DBG_CRIT, "Error selecting angle %d ! (%p)\n", angle, bd);
return 0;
}
return 1;
}
unsigned bd_get_current_angle(BLURAY *bd)
{
return bd->title->angle;
}
void bd_seamless_angle_change(BLURAY *bd, unsigned angle)
{
uint32_t clip_pkt;
clip_pkt = (bd->st0.clip_pos + 191) / 192;
bd->angle_change_pkt = nav_angle_change_search(bd->st0.clip, clip_pkt,
&bd->angle_change_time);
bd->request_angle = angle;
bd->seamless_angle_change = 1;
}
/*
* title lists
*/
uint32_t bd_get_titles(BLURAY *bd, uint8_t flags)
{
if (!bd) {
DEBUG(DBG_BLURAY | DBG_CRIT, "bd_get_titles(NULL) failed (%p)\n", bd);
return 0;
}
if (bd->title_list != NULL) {
nav_free_title_list(bd->title_list);
}
bd->title_list = nav_get_title_list(bd->device_path, flags);
if (!bd->title_list) {
DEBUG(DBG_BLURAY | DBG_CRIT, "nav_get_title_list(%s) failed (%p)\n", bd->device_path, bd);
return 0;
}
return bd->title_list->count;
}
static void _copy_streams(BLURAY_STREAM_INFO *streams, MPLS_STREAM *si, int count)
{
int ii;
for (ii = 0; ii < count; ii++) {
streams[ii].coding_type = si[ii].coding_type;
streams[ii].format = si[ii].format;
streams[ii].rate = si[ii].rate;
streams[ii].char_code = si[ii].char_code;
memcpy(streams[ii].lang, si[ii].lang, 4);
streams[ii].pid = si[ii].pid;
}
}
static BLURAY_TITLE_INFO* _fill_title_info(NAV_TITLE* title, uint32_t title_idx, uint32_t playlist)
{
BLURAY_TITLE_INFO *title_info;
unsigned int ii;
title_info = calloc(1, sizeof(BLURAY_TITLE_INFO));
title_info->idx = title_idx;
title_info->playlist = playlist;
title_info->duration = (uint64_t)title->duration * 2;
title_info->angle_count = title->angle_count;
title_info->chapter_count = title->chap_list.count;
title_info->chapters = calloc(title_info->chapter_count, sizeof(BLURAY_TITLE_CHAPTER));
for (ii = 0; ii < title_info->chapter_count; ii++) {
title_info->chapters[ii].idx = ii;
title_info->chapters[ii].start = (uint64_t)title->chap_list.mark[ii].title_time * 2;
title_info->chapters[ii].duration = (uint64_t)title->chap_list.mark[ii].duration * 2;
title_info->chapters[ii].offset = (uint64_t)title->chap_list.mark[ii].title_pkt * 192;
}
title_info->clip_count = title->clip_list.count;
title_info->clips = calloc(title_info->clip_count, sizeof(BLURAY_CLIP_INFO));
for (ii = 0; ii < title_info->clip_count; ii++) {
MPLS_PI *pi = &title->pl->play_item[ii];
BLURAY_CLIP_INFO *ci = &title_info->clips[ii];
ci->video_stream_count = pi->stn.num_video;
ci->audio_stream_count = pi->stn.num_audio;
ci->pg_stream_count = pi->stn.num_pg + pi->stn.num_pip_pg;
ci->ig_stream_count = pi->stn.num_ig;
ci->sec_video_stream_count = pi->stn.num_secondary_video;
ci->sec_audio_stream_count = pi->stn.num_secondary_audio;
ci->video_streams = calloc(ci->video_stream_count, sizeof(BLURAY_STREAM_INFO));
ci->audio_streams = calloc(ci->audio_stream_count, sizeof(BLURAY_STREAM_INFO));
ci->pg_streams = calloc(ci->pg_stream_count, sizeof(BLURAY_STREAM_INFO));
ci->ig_streams = calloc(ci->ig_stream_count, sizeof(BLURAY_STREAM_INFO));
ci->sec_video_streams = calloc(ci->sec_video_stream_count, sizeof(BLURAY_STREAM_INFO));
ci->sec_audio_streams = calloc(ci->sec_audio_stream_count, sizeof(BLURAY_STREAM_INFO));
_copy_streams(ci->video_streams, pi->stn.video, ci->video_stream_count);
_copy_streams(ci->audio_streams, pi->stn.audio, ci->audio_stream_count);
_copy_streams(ci->pg_streams, pi->stn.pg, ci->pg_stream_count);
_copy_streams(ci->ig_streams, pi->stn.ig, ci->ig_stream_count);
_copy_streams(ci->sec_video_streams, pi->stn.secondary_video, ci->sec_video_stream_count);
_copy_streams(ci->sec_audio_streams, pi->stn.secondary_audio, ci->sec_audio_stream_count);
}
return title_info;
}
static BLURAY_TITLE_INFO *_get_title_info(BLURAY *bd, uint32_t title_idx, uint32_t playlist, const char *mpls_name)
{
NAV_TITLE *title;
BLURAY_TITLE_INFO *title_info;
title = nav_title_open(bd->device_path, mpls_name);
if (title == NULL) {
DEBUG(DBG_BLURAY | DBG_CRIT, "Unable to open title %s! (%p)\n",
mpls_name, bd);
return NULL;
}
title_info = _fill_title_info(title, title_idx, playlist);
nav_title_close(title);
return title_info;
}
BLURAY_TITLE_INFO* bd_get_title_info(BLURAY *bd, uint32_t title_idx)
{
if (bd->title_list == NULL) {
DEBUG(DBG_BLURAY, "Title list not yet read! (%p)\n", bd);
return NULL;
}
if (bd->title_list->count <= title_idx) {
DEBUG(DBG_BLURAY, "Invalid title index %d! (%p)\n", title_idx, bd);
return NULL;
}
return _get_title_info(bd,
title_idx, bd->title_list->title_info[title_idx].mpls_id,
bd->title_list->title_info[title_idx].name);
}
BLURAY_TITLE_INFO* bd_get_playlist_info(BLURAY *bd, uint32_t playlist)
{
char *f_name = str_printf("%05d.mpls", playlist);
BLURAY_TITLE_INFO *title_info;
title_info = _get_title_info(bd, 0, playlist, f_name);
X_FREE(f_name);
return title_info;
}
void bd_free_title_info(BLURAY_TITLE_INFO *title_info)
{
unsigned int ii;
X_FREE(title_info->chapters);
for (ii = 0; ii < title_info->clip_count; ii++) {
X_FREE(title_info->clips[ii].video_streams);
X_FREE(title_info->clips[ii].audio_streams);
X_FREE(title_info->clips[ii].pg_streams);
X_FREE(title_info->clips[ii].ig_streams);
X_FREE(title_info->clips[ii].sec_video_streams);
X_FREE(title_info->clips[ii].sec_audio_streams);
}
X_FREE(title_info->clips);
X_FREE(title_info);
}
/*
* player settings
*/
int bd_set_player_setting(BLURAY *bd, uint32_t idx, uint32_t value)
{
static const struct { uint32_t idx; uint32_t psr; } map[] = {
{ BLURAY_PLAYER_SETTING_PARENTAL, PSR_PARENTAL },
{ BLURAY_PLAYER_SETTING_AUDIO_CAP, PSR_AUDIO_CAP },
{ BLURAY_PLAYER_SETTING_AUDIO_LANG, PSR_AUDIO_LANG },
{ BLURAY_PLAYER_SETTING_PG_LANG, PSR_PG_AND_SUB_LANG },
{ BLURAY_PLAYER_SETTING_MENU_LANG, PSR_MENU_LANG },
{ BLURAY_PLAYER_SETTING_COUNTRY_CODE, PSR_COUNTRY },
{ BLURAY_PLAYER_SETTING_REGION_CODE, PSR_REGION },
{ BLURAY_PLAYER_SETTING_VIDEO_CAP, PSR_VIDEO_CAP },
{ BLURAY_PLAYER_SETTING_TEXT_CAP, PSR_TEXT_CAP },
{ BLURAY_PLAYER_SETTING_PLAYER_PROFILE, PSR_PROFILE_VERSION },
};
unsigned i;
if (idx == BLURAY_PLAYER_SETTING_PLAYER_PROFILE) {
value = ((value & 0xf) << 16) | 0x0200; /* version fixed to BD-RO Part 3, version 2.0 */
}
for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
if (idx == map[i].idx) {
return bd_psr_setting_write(bd->regs, idx, value);
}
}
return 0;
}
static uint32_t _string_to_uint(const char *s, int n)
{
uint32_t val = 0;
if (n > 4)
n = 4;
while (n--)
val = (val << 8) | s[n];
return val;
}
int bd_set_player_setting_str(BLURAY *bd, uint32_t idx, const char *s)
{
switch (idx) {
case BLURAY_PLAYER_SETTING_AUDIO_LANG:
case BLURAY_PLAYER_SETTING_PG_LANG:
case BLURAY_PLAYER_SETTING_MENU_LANG:
return bd_set_player_setting(bd, idx, s ? _string_to_uint(s, 3) : 0xffffff);
case BLURAY_PLAYER_SETTING_COUNTRY_CODE:
return bd_set_player_setting(bd, idx, s ? _string_to_uint(s, 3) : 0xffff );
default:
return 0;
}
}
/*
* bdj
*/
int bd_start_bdj(BLURAY *bd, const char *start_object)
{
#ifdef USING_BDJAVA
if (bd->bdjava == NULL) {
bd->bdjava = bdj_open(bd->device_path, start_object, bd->regs, bd);
return 0;
} else {
DEBUG(DBG_BLURAY | DBG_CRIT, "BD-J is already running (%p)\n", bd);
return -1;
}
#else
DEBUG(DBG_BLURAY | DBG_CRIT, "%s.bdjo: BD-J not compiled in (%p)\n", start_object, bd);
#endif
return -1;
}
void bd_stop_bdj(BLURAY *bd)
{
if (bd->bdjava != NULL) {
#ifdef USING_BDJAVA
bdj_close((BDJAVA*)bd->bdjava);
#else
DEBUG(DBG_BLURAY, "BD-J not compiled in (%p)\n", bd);
#endif
bd->bdjava = NULL;
}
}
/*
* Navigation mode interface
*/
/*
* notification events to APP
*/
static void _process_psr_event(void *handle, BD_PSR_EVENT *ev)
{
BLURAY *bd = (BLURAY*)handle;
DEBUG(DBG_BLURAY, "PSR event %d %d (%p)\n", ev->psr_idx, ev->new_val, bd);
switch (ev->psr_idx) {
/* current playback position */
case PSR_ANGLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_ANGLE, ev->new_val}); break;
case PSR_TITLE_NUMBER: _queue_event(bd, (BD_EVENT){BD_EVENT_TITLE, ev->new_val}); break;
case PSR_PLAYLIST: _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYLIST, ev->new_val}); break;
case PSR_PLAYITEM: _queue_event(bd, (BD_EVENT){BD_EVENT_PLAYITEM, ev->new_val}); break;
case PSR_CHAPTER: _queue_event(bd, (BD_EVENT){BD_EVENT_CHAPTER, ev->new_val}); break;
/* Interactive Graphics */
case PSR_SELECTED_BUTTON_ID:
_queue_event(bd, (BD_EVENT){BD_EVENT_SELECTED_BUTTON_ID, ev->new_val});
break;
case PSR_MENU_PAGE_ID:
_queue_event(bd, (BD_EVENT){BD_EVENT_MENU_PAGE_ID, ev->new_val});
break;
/* stream selection */
case PSR_IG_STREAM_ID:
_queue_event(bd, (BD_EVENT){BD_EVENT_IG_STREAM, ev->new_val});
break;
case PSR_PRIMARY_AUDIO_ID:
_queue_event(bd, (BD_EVENT){BD_EVENT_AUDIO_STREAM, ev->new_val});
break;
case PSR_PG_STREAM:
if ((ev->new_val & 0x80000fff) != (ev->old_val & 0x80000fff)) {
_queue_event(bd, (BD_EVENT){BD_EVENT_PG_TEXTST, !!(ev->new_val & 0x80000000)});
_queue_event(bd, (BD_EVENT){BD_EVENT_PG_TEXTST_STREAM, ev->new_val & 0xfff});
}
break;
case PSR_SECONDARY_AUDIO_VIDEO:
/* secondary video */
if ((ev->new_val & 0x8f00ff00) != (ev->old_val & 0x8f00ff00)) {
_queue_event(bd, (BD_EVENT){BD_EVENT_SECONDARY_VIDEO, !!(ev->new_val & 0x80000000)});
_queue_event(bd, (BD_EVENT){BD_EVENT_SECONDARY_VIDEO_SIZE, (ev->new_val >> 24) & 0xf});
_queue_event(bd, (BD_EVENT){BD_EVENT_SECONDARY_VIDEO_STREAM, (ev->new_val & 0xff00) >> 8});
}
/* secondary audio */
if ((ev->new_val & 0x400000ff) != (ev->old_val & 0x400000ff)) {
_queue_event(bd, (BD_EVENT){BD_EVENT_SECONDARY_AUDIO, !!(ev->new_val & 0x40000000)});
_queue_event(bd, (BD_EVENT){BD_EVENT_SECONDARY_AUDIO_STREAM, ev->new_val & 0xff});
}
break;
default:;
}
}
static int _play_bdj(BLURAY *bd, const char *name)
{
bd->title_type = title_bdj;
#ifdef USING_BDJAVA
bd_stop_bdj(bd);
return bd_start_bdj(bd, name);
#else
DEBUG(DBG_BLURAY|DBG_CRIT, "_bdj_play(BDMV/BDJ/%s.jar) not implemented (%p)\n", name, bd);
return -1;
#endif
}
static int _play_hdmv(BLURAY *bd, unsigned id_ref)
{
bd->title_type = title_hdmv;
#ifdef USING_BDJAVA
bd_stop_bdj(bd);
#endif
if (!bd->hdmv_vm) {
bd->hdmv_vm = hdmv_vm_init(bd->device_path, bd->regs);
}
bd->hdmv_suspended = 0;
return hdmv_vm_select_object(bd->hdmv_vm, id_ref, NULL);
}
#define TITLE_FIRST_PLAY 0xffff /* 10.4.3.2 (E) */
#define TITLE_TOP_MENU 0x0000 /* 5.2.3.3 */
int bd_play_title(BLURAY *bd, unsigned title)
{
/* first play object ? */
if (title == TITLE_FIRST_PLAY) {
INDX_PLAY_ITEM *p = &bd->index->first_play;
bd_psr_write(bd->regs, PSR_TITLE_NUMBER, 0xffff); /* 5.2.3.3 */
if (p->object_type == indx_object_type_hdmv) {
if (p->hdmv.id_ref == 0xffff) {
/* no first play title (5.2.3.3) */
bd->title_type = title_hdmv;
return 0;
}
return _play_hdmv(bd, p->hdmv.id_ref);
}
if (p->object_type == indx_object_type_bdj) {
return _play_bdj(bd, p->bdj.name);
}
return -1;
}
/* bd_play not called ? */
if (bd->title_type == title_undef) {
DEBUG(DBG_BLURAY|DBG_CRIT, "bd_call_title(): bd_play() not called !\n");
return -1;
}
/* top menu ? */
if (title == TITLE_TOP_MENU) {
INDX_PLAY_ITEM *p = &bd->index->top_menu;
bd_psr_write(bd->regs, PSR_TITLE_NUMBER, 0); /* 5.2.3.3 */
if (p->object_type == indx_object_type_hdmv) {
if (p->hdmv.id_ref == 0xffff) {
/* no top menu (5.2.3.3) */
bd->title_type = title_hdmv;
return -1;
}
return _play_hdmv(bd, p->hdmv.id_ref);
}
if (p->object_type == indx_object_type_bdj) {
return _play_bdj(bd, p->bdj.name);
}
return -1;
}
/* valid title from disc index ? */
if (title > 0 && title <= bd->index->num_titles) {
INDX_TITLE *t = &bd->index->titles[title-1];
bd_psr_write(bd->regs, PSR_TITLE_NUMBER, title); /* 5.2.3.3 */
if (t->object_type == indx_object_type_hdmv) {
return _play_hdmv(bd, t->hdmv.id_ref);
} else {
return _play_bdj(bd, t->bdj.name);
}
}
return -1;
}
int bd_play(BLURAY *bd)
{
/* reset player state */
bd->title_type = title_undef;
if (bd->hdmv_vm) {
hdmv_vm_free(bd->hdmv_vm);
bd->hdmv_vm = NULL;
bd->hdmv_suspended = 1;
}
_init_event_queue(bd);
bd_psr_register_cb(bd->regs, _process_psr_event, bd);
return bd_play_title(bd, TITLE_FIRST_PLAY);
}
int bd_menu_call(BLURAY *bd)
{
if (bd->title_type == title_undef) {
// bd_play not called
return -1;
}
return bd_play_title(bd, TITLE_TOP_MENU);
}
static void _process_hdmv_vm_event(BLURAY *bd, HDMV_EVENT *hev)
{
DEBUG(DBG_BLURAY, "HDMV event: %d %d\n", hev->event, hev->param);
switch (hev->event) {
case HDMV_EVENT_TITLE:
bd_play_title(bd, hev->param);
break;
case HDMV_EVENT_PLAY_PL:
bd_select_playlist(bd, hev->param);
bd->hdmv_suspended = 1;
break;
case HDMV_EVENT_PLAY_PI:
//bd_seek_pi(bd, hev->param);
DEBUG(DBG_BLURAY|DBG_CRIT, "HDMV_EVENT_PLAY_PI: not implemented\n");
break;
case HDMV_EVENT_PLAY_PM:
bd_seek_mark(bd, hev->param);
break;
case HDMV_EVENT_PLAY_STOP:
DEBUG(DBG_BLURAY|DBG_CRIT, "HDMV_EVENT_PLAY_STOP: not tested !\n");
// stop current playlist
bd_seek(bd, (uint64_t)bd->title->packets * 192 - 1);
bd->st0.clip = NULL;
// resume suspended movie object
bd->hdmv_suspended = 0;
break;
case HDMV_EVENT_STILL:
_queue_event(bd, (BD_EVENT){BD_EVENT_STILL, hev->param});
break;
case HDMV_EVENT_ENABLE_BUTTON:
_queue_event(bd, (BD_EVENT){BD_EVENT_ENABLE_BUTTON, hev->param});
break;
case HDMV_EVENT_DISABLE_BUTTON:
_queue_event(bd, (BD_EVENT){BD_EVENT_DISABLE_BUTTON, hev->param});
break;
case HDMV_EVENT_POPUP_OFF:
_queue_event(bd, (BD_EVENT){BD_EVENT_POPUP_OFF, 0});
break;
case HDMV_EVENT_END:
case HDMV_EVENT_NONE:
default:
break;
}
}
static int _run_hdmv(BLURAY *bd)
{
HDMV_EVENT hdmv_ev;
/* run VM */
if (hdmv_vm_run(bd->hdmv_vm, &hdmv_ev) < 0) {
_queue_event(bd, (BD_EVENT){BD_EVENT_ERROR, 0});
return -1;
}
/* process all events */
do {
_process_hdmv_vm_event(bd, &hdmv_ev);
} while (!hdmv_vm_get_event(bd->hdmv_vm, &hdmv_ev));
return 0;
}
int bd_read_ext(BLURAY *bd, unsigned char *buf, int len, BD_EVENT *event)
{
if (_get_event(bd, event)) {
return 0;
}
/* run HDMV VM ? */
if (bd->title_type == title_hdmv) {
while (!bd->hdmv_suspended) {
if (_run_hdmv(bd) < 0) {
DEBUG(DBG_BLURAY|DBG_CRIT, "bd_read_ext(): HDMV VM error\n");
bd->title_type = title_undef;
return -1;
}
if (_get_event(bd, event)) {
return 0;
}
}
}
if (len < 1) {
/* just polled events ? */
return 0;
}
int bytes = bd_read(bd, buf, len);
if (bytes == 0) {
if (bd->title_type == title_hdmv) {
DEBUG(DBG_BLURAY, "bd_read_ext(): reached end of playlist. hdmv_suspended=%d\n", bd->hdmv_suspended);
bd->hdmv_suspended = 0;
}
}
_get_event(bd, event);
return bytes;
}
int bd_get_event(BLURAY *bd, BD_EVENT *event)
{
if (!bd->event_queue) {
_init_event_queue(bd);
bd_psr_register_cb(bd->regs, _process_psr_event, bd);
}
if (_get_event(bd, event)) {
return 0;
}
return 1;
}