diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-17 00:32:19 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-17 00:32:19 +0000 |
commit | 0d382a262c0638d0f572fc37193ccc5ed3dc895f (patch) | |
tree | 8578dcddfce4191f3f7a142a37769df7add48475 /libdvdnav | |
download | k9copy-0d382a262c0638d0f572fc37193ccc5ed3dc895f.tar.gz k9copy-0d382a262c0638d0f572fc37193ccc5ed3dc895f.zip |
Added old abandoned version of k9copy
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/k9copy@1091546 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libdvdnav')
-rw-r--r-- | libdvdnav/Makefile.am | 14 | ||||
-rw-r--r-- | libdvdnav/common.h | 16 | ||||
-rw-r--r-- | libdvdnav/compat.h | 58 | ||||
-rw-r--r-- | libdvdnav/decoder.c | 777 | ||||
-rw-r--r-- | libdvdnav/decoder.h | 118 | ||||
-rw-r--r-- | libdvdnav/dvd_types.h | 281 | ||||
-rw-r--r-- | libdvdnav/dvdnav.c | 1093 | ||||
-rw-r--r-- | libdvdnav/dvdnav.h | 630 | ||||
-rw-r--r-- | libdvdnav/dvdnav_events.h | 241 | ||||
-rw-r--r-- | libdvdnav/dvdnav_internal.h | 199 | ||||
-rw-r--r-- | libdvdnav/dvdread.c | 74 | ||||
-rw-r--r-- | libdvdnav/dvdread.h | 28 | ||||
-rw-r--r-- | libdvdnav/highlight.c | 512 | ||||
-rw-r--r-- | libdvdnav/navigation.c | 231 | ||||
-rw-r--r-- | libdvdnav/read_cache.c | 347 | ||||
-rw-r--r-- | libdvdnav/read_cache.h | 51 | ||||
-rw-r--r-- | libdvdnav/remap.c | 257 | ||||
-rw-r--r-- | libdvdnav/remap.h | 33 | ||||
-rw-r--r-- | libdvdnav/searching.c | 567 | ||||
-rw-r--r-- | libdvdnav/settings.c | 121 | ||||
-rw-r--r-- | libdvdnav/vm.c | 1861 | ||||
-rw-r--r-- | libdvdnav/vm.h | 181 | ||||
-rw-r--r-- | libdvdnav/vmcmd.c | 539 | ||||
-rw-r--r-- | libdvdnav/vmcmd.h | 33 |
24 files changed, 8262 insertions, 0 deletions
diff --git a/libdvdnav/Makefile.am b/libdvdnav/Makefile.am new file mode 100644 index 0000000..13baa2d --- /dev/null +++ b/libdvdnav/Makefile.am @@ -0,0 +1,14 @@ + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libk9dvdnav.la +libk9dvdnav_la_SOURCES = decoder.c decoder.h dvdnav.c dvdnav_events.h dvdnav.h \ + dvdnav_internal.h highlight.c navigation.c read_cache.c read_cache.h remap.c remap.h \ + searching.c settings.c vm.c vmcmd.c vmcmd.h vm.h compat.h dvd_types.h + + +noinst_HEADERS = common.h dvdread.h + + + +INCLUDES = -I$(top_srcdir)/dvdread diff --git a/libdvdnav/common.h b/libdvdnav/common.h new file mode 100644 index 0000000..c54924e --- /dev/null +++ b/libdvdnav/common.h @@ -0,0 +1,16 @@ +#include "config.h" +#ifndef __STDC_LIMIT_MACROS + #define __STDC_LIMIT_MACROS +#endif + +#if defined(HAVE_INTTYPES_H) + #include <inttypes.h> +#elif defined( HAVE_STDINT_H) + #include <stdint.h> +#endif + +#include "ifo_types.h" +#include "dvd_reader.h" +#include "ifo_read.h" +#include "nav_read.h" +#include "nav_types.h" diff --git a/libdvdnav/compat.h b/libdvdnav/compat.h new file mode 100644 index 0000000..08b2957 --- /dev/null +++ b/libdvdnav/compat.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2000-2001 the xine project + * + * This file is part of xine, a unix video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: compat.h,v 1.3 2003/04/20 21:13:26 guenter Exp $ + * + */ + +#ifndef XINE_COMPAT_H +#define XINE_COMPAT_H + +#include <limits.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined _MSC_VER +#define __XINE_FUNCTION__ __FILE__ +#elif defined __GNUC__ +#define __XINE_FUNCTION__ __FUNCTION__ +#else +#define __XINE_FUNCTION__ __func__ +#endif + +#ifndef NAME_MAX +#define XINE_NAME_MAX 256 +#else +#define XINE_NAME_MAX NAME_MAX +#endif + +#ifndef PATH_MAX +#define XINE_PATH_MAX 768 +#else +#define XINE_PATH_MAX PATH_MAX +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libdvdnav/decoder.c b/libdvdnav/decoder.c new file mode 100644 index 0000000..1c21f60 --- /dev/null +++ b/libdvdnav/decoder.c @@ -0,0 +1,777 @@ +/* + * Copyright (C) 2000, 2001 Martin Norb�k, H�an Hjort + * 2002-2004 the dvdnav project + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: decoder.c,v 1.14 2004/03/16 11:43:38 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "common.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> /* For memset */ + +#include "dvdnav_internal.h" + +uint32_t vm_getbits(command_t *command, int32_t start, int32_t count) { + uint64_t result = 0; + uint64_t bit_mask = 0; + uint64_t examining = 0; + int32_t bits; + + if (count == 0) return 0; + + if ( ((start - count) < -1) || + (count > 32) || + (start > 63) || + (count < 0) || + (start < 0) ) { + fprintf(MSG_OUT, "libdvdnav: Bad call to vm_getbits. Parameter out of range\n"); + abort(); + } + /* all ones, please */ + bit_mask = ~bit_mask; + bit_mask >>= 63 - start; + bits = start + 1 - count; + examining = ((bit_mask >> bits) << bits ); + command->examined |= examining; + result = (command->instruction & bit_mask) >> bits; + return (uint32_t) result; +} + +static uint16_t get_GPRM(registers_t* registers, uint8_t reg) { + if (registers->GPRM_mode[reg] & 0x01) { + struct timeval current_time, time_offset; + uint16_t result; + /* Counter mode */ + /* fprintf(MSG_OUT, "libdvdnav: Getting counter %d\n",reg);*/ + gettimeofday(¤t_time, NULL); + time_offset.tv_sec = current_time.tv_sec - registers->GPRM_time[reg].tv_sec; + time_offset.tv_usec = current_time.tv_usec - registers->GPRM_time[reg].tv_usec; + if (time_offset.tv_usec < 0) { + time_offset.tv_sec--; + time_offset.tv_usec += 1000000; + } + result = (uint16_t) (time_offset.tv_sec & 0xffff); + registers->GPRM[reg]=result; + return result; + + } else { + /* Register mode */ + return registers->GPRM[reg]; + } + +} + +static void set_GPRM(registers_t* registers, uint8_t reg, uint16_t value) { + if (registers->GPRM_mode[reg] & 0x01) { + struct timeval current_time; + /* Counter mode */ + /* fprintf(MSG_OUT, "libdvdnav: Setting counter %d\n",reg); */ + gettimeofday(¤t_time, NULL); + registers->GPRM_time[reg] = current_time; + registers->GPRM_time[reg].tv_sec -= value; + } + registers->GPRM[reg] = value; +} + +/* Eval register code, can either be system or general register. + SXXX_XXXX, where S is 1 if it is system register. */ +static uint16_t eval_reg(command_t* command, uint8_t reg) { + if(reg & 0x80) { + if ((reg & 0x1f) == 20) { + fprintf(MSG_OUT, "libdvdnav: Suspected RCE Region Protection!!!\n"); + } + return command->registers->SPRM[reg & 0x1f]; /* FIXME max 24 not 32 */ + } else { + return get_GPRM(command->registers, reg & 0x0f) ; + } +} + +/* Eval register or immediate data. + AAAA_AAAA BBBB_BBBB, if immediate use all 16 bits for data else use + lower eight bits for the system or general purpose register. */ +static uint16_t eval_reg_or_data(command_t* command, int32_t imm, int32_t start) { + if(imm) { /* immediate */ + return vm_getbits(command, start, 16); + } else { + return eval_reg(command, vm_getbits(command, (start - 8), 8)); + } +} + +/* Eval register or immediate data. + xBBB_BBBB, if immediate use all 7 bits for data else use + lower four bits for the general purpose register number. */ +/* Evaluates gprm or data depending on bit, data is in byte n */ +static uint16_t eval_reg_or_data_2(command_t* command, + int32_t imm, int32_t start) { + if(imm) /* immediate */ + return vm_getbits(command, (start - 1), 7); + else + return get_GPRM(command->registers, (vm_getbits(command, (start - 4), 4)) ); +} + + +/* Compare data using operation, return result from comparison. + Helper function for the different if functions. */ +static int32_t eval_compare(uint8_t operation, uint16_t data1, uint16_t data2) { + switch(operation) { + case 1: + return data1 & data2; + case 2: + return data1 == data2; + case 3: + return data1 != data2; + case 4: + return data1 >= data2; + case 5: + return data1 > data2; + case 6: + return data1 <= data2; + case 7: + return data1 < data2; + } + fprintf(MSG_OUT, "libdvdnav: eval_compare: Invalid comparison code\n"); + return 0; +} + + +/* Evaluate if version 1. + Has comparison data in byte 3 and 4-5 (immediate or register) */ +static int32_t eval_if_version_1(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + if(op) { + return eval_compare(op, eval_reg(command, vm_getbits(command, 39, 8)), + eval_reg_or_data(command, vm_getbits(command, 55, 1), 31)); + } + return 1; +} + +/* Evaluate if version 2. + This version only compares register which are in byte 6 and 7 */ +static int32_t eval_if_version_2(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + if(op) { + return eval_compare(op, eval_reg(command, vm_getbits(command, 15, 8)), + eval_reg(command, vm_getbits(command, 7, 8))); + } + return 1; +} + +/* Evaluate if version 3. + Has comparison data in byte 2 and 6-7 (immediate or register) */ +static int32_t eval_if_version_3(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + if(op) { + return eval_compare(op, eval_reg(command, vm_getbits(command, 47, 8)), + eval_reg_or_data(command, vm_getbits(command, 55, 1), 15)); + } + return 1; +} + +/* Evaluate if version 4. + Has comparison data in byte 1 and 4-5 (immediate or register) + The register in byte 1 is only the lowe nibble (4 bits) */ +static int32_t eval_if_version_4(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + if(op) { + return eval_compare(op, eval_reg(command, vm_getbits(command, 51, 4)), + eval_reg_or_data(command, vm_getbits(command, 55, 1), 31)); + } + return 1; +} + +/* Evaluate special instruction.... returns the new row/line number, + 0 if no new row and 256 if Break. */ +static int32_t eval_special_instruction(command_t* command, int32_t cond) { + int32_t line, level; + + switch(vm_getbits(command, 51, 4)) { + case 0: /* NOP */ + line = 0; + return cond ? line : 0; + case 1: /* Goto line */ + line = vm_getbits(command, 7, 8); + return cond ? line : 0; + case 2: /* Break */ + /* max number of rows < 256, so we will end this set */ + line = 256; + return cond ? 256 : 0; + case 3: /* Set temporary parental level and goto */ + line = vm_getbits(command, 7, 8); + level = vm_getbits(command, 11, 4); + if(cond) { + /* This always succeeds now, if we want real parental protection */ + /* we need to ask the user and have passwords and stuff. */ + command->registers->SPRM[13] = level; + } + return cond ? line : 0; + } + return 0; +} + +/* Evaluate link by subinstruction. + Return 1 if link, or 0 if no link + Actual link instruction is in return_values parameter */ +static int32_t eval_link_subins(command_t* command, int32_t cond, link_t *return_values) { + uint16_t button = vm_getbits(command, 15, 6); + uint8_t linkop = vm_getbits(command, 4, 5); + + if(linkop > 0x10) + return 0; /* Unknown Link by Sub-Instruction command */ + + /* Assumes that the link_cmd_t enum has the same values as the LinkSIns codes */ + return_values->command = linkop; + return_values->data1 = button; + return cond; +} + + +/* Evaluate link instruction. + Return 1 if link, or 0 if no link + Actual link instruction is in return_values parameter */ +static int32_t eval_link_instruction(command_t* command, int32_t cond, link_t *return_values) { + uint8_t op = vm_getbits(command, 51, 4); + + switch(op) { + case 1: + return eval_link_subins(command, cond, return_values); + case 4: + return_values->command = LinkPGCN; + return_values->data1 = vm_getbits(command, 14, 15); + return cond; + case 5: + return_values->command = LinkPTTN; + return_values->data1 = vm_getbits(command, 9, 10); + return_values->data2 = vm_getbits(command, 15, 6); + return cond; + case 6: + return_values->command = LinkPGN; + return_values->data1 = vm_getbits(command, 6, 7); + return_values->data2 = vm_getbits(command, 15, 6); + return cond; + case 7: + return_values->command = LinkCN; + return_values->data1 = vm_getbits(command, 7, 8); + return_values->data2 = vm_getbits(command, 15, 6); + return cond; + } + return 0; +} + + +/* Evaluate a jump instruction. + returns 1 if jump or 0 if no jump + actual jump instruction is in return_values parameter */ +static int32_t eval_jump_instruction(command_t* command, int32_t cond, link_t *return_values) { + + switch(vm_getbits(command, 51, 4)) { + case 1: + return_values->command = Exit; + return cond; + case 2: + return_values->command = JumpTT; + return_values->data1 = vm_getbits(command, 22, 7); + return cond; + case 3: + return_values->command = JumpVTS_TT; + return_values->data1 = vm_getbits(command, 22, 7); + return cond; + case 5: + return_values->command = JumpVTS_PTT; + return_values->data1 = vm_getbits(command, 22, 7); + return_values->data2 = vm_getbits(command, 41, 10); + return cond; + case 6: + switch(vm_getbits(command, 23, 2)) { + case 0: + return_values->command = JumpSS_FP; + return cond; + case 1: + return_values->command = JumpSS_VMGM_MENU; + return_values->data1 = vm_getbits(command, 19, 4); + return cond; + case 2: + return_values->command = JumpSS_VTSM; + return_values->data1 = vm_getbits(command, 31, 8); + return_values->data2 = vm_getbits(command, 39, 8); + return_values->data3 = vm_getbits(command, 19, 4); + return cond; + case 3: + return_values->command = JumpSS_VMGM_PGC; + return_values->data1 = vm_getbits(command, 46, 15); + return cond; + } + break; + case 8: + switch(vm_getbits(command, 23, 2)) { + case 0: + return_values->command = CallSS_FP; + return_values->data1 = vm_getbits(command, 31, 8); + return cond; + case 1: + return_values->command = CallSS_VMGM_MENU; + return_values->data1 = vm_getbits(command, 19, 4); + return_values->data2 = vm_getbits(command, 31, 8); + return cond; + case 2: + return_values->command = CallSS_VTSM; + return_values->data1 = vm_getbits(command, 19, 4); + return_values->data2 = vm_getbits(command, 31, 8); + return cond; + case 3: + return_values->command = CallSS_VMGM_PGC; + return_values->data1 = vm_getbits(command, 46, 15); + return_values->data2 = vm_getbits(command, 31, 8); + return cond; + } + break; + } + return 0; +} + +/* Evaluate a set sytem register instruction + May contain a link so return the same as eval_link */ +static int32_t eval_system_set(command_t* command, int32_t cond, link_t *return_values) { + int32_t i; + uint16_t data, data2; + + switch(vm_getbits(command, 59, 4)) { + case 1: /* Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */ + for(i = 1; i <= 3; i++) { + if(vm_getbits(command, 63 - ((2 + i)*8), 1)) { + data = eval_reg_or_data_2(command, vm_getbits(command, 60, 1), (47 - (i*8))); + if(cond) { + command->registers->SPRM[i] = data; + } + } + } + break; + case 2: /* Set system reg 9 & 10 (Navigation timer, Title PGC number) */ + data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47); + data2 = vm_getbits(command, 23, 8); /* ?? size */ + if(cond) { + command->registers->SPRM[9] = data; /* time */ + command->registers->SPRM[10] = data2; /* pgcN */ + } + break; + case 3: /* Mode: Counter / Register + Set */ + data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47); + data2 = vm_getbits(command, 19, 4); + if(vm_getbits(command, 23, 1)) { + command->registers->GPRM_mode[data2] |= 1; /* Set bit 0 */ + } else { + command->registers->GPRM_mode[data2] &= ~ 0x01; /* Reset bit 0 */ + } + if(cond) { + set_GPRM(command->registers, data2, data); + } + break; + case 6: /* Set system reg 8 (Highlighted button) */ + data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31); /* Not system reg!! */ + if(cond) { + command->registers->SPRM[8] = data; + } + break; + } + if(vm_getbits(command, 51, 4)) { + return eval_link_instruction(command, cond, return_values); + } + return 0; +} + + +/* Evaluate set operation + Sets the register given to the value indicated by op and data. + For the swap case the contents of reg is stored in reg2. +*/ +static void eval_set_op(command_t* command, int32_t op, int32_t reg, int32_t reg2, int32_t data) { + const int32_t shortmax = 0xffff; + int32_t tmp; + switch(op) { + case 1: + set_GPRM(command->registers, reg, data); + break; + case 2: /* SPECIAL CASE - SWAP! */ + set_GPRM(command->registers, reg2, get_GPRM(command->registers, reg)); + set_GPRM(command->registers, reg, data); + break; + case 3: + tmp = get_GPRM(command->registers, reg) + data; + if(tmp > shortmax) tmp = shortmax; + set_GPRM(command->registers, reg, (uint16_t)tmp); + break; + case 4: + tmp = get_GPRM(command->registers, reg) - data; + if(tmp < 0) tmp = 0; + set_GPRM(command->registers, reg, (uint16_t)tmp); + break; + case 5: + tmp = get_GPRM(command->registers, reg) * data; + if(tmp > shortmax) tmp = shortmax; + set_GPRM(command->registers, reg, (uint16_t)tmp); + break; + case 6: + if (data != 0) { + set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) / data) ); + } else { + set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */ + } + break; + case 7: + if (data != 0) { + set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) % data) ); + } else { + set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */ + } + break; + case 8: /* SPECIAL CASE - RND! Return numbers between 1 and data. */ + set_GPRM(command->registers, reg, 1 + ((uint16_t) ((float) data * rand()/(RAND_MAX+1.0))) ); + break; + case 9: + set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) & data) ); + break; + case 10: + set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) | data) ); + break; + case 11: + set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) ^ data) ); + break; + } +} + +/* Evaluate set instruction, combined with either Link or Compare. */ +static void eval_set_version_1(command_t* command, int32_t cond) { + uint8_t op = vm_getbits(command, 59, 4); + uint8_t reg = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */ + uint8_t reg2 = vm_getbits(command, 19, 4); + uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31); + + if(cond) { + eval_set_op(command, op, reg, reg2, data); + } +} + + +/* Evaluate set instruction, combined with both Link and Compare. */ +static void eval_set_version_2(command_t* command, int32_t cond) { + uint8_t op = vm_getbits(command, 59, 4); + uint8_t reg = vm_getbits(command, 51, 4); + uint8_t reg2 = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */ + uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47); + + if(cond) { + eval_set_op(command, op, reg, reg2, data); + } +} + + +/* Evaluate a command + returns row number of goto, 0 if no goto, -1 if link. + Link command in return_values */ +static int32_t eval_command(uint8_t *bytes, registers_t* registers, link_t *return_values) { + int32_t cond, res = 0; + command_t command; + command.instruction =( (uint64_t) bytes[0] << 56 ) | + ( (uint64_t) bytes[1] << 48 ) | + ( (uint64_t) bytes[2] << 40 ) | + ( (uint64_t) bytes[3] << 32 ) | + ( (uint64_t) bytes[4] << 24 ) | + ( (uint64_t) bytes[5] << 16 ) | + ( (uint64_t) bytes[6] << 8 ) | + (uint64_t) bytes[7] ; + command.examined = 0; + command.registers = registers; + memset(return_values, 0, sizeof(link_t)); + + switch(vm_getbits(&command, 63, 3)) { /* three first old_bits */ + case 0: /* Special instructions */ + cond = eval_if_version_1(&command); + res = eval_special_instruction(&command, cond); + if(res == -1) { + fprintf(MSG_OUT, "libdvdnav: Unknown Instruction!\n"); + abort(); + } + break; + case 1: /* Link/jump instructions */ + if(vm_getbits(&command, 60, 1)) { + cond = eval_if_version_2(&command); + res = eval_jump_instruction(&command, cond, return_values); + } else { + cond = eval_if_version_1(&command); + res = eval_link_instruction(&command, cond, return_values); + } + if(res) + res = -1; + break; + case 2: /* System set instructions */ + cond = eval_if_version_2(&command); + res = eval_system_set(&command, cond, return_values); + if(res) + res = -1; + break; + case 3: /* Set instructions, either Compare or Link may be used */ + cond = eval_if_version_3(&command); + eval_set_version_1(&command, cond); + if(vm_getbits(&command, 51, 4)) { + res = eval_link_instruction(&command, cond, return_values); + } + if(res) + res = -1; + break; + case 4: /* Set, Compare -> Link Sub-Instruction */ + eval_set_version_2(&command, /*True*/ 1); + cond = eval_if_version_4(&command); + res = eval_link_subins(&command, cond, return_values); + if(res) + res = -1; + break; + case 5: /* Compare -> (Set and Link Sub-Instruction) */ + /* FIXME: These are wrong. Need to be updated from vmcmd.c */ + cond = eval_if_version_4(&command); + eval_set_version_2(&command, cond); + res = eval_link_subins(&command, cond, return_values); + if(res) + res = -1; + break; + case 6: /* Compare -> Set, allways Link Sub-Instruction */ + /* FIXME: These are wrong. Need to be updated from vmcmd.c */ + cond = eval_if_version_4(&command); + eval_set_version_2(&command, cond); + res = eval_link_subins(&command, /*True*/ 1, return_values); + if(res) + res = -1; + break; + default: /* Unknown command */ + fprintf(MSG_OUT, "libdvdnav: WARNING: Unknown Command=%x\n", vm_getbits(&command, 63, 3)); + abort(); + } + /* Check if there are bits not yet examined */ + + if(command.instruction & ~ command.examined) { + fprintf(MSG_OUT, "libdvdnav: decoder.c: [WARNING, unknown bits:"); + fprintf(MSG_OUT, " %08llx", (command.instruction & ~ command.examined) ); + fprintf(MSG_OUT, "]\n"); + } + + return res; +} + +/* Evaluate a set of commands in the given register set (which is modified) */ +int32_t vmEval_CMD(vm_cmd_t commands[], int32_t num_commands, + registers_t *registers, link_t *return_values) { + int32_t i = 0; + int32_t total = 0; + +#ifdef TRACE + /* DEBUG */ + fprintf(MSG_OUT, "libdvdnav: Registers before transaction\n"); + vm_print_registers( registers ); + fprintf(MSG_OUT, "libdvdnav: Full list of commands to execute\n"); + for(i = 0; i < num_commands; i++) + vm_print_cmd(i, &commands[i]); + fprintf(MSG_OUT, "libdvdnav: --------------------------------------------\n"); + fprintf(MSG_OUT, "libdvdnav: Single stepping commands\n"); +#endif + + i = 0; + while(i < num_commands && total < 100000) { + int32_t line; + +#ifdef TRACE + vm_print_cmd(i, &commands[i]); +#endif + + line = eval_command(&commands[i].bytes[0], registers, return_values); + + if (line < 0) { /* Link command */ +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Registers after transaction\n"); + vm_print_registers( registers ); + fprintf(MSG_OUT, "libdvdnav: eval: Doing Link/Jump/Call\n"); +#endif + return 1; + } + + if (line > 0) /* Goto command */ + i = line - 1; + else /* Just continue on the next line */ + i++; + + total++; + } + + memset(return_values, 0, sizeof(link_t)); +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Registers after transaction\n"); + vm_print_registers( registers ); +#endif + return 0; +} + +#ifdef TRACE + +static char *linkcmd2str(link_cmd_t cmd) { + switch(cmd) { + case LinkNoLink: + return "LinkNoLink"; + case LinkTopC: + return "LinkTopC"; + case LinkNextC: + return "LinkNextC"; + case LinkPrevC: + return "LinkPrevC"; + case LinkTopPG: + return "LinkTopPG"; + case LinkNextPG: + return "LinkNextPG"; + case LinkPrevPG: + return "LinkPrevPG"; + case LinkTopPGC: + return "LinkTopPGC"; + case LinkNextPGC: + return "LinkNextPGC"; + case LinkPrevPGC: + return "LinkPrevPGC"; + case LinkGoUpPGC: + return "LinkGoUpPGC"; + case LinkTailPGC: + return "LinkTailPGC"; + case LinkRSM: + return "LinkRSM"; + case LinkPGCN: + return "LinkPGCN"; + case LinkPTTN: + return "LinkPTTN"; + case LinkPGN: + return "LinkPGN"; + case LinkCN: + return "LinkCN"; + case Exit: + return "Exit"; + case JumpTT: + return "JumpTT"; + case JumpVTS_TT: + return "JumpVTS_TT"; + case JumpVTS_PTT: + return "JumpVTS_PTT"; + case JumpSS_FP: + return "JumpSS_FP"; + case JumpSS_VMGM_MENU: + return "JumpSS_VMGM_MENU"; + case JumpSS_VTSM: + return "JumpSS_VTSM"; + case JumpSS_VMGM_PGC: + return "JumpSS_VMGM_PGC"; + case CallSS_FP: + return "CallSS_FP"; + case CallSS_VMGM_MENU: + return "CallSS_VMGM_MENU"; + case CallSS_VTSM: + return "CallSS_VTSM"; + case CallSS_VMGM_PGC: + return "CallSS_VMGM_PGC"; + case PlayThis: + return "PlayThis"; + } + return "*** (bug)"; +} + +void vm_print_link(link_t value) { + char *cmd = linkcmd2str(value.command); + + switch(value.command) { + case LinkNoLink: + case LinkTopC: + case LinkNextC: + case LinkPrevC: + case LinkTopPG: + case LinkNextPG: + case LinkPrevPG: + case LinkTopPGC: + case LinkNextPGC: + case LinkPrevPGC: + case LinkGoUpPGC: + case LinkTailPGC: + case LinkRSM: + fprintf(MSG_OUT, "libdvdnav: %s (button %d)\n", cmd, value.data1); + break; + case LinkPGCN: + case JumpTT: + case JumpVTS_TT: + case JumpSS_VMGM_MENU: /* == 2 -> Title Menu */ + case JumpSS_VMGM_PGC: + fprintf(MSG_OUT, "libdvdnav: %s %d\n", cmd, value.data1); + break; + case LinkPTTN: + case LinkPGN: + case LinkCN: + fprintf(MSG_OUT, "libdvdnav: %s %d (button %d)\n", cmd, value.data1, value.data2); + break; + case Exit: + case JumpSS_FP: + case PlayThis: /* Humm.. should we have this at all.. */ + fprintf(MSG_OUT, "libdvdnav: %s\n", cmd); + break; + case JumpVTS_PTT: + fprintf(MSG_OUT, "libdvdnav: %s %d:%d\n", cmd, value.data1, value.data2); + break; + case JumpSS_VTSM: + fprintf(MSG_OUT, "libdvdnav: %s vts %d title %d menu %d\n", + cmd, value.data1, value.data2, value.data3); + break; + case CallSS_FP: + fprintf(MSG_OUT, "libdvdnav: %s resume cell %d\n", cmd, value.data1); + break; + case CallSS_VMGM_MENU: /* == 2 -> Title Menu */ + case CallSS_VTSM: + fprintf(MSG_OUT, "libdvdnav: %s %d resume cell %d\n", cmd, value.data1, value.data2); + break; + case CallSS_VMGM_PGC: + fprintf(MSG_OUT, "libdvdnav: %s %d resume cell %d\n", cmd, value.data1, value.data2); + break; + } + } + +void vm_print_registers( registers_t *registers ) { + int32_t i; + fprintf(MSG_OUT, "libdvdnav: # "); + for(i = 0; i < 24; i++) + fprintf(MSG_OUT, " %2d |", i); + fprintf(MSG_OUT, "\nlibdvdnav: SRPMS: "); + for(i = 0; i < 24; i++) + fprintf(MSG_OUT, "%04x|", registers->SPRM[i]); + fprintf(MSG_OUT, "\nlibdvdnav: GRPMS: "); + for(i = 0; i < 16; i++) + fprintf(MSG_OUT, "%04x|", get_GPRM(registers, i) ); + fprintf(MSG_OUT, "\nlibdvdnav: Gmode: "); + for(i = 0; i < 16; i++) + fprintf(MSG_OUT, "%04x|", registers->GPRM_mode[i]); + fprintf(MSG_OUT, "\nlibdvdnav: Gtime: "); + for(i = 0; i < 16; i++) + fprintf(MSG_OUT, "%04lx|", registers->GPRM_time[i].tv_sec & 0xffff); + fprintf(MSG_OUT, "\n"); +} + +#endif + diff --git a/libdvdnav/decoder.h b/libdvdnav/decoder.h new file mode 100644 index 0000000..d00ceaa --- /dev/null +++ b/libdvdnav/decoder.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2000, 2001 Martin Norb�k, H�an Hjort + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: decoder.h,v 1.9 2004/03/16 11:43:38 mroi Exp $ + * + */ + +#ifndef DECODER_H_INCLUDED +#define DECODER_H_INCLUDED + +#include "common.h" +#include <sys/time.h> + +/* vm_cmd_t */ +#include "dvdnav_internal.h" + +/* link command types */ +typedef enum { + LinkNoLink = 0, + + LinkTopC = 1, + LinkNextC = 2, + LinkPrevC = 3, + + LinkTopPG = 5, + LinkNextPG = 6, + LinkPrevPG = 7, + + LinkTopPGC = 9, + LinkNextPGC = 10, + LinkPrevPGC = 11, + LinkGoUpPGC = 12, + LinkTailPGC = 13, + + LinkRSM = 16, + + LinkPGCN, + LinkPTTN, + LinkPGN, + LinkCN, + + Exit, + + JumpTT, /* 22 */ + JumpVTS_TT, + JumpVTS_PTT, + + JumpSS_FP, + JumpSS_VMGM_MENU, + JumpSS_VTSM, + JumpSS_VMGM_PGC, + + CallSS_FP, /* 29 */ + CallSS_VMGM_MENU, + CallSS_VTSM, + CallSS_VMGM_PGC, + + PlayThis +} link_cmd_t; + +/* a link's data set */ +typedef struct { + link_cmd_t command; + uint16_t data1; + uint16_t data2; + uint16_t data3; +} link_t; + +/* the VM registers */ +typedef struct { + uint16_t SPRM[24]; + uint16_t GPRM[16]; + uint8_t GPRM_mode[16]; /* Need to have some thing to indicate normal/counter mode for every GPRM */ + struct timeval GPRM_time[16]; /* For counter mode */ +} registers_t; + +/* a VM command data set */ +typedef struct { + uint64_t instruction; + uint64_t examined; + registers_t *registers; +} command_t; + +/* the big VM function, executing the given commands and writing + * the link where to continue, the return value indicates if a jump + * has been performed */ +int32_t vmEval_CMD(vm_cmd_t commands[], int32_t num_commands, + registers_t *registers, link_t *return_values); + +/* extracts some bits from the command */ +uint32_t vm_getbits(command_t* command, int32_t start, int32_t count); + +#ifdef TRACE +/* for debugging: prints a link in readable form */ +void vm_print_link(link_t value); + +/* for debugging: dumps VM registers */ +void vm_print_registers( registers_t *registers ); +#endif + +#endif /* DECODER_H_INCLUDED */ diff --git a/libdvdnav/dvd_types.h b/libdvdnav/dvd_types.h new file mode 100644 index 0000000..dc9aeb1 --- /dev/null +++ b/libdvdnav/dvd_types.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2000, 2001 Bj�n Englund, H�an Hjort + * + * This file is part of libdvdnav, a DVD navigation library. It is a modified + * file originally part of the Ogle DVD player project. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: dvd_types.h,v 1.4 2003/04/29 15:58:30 jcdutton Exp $ + * + */ + +/* + * Various useful structs and enums for DVDs. + */ + +#ifndef DVD_H_INCLUDED +#define DVD_H_INCLUDED + +#include "common.h" + + +/* + * DVD Menu ID + * (see dvdnav_menu_call()) + */ +typedef enum { + /* When used in VTS domain, DVD_MENU_Escape behaves like DVD_MENU_Root, + * but from within a menu domain, DVD_MENU_Escape resumes playback. */ + DVD_MENU_Escape = 0, + DVD_MENU_Title = 2, + DVD_MENU_Root = 3, + DVD_MENU_Subpicture = 4, + DVD_MENU_Audio = 5, + DVD_MENU_Angle = 6, + DVD_MENU_Part = 7 +} DVDMenuID_t; + + +/* + * Structure containing info on highlight areas + * (see dvdnav_get_highlight_area()) + */ +typedef struct { + uint32_t palette; /* The CLUT entries for the highlight palette + (4-bits per entry -> 4 entries) */ + uint16_t sx,sy,ex,ey; /* The start/end x,y positions */ + uint32_t pts; /* Highlight PTS to match with SPU */ + + /* button number for the SPU decoder/overlaying engine */ + uint32_t buttonN; +} dvdnav_highlight_area_t; + + +/* the following types are currently unused */ + +#if 0 + +/* Domain */ +typedef enum { + DVD_DOMAIN_FirstPlay, /* First Play Domain */ + DVD_DOMAIN_VMG, /* Video Manager Domain */ + DVD_DOMAIN_VTSMenu, /* Video Title Set Menu Domain */ + DVD_DOMAIN_VTSTitle, /* Video Title Set Domain */ + DVD_DOMAIN_Stop /* Stop Domain */ +} DVDDomain_t; + +/* User operation permissions */ +typedef enum { + UOP_FLAG_TitleOrTimePlay = 0x00000001, + UOP_FLAG_ChapterSearchOrPlay = 0x00000002, + UOP_FLAG_TitlePlay = 0x00000004, + UOP_FLAG_Stop = 0x00000008, + UOP_FLAG_GoUp = 0x00000010, + UOP_FLAG_TimeOrChapterSearch = 0x00000020, + UOP_FLAG_PrevOrTopPGSearch = 0x00000040, + UOP_FLAG_NextPGSearch = 0x00000080, + UOP_FLAG_ForwardScan = 0x00000100, + UOP_FLAG_BackwardScan = 0x00000200, + UOP_FLAG_TitleMenuCall = 0x00000400, + UOP_FLAG_RootMenuCall = 0x00000800, + UOP_FLAG_SubPicMenuCall = 0x00001000, + UOP_FLAG_AudioMenuCall = 0x00002000, + UOP_FLAG_AngleMenuCall = 0x00004000, + UOP_FLAG_ChapterMenuCall = 0x00008000, + UOP_FLAG_Resume = 0x00010000, + UOP_FLAG_ButtonSelectOrActivate = 0x00020000, + UOP_FLAG_StillOff = 0x00040000, + UOP_FLAG_PauseOn = 0x00080000, + UOP_FLAG_AudioStreamChange = 0x00100000, + UOP_FLAG_SubPicStreamChange = 0x00200000, + UOP_FLAG_AngleChange = 0x00400000, + UOP_FLAG_KaraokeAudioPresModeChange = 0x00800000, + UOP_FLAG_VideoPresModeChange = 0x01000000 +} DVDUOP_t; + +/* Parental Level */ +typedef enum { + DVD_PARENTAL_LEVEL_1 = 1, + DVD_PARENTAL_LEVEL_2 = 2, + DVD_PARENTAL_LEVEL_3 = 3, + DVD_PARENTAL_LEVEL_4 = 4, + DVD_PARENTAL_LEVEL_5 = 5, + DVD_PARENTAL_LEVEL_6 = 6, + DVD_PARENTAL_LEVEL_7 = 7, + DVD_PARENTAL_LEVEL_8 = 8, + DVD_PARENTAL_LEVEL_None = 15 +} DVDParentalLevel_t; + +/* Language ID (ISO-639 language code) */ +typedef uint16_t DVDLangID_t; + +/* Country ID (ISO-3166 country code) */ +typedef uint16_t DVDCountryID_t; + +/* Register */ +typedef uint16_t DVDRegister_t; +typedef enum { + DVDFalse = 0, + DVDTrue = 1 +} DVDBool_t; +typedef DVDRegister_t DVDGPRMArray_t[16]; +typedef DVDRegister_t DVDSPRMArray_t[24]; + +/* Navigation */ +typedef int DVDStream_t; +typedef int DVDPTT_t; +typedef int DVDTitle_t; + +/* Angle number (1-9 or default?) */ +typedef int DVDAngle_t; + +/* Timecode */ +typedef struct { + uint8_t Hours; + uint8_t Minutes; + uint8_t Seconds; + uint8_t Frames; +} DVDTimecode_t; + +/* Subpicture stream number (0-31,62,63) */ +typedef int DVDSubpictureStream_t; + +/* Audio stream number (0-7, 15(none)) */ +typedef int DVDAudioStream_t; + +/* The audio application mode */ +typedef enum { + DVD_AUDIO_APP_MODE_None = 0, + DVD_AUDIO_APP_MODE_Karaoke = 1, + DVD_AUDIO_APP_MODE_Surround = 2, + DVD_AUDIO_APP_MODE_Other = 3 +} DVDAudioAppMode_t; + +/* The audio format */ +typedef enum { + DVD_AUDIO_FORMAT_AC3 = 0, + DVD_AUDIO_FORMAT_MPEG1 = 1, + DVD_AUDIO_FORMAT_MPEG1_DRC = 2, + DVD_AUDIO_FORMAT_MPEG2 = 3, + DVD_AUDIO_FORMAT_MPEG2_DRC = 4, + DVD_AUDIO_FORMAT_LPCM = 5, + DVD_AUDIO_FORMAT_DTS = 6, + DVD_AUDIO_FORMAT_SDDS = 7, + DVD_AUDIO_FORMAT_Other = 8 +} DVDAudioFormat_t; + +/* Audio language extension */ +typedef enum { + DVD_AUDIO_LANG_EXT_NotSpecified = 0, + DVD_AUDIO_LANG_EXT_NormalCaptions = 1, + DVD_AUDIO_LANG_EXT_VisuallyImpaired = 2, + DVD_AUDIO_LANG_EXT_DirectorsComments1 = 3, + DVD_AUDIO_LANG_EXT_DirectorsComments2 = 4 +} DVDAudioLangExt_t; + +/* Subpicture language extension */ +typedef enum { + DVD_SUBPICTURE_LANG_EXT_NotSpecified = 0, + DVD_SUBPICTURE_LANG_EXT_NormalCaptions = 1, + DVD_SUBPICTURE_LANG_EXT_BigCaptions = 2, + DVD_SUBPICTURE_LANG_EXT_ChildrensCaptions = 3, + DVD_SUBPICTURE_LANG_EXT_NormalCC = 5, + DVD_SUBPICTURE_LANG_EXT_BigCC = 6, + DVD_SUBPICTURE_LANG_EXT_ChildrensCC = 7, + DVD_SUBPICTURE_LANG_EXT_Forced = 9, + DVD_SUBPICTURE_LANG_EXT_NormalDirectorsComments = 13, + DVD_SUBPICTURE_LANG_EXT_BigDirectorsComments = 14, + DVD_SUBPICTURE_LANG_EXT_ChildrensDirectorsComments = 15, +} DVDSubpictureLangExt_t; + +/* Karaoke Downmix mode */ +typedef enum { + DVD_KARAOKE_DOWNMIX_0to0 = 0x0001, + DVD_KARAOKE_DOWNMIX_1to0 = 0x0002, + DVD_KARAOKE_DOWNMIX_2to0 = 0x0004, + DVD_KARAOKE_DOWNMIX_3to0 = 0x0008, + DVD_KARAOKE_DOWNMIX_4to0 = 0x0010, + DVD_KARAOKE_DOWNMIX_Lto0 = 0x0020, + DVD_KARAOKE_DOWNMIX_Rto0 = 0x0040, + DVD_KARAOKE_DOWNMIX_0to1 = 0x0100, + DVD_KARAOKE_DOWNMIX_1to1 = 0x0200, + DVD_KARAOKE_DOWNMIX_2to1 = 0x0400, + DVD_KARAOKE_DOWNMIX_3to1 = 0x0800, + DVD_KARAOKE_DOWNMIX_4to1 = 0x1000, + DVD_KARAOKE_DOWNMIX_Lto1 = 0x2000, + DVD_KARAOKE_DOWNMIX_Rto1 = 0x4000 +} DVDKaraokeDownmix_t; +typedef int DVDKaraokeDownmixMask_t; + +/* Display mode */ +typedef enum { + DVD_DISPLAY_MODE_ContentDefault = 0, + DVD_DISPLAY_MODE_16x9 = 1, + DVD_DISPLAY_MODE_4x3PanScan = 2, + DVD_DISPLAY_MODE_4x3Letterboxed = 3 +} DVDDisplayMode_t; + +/* Audio attributes */ +typedef struct { + DVDAudioAppMode_t AppMode; + DVDAudioFormat_t AudioFormat; + DVDLangID_t Language; + DVDAudioLangExt_t LanguageExtension; + DVDBool_t HasMultichannelInfo; + DVDAudioSampleFreq_t SampleFrequency; + DVDAudioSampleQuant_t SampleQuantization; + DVDChannelNumber_t NumberOfChannels; +} DVDAudioAttributes_t; +typedef int DVDAudioSampleFreq_t; +typedef int DVDAudioSampleQuant_t; +typedef int DVDChannelNumber_t; + +/* Subpicture attributes */ +typedef enum { + DVD_SUBPICTURE_TYPE_NotSpecified = 0, + DVD_SUBPICTURE_TYPE_Language = 1, + DVD_SUBPICTURE_TYPE_Other = 2 +} DVDSubpictureType_t; +typedef enum { + DVD_SUBPICTURE_CODING_RunLength = 0, + DVD_SUBPICTURE_CODING_Extended = 1, + DVD_SUBPICTURE_CODING_Other = 2 +} DVDSubpictureCoding_t; +typedef struct { + DVDSubpictureType_t Type; + DVDSubpictureCoding_t CodingMode; + DVDLangID_t Language; + DVDSubpictureLangExt_t LanguageExtension; +} DVDSubpictureAttributes_t; + +/* Video attributes */ +typedef struct { + DVDBool_t PanscanPermitted; + DVDBool_t LetterboxPermitted; + int AspectX; + int AspectY; + int FrameRate; + int FrameHeight; + DVDVideoCompression_t Compression; + DVDBool_t Line21Field1InGop; + DVDBool_t Line21Field2InGop; + int more_to_come; +} DVDVideoAttributes_t; +typedef int DVDVideoCompression_t; + +#endif + +#endif /* DVD_H_INCLUDED */ diff --git a/libdvdnav/dvdnav.c b/libdvdnav/dvdnav.c new file mode 100644 index 0000000..00ffd7d --- /dev/null +++ b/libdvdnav/dvdnav.c @@ -0,0 +1,1093 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: dvdnav.c,v 1.34 2005/10/15 14:04:05 jcdutton Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* +#define LOG_DEBUG +*/ +#include "common.h" +#include "dvdnav_internal.h" +#include "read_cache.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> + +#include "remap.h" +#include "dvdread.h" + +static dvdnav_status_t dvdnav_clear(dvdnav_t * this) { + /* clear everything except file, vm, mutex, readahead */ + + if (this->file) DVDCloseFile(this->file); + this->file = NULL; + + memset(&this->pci,0,sizeof(this->pci)); + memset(&this->dsi,0,sizeof(this->dsi)); + this->last_cmd_nav_lbn = SRI_END_OF_CELL; + + /* Set initial values of flags */ + this->position_current.still = 0; + this->skip_still = 0; + this->sync_wait = 0; + this->sync_wait_skip = 0; + this->spu_clut_changed = 0; + this->started = 0; + + dvdnav_read_cache_clear(this->cache); + + return DVDNAV_STATUS_OK; +} + + +dvdnav_status_t dvdnav_open(dvdnav_t** dest, const char *path,dvd_reader_t *dvd) { + dvdnav_t *this; + struct timeval time; + + /* Create a new structure */ + fprintf(MSG_OUT, "libdvdnav: Using dvdnav version %s from http://xine.sf.net\n", VERSION); + + (*dest) = NULL; + this = (dvdnav_t*)malloc(sizeof(dvdnav_t)); + if(!this) + return DVDNAV_STATUS_ERR; + memset(this, 0, (sizeof(dvdnav_t) ) ); /* Make sure this structure is clean */ + + pthread_mutex_init(&this->vm_lock, NULL); + /* Initialise the error string */ + printerr(""); + + /* Initialise the VM */ + this->vm = vm_new_vm(); + if(!this->vm) { + printerr("Error initialising the DVD VM."); + pthread_mutex_destroy(&this->vm_lock); + free(this); + return DVDNAV_STATUS_ERR; + } + this->vm->openedDvd=dvd; + if(!vm_reset(this->vm, path)) { + printerr("Error starting the VM / opening the DVD device."); + pthread_mutex_destroy(&this->vm_lock); + vm_free_vm(this->vm); + free(this); + return DVDNAV_STATUS_ERR; + } + + /* Set the path. FIXME: Is a deep copy 'right' */ + strncpy(this->path, path, MAX_PATH_LEN); + + /* Pre-open and close a file so that the CSS-keys are cached. */ + this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), 0, DVD_READ_MENU_VOBS); + + /* Start the read-ahead cache. */ + this->cache = dvdnav_read_cache_new(this); + + /* Seed the random numbers. So that the DVD VM Command rand() + * gives a different start value each time a DVD is played. */ + gettimeofday(&time, NULL); + srand(time.tv_usec); + + dvdnav_clear(this); + + (*dest) = this; + return DVDNAV_STATUS_OK; +} + + + +dvdnav_status_t dvdnav_close(dvdnav_t *this) { + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: close:called\n"); +#endif + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + if (this->file) { + DVDCloseFile(this->file); +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: close:file closing\n"); +#endif + this->file = NULL; + } + + /* Free the VM */ + if(this->vm) + vm_free_vm(this->vm); + + pthread_mutex_destroy(&this->vm_lock); + + /* We leave the final freeing of the entire structure to the cache, + * because we don't know, if there are still buffers out in the wild, + * that must return first. */ + if(this->cache) + dvdnav_read_cache_free(this->cache); + else + free(this); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_reset(dvdnav_t *this) { + dvdnav_status_t result; + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: reset:called\n"); +#endif + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: reseting vm\n"); +#endif + if(!vm_reset(this->vm, NULL)) { + printerr("Error restarting the VM."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: clearing dvdnav\n"); +#endif + result = dvdnav_clear(this); + + pthread_mutex_unlock(&this->vm_lock); + return result; +} + +dvdnav_status_t dvdnav_path(dvdnav_t *this, const char** path) { + + if(!this || !path) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + (*path) = this->path; + + return DVDNAV_STATUS_OK; +} + +const char* dvdnav_err_to_string(dvdnav_t *this) { + + if(!this) + return "Hey! You gave me a NULL pointer you naughty person!"; + + return this->err_str; +} + +/* converts a dvd_time_t to PTS ticks */ +int64_t dvdnav_convert_time(dvd_time_t *time) { + int64_t result; + int64_t frames; + + result = (time->hour >> 4 ) * 10 * 60 * 60 * 90000; + result += (time->hour & 0x0f) * 60 * 60 * 90000; + result += (time->minute >> 4 ) * 10 * 60 * 90000; + result += (time->minute & 0x0f) * 60 * 90000; + result += (time->second >> 4 ) * 10 * 90000; + result += (time->second & 0x0f) * 90000; + + frames = ((time->frame_u & 0x30) >> 4) * 10; + frames += ((time->frame_u & 0x0f) ) ; + + if (time->frame_u & 0x80) + result += frames * 3000; + else + result += frames * 3600; + + return result; +} + +/* + * Returns 1 if block contains NAV packet, 0 otherwise. + * Processes said NAV packet if present. + * + * Most of the code in here is copied from xine's MPEG demuxer + * so any bugs which are found in that should be corrected here also. + */ +static int32_t dvdnav_decode_packet(dvdnav_t *this, uint8_t *p, dsi_t *nav_dsi, pci_t *nav_pci) { + int32_t bMpeg1 = 0; + uint32_t nHeaderLen; + uint32_t nPacketLen; + uint32_t nStreamID; + + if (p[3] == 0xBA) { /* program stream pack header */ + int32_t nStuffingBytes; + + bMpeg1 = (p[4] & 0x40) == 0; + + if (bMpeg1) { + p += 12; + } else { /* mpeg2 */ + nStuffingBytes = p[0xD] & 0x07; + p += 14 + nStuffingBytes; + } + } + + if (p[3] == 0xbb) { /* program stream system header */ + nHeaderLen = (p[4] << 8) | p[5]; + p += 6 + nHeaderLen; + } + + /* we should now have a PES packet here */ + if (p[0] || p[1] || (p[2] != 1)) { + fprintf(MSG_OUT, "libdvdnav: demux error! %02x %02x %02x (should be 0x000001) \n",p[0],p[1],p[2]); + return 0; + } + + nPacketLen = p[4] << 8 | p[5]; + nStreamID = p[3]; + + nHeaderLen = 6; + p += nHeaderLen; + + if (nStreamID == 0xbf) { /* Private stream 2 */ +#if 0 + int32_t i; + fprintf(MSG_OUT, "libdvdnav: nav packet=%u\n",p-p_start-6); + for(i=0;i<80;i++) + fprintf(MSG_OUT, "%02x ",p[i-6]); + fprintf(MSG_OUT, "\n"); +#endif + + if(p[0] == 0x00) { + navRead_PCI(nav_pci, p+1); + } + + p += nPacketLen; + + /* We should now have a DSI packet. */ + if(p[6] == 0x01) { + nPacketLen = p[4] << 8 | p[5]; + p += 6; + navRead_DSI(nav_dsi, p+1); + } + + return 1; + } + return 0; +} + +/* DSI is used for most angle stuff. + * PCI is used for only non-seemless angle stuff + */ +static int32_t dvdnav_get_vobu(dvdnav_t *this, dsi_t *nav_dsi, pci_t *nav_pci, dvdnav_vobu_t *vobu) { + uint32_t next; + int32_t angle, num_angle; + + vobu->vobu_start = nav_dsi->dsi_gi.nv_pck_lbn; /* Absolute offset from start of disk */ + vobu->vobu_length = nav_dsi->dsi_gi.vobu_ea; /* Relative offset from vobu_start */ + + /* + * If we're not at the end of this cell, we can determine the next + * VOBU to display using the VOBU_SRI information section of the + * DSI. Using this value correctly follows the current angle, + * avoiding the doubled scenes in The Matrix, and makes our life + * really happy. + * + * vobu_next is an offset value, 0x3fffffff = SRI_END_OF_CELL + * DVDs are about 6 Gigs, which is only up to 0x300000 blocks + * Should really assert if bit 31 != 1 + */ + +#if 0 + /* Old code -- may still be useful one day */ + if(nav_dsi->vobu_sri.next_vobu != SRI_END_OF_CELL ) { + vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff ); + } else { + vobu->vobu_next = vobu->vobu_length; + } +#else + /* Relative offset from vobu_start */ + vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff ); +#endif + + vm_get_angle_info(this->vm, &angle, &num_angle); + + /* FIMXE: The angle reset doesn't work for some reason for the moment */ +#if 0 + if((num_angle < angle) && (angle != 1)) { + fprintf(MSG_OUT, "libdvdnav: angle ends!\n"); + + /* This is to switch back to angle one when we + * finish with angles. */ + dvdnav_angle_change(this, 1); + } +#endif + + if(num_angle != 0) { + + if((next = nav_pci->nsml_agli.nsml_agl_dsta[angle-1]) != 0) { + if((next & 0x3fffffff) != 0) { + if(next & 0x80000000) + vobu->vobu_next = - (int32_t)(next & 0x3fffffff); + else + vobu->vobu_next = + (int32_t)(next & 0x3fffffff); + } + } else if((next = nav_dsi->sml_agli.data[angle-1].address) != 0) { + vobu->vobu_length = nav_dsi->sml_pbi.ilvu_ea; + + if((next & 0x80000000) && (next != 0x7fffffff)) + vobu->vobu_next = - (int32_t)(next & 0x3fffffff); + else + vobu->vobu_next = + (int32_t)(next & 0x3fffffff); + } + } + + return 1; +} + +/* + * These are the main get_next_block function which actually get the media stream video and audio etc. + * + * There are two versions: The second one is using the zero-copy read ahead cache and therefore + * hands out pointers targetting directly into the cache. + * The first one uses a memcopy to fill this cache block into the application provided memory. + * The benefit of this first one is that no special memory management is needed. The application is + * the only one responsible of allocating and freeing the memory associated with the pointer. + * The drawback is the additional memcopy. + */ + +dvdnav_status_t dvdnav_get_next_block(dvdnav_t *this, uint8_t *buf, + int32_t *event, int32_t *len) { + unsigned char *block; + dvdnav_status_t status; + + block = buf; + status = dvdnav_get_next_cache_block(this, &block, event, len); + if (status == DVDNAV_STATUS_OK && block != buf) { + /* we received a block from the cache, copy it, so we can give it back */ + memcpy(buf, block, DVD_VIDEO_LB_LEN); + dvdnav_free_cache_block(this, block); + } + return status; +} + +dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *this, uint8_t **buf, + int32_t *event, int32_t *len) { + dvd_state_t *state; + int32_t result; + + if(!this || !event || !len || !buf || !*buf) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + + if(!this->started) { + /* Start the VM */ + if (!vm_start(this->vm)) { + printerr("Encrypted or faulty DVD"); + return DVDNAV_STATUS_ERR; + } + this->started = 1; + } + + state = &(this->vm->state); + (*event) = DVDNAV_NOP; + (*len) = 0; + + /* Check the STOP flag */ + if(this->vm->stopped) { + vm_stop(this->vm); + (*event) = DVDNAV_STOP; + this->started = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + vm_position_get(this->vm, &this->position_next); + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: POS-NEXT "); + vm_position_print(this->vm, &this->position_next); + fprintf(MSG_OUT, "libdvdnav: POS-CUR "); + vm_position_print(this->vm, &this->position_current); +#endif + + /* did we hop? */ + if(this->position_current.hop_channel != this->position_next.hop_channel) { + (*event) = DVDNAV_HOP_CHANNEL; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: HOP_CHANNEL\n"); +#endif + if (this->position_next.hop_channel - this->position_current.hop_channel >= HOP_SEEK) { + int32_t num_angles = 0, current; + + /* we seeked -> check for multiple angles */ + vm_get_angle_info(this->vm, ¤t, &num_angles); + if (num_angles > 1) { + int32_t result, block; + /* we have to skip the first VOBU when seeking in a multiangle feature, + * because it might belong to the wrong angle */ + block = this->position_next.cell_start + this->position_next.block; + result = dvdnav_read_cache_block(this->cache, block, 1, buf); + if(result <= 0) { + printerr("Error reading NAV packet."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + /* Decode nav into pci and dsi. Then get next VOBU info. */ + if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) { + printerr("Expected NAV packet but none found."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu); + /* skip to next, if there is a next */ + if (this->vobu.vobu_next != SRI_END_OF_CELL) { + this->vobu.vobu_start += this->vobu.vobu_next; + this->vobu.vobu_next = 0; + } + /* update VM state */ + this->vm->state.blockN = this->vobu.vobu_start - this->position_next.cell_start; + } + } + this->position_current.hop_channel = this->position_next.hop_channel; + /* update VOBU info */ + this->vobu.vobu_start = this->position_next.cell_start + this->position_next.block; + this->vobu.vobu_next = 0; + /* Make blockN == vobu_length to do expected_nav */ + this->vobu.vobu_length = 0; + this->vobu.blockN = 0; + this->sync_wait = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Check the HIGHLIGHT flag */ + if(this->position_current.button != this->position_next.button) { + dvdnav_highlight_event_t *hevent = (dvdnav_highlight_event_t *)*buf; + + (*event) = DVDNAV_HIGHLIGHT; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: HIGHLIGHT\n"); +#endif + (*len) = sizeof(dvdnav_highlight_event_t); + hevent->display = 1; + hevent->buttonN = this->position_next.button; + this->position_current.button = this->position_next.button; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Check the WAIT flag */ + if(this->sync_wait) { + (*event) = DVDNAV_WAIT; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: WAIT\n"); +#endif + (*len) = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Check to see if we need to change the currently opened VOB */ + if((this->position_current.vts != this->position_next.vts) || + (this->position_current.domain != this->position_next.domain)) { + dvd_read_domain_t domain; + int32_t vtsN; + dvdnav_vts_change_event_t *vts_event = (dvdnav_vts_change_event_t *)*buf; + + if(this->file) { + DVDCloseFile(this->file); + this->file = NULL; + } + + vts_event->old_vtsN = this->position_current.vts; + vts_event->old_domain = this->position_current.domain; + + /* Use the DOMAIN to find whether to open menu or title VOBs */ + switch(this->position_next.domain) { + case FP_DOMAIN: + case VMGM_DOMAIN: + domain = DVD_READ_MENU_VOBS; + vtsN = 0; + break; + case VTSM_DOMAIN: + domain = DVD_READ_MENU_VOBS; + vtsN = this->position_next.vts; + break; + case VTS_DOMAIN: + domain = DVD_READ_TITLE_VOBS; + vtsN = this->position_next.vts; + break; + default: + printerr("Unknown domain when changing VTS."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + this->position_current.vts = this->position_next.vts; + this->position_current.domain = this->position_next.domain; + dvdnav_read_cache_clear(this->cache); + this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), vtsN, domain); + vts_event->new_vtsN = this->position_next.vts; + vts_event->new_domain = this->position_next.domain; + + /* If couldn't open the file for some reason, moan */ + if(this->file == NULL) { + printerrf("Error opening vtsN=%i, domain=%i.", vtsN, domain); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + /* File opened successfully so return a VTS change event */ + (*event) = DVDNAV_VTS_CHANGE; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: VTS_CHANGE\n"); +#endif + (*len) = sizeof(dvdnav_vts_change_event_t); + + this->spu_clut_changed = 1; + this->position_current.cell = -1; /* Force an update */ + this->position_current.spu_channel = -1; /* Force an update */ + this->position_current.audio_channel = -1; /* Force an update */; + + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Check if the cell changed */ + if( (this->position_current.cell != this->position_next.cell) || + (this->position_current.cell_restart != this->position_next.cell_restart) || + (this->position_current.cell_start != this->position_next.cell_start) ) { + dvdnav_cell_change_event_t *cell_event = (dvdnav_cell_change_event_t *)*buf; + int32_t first_cell_nr, last_cell_nr, i; + dvd_state_t *state = &this->vm->state; + + (*event) = DVDNAV_CELL_CHANGE; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: CELL_CHANGE\n"); +#endif + (*len) = sizeof(dvdnav_cell_change_event_t); + + cell_event->cellN = state->cellN; + cell_event->pgN = state->pgN; + cell_event->cell_length = + dvdnav_convert_time(&state->pgc->cell_playback[state->cellN-1].playback_time); + + cell_event->pg_length = 0; + /* Find start cell of program. */ + first_cell_nr = state->pgc->program_map[state->pgN-1]; + /* Find end cell of program */ + if(state->pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[state->pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + for (i = first_cell_nr; i <= last_cell_nr; i++) + cell_event->pg_length += + dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time); + cell_event->pgc_length = dvdnav_convert_time(&state->pgc->playback_time); + + cell_event->cell_start = 0; + for (i = 1; i < state->cellN; i++) + cell_event->cell_start += + dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time); + + cell_event->pg_start = 0; + for (i = 1; i < state->pgc->program_map[state->pgN-1]; i++) + cell_event->pg_start += + dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time); + + this->position_current.cell = this->position_next.cell; + this->position_current.cell_restart = this->position_next.cell_restart; + this->position_current.cell_start = this->position_next.cell_start; + this->position_current.block = this->position_next.block; + + /* vobu info is used for mid cell resumes */ + this->vobu.vobu_start = this->position_next.cell_start + this->position_next.block; + this->vobu.vobu_next = 0; + /* Make blockN == vobu_length to do expected_nav */ + this->vobu.vobu_length = 0; + this->vobu.blockN = 0; + + /* update the spu palette at least on PGC changes */ + this->spu_clut_changed = 1; + this->position_current.spu_channel = -1; /* Force an update */ + this->position_current.audio_channel = -1; /* Force an update */ + + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* has the CLUT changed? */ + if(this->spu_clut_changed) { + (*event) = DVDNAV_SPU_CLUT_CHANGE; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: SPU_CLUT_CHANGE\n"); +#endif + (*len) = 16 * sizeof(uint32_t); + memcpy(*buf, &(state->pgc->palette), 16 * sizeof(uint32_t)); + this->spu_clut_changed = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* has the SPU channel changed? */ + if(this->position_current.spu_channel != this->position_next.spu_channel) { + dvdnav_spu_stream_change_event_t *stream_change = (dvdnav_spu_stream_change_event_t *)*buf; + + (*event) = DVDNAV_SPU_STREAM_CHANGE; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE\n"); +#endif + (*len) = sizeof(dvdnav_spu_stream_change_event_t); + stream_change->physical_wide = vm_get_subp_active_stream(this->vm, 0); + stream_change->physical_letterbox = vm_get_subp_active_stream(this->vm, 1); + stream_change->physical_pan_scan = vm_get_subp_active_stream(this->vm, 2); + this->position_current.spu_channel = this->position_next.spu_channel; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_wide=%d\n",stream_change->physical_wide); + fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_letterbox=%d\n",stream_change->physical_letterbox); + fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE stream_id_pan_scan=%d\n",stream_change->physical_pan_scan); + fprintf(MSG_OUT, "libdvdnav: SPU_STREAM_CHANGE returning DVDNAV_STATUS_OK\n"); +#endif + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* has the audio channel changed? */ + if(this->position_current.audio_channel != this->position_next.audio_channel) { + dvdnav_audio_stream_change_event_t *stream_change = (dvdnav_audio_stream_change_event_t *)*buf; + + (*event) = DVDNAV_AUDIO_STREAM_CHANGE; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: AUDIO_STREAM_CHANGE\n"); +#endif + (*len) = sizeof(dvdnav_audio_stream_change_event_t); + stream_change->physical = vm_get_audio_active_stream( this->vm ); + this->position_current.audio_channel = this->position_next.audio_channel; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: AUDIO_STREAM_CHANGE stream_id=%d returning DVDNAV_STATUS_OK\n",stream_change->physical); +#endif + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Check the STILLFRAME flag */ + if(this->position_current.still != 0) { + dvdnav_still_event_t *still_event = (dvdnav_still_event_t *)*buf; + + (*event) = DVDNAV_STILL_FRAME; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: STILL_FRAME\n"); +#endif + (*len) = sizeof(dvdnav_still_event_t); + still_event->length = this->position_current.still; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Have we reached the end of a VOBU? */ + if (this->vobu.blockN >= this->vobu.vobu_length) { + + /* Have we reached the end of a cell? */ + if(this->vobu.vobu_next == SRI_END_OF_CELL) { + /* End of Cell from NAV DSI info */ +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: Still set to %x\n", this->position_next.still); +#endif + this->position_current.still = this->position_next.still; + + /* we are about to leave a cell, so a lot of state changes could occur; + * under certain conditions, the application should get in sync with us before this, + * otherwise it might show stills or menus too shortly */ + if ((this->position_current.still || this->pci.hli.hl_gi.hli_ss) && !this->sync_wait_skip) { + this->sync_wait = 1; + } else { + if( this->position_current.still == 0 || this->skip_still ) { + /* no active cell still -> get us to the next cell */ + vm_get_next_cell(this->vm); + this->position_current.still = 0; /* still gets activated at end of cell */ + this->skip_still = 0; + this->sync_wait_skip = 0; + } + } + /* handle related state changes in next iteration */ + (*event) = DVDNAV_NOP; + (*len) = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* Perform remapping jump if necessary (this is always a + * VOBU boundary). */ + if (this->vm->map) { + this->vobu.vobu_next = remap_block( this->vm->map, + this->vm->state.domain, this->vm->state.TTN_REG, + this->vm->state.pgN, + this->vobu.vobu_start, this->vobu.vobu_next); + } + + /* at the start of the next VOBU -> expecting NAV packet */ + result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.vobu_next, 1, buf); + + if(result <= 0) { + printerr("Error reading NAV packet."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + /* Decode nav into pci and dsi. Then get next VOBU info. */ + if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) { + printerr("Expected NAV packet but none found."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + /* We need to update the vm state->blockN with which VOBU we are in. + * This is so RSM resumes to the VOBU level and not just the CELL level. + */ + this->vm->state.blockN = this->vobu.vobu_start - this->position_current.cell_start; + + dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu); + this->vobu.blockN = 0; + /* Give the cache a hint about the size of next VOBU. + * This improves pre-caching, because the VOBU will almost certainly be read entirely. + */ + dvdnav_pre_cache_blocks(this->cache, this->vobu.vobu_start+1, this->vobu.vobu_length+1); + + /* release NAV menu filter, when we reach the same NAV packet again */ + if (this->last_cmd_nav_lbn == this->pci.pci_gi.nv_pck_lbn) + this->last_cmd_nav_lbn = SRI_END_OF_CELL; + + /* Successfully got a NAV packet */ + (*event) = DVDNAV_NAV_PACKET; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: NAV_PACKET\n"); +#endif + (*len) = 2048; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + + /* If we've got here, it must just be a normal block. */ + if(!this->file) { + printerr("Attempting to read without opening file."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + this->vobu.blockN++; + result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.blockN, 1, buf); + if(result <= 0) { + printerr("Error reading from DVD."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + (*event) = DVDNAV_BLOCK_OK; + (*len) = 2048; + + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_title_string(dvdnav_t *this, const char **title_str) { + + if(!this || !title_str) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + (*title_str) = this->vm->dvd_name; + return DVDNAV_STATUS_OK; +} + +uint8_t dvdnav_get_video_aspect(dvdnav_t *this) { + uint8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + retval = (uint8_t)vm_get_video_aspect(this->vm); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +uint8_t dvdnav_get_video_scale_permission(dvdnav_t *this) { + uint8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + retval = (uint8_t)vm_get_video_scale_permission(this->vm); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *this, uint8_t stream) { + audio_attr_t attr; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + attr = vm_get_audio_attr(this->vm, stream); + pthread_mutex_unlock(&this->vm_lock); + + if(attr.lang_type != 1) + return 0xffff; + + return attr.lang_code; +} + +uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *this, uint8_t stream) { + subp_attr_t attr; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + attr = vm_get_subp_attr(this->vm, stream); + pthread_mutex_unlock(&this->vm_lock); + + if(attr.type != 1) + return 0xffff; + + return attr.lang_code; +} + +int8_t dvdnav_get_audio_logical_stream(dvdnav_t *this, uint8_t audio_num) { + int8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return -1; + } + retval = vm_get_audio_stream(this->vm, audio_num); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +int8_t dvdnav_get_spu_logical_stream(dvdnav_t *this, uint8_t subp_num) { + int8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return -1; + } + retval = vm_get_subp_stream(this->vm, subp_num, 0); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +int8_t dvdnav_get_active_audio_stream(dvdnav_t *this) { + int8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return -1; + } + retval = vm_get_audio_active_stream(this->vm); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +int8_t dvdnav_get_active_spu_stream(dvdnav_t *this) { + int8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return -1; + } + retval = vm_get_subp_active_stream(this->vm, 0); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +static int8_t dvdnav_is_domain(dvdnav_t *this, domain_t domain) { + int8_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return -1; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&this->vm_lock); + retval = (this->vm->state.domain == domain); + pthread_mutex_unlock(&this->vm_lock); + + return retval; +} + +/* First Play domain. (Menu) */ +int8_t dvdnav_is_domain_fp(dvdnav_t *this) { + return dvdnav_is_domain(this, FP_DOMAIN); +} +/* Video management Menu domain. (Menu) */ +int8_t dvdnav_is_domain_vmgm(dvdnav_t *this) { + return dvdnav_is_domain(this, VMGM_DOMAIN); +} +/* Video Title Menu domain (Menu) */ +int8_t dvdnav_is_domain_vtsm(dvdnav_t *this) { + return dvdnav_is_domain(this, VTSM_DOMAIN); +} +/* Video Title domain (playing movie). */ +int8_t dvdnav_is_domain_vts(dvdnav_t *this) { + return dvdnav_is_domain(this, VTS_DOMAIN); +} + +/* Generally delegate angle information handling to VM */ +dvdnav_status_t dvdnav_angle_change(dvdnav_t *this, int32_t angle) { + int32_t num, current; + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + vm_get_angle_info(this->vm, ¤t, &num); + /* Set angle SPRM if valid */ + if((angle > 0) && (angle <= num)) { + this->vm->state.AGL_REG = angle; + } else { + printerr("Passed an invalid angle number."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *this, int32_t *current_angle, + int32_t *number_of_angles) { + if(!this || !current_angle || !number_of_angles) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + vm_get_angle_info(this->vm, current_angle, number_of_angles); + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +pci_t* dvdnav_get_current_nav_pci(dvdnav_t *this) { + if(!this) return 0; + return &this->pci; +} + +dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *this) { + if(!this) return 0; + return &this->dsi; +} + +uint32_t dvdnav_get_next_still_flag(dvdnav_t *this) { + if(!this) return -1; + return this->position_next.still; +} + diff --git a/libdvdnav/dvdnav.h b/libdvdnav/dvdnav.h new file mode 100644 index 0000000..1e39d0b --- /dev/null +++ b/libdvdnav/dvdnav.h @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2001 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: dvdnav.h,v 1.15 2005/10/14 21:02:16 miguelfreitas Exp $ + * + */ + +/* + * This is the main header file applications should include if they want + * to access dvdnav functionality. + */ + +#ifndef DVDNAV_H_INCLUDED +#define DVDNAV_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common.h" +# include "dvdnav_events.h" +# include "dvd_types.h" + + + + +/********************************************************************* + * dvdnav data types * + *********************************************************************/ + +/* + * Opaque data-type can be viewed as a 'DVD handle'. You should get + * a pointer to a dvdnav_t from the dvdnav_open() function. + * Never call free() on the pointer, you have to give it back with + * dvdnav_close(). + */ +typedef struct dvdnav_s dvdnav_t; + +/* Status as reported by most of libdvdnav's functions */ +typedef int32_t dvdnav_status_t; + +/* + * Unless otherwise stated, all functions return DVDNAV_STATUS_OK if + * they succeeded, otherwise DVDNAV_STATUS_ERR is returned and the error may + * be obtained by calling dvdnav_err_to_string(). + */ +#define DVDNAV_STATUS_ERR 0 +#define DVDNAV_STATUS_OK 1 + + +/********************************************************************* + * initialisation & housekeeping functions * + *********************************************************************/ + +/* + * These functions allow you to open a DVD device and associate it + * with a dvdnav_t. + */ + +/* + * Attempts to open the DVD drive at the specified path and pre-cache + * the CSS-keys. libdvdread is used to access the DVD, so any source + * supported by libdvdread can be given with "path". Currently, + * libdvdread can access: DVD drives, DVD image files, DVD file-by-file + * copies. + * + * The resulting dvdnav_t handle will be written to *dest. + */ +dvdnav_status_t dvdnav_open(dvdnav_t **dest, const char *path,dvd_reader_t *dvd); + +/* + * Closes a dvdnav_t previously opened with dvdnav_open(), freeing any + * memory associated with it. + */ +dvdnav_status_t dvdnav_close(dvdnav_t *self); + +/* + * Resets the DVD virtual machine and cache buffers. + */ +dvdnav_status_t dvdnav_reset(dvdnav_t *self); + +/* + * Fills a pointer with a value pointing to a string describing + * the path associated with an open dvdnav_t. It assigns *path to NULL + * on error. + */ +dvdnav_status_t dvdnav_path(dvdnav_t *self, const char **path); + +/* + * Returns a human-readable string describing the last error. + */ +const char* dvdnav_err_to_string(dvdnav_t *self); + +/* converts a dvd_time_t to PTS ticks */ +int64_t dvdnav_convert_time(dvd_time_t *time); + +/********************************************************************* + * changing and reading DVD player characteristics * + *********************************************************************/ + +/* + * These functions allow you to manipulate the various global characteristics + * of the DVD playback engine. + */ + +/* + * Sets the region mask (bit 0 set implies region 1, bit 1 set implies + * region 2, etc) of the virtual machine. Generally you will only need to set + * this if you are playing RCE discs which query the virtual machine as to its + * region setting. + * + * This has _nothing_ to do with the region setting of the DVD drive. + */ +dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *self, int32_t region_mask); + +/* + * Returns the region mask (bit 0 set implies region 1, bit 1 set implies + * region 2, etc) of the virtual machine. + * + * This has _nothing_ to do with the region setting of the DVD drive. + */ +dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *self, int32_t *region_mask); + +/* + * Specify whether read-ahead caching should be used. You may not want this if your + * decoding engine does its own buffering. + * + * The default read-ahead cache does not use an additional thread for the reading + * (see read_cache.c for a threaded cache, but note that this code is currently + * unmaintained). It prebuffers on VOBU level by reading ahead several buffers + * on every read request. The speed of this prebuffering has been optimized to + * also work on slow DVD drives. + * + * If in addition you want to prevent memcpy's to improve performance, have a look + * at dvdnav_get_next_cache_block(). + */ +dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *self, int32_t read_ahead_flag); + +/* + * Query whether read-ahead caching/buffering will be used. + */ +dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *self, int32_t *read_ahead_flag); + +/* + * Specify whether the positioning works PGC or PG based. + * Programs (PGs) on DVDs are similar to Chapters and a program chain (PGC) + * usually covers a whole feature. This affects the behaviour of the + * functions dvdnav_get_position() and dvdnav_sector_search(). See there. + * Default is PG based positioning. + */ +dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *self, int32_t pgc_based_flag); + +/* + * Query whether positioning is PG or PGC based. + */ +dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *self, int32_t *pgc_based_flag); + + +/********************************************************************* + * reading data * + *********************************************************************/ + +/* + * These functions are used to poll the playback enginge and actually get data + * off the DVD. + */ + +/* + * Attempts to get the next block off the DVD and copies it into the buffer 'buf'. + * If there is any special actions that may need to be performed, the value + * pointed to by 'event' gets set accordingly. + * + * If 'event' is DVDNAV_BLOCK_OK then 'buf' is filled with the next block + * (note that means it has to be at /least/ 2048 bytes big). 'len' is + * then set to 2048. + * + * Otherwise, buf is filled with an appropriate event structure and + * len is set to the length of that structure. + * + * See the dvdnav_events.h header for information on the various events. + */ +dvdnav_status_t dvdnav_get_next_block(dvdnav_t *self, uint8_t *buf, + int32_t *event, int32_t *len); + +/* + * This basically does the same as dvdnav_get_next_block. The only difference is + * that it avoids a memcopy, when the requested block was found in the cache. + * I such a case (cache hit) this function will return a different pointer than + * the one handed in, pointing directly into the relevant block in the cache. + * Those pointers must _never_ be freed but instead returned to the library via + * dvdnav_free_cache_block(). + */ +dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *self, uint8_t **buf, + int32_t *event, int32_t *len); + +/* + * All buffers which came from the internal cache (when dvdnav_get_next_cache_block() + * returned a buffer different from the one handed in) have to be freed with this + * function. Although handing in other buffers not from the cache doesn't cause any harm. + */ +dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf); + +/* + * If we are currently in a still-frame this function skips it. + * + * See also the DVDNAV_STILL_FRAME event. + */ +dvdnav_status_t dvdnav_still_skip(dvdnav_t *self); + +/* + * If we are currently in WAIT state, that is: the application is required to + * wait for its fifos to become empty, calling this signals libdvdnav that this + * is achieved and that it can continue. + * + * See also the DVDNAV_WAIT event. + */ +dvdnav_status_t dvdnav_wait_skip(dvdnav_t *self); + +/* + * Returns the still time from the currently playing cell. + * The still time is given in seconds with 0xff meaning an indefinite still. + * + * This function can be used to detect still frames before they are reached. + * Some players might need this to prepare for a frame to be shown for a + * longer time than usual. + */ +uint32_t dvdnav_get_next_still_flag(dvdnav_t *self); + +/* + * Stops playback. The next event obtained with one of the get_next_block + * functions will be a DVDNAV_STOP event. + * + * It is not required to call this before dvdnav_close(). + */ +dvdnav_status_t dvdnav_stop(dvdnav_t *self); + + +/********************************************************************* + * title/part navigation * + *********************************************************************/ + +/* + * Returns the number of titles on the disk. + */ +dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *self, int32_t *titles); + +/* + * Returns the number of parts within the given title. + */ +dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *self, int32_t title, int32_t *parts); + +/* + * Plays the specified title of the DVD from its beginning (that is: part 1). + */ +dvdnav_status_t dvdnav_title_play(dvdnav_t *self, int32_t title); + +/* + * Plays the specified title, starting from the specified part. + */ +dvdnav_status_t dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part); + +/* + * Play the specified amount of parts of the specified title of + * the DVD then STOP. + * + * Currently unimplemented! + */ +dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *self, int32_t title, + int32_t part, int32_t parts_to_play); + +/* + * Play the specified title starting from the specified time. + * + * Currently unimplemented! + */ +dvdnav_status_t dvdnav_time_play(dvdnav_t *self, int32_t title, + uint64_t time); + +/* + * Stop playing the current position and jump to the specified menu. + * + * See also DVDMenuID_t from libdvdread + */ +dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu); + +/* + * Return the title number and part currently being played. + * A title of 0 indicates, we are in a menu. In this case, part + * is set to the current menu's ID. + */ +dvdnav_status_t dvdnav_current_title_info(dvdnav_t *self, int32_t *title, + int32_t *part); + +/* + * Return the current position (in blocks) within the current + * title and the length (in blocks) of said title. + * + * Current implementation is wrong and likely to behave unpredictably! + * Use is discouraged! + */ +dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *self, + uint32_t *pos, + uint32_t *len); + +/* + * This function is only available for compatibility reasons. + * + * Stop playing the current position and start playback of the current title + * from the specified part. + */ +dvdnav_status_t dvdnav_part_search(dvdnav_t *self, int32_t part); + + +/********************************************************************* + * program chain/program navigation * + *********************************************************************/ + +/* + * Stop playing the current position and start playback from the last + * VOBU boundary before the given sector. The sector number is not + * meant to be an absolute physical DVD sector, but a relative sector + * in the current program. This function cannot leave the current + * program and will fail, if asked to do so. + * + * If program chain based positioning is enabled + * (see dvdnav_set_PGC_positioning_flag()), this will seek to the relative + * sector inside the current program chain. + * + * 'origin' can be one of SEEK_SET, SEEK_CUR, SEEK_END as defined in + * fcntl.h. + */ +dvdnav_status_t dvdnav_sector_search(dvdnav_t *self, + uint64_t offset, int32_t origin); + +/* + * Stop playing the current position and start playback of the title + * from the specified timecode. + * + * Currently unimplemented! + */ +dvdnav_status_t dvdnav_time_search(dvdnav_t *self, + uint64_t time); + +/* + * Stop playing current position and play the "GoUp"-program chain. + * (which generally leads to the title menu or a higer-level menu). + */ +dvdnav_status_t dvdnav_go_up(dvdnav_t *self); + +/* + * Stop playing the current position and start playback at the + * previous program (if it exists). + */ +dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *self); + +/* + * Stop playing the current position and start playback at the + * first program. + */ +dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *self); + +/* + * Stop playing the current position and start playback at the + * next program (if it exists). + */ +dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *self); + +/* + * Return the current position (in blocks) within the current + * program and the length (in blocks) of current program. + * + * If program chain based positioning is enabled + * (see dvdnav_set_PGC_positioning_flag()), this will return the + * relative position in and the length of the current program chain. + */ +dvdnav_status_t dvdnav_get_position(dvdnav_t *self, uint32_t *pos, + uint32_t *len); + + +/********************************************************************* + * menu highlights * + *********************************************************************/ + +/* + * Most functions related to highlights take a NAV PCI packet as a parameter. + * While you can get the such a packet from libdvdnav, for players with internal + * FIFOs, this will result in errors, because due to the FIFO length, libdvdnav will + * be ahead in the stream compared to what the user is seeing on screen. + * Therefore, player applications who have a NAV packet available, which is + * better in sync with the actual playback should always pass this one to these + * functions. + */ + +/* + * Get the currently highlighted button + * number (1..36) or 0 if no button is highlighted. + */ +dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *self, int32_t *button); + +/* + * Returns the Presentation Control Information (PCI) structure associated + * with the current position. + * + * Read the general notes above. + * See also libdvdreads nav_types.h for definition of pci_t. + */ +pci_t* dvdnav_get_current_nav_pci(dvdnav_t *self); + +/* + * Returns the DSI (data search information) structure associated + * with the current position. + * + * Read the general notes above. + * See also libdvdreads nav_types.h for definition of dsi_t. + */ +dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *self); + +/* + * Get the area associated with a certain button. + */ +dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode, + dvdnav_highlight_area_t *highlight); + +/* + * Move button highlight around as suggested by function name (e.g. with arrow keys). + */ +dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *self, pci_t *pci); +dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *self, pci_t *pci); +dvdnav_status_t dvdnav_right_button_select(dvdnav_t *self, pci_t *pci); +dvdnav_status_t dvdnav_left_button_select(dvdnav_t *self, pci_t *pci); + +/* + * Activate ("press") the currently highlighted button. + */ +dvdnav_status_t dvdnav_button_activate(dvdnav_t *self, pci_t *pci); + +/* + * Highlight a specific button. + */ +dvdnav_status_t dvdnav_button_select(dvdnav_t *self, pci_t *pci, int32_t button); + +/* + * Activate ("press") specified button. + */ +dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *self, pci_t *pci, int32_t button); + +/* + * Activate (press) a button and execute specified command. + */ +dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *self, int32_t button, vm_cmd_t *cmd); + +/* + * Select button at specified video frame coordinates. + */ +dvdnav_status_t dvdnav_mouse_select(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y); + +/* + * Activate ("press") button at specified video frame coordinates. + */ +dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *self, pci_t *pci, int32_t x, int32_t y); + + +/********************************************************************* + * languages * + *********************************************************************/ + +/* + * The language codes expected by these functions are two character + * codes as defined in ISO639. + */ + +/* + * Set which menu language we should use per default. + */ +dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *self, + char *code); + +/* + * Set which audio language we should use per default. + */ +dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *self, + char *code); + +/* + * Set which spu language we should use per default. + */ +dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *self, + char *code); + + +/********************************************************************* + * obtaining stream attributes * + *********************************************************************/ + +/* + * Return a string describing the title of the DVD. + * This is an ID string encoded on the disc by the author. In many cases + * this is a descriptive string such as `THE_MATRIX' but sometimes is sigularly + * uninformative such as `PDVD-011421'. Some DVD authors even forget to set this, + * so you may also read the default of the authoring software they used, like + * `DVDVolume'. + */ +dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, const char **title_str); + +/* + * Get video aspect code. + * The aspect code does only change on VTS boundaries. + * See the DVDNAV_VTS_CHANGE event. + * + * 0 -- 4:3, 2 -- 16:9 + */ +uint8_t dvdnav_get_video_aspect(dvdnav_t *self); + +/* + * Get video scaling permissions. + * The scaling permission does only change on VTS boundaries. + * See the DVDNAV_VTS_CHANGE event. + * + * bit0 set = deny letterboxing, bit1 set = deny pan&scan + */ +uint8_t dvdnav_get_video_scale_permission(dvdnav_t *self); + +/* + * Converts a *logical* audio stream id into language code + * (returns 0xffff if no such stream). + */ +uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *self, uint8_t stream); + +/* + * Converts a *logical* subpicture stream id into country code + * (returns 0xffff if no such stream). + */ +uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *self, uint8_t stream); + +/* + * Converts a *physical* (MPEG) audio stream id into a logical stream number. + */ +int8_t dvdnav_get_audio_logical_stream(dvdnav_t *self, uint8_t audio_num); + +/* + * Converts a *physical* (MPEG) subpicture stream id into a logical stream number. + */ +int8_t dvdnav_get_spu_logical_stream(dvdnav_t *self, uint8_t subp_num); + +/* + * Get active audio stream. + */ +int8_t dvdnav_get_active_audio_stream(dvdnav_t *self); + +/* + * Get active spu stream. + */ +int8_t dvdnav_get_active_spu_stream(dvdnav_t *self); + + +/********************************************************************* + * multiple angles * + *********************************************************************/ + +/* + * The libdvdnav library abstracts away the difference between seamless and + * non-seamless angles. From the point of view of the programmer you just set the + * angle number and all is well in the world. You will always see only the + * selected angle coming from the get_next_block functions. + * + * Note: + * It is quite possible that some tremendously strange DVD feature might change the + * angle number from under you. Generally you should always view the results from + * dvdnav_get_angle_info() as definitive only up to the next time you call + * dvdnav_get_next_block(). + */ + +/* + * Sets the current angle. If you try to follow a non existant angle + * the call fails. + */ +dvdnav_status_t dvdnav_angle_change(dvdnav_t *self, int32_t angle); + +/* + * Returns the current angle and number of angles present. + */ +dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *self, int32_t *current_angle, + int32_t *number_of_angles); + +/********************************************************************* + * domain queries * + *********************************************************************/ + +/* + * Are we in the First Play domain? + */ +int8_t dvdnav_is_domain_fp(dvdnav_t *self); + +/* + * Are we in the Video management Menu domain? + */ +int8_t dvdnav_is_domain_vmgm(dvdnav_t *self); + +/* + * Are we in the Video Title Menu domain? + */ +int8_t dvdnav_is_domain_vtsm(dvdnav_t *self); + +/* + * Are we in the Video Title Set domain? + */ +int8_t dvdnav_is_domain_vts(dvdnav_t *self); + + +#ifdef __cplusplus +} +#endif + +#endif /* DVDNAV_H_INCLUDED */ diff --git a/libdvdnav/dvdnav_events.h b/libdvdnav/dvdnav_events.h new file mode 100644 index 0000000..1623277 --- /dev/null +++ b/libdvdnav/dvdnav_events.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2001 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: dvdnav_events.h,v 1.11 2003/04/07 18:10:48 mroi Exp $ + * + */ + +/* + * This header defines events and event types + */ + +#ifndef DVDNAV_EVENTS_H_INCLUDED +#define DVDNAV_EVENTS_H_INCLUDED + +#include "common.h" + +/* + * DVDNAV_BLOCK_OK + * + * A regular data block from the DVD has been returned. + * This one should be demuxed and decoded for playback. + */ +#define DVDNAV_BLOCK_OK 0 + + +/* + * DVDNAV_NOP + * + * Just ignore this. + */ +#define DVDNAV_NOP 1 + + +/* + * DVDNAV_STILL_FRAME + * + * We have reached a still frame. The player application should wait + * the amount of time specified by the still's length while still handling + * user input to make menus and other interactive stills work. + * The last delivered frame should be kept showing. + * Once the still has timed out, call dvdnav_skip_still(). + * A length of 0xff means an infinite still which has to be skipped + * indirectly by some user interaction. + */ +#define DVDNAV_STILL_FRAME 2 + +typedef struct { + /* The length (in seconds) the still frame should be displayed for, + * or 0xff if infinite. */ + int length; +} dvdnav_still_event_t; + + +/* + * DVDNAV_SPU_STREAM_CHANGE + * + * Inform the SPU decoding/overlaying engine to switch SPU channels. + */ +#define DVDNAV_SPU_STREAM_CHANGE 3 + +typedef struct { + /* The physical (MPEG) stream number for widescreen SPU display. + * Use this, if you blend the SPU on an anamorphic image before + * unsqueezing it. */ + int physical_wide; + + /* The physical (MPEG) stream number for letterboxed display. + * Use this, if you blend the SPU on an anamorphic image after + * unsqueezing it. */ + int physical_letterbox; + + /* The physical (MPEG) stream number for pan&scan display. + * Use this, if you blend the SPU on an anamorphic image after + * unsqueezing it the pan&scan way. */ + int physical_pan_scan; + + /* The logical (DVD) stream number. */ + int logical; +} dvdnav_spu_stream_change_event_t; + + +/* + * DVDNAV_AUDIO_STREAM_CHANGE + * + * Inform the audio decoder to switch channels. + */ +#define DVDNAV_AUDIO_STREAM_CHANGE 4 + +typedef struct { + /* The physical (MPEG) stream number. */ + int physical; + + /* The logical (DVD) stream number. */ + int logical; +} dvdnav_audio_stream_change_event_t; + + +/* + * DVDNAV_VTS_CHANGE + * + * Some status information like video aspect and video scale permissions do + * not change inside a VTS. Therefore this event can be used to query such + * information only when necessary and update the decoding/displaying + * accordingly. + */ +#define DVDNAV_VTS_CHANGE 5 + +typedef struct { + int old_vtsN; /* the old VTS number */ + dvd_read_domain_t old_domain; /* the old domain */ + int new_vtsN; /* the new VTS number */ + dvd_read_domain_t new_domain; /* the new domain */ +} dvdnav_vts_change_event_t; + + +/* + * DVDNAV_CELL_CHANGE + * + * Some status information like the current Title and Part numbers do not + * change inside a cell. Therefore this event can be used to query such + * information only when necessary and update the decoding/displaying + * accordingly. + * Some useful information for accurate time display is also reported + * together with this event. + */ +#define DVDNAV_CELL_CHANGE 6 + +typedef struct { + int cellN; /* the new cell number */ + int pgN; /* the current program number */ + int64_t cell_length; /* the length of the current cell in PTS ticks */ + int64_t pg_length; /* the length of the current program in PTS ticks */ + int64_t pgc_length; /* the length of the current program chain in PTS ticks */ + int64_t cell_start; /* the start time of the current cell relatively to the PGC in PTS ticks */ + int64_t pg_start; /* the start time of the current PG relatively to the PGC in PTS ticks */ +} dvdnav_cell_change_event_t; + + +/* + * DVDNAV_NAV_PACKET + * + * NAV packets are useful for various purposes. They define the button + * highlight areas and VM commands of DVD menus, so they should in any + * case be sent to the SPU decoder/overlaying engine for the menus to work. + * NAV packets also provide a way to detect PTS discontinuities, because + * they carry the start and end PTS values for the current VOBU. + * (pci.vobu_s_ptm and pci.vobu_e_ptm) Whenever the start PTS of the + * current NAV does not match the end PTS of the previous NAV, a PTS + * discontinuity has occured. + * NAV packets can also be used for time display, because they are + * timestamped relatively to the current Cell. + */ +#define DVDNAV_NAV_PACKET 7 + + +/* + * DVDNAV_STOP + * + * Applications should end playback here. A subsequent dvdnav_get_next_block() + * call will restart the VM from the beginning of the DVD. + */ +#define DVDNAV_STOP 8 + + +/* + * DVDNAV_HIGHLIGHT + * + * The current button highlight changed. Inform the overlaying engine to + * highlight a different button. Please note, that at the moment only mode 1 + * highlights are reported this way. That means, when the button highlight + * has been moved around by some function call, you will receive an event + * telling you the new button. But when a button gets activated, you have + * to handle the mode 2 highlighting (that is some different colour the + * button turns to on activation) in your application. + */ +#define DVDNAV_HIGHLIGHT 9 + +typedef struct { + /* highlight mode: 0 - hide, 1 - show, 2 - activate, currently always 1 */ + int display; + + /* FIXME: these fields are currently not set */ + uint32_t palette; /* The CLUT entries for the highlight palette + (4-bits per entry -> 4 entries) */ + uint16_t sx,sy,ex,ey; /* The start/end x,y positions */ + uint32_t pts; /* Highlight PTS to match with SPU */ + + /* button number for the SPU decoder/overlaying engine */ + uint32_t buttonN; +} dvdnav_highlight_event_t; + + +/* + * DVDNAV_SPU_CLUT_CHANGE + * + * Inform the SPU decoder/overlaying engine to update its colour lookup table. + * The CLUT is given as 16 uint32_t's in the buffer. + */ +#define DVDNAV_SPU_CLUT_CHANGE 10 + + +/* + * DVDNAV_HOP_CHANNEL + * + * A non-seamless operation has been performed. Applications can drop all + * their internal fifo's content, which will speed up the response. + */ +#define DVDNAV_HOP_CHANNEL 12 + + +/* + * DVDNAV_WAIT + * + * We have reached a point in DVD playback, where timing is critical. + * Player application with internal fifos can introduce state + * inconsistencies, because libdvdnav is always the fifo's length + * ahead in the stream compared to what the application sees. + * Such applications should wait until their fifos are empty + * when they receive this type of event. + * Once this is achieved, call dvdnav_skip_wait(). + */ +#define DVDNAV_WAIT 13 + + +#endif /* DVDNAV_EVENTS_H_INCLUDED */ diff --git a/libdvdnav/dvdnav_internal.h b/libdvdnav/dvdnav_internal.h new file mode 100644 index 0000000..efa8708 --- /dev/null +++ b/libdvdnav/dvdnav_internal.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2001-2004 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: dvdnav_internal.h,v 1.15 2004/09/20 19:30:04 valtri Exp $ + * + */ + +#ifndef DVDNAV_INTERNAL_H_INCLUDED +#define DVDNAV_INTERNAL_H_INCLUDED + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "common.h" +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> + +#ifdef WIN32 + +/* pthread_mutex_* wrapper for win32 */ +#include <windows.h> +#include <process.h> +typedef CRITICAL_SECTION pthread_mutex_t; +#define pthread_mutex_init(a, b) InitializeCriticalSection(a) +#define pthread_mutex_lock(a) EnterCriticalSection(a) +#define pthread_mutex_unlock(a) LeaveCriticalSection(a) +#define pthread_mutex_destroy(a) + +/* replacement gettimeofday implementation */ +#include <sys/timeb.h> +static inline int _private_gettimeofday( struct timeval *tv, void *tz ) +{ + struct timeb t; + ftime( &t ); + tv->tv_sec = t.time; + tv->tv_usec = t.millitm * 1000; + return 0; +} +#define gettimeofday(TV, TZ) _private_gettimeofday((TV), (TZ)) +#include <io.h> /* read() */ +#define lseek64 _lseeki64 + +#else + +#include <pthread.h> + +#endif /* WIN32 */ + +/* Uncomment for VM command tracing */ +/* #define TRACE */ + +#include "decoder.h" +#include "dvdnav.h" +#include "vm.h" +#include "vmcmd.h" + +/* where should libdvdnav write its messages (stdout/stderr) */ +#define MSG_OUT stderr + +/* Maximum length of an error string */ +#define MAX_ERR_LEN 255 + +/* Use the POSIX PATH_MAX if available */ +#ifdef PATH_MAX +#define MAX_PATH_LEN PATH_MAX +#else +#define MAX_PATH_LEN 255 /* Arbitrary */ +#endif + +#ifndef DVD_VIDEO_LB_LEN +#define DVD_VIDEO_LB_LEN 2048 +#endif + +typedef struct read_cache_s read_cache_t; + +/* + * These are defined here because they are + * not in ifo_types.h, they maybe one day + */ + +#ifndef audio_status_t +typedef struct { +#ifdef WORDS_BIGENDIAN + unsigned int available : 1; + unsigned int zero1 : 4; + unsigned int stream_number : 3; + uint8_t zero2; +#else + uint8_t zero2; + unsigned int stream_number : 3; + unsigned int zero1 : 4; + unsigned int available : 1; +#endif +} ATTRIBUTE_PACKED audio_status_t; +#endif + +#ifndef spu_status_t +typedef struct { +#ifdef WORDS_BIGENDIAN + unsigned int available : 1; + unsigned int zero1 : 2; + unsigned int stream_number_4_3 : 5; + unsigned int zero2 : 3; + unsigned int stream_number_wide : 5; + unsigned int zero3 : 3; + unsigned int stream_number_letterbox : 5; + unsigned int zero4 : 3; + unsigned int stream_number_pan_scan : 5; +#else + unsigned int stream_number_pan_scan : 5; + unsigned int zero4 : 3; + unsigned int stream_number_letterbox : 5; + unsigned int zero3 : 3; + unsigned int stream_number_wide : 5; + unsigned int zero2 : 3; + unsigned int stream_number_4_3 : 5; + unsigned int zero1 : 2; + unsigned int available : 1; +#endif +} ATTRIBUTE_PACKED spu_status_t; +#endif + +typedef struct dvdnav_vobu_s { + int32_t vobu_start; /* Logical Absolute. MAX needed is 0x300000 */ + int32_t vobu_length; + int32_t blockN; /* Relative offset */ + int32_t vobu_next; /* Relative offset */ +} dvdnav_vobu_t; + +/** The main DVDNAV type **/ + +struct dvdnav_s { + /* General data */ + char path[MAX_PATH_LEN]; /* Path to DVD device/dir */ + dvd_file_t *file; /* Currently opened file */ + + /* Position data */ + vm_position_t position_next; + vm_position_t position_current; + dvdnav_vobu_t vobu; + + /* NAV data */ + pci_t pci; + dsi_t dsi; + uint32_t last_cmd_nav_lbn; /* detects when a command is issued on an already left NAV */ + + /* Flags */ + int skip_still; /* Set when skipping a still */ + int sync_wait; /* applications should wait till they are in sync with us */ + int sync_wait_skip; /* Set when skipping wait state */ + int spu_clut_changed; /* The SPU CLUT changed */ + int started; /* vm_start has been called? */ + int use_read_ahead; /* 1 - use read-ahead cache, 0 - don't */ + int pgc_based; /* positioning works PGC based instead of PG based */ + + /* VM */ + vm_t *vm; + pthread_mutex_t vm_lock; + + /* Read-ahead cache */ + read_cache_t *cache; + + /* Errors */ + char err_str[MAX_ERR_LEN]; +}; + +/** USEFUL MACROS **/ + +#ifdef __GNUC__ +#define printerrf(format, args...) snprintf(this->err_str, MAX_ERR_LEN, format, ## args); +#else +#ifdef _MSC_VER +#define printerrf(str) snprintf(this->err_str, MAX_ERR_LEN, str); +#else +#define printerrf(...) snprintf(this->err_str, MAX_ERR_LEN, __VA_ARGS__); +#endif /* WIN32 */ +#endif +#define printerr(str) strncpy(this->err_str, str, MAX_ERR_LEN); + +#endif /* DVDNAV_INTERNAL_H_INCLUDED */ diff --git a/libdvdnav/dvdread.c b/libdvdnav/dvdread.c new file mode 100644 index 0000000..9291ff6 --- /dev/null +++ b/libdvdnav/dvdread.c @@ -0,0 +1,74 @@ +#include "dvdread.h" + +#include <dlfcn.h> + +void *libHandle=0; + +DvdreadFunctions_t f_dvdread; + +void loadDvdread() { + libHandle=dlopen("libdvdread.so",RTLD_LAZY | RTLD_GLOBAL); + if (!libHandle) { + libHandle=dlopen("libdvdread.so.3",RTLD_LAZY | RTLD_GLOBAL); + } + if (!libHandle) { + libHandle=dlopen("libdvdread.so.2",RTLD_LAZY | RTLD_GLOBAL); + } + f_dvdread.DVDOpen=dlsym(libHandle,"DVDOpen"); + f_dvdread.DVDOpenFile=dlsym(libHandle,"DVDOpenFile"); + f_dvdread.DVDClose=dlsym(libHandle,"DVDClose"); + f_dvdread.DVDCloseFile=dlsym(libHandle,"DVDCloseFile"); + f_dvdread.DVDReadBlocks=dlsym(libHandle,"DVDReadBlocks"); + f_dvdread.DVDFileSeek=dlsym(libHandle,"DVDFileSeek"); + f_dvdread.DVDReadBytes=dlsym(libHandle,"DVDReadBytes"); + f_dvdread.DVDFileSize=dlsym(libHandle,"DVDFileSize"); + f_dvdread.DVDDiscID=dlsym(libHandle,"DVDDiscID"); + f_dvdread.DVDUDFVolumeInfo=dlsym(libHandle,"DVDUDFVolumeInfo"); + f_dvdread.DVDISOVolumeInfo=dlsym(libHandle,"DVDISOVolumeInfo"); + f_dvdread.DVDUDFCacheLevel=dlsym(libHandle,"DVDUDFCacheLevel"); + f_dvdread.navRead_PCI=dlsym(libHandle,"navRead_PCI"); + f_dvdread.navRead_DSI=dlsym(libHandle,"navRead_DSI"); + + + f_dvdread.ifoOpen=dlsym(libHandle,"ifoOpen"); + f_dvdread.ifoOpenVMGI=dlsym(libHandle,"ifoOpenVMGI"); + f_dvdread.ifoOpenVTSI=dlsym(libHandle,"ifoOpenVTSI"); + f_dvdread.ifoClose=dlsym(libHandle,"ifoClose"); + f_dvdread.ifoRead_PTL_MAIT=dlsym(libHandle,"ifoRead_PTL_MAIT"); + f_dvdread.ifoRead_VTS_ATRT=dlsym(libHandle,"ifoRead_VTS_ATRT"); + f_dvdread.ifoRead_TT_SRPT=dlsym(libHandle,"ifoRead_TT_SRPT"); + f_dvdread.ifoRead_VTS_PTT_SRPT=dlsym(libHandle,"ifoRead_VTS_PTT_SRPT"); + + f_dvdread.ifoRead_FP_PGC=dlsym(libHandle,"ifoRead_FP_PGC"); + f_dvdread.ifoRead_PGCIT=dlsym(libHandle,"ifoRead_PGCIT"); + f_dvdread.ifoRead_PGCI_UT=dlsym(libHandle,"ifoRead_PGCI_UT"); + f_dvdread.ifoRead_VTS_TMAPT=dlsym(libHandle,"ifoRead_VTS_TMAPT"); + f_dvdread.ifoRead_C_ADT=dlsym(libHandle,"ifoRead_C_ADT"); + f_dvdread.ifoRead_TITLE_C_ADT=dlsym(libHandle,"ifoRead_TITLE_C_ADT"); + f_dvdread.ifoRead_VOBU_ADMAP=dlsym(libHandle,"ifoRead_VOBU_ADMAP"); + f_dvdread.ifoRead_TITLE_VOBU_ADMAP=dlsym(libHandle,"ifoRead_TITLE_VOBU_ADMAP"); + f_dvdread.ifoRead_TXTDT_MGI=dlsym(libHandle,"ifoRead_TXTDT_MGI"); + + f_dvdread.ifoFree_PTL_MAIT=dlsym(libHandle,"ifoFree_PTL_MAIT"); + f_dvdread.ifoFree_VTS_ATRT=dlsym(libHandle,"ifoFree_VTS_ATRT"); + f_dvdread.ifoFree_TT_SRPT=dlsym(libHandle,"ifoFree_TT_SRPT"); + f_dvdread.ifoFree_VTS_PTT_SRPT=dlsym(libHandle,"ifoFree_VTS_PTT_SRPT"); + f_dvdread.ifoFree_FP_PGC=dlsym(libHandle,"ifoFree_FP_PGC"); + f_dvdread.ifoFree_PGCIT=dlsym(libHandle,"ifoFree_PGCIT"); + f_dvdread.ifoFree_PGCI_UT=dlsym(libHandle,"ifoFree_PGCI_UT"); + f_dvdread.ifoFree_VTS_TMAPT=dlsym(libHandle,"ifoFree_VTS_TMAPT"); + f_dvdread.ifoFree_C_ADT=dlsym(libHandle,"ifoFree_C_ADT"); + f_dvdread.ifoFree_TITLE_C_ADT=dlsym(libHandle,"ifoFree_TITLE_C_ADT"); + f_dvdread.ifoFree_VOBU_ADMAP=dlsym(libHandle,"ifoFree_VOBU_ADMAP"); + f_dvdread.ifoFree_TITLE_VOBU_ADMAP=dlsym(libHandle,"ifoFree_TITLE_VOBU_ADMAP"); + f_dvdread.ifoFree_TXTDT_MGI=dlsym(libHandle,"ifoFree_TXTDT_MGI"); + +} + +DvdreadFunctions_t *DvdreadF(void) { + return &f_dvdread; +} + +void unloadDvdread() { + dlclose(libHandle); +} diff --git a/libdvdnav/dvdread.h b/libdvdnav/dvdread.h new file mode 100644 index 0000000..8150385 --- /dev/null +++ b/libdvdnav/dvdread.h @@ -0,0 +1,28 @@ +/* +// C++ Interface: dvdread +// +// Description: +// +// +// Author: Jean-Michel PETIT <[email protected]>, (C) 2006 +// +// Copyright: See COPYING file that comes with this distribution +// +// +*/ +#ifndef DVDREAD_H_INCLUDED +#define DVDREAD_H_INCLUDED + + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common.h" + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/libdvdnav/highlight.c b/libdvdnav/highlight.c new file mode 100644 index 0000000..0335c36 --- /dev/null +++ b/libdvdnav/highlight.c @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: highlight.c,v 1.17 2004/03/16 11:43:38 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "common.h" +#include <assert.h> +#include "dvdnav_internal.h" + +/* +#define BUTTON_TESTING +*/ + +#ifdef BUTTON_TESTING + +#include "nav_print.h" + +static void print_time(dvd_time_t *dtime) { + const char *rate; + + assert((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa); + assert((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa); + assert((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa); + assert((dtime->frame_u&0xf) < 0xa); + + fprintf(MSG_OUT,"%02x:%02x:%02x.%02x", + dtime->hour, + dtime->minute, + dtime->second, + dtime->frame_u & 0x3f); + switch((dtime->frame_u & 0xc0) >> 6) { + case 1: + rate = "25.00"; + break; + case 3: + rate = "29.97"; + break; + default: + rate = "(please send a bug report)"; + break; + } + fprintf(MSG_OUT," @ %s fps", rate); +} + +static void nav_print_PCI_GI(pci_gi_t *pci_gi) { + int32_t i; + + fprintf(MSG_OUT,"libdvdnav: pci_gi:\n"); + fprintf(MSG_OUT,"libdvdnav: nv_pck_lbn 0x%08x\n", pci_gi->nv_pck_lbn); + fprintf(MSG_OUT,"libdvdnav: vobu_cat 0x%04x\n", pci_gi->vobu_cat); + fprintf(MSG_OUT,"libdvdnav: vobu_uop_ctl 0x%08x\n", *(uint32_t*)&pci_gi->vobu_uop_ctl); + fprintf(MSG_OUT,"libdvdnav: vobu_s_ptm 0x%08x\n", pci_gi->vobu_s_ptm); + fprintf(MSG_OUT,"libdvdnav: vobu_e_ptm 0x%08x\n", pci_gi->vobu_e_ptm); + fprintf(MSG_OUT,"libdvdnav: vobu_se_e_ptm 0x%08x\n", pci_gi->vobu_se_e_ptm); + fprintf(MSG_OUT,"libdvdnav: e_eltm "); + print_time(&pci_gi->e_eltm); + fprintf(MSG_OUT,"\n"); + + fprintf(MSG_OUT,"libdvdnav: vobu_isrc \""); + for(i = 0; i < 32; i++) { + char c = pci_gi->vobu_isrc[i]; + if((c >= ' ') && (c <= '~')) + fprintf(MSG_OUT,"%c", c); + else + fprintf(MSG_OUT,"."); + } + fprintf(MSG_OUT,"\"\n"); +} + +static void nav_print_NSML_AGLI(nsml_agli_t *nsml_agli) { + int32_t i, j = 0; + + for(i = 0; i < 9; i++) + j |= nsml_agli->nsml_agl_dsta[i]; + if(j == 0) + return; + + fprintf(MSG_OUT,"libdvdnav: nsml_agli:\n"); + for(i = 0; i < 9; i++) + if(nsml_agli->nsml_agl_dsta[i]) + fprintf(MSG_OUT,"libdvdnav: nsml_agl_c%d_dsta 0x%08x\n", i + 1, + nsml_agli->nsml_agl_dsta[i]); +} + +static void nav_print_HL_GI(hl_gi_t *hl_gi, int32_t *btngr_ns, int32_t *btn_ns) { + + if((hl_gi->hli_ss & 0x03) == 0) + return; + + fprintf(MSG_OUT,"libdvdnav: hl_gi:\n"); + fprintf(MSG_OUT,"libdvdnav: hli_ss 0x%01x\n", hl_gi->hli_ss & 0x03); + fprintf(MSG_OUT,"libdvdnav: hli_s_ptm 0x%08x\n", hl_gi->hli_s_ptm); + fprintf(MSG_OUT,"libdvdnav: hli_e_ptm 0x%08x\n", hl_gi->hli_e_ptm); + fprintf(MSG_OUT,"libdvdnav: btn_se_e_ptm 0x%08x\n", hl_gi->btn_se_e_ptm); + + *btngr_ns = hl_gi->btngr_ns; + fprintf(MSG_OUT,"libdvdnav: btngr_ns %d\n", hl_gi->btngr_ns); + fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty 0x%02x\n", 1, hl_gi->btngr1_dsp_ty); + fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty 0x%02x\n", 2, hl_gi->btngr2_dsp_ty); + fprintf(MSG_OUT,"libdvdnav: btngr%d_dsp_ty 0x%02x\n", 3, hl_gi->btngr3_dsp_ty); + + fprintf(MSG_OUT,"libdvdnav: btn_ofn %d\n", hl_gi->btn_ofn); + *btn_ns = hl_gi->btn_ns; + fprintf(MSG_OUT,"libdvdnav: btn_ns %d\n", hl_gi->btn_ns); + fprintf(MSG_OUT,"libdvdnav: nsl_btn_ns %d\n", hl_gi->nsl_btn_ns); + fprintf(MSG_OUT,"libdvdnav: fosl_btnn %d\n", hl_gi->fosl_btnn); + fprintf(MSG_OUT,"libdvdnav: foac_btnn %d\n", hl_gi->foac_btnn); +} + +static void nav_print_BTN_COLIT(btn_colit_t *btn_colit) { + int32_t i, j; + + j = 0; + for(i = 0; i < 6; i++) + j |= btn_colit->btn_coli[i/2][i&1]; + if(j == 0) + return; + + fprintf(MSG_OUT,"libdvdnav: btn_colit:\n"); + for(i = 0; i < 3; i++) + for(j = 0; j < 2; j++) + fprintf(MSG_OUT,"libdvdnav: btn_cqoli %d %s_coli: %08x\n", + i, (j == 0) ? "sl" : "ac", + btn_colit->btn_coli[i][j]); +} + +static void nav_print_BTNIT(btni_t *btni_table, int32_t btngr_ns, int32_t btn_ns) { + int32_t i, j, k; + + fprintf(MSG_OUT,"libdvdnav: btnit:\n"); + fprintf(MSG_OUT,"libdvdnav: btngr_ns: %i\n", btngr_ns); + fprintf(MSG_OUT,"libdvdnav: btn_ns: %i\n", btn_ns); + + if(btngr_ns == 0) + return; + + for(i = 0; i < btngr_ns; i++) { + for(j = 0; j < (36 / btngr_ns); j++) { + if(j < btn_ns) { + btni_t *btni = &btni_table[(36 / btngr_ns) * i + j]; + + fprintf(MSG_OUT,"libdvdnav: group %d btni %d: ", i+1, j+1); + fprintf(MSG_OUT,"btn_coln %d, auto_action_mode %d\n", + btni->btn_coln, btni->auto_action_mode); + fprintf(MSG_OUT,"libdvdnav: coords (%d, %d) .. (%d, %d)\n", + btni->x_start, btni->y_start, btni->x_end, btni->y_end); + + fprintf(MSG_OUT,"libdvdnav: up %d, ", btni->up); + fprintf(MSG_OUT,"down %d, ", btni->down); + fprintf(MSG_OUT,"left %d, ", btni->left); + fprintf(MSG_OUT,"right %d\n", btni->right); + for(k = 0; k < 8; k++) { + fprintf(MSG_OUT, "libdvdnav: %02x ", btni->cmd.bytes[k]); + } + fprintf(MSG_OUT, "| "); +#ifdef TRACE + vm_print_mnemonic(&btni->cmd); +#endif + fprintf(MSG_OUT, "\n"); + } + } + } +} + +static void nav_print_HLI(hli_t *hli) { + int32_t btngr_ns = 0, btn_ns = 0; + + fprintf(MSG_OUT,"libdvdnav: hli:\n"); + nav_print_HL_GI(&hli->hl_gi, & btngr_ns, & btn_ns); + nav_print_BTN_COLIT(&hli->btn_colit); + nav_print_BTNIT(hli->btnit, btngr_ns, btn_ns); +} + +void nav_print_PCI(pci_t *pci) { + fprintf(MSG_OUT,"libdvdnav: pci packet:\n"); + nav_print_PCI_GI(&pci->pci_gi); + nav_print_NSML_AGLI(&pci->nsml_agli); + nav_print_HLI(&pci->hli); +} + +#endif + + +/* Highlighting API calls */ + +dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *this, int32_t *button) { + + if(!this || !button) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + /* Simply return the appropriate value based on the SPRM */ + if(((*button) = this->position_current.button) == -1) + (*button) = this->vm->state.HL_BTNN_REG >> 10; + + return DVDNAV_STATUS_OK; +} + +static btni_t *get_current_button(dvdnav_t *this, pci_t *pci) { + int32_t button = 0; + + if(!this || !pci) { + printerr("Passed a NULL pointer."); + return NULL; + } + if(!pci->hli.hl_gi.hli_ss) { + printerr("Not in a menu."); + return NULL; + } + if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { + printerr("This NAV has already been left."); + return NULL; + } + + button = this->vm->state.HL_BTNN_REG >> 10; +#ifdef BUTTON_TESTING + nav_print_PCI(pci); +#endif + + return &(pci->hli.btnit[button-1]); +} + +static dvdnav_status_t button_auto_action(dvdnav_t *this, pci_t *pci) { + if (get_current_button(this, pci)->auto_action_mode) + return dvdnav_button_activate(this, pci); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *this, pci_t *pci) { + btni_t *button_ptr; + + if(!(button_ptr = get_current_button(this, pci))) + return DVDNAV_STATUS_ERR; + + dvdnav_button_select(this, pci, button_ptr->up); + return button_auto_action(this, pci); +} + +dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *this, pci_t *pci) { + btni_t *button_ptr; + + if(!(button_ptr = get_current_button(this, pci))) + return DVDNAV_STATUS_ERR; + + dvdnav_button_select(this, pci, button_ptr->down); + return button_auto_action(this, pci); +} + +dvdnav_status_t dvdnav_right_button_select(dvdnav_t *this, pci_t *pci) { + btni_t *button_ptr; + + if(!(button_ptr = get_current_button(this, pci))) + return DVDNAV_STATUS_ERR; + + dvdnav_button_select(this, pci, button_ptr->right); + return button_auto_action(this, pci); +} + +dvdnav_status_t dvdnav_left_button_select(dvdnav_t *this, pci_t *pci) { + btni_t *button_ptr; + + if(!(button_ptr = get_current_button(this, pci))) + return DVDNAV_STATUS_ERR; + + dvdnav_button_select(this, pci, button_ptr->left); + return button_auto_action(this, pci); +} + +dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode, + dvdnav_highlight_area_t *highlight) { + btni_t *button_ptr; + +#ifdef BUTTON_TESTING + fprintf(MSG_OUT, "libdvdnav: Button get_highlight_area %i\n", button); +#endif + + if(!nav_pci->hli.hl_gi.hli_ss) + return DVDNAV_STATUS_ERR; + if((button <= 0) || (button > nav_pci->hli.hl_gi.btn_ns)) + return DVDNAV_STATUS_ERR; + + + button_ptr = &nav_pci->hli.btnit[button-1]; + + highlight->sx = button_ptr->x_start; + highlight->sy = button_ptr->y_start; + highlight->ex = button_ptr->x_end; + highlight->ey = button_ptr->y_end; + if(button_ptr->btn_coln != 0) { + highlight->palette = nav_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode]; + } else { + highlight->palette = 0; + } + highlight->pts = nav_pci->hli.hl_gi.hli_s_ptm; + highlight->buttonN = button; +#ifdef BUTTON_TESTING + fprintf(MSG_OUT, "libdvdnav: highlight: Highlight area is (%u,%u)-(%u,%u), display = %i, button = %u\n", + button_ptr->x_start, button_ptr->y_start, + button_ptr->x_end, button_ptr->y_end, + 1, + button); +#endif + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_button_activate(dvdnav_t *this, pci_t *pci) { + int32_t button; + btni_t *button_ptr = NULL; + + if(!this || !pci) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if(!pci->hli.hl_gi.hli_ss) { + printerr("Not in a menu."); + return DVDNAV_STATUS_ERR; + } + if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { + printerr("This NAV has already been left."); + return DVDNAV_STATUS_ERR; + } + pthread_mutex_lock(&this->vm_lock); + + button = this->vm->state.HL_BTNN_REG >> 10; + + if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) { + /* Special code to handle still menus with no buttons. + * The navigation is expected to report to the application that a STILL is + * underway. In turn, the application is supposed to report to the user + * that the playback is paused. The user is then expected to undo the pause, + * ie: hit play. At that point, the navigation should release the still and + * go to the next Cell. + * Explanation by Mathieu Lacage <[email protected]> + * Code added by jcdutton. + */ + if (this->position_current.still != 0) { + /* In still, but no buttons. */ + vm_get_next_cell(this->vm); + this->position_current.still = 0; + this->sync_wait = 0; + this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn; + pthread_mutex_unlock(&this->vm_lock); + /* clear error message */ + printerr(""); + return DVDNAV_STATUS_OK; + } + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + button_ptr = get_current_button(this, pci); + /* Finally, make the VM execute the appropriate code and probably + * scedule a jump */ +#ifdef BUTTON_TESTING + fprintf(MSG_OUT, "libdvdnav: Evaluating Button Activation commands.\n"); +#endif + if(vm_exec_cmd(this->vm, &(button_ptr->cmd)) == 1) { + /* Command caused a jump */ + this->vm->hop_channel++; + this->position_current.still = 0; + this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn; + } + + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *this, int32_t button, vm_cmd_t *cmd) +{ + if(!this || !cmd) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + /* make the VM execute the appropriate code and probably + * schedule a jump */ +#ifdef BUTTON_TESTING + fprintf(MSG_OUT, "libdvdnav: dvdnav_button_activate_cmd: Evaluating Button Activation commands.\n"); +#endif + if(button > 0) { + this->vm->state.HL_BTNN_REG = (button << 10); + if(vm_exec_cmd(this->vm, cmd) == 1) { + /* Command caused a jump */ + this->vm->hop_channel++; + } + } + /* Always remove still, because some still menus have no buttons. */ + this->position_current.still = 0; + this->sync_wait = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_button_select(dvdnav_t *this, pci_t *pci, int32_t button) { + + if(!this || !pci) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if(!pci->hli.hl_gi.hli_ss) { + printerr("Not in a menu."); + return DVDNAV_STATUS_ERR; + } + if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { + printerr("This NAV has already been left."); + return DVDNAV_STATUS_ERR; + } + +#ifdef BUTTON_TESTING + fprintf(MSG_OUT, "libdvdnav: Button select %i\n", button); +#endif + + if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) { + printerr("Button does not exist."); + return DVDNAV_STATUS_ERR; + } + + this->vm->state.HL_BTNN_REG = (button << 10); + this->position_current.button = -1; /* Force Highligh change */ + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *this, pci_t *pci, + int32_t button) { + /* A trivial function */ + if(dvdnav_button_select(this, pci, button) != DVDNAV_STATUS_ERR) + return dvdnav_button_activate(this, pci); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_mouse_select(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) { + int32_t button, cur_button; + int32_t best,dist,d; + int32_t mx,my,dx,dy; + + if(!this || !pci) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if(!pci->hli.hl_gi.hli_ss) { + printerr("Not in a menu."); + return DVDNAV_STATUS_ERR; + } + if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { + printerr("This NAV has already been left."); + return DVDNAV_STATUS_ERR; + } + + cur_button = this->vm->state.HL_BTNN_REG >> 10; + + best = 0; + dist = 0x08000000; /* >> than (720*720)+(567*567); */ + + /* Loop through all buttons */ + for(button = 1; button <= pci->hli.hl_gi.btn_ns; button++) { + btni_t *button_ptr = &(pci->hli.btnit[button-1]); + + if((x >= button_ptr->x_start) && (x <= button_ptr->x_end) && + (y >= button_ptr->y_start) && (y <= button_ptr->y_end)) { + mx = (button_ptr->x_start + button_ptr->x_end)/2; + my = (button_ptr->y_start + button_ptr->y_end)/2; + dx = mx - x; + dy = my - y; + d = (dx*dx) + (dy*dy); + /* If the mouse is within the button and the mouse is closer + * to the center of this button then it is the best choice. */ + if(d < dist) { + dist = d; + best = button; + } + } + } + /* As an efficiency measure, only re-select the button + * if it is different to the previously selected one. */ + if (best != 0 && best != cur_button) + dvdnav_button_select(this, pci, best); + + /* return DVDNAV_STATUS_OK only if we actually found a matching button */ + return best ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) { + /* A trivial function */ + if(dvdnav_mouse_select(this, pci, x,y) != DVDNAV_STATUS_ERR) + return dvdnav_button_activate(this, pci); + return DVDNAV_STATUS_ERR; +} diff --git a/libdvdnav/navigation.c b/libdvdnav/navigation.c new file mode 100644 index 0000000..07d18d7 --- /dev/null +++ b/libdvdnav/navigation.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: navigation.c,v 1.11 2004/06/20 16:59:13 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dvdnav_internal.h" + +/* Navigation API calls */ + +dvdnav_status_t dvdnav_still_skip(dvdnav_t *this) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + this->position_current.still = 0; + this->skip_still = 1; + this->sync_wait = 0; + this->sync_wait_skip = 1; + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_wait_skip(dvdnav_t *this) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + this->sync_wait = 0; + this->sync_wait_skip = 1; + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *this, int32_t *titles) { + if(!this || !titles) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if (!this->vm->vmgi) { + printerr("Bad VM state."); + return DVDNAV_STATUS_ERR; + } + + (*titles) = vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts; + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *this, int32_t title, int32_t *parts) { + if(!this || !parts) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if (!this->vm->vmgi) { + printerr("Bad VM state."); + return DVDNAV_STATUS_ERR; + } + if ((title < 1) || (title > vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts) ) { + printerr("Passed a title number out of range."); + return DVDNAV_STATUS_ERR; + } + + (*parts) = vm_get_vmgi(this->vm)->tt_srpt->title[title-1].nr_of_ptts; + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_current_title_info(dvdnav_t *this, int32_t *title, int32_t *part) { + int32_t retval; + + if(!this || !title || !part) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->vtsi || !this->vm->vmgi) { + printerr("Bad VM state."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (!this->started) { + printerr("Virtual DVD machine not started."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if ( (this->vm->state.domain == VTSM_DOMAIN) + || (this->vm->state.domain == VMGM_DOMAIN) ) { + /* Get current Menu ID: into *part. */ + vm_get_current_menu(this->vm, part); + if (*part > -1) { + *title = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + if (this->vm->state.domain == VTS_DOMAIN) { + retval = vm_get_current_title_part(this->vm, title, part); + pthread_mutex_unlock(&this->vm_lock); + return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR; + } + printerr("Not in a title or menu."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_title_play(dvdnav_t *this, int32_t title) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + return dvdnav_part_play(this, title, 1); +} + +dvdnav_status_t dvdnav_part_play(dvdnav_t *this, int32_t title, int32_t part) { + int32_t retval; + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if (!this->vm->vmgi) { + printerr("Bad VM state."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (!this->started) { + /* don't report an error but be nice */ + vm_start(this->vm); + this->started = 1; + } + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if((title < 1) || (title > this->vm->vmgi->tt_srpt->nr_of_srpts)) { + printerr("Title out of range."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if((part < 1) || (part > this->vm->vmgi->tt_srpt->title[title-1].nr_of_ptts)) { + printerr("Part out of range."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + retval = vm_jump_title_part(this->vm, title, part); + if (retval) + this->vm->hop_channel++; + pthread_mutex_unlock(&this->vm_lock); + + return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *this, int32_t title, + int32_t part, int32_t parts_to_play) { + /* FIXME: Implement auto-stop */ + if (dvdnav_part_play(this, title, part) == DVDNAV_STATUS_OK) + printerr("Not implemented yet."); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_time_play(dvdnav_t *this, int32_t title, + uint64_t time) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + /* FIXME: Implement */ + printerr("Not implemented yet."); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_stop(dvdnav_t *this) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + this->vm->stopped = 1; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_go_up(dvdnav_t *this) { + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + /* A nice easy function... delegate to the VM */ + pthread_mutex_lock(&this->vm_lock); + vm_jump_up(this->vm); + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} diff --git a/libdvdnav/read_cache.c b/libdvdnav/read_cache.c new file mode 100644 index 0000000..1941c3c --- /dev/null +++ b/libdvdnav/read_cache.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * 2001-2004 the dvdnav project + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: read_cache.c,v 1.12 2004/03/16 11:43:38 mroi Exp $ + * + */ +/* + * There was a multithreaded read ahead cache in here for some time, but + * it had only been used for a short time. If you want to have a look at it, + * search the CVS attic. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dvdnav.h" +#include "dvdnav_internal.h" +#include "read_cache.h" +#include <sys/time.h> +#include <time.h> +#include "dvdread.h" +#define READ_CACHE_CHUNKS 10 + +/* all cache chunks must be memory aligned to allow use of raw devices */ +#define ALIGNMENT 2048 + +#define READ_AHEAD_SIZE_MIN 4 +#define READ_AHEAD_SIZE_MAX 512 + +typedef struct read_cache_chunk_s { + uint8_t *cache_buffer; + uint8_t *cache_buffer_base; /* used in malloc and free for alignment */ + int32_t cache_start_sector; /* -1 means cache invalid */ + int32_t cache_read_count; /* this many sectors are already read */ + size_t cache_block_count; /* this many sectors will go in this chunk */ + size_t cache_malloc_size; + int cache_valid; + int usage_count; /* counts how many buffers where issued from this chunk */ +} read_cache_chunk_t; + +struct read_cache_s { + read_cache_chunk_t chunk[READ_CACHE_CHUNKS]; + int current; + int freeing; /* is set to one when we are about to dispose the cache */ + uint32_t read_ahead_size; + int read_ahead_incr; + int last_sector; + pthread_mutex_t lock; + + /* Bit of strange cross-linking going on here :) -- Gotta love C :) */ + dvdnav_t *dvd_self; +}; + +/* +#define READ_CACHE_TRACE 0 +*/ + +#ifdef __GNUC__ +# if READ_CACHE_TRACE +# define dprintf(fmt, args...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , ## args) +# else +# define dprintf(fmt, args...) /* Nowt */ +# endif +#else +# if READ_CACHE_TRACE +# define dprintf(fmt, ...) fprintf(MSG_OUT, "libdvdnav: %s: "fmt, __func__ , __VA_ARGS__) +# else +#ifdef _MSC_VER +# define dprintf(fmt, str) /* Nowt */ +#else +# define dprintf(fmt, ...) /* Nowt */ +#endif /* _MSC_VER */ +# endif +#endif + + +read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) { + read_cache_t *self; + int i; + + self = (read_cache_t *)malloc(sizeof(read_cache_t)); + + if(self) { + self->current = 0; + self->freeing = 0; + self->dvd_self = dvd_self; + self->last_sector = 0; + self->read_ahead_size = READ_AHEAD_SIZE_MIN; + self->read_ahead_incr = 0; + pthread_mutex_init(&self->lock, NULL); + dvdnav_read_cache_clear(self); + for (i = 0; i < READ_CACHE_CHUNKS; i++) { + self->chunk[i].cache_buffer = NULL; + self->chunk[i].usage_count = 0; + } + } + + return self; +} + +void dvdnav_read_cache_free(read_cache_t* self) { + dvdnav_t *tmp; + int i; + + pthread_mutex_lock(&self->lock); + self->freeing = 1; + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) { + free(self->chunk[i].cache_buffer_base); + self->chunk[i].cache_buffer = NULL; + } + pthread_mutex_unlock(&self->lock); + + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].cache_buffer) return; + + /* all buffers returned, free everything */ + tmp = self->dvd_self; + pthread_mutex_destroy(&self->lock); + free(self); + free(tmp); +} + +/* This function MUST be called whenever self->file changes. */ +void dvdnav_read_cache_clear(read_cache_t *self) { + int i; + + if(!self) + return; + + pthread_mutex_lock(&self->lock); + for (i = 0; i < READ_CACHE_CHUNKS; i++) + self->chunk[i].cache_valid = 0; + pthread_mutex_unlock(&self->lock); +} + +/* This function is called just after reading the NAV packet. */ +void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) { + int i, use; + + if(!self) + return; + + if(!self->dvd_self->use_read_ahead) + return; + + pthread_mutex_lock(&self->lock); + + /* find a free cache chunk that best fits the required size */ + use = -1; + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer && + self->chunk[i].cache_malloc_size >= block_count && + (use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size)) + use = i; + + if (use == -1) { + /* we haven't found a cache chunk, so we try to reallocate an existing one */ + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer && + (use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size)) + use = i; + if (use >= 0) { + self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base, + block_count * DVD_VIDEO_LB_LEN + ALIGNMENT); + self->chunk[use].cache_buffer = + (uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT); + dprintf("pre_cache DVD read realloc happened\n"); + self->chunk[use].cache_malloc_size = block_count; + } else { + /* we still haven't found a cache chunk, let's allocate a new one */ + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (!self->chunk[i].cache_buffer) { + use = i; + break; + } + if (use >= 0) { + /* We start with a sensible figure for the first malloc of 500 blocks. + * Some DVDs I have seen venture to 450 blocks. + * This is so that fewer realloc's happen if at all. + */ + self->chunk[i].cache_buffer_base = + malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT); + self->chunk[i].cache_buffer = + (uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT); + self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500; + dprintf("pre_cache DVD read malloc %d blocks\n", + (block_count > 500 ? block_count : 500 )); + } + } + } + + if (use >= 0) { + self->chunk[use].cache_start_sector = sector; + self->chunk[use].cache_block_count = block_count; + self->chunk[use].cache_read_count = 0; + self->chunk[use].cache_valid = 1; + self->current = use; + } else { + dprintf("pre_caching was impossible, no cache chunk available\n"); + } + pthread_mutex_unlock(&self->lock); +} + +int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) { + int i, use; + int start; + int size; + int incr; + uint8_t *read_ahead_buf; + int32_t res; + + if(!self) + return 0; + + use = -1; + + if(self->dvd_self->use_read_ahead) { + /* first check, if sector is in current chunk */ + read_cache_chunk_t cur = self->chunk[self->current]; + if (cur.cache_valid && sector >= cur.cache_start_sector && + sector <= (cur.cache_start_sector + cur.cache_read_count) && + sector + block_count <= cur.cache_start_sector + cur.cache_block_count) + use = self->current; + else + for (i = 0; i < READ_CACHE_CHUNKS; i++) + if (self->chunk[i].cache_valid && + sector >= self->chunk[i].cache_start_sector && + sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) && + sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count) + use = i; + } + + if (use >= 0) { + read_cache_chunk_t *chunk; + + /* Increment read-ahead size if sector follows the last sector */ + if (sector == (self->last_sector + 1)) { + if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX) + self->read_ahead_incr++; + } else { + self->read_ahead_size = READ_AHEAD_SIZE_MIN; + self->read_ahead_incr = 0; + } + self->last_sector = sector; + + /* The following resources need to be protected by a mutex : + * self->chunk[*].cache_buffer + * self->chunk[*].cache_malloc_size + * self->chunk[*].usage_count + */ + pthread_mutex_lock(&self->lock); + chunk = &self->chunk[use]; + read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN; + *buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN; + chunk->usage_count++; + pthread_mutex_unlock(&self->lock); + + dprintf("libdvdnav: sector=%d, start_sector=%d, last_sector=%d\n", sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count); + + /* read_ahead_size */ + incr = self->read_ahead_incr >> 1; + if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) { + self->read_ahead_size = READ_AHEAD_SIZE_MAX; + } else { + self->read_ahead_size += incr; + } + + /* real read size */ + start = chunk->cache_start_sector + chunk->cache_read_count; + if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) { + size = chunk->cache_block_count - chunk->cache_read_count; + } else { + size = self->read_ahead_size; + /* ensure that the sector we want will be read */ + if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size) + size = sector - chunk->cache_start_sector - chunk->cache_read_count; + } + dprintf("libdvdnav: read_ahead_size=%d, size=%d\n", self->read_ahead_size, size); + + if (size) + chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file, + start, + size, + read_ahead_buf); + + res = DVD_VIDEO_LB_LEN * block_count; + + } else { + + if (self->dvd_self->use_read_ahead) + dprintf("cache miss on sector %d\n", sector); + + res = DVDReadBlocks(self->dvd_self->file, + sector, + block_count, + *buf) * DVD_VIDEO_LB_LEN; + } + + return res; + +} + +dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) { + read_cache_t *cache; + int i; + + if (!self) + return DVDNAV_STATUS_ERR; + + cache = self->cache; + if (!cache) + return DVDNAV_STATUS_ERR; + + pthread_mutex_lock(&cache->lock); + for (i = 0; i < READ_CACHE_CHUNKS; i++) { + if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer && + buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) { + cache->chunk[i].usage_count--; + } + } + pthread_mutex_unlock(&cache->lock); + + if (cache->freeing) + /* when we want to dispose the cache, try freeing it now */ + dvdnav_read_cache_free(cache); + + return DVDNAV_STATUS_OK; +} diff --git a/libdvdnav/read_cache.h b/libdvdnav/read_cache.h new file mode 100644 index 0000000..857d676 --- /dev/null +++ b/libdvdnav/read_cache.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: read_cache.h,v 1.1 2002/08/08 17:49:21 richwareham Exp $ + * + */ + +#ifndef __DVDNAV_READ_CACHE_H +#define __DVDNAV_READ_CACHE_H + +#include "dvdnav_internal.h" + +/* Opaque cache type -- defined in dvdnav_internal.h */ +/* typedef struct read_cache_s read_cache_t; */ + +/* EXPERIMENTAL: Setting the following to 1 will use an experimental multi-threaded + * read-ahead cache. + */ +#define _MULTITHREAD_ 0 + +/* Constructor/destructors */ +read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self); +void dvdnav_read_cache_free(read_cache_t* self); + +/* This function MUST be called whenever self->file changes. */ +void dvdnav_read_cache_clear(read_cache_t *self); +/* This function is called just after reading the NAV packet. */ +void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count); +/* This function will do the cache read. + * The buffer handed in must be malloced to take one dvd block. + * On a cache hit, a different buffer will be returned though. + * Those buffers must _never_ be freed. */ +int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf); + +#endif /* __DVDNAV_READ_CACHE_H */ diff --git a/libdvdnav/remap.c b/libdvdnav/remap.c new file mode 100644 index 0000000..1788b15 --- /dev/null +++ b/libdvdnav/remap.c @@ -0,0 +1,257 @@ +/* + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: remap.c,v 1.4 2003/08/25 21:51:40 f1rmb Exp $ + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#ifndef _MSC_VER +#include <sys/param.h> +#include <sys/fcntl.h> +#else +#ifndef MAXPATHLEN +#define MAXPATHLEN 255 +#endif +#endif /* _MSC_VER */ + +#include <assert.h> +#include "remap.h" +#include "dvdnav_internal.h" + +struct block_s { + int domain; + int title; + int program; + unsigned long start_block; + unsigned long end_block; +}; + +struct remap_s { + char *title; + int maxblocks; + int nblocks; + int debug; + struct block_s *blocks; +}; + +static remap_t* remap_new( char *title) { + remap_t *map = malloc( sizeof(remap_t)); + map->title = strdup(title); + map->maxblocks = 0; + map->nblocks = 0; + map->blocks = NULL; + map->debug = 0; + return map; +} + +static int compare_block( block_t *a, block_t *b) { + /* returns -1 if a precedes b, 1 if a follows b, and 0 if a and b overlap */ + if (a->domain < b->domain) { + return -1; + } else if (a->domain > b->domain) { + return 1; + } + + if (a->title < b->title) { + return -1; + } else if (a->title > b->title) { + return 1; + } + + if (a->program < b->program) { + return -1; + } else if (a->program > b->program) { + return 1; + } + + if (a->end_block < b->start_block) { + return -1; + } else if (a->start_block > b->end_block) { + /* + * if a->start_block == b->end_block then the two regions + * aren't strictly overlapping, but they should be merged + * anyway since there are zero blocks between them + */ + return 1; + } + + return 0; +} + +static block_t *findblock( remap_t *map, block_t *key) { + int lb = 0; + int ub = map->nblocks - 1; + int mid; + int res; + + while (lb <= ub) { + mid = lb + (ub - lb)/2; + res = compare_block( key, &map->blocks[mid]); + if (res < 0) { + ub = mid-1; + } else if (res > 0) { + lb = mid+1; + } else { + return &map->blocks[mid]; + } + } + return NULL; +} + +static void mergeblock( block_t *b, block_t tmp) { + if (tmp.start_block < b->start_block) b->start_block = tmp.start_block; + if (tmp.end_block > b->end_block) b->end_block = tmp.end_block; +} + +static void remap_add_node( remap_t *map, block_t block) { + block_t *b; + int n; + b = findblock( map, &block); + if (b) { + /* overlaps an existing block */ + mergeblock( b, block); + } else { + /* new block */ + if (map->nblocks >= map->maxblocks) { + map->maxblocks += 20; + map->blocks = realloc( map->blocks, sizeof( block_t)*map->maxblocks); + } + n = map->nblocks++; + while (n > 0 && compare_block( &block, &map->blocks[ n-1]) < 0) { + map->blocks[ n] = map->blocks[ n-1]; + n--; + } + map->blocks[ n] = block; + } +} + +static int parseblock(char *buf, int *dom, int *tt, int *pg, + unsigned long *start, unsigned long *end) { + long tmp; + char *tok; + char *epos; + char *marker[]={"domain", "title", "program", "start", "end"}; + int st = 0; + tok = strtok( buf, " "); + while (st < 5) { + if (strcmp(tok, marker[st])) return -st-1000; + tok = strtok( NULL, " "); + if (!tok) return -st-2000; + tmp = strtol( tok, &epos, 0); + if (*epos != 0 && *epos != ',') return -st-3000; + switch (st) { + case 0: + *dom = (int)tmp; + break; + case 1: + *tt = (int)tmp; + break; + case 2: + *pg = (int)tmp; + break; + case 3: + *start = tmp; + break; + case 4: + *end = tmp; + break; + } + st++; + tok = strtok( NULL, " "); + } + return st; +} + +remap_t* remap_loadmap( char *title) { + char buf[160]; + char *fname; + char *home; + int res; + int fname_len=0; + FILE *fp; + block_t tmp; + remap_t *map; + + /* Build the map filename */ + home = getenv("HOME"); assert(home); + /*strncpy(fname, home, sizeof(fname)); + strncat(fname, "/.dvdnav/", sizeof(fname)); + strncat(fname, title, sizeof(fname)); + strncat(fname, ".map", sizeof(fname)); + */ + fname_len = strlen(home)+strlen("/.dvdnav/")+strlen(title)+strlen(".map")+1; + fname = calloc(fname_len, sizeof(char)); + snprintf(fname, fname_len, "%s%s%s%s", home, "/.dvdnav/", title, ".map"); + + /* Open the map file */ + fp = fopen( fname, "r"); + if (!fp) { + fprintf(MSG_OUT, "libdvdnav: Unable to find map file '%s'\n", fname); + return NULL; + } + + /* Load the map file */ + map = remap_new( title); + while (fgets( buf, sizeof(buf), fp) != NULL) { + if (buf[0] == '\n' || buf[0] == '#' || buf[0] == 0) continue; + if (strncasecmp( buf, "debug", 5) == 0) { + map->debug = 1; + } else { + res = parseblock( buf, + &tmp.domain, &tmp.title, &tmp.program, &tmp.start_block, &tmp.end_block); + if (res != 5) { + fprintf(MSG_OUT, "libdvdnav: Ignoring map line (%d): %s\n", res, buf); + continue; + } + remap_add_node( map, tmp); + } + } + + free(fname); + if (map->nblocks == 0 && map->debug == 0) return NULL; + return map; +} + +unsigned long remap_block( + remap_t *map, int domain, int title, int program, + unsigned long cblock, unsigned long offset) +{ + block_t key; + block_t *b; + + if (map->debug) { + fprintf(MSG_OUT, "libdvdnav: %s: domain %d, title %d, program %d, start %lx, next %lx\n", + map->title, domain, title, program, cblock, cblock+offset); + } + + key.domain = domain; + key.title = title; + key.program = program; + key.start_block = key.end_block = cblock + offset; + b = findblock( map, &key); + + if (b) { + if (map->debug) { + fprintf(MSG_OUT, "libdvdnav: Redirected to %lx\n", b->end_block); + } + return b->end_block - cblock; + } + return offset; +} diff --git a/libdvdnav/remap.h b/libdvdnav/remap.h new file mode 100644 index 0000000..88e06d2 --- /dev/null +++ b/libdvdnav/remap.h @@ -0,0 +1,33 @@ +/* + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: remap.h,v 1.2 2003/02/20 16:02:00 mroi Exp $ + */ + +#ifndef __REMAP__H +#define __REMAP__H +typedef struct block_s block_t; + +typedef struct remap_s remap_t; + +remap_t* remap_loadmap( char *title); + +unsigned long remap_block( + remap_t *map, int domain, int title, int program, + unsigned long cblock, unsigned long offset); + +#endif diff --git a/libdvdnav/searching.c b/libdvdnav/searching.c new file mode 100644 index 0000000..a069f51 --- /dev/null +++ b/libdvdnav/searching.c @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: searching.c,v 1.19 2005/10/14 21:02:16 miguelfreitas Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include "dvdnav_internal.h" +#include "dvdnav.h" + +/* +#define LOG_DEBUG +*/ + +/* Searching API calls */ + +/* Scan the ADMAP for a particular block number. */ +/* Return placed in vobu. */ +/* Returns error status */ +/* FIXME: Maybe need to handle seeking outside current cell. */ +static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, uint32_t *vobu) { + vobu_admap_t *admap = NULL; + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: Seeking to target %u ...\n", seekto_block); +#endif + *vobu = -1; + + /* Search through the VOBU_ADMAP for the nearest VOBU + * to the target block */ + switch(domain) { + case FP_DOMAIN: + case VMGM_DOMAIN: + admap = this->vm->vmgi->menu_vobu_admap; + break; + case VTSM_DOMAIN: + admap = this->vm->vtsi->menu_vobu_admap; + break; + case VTS_DOMAIN: + admap = this->vm->vtsi->vts_vobu_admap; + break; + default: + fprintf(MSG_OUT, "libdvdnav: Error: Unknown domain for seeking.\n"); + } + if(admap) { + uint32_t address = 0; + uint32_t vobu_start, next_vobu; + int32_t found = 0; + + /* Search through ADMAP for best sector */ + vobu_start = SRI_END_OF_CELL; + /* FIXME: Implement a faster search algorithm */ + while((!found) && ((address<<2) < admap->last_byte)) { + next_vobu = admap->vobu_start_sectors[address]; + + /* fprintf(MSG_OUT, "libdvdnav: Found block %u\n", next_vobu); */ + + if(vobu_start <= seekto_block && + next_vobu > seekto_block) { + found = 1; + } else { + vobu_start = next_vobu; + } + address ++; + } + if(found) { + *vobu = vobu_start; + return DVDNAV_STATUS_OK; + } else { + fprintf(MSG_OUT, "libdvdnav: Could not locate block\n"); + return DVDNAV_STATUS_ERR; + } + } + fprintf(MSG_OUT, "libdvdnav: admap not located\n"); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_time_search(dvdnav_t *this, + uint64_t time) { + + uint64_t target = time; + uint64_t length = 0; + uint32_t first_cell_nr, last_cell_nr, cell_nr; + int32_t found; + cell_playback_t *cell; + dvd_state_t *state; + dvdnav_status_t result; + + if(this->position_current.still != 0) { + printerr("Cannot seek in a still frame."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + state = &(this->vm->state); + if(!state->pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + + if (this->pgc_based) { + first_cell_nr = 1; + last_cell_nr = state->pgc->nr_of_cells; + } else { + /* Find start cell of program. */ + first_cell_nr = state->pgc->program_map[state->pgN-1]; + /* Find end cell of program */ + if(state->pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[state->pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + } + + found = 0; + for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { + cell = &(state->pgc->cell_playback[cell_nr-1]); + length = dvdnav_convert_time(&cell->playback_time); + if (target >= length) { + target -= length; + } else { + /* FIXME: there must be a better way than interpolation */ + target = target * (cell->last_sector - cell->first_sector + 1) / length; + target += cell->first_sector; + + found = 1; + break; + } + } + + if(found) { + int32_t vobu; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", + cell_nr, first_cell_nr, last_cell_nr); +#endif + if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) { + int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; + + if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , + state->cellN, state->blockN, target, vobu, start); +#endif + this->vm->hop_channel += HOP_SEEK; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + } + + fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); + printerr("Error when seeking."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_sector_search(dvdnav_t *this, + uint64_t offset, int32_t origin) { + uint32_t target = 0; + uint32_t length = 0; + uint32_t first_cell_nr, last_cell_nr, cell_nr; + int32_t found; + cell_playback_t *cell; + dvd_state_t *state; + dvdnav_status_t result; + + if(this->position_current.still != 0) { + printerr("Cannot seek in a still frame."); + return DVDNAV_STATUS_ERR; + } + + result = dvdnav_get_position(this, &target, &length); + if(!result) { + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + state = &(this->vm->state); + if(!state->pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length); + fprintf(MSG_OUT, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN); +#endif + + switch(origin) { + case SEEK_SET: + if(offset > length) { + printerr("Request to seek behind end."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + target = offset; + break; + case SEEK_CUR: + if(target + offset > length) { + printerr("Request to seek behind end."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + target += offset; + break; + case SEEK_END: + if(length - offset < 0) { + printerr("Request to seek before start."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + target = length - offset; + break; + default: + /* Error occured */ + printerr("Illegal seek mode."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + if (this->pgc_based) { + first_cell_nr = 1; + last_cell_nr = state->pgc->nr_of_cells; + } else { + /* Find start cell of program. */ + first_cell_nr = state->pgc->program_map[state->pgN-1]; + /* Find end cell of program */ + if(state->pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[state->pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + } + + found = 0; + for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) { + cell = &(state->pgc->cell_playback[cell_nr-1]); + length = cell->last_sector - cell->first_sector + 1; + if (target >= length) { + target -= length; + } else { + /* convert the target sector from Cell-relative to absolute physical sector */ + target += cell->first_sector; + found = 1; + break; + } + } + + if(found) { + int32_t vobu; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: Seeking to cell %i from choice of %i to %i\n", + cell_nr, first_cell_nr, last_cell_nr); +#endif + if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) { + int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector; + + if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) { +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" , + state->cellN, state->blockN, target, vobu, start); +#endif + this->vm->hop_channel += HOP_SEEK; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + } + + fprintf(MSG_OUT, "libdvdnav: Error when seeking\n"); + fprintf(MSG_OUT, "libdvdnav: FIXME: Implement seeking to location %u\n", target); + printerr("Error when seeking."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) { + int32_t title, old_part; + + if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK) + return dvdnav_part_play(this, title, part); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) { + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: previous chapter\n"); +#endif + if (!vm_jump_prev_pg(this->vm)) { + fprintf(MSG_OUT, "libdvdnav: previous chapter failed.\n"); + printerr("Skip to previous chapter failed."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->position_current.still = 0; + this->vm->hop_channel++; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: previous chapter done\n"); +#endif + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) { + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: top chapter\n"); +#endif + if (!vm_jump_top_pg(this->vm)) { + fprintf(MSG_OUT, "libdvdnav: top chapter failed.\n"); + printerr("Skip to top chapter failed."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->position_current.still = 0; + this->vm->hop_channel++; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: top chapter done\n"); +#endif + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) { + vm_t *try_vm; + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: next chapter\n"); +#endif + /* make a copy of current VM and try to navigate the copy to the next PG */ + try_vm = vm_new_copy(this->vm); + if (!vm_jump_next_pg(try_vm) || try_vm->stopped) { + vm_free_copy(try_vm); + /* next_pg failed, try to jump at least to the next cell */ + try_vm = vm_new_copy(this->vm); + vm_get_next_cell(try_vm); + if (try_vm->stopped) { + vm_free_copy(try_vm); + fprintf(MSG_OUT, "libdvdnav: next chapter failed.\n"); + printerr("Skip to next chapter failed."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + } + /* merge changes on success */ + vm_merge(this->vm, try_vm); + vm_free_copy(try_vm); + this->position_current.still = 0; + this->vm->hop_channel++; +#ifdef LOG_DEBUG + fprintf(MSG_OUT, "libdvdnav: next chapter done\n"); +#endif + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) { + vm_t *try_vm; + + if(!this) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + /* make a copy of current VM and try to navigate the copy to the menu */ + try_vm = vm_new_copy(this->vm); + if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) { + /* Try resume */ + if (vm_jump_resume(try_vm) && !try_vm->stopped) { + /* merge changes on success */ + vm_merge(this->vm, try_vm); + vm_free_copy(try_vm); + this->position_current.still = 0; + this->vm->hop_channel++; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root; + + if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) { + /* merge changes on success */ + vm_merge(this->vm, try_vm); + vm_free_copy(try_vm); + this->position_current.still = 0; + this->vm->hop_channel++; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } else { + vm_free_copy(try_vm); + printerr("No such menu or menu not reachable."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } +} + +dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos, + uint32_t *len) { + uint32_t cur_sector; + int32_t cell_nr, first_cell_nr, last_cell_nr; + cell_playback_t *cell; + dvd_state_t *state; + + if(!this || !pos || !len) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + if(!this->started) { + printerr("Virtual DVD machine not started."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + state = &(this->vm->state); + if(!state->pgc || this->vm->stopped) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (this->position_current.hop_channel != this->vm->hop_channel || + this->position_current.domain != state->domain || + this->position_current.vts != state->vtsN || + this->position_current.cell_restart != state->cell_restart) { + printerr("New position not yet determined."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + /* Get current sector */ + cur_sector = this->vobu.vobu_start + this->vobu.blockN; + + if (this->pgc_based) { + first_cell_nr = 1; + last_cell_nr = state->pgc->nr_of_cells; + } else { + /* Find start cell of program. */ + first_cell_nr = state->pgc->program_map[state->pgN-1]; + /* Find end cell of program */ + if(state->pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[state->pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + } + + *pos = -1; + *len = 0; + for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) { + cell = &(state->pgc->cell_playback[cell_nr-1]); + if (cell_nr == state->cellN) { + /* the current sector is in this cell, + * pos is length of PG up to here + sector's offset in this cell */ + *pos = *len + cur_sector - cell->first_sector; + } + *len += cell->last_sector - cell->first_sector + 1; + } + + assert((signed)*pos != -1); + + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this, + uint32_t *pos, + uint32_t *len) { + uint32_t cur_sector; + uint32_t first_cell_nr; + uint32_t last_cell_nr; + cell_playback_t *first_cell; + cell_playback_t *last_cell; + dvd_state_t *state; + + if(!this || !pos || !len) { + printerr("Passed a NULL pointer."); + return DVDNAV_STATUS_ERR; + } + + state = &(this->vm->state); + if(!state->pgc) { + printerr("No current PGC."); + return DVDNAV_STATUS_ERR; + } + + /* Get current sector */ + cur_sector = this->vobu.vobu_start + this->vobu.blockN; + + /* Now find first and last cells in title. */ + first_cell_nr = state->pgc->program_map[0]; + first_cell = &(state->pgc->cell_playback[first_cell_nr-1]); + last_cell_nr = state->pgc->nr_of_cells; + last_cell = &(state->pgc->cell_playback[last_cell_nr-1]); + + *pos = cur_sector - first_cell->first_sector; + *len = last_cell->last_sector - first_cell->first_sector; + + return DVDNAV_STATUS_OK; +} diff --git a/libdvdnav/settings.c b/libdvdnav/settings.c new file mode 100644 index 0000000..4af8aea --- /dev/null +++ b/libdvdnav/settings.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2000 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: settings.c,v 1.7 2004/03/16 11:43:38 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dvdnav_internal.h" + +/* Characteristics/setting API calls */ + +dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *this, int32_t *region) { + if(!this || !region) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + (*region) = this->vm->state.registers.SPRM[20]; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *this, int32_t mask) { + if(!this) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + this->vm->state.registers.SPRM[20] = (mask & 0xff); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *this, int32_t use_readahead) { + if(!this) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + this->use_read_ahead = use_readahead; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *this, int32_t *flag) { + if(!this || !flag) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + (*flag) = this->use_read_ahead; + return DVDNAV_STATUS_OK; +} + +static dvdnav_status_t set_language_register(dvdnav_t *this, char *code, int reg) { + if(!this || !code) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + if(!code[0] || !code[1]) { + printerr("Passed illegal language code."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + this->vm->state.registers.SPRM[reg] = (code[0] << 8) | code[1]; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *this, char *code) { + return set_language_register(this, code, 0); +} + +dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *this, char *code) { + return set_language_register(this, code, 16); +} + +dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *this, char *code) { + return set_language_register(this, code, 18); +} + +dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *this, int32_t pgc) { + if(!this) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + this->pgc_based = pgc; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *this, int32_t *flag) { + if(!this || !flag) { + printerr("Passed a NULL this pointer."); + return DVDNAV_STATUS_ERR; + } + + (*flag) = this->pgc_based; + return DVDNAV_STATUS_OK; +} diff --git a/libdvdnav/vm.c b/libdvdnav/vm.c new file mode 100644 index 0000000..dbebafa --- /dev/null +++ b/libdvdnav/vm.c @@ -0,0 +1,1861 @@ +/* + * Copyright (C) 2000, 2001 H�an Hjort + * Copyright (C) 2001 Rich Wareham <[email protected]> + * 2002-2004 the dvdnav project + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: vm.c,v 1.32 2004/12/20 19:27:20 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "dvdread.h" + +#include "dvdnav_internal.h" + +#ifdef _MSC_VER +#include <io.h> /* read() */ +#endif /* _MSC_VER */ + +/* +#define STRICT +*/ + +/* Local prototypes */ + +/* get_XYZ returns a value. + * set_XYZ sets state using passed parameters. + * returns success/failure. + */ + +/* Play */ +static link_t play_PGC(vm_t *vm); +static link_t play_PGC_PG(vm_t *vm, int pgN); +static link_t play_PGC_post(vm_t *vm); +static link_t play_PG(vm_t *vm); +static link_t play_Cell(vm_t *vm); +static link_t play_Cell_post(vm_t *vm); + +/* Process link - returns 1 if a hop has been performed */ +static int process_command(vm_t *vm,link_t link_values); + +/* Set */ +static int set_TT(vm_t *vm, int tt); +static int set_PTT(vm_t *vm, int tt, int ptt); +static int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn); +static int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part); +static int set_FP_PGC(vm_t *vm); +static int set_MENU(vm_t *vm, int menu); +static int set_PGCN(vm_t *vm, int pgcN); +static int set_PGN(vm_t *vm); /* Set PGN based on (vm->state).CellN */ +static void set_RSMinfo(vm_t *vm, int cellN, int blockN); + +/* Get */ +static int get_TT(vm_t *vm, int vtsN, int vts_ttn); +static int get_ID(vm_t *vm, int id); +static int get_PGCN(vm_t *vm); + +static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang); +static pgcit_t* get_PGCIT(vm_t *vm); + + +/* Helper functions */ + +#ifdef TRACE +static void vm_print_current_domain_state(vm_t *vm) { + switch((vm->state).domain) { + case VTS_DOMAIN: + fprintf(MSG_OUT, "libdvdnav: Video Title Domain: -\n"); + break; + + case VTSM_DOMAIN: + fprintf(MSG_OUT, "libdvdnav: Video Title Menu Domain: -\n"); + break; + + case VMGM_DOMAIN: + fprintf(MSG_OUT, "libdvdnav: Video Manager Menu Domain: -\n"); + break; + + case FP_DOMAIN: + fprintf(MSG_OUT, "libdvdnav: First Play Domain: -\n"); + break; + + default: + fprintf(MSG_OUT, "libdvdnav: Unknown Domain: -\n"); + break; + } + fprintf(MSG_OUT, "libdvdnav: VTS:%d PGC:%d PG:%u CELL:%u BLOCK:%u VTS_TTN:%u TTN:%u TT_PGCN:%u\n", + (vm->state).vtsN, + get_PGCN(vm), + (vm->state).pgN, + (vm->state).cellN, + (vm->state).blockN, + (vm->state).VTS_TTN_REG, + (vm->state).TTN_REG, + (vm->state).TT_PGCN_REG); +} +#endif + +static void dvd_read_name(char *name, const char *device) { + /* Because we are compiling with _FILE_OFFSET_BITS=64 + * all off_t are 64bit. + */ + off_t off; + int fd, i; + uint8_t data[DVD_VIDEO_LB_LEN]; + + /* Read DVD name */ + fd = open(device, O_RDONLY); + if (fd > 0) { + off = lseek( fd, 32 * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET ); + if( off == ( 32 * (off_t) DVD_VIDEO_LB_LEN ) ) { + off = read( fd, data, DVD_VIDEO_LB_LEN ); + close(fd); + if (off == ( (off_t) DVD_VIDEO_LB_LEN )) { + fprintf(MSG_OUT, "libdvdnav: DVD Title: "); + for(i=25; i < 73; i++ ) { + if((data[i] == 0)) break; + if((data[i] > 32) && (data[i] < 127)) { + fprintf(MSG_OUT, "%c", data[i]); + } else { + fprintf(MSG_OUT, " "); + } + } + strncpy(name, &data[25], 48); + name[48] = 0; + fprintf(MSG_OUT, "\nlibdvdnav: DVD Serial Number: "); + for(i=73; i < 89; i++ ) { + if((data[i] == 0)) break; + if((data[i] > 32) && (data[i] < 127)) { + fprintf(MSG_OUT, "%c", data[i]); + } else { + fprintf(MSG_OUT, " "); + } + } + fprintf(MSG_OUT, "\nlibdvdnav: DVD Title (Alternative): "); + for(i=89; i < 128; i++ ) { + if((data[i] == 0)) break; + if((data[i] > 32) && (data[i] < 127)) { + fprintf(MSG_OUT, "%c", data[i]); + } else { + fprintf(MSG_OUT, " "); + } + } + fprintf(MSG_OUT, "\n"); + } else { + fprintf(MSG_OUT, "libdvdnav: Can't read name block. Probably not a DVD-ROM device.\n"); + } + } else { + fprintf(MSG_OUT, "libdvdnav: Can't seek to block %u\n", 32 ); + } + close(fd); + } else { + fprintf(MSG_OUT, "NAME OPEN FAILED\n"); + } +} + +static int ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN) { + if((vm->state).vtsN == vtsN) { + return 1; /* We alread have it */ + } + + if(vm->vtsi != NULL) + ifoClose(vm->vtsi); + + vm->vtsi = ifoOpenVTSI(dvd, vtsN); + if(vm->vtsi == NULL) { + fprintf(MSG_OUT, "libdvdnav: ifoOpenVTSI failed\n"); + return 0; + } + if(!ifoRead_VTS_PTT_SRPT(vm->vtsi)) { + fprintf(MSG_OUT, "libdvdnav: ifoRead_VTS_PTT_SRPT failed\n"); + return 0; + } + if(!ifoRead_PGCIT(vm->vtsi)) { + fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCIT failed\n"); + return 0; + } + if(!ifoRead_PGCI_UT(vm->vtsi)) { + fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCI_UT failed\n"); + return 0; + } + if(!ifoRead_VOBU_ADMAP(vm->vtsi)) { + fprintf(MSG_OUT, "libdvdnav: ifoRead_VOBU_ADMAP vtsi failed\n"); + return 0; + } + if(!ifoRead_TITLE_VOBU_ADMAP(vm->vtsi)) { + fprintf(MSG_OUT, "libdvdnav: ifoRead_TITLE_VOBU_ADMAP vtsi failed\n"); + return 0; + } + (vm->state).vtsN = vtsN; + + return 1; +} + + +/* Initialisation & Destruction */ + +vm_t* vm_new_vm() { + return (vm_t*)calloc(sizeof(vm_t), sizeof(char)); +} + +void vm_free_vm(vm_t *vm) { + vm_stop(vm); + free(vm); +} + + +/* IFO Access */ + +ifo_handle_t *vm_get_vmgi(vm_t *vm) { + return vm->vmgi; +} + +ifo_handle_t *vm_get_vtsi(vm_t *vm) { + return vm->vtsi; +} + + +/* Reader Access */ + +dvd_reader_t *vm_get_dvd_reader(vm_t *vm) { + return vm->dvd; +} + + +/* Basic Handling */ + +int vm_start(vm_t *vm) { + /* Set pgc to FP (First Play) pgc */ + set_FP_PGC(vm); + process_command(vm, play_PGC(vm)); + return !vm->stopped; +} + +void vm_stop(vm_t *vm) { + if(vm->vmgi) { + ifoClose(vm->vmgi); + vm->vmgi=NULL; + } + if(vm->vtsi) { + ifoClose(vm->vtsi); + vm->vtsi=NULL; + } + if(vm->dvd) { + if (!vm->openedDvd) + DVDClose(vm->dvd); + vm->dvd=NULL; + } + vm->stopped = 1; +} + +int vm_reset(vm_t *vm, const char *dvdroot) { + /* Setup State */ + memset((vm->state).registers.SPRM, 0, sizeof((vm->state).registers.SPRM)); + memset((vm->state).registers.GPRM, 0, sizeof((vm->state).registers.GPRM)); + memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode)); + memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode)); + memset((vm->state).registers.GPRM_time, 0, sizeof((vm->state).registers.GPRM_time)); + (vm->state).registers.SPRM[0] = ('e'<<8)|'n'; /* Player Menu Languange code */ + (vm->state).AST_REG = 15; /* 15 why? */ + (vm->state).SPST_REG = 62; /* 62 why? */ + (vm->state).AGL_REG = 1; + (vm->state).TTN_REG = 1; + (vm->state).VTS_TTN_REG = 1; + /* (vm->state).TT_PGCN_REG = 0 */ + (vm->state).PTTN_REG = 1; + (vm->state).HL_BTNN_REG = 1 << 10; + (vm->state).PTL_REG = 15; /* Parental Level */ + (vm->state).registers.SPRM[12] = ('U'<<8)|'S'; /* Parental Management Country Code */ + (vm->state).registers.SPRM[16] = ('e'<<8)|'n'; /* Initial Language Code for Audio */ + (vm->state).registers.SPRM[18] = ('e'<<8)|'n'; /* Initial Language Code for Spu */ + (vm->state).registers.SPRM[20] = 0x1; /* Player Regional Code Mask. Region free! */ + (vm->state).registers.SPRM[14] = 0x100; /* Try Pan&Scan */ + + (vm->state).pgN = 0; + (vm->state).cellN = 0; + (vm->state).cell_restart = 0; + + (vm->state).domain = FP_DOMAIN; + (vm->state).rsm_vtsN = 0; + (vm->state).rsm_cellN = 0; + (vm->state).rsm_blockN = 0; + + (vm->state).vtsN = -1; + + if (vm->dvd && dvdroot) { + /* a new dvd device has been requested */ + vm_stop(vm); + } + + if (!vm->dvd) { + if (vm->openedDvd) /*JMP we use the dvd opened by copy*/ + vm->dvd=vm->openedDvd; + else + vm->dvd = DVDOpen(dvdroot); + if(!vm->dvd) { + fprintf(MSG_OUT, "libdvdnav: vm: faild to open/read the DVD\n"); + return 0; + } + dvd_read_name(vm->dvd_name, dvdroot); + vm->map = remap_loadmap(vm->dvd_name); + vm->vmgi = ifoOpenVMGI(vm->dvd); + if(!vm->vmgi) { + fprintf(MSG_OUT, "libdvdnav: vm: faild to read VIDEO_TS.IFO\n"); + return 0; + } + if(!ifoRead_FP_PGC(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_FP_PGC failed\n"); + return 0; + } + if(!ifoRead_TT_SRPT(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_TT_SRPT failed\n"); + return 0; + } + if(!ifoRead_PGCI_UT(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PGCI_UT failed\n"); + return 0; + } + if(!ifoRead_PTL_MAIT(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PTL_MAIT failed\n"); + /* return 0; Not really used for now.. */ + } + if(!ifoRead_VTS_ATRT(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VTS_ATRT failed\n"); + /* return 0; Not really used for now.. */ + } + if(!ifoRead_VOBU_ADMAP(vm->vmgi)) { + fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VOBU_ADMAP vgmi failed\n"); + /* return 0; Not really used for now.. */ + } + /* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */ + } + if (vm->vmgi) { + int i, mask; + fprintf(MSG_OUT, "libdvdnav: DVD disk reports itself with Region mask 0x%08x. Regions:", + vm->vmgi->vmgi_mat->vmg_category); + for (i = 1, mask = 1; i <= 8; i++, mask <<= 1) + if (((vm->vmgi->vmgi_mat->vmg_category >> 16) & mask) == 0) + fprintf(MSG_OUT, " %d", i); + fprintf(MSG_OUT, "\n"); + } + return 1; +} + + +/* copying and merging */ + +vm_t *vm_new_copy(vm_t *source) { + vm_t *target = vm_new_vm(); + int vtsN; + int pgcN = get_PGCN(source); + int pgN = (source->state).pgN; + + assert(pgcN); + + memcpy(target, source, sizeof(vm_t)); + + /* open a new vtsi handle, because the copy might switch to another VTS */ + target->vtsi = NULL; + vtsN = (target->state).vtsN; + if (vtsN > 0) { + (target->state).vtsN = 0; + if (!ifoOpenNewVTSI(target, target->dvd, vtsN)) + assert(0); + + /* restore pgc pointer into the new vtsi */ + if (!set_PGCN(target, pgcN)) + assert(0); + (target->state).pgN = pgN; + } + + return target; +} + +void vm_merge(vm_t *target, vm_t *source) { + if(target->vtsi) + ifoClose(target->vtsi); + memcpy(target, source, sizeof(vm_t)); + memset(source, 0, sizeof(vm_t)); +} + +void vm_free_copy(vm_t *vm) { + if(vm->vtsi) + ifoClose(vm->vtsi); + free(vm); +} + + +/* regular playback */ + +void vm_position_get(vm_t *vm, vm_position_t *position) { + position->button = (vm->state).HL_BTNN_REG >> 10; + position->vts = (vm->state).vtsN; + position->domain = (vm->state).domain; + position->spu_channel = (vm->state).SPST_REG; + position->audio_channel = (vm->state).AST_REG; + position->angle_channel = (vm->state).AGL_REG; + position->hop_channel = vm->hop_channel; /* Increases by one on each hop */ + position->cell = (vm->state).cellN; + position->cell_restart = (vm->state).cell_restart; + position->cell_start = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector; + position->still = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].still_time; + position->block = (vm->state).blockN; + + /* handle PGC stills at PGC end */ + if ((vm->state).cellN == (vm->state).pgc->nr_of_cells) + position->still += (vm->state).pgc->still_time; + /* still already determined */ + if (position->still) + return; + /* This is a rough fix for some strange still situations on some strange DVDs. + * There are discs (like the German "Back to the Future" RC2) where the only + * indication of a still is a cell playback time higher than the time the frames + * in this cell actually take to play (like 1 frame with 1 minute playback time). + * On the said BTTF disc, for these cells last_sector and last_vobu_start_sector + * are equal and the cells are very short, so we abuse these conditions to + * detect such discs. I consider these discs broken, so the fix is somewhat + * broken, too. */ + if (((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector == + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_vobu_start_sector) && + ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector - + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector < 1024)) { + int time; + int size = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector - + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector; + time = ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour >> 4 ) * 36000; + time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour & 0x0f) * 3600; + time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute >> 4 ) * 600; + time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute & 0x0f) * 60; + time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second >> 4 ) * 10; + time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second & 0x0f) * 1; + if (!time || size / time > 30) + /* datarate is too high, it might be a very short, but regular cell */ + return; + if (time > 0xff) time = 0xff; + position->still = time; + } +} + +void vm_get_next_cell(vm_t *vm) { + process_command(vm, play_Cell_post(vm)); +} + + +/* Jumping */ + +int vm_jump_pg(vm_t *vm, int pg) { + (vm->state).pgN = pg; + process_command(vm, play_PG(vm)); + return 1; +} + +int vm_jump_cell_block(vm_t *vm, int cell, int block) { + (vm->state).cellN = cell; + process_command(vm, play_Cell(vm)); + /* play_Cell can jump to a different cell in case of angles */ + if ((vm->state).cellN == cell) + (vm->state).blockN = block; + return 1; +} + +int vm_jump_title_part(vm_t *vm, int title, int part) { + link_t link; + + if(!set_PTT(vm, title, part)) + return 0; + /* Some DVDs do not want us to jump directly into a title and have + * PGC pre commands taking us back to some menu. Since we do not like that, + * we do not execute PGC pre commands that would do a jump. */ + /* process_command(vm, play_PGC_PG(vm, (vm->state).pgN)); */ + link = play_PGC_PG(vm, (vm->state).pgN); + if (link.command != PlayThis) + /* jump occured -> ignore it and play the PG anyway */ + process_command(vm, play_PG(vm)); + else + process_command(vm, link); + return 1; +} + +int vm_jump_top_pg(vm_t *vm) { + process_command(vm, play_PG(vm)); + return 1; +} + +int vm_jump_next_pg(vm_t *vm) { + if((vm->state).pgN >= (vm->state).pgc->nr_of_programs) { + /* last program -> move to TailPGC */ + process_command(vm, play_PGC_post(vm)); + return 1; + } else { + vm_jump_pg(vm, (vm->state).pgN + 1); + return 1; + } +} + +int vm_jump_prev_pg(vm_t *vm) { + if ((vm->state).pgN <= 1) { + /* first program -> move to last program of previous PGC */ + if ((vm->state).pgc->prev_pgc_nr && set_PGCN(vm, (vm->state).pgc->prev_pgc_nr)) { + process_command(vm, play_PGC(vm)); + vm_jump_pg(vm, (vm->state).pgc->nr_of_programs); + return 1; + } + return 0; + } else { + vm_jump_pg(vm, (vm->state).pgN - 1); + return 1; + } +} + +int vm_jump_up(vm_t *vm) { + if((vm->state).pgc->goup_pgc_nr && set_PGCN(vm, (vm->state).pgc->goup_pgc_nr)) { + process_command(vm, play_PGC(vm)); + return 1; + } + return 0; +} + +int vm_jump_menu(vm_t *vm, DVDMenuID_t menuid) { + domain_t old_domain = (vm->state).domain; + + switch ((vm->state).domain) { + case VTS_DOMAIN: + set_RSMinfo(vm, 0, (vm->state).blockN); + /* FALL THROUGH */ + case VTSM_DOMAIN: + case VMGM_DOMAIN: + switch(menuid) { + case DVD_MENU_Title: + case DVD_MENU_Escape: + (vm->state).domain = VMGM_DOMAIN; + break; + case DVD_MENU_Root: + case DVD_MENU_Subpicture: + case DVD_MENU_Audio: + case DVD_MENU_Angle: + case DVD_MENU_Part: + (vm->state).domain = VTSM_DOMAIN; + break; + } + if(get_PGCIT(vm) && set_MENU(vm, menuid)) { + process_command(vm, play_PGC(vm)); + return 1; /* Jump */ + } else { + (vm->state).domain = old_domain; + } + break; + case FP_DOMAIN: /* FIXME XXX $$$ What should we do here? */ + break; + } + + return 0; +} + +int vm_jump_resume(vm_t *vm) { + link_t link_values = { LinkRSM, 0, 0, 0 }; + + if (!(vm->state).rsm_vtsN) /* Do we have resume info? */ + return 0; + if (!process_command(vm, link_values)) + return 0; + return 1; +} + +int vm_exec_cmd(vm_t *vm, vm_cmd_t *cmd) { + link_t link_values; + + if(vmEval_CMD(cmd, 1, &(vm->state).registers, &link_values)) + return process_command(vm, link_values); + else + return 0; /* It updated some state thats all... */ +} + + +/* getting information */ + +int vm_get_current_menu(vm_t *vm, int *menuid) { + pgcit_t* pgcit; + int pgcn; + pgcn = (vm->state).pgcN; + pgcit = get_PGCIT(vm); + *menuid = pgcit->pgci_srp[pgcn - 1].entry_id & 0xf ; + return 1; +} + +int vm_get_current_title_part(vm_t *vm, int *title_result, int *part_result) { + vts_ptt_srpt_t *vts_ptt_srpt; + int title, part = 0, vts_ttn; + int found; + int16_t pgcN, pgN; + + vts_ptt_srpt = vm->vtsi->vts_ptt_srpt; + pgcN = get_PGCN(vm); + pgN = vm->state.pgN; + + found = 0; + for (vts_ttn = 0; (vts_ttn < vts_ptt_srpt->nr_of_srpts) && !found; vts_ttn++) { + for (part = 0; (part < vts_ptt_srpt->title[vts_ttn].nr_of_ptts) && !found; part++) { + if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgcn == pgcN) { + if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgn == pgN) { + found = 1; + break; + } + if (part > 0 && vts_ptt_srpt->title[vts_ttn].ptt[part].pgn > pgN && + vts_ptt_srpt->title[vts_ttn].ptt[part - 1].pgn < pgN) { + part--; + found = 1; + break; + } + } + } + if (found) break; + } + vts_ttn++; + part++; + + if (!found) { + fprintf(MSG_OUT, "libdvdnav: chapter NOT FOUND!\n"); + return 0; + } + + title = get_TT(vm, vm->state.vtsN, vts_ttn); + +#ifdef TRACE + if (title) { + fprintf(MSG_OUT, "libdvdnav: ************ this chapter FOUND!\n"); + fprintf(MSG_OUT, "libdvdnav: VTS_PTT_SRPT - Title %3i part %3i: PGC: %3i PG: %3i\n", + title, part, + vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgcn , + vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgn ); + } +#endif + *title_result = title; + *part_result = part; + return 1; +} + +/* Return the substream id for 'logical' audio stream audioN. + * 0 <= audioN < 8 + */ +int vm_get_audio_stream(vm_t *vm, int audioN) { + int streamN = -1; + + if((vm->state).domain != VTS_DOMAIN) + audioN = 0; + + if(audioN < 8) { + /* Is there any control info for this logical stream */ + if((vm->state).pgc->audio_control[audioN] & (1<<15)) { + streamN = ((vm->state).pgc->audio_control[audioN] >> 8) & 0x07; + } + } + + if((vm->state).domain != VTS_DOMAIN && streamN == -1) + streamN = 0; + + /* FIXME: Should also check in vtsi/vmgi status what kind of stream + * it is (ac3/lpcm/dts/sdds...) to find the right (sub)stream id */ + return streamN; +} + +/* Return the substream id for 'logical' subpicture stream subpN and given mode. + * 0 <= subpN < 32 + * mode == 0 - widescreen + * mode == 1 - letterbox + * mode == 2 - pan&scan + */ +int vm_get_subp_stream(vm_t *vm, int subpN, int mode) { + int streamN = -1; + int source_aspect = vm_get_video_aspect(vm); + + if((vm->state).domain != VTS_DOMAIN) + subpN = 0; + + if(subpN < 32) { /* a valid logical stream */ + /* Is this logical stream present */ + if((vm->state).pgc->subp_control[subpN] & (1<<31)) { + if(source_aspect == 0) /* 4:3 */ + streamN = ((vm->state).pgc->subp_control[subpN] >> 24) & 0x1f; + if(source_aspect == 3) /* 16:9 */ + switch (mode) { + case 0: + streamN = ((vm->state).pgc->subp_control[subpN] >> 16) & 0x1f; + break; + case 1: + streamN = ((vm->state).pgc->subp_control[subpN] >> 8) & 0x1f; + break; + case 2: + streamN = (vm->state).pgc->subp_control[subpN] & 0x1f; + } + } + } + + if((vm->state).domain != VTS_DOMAIN && streamN == -1) + streamN = 0; + + /* FIXME: Should also check in vtsi/vmgi status what kind of stream it is. */ + return streamN; +} + +int vm_get_audio_active_stream(vm_t *vm) { + int audioN; + int streamN; + audioN = (vm->state).AST_REG ; + streamN = vm_get_audio_stream(vm, audioN); + + /* If no such stream, then select the first one that exists. */ + if(streamN == -1) { + for(audioN = 0; audioN < 8; audioN++) { + if((vm->state).pgc->audio_control[audioN] & (1<<15)) { + if ((streamN = vm_get_audio_stream(vm, audioN)) >= 0) + break; + } + } + } + + return streamN; +} + +int vm_get_subp_active_stream(vm_t *vm, int mode) { + int subpN; + int streamN; + subpN = (vm->state).SPST_REG & ~0x40; + streamN = vm_get_subp_stream(vm, subpN, mode); + + /* If no such stream, then select the first one that exists. */ + if(streamN == -1) { + for(subpN = 0; subpN < 32; subpN++) { + if((vm->state).pgc->subp_control[subpN] & (1<<31)) { + if ((streamN = vm_get_subp_stream(vm, subpN, mode)) >= 0) + break; + } + } + } + + if((vm->state).domain == VTS_DOMAIN && !((vm->state).SPST_REG & 0x40)) + /* Bit 7 set means hide, and only let Forced display show */ + return (streamN | 0x80); + else + return streamN; +} + +void vm_get_angle_info(vm_t *vm, int *current, int *num_avail) { + *num_avail = 1; + *current = 1; + + if((vm->state).domain == VTS_DOMAIN) { + title_info_t *title; + /* TTN_REG does not allways point to the correct title.. */ + if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts) + return; + title = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1]; + if(title->title_set_nr != (vm->state).vtsN || + title->vts_ttn != (vm->state).VTS_TTN_REG) + return; + *num_avail = title->nr_of_angles; + *current = (vm->state).AGL_REG; + } +} + +#if 0 +/* currently unused */ +void vm_get_audio_info(vm_t *vm, int *current, int *num_avail) { + switch ((vm->state).domain) { + case VTS_DOMAIN: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_audio_streams; + *current = (vm->state).AST_REG; + break; + case VTSM_DOMAIN: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_audio_streams; /* 1 */ + *current = 1; + break; + case VMGM_DOMAIN: + case FP_DOMAIN: + *num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_audio_streams; /* 1 */ + *current = 1; + break; + } +} + +/* currently unused */ +void vm_get_subp_info(vm_t *vm, int *current, int *num_avail) { + switch ((vm->state).domain) { + case VTS_DOMAIN: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_subp_streams; + *current = (vm->state).SPST_REG; + break; + case VTSM_DOMAIN: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_subp_streams; /* 1 */ + *current = 0x41; + break; + case VMGM_DOMAIN: + case FP_DOMAIN: + *num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_subp_streams; /* 1 */ + *current = 0x41; + break; + } +} + +/* currently unused */ +void vm_get_video_res(vm_t *vm, int *width, int *height) { + video_attr_t attr = vm_get_video_attr(vm); + + if(attr.video_format != 0) + *height = 576; + else + *height = 480; + switch(attr.picture_size) { + case 0: + *width = 720; + break; + case 1: + *width = 704; + break; + case 2: + *width = 352; + break; + case 3: + *width = 352; + *height /= 2; + break; + } +} +#endif + +int vm_get_video_aspect(vm_t *vm) { + int aspect = vm_get_video_attr(vm).display_aspect_ratio; + + assert(aspect == 0 || aspect == 3); + (vm->state).registers.SPRM[14] &= ~(0x3 << 10); + (vm->state).registers.SPRM[14] |= aspect << 10; + + return aspect; +} + +int vm_get_video_scale_permission(vm_t *vm) { + return vm_get_video_attr(vm).permitted_df; +} + +video_attr_t vm_get_video_attr(vm_t *vm) { + switch ((vm->state).domain) { + case VTS_DOMAIN: + return vm->vtsi->vtsi_mat->vts_video_attr; + case VTSM_DOMAIN: + return vm->vtsi->vtsi_mat->vtsm_video_attr; + case VMGM_DOMAIN: + case FP_DOMAIN: + return vm->vmgi->vmgi_mat->vmgm_video_attr; + default: + abort(); + } +} + +audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN) { + switch ((vm->state).domain) { + case VTS_DOMAIN: + return vm->vtsi->vtsi_mat->vts_audio_attr[streamN]; + case VTSM_DOMAIN: + return vm->vtsi->vtsi_mat->vtsm_audio_attr; + case VMGM_DOMAIN: + case FP_DOMAIN: + return vm->vmgi->vmgi_mat->vmgm_audio_attr; + default: + abort(); + } +} + +subp_attr_t vm_get_subp_attr(vm_t *vm, int streamN) { + switch ((vm->state).domain) { + case VTS_DOMAIN: + return vm->vtsi->vtsi_mat->vts_subp_attr[streamN]; + case VTSM_DOMAIN: + return vm->vtsi->vtsi_mat->vtsm_subp_attr; + case VMGM_DOMAIN: + case FP_DOMAIN: + return vm->vmgi->vmgi_mat->vmgm_subp_attr; + default: + abort(); + } +} + + +/* Playback control */ + +static link_t play_PGC(vm_t *vm) { + link_t link_values; + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_PGC:"); + if((vm->state).domain != FP_DOMAIN) { + fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm)); + } else { + fprintf(MSG_OUT, " first_play_pgc\n"); + } +#endif + + /* This must be set before the pre-commands are executed because they + * might contain a CallSS that will save resume state */ + + /* FIXME: This may be only a temporary fix for something... */ + (vm->state).pgN = 1; + (vm->state).cellN = 0; + (vm->state).blockN = 0; + + /* eval -> updates the state and returns either + - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN) + - just play video i.e first PG + (This is what happens if you fall of the end of the pre_cmds) + - or an error (are there more cases?) */ + if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) { + if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds, + (vm->state).pgc->command_tbl->nr_of_pre, + &(vm->state).registers, &link_values)) { + /* link_values contains the 'jump' return value */ + return link_values; + } else { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n"); +#endif + } + } + return play_PG(vm); +} + +static link_t play_PGC_PG(vm_t *vm, int pgN) { + link_t link_values; + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_PGC_PG:"); + if((vm->state).domain != FP_DOMAIN) { + fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm)); + } else { + fprintf(MSG_OUT, " first_play_pgc\n"); + } +#endif + + /* This must be set before the pre-commands are executed because they + * might contain a CallSS that will save resume state */ + + /* FIXME: This may be only a temporary fix for something... */ + (vm->state).pgN = pgN; + (vm->state).cellN = 0; + (vm->state).blockN = 0; + + /* eval -> updates the state and returns either + - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN) + - just play video i.e first PG + (This is what happens if you fall of the end of the pre_cmds) + - or an error (are there more cases?) */ + if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) { + if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds, + (vm->state).pgc->command_tbl->nr_of_pre, + &(vm->state).registers, &link_values)) { + /* link_values contains the 'jump' return value */ + return link_values; + } else { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n"); +#endif + } + } + return play_PG(vm); +} + +static link_t play_PGC_post(vm_t *vm) { + link_t link_values; + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_PGC_post:\n"); +#endif + + /* eval -> updates the state and returns either + - some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN) + - just go to next PGC + (This is what happens if you fall of the end of the post_cmds) + - or an error (are there more cases?) */ + if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_post && + vmEval_CMD((vm->state).pgc->command_tbl->post_cmds, + (vm->state).pgc->command_tbl->nr_of_post, + &(vm->state).registers, &link_values)) { + return link_values; + } + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: ** Fell of the end of the pgc, continuing in NextPGC\n"); +#endif + /* Should end up in the STOP_DOMAIN if next_pgc is 0. */ + if(!set_PGCN(vm, (vm->state).pgc->next_pgc_nr)) { + link_values.command = Exit; + return link_values; + } + return play_PGC(vm); +} + +static link_t play_PG(vm_t *vm) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i)\n", (vm->state).pgN); +#endif + + assert((vm->state).pgN > 0); + if((vm->state).pgN > (vm->state).pgc->nr_of_programs) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i) > pgc->nr_of_programs (%i)\n", + (vm->state).pgN, (vm->state).pgc->nr_of_programs ); +#endif + assert((vm->state).pgN == (vm->state).pgc->nr_of_programs + 1); + return play_PGC_post(vm); + } + + (vm->state).cellN = (vm->state).pgc->program_map[(vm->state).pgN - 1]; + + return play_Cell(vm); +} + +static link_t play_Cell(vm_t *vm) { + static const link_t play_this = {PlayThis, /* Block in Cell */ 0, 0, 0}; + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_Cell: (vm->state).cellN (%i)\n", (vm->state).cellN); +#endif + + assert((vm->state).cellN > 0); + if((vm->state).cellN > (vm->state).pgc->nr_of_cells) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: (vm->state).cellN (%i) > pgc->nr_of_cells (%i)\n", + (vm->state).cellN, (vm->state).pgc->nr_of_cells ); +#endif + assert((vm->state).cellN == (vm->state).pgc->nr_of_cells + 1); + return play_PGC_post(vm); + } + + /* Multi angle/Interleaved */ + switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) { + case 0: /* Normal */ + assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0); + break; + case 1: /* The first cell in the block */ + switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) { + case 0: /* Not part of a block */ + assert(0); + break; + case 1: /* Angle block */ + /* Loop and check each cell instead? So we don't get outside the block? */ + (vm->state).cellN += (vm->state).AGL_REG - 1; +#ifdef STRICT + assert((vm->state).cellN <= (vm->state).pgc->nr_of_cells); + assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0); + assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1); +#else + if (!((vm->state).cellN <= (vm->state).pgc->nr_of_cells) || + !((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0) || + !((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1)) { + fprintf(MSG_OUT, "libdvdnav: Invalid angle block\n"); + (vm->state).cellN -= (vm->state).AGL_REG - 1; + } +#endif + break; + case 2: /* ?? */ + case 3: /* ?? */ + default: + fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n", + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode, + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type); + assert(0); + } + break; + case 2: /* Cell in the block */ + case 3: /* Last cell in the block */ + /* These might perhaps happen for RSM or LinkC commands? */ + default: + fprintf(MSG_OUT, "libdvdnav: Cell is in block but did not enter at first cell!\n"); + } + + /* Updates (vm->state).pgN and PTTN_REG */ + if(!set_PGN(vm)) { + /* Should not happen */ + assert(0); + return play_PGC_post(vm); + } + (vm->state).cell_restart++; + (vm->state).blockN = 0; +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Cell should restart here\n"); +#endif + return play_this; +} + +static link_t play_Cell_post(vm_t *vm) { + cell_playback_t *cell; + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: play_Cell_post: (vm->state).cellN (%i)\n", (vm->state).cellN); +#endif + + cell = &(vm->state).pgc->cell_playback[(vm->state).cellN - 1]; + + /* Still time is already taken care of before we get called. */ + + /* Deal with a Cell command, if any */ + if(cell->cell_cmd_nr != 0) { + link_t link_values; + +/* These asserts are now not needed. + * Some DVDs have no cell commands listed in the PGC, + * but the Cell itself points to a cell command that does not exist. + * For this situation, just ignore the cell command and continue. + * + * assert((vm->state).pgc->command_tbl != NULL); + * assert((vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr); + */ + + if ((vm->state).pgc->command_tbl != NULL && + (vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Cell command present, executing\n"); +#endif + if(vmEval_CMD(&(vm->state).pgc->command_tbl->cell_cmds[cell->cell_cmd_nr - 1], 1, + &(vm->state).registers, &link_values)) { + return link_values; + } else { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Cell command didn't do a Jump, Link or Call\n"); +#endif + } + } else { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Invalid Cell command\n"); +#endif + } + } + + /* Where to continue after playing the cell... */ + /* Multi angle/Interleaved */ + switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) { + case 0: /* Normal */ + assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0); + (vm->state).cellN++; + break; + case 1: /* The first cell in the block */ + case 2: /* A cell in the block */ + case 3: /* The last cell in the block */ + default: + switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) { + case 0: /* Not part of a block */ + assert(0); + break; + case 1: /* Angle block */ + /* Skip the 'other' angles */ + (vm->state).cellN++; + while((vm->state).cellN <= (vm->state).pgc->nr_of_cells && + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode >= 2) { + (vm->state).cellN++; + } + break; + case 2: /* ?? */ + case 3: /* ?? */ + default: + fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n", + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode, + (vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type); + assert(0); + } + break; + } + + /* Figure out the correct pgN for the new cell */ + if(!set_PGN(vm)) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: last cell in this PGC\n"); +#endif + return play_PGC_post(vm); + } + return play_Cell(vm); +} + + +/* link processing */ + +static int process_command(vm_t *vm, link_t link_values) { + + while(link_values.command != PlayThis) { + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Before printout starts:\n"); + vm_print_link(link_values); + fprintf(MSG_OUT, "libdvdnav: Link values %i %i %i %i\n", link_values.command, + link_values.data1, link_values.data2, link_values.data3); + vm_print_current_domain_state(vm); + fprintf(MSG_OUT, "libdvdnav: Before printout ends.\n"); +#endif + + switch(link_values.command) { + case LinkNoLink: + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + return 0; /* no actual jump */ + + case LinkTopC: + /* Restart playing from the beginning of the current Cell. */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + link_values = play_Cell(vm); + break; + case LinkNextC: + /* Link to Next Cell */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + (vm->state).cellN += 1; + link_values = play_Cell(vm); + break; + case LinkPrevC: + /* Link to Previous Cell */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + assert((vm->state).cellN > 1); + (vm->state).cellN -= 1; + link_values = play_Cell(vm); + break; + + case LinkTopPG: + /* Link to Top of current Program */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + link_values = play_PG(vm); + break; + case LinkNextPG: + /* Link to Next Program */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + (vm->state).pgN += 1; + link_values = play_PG(vm); + break; + case LinkPrevPG: + /* Link to Previous Program */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + assert((vm->state).pgN > 1); + (vm->state).pgN -= 1; + link_values = play_PG(vm); + break; + + case LinkTopPGC: + /* Restart playing from beginning of current Program Chain */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + link_values = play_PGC(vm); + break; + case LinkNextPGC: + /* Link to Next Program Chain */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + assert((vm->state).pgc->next_pgc_nr != 0); + if(set_PGCN(vm, (vm->state).pgc->next_pgc_nr)) + link_values = play_PGC(vm); + else + link_values.command = Exit; + break; + case LinkPrevPGC: + /* Link to Previous Program Chain */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + assert((vm->state).pgc->prev_pgc_nr != 0); + if(set_PGCN(vm, (vm->state).pgc->prev_pgc_nr)) + link_values = play_PGC(vm); + else + link_values.command = Exit; + break; + case LinkGoUpPGC: + /* Link to GoUp Program Chain */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + assert((vm->state).pgc->goup_pgc_nr != 0); + if(set_PGCN(vm, (vm->state).pgc->goup_pgc_nr)) + link_values = play_PGC(vm); + else + link_values.command = Exit; + break; + case LinkTailPGC: + /* Link to Tail of Program Chain */ + /* BUTTON number:data1 */ + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + link_values = play_PGC_post(vm); + break; + + case LinkRSM: + { + /* Link to Resume point */ + int i; + + /* Check and see if there is any rsm info!! */ + if (!(vm->state).rsm_vtsN) { + fprintf(MSG_OUT, "libdvdnav: trying to resume without any resume info set\n"); + link_values.command = Exit; + break; + } + + (vm->state).domain = VTS_DOMAIN; + if (!ifoOpenNewVTSI(vm, vm->dvd, (vm->state).rsm_vtsN)) + assert(0); + set_PGCN(vm, (vm->state).rsm_pgcN); + + /* These should never be set in SystemSpace and/or MenuSpace */ + /* (vm->state).TTN_REG = rsm_tt; ?? */ + /* (vm->state).TT_PGCN_REG = (vm->state).rsm_pgcN; ?? */ + for(i = 0; i < 5; i++) { + (vm->state).registers.SPRM[4 + i] = (vm->state).rsm_regs[i]; + } + + if(link_values.data1 != 0) + (vm->state).HL_BTNN_REG = link_values.data1 << 10; + + if((vm->state).rsm_cellN == 0) { + assert((vm->state).cellN); /* Checking if this ever happens */ + (vm->state).pgN = 1; + link_values = play_PG(vm); + } else { + /* (vm->state).pgN = ?? this gets the right value in set_PGN() below */ + (vm->state).cellN = (vm->state).rsm_cellN; + link_values.command = PlayThis; + link_values.data1 = (vm->state).rsm_blockN & 0xffff; + link_values.data2 = (vm->state).rsm_blockN >> 16; + if(!set_PGN(vm)) { + /* Were at the end of the PGC, should not happen for a RSM */ + assert(0); + link_values.command = LinkTailPGC; + link_values.data1 = 0; /* No button */ + } + } + } + break; + case LinkPGCN: + /* Link to Program Chain Number:data1 */ + if(!set_PGCN(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case LinkPTTN: + /* Link to Part of current Title Number:data1 */ + /* BUTTON number:data2 */ + /* PGC Pre-Commands are not executed */ + assert((vm->state).domain == VTS_DOMAIN); + if(link_values.data2 != 0) + (vm->state).HL_BTNN_REG = link_values.data2 << 10; + if(!set_VTS_PTT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG, link_values.data1)) + assert(0); + link_values = play_PG(vm); + break; + case LinkPGN: + /* Link to Program Number:data1 */ + /* BUTTON number:data2 */ + if(link_values.data2 != 0) + (vm->state).HL_BTNN_REG = link_values.data2 << 10; + /* Update any other state, PTTN perhaps? */ + (vm->state).pgN = link_values.data1; + link_values = play_PG(vm); + break; + case LinkCN: + /* Link to Cell Number:data1 */ + /* BUTTON number:data2 */ + if(link_values.data2 != 0) + (vm->state).HL_BTNN_REG = link_values.data2 << 10; + /* Update any other state, pgN, PTTN perhaps? */ + (vm->state).cellN = link_values.data1; + link_values = play_Cell(vm); + break; + + case Exit: + vm->stopped = 1; + return 0; + + case JumpTT: + /* Jump to VTS Title Domain */ + /* Only allowed from the First Play domain(PGC) */ + /* or the Video Manager domain (VMG) */ + /* Stop SPRM9 Timer */ + /* Set SPRM1 and SPRM2 */ + assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */ + if(set_TT(vm, link_values.data1)) + link_values = play_PGC(vm); + else + link_values.command = Exit; + break; + case JumpVTS_TT: + /* Jump to Title:data1 in same VTS Title Domain */ + /* Only allowed from the VTS Menu Domain(VTSM) */ + /* or the Video Title Set Domain(VTS) */ + /* Stop SPRM9 Timer */ + /* Set SPRM1 and SPRM2 */ + assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */ + if(!set_VTS_TT(vm, (vm->state).vtsN, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case JumpVTS_PTT: + /* Jump to Part:data2 of Title:data1 in same VTS Title Domain */ + /* Only allowed from the VTS Menu Domain(VTSM) */ + /* or the Video Title Set Domain(VTS) */ + /* Stop SPRM9 Timer */ + /* Set SPRM1 and SPRM2 */ + assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */ + if(!set_VTS_PTT(vm, (vm->state).vtsN, link_values.data1, link_values.data2)) + assert(0); + link_values = play_PGC_PG(vm, (vm->state).pgN); + break; + + case JumpSS_FP: + /* Jump to First Play Domain */ + /* Only allowed from the VTS Menu Domain(VTSM) */ + /* or the Video Manager domain (VMG) */ + /* Stop SPRM9 Timer and any GPRM counters */ + assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN); /* ?? */ + if (!set_FP_PGC(vm)) + assert(0); + link_values = play_PGC(vm); + break; + case JumpSS_VMGM_MENU: + /* Jump to Video Manger domain - Title Menu:data1 or any PGC in VMG */ + /* Allowed from anywhere except the VTS Title domain */ + /* Stop SPRM9 Timer and any GPRM counters */ + assert((vm->state).domain != VTS_DOMAIN); /* ?? */ + (vm->state).domain = VMGM_DOMAIN; + if(!set_MENU(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case JumpSS_VTSM: + /* Jump to a menu in Video Title domain, */ + /* or to a Menu is the current VTS */ + /* Stop SPRM9 Timer and any GPRM counters */ + /* ifoOpenNewVTSI:data1 */ + /* VTS_TTN_REG:data2 */ + /* get_MENU:data3 */ + if(link_values.data1 != 0) { + if (link_values.data1 != (vm->state).vtsN) { + /* the normal case */ + assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */ + (vm->state).domain = VTSM_DOMAIN; + if (!ifoOpenNewVTSI(vm, vm->dvd, link_values.data1)) /* Also sets (vm->state).vtsN */ + assert(0); + } else { + /* This happens on some discs like "Captain Scarlet & the Mysterons" or + * the German RC2 of "Anatomie" in VTSM. */ + assert((vm->state).domain == VTSM_DOMAIN || + (vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */ + (vm->state).domain = VTSM_DOMAIN; + } + } else { + /* This happens on 'The Fifth Element' region 2. */ + assert((vm->state).domain == VTSM_DOMAIN); + } + /* I don't know what title is supposed to be used for. */ + /* Alien or Aliens has this != 1, I think. */ + /* assert(link_values.data2 == 1); */ + (vm->state).VTS_TTN_REG = link_values.data2; + /* TTN_REG (SPRM4), VTS_TTN_REG (SPRM5), TT_PGCN_REG (SPRM6) are linked, */ + /* so if one changes, the others must change to match it. */ + (vm->state).TTN_REG = get_TT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG); + if(!set_MENU(vm, link_values.data3)) + assert(0); + link_values = play_PGC(vm); + break; + case JumpSS_VMGM_PGC: + /* set_PGCN:data1 */ + /* Stop SPRM9 Timer and any GPRM counters */ + assert((vm->state).domain != VTS_DOMAIN); /* ?? */ + (vm->state).domain = VMGM_DOMAIN; + if(!set_PGCN(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + + case CallSS_FP: + /* set_RSMinfo:data1 */ + assert((vm->state).domain == VTS_DOMAIN); /* ?? */ + /* Must be called before domain is changed */ + set_RSMinfo(vm, link_values.data1, /* We dont have block info */ 0); + set_FP_PGC(vm); + link_values = play_PGC(vm); + break; + case CallSS_VMGM_MENU: + /* set_MENU:data1 */ + /* set_RSMinfo:data2 */ + assert((vm->state).domain == VTS_DOMAIN); /* ?? */ + /* Must be called before domain is changed */ + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + (vm->state).domain = VMGM_DOMAIN; + if(!set_MENU(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case CallSS_VTSM: + /* set_MENU:data1 */ + /* set_RSMinfo:data2 */ + assert((vm->state).domain == VTS_DOMAIN); /* ?? */ + /* Must be called before domain is changed */ + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + (vm->state).domain = VTSM_DOMAIN; + if(!set_MENU(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case CallSS_VMGM_PGC: + /* set_PGC:data1 */ + /* set_RSMinfo:data2 */ + assert((vm->state).domain == VTS_DOMAIN); /* ?? */ + /* Must be called before domain is changed */ + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + (vm->state).domain = VMGM_DOMAIN; + if(!set_PGCN(vm, link_values.data1)) + assert(0); + link_values = play_PGC(vm); + break; + case PlayThis: + /* Should never happen. */ + assert(0); + break; + } + +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: After printout starts:\n"); + vm_print_current_domain_state(vm); + fprintf(MSG_OUT, "libdvdnav: After printout ends.\n"); +#endif + + } + (vm->state).blockN = link_values.data1 | (link_values.data2 << 16); + return 1; +} + + +/* Set functions */ + +static int set_TT(vm_t *vm, int tt) { + return set_PTT(vm, tt, 1); +} + +static int set_PTT(vm_t *vm, int tt, int ptt) { + assert(tt <= vm->vmgi->tt_srpt->nr_of_srpts); + return set_VTS_PTT(vm, vm->vmgi->tt_srpt->title[tt - 1].title_set_nr, + vm->vmgi->tt_srpt->title[tt - 1].vts_ttn, ptt); +} + +static int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn) { + return set_VTS_PTT(vm, vtsN, vts_ttn, 1); +} + +static int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part) { + int pgcN, pgN, res; + + (vm->state).domain = VTS_DOMAIN; + + if (vtsN != (vm->state).vtsN) + if (!ifoOpenNewVTSI(vm, vm->dvd, vtsN)) /* Also sets (vm->state).vtsN */ + return 0; + + if ((vts_ttn < 1) || (vts_ttn > vm->vtsi->vts_ptt_srpt->nr_of_srpts) || + (part < 1) || (part > vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].nr_of_ptts) ) { + return 0; + } + + pgcN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgcn; + pgN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgn; + + (vm->state).TT_PGCN_REG = pgcN; + (vm->state).PTTN_REG = part; + (vm->state).TTN_REG = get_TT(vm, vtsN, vts_ttn); + assert( (vm->state.TTN_REG) != 0 ); + (vm->state).VTS_TTN_REG = vts_ttn; + (vm->state).vtsN = vtsN; /* Not sure about this one. We can get to it easily from TTN_REG */ + /* Any other registers? */ + + res = set_PGCN(vm, pgcN); /* This clobber's state.pgN (sets it to 1), but we don't want clobbering here. */ + (vm->state).pgN = pgN; + return res; +} + +static int set_FP_PGC(vm_t *vm) { + (vm->state).domain = FP_DOMAIN; + if (!vm->vmgi->first_play_pgc) { + return set_PGCN(vm, 1); + } + (vm->state).pgc = vm->vmgi->first_play_pgc; + (vm->state).pgcN = vm->vmgi->vmgi_mat->first_play_pgc; + return 1; +} + + +static int set_MENU(vm_t *vm, int menu) { + assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN); + return set_PGCN(vm, get_ID(vm, menu)); +} + +static int set_PGCN(vm_t *vm, int pgcN) { + pgcit_t *pgcit; + + pgcit = get_PGCIT(vm); + assert(pgcit != NULL); /* ?? Make this return -1 instead */ + + if(pgcN < 1 || pgcN > pgcit->nr_of_pgci_srp) { +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: ** No such pgcN = %d\n", pgcN); +#endif + return 0; + } + + (vm->state).pgc = pgcit->pgci_srp[pgcN - 1].pgc; + (vm->state).pgcN = pgcN; + (vm->state).pgN = 1; + + if((vm->state).domain == VTS_DOMAIN) + (vm->state).TT_PGCN_REG = pgcN; + + return 1; +} + +/* Figure out the correct pgN from the cell and update (vm->state). */ +static int set_PGN(vm_t *vm) { + int new_pgN = 0; + + while(new_pgN < (vm->state).pgc->nr_of_programs + && (vm->state).cellN >= (vm->state).pgc->program_map[new_pgN]) + new_pgN++; + + if(new_pgN == (vm->state).pgc->nr_of_programs) /* We are at the last program */ + if((vm->state).cellN > (vm->state).pgc->nr_of_cells) + return 0; /* We are past the last cell */ + + (vm->state).pgN = new_pgN; + + if((vm->state).domain == VTS_DOMAIN) { + playback_type_t *pb_ty; + if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts) + return 0; /* ?? */ + pb_ty = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1].pb_ty; + if(pb_ty->multi_or_random_pgc_title == /* One_Sequential_PGC_Title */ 0) { + int dummy, part; + vm_get_current_title_part(vm, &dummy, &part); + (vm->state).PTTN_REG = part; + } else { + /* FIXME: Handle RANDOM or SHUFFLE titles. */ + fprintf(MSG_OUT, "libdvdnav: RANDOM or SHUFFLE titles are NOT handled yet.\n"); + } + } + return 1; +} + +/* Must be called before domain is changed (set_PGCN()) */ +static void set_RSMinfo(vm_t *vm, int cellN, int blockN) { + int i; + + if(cellN) { + (vm->state).rsm_cellN = cellN; + (vm->state).rsm_blockN = blockN; + } else { + (vm->state).rsm_cellN = (vm->state).cellN; + (vm->state).rsm_blockN = blockN; + } + (vm->state).rsm_vtsN = (vm->state).vtsN; + (vm->state).rsm_pgcN = get_PGCN(vm); + + /* assert((vm->state).rsm_pgcN == (vm->state).TT_PGCN_REG); for VTS_DOMAIN */ + + for(i = 0; i < 5; i++) { + (vm->state).rsm_regs[i] = (vm->state).registers.SPRM[4 + i]; + } +} + + +/* Get functions */ + +/* Searches the TT tables, to find the current TT. + * returns the current TT. + * returns 0 if not found. + */ +static int get_TT(vm_t *vm, int vtsN, int vts_ttn) { + int i; + int tt=0; + + for(i = 1; i <= vm->vmgi->tt_srpt->nr_of_srpts; i++) { + if( vm->vmgi->tt_srpt->title[i - 1].title_set_nr == vtsN && + vm->vmgi->tt_srpt->title[i - 1].vts_ttn == vts_ttn) { + tt=i; + break; + } + } + return tt; +} + +/* Search for entry_id match of the PGC Category in the current VTS PGCIT table. + * Return pgcN based on entry_id match. + */ +static int get_ID(vm_t *vm, int id) { + int pgcN, i; + pgcit_t *pgcit; + + /* Relies on state to get the correct pgcit. */ + pgcit = get_PGCIT(vm); + assert(pgcit != NULL); +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: ** Searching for menu (0x%x) entry PGC\n", id); +#endif + + /* Force high bit set. */ + id |=0x80; + + /* Get menu/title */ + for(i = 0; i < pgcit->nr_of_pgci_srp; i++) { + if( (pgcit->pgci_srp[i].entry_id) == id) { + pgcN = i + 1; +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: Found menu.\n"); +#endif + return pgcN; + } + } +#ifdef TRACE + fprintf(MSG_OUT, "libdvdnav: ** No such id/menu (0x%02x) entry PGC\n", id & 0x7f); + for(i = 0; i < pgcit->nr_of_pgci_srp; i++) { + if ( (pgcit->pgci_srp[i].entry_id & 0x80) == 0x80) { + fprintf(MSG_OUT, "libdvdnav: Available menus: 0x%x\n", + pgcit->pgci_srp[i].entry_id & 0x7f); + } + } +#endif + return 0; /* error */ +} + +/* FIXME: we have a pgcN member in the vm's state now, so this should be obsolete */ +static int get_PGCN(vm_t *vm) { + pgcit_t *pgcit; + int pgcN = 1; + + pgcit = get_PGCIT(vm); + + if (pgcit) { + while(pgcN <= pgcit->nr_of_pgci_srp) { + if(pgcit->pgci_srp[pgcN - 1].pgc == (vm->state).pgc) { + assert((vm->state).pgcN == pgcN); + return pgcN; + } + pgcN++; + } + } + fprintf(MSG_OUT, "libdvdnav: get_PGCN failed. Was trying to find pgcN in domain %d\n", + (vm->state).domain); + return 0; /* error */ +} + +static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang) { + int i; + + if(h == NULL || h->pgci_ut == NULL) { + fprintf(MSG_OUT, "libdvdnav: *** pgci_ut handle is NULL ***\n"); + return NULL; /* error? */ + } + + i = 0; + while(i < h->pgci_ut->nr_of_lus + && h->pgci_ut->lu[i].lang_code != lang) + i++; + if(i == h->pgci_ut->nr_of_lus) { + fprintf(MSG_OUT, "libdvdnav: Language '%c%c' not found, using '%c%c' instead\n", + (char)(lang >> 8), (char)(lang & 0xff), + (char)(h->pgci_ut->lu[0].lang_code >> 8), + (char)(h->pgci_ut->lu[0].lang_code & 0xff)); + fprintf(MSG_OUT, "libdvdnav: Menu Languages available: "); + for(i = 0; i < h->pgci_ut->nr_of_lus; i++) { + fprintf(MSG_OUT, "%c%c ", + (char)(h->pgci_ut->lu[i].lang_code >> 8), + (char)(h->pgci_ut->lu[i].lang_code & 0xff)); + } + fprintf(MSG_OUT, "\n"); + i = 0; /* error? */ + } + + return h->pgci_ut->lu[i].pgcit; +} + +/* Uses state to decide what to return */ +static pgcit_t* get_PGCIT(vm_t *vm) { + pgcit_t *pgcit; + + switch ((vm->state).domain) { + case VTS_DOMAIN: + pgcit = vm->vtsi->vts_pgcit; + break; + case VTSM_DOMAIN: + pgcit = get_MENU_PGCIT(vm, vm->vtsi, (vm->state).registers.SPRM[0]); + break; + case VMGM_DOMAIN: + case FP_DOMAIN: + pgcit = get_MENU_PGCIT(vm, vm->vmgi, (vm->state).registers.SPRM[0]); + break; + default: + abort(); + } + + return pgcit; +} + + +/* Debug functions */ + +#ifdef TRACE +void vm_position_print(vm_t *vm, vm_position_t *position) { + fprintf(MSG_OUT, "libdvdnav: But=%x Spu=%x Aud=%x Ang=%x Hop=%x vts=%x dom=%x cell=%x cell_restart=%x cell_start=%x still=%x block=%x\n", + position->button, + position->spu_channel, + position->audio_channel, + position->angle_channel, + position->hop_channel, + position->vts, + position->domain, + position->cell, + position->cell_restart, + position->cell_start, + position->still, + position->block); +} +#endif + diff --git a/libdvdnav/vm.h b/libdvdnav/vm.h new file mode 100644 index 0000000..e853182 --- /dev/null +++ b/libdvdnav/vm.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2000, 2001 H�an Hjort + * Copyright (C) 2001 Rich Wareham <[email protected]> + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: vm.h,v 1.11 2004/10/05 19:07:10 hadess Exp $ + * + */ + +#ifndef VM_H_INCLUDED +#define VM_H_INCLUDED + +#include "remap.h" +#include "dvdnav_internal.h" + +/* DOMAIN enum */ + +typedef enum { + FP_DOMAIN = 1, + VTS_DOMAIN = 2, + VMGM_DOMAIN = 4, + VTSM_DOMAIN = 8 +} domain_t; + +/** + * State: SPRM, GPRM, Domain, pgc, pgN, cellN, ? + */ +typedef struct { + registers_t registers; + + domain_t domain; + int vtsN; /* 0 is vmgm? */ + pgc_t *pgc; /* either this or 'int pgcN' is enough? */ + int pgcN; /* but provide pgcN for quick lookup */ + int pgN; /* is this needed? can allways fid pgN from cellN? */ + int cellN; + int32_t cell_restart; /* get cell to restart */ + int blockN; + + /* Resume info */ + int rsm_vtsN; + int rsm_blockN; /* of nav_packet */ + uint16_t rsm_regs[5]; /* system registers 4-8 */ + int rsm_pgcN; + int rsm_cellN; +} dvd_state_t; + +typedef struct vm_position_s { + int16_t button; /* Button highlighted */ + int32_t vts; /* vts number to use */ + domain_t domain; /* domain to use */ + int32_t spu_channel; /* spu channel to use */ + int32_t angle_channel; /* angle channel to use */ + int32_t audio_channel; /* audio channel to use */ + int32_t hop_channel; /* channel hopping. E.g menu button pressed */ +#if 0 + /* currently unused */ + int32_t title; /* title number */ + int32_t chapter; /* chapter number */ +#endif + int32_t cell; /* cell number */ + int32_t cell_restart; /* get cell to restart */ + int32_t cell_start; /* sector number of start of current cell in use */ + int32_t still; /* is cell still */ + int32_t block; /* block number within cell in use */ +} vm_position_t; + +typedef struct { + dvd_reader_t *dvd,*openedDvd; + ifo_handle_t *vmgi; + ifo_handle_t *vtsi; + dvd_state_t state; + int32_t hop_channel; + char dvd_name[50]; + remap_t *map; + int stopped; +} vm_t; + +/* magic number for seeking hops */ +#define HOP_SEEK 0x1000 + + +/* Audio stream number */ +#define AST_REG registers.SPRM[1] +/* Subpicture stream number */ +#define SPST_REG registers.SPRM[2] +/* Angle number */ +#define AGL_REG registers.SPRM[3] +/* Title Track Number */ +#define TTN_REG registers.SPRM[4] +/* VTS Title Track Number */ +#define VTS_TTN_REG registers.SPRM[5] +/* PGC Number for this Title Track */ +#define TT_PGCN_REG registers.SPRM[6] +/* Current Part of Title (PTT) number for (One_Sequential_PGC_Title) */ +#define PTTN_REG registers.SPRM[7] +/* Highlighted Button Number (btn nr 1 == value 1024) */ +#define HL_BTNN_REG registers.SPRM[8] +/* Parental Level */ +#define PTL_REG registers.SPRM[13] + +/* Initialisation & destruction */ +vm_t *vm_new_vm(void); +void vm_free_vm(vm_t *vm); + +/* IFO access */ +ifo_handle_t *vm_get_vmgi(vm_t *vm); +ifo_handle_t *vm_get_vtsi(vm_t *vm); + +/* Reader Access */ +dvd_reader_t *vm_get_dvd_reader(vm_t *vm); + +/* Basic Handling */ +int vm_start(vm_t *vm); +void vm_stop(vm_t *vm); +int vm_reset(vm_t *vm, const char *dvdroot); + +/* copying and merging - useful for try-running an operation */ +vm_t *vm_new_copy(vm_t *vm); +void vm_merge(vm_t *target, vm_t *source); +void vm_free_copy(vm_t *vm); + +/* regular playback */ +void vm_position_get(vm_t *vm, vm_position_t *position); +void vm_get_next_cell(vm_t *vm); + +/* Jumping - all these return 1, if a hop has been performed */ +int vm_jump_pg(vm_t *vm, int pg); +int vm_jump_cell_block(vm_t *vm, int cell, int block); +int vm_jump_title_part(vm_t *vm, int title, int part); +int vm_jump_top_pg(vm_t *vm); +int vm_jump_next_pg(vm_t *vm); +int vm_jump_prev_pg(vm_t *vm); +int vm_jump_up(vm_t *vm); +int vm_jump_menu(vm_t *vm, DVDMenuID_t menuid); +int vm_jump_resume(vm_t *vm); +int vm_exec_cmd(vm_t *vm, vm_cmd_t *cmd); + +/* getting information */ +int vm_get_current_menu(vm_t *vm, int *menuid); +int vm_get_current_title_part(vm_t *vm, int *title_result, int *part_result); +int vm_get_audio_stream(vm_t *vm, int audioN); +int vm_get_subp_stream(vm_t *vm, int subpN, int mode); +int vm_get_audio_active_stream(vm_t *vm); +int vm_get_subp_active_stream(vm_t *vm, int mode); +void vm_get_angle_info(vm_t *vm, int *current, int *num_avail); +#if 0 +/* currently unused */ +void vm_get_audio_info(vm_t *vm, int *current, int *num_avail); +void vm_get_subp_info(vm_t *vm, int *current, int *num_avail); +void vm_get_video_res(vm_t *vm, int *width, int *height); +#endif +int vm_get_video_aspect(vm_t *vm); +int vm_get_video_scale_permission(vm_t *vm); +video_attr_t vm_get_video_attr(vm_t *vm); +audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN); +subp_attr_t vm_get_subp_attr(vm_t *vm, int streamN); + +#ifdef TRACE +/* Debug */ +void vm_position_print(vm_t *vm, vm_position_t *position); +#endif + + +#endif /* VM_HV_INCLUDED */ diff --git a/libdvdnav/vmcmd.c b/libdvdnav/vmcmd.c new file mode 100644 index 0000000..76f0a4b --- /dev/null +++ b/libdvdnav/vmcmd.c @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2000, 2001 Martin Norb�k, H�an Hjort + * 2002-2004 the dvdnav project + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: vmcmd.c,v 1.6 2004/03/16 11:43:38 mroi Exp $ + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <ctype.h> +#include <inttypes.h> + +#include "dvdnav_internal.h" + +/* freebsd compatibility */ +#ifndef PRIu8 +#define PRIu8 "d" +#endif + +/* freebsd compatibility */ +#ifndef PRIu16 +#define PRIu16 "d" +#endif + +static const char *cmp_op_table[] = { + NULL, "&", "==", "!=", ">=", ">", "<=", "<" +}; +static const char *set_op_table[] = { + NULL, "=", "<->", "+=", "-=", "*=", "/=", "%=", "rnd", "&=", "|=", "^=" +}; + +static const char *link_table[] = { + "LinkNoLink", "LinkTopC", "LinkNextC", "LinkPrevC", + NULL, "LinkTopPG", "LinkNextPG", "LinkPrevPG", + NULL, "LinkTopPGC", "LinkNextPGC", "LinkPrevPGC", + "LinkGoUpPGC", "LinkTailPGC", NULL, NULL, + "RSM" +}; + +static const char *system_reg_table[] = { + "Menu Description Language Code", + "Audio Stream Number", + "Sub-picture Stream Number", + "Angle Number", + "Title Track Number", + "VTS Title Track Number", + "VTS PGC Number", + "PTT Number for One_Sequential_PGC_Title", + "Highlighted Button Number", + "Navigation Timer", + "Title PGC Number for Navigation Timer", + "Audio Mixing Mode for Karaoke", + "Country Code for Parental Management", + "Parental Level", + "Player Configurations for Video", + "Player Configurations for Audio", + "Initial Language Code for Audio", + "Initial Language Code Extension for Audio", + "Initial Language Code for Sub-picture", + "Initial Language Code Extension for Sub-picture", + "Player Regional Code", + "Reserved 21", + "Reserved 22", + "Reserved 23" +}; + +static const char *system_reg_abbr_table[] = { + NULL, + "ASTN", + "SPSTN", + "AGLN", + "TTN", + "VTS_TTN", + "TT_PGCN", + "PTTN", + "HL_BTNN", + "NVTMR", + "NV_PGCN", + NULL, + "CC_PLT", + "PLT", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static void print_system_reg(uint16_t reg) { + if(reg < sizeof(system_reg_abbr_table) / sizeof(char *)) + fprintf(MSG_OUT, "%s (SRPM:%d)", system_reg_table[reg], reg); + else + fprintf(MSG_OUT, " WARNING: Unknown system register ( reg=%d ) ", reg); +} + +static void print_g_reg(uint8_t reg) { + if(reg < 16) + fprintf(MSG_OUT, "g[%" PRIu8 "]", reg); + else + fprintf(MSG_OUT, " WARNING: Unknown general register "); +} + +static void print_reg(uint8_t reg) { + if(reg & 0x80) + print_system_reg(reg & 0x7f); + else + print_g_reg(reg & 0x7f); +} + +static void print_cmp_op(uint8_t op) { + if(op < sizeof(cmp_op_table) / sizeof(char *) && cmp_op_table[op] != NULL) + fprintf(MSG_OUT, " %s ", cmp_op_table[op]); + else + fprintf(MSG_OUT, " WARNING: Unknown compare op "); +} + +static void print_set_op(uint8_t op) { + if(op < sizeof(set_op_table) / sizeof(char *) && set_op_table[op] != NULL) + fprintf(MSG_OUT, " %s ", set_op_table[op]); + else + fprintf(MSG_OUT, " WARNING: Unknown set op "); +} + +static void print_reg_or_data(command_t* command, int immediate, int start) { + if(immediate) { + uint32_t i = vm_getbits(command, start, 16); + + fprintf(MSG_OUT, "0x%x", i); + if(isprint(i & 0xff) && isprint((i>>8) & 0xff)) + fprintf(MSG_OUT, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff)); + } else { + print_reg(vm_getbits(command, start - 8, 8)); + } +} + +static void print_reg_or_data_2(command_t* command, int immediate, int start) { + if(immediate) + fprintf(MSG_OUT, "0x%x", vm_getbits(command, start - 1, 7)); + else + fprintf(MSG_OUT, "g[%" PRIu8 "]", vm_getbits(command, start - 4, 4)); +} + +static void print_reg_or_data_3(command_t* command, int immediate, int start) { + if(immediate) { + uint32_t i = vm_getbits(command, start, 16); + + fprintf(MSG_OUT, "0x%x", i); + if(isprint(i & 0xff) && isprint((i>>8) & 0xff)) + fprintf(MSG_OUT, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff)); + } else { + print_reg(vm_getbits(command, start, 8)); + } +} + + +static void print_if_version_1(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + + if(op) { + fprintf(MSG_OUT, "if ("); + print_g_reg(vm_getbits(command,39,8)); + print_cmp_op(op); + print_reg_or_data(command, vm_getbits(command, 55,1), 31); + fprintf(MSG_OUT, ") "); + } +} + +static void print_if_version_2(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + + if(op) { + fprintf(MSG_OUT, "if ("); + print_reg(vm_getbits(command, 15, 8)); + print_cmp_op(op); + print_reg(vm_getbits(command, 7, 8)); + fprintf(MSG_OUT, ") "); + } +} + +static void print_if_version_3(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + + if(op) { + fprintf(MSG_OUT, "if ("); + print_g_reg(vm_getbits(command, 43, 4)); + print_cmp_op(op); + print_reg_or_data(command, vm_getbits(command, 55, 1), 15); + fprintf(MSG_OUT, ") "); + } +} + +static void print_if_version_4(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + + if(op) { + fprintf(MSG_OUT, "if ("); + print_g_reg(vm_getbits(command, 51, 4)); + print_cmp_op(op); + print_reg_or_data(command, vm_getbits(command, 55, 1), 31); + fprintf(MSG_OUT, ") "); + } +} + +static void print_if_version_5(command_t* command) { + uint8_t op = vm_getbits(command, 54, 3); + int set_immediate = vm_getbits(command, 60, 1); + + if(op) { + if (set_immediate) { + fprintf(MSG_OUT, "if ("); + print_g_reg(vm_getbits(command, 31, 8)); + print_cmp_op(op); + print_reg(vm_getbits(command, 23, 8)); + fprintf(MSG_OUT, ") "); + } else { + fprintf(MSG_OUT, "if ("); + print_g_reg(vm_getbits(command, 39, 8)); + print_cmp_op(op); + print_reg_or_data(command, vm_getbits(command, 55, 1), 31); + fprintf(MSG_OUT, ") "); + } + } +} + +static void print_special_instruction(command_t* command) { + uint8_t op = vm_getbits(command, 51, 4); + + switch(op) { + case 0: /* NOP */ + fprintf(MSG_OUT, "Nop"); + break; + case 1: /* Goto line */ + fprintf(MSG_OUT, "Goto %" PRIu8, vm_getbits(command, 7, 8)); + break; + case 2: /* Break */ + fprintf(MSG_OUT, "Break"); + break; + case 3: /* Parental level */ + fprintf(MSG_OUT, "SetTmpPML %" PRIu8 ", Goto %" PRIu8, + vm_getbits(command, 11, 4), vm_getbits(command, 7, 8)); + break; + default: + fprintf(MSG_OUT, "WARNING: Unknown special instruction (%i)", + vm_getbits(command, 51, 4)); + } +} + +static void print_linksub_instruction(command_t* command) { + uint32_t linkop = vm_getbits(command, 7, 8); + uint32_t button = vm_getbits(command, 15, 6); + + if(linkop < sizeof(link_table)/sizeof(char *) && link_table[linkop] != NULL) + fprintf(MSG_OUT, "%s (button %" PRIu8 ")", link_table[linkop], button); + else + fprintf(MSG_OUT, "WARNING: Unknown linksub instruction (%i)", linkop); +} + +static void print_link_instruction(command_t* command, int optional) { + uint8_t op = vm_getbits(command, 51, 4); + + if(optional && op) + fprintf(MSG_OUT, ", "); + + switch(op) { + case 0: + if(!optional) + fprintf(MSG_OUT, "WARNING: NOP (link)!"); + break; + case 1: + print_linksub_instruction(command); + break; + case 4: + fprintf(MSG_OUT, "LinkPGCN %" PRIu16, vm_getbits(command, 14, 15)); + break; + case 5: + fprintf(MSG_OUT, "LinkPTT %" PRIu16 " (button %" PRIu8 ")", + vm_getbits(command, 9, 10), vm_getbits(command, 15, 6)); + break; + case 6: + fprintf(MSG_OUT, "LinkPGN %" PRIu8 " (button %" PRIu8 ")", + vm_getbits(command, 6, 7), vm_getbits(command, 15, 6)); + break; + case 7: + fprintf(MSG_OUT, "LinkCN %" PRIu8 " (button %" PRIu8 ")", + vm_getbits(command, 7, 8), vm_getbits(command, 15, 6)); + break; + default: + fprintf(MSG_OUT, "WARNING: Unknown link instruction"); + } +} + +static void print_jump_instruction(command_t* command) { + switch(vm_getbits(command, 51, 4)) { + case 1: + fprintf(MSG_OUT, "Exit"); + break; + case 2: + fprintf(MSG_OUT, "JumpTT %" PRIu8, vm_getbits(command, 22, 7)); + break; + case 3: + fprintf(MSG_OUT, "JumpVTS_TT %" PRIu8, vm_getbits(command, 22, 7)); + break; + case 5: + fprintf(MSG_OUT, "JumpVTS_PTT %" PRIu8 ":%" PRIu16, + vm_getbits(command, 22, 7), vm_getbits(command, 41, 10)); + break; + case 6: + switch(vm_getbits(command, 23, 2)) { + case 0: + fprintf(MSG_OUT, "JumpSS FP"); + break; + case 1: + fprintf(MSG_OUT, "JumpSS VMGM (menu %" PRIu8 ")", vm_getbits(command, 19, 4)); + break; + case 2: + fprintf(MSG_OUT, "JumpSS VTSM (vts %" PRIu8 ", title %" PRIu8 + ", menu %" PRIu8 ")", vm_getbits(command, 30, 7), vm_getbits(command, 38, 7), vm_getbits(command, 19, 4)); + break; + case 3: + fprintf(MSG_OUT, "JumpSS VMGM (pgc %" PRIu8 ")", vm_getbits(command, 46, 15)); + break; + } + break; + case 8: + switch(vm_getbits(command, 23, 2)) { + case 0: + fprintf(MSG_OUT, "CallSS FP (rsm_cell %" PRIu8 ")", + vm_getbits(command, 31, 8)); + break; + case 1: + fprintf(MSG_OUT, "CallSS VMGM (menu %" PRIu8 + ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8)); + break; + case 2: + fprintf(MSG_OUT, "CallSS VTSM (menu %" PRIu8 + ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8)); + break; + case 3: + fprintf(MSG_OUT, "CallSS VMGM (pgc %" PRIu8 ", rsm_cell %" PRIu8 ")", + vm_getbits(command, 46, 15), vm_getbits(command, 31, 8)); + break; + } + break; + default: + fprintf(MSG_OUT, "WARNING: Unknown Jump/Call instruction"); + } +} + +static void print_system_set(command_t* command) { + int i; +/* FIXME: What about SPRM11 ? Karaoke */ +/* Surely there must be some system set command for that ? */ + + switch(vm_getbits(command, 59, 4)) { + case 1: /* Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */ + for(i = 1; i <= 3; i++) { + if(vm_getbits(command, 47 - (i*8), 1)) { + print_system_reg(i); + fprintf(MSG_OUT, " = "); + print_reg_or_data_2(command, vm_getbits(command, 60, 1), 47 - (i*8) ); + fprintf(MSG_OUT, " "); + } + } + break; + case 2: /* Set system reg 9 & 10 (Navigation timer, Title PGC number) */ + print_system_reg(9); + fprintf(MSG_OUT, " = "); + print_reg_or_data(command, vm_getbits(command, 60, 1), 47); + fprintf(MSG_OUT, " "); + print_system_reg(10); + fprintf(MSG_OUT, " = %" PRIu16, vm_getbits(command, 30, 15)); /* ?? */ + break; + case 3: /* Mode: Counter / Register + Set */ + fprintf(MSG_OUT, "SetMode "); + if(vm_getbits(command, 23, 1)) + fprintf(MSG_OUT, "Counter "); + else + fprintf(MSG_OUT, "Register "); + print_g_reg(vm_getbits(command, 19, 4)); + print_set_op(0x1); /* '=' */ + print_reg_or_data(command, vm_getbits(command, 60, 1), 47); + break; + case 6: /* Set system reg 8 (Highlighted button) */ + print_system_reg(8); + if(vm_getbits(command, 60, 1)) /* immediate */ + fprintf(MSG_OUT, " = 0x%x (button no %d)", vm_getbits(command, 31, 16), vm_getbits(command, 31, 6)); + else + fprintf(MSG_OUT, " = g[%" PRIu8 "]", vm_getbits(command, 19, 4)); + break; + default: + fprintf(MSG_OUT, "WARNING: Unknown system set instruction (%i)", + vm_getbits(command, 59, 4)); + } +} + +static void print_set_version_1(command_t* command) { + uint8_t set_op = vm_getbits(command, 59, 4); + + if(set_op) { + print_g_reg(vm_getbits(command, 35, 4)); + print_set_op(set_op); + print_reg_or_data(command, vm_getbits(command, 60, 1), 31); + } else { + fprintf(MSG_OUT, "NOP"); + } +} + +static void print_set_version_2(command_t* command) { + uint8_t set_op = vm_getbits(command, 59, 4); + + if(set_op) { + print_g_reg(vm_getbits(command, 51, 4)); + print_set_op(set_op); + print_reg_or_data(command, vm_getbits(command, 60, 1), 47); + } else { + fprintf(MSG_OUT, "NOP"); + } +} + +static void print_set_version_3(command_t* command) { + uint8_t set_op = vm_getbits(command, 59, 4); + + if(set_op) { + print_g_reg(vm_getbits(command, 51, 4)); + print_set_op(set_op); + print_reg_or_data_3(command, vm_getbits(command, 60, 1), 47); + } else { + fprintf(MSG_OUT, "NOP"); + } +} + + +void vm_print_mnemonic(vm_cmd_t *vm_command) { + command_t command; + command.instruction =( (uint64_t) vm_command->bytes[0] << 56 ) | + ( (uint64_t) vm_command->bytes[1] << 48 ) | + ( (uint64_t) vm_command->bytes[2] << 40 ) | + ( (uint64_t) vm_command->bytes[3] << 32 ) | + ( (uint64_t) vm_command->bytes[4] << 24 ) | + ( (uint64_t) vm_command->bytes[5] << 16 ) | + ( (uint64_t) vm_command->bytes[6] << 8 ) | + (uint64_t) vm_command->bytes[7] ; + command.examined = 0; + + switch(vm_getbits(&command,63,3)) { /* three first bits */ + case 0: /* Special instructions */ + print_if_version_1(&command); + print_special_instruction(&command); + break; + case 1: /* Jump/Call or Link instructions */ + if(vm_getbits(&command,60,1)) { + print_if_version_2(&command); + print_jump_instruction(&command); + } else { + print_if_version_1(&command); + print_link_instruction(&command, 0); /* must be pressent */ + } + break; + case 2: /* Set System Parameters instructions */ + print_if_version_2(&command); + print_system_set(&command); + print_link_instruction(&command, 1); /* either 'if' or 'link' */ + break; + case 3: /* Set General Parameters instructions */ + print_if_version_3(&command); + print_set_version_1(&command); + print_link_instruction(&command, 1); /* either 'if' or 'link' */ + break; + case 4: /* Set, Compare -> LinkSub instructions */ + print_set_version_2(&command); + fprintf(MSG_OUT, ", "); + print_if_version_4(&command); + print_linksub_instruction(&command); + break; + case 5: /* Compare -> (Set and LinkSub) instructions */ + print_if_version_5(&command); + fprintf(MSG_OUT, "{ "); + print_set_version_3(&command); + fprintf(MSG_OUT, ", "); + print_linksub_instruction(&command); + fprintf(MSG_OUT, " }"); + break; + case 6: /* Compare -> Set, always LinkSub instructions */ + print_if_version_5(&command); + fprintf(MSG_OUT, "{ "); + print_set_version_3(&command); + fprintf(MSG_OUT, " } "); + print_linksub_instruction(&command); + break; + default: + fprintf(MSG_OUT, "WARNING: Unknown instruction type (%i)", vm_getbits(&command, 63, 3)); + } + /* Check if there still are bits set that were not examined */ + + if(command.instruction & ~ command.examined) { + fprintf(MSG_OUT, " libdvdnav: vmcmd.c: [WARNING, unknown bits:"); + fprintf(MSG_OUT, " %08llx", (command.instruction & ~ command.examined) ); + fprintf(MSG_OUT, "]"); + } +} + +void vm_print_cmd(int row, vm_cmd_t *vm_command) { + int i; + + fprintf(MSG_OUT, "(%03d) ", row + 1); + for(i = 0; i < 8; i++) + fprintf(MSG_OUT, "%02x ", vm_command->bytes[i]); + fprintf(MSG_OUT, "| "); + + vm_print_mnemonic(vm_command); + fprintf(MSG_OUT, "\n"); +} + diff --git a/libdvdnav/vmcmd.h b/libdvdnav/vmcmd.h new file mode 100644 index 0000000..2e823f9 --- /dev/null +++ b/libdvdnav/vmcmd.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2000, 2001 Martin Norb�k, H�an Hjort + * + * This file is part of libdvdnav, a DVD navigation library. It is modified + * from a file originally part of the Ogle DVD player. + * + * libdvdnav is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libdvdnav 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: vmcmd.h,v 1.7 2003/05/11 13:44:05 jcdutton Exp $ + * + */ + +#ifndef VMCMD_H_INCLUDED +#define VMCMD_H_INCLUDED + +#include "common.h" + +void vm_print_mnemonic(vm_cmd_t *command); +void vm_print_cmd(int row, vm_cmd_t *command); + +#endif /* VMCMD_H_INCLUDED */ |