diff options
Diffstat (limited to 'libdvdnav-embedded/src')
25 files changed, 9760 insertions, 0 deletions
diff --git a/libdvdnav-embedded/src/dvdnav.c b/libdvdnav-embedded/src/dvdnav.c new file mode 100644 index 0000000..4ef7d1a --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav.c @@ -0,0 +1,1470 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Un-comment the following to enable additional Log3() function calls. + * Do not forget to #define TRACE in src/vm/vm.h (required) */ +/* +#define LOG_DEBUG +*/ + +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> +#include <sys/time.h> +#include "dvdnav/dvdnav.h" +#include <dvdread/dvd_reader.h> +#include <dvdread/nav_types.h> +#include "vm/decoder.h" +#include "vm/vm.h" +#include "vm/getset.h" +#include "dvdnav_internal.h" +#include "logger.h" +#include "read_cache.h" +#include <dvdread/nav_read.h> + +static dvdnav_status_t dvdnav_clear(dvdnav_t * this) { + /* clear everything except file, vm, mutex, readahead */ + + pthread_mutex_lock(&this->vm_lock); + if (this->file) DVDCloseFile(this->file); + this->file = NULL; + + memset(&this->position_current,0,sizeof(this->position_current)); + 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->skip_still = 0; + this->sync_wait = 0; + this->sync_wait_skip = 0; + this->spu_clut_changed = 0; + this->started = 0; + this->cur_cell_time = 0; + + dvdnav_read_cache_clear(this->cache); + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_dup(dvdnav_t **dest, dvdnav_t *src) { + dvdnav_t *this; + + (*dest) = NULL; + this = (dvdnav_t*)malloc(sizeof(dvdnav_t)); + if (!this) + return DVDNAV_STATUS_ERR; + + memcpy(this, src, sizeof(dvdnav_t)); + this->file = NULL; + this->vm = NULL; + this->path = NULL; + this->cache = NULL; + + pthread_mutex_init(&this->vm_lock, NULL); + + this->vm = vm_new_copy(src->vm); + if (!this->vm) + goto fail; + + this->path = strdup(src->path); + if (!this->path) + goto fail; + + /* Start the read-ahead cache. */ + this->cache = dvdnav_read_cache_new(this); + if (!this->cache) + goto fail; + + (*dest) = this; + return DVDNAV_STATUS_OK; + +fail: + printerr("Error initialising the DVD VM."); + pthread_mutex_destroy(&this->vm_lock); + vm_free_vm(this->vm); + free(this->path); + free(this); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_free_dup(dvdnav_t *this) { + +#ifdef LOG_DEBUG + Log3(this, "free_dup:called"); +#endif + + if (this->file) { + pthread_mutex_lock(&this->vm_lock); + DVDCloseFile(this->file); +#ifdef LOG_DEBUG + Log3(this, "close:file closing"); +#endif + this->file = NULL; + pthread_mutex_unlock(&this->vm_lock); + } + + /* Free the VM */ + if(this->vm) + vm_free_copy(this->vm); + + pthread_mutex_destroy(&this->vm_lock); + + free(this->path); + + /* 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; +} + +static dvdnav_status_t dvdnav_open_common(dvdnav_t** dest, + void *priv, const dvdnav_logger_cb *logcb, + const char *path, + dvdnav_stream_cb *stream_cb) { + dvdnav_t *this; + struct timeval time; + + /* Create a new structure */ + (*dest) = NULL; + this = (dvdnav_t*)calloc(1, sizeof(dvdnav_t)); + if(!this) + return DVDNAV_STATUS_ERR; + + this->priv = priv; + if(logcb) + this->logcb = *logcb; + + pthread_mutex_init(&this->vm_lock, NULL); + /* Initialise the error string */ + printerr(""); + + /* Initialise the VM */ + this->vm = vm_new_vm(priv, logcb); + if(!this->vm) { + goto fail; + } + if(!vm_reset(this->vm, path, priv, stream_cb)) { + goto fail; + } + + /* Set the path. */ + if(path != NULL) + { + this->path = strdup(path); + if(!this->path) + goto fail; + } + + /* 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); + if(!this->cache) + goto fail; + + /* 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; + +fail: + pthread_mutex_destroy(&this->vm_lock); + vm_free_vm(this->vm); + free(this->path); + free(this); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_open(dvdnav_t** dest, const char *path) { + return dvdnav_open_common(dest, NULL, NULL, path, NULL); +} + +dvdnav_status_t dvdnav_open2(dvdnav_t** dest, + void *priv,const dvdnav_logger_cb *logcb, + const char *path) { + return dvdnav_open_common(dest, priv, logcb, path, NULL); +} + +dvdnav_status_t dvdnav_open_stream(dvdnav_t** dest, + void *priv, dvdnav_stream_cb *stream_cb) { + return dvdnav_open_common(dest, priv, NULL, NULL, stream_cb); +} + +dvdnav_status_t dvdnav_open_stream2(dvdnav_t** dest, + void *priv,const dvdnav_logger_cb *logcb, + dvdnav_stream_cb *stream_cb) { + return dvdnav_open_common(dest, priv, logcb, NULL, stream_cb); +} + +dvdnav_status_t dvdnav_close(dvdnav_t *this) { + +#ifdef LOG_DEBUG + Log3(this, "close:called"); +#endif + + if (this->file) { + pthread_mutex_lock(&this->vm_lock); + DVDCloseFile(this->file); +#ifdef LOG_DEBUG + Log3(this, "close:file closing"); +#endif + this->file = NULL; + pthread_mutex_unlock(&this->vm_lock); + } + + /* Free the VM */ + if(this->vm) + vm_free_vm(this->vm); + + pthread_mutex_destroy(&this->vm_lock); + + free(this->path); + + /* 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 + Log3(this, "reset:called"); +#endif + + pthread_mutex_lock(&this->vm_lock); + +#ifdef LOG_DEBUG + Log3(this, "resetting vm"); +#endif + if(!vm_reset(this->vm, NULL, NULL, NULL)) { + printerr("Error restarting the VM."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } +#ifdef LOG_DEBUG + Log3(this, "clearing dvdnav"); +#endif + pthread_mutex_unlock(&this->vm_lock); + result = dvdnav_clear(this); + + return result; +} + +dvdnav_status_t dvdnav_path(dvdnav_t *this, const char** path) { + (*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(const dvd_time_t *time) { + int64_t result; + int64_t frames; + + result = ((int64_t)(time->hour >> 4 )) * 10 * 60 * 60 * 90000; + result += ((int64_t)(time->hour & 0x0f)) * 60 * 60 * 90000; + result += ((int64_t)(time->minute >> 4 )) * 10 * 60 * 90000; + result += ((int64_t)(time->minute & 0x0f)) * 60 * 90000; + result += ((int64_t)(time->second >> 4 )) * 10 * 90000; + result += ((int64_t)(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)) { + Log1(this, "demux error! %02x %02x %02x (should be 0x000001)",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 + char buffer[80 * 3 + 1]; + int32_t i; + for(i=0;i<80;i++) + sprintf(&buffer[i*3], "%02x ",p[i-6]); + Log3(this, "nav packet=%u %s",p-p_start-6, buffer); +#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)) { + Log3(this, "angle ends!"); + + /* This is to switch back to angle one when we + * finish with angles. */ + dvdnav_angle_change(this, 1); + } +#endif + /* only use ILVU information if we are at the last vobunit in ILVU */ + /* otherwise we will miss nav packets from vobunits inbetween */ + if(num_angle != 0 && (nav_dsi->sml_pbi.category & DSI_ILVU_MASK) == (DSI_ILVU_BLOCK | DSI_ILVU_LAST)) { + + 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 targeting 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; +} + +int64_t dvdnav_get_current_time(dvdnav_t *this) { + int i; + int64_t tm=0; + dvd_state_t *state = &this->vm->state; + + if(! state->pgc) + return 0; + + for(i=0; i<state->cellN-1; i++) { + if(! + (state->pgc->cell_playback[i].block_type == BLOCK_TYPE_ANGLE_BLOCK && + state->pgc->cell_playback[i].block_mode != BLOCK_MODE_FIRST_CELL) + ) + tm += dvdnav_convert_time(&state->pgc->cell_playback[i].playback_time); + } + tm += this->cur_cell_time; + + return tm; +} + +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) + 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"); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->started = 1; + } + + if (!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + 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 + Log3(this->vm, "POS-NEXT "); + vm_position_print(this->vm, &this->position_next); + Log3(this->vm, "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 + Log3(this->vm, "HOP_CHANNEL"); +#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 + Log3(this, "HIGHLIGHT"); +#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 + Log3(this, "WAIT"); +#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 or open + * a new one because we don't currently have an opened VOB. */ + if((this->file == NULL) || + (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 DVD_DOMAIN_FirstPlay: + case DVD_DOMAIN_VMGM: + domain = DVD_READ_MENU_VOBS; + vtsN = 0; + break; + case DVD_DOMAIN_VTSMenu: + domain = DVD_READ_MENU_VOBS; + vtsN = this->position_next.vts; + break; + case DVD_DOMAIN_VTSTitle: + 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 + Log3(this, "VTS_CHANGE"); +#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; + + this->cur_cell_time = 0; + (*event) = DVDNAV_CELL_CHANGE; +#ifdef LOG_DEBUG + Log3(this, "CELL_CHANGE"); +#endif + (*len) = sizeof(dvdnav_cell_change_event_t); + + cell_event->cellN = state->cellN; + cell_event->pgN = state->pgN; + cell_event->cell_length = + (state->pgc->cell_playback[state->cellN - 1].last_sector + 1); + + 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 += + (state->pgc->cell_playback[i - 1].last_sector + 1); + + 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 += + (state->pgc->cell_playback[i - 1].last_sector + 1); + + cell_event->pg_start = 0; + for (i = 1; i < state->pgc->program_map[state->pgN-1]; i++) + cell_event->pg_start += + (state->pgc->cell_playback[i - 1].last_sector + 1); + + 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 + Log3(this, "SPU_CLUT_CHANGE"); +#endif + (*len) = 16 * sizeof(uint32_t); + memcpy(*buf, state->pgc->palette, sizeof(state->pgc->palette)); + 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 + Log3(this, "SPU_STREAM_CHANGE"); +#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 + Log3(this, "SPU_STREAM_CHANGE stream_id_wide=%d",stream_change->physical_wide); + Log3(this, "SPU_STREAM_CHANGE stream_id_letterbox=%d",stream_change->physical_letterbox); + Log3(this, "SPU_STREAM_CHANGE stream_id_pan_scan=%d",stream_change->physical_pan_scan); + Log3(this, "SPU_STREAM_CHANGE returning DVDNAV_STATUS_OK"); +#endif + /* This is not really the right place to do this. FOSL_BTNN should set the register + * at HLI_S_PTM rather than when we enter the SPU. As well we should activate FOAC_BTNN + * at HLI_E_PTM + */ + if (this->pci.hli.hl_gi.fosl_btnn != 0) { + set_HL_BTN(this->vm, this->pci.hli.hl_gi.fosl_btnn); + } + + 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 + Log3(this, "AUDIO_STREAM_CHANGE"); +#endif + (*len) = sizeof(dvdnav_audio_stream_change_event_t); + stream_change->physical = vm_get_audio_active_stream( this->vm ); + stream_change->logical = this->position_next.audio_channel; + this->position_current.audio_channel = this->position_next.audio_channel; +#ifdef LOG_DEBUG + Log3(this, "AUDIO_STREAM_CHANGE stream_id=%d returning DVDNAV_STATUS_OK",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 + Log3(this, "STILL_FRAME"); +#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 + Log3(this, "Still set to %x", 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; + + if(!this->position_current.still || 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; + } + + /* 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 + Log3(this, "NAV_PACKET"); +#endif + (*len) = 2048; + this->cur_cell_time = dvdnav_convert_time(&this->dsi.dsi_gi.c_eltm); + 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) { + (*title_str) = this->vm->dvd_name; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_serial_string(dvdnav_t *this, const char **serial_str) { + (*serial_str) = this->vm->dvd_serial; + return DVDNAV_STATUS_OK; +} + +const char * dvdnav_get_volid_string(dvdnav_t *this) { + if (!this || !this->vm || !this->vm->dvd) { + printerr("Invalid state, vm or reader not available."); + return NULL; + } + + char *volid_str = malloc(33); + if (volid_str == NULL) { + printerr("Insufficient memory available."); + return NULL; + } + + if (DVDUDFVolumeInfo(this->vm->dvd, volid_str, 32, NULL, 0) == -1) { + if (DVDISOVolumeInfo(this->vm->dvd, volid_str, 33, NULL, 0) == -1) { + printerr("Failed to obtain volume id."); + free(volid_str); + return NULL; + } + } + return volid_str; +} + +uint8_t dvdnav_get_video_aspect(dvdnav_t *this) { + uint8_t retval; + + 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; +} +dvdnav_status_t dvdnav_get_video_resolution(dvdnav_t *this, uint32_t *width, uint32_t *height) { + int w, h; + + if(!this->started) { + printerr("Virtual DVD machine not started."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + vm_get_video_res(this->vm, &w, &h); + pthread_mutex_unlock(&this->vm_lock); + + *width = w; + *height = h; + return DVDNAV_STATUS_OK; +} + +uint8_t dvdnav_get_video_scale_permission(dvdnav_t *this) { + uint8_t retval; + + 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->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_audio_stream_format(dvdnav_t *this, uint8_t stream) { + audio_attr_t attr; + uint16_t format; + + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; /* 0xffff */ + } + + pthread_mutex_lock(&this->vm_lock); + attr = vm_get_audio_attr(this->vm, stream); + pthread_mutex_unlock(&this->vm_lock); + + switch(attr.audio_format) { + case 0: + format = DVD_AUDIO_FORMAT_AC3; + break; + case 2: /* MPEG-1 or MPEG-2 without extension bitstream. */ + case 3: /* MPEG-2 with extension bitstream. */ + format = DVD_AUDIO_FORMAT_MPEG2_EXT; + break; + case 4: + format = DVD_AUDIO_FORMAT_LPCM; + break; + case 6: + format = DVD_AUDIO_FORMAT_DTS; + break; + case 7: + format = DVD_AUDIO_FORMAT_SDDS; + break; + default: + format = 0xffff; + break; + } + + return format; +} + +uint16_t dvdnav_audio_stream_channels(dvdnav_t *this, uint8_t stream) { + audio_attr_t attr; + + if(!this->started) { + printerr("Virtual DVD machine not started."); + return -1; /* 0xffff */ + } + + pthread_mutex_lock(&this->vm_lock); + attr = vm_get_audio_attr(this->vm, stream); + pthread_mutex_unlock(&this->vm_lock); + + return attr.channels + 1; +} + +uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *this, uint8_t stream) { + subp_attr_t attr; + + 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->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_number_of_streams(dvdnav_t *this, dvdnav_stream_type_t stream_type) { + + if (stream_type != DVD_SUBTITLE_STREAM && stream_type != DVD_AUDIO_STREAM) { + printerr("Invalid provided stream type"); + 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; + } + + if (this->vm->state.domain != DVD_DOMAIN_VTSTitle && + this->vm->state.domain != DVD_DOMAIN_VTSMenu) + { + printerr("Invalid domain provided"); + pthread_mutex_unlock(&this->vm_lock); + return -1; + } + + int8_t count = 0; + switch (stream_type) { + case DVD_SUBTITLE_STREAM: + for (int i = 0; i < 32; i++) + { + if (this->vm->state.pgc->subp_control[i] & (1<<31)) + count++; + } + break; + case DVD_AUDIO_STREAM: + for (int i = 0; i < 8; i++) + { + if (this->vm->state.pgc->audio_control[i] & (1<<15)) + count++; + } + break; + } + pthread_mutex_unlock(&this->vm_lock); + return count; +} + +dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *this, uint8_t audio_num, audio_attr_t *audio_attr) { + if(!this->started) { + printerr("Virtual DVD machine not started."); + 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; + } + *audio_attr=vm_get_audio_attr(this->vm, audio_num); + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +int8_t dvdnav_get_spu_logical_stream(dvdnav_t *this, uint8_t subp_num) { + int8_t retval; + + 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; +} + +dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *this, uint8_t audio_num, subp_attr_t *subp_attr) { + if(!this->started) { + printerr("Virtual DVD machine not started."); + 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; + } + *subp_attr=vm_get_subp_attr(this->vm, audio_num); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +int8_t dvdnav_get_active_audio_stream(dvdnav_t *this) { + int8_t retval; + + 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->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; +} + +dvdnav_status_t dvdnav_set_active_stream(dvdnav_t *this, uint8_t stream_num, dvdnav_stream_type_t stream_type) { + if (stream_type != DVD_SUBTITLE_STREAM && stream_type != DVD_AUDIO_STREAM) { + printerr("Invalid provided stream type"); + return DVDNAV_STATUS_ERR; + } + + if (!this->started) { + printerr("Virtual DVD machine not started."); + 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; + } + + if (this->vm->state.domain != DVD_DOMAIN_VTSTitle && + this->vm->state.domain != DVD_DOMAIN_VTSMenu) + { + printerr("Invalid active domain"); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + switch (stream_type) { + case DVD_SUBTITLE_STREAM: + if (stream_num >= 32 || + !(this->vm->state.pgc->subp_control[stream_num] & (1 << 31))) { + printerr("Invalid stream index not allowed"); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + // set state without changing the current visibility + this->vm->state.SPST_REG = stream_num | (this->vm->state.SPST_REG & 0x40); + break; + case DVD_AUDIO_STREAM: + if (stream_num >= 8 || + !(this->vm->state.pgc->audio_control[stream_num] & (1 << 15))) { + printerr("Invalid stream index not allowed"); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->vm->state.AST_REG = stream_num; + break; + } + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_toggle_spu_stream(dvdnav_t *this, uint8_t visibility) { + if(!this->started) { + printerr("Virtual DVD machine not started."); + return DVDNAV_STATUS_ERR; + } + + pthread_mutex_lock(&this->vm_lock); + switch(visibility) { + case 0: /* disable */ + this->vm->state.SPST_REG &= ~0x40; + break; + case 1: /* enable */ + this->vm->state.SPST_REG |= 0x40; + break; + default: + printerr("Invalid provided enabled_flag value"); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; +} + +static int8_t dvdnav_is_domain(dvdnav_t *this, DVDDomain_t domain) { + int8_t retval; + + 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, DVD_DOMAIN_FirstPlay); +} +/* Video management Menu domain. (Menu) */ +int8_t dvdnav_is_domain_vmgm(dvdnav_t *this) { + return dvdnav_is_domain(this, DVD_DOMAIN_VMGM); +} +/* Video Title Menu domain (Menu) */ +int8_t dvdnav_is_domain_vtsm(dvdnav_t *this) { + return dvdnav_is_domain(this, DVD_DOMAIN_VTSMenu); +} +/* Video Title domain (playing movie). */ +int8_t dvdnav_is_domain_vts(dvdnav_t *this) { + return dvdnav_is_domain(this, DVD_DOMAIN_VTSTitle); +} + +/* Generally delegate angle information handling to VM */ +dvdnav_status_t dvdnav_angle_change(dvdnav_t *this, int32_t angle) { + int32_t num, current; + + 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) { + 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; +} + +dvdnav_status_t dvdnav_get_disk_region_mask(dvdnav_t *this, int32_t *region_mask) { + pthread_mutex_lock(&this->vm_lock); + if (!this->vm || !this->vm->vmgi || !this->vm->vmgi->vmgi_mat) { + printerr("Bad VM state."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + (*region_mask) = ((this->vm->vmgi->vmgi_mat->vmg_category >> 16) & 0xff) ^ 0xff; + 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; +} + +user_ops_t dvdnav_get_restrictions(dvdnav_t* this) { + /* + * user_ops_t is a structure of 32 bits. We want to compute + * the union of two of those bitfields so to make this quicker + * than performing 32 ORs, we will access them as 32bits words. + */ + union { + user_ops_t ops_struct; + uint32_t ops_int; + } ops, tmp; + + ops.ops_int = 0; + + if(!this || !this->started) { + printerr("Virtual DVD machine not started."); + return ops.ops_struct; + } + + pthread_mutex_lock(&this->vm_lock); + ops.ops_struct = this->pci.pci_gi.vobu_uop_ctl; + + if(this->vm && this->vm->state.pgc) { + tmp.ops_struct = this->vm->state.pgc->prohibited_ops; + ops.ops_int |= tmp.ops_int; + } + pthread_mutex_unlock(&this->vm_lock); + + return ops.ops_struct; +} + +const char* dvdnav_version(void) { + return VERSION; +} diff --git a/libdvdnav-embedded/src/dvdnav/dvd_types.h b/libdvdnav-embedded/src/dvdnav/dvd_types.h new file mode 100644 index 0000000..04547a1 --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav/dvd_types.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2000, 2001 Björn Englund, Håkan 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Various useful structs and enums for DVDs. + */ + +#ifndef LIBDVDNAV_DVD_TYPES_H +#define LIBDVDNAV_DVD_TYPES_H + +#include <stdint.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; + +/* + * Stream Types + * (see dvdnav_get_number_of_streams()) + */ +typedef enum { + DVD_SUBTITLE_STREAM = 0, + DVD_AUDIO_STREAM = 1 +} dvdnav_stream_type_t; + +/* Domain */ +typedef enum { + DVD_DOMAIN_FirstPlay = 1, /* First Play Domain */ + DVD_DOMAIN_VTSTitle = 2, /* Video Title Set Domain */ + DVD_DOMAIN_VMGM = 4, /* Video Manager Domain */ + DVD_DOMAIN_VTSMenu = 8 /* Video Title Set Menu Domain */ +} DVDDomain_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 audio format */ +typedef enum { + DVD_AUDIO_FORMAT_AC3 = 0, + DVD_AUDIO_FORMAT_UNKNOWN_1 = 1, + DVD_AUDIO_FORMAT_MPEG = 2, + DVD_AUDIO_FORMAT_MPEG2_EXT = 3, + DVD_AUDIO_FORMAT_LPCM = 4, + DVD_AUDIO_FORMAT_UNKNOWN_5 = 5, + DVD_AUDIO_FORMAT_DTS = 6, + DVD_AUDIO_FORMAT_SDDS = 7 +} DVDAudioFormat_t; + +/* the following types are currently unused */ + +#if 0 + +/* 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; + +/* 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 /* LIBDVDNAV_DVD_TYPES_H */ diff --git a/libdvdnav-embedded/src/dvdnav/dvdnav.h b/libdvdnav-embedded/src/dvdnav/dvdnav.h new file mode 100644 index 0000000..85136a4 --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav/dvdnav.h @@ -0,0 +1,797 @@ +/* + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This is the main header file applications should include if they want + * to access dvdnav functionality. + */ + +#ifndef LIBDVDNAV_DVDNAV_H +#define LIBDVDNAV_DVDNAV_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "version.h" +#include <dvdnav/dvd_types.h> +#include <dvdread/dvd_reader.h> +#include <dvdread/nav_types.h> +#include <dvdnav/dvdnav_events.h> + +#include <stdarg.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; + +typedef dvd_reader_stream_cb dvdnav_stream_cb; + +/* + * 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 * + *********************************************************************/ + +/* + * Logger callback definition + */ +typedef enum +{ + DVDNAV_LOGGER_LEVEL_INFO, + DVDNAV_LOGGER_LEVEL_ERROR, + DVDNAV_LOGGER_LEVEL_WARN, + DVDNAV_LOGGER_LEVEL_DEBUG, +} dvdnav_logger_level_t; + +typedef struct +{ + void ( *pf_log ) ( void *, dvdnav_logger_level_t, const char *, va_list ); +} dvdnav_logger_cb; + +/* + * 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 or using external + * seek/read functions (dvdnav_open_stream) and pre-cache the CSS-keys. + * libdvdread is used to access the DVD, so any source supported by libdvdread + * can be given with "path" or "stream_cb". Currently, using dvdnav_open, + * libdvdread can access : DVD drives, DVD image files, DVD file-by-file + * copies. Using dvdnav_open_stream, libdvdread can access any kind of DVD + * storage via custom implementation of seek/read functions. + * + * The resulting dvdnav_t handle will be written to *dest. + */ +dvdnav_status_t dvdnav_open(dvdnav_t **dest, const char *path); +dvdnav_status_t +dvdnav_open_stream(dvdnav_t **dest, void *priv, dvdnav_stream_cb *stream_cb); + +dvdnav_status_t dvdnav_open2(dvdnav_t **dest, + void *, const dvdnav_logger_cb *, + const char *path); +dvdnav_status_t dvdnav_open_stream2(dvdnav_t **dest, + void *priv, const dvdnav_logger_cb *, + dvdnav_stream_cb *stream_cb); + +dvdnav_status_t dvdnav_dup(dvdnav_t **dest, dvdnav_t *src); +dvdnav_status_t dvdnav_free_dup(dvdnav_t * _this); + +/* + * 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); + +const char* dvdnav_version(void); + +/********************************************************************* + * 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 engine 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. + * In 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); + +/* + * Returns the region mask (bit 0 set implies region 1, bit 1 set implies + * region 2, etc) reported by the dvd disc being played. + * + * Note this has no relation with the region setting of the DVD drive. + * Old DVD drives (RPC-I) used to delegate most of the RCE handling to the CPU and + * will actually call the virtual machine (VM) for its region setting. In those cases, + * changing the VM region mask via dvdnav_set_region_mask() will circunvent + * the region protection scheme. This is no longer the case with more recent (RPC-II) drives + * as RCE is handled internally by the drive firmware. + * + */ +dvdnav_status_t dvdnav_get_disk_region_mask(dvdnav_t *self, int32_t *region_mask); + +/********************************************************************* + * 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); + +/* + * Returns the number of angles for the given title. + */ +dvdnav_status_t dvdnav_get_number_of_angles(dvdnav_t *self, int32_t title, int32_t *angles); + +/* + * 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); + +/* + * Plays the specified title, starting from the specified program + */ +dvdnav_status_t dvdnav_program_play(dvdnav_t *self, int32_t title, int32_t pgcn, int32_t pgn); + +/* + * Stores in *times an array (that the application *must* free) of + * dvdtimes corresponding to the chapter times for the chosen title. + * *duration will have the duration of the title + * The number of entries in *times is the result of the function. + * On error *times is NULL and the output is 0 + */ +uint32_t dvdnav_describe_title_chapters(dvdnav_t *self, int32_t title, uint64_t **times, uint64_t *duration); + +/* + * 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 title number, pgcn and pgn currently being played. + * A title of 0 indicates, we are in a menu. + */ +dvdnav_status_t dvdnav_current_title_program(dvdnav_t *self, int32_t *title, + int32_t *pgcn, int32_t *pgn); + +/* + * 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, + int64_t offset, int32_t origin); + +/* + returns the current stream time in PTS ticks as reported by the IFO structures + divide it by 90000 to get the current play time in seconds + */ +int64_t dvdnav_get_current_time(dvdnav_t *self); + +/* + * Stop playing the current position and start playback of the title + * from the specified timecode. + * + * Currently implemented using interpolation. That interpolation is slightly + * inaccurate. + */ +dvdnav_status_t dvdnav_time_search(dvdnav_t *self, + uint64_t time); + +/* + * Find the nearest vobu and jump to it + * + * Alternative to dvdnav_time_search (see full documentation on searching.jump_to_time.readme) + * Jumps to the provided PTS (which is defined as time_in_ms * 90). mode means the navigation mode, + * currently only the Default (0) is implemented: + * 0: Default. Jump to a time which may be either <> time_in_pts_ticks + * 1: After. Always jump to a time that is > time_in_pts_ticks + * -1: Before. Always jump to a time that is < time_in_pts_ticks + */ +dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *self, + uint64_t time_in_pts_ticks, int32_t mode); + +/* + * Stop playing current position and play the "GoUp"-program chain. + * (which generally leads to the title menu or a higher-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 such a packet from libdvdnav, this will result in + * errors for players with internal FIFOs 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 singularly + * 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' (see also dvdnav_get_volid_string). + */ +dvdnav_status_t dvdnav_get_title_string(dvdnav_t *self, const char **title_str); + +/* + * Returns a string containing the serial number of the DVD. + * This has a max of 15 characters and should be more unique than the + * title string. + */ +dvdnav_status_t dvdnav_get_serial_string(dvdnav_t *self, const char **serial_str); + +/* + * Returns the VolumeIdentifier of the disc or NULL if it could + * not be obtained. The VolumeIdentifier might be latin-1 encoded + * (8bit unicode) null terminated and max 32 bytes (including '\0'); + * or coded with '0-9','A-Z','_' null terminated and max 33 bytes + * (including '\0'). + * See also dvdnav_get_title_string + * + * Note: The string is malloc'd so caller has to free() the returned + * string when done with it. + */ +const char * dvdnav_get_volid_string(dvdnav_t *self); + +/* + * 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 resolution. + */ +dvdnav_status_t dvdnav_get_video_resolution(dvdnav_t *self, uint32_t *width, uint32_t *height); + +/* + * 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); + +/* + * Returns the format of *logical* audio stream 'stream' + * (returns 0xffff if no such stream). + */ +uint16_t dvdnav_audio_stream_format(dvdnav_t *self, uint8_t stream); + +/* + * Returns number of channels in *logical* audio stream 'stream' + * (returns 0xffff if no such stream). + */ +uint16_t dvdnav_audio_stream_channels(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); + +#define HAVE_GET_AUDIO_ATTR +/* + * Get audio attr + */ +dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *self, uint8_t audio_mum, audio_attr_t *audio_attr); + +/* + * 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); + +#define HAVE_GET_SPU_ATTR +/* + * Get spu attr + */ +dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *self, uint8_t audio_mum, subp_attr_t *subp_attr); + +/* + * 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); + +/* + * Get the set of user operations that are currently prohibited. + * There are potentially new restrictions right after + * DVDNAV_CHANNEL_HOP and DVDNAV_NAV_PACKET. + */ +user_ops_t dvdnav_get_restrictions(dvdnav_t *self); + +/* + * Returns the number of streams provided its type (e.g. subtitles, audio, etc) + */ +int8_t dvdnav_get_number_of_streams(dvdnav_t *self, dvdnav_stream_type_t stream_type); + + +/********************************************************************* + * setting stream attributes * + *********************************************************************/ + +/* + * Set the visible (enable) status of the current spu stream + * (to enable/disable subtitles) + * visibility defines if the spu stream should be enabled/visible (1) or disabled (0) + */ +dvdnav_status_t dvdnav_toggle_spu_stream(dvdnav_t *self, uint8_t visibility); + +/* + * Set the given stream id and stream type as active + * stream_num - the physical index of the stream + * stream_type - the stream type (audio or subtitles) + */ +dvdnav_status_t dvdnav_set_active_stream(dvdnav_t *self, uint8_t stream_num, + dvdnav_stream_type_t stream_type); + +/********************************************************************* + * 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 existent 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 /* LIBDVDNAV_DVDNAV_H */ diff --git a/libdvdnav-embedded/src/dvdnav/dvdnav_events.h b/libdvdnav-embedded/src/dvdnav/dvdnav_events.h new file mode 100644 index 0000000..a7dc6da --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav/dvdnav_events.h @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This header defines events and event types + */ + +#ifndef LIBDVDNAV_DVDNAV_EVENTS_H +#define LIBDVDNAV_DVDNAV_EVENTS_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 */ + DVDDomain_t old_domain; /* the old domain */ + int new_vtsN; /* the new VTS number */ + DVDDomain_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 sectors */ + int64_t pg_length; /* the length of the current program in sectors */ + int64_t pgc_length; /* the length of the current program chain in PTS ticks */ + int64_t cell_start; /* the start offset of the current cell relatively to the PGC in sectors */ + int64_t pg_start; /* the start offset of the current PG relatively to the PGC in sectors */ +} 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 occurred. + * 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 /* LIBDVDNAV_DVDNAV_EVENTS_H */ diff --git a/libdvdnav-embedded/src/dvdnav/version.h.in b/libdvdnav-embedded/src/dvdnav/version.h.in new file mode 100644 index 0000000..9304e8d --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav/version.h.in @@ -0,0 +1,35 @@ +/* +* This file is part of libdvdnav, a DVD navigation library. +* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +*/ +#ifndef LIBDVDNAV_VERSION_H +#define LIBDVDNAV_VERSION_H + +#define DVDNAV_VERSION_CODE(major, minor, micro) \ + (((major) * 10000) + \ + ((minor) * 100) + \ + ((micro) * 1)) + +#define DVDNAV_VERSION_MAJOR @DVDNAV_MAJOR@ +#define DVDNAV_VERSION_MINOR @DVDNAV_MINOR@ +#define DVDNAV_VERSION_MICRO @DVDNAV_SUB@ + +#define DVDNAV_VERSION_STRING "@DVDNAV_MAJOR@.@DVDNAV_MINOR@.@DVDNAV_SUB@" + +#define DVDNAV_VERSION \ + DVDNAV_VERSION_CODE(DVDNAV_VERSION_MAJOR, DVDNAV_VERSION_MINOR, DVDNAV_VERSION_MICRO) + +#endif diff --git a/libdvdnav-embedded/src/dvdnav_internal.h b/libdvdnav-embedded/src/dvdnav_internal.h new file mode 100644 index 0000000..3b49aa2 --- /dev/null +++ b/libdvdnav-embedded/src/dvdnav_internal.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2001-2004 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBDVDNAV_DVDNAV_INTERNAL_H +#define LIBDVDNAV_DVDNAV_INTERNAL_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#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) DeleteCriticalSection(a) + +#ifndef HAVE_GETTIMEOFDAY +/* 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)) +#endif + +#include <io.h> /* read() */ +#define lseek64 _lseeki64 + +#else + +#include <pthread.h> + +#endif /* _WIN32 */ + +#ifdef __ANDROID__ +# undef lseek +# define lseek lseek64 +# undef off_t +# define off_t off64_t +#endif + +/* where should libdvdnav write its messages (stdout/stderr) */ +#define MSG_OUT stderr + +/* Maximum length of an error string */ +#define MAX_ERR_LEN 255 + +#ifndef DVD_VIDEO_LB_LEN +#define DVD_VIDEO_LB_LEN 2048 +#endif + +typedef enum { + DSI_ILVU_PRE = 1 << 15, /* set during the last 3 VOBU preceding an interleaved block. */ + DSI_ILVU_BLOCK = 1 << 14, /* set for all VOBU in an interleaved block */ + DSI_ILVU_FIRST = 1 << 13, /* set for the first VOBU for a given angle or scene within a ILVU, or the first VOBU in the preparation (PREU) sequence */ + DSI_ILVU_LAST = 1 << 12, /* set for the last VOBU for a given angle or scene within a ILVU, or the last VOBU in the preparation (PREU) sequence */ + DSI_ILVU_MASK = 0xf000 +} DSI_ILVU; + +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 + +/* + * Describes a given time, and the closest sector, vobu and tmap index + */ +typedef struct { + uint64_t time; + uint32_t sector; + uint32_t vobu_idx; + int32_t tmap_idx; +} dvdnav_pos_data_t; + +/* + * Encapsulates cell data + */ +typedef struct { + int32_t idx; + dvdnav_pos_data_t *bgn; + dvdnav_pos_data_t *end; +} dvdnav_cell_data_t; + +/* + * Encapsulates common variables used by internal functions of jump_to_time + */ +typedef struct { + vobu_admap_t *admap; + int32_t admap_len; + const vts_tmap_t *tmap; + int32_t tmap_len; + int32_t tmap_interval; +} dvdnav_jump_args_t; + +/* + * Utility constants for jump_to_time + */ +#define TMAP_IDX_EDGE_BGN -1 +#define TMAP_IDX_EDGE_END -2 +#define JUMP_MODE_TIME_AFTER 1 +#define JUMP_MODE_TIME_DEFAULT 0 +#define JUMP_MODE_TIME_BEFORE -1 + +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; /* 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 */ + int cur_cell_time; /* time expired since the beginning of the current cell, read from the dsi */ + + /* VM */ + vm_t *vm; + pthread_mutex_t vm_lock; + + /* private context and logger*/ + void *priv; + dvdnav_logger_cb logcb; + + /* Read-ahead cache */ + read_cache_t *cache; + + /* Errors */ + char err_str[MAX_ERR_LEN]; +}; + +/** HELPER FUNCTIONS **/ + +/* converts a dvd_time_t to PTS ticks */ +int64_t dvdnav_convert_time(const dvd_time_t *time); + +/** USEFUL MACROS **/ + +#ifdef __GNUC__ +#define printerrf(format, args...) \ + do { if (this) snprintf(this->err_str, MAX_ERR_LEN, format, ## args); } while (0) +#else +#ifdef _MSC_VER +#define printerrf(str) \ + do { if (this) snprintf(this->err_str, MAX_ERR_LEN, str); } while (0) +#else +#define printerrf(...) \ + do { if (this) snprintf(this->err_str, MAX_ERR_LEN, __VA_ARGS__); } while (0) +#endif /* _MSC_VER */ +#endif +#define printerr(str) \ + do { if (this) strncpy(this->err_str, str, MAX_ERR_LEN - 1); } while (0) + +#endif /* LIBDVDNAV_DVDNAV_INTERNAL_H */ diff --git a/libdvdnav-embedded/src/highlight.c b/libdvdnav-embedded/src/highlight.c new file mode 100644 index 0000000..9954347 --- /dev/null +++ b/libdvdnav-embedded/src/highlight.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> +#include <sys/time.h> +#include <dvdread/nav_types.h> +#include "dvdnav/dvdnav.h" +#include "vm/decoder.h" +#include "vm/vm.h" +#include "vm/vmcmd.h" +#include "dvdnav_internal.h" + +/* +#define BUTTON_TESTING +*/ + +#ifdef BUTTON_TESTING + +#include <dvdread/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) { + /* 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 const btni_t *get_current_button(dvdnav_t *this, pci_t *pci) { + int32_t button = 0; + + 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) { + const btni_t *button_ptr; + if ((button_ptr = get_current_button(this, pci)) == NULL) + return DVDNAV_STATUS_ERR; + + if (button_ptr->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) { + const 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) { + const 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) { + const 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) { + const 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) { + const 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; + const btni_t *button_ptr = NULL; + + 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); + + if(!this->vm->state.pgc) { + printerr("No current PGC."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + 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 <mathieu_lacage@realmagic.fr> + * 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; + } + + if ((button_ptr = get_current_button(this, pci)) == NULL) { + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + + /* Finally, make the VM execute the appropriate code and probably + * schedule 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) +{ + 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 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(!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 Highlight 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(!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++) { + const 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-embedded/src/logger.c b/libdvdnav-embedded/src/logger.c new file mode 100644 index 0000000..ae06ff6 --- /dev/null +++ b/libdvdnav-embedded/src/logger.c @@ -0,0 +1,41 @@ +/* +* This file is part of libdvdnav, a DVD navigation library. +* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +*/ + +#include "config.h" +#include <stdio.h> +#include <stdarg.h> + +#include "dvdnav/dvdnav.h" +#include "logger.h" + +void dvdnav_log( void *priv, const dvdnav_logger_cb *logcb, + dvdnav_logger_level_t level, const char *fmt, ... ) +{ + va_list list; + va_start(list, fmt); + if(logcb && logcb->pf_log) + logcb->pf_log(priv, level, fmt, list); + else + { + FILE *stream = (level == DVDNAV_LOGGER_LEVEL_ERROR) ? stderr : stdout; + fprintf(stream, "libdvdnav: "); + vfprintf(stream, fmt, list); + fprintf(stream, "\n"); + } + va_end(list); +} diff --git a/libdvdnav-embedded/src/logger.h b/libdvdnav-embedded/src/logger.h new file mode 100644 index 0000000..ca4c538 --- /dev/null +++ b/libdvdnav-embedded/src/logger.h @@ -0,0 +1,32 @@ +/* +* This file is part of libdvdnav, a DVD navigation library. +* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +*/ + +#ifndef LIBDVDNAV_LOGGER_H +#define LIBDVDNAV_LOGGER_H + +void dvdnav_log( void *priv, const dvdnav_logger_cb *logcb, + dvdnav_logger_level_t level, const char *fmt, ... ); + +#define LOG(ctx, level, ...) \ + dvdnav_log(ctx->priv, &ctx->logcb, level, __VA_ARGS__) +#define Log0(ctx, ...) LOG(ctx, DVDNAV_LOGGER_LEVEL_ERROR, __VA_ARGS__) +#define Log1(ctx, ...) LOG(ctx, DVDNAV_LOGGER_LEVEL_WARN, __VA_ARGS__) +#define Log2(ctx, ...) LOG(ctx, DVDNAV_LOGGER_LEVEL_INFO, __VA_ARGS__) +#define Log3(ctx, ...) LOG(ctx, DVDNAV_LOGGER_LEVEL_DEBUG, __VA_ARGS__) + +#endif diff --git a/libdvdnav-embedded/src/navigation.c b/libdvdnav-embedded/src/navigation.c new file mode 100644 index 0000000..ba61102 --- /dev/null +++ b/libdvdnav-embedded/src/navigation.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <inttypes.h> +#include <limits.h> +#include <string.h> +#include <sys/time.h> +#include "dvdnav/dvdnav.h" +#include <dvdread/nav_types.h> +#include "vm/decoder.h" +#include "vm/vm.h" +#include "dvdnav_internal.h" + +/* Navigation API calls */ + +dvdnav_status_t dvdnav_still_skip(dvdnav_t *this) { + pthread_mutex_lock(&this->vm_lock); + this->position_current.still = 0; + pthread_mutex_unlock(&this->vm_lock); + 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) { + 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->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; +} + +static dvdnav_status_t get_title_by_number(dvdnav_t *this, int32_t title, + const title_info_t **pp_title) +{ + int32_t titlescount; + dvdnav_status_t status = dvdnav_get_number_of_titles(this, &titlescount); + if(status == DVDNAV_STATUS_OK) + { + if ((title < 1) || (title > titlescount)) { + printerr("Passed a title number out of range."); + status = DVDNAV_STATUS_ERR; + } + else { + *pp_title = &vm_get_vmgi(this->vm)->tt_srpt->title[title-1]; + } + } + return status; +} + +dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *this, int32_t title, int32_t *parts) { + const title_info_t *info; + dvdnav_status_t status = get_title_by_number(this, title, &info); + if(status == DVDNAV_STATUS_OK) + (*parts) = info->nr_of_ptts; + return status; +} + +dvdnav_status_t dvdnav_get_number_of_angles(dvdnav_t *this, int32_t title, int32_t *angles) { + const title_info_t *info; + dvdnav_status_t status = get_title_by_number(this, title, &info); + if(status == DVDNAV_STATUS_OK) + (*angles) = info->nr_of_angles; + return status; +} + +dvdnav_status_t dvdnav_current_title_info(dvdnav_t *this, int32_t *title, int32_t *part) { + int32_t retval; + + 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 == DVD_DOMAIN_VTSMenu) + || (this->vm->state.domain == DVD_DOMAIN_VMGM) ) { + /* Get current Menu ID: into *part. */ + if(! vm_get_current_menu(this->vm, part)) { + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (*part > -1) { + *title = 0; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + if (this->vm->state.domain == DVD_DOMAIN_VTSTitle) { + 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_current_title_program(dvdnav_t *this, int32_t *title, int32_t *pgcn, int32_t *pgn) { + int32_t retval; + int32_t part; + + 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 == DVD_DOMAIN_VTSMenu) + || (this->vm->state.domain == DVD_DOMAIN_VMGM) ) { + /* Get current Menu ID: into *part. */ + if(! vm_get_current_menu(this->vm, &part)) { + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (part > -1) { + *title = 0; + *pgcn = this->vm->state.pgcN; + *pgn = this->vm->state.pgN; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + if (this->vm->state.domain == DVD_DOMAIN_VTSTitle) { + retval = vm_get_current_title_part(this->vm, title, &part); + *pgcn = this->vm->state.pgcN; + *pgn = this->vm->state.pgN; + 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) { + return dvdnav_part_play(this, title, 1); +} + +dvdnav_status_t dvdnav_program_play(dvdnav_t *this, int32_t title, int32_t pgcn, int32_t pgn) { + int32_t retval; + + 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; + } + + retval = vm_jump_title_program(this->vm, title, pgcn, pgn); + 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(dvdnav_t *this, int32_t title, int32_t part) { + int32_t retval; + + 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) { + /* FIXME: Implement */ + printerr("Not implemented yet."); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_stop(dvdnav_t *this) { + 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) { + /* A nice easy function... delegate to the VM */ + int retval; + 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; + } + + retval = vm_jump_up(this->vm); + pthread_mutex_unlock(&this->vm_lock); + + return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR; +} diff --git a/libdvdnav-embedded/src/read_cache.c b/libdvdnav-embedded/src/read_cache.c new file mode 100644 index 0000000..4af4056 --- /dev/null +++ b/libdvdnav-embedded/src/read_cache.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* + * 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 <assert.h> +#include <inttypes.h> +#include <stdlib.h> +#include <limits.h> +#include <sys/time.h> +#include <time.h> +#include "dvdnav/dvdnav.h" +#include <dvdread/nav_types.h> +#include "vm/decoder.h" +#include "vm/vm.h" +#include "dvdnav_internal.h" +#include "read_cache.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 *)calloc(1, sizeof(read_cache_t)); + + if(!self) + return NULL; + + self->dvd_self = dvd_self; + self->read_ahead_size = READ_AHEAD_SIZE_MIN; + 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) { + assert(cache->chunk[i].usage_count > 0); + 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-embedded/src/read_cache.h b/libdvdnav-embedded/src/read_cache.h new file mode 100644 index 0000000..3a0e48c --- /dev/null +++ b/libdvdnav-embedded/src/read_cache.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBDVDNAV_READ_CACHE_H +#define LIBDVDNAV_READ_CACHE_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 /* LIBDVDNAV_READ_CACHE_H */ diff --git a/libdvdnav-embedded/src/searching.c b/libdvdnav-embedded/src/searching.c new file mode 100644 index 0000000..7c8e6e6 --- /dev/null +++ b/libdvdnav-embedded/src/searching.c @@ -0,0 +1,1356 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/time.h> +#include "dvdnav/dvdnav.h" +#include <dvdread/nav_types.h> +#include "vm/decoder.h" +#include "vm/vm.h" +#include "dvdnav_internal.h" +#include "logger.h" +#include <dvdread/ifo_read.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, int next, uint32_t *vobu) { + vobu_admap_t *admap = NULL; + +#ifdef LOG_DEBUG + Log3(this, "Seeking to target %u ...", seekto_block); +#endif + *vobu = -1; + + /* Search through the VOBU_ADMAP for the nearest VOBU + * to the target block */ + switch(domain) { + case DVD_DOMAIN_FirstPlay: + case DVD_DOMAIN_VMGM: + admap = this->vm->vmgi->menu_vobu_admap; + break; + case DVD_DOMAIN_VTSMenu: + admap = this->vm->vtsi->menu_vobu_admap; + break; + case DVD_DOMAIN_VTSTitle: + admap = this->vm->vtsi->vts_vobu_admap; + break; + default: + Log0(this, "Error: Unknown domain for seeking."); + } + if(admap) { + uint32_t address = 0; + uint32_t vobu_start, next_vobu = 0; + uint32_t admap_entries = (admap->last_byte + 1 - VOBU_ADMAP_SIZE)/VOBU_ADMAP_SIZE; + + /* Search through ADMAP for best sector */ + vobu_start = SRI_END_OF_CELL; + /* FIXME: Implement a faster search algorithm */ + while(address < admap_entries) { + 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) { + /* We expect the ADMAP to be sorted in order of increasing + * sector number, but some discs store out-of-order values in + * cells that would not normally be playable, cells which + * themselves contain bad sectors. The straightforward linear + * search would get "stuck" on those values, essentially + * forcing playback of the bad sectors on any sector seek + * anywhere within the title. To prevent this, scan the next + * few entries to make sure they're all really later than the + * current entry, and skip over the out-of-order entries if + * not. */ + uint32_t ooo_address = 0; + uint32_t ooo_vobu = SRI_END_OF_CELL; + uint32_t check; + for( check = 1 ; check <= 32 && address+check < admap_entries ; check++ ) { + uint32_t check_vobu = admap->vobu_start_sectors[address+check]; + if (check_vobu < next_vobu && check_vobu < ooo_vobu) { + ooo_address = address+check; + ooo_vobu = check_vobu; + } + } + if (ooo_vobu < next_vobu) { + address = ooo_address; + next_vobu = ooo_vobu; + } else { + break; + } + } + vobu_start = next_vobu; + address++; + } + *vobu = next ? next_vobu : vobu_start; + return DVDNAV_STATUS_OK; + } + Log0(this, "admap not located"); + return DVDNAV_STATUS_ERR; +} + +/* FIXME: right now, this function does not use the time tables but interpolates + only the cell times */ +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; + const cell_playback_t *cell; + dvd_state_t *state; + + 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; + } + + + this->cur_cell_time = 0; + 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]); + if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) + continue; + 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) { + uint32_t vobu; +#ifdef LOG_DEBUG + Log3(this, "Seeking to cell %i from choice of %i to %i", + cell_nr, first_cell_nr, last_cell_nr); +#endif + if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) == DVDNAV_STATUS_OK) { + uint32_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 + Log3(this, "After cellN=%u blockN=%u target=%x vobu=%x start=%x" , + state->cellN, state->blockN, target, vobu, start); +#endif + this->vm->hop_channel += HOP_SEEK; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + } + + Log0(this, "Error when seeking"); + printerr("Error when seeking."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_sector_search(dvdnav_t *this, + int64_t offset, int32_t origin) { + uint32_t target = 0; + uint32_t current_pos; + uint32_t cur_sector; + uint32_t cur_cell_nr; + uint32_t length = 0; + uint32_t first_cell_nr, last_cell_nr, cell_nr; + int32_t found; + int forward = 0; + const 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) { + printerr("Cannot get current position."); + 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 + Log3(this, "seeking to offset=%lld pos=%u length=%u", offset, target, length); + Log3(this, "Before cellN=%u blockN=%u", state->cellN, state->blockN); +#endif + + current_pos = target; + cur_sector = this->vobu.vobu_start + this->vobu.blockN; + cur_cell_nr = state->cellN; + + 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((signed)target + offset >= length) { + printerr("Request to seek behind end."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + if((signed)target + offset < 0) { + printerr("Request to seek before start."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + target += offset; + break; + case SEEK_END: + if(length < offset) { + printerr("Request to seek before start."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + target = length - offset; + break; + default: + /* Error occurred */ + printerr("Illegal seek mode."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + forward = target > current_pos; + + this->cur_cell_time = 0; + 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]); + if(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) + continue; + 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; + if (forward && (cell_nr == cur_cell_nr)) { + uint32_t vobu; + /* if we are seeking forward from the current position, make sure + * we move to a new position that is after our current position. + * simply truncating to the vobu will go backwards */ + if (dvdnav_scan_admap(this, state->domain, target, 0, &vobu) != DVDNAV_STATUS_OK) + break; + if (vobu <= cur_sector) { + if (dvdnav_scan_admap(this, state->domain, target, 1, &vobu) != DVDNAV_STATUS_OK) + break; + if (vobu > cell->last_sector) { + if (cell_nr == last_cell_nr) + break; + cell_nr++; + cell = &(state->pgc->cell_playback[cell_nr-1]); + target = cell->first_sector; + } else { + target = vobu; + } + } + } + found = 1; + break; + } + } + + if(found) { + uint32_t vobu; +#ifdef LOG_DEBUG + Log3(this, "Seeking to cell %i from choice of %i to %i", + cell_nr, first_cell_nr, last_cell_nr); +#endif + if (dvdnav_scan_admap(this, state->domain, target, 0, &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 + Log3(this, "After cellN=%u blockN=%u target=%x vobu=%x start=%x" , + state->cellN, state->blockN, target, vobu, start); +#endif + this->vm->hop_channel += HOP_SEEK; + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_OK; + } + } + } + + Log0(this, "Error when seeking"); + Log1(this, "FIXME: Implement seeking to location %u", 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) { + 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 + Log3(this, "previous chapter"); +#endif + if (!vm_jump_prev_pg(this->vm)) { + Log0(this, "previous chapter failed."); + printerr("Skip to previous chapter failed."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->cur_cell_time = 0; + this->position_current.still = 0; + this->vm->hop_channel++; +#ifdef LOG_DEBUG + Log3(this, "previous chapter done"); +#endif + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) { + 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 + Log3(this, "top chapter"); +#endif + if (!vm_jump_top_pg(this->vm)) { + Log0(this, "top chapter failed."); + printerr("Skip to top chapter failed."); + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; + } + this->cur_cell_time = 0; + this->position_current.still = 0; + this->vm->hop_channel++; +#ifdef LOG_DEBUG + Log3(this, "top chapter done"); +#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; + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + goto fail; + } + +#ifdef LOG_DEBUG + Log3(this, "next chapter"); +#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 (try_vm == NULL) { + printerr("Unable to copy the VM."); + goto fail; + } + + 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); + if (try_vm == NULL) { + printerr("Unable to copy the VM."); + goto fail; + } + vm_get_next_cell(try_vm); + if (try_vm->stopped) { + vm_free_copy(try_vm); + Log0(this, "next chapter failed."); + printerr("Skip to next chapter failed."); + goto fail; + } + } + this->cur_cell_time = 0; + /* 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 + Log3(this, "next chapter done"); +#endif + pthread_mutex_unlock(&this->vm_lock); + + return DVDNAV_STATUS_OK; + +fail: + pthread_mutex_unlock(&this->vm_lock); + return DVDNAV_STATUS_ERR; +} + +dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) { + vm_t *try_vm; + + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->state.pgc) { + printerr("No current PGC."); + goto fail; + } + + this->cur_cell_time = 0; + /* make a copy of current VM and try to navigate the copy to the menu */ + try_vm = vm_new_copy(this->vm); + if (try_vm == NULL) { + printerr("Unable to copy VM."); + goto fail; + } + + if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != DVD_DOMAIN_VTSTitle)) { + /* 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."); + goto fail; + } + +fail: + 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; + const cell_playback_t *cell; + dvd_state_t *state; + + 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->block_type == BLOCK_TYPE_ANGLE_BLOCK && cell->block_mode != BLOCK_MODE_FIRST_CELL) + continue; + 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; + } + + pthread_mutex_unlock(&this->vm_lock); + + if((signed)*pos == -1) + return DVDNAV_STATUS_ERR; + + 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; + const cell_playback_t *first_cell; + const cell_playback_t *last_cell; + dvd_state_t *state; + + state = &(this->vm->state); + if(!state->pgc) { + printerr("No current PGC."); + return DVDNAV_STATUS_ERR; + } + + if (state->pgc->program_map == NULL) { + printerr("Program map missing."); + 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; +} + +uint32_t dvdnav_describe_title_chapters(dvdnav_t *this, int32_t title, uint64_t **times, uint64_t *duration) { + int32_t retval=0; + uint16_t parts, i; + const title_info_t *ptitle = NULL; + const ptt_info_t *ptt = NULL; + ifo_handle_t *ifo = NULL; + const pgc_t *pgc; + const cell_playback_t *cell; + uint64_t length, *tmp=NULL; + + *times = NULL; + *duration = 0; + pthread_mutex_lock(&this->vm_lock); + if(!this->vm->vmgi) { + printerr("Bad VM state or missing VTSI."); + goto fail; + } + if(!this->started) { + /* don't report an error but be nice */ + vm_start(this->vm); + this->started = 1; + } + ifo = vm_get_title_ifo(this->vm, title); + if(!ifo || !ifo->vts_pgcit) { + printerr("Couldn't open IFO for chosen title, exit."); + retval = 0; + goto fail; + } + + ptitle = &this->vm->vmgi->tt_srpt->title[title-1]; + parts = ptitle->nr_of_ptts; + if(ptitle->vts_ttn == 0) + goto fail; + ptt = ifo->vts_ptt_srpt->title[ptitle->vts_ttn-1].ptt; + + tmp = calloc(1, sizeof(uint64_t)*parts); + if(!tmp) + goto fail; + + if(!ptt) { + printerr("ptt NULL"); + goto fail; + } + + length = 0; + for(i=0; i<parts; i++) { + uint32_t cellnr, endcellnr; + if (ptt[i].pgcn == 0 || ptt[i].pgcn > ifo->vts_pgcit->nr_of_pgci_srp) { + printerr("PGCN out of bounds."); + continue; + } + if (ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte >= ifo->vts_pgcit->last_byte) { + printerr("PGC start out of bounds"); + continue; + } + if (0 == ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte) { + printerr("PGC start zero."); + continue; + } + if (0 != (ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc_start_byte & 1)) { + printerr("PGC start unaligned."); + continue; + } + if (0 != ((uintptr_t)(ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc) & 1)) { + printerr("PGC pointer unaligned."); + continue; + } + pgc = ifo->vts_pgcit->pgci_srp[ptt[i].pgcn-1].pgc; + if (pgc == NULL) { + printerr("PGC missing."); + continue; + } + if (pgc->program_map == NULL) { + printerr("Program map missing."); + continue; + } + if(ptt[i].pgn == 0 || ptt[i].pgn > pgc->nr_of_programs) { + printerr("WRONG part number."); + goto fail; + } + + if (pgc->nr_of_cells == 0) { + printerr("Number of cells cannot be 0"); + continue; + } + if ((cellnr = pgc->program_map[ptt[i].pgn-1]) == 0) { + printerr("Cell new row cannot be 0"); + continue; + } + if (pgc->cell_playback == NULL) { + printerr("Cell missing"); + continue; + } + + if(ptt[i].pgn < pgc->nr_of_programs) + endcellnr = pgc->program_map[ptt[i].pgn]; + else + endcellnr = 0; + + do { + cell = &pgc->cell_playback[cellnr-1]; + if(!(cell->block_type == BLOCK_TYPE_ANGLE_BLOCK && + cell->block_mode != BLOCK_MODE_FIRST_CELL + )) + { + tmp[i] = length + dvdnav_convert_time(&cell->playback_time); + length = tmp[i]; + } + cellnr++; + } while(cellnr < endcellnr); + } + *duration = length; + vm_ifo_close(ifo); + ifo = NULL; + retval = parts; + *times = tmp; + +fail: + pthread_mutex_unlock(&this->vm_lock); + if(!retval && ifo) + vm_ifo_close(ifo); + if(!retval && tmp) + free(tmp); + return retval; +} + +/* Get an admap and admap_len */ +static vobu_admap_t* dvdnav_admap_get(dvdnav_t *this, dvd_state_t *state, + int32_t *admap_len) { + vobu_admap_t *admap = NULL; + switch(state->domain) { + case DVD_DOMAIN_FirstPlay: + case DVD_DOMAIN_VMGM: + admap = this->vm->vmgi->menu_vobu_admap; + break; + case DVD_DOMAIN_VTSMenu: + admap = this->vm->vtsi->menu_vobu_admap; + break; + case DVD_DOMAIN_VTSTitle: + admap = this->vm->vtsi->vts_vobu_admap; + break; + default: { + Log1(this, "Unknown domain"); + return NULL; + } + } + if (admap == NULL) return NULL; + + *admap_len = (admap->last_byte + 1 - VOBU_ADMAP_SIZE) / VOBU_ADMAP_SIZE; + if (*admap_len <= 0) { + Log1(this, "admap_len <= 0"); + return NULL; + } + return admap; +} + +/* Get a tmap, tmap_len and tmap_interval */ +static const vts_tmap_t* dvdnav_tmap_get(dvdnav_t *this, dvd_state_t *state, + int32_t *tmap_len, int32_t *tmap_interval) { + int32_t domain; + ifo_handle_t *ifo = NULL; + vts_tmapt_t *tmapt = NULL; + uint16_t tmap_count = 0; + int32_t pgcN = 0; + const vts_tmap_t *tmap = NULL; + int32_t result = 0; + + domain = state->domain; + switch(domain) { + case DVD_DOMAIN_FirstPlay: + case DVD_DOMAIN_VTSMenu: + case DVD_DOMAIN_VMGM: { + ifo = this->vm->vmgi; + break; + } + case DVD_DOMAIN_VTSTitle: { + ifo = this->vm->vtsi; + break; + } + default: { + Log1(this, "unknown domain for tmap"); + return NULL; + } + } + if (ifo == NULL) return NULL; + tmapt = ifo->vts_tmapt; + /* HACK: ifo->vts_tmapt is NULL b/c ifo_read.c never loads it + * load ifo->vts_tmapt directly*/ + if (tmapt == NULL) { + result = ifoRead_VTS_TMAPT(ifo); + if (!result) { + return NULL; + } + tmapt = ifo->vts_tmapt; + if (tmapt == NULL) return NULL; + } + + tmap_count = tmapt->nr_of_tmaps; + pgcN = state->pgcN - 1; /* -1 b/c pgcN is base1 */ + if (pgcN < 0) { + Log1(this, "pgcN < 0"); + return NULL; + } + + /* get tmap */ + switch(domain) { + case DVD_DOMAIN_FirstPlay: + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_VTSMenu: { + if (tmap_count == 0) { + Log1(this, "tmap_count == 0"); + return NULL; + } + tmap = &tmapt->tmap[0]; /* ASSUME: vmgi only has one time map */ + break; + } + case DVD_DOMAIN_VTSTitle: { + if (pgcN >= tmap_count) { + Log1(this, "pgcN >= tmap_count; pgcN=%i tmap_count=%i", + pgcN, tmap_count); + return NULL; + } + tmap = &tmapt->tmap[pgcN]; + break; + } + } + if (tmap == NULL) return NULL; + + /* tmap->tmu is in seconds; convert to millisecs */ + *tmap_interval = tmap->tmu * 1000; + if (*tmap_interval == 0) { + Log1(this, "tmap_interval == 0"); + return NULL; + } + *tmap_len = tmap->nr_of_entries; + if (*tmap_len == 0) { + Log1(this, "tmap_len == 0"); + return NULL; + } + return tmap; +} + +/* Get a sector from a tmap */ +static int32_t dvdnav_tmap_get_entry(dvdnav_t *this, + const vts_tmap_t *tmap, uint16_t tmap_len, + int32_t idx, uint32_t *sector) { + /* tmaps start at idx 0 which represents a sector at time 1 * tmap_interval + * this creates a "fake" tmap index at idx -1 for sector 0 */ + if (idx == TMAP_IDX_EDGE_BGN) { + *sector = 0; + return 1; + } + if (idx < TMAP_IDX_EDGE_BGN || idx >= tmap_len) { + Log1(this, "idx out of bounds idx=%i %i", idx, tmap_len); + return 0; + } + /* 0x7fffffff unsets discontinuity bit if present */ + *sector = tmap->map_ent[idx] & 0x7fffffff; + return 1; +} + +/* Do a binary search for earlier admap index near find_sector */ +static int32_t dvdnav_admap_search(vobu_admap_t *admap, uint32_t admap_len, + uint32_t find_sector, uint32_t *vobu) { + int32_t adj = 1; + int32_t prv_pos = 0; + int32_t prv_len = admap_len; + int32_t cur_len = 0; + int32_t cur_idx = 0; + uint32_t cur_sector = 0; + while (1) { + cur_len = prv_len / 2; + /* need to add 1 when prv_len == 3 (cur_len should go to 2, not 1) */ + if (prv_len % 2 == 1) ++cur_len; + cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) + cur_idx = 0; + else if (cur_idx >= (int32_t)admap_len) + cur_idx = admap_len - 1; + + cur_sector = admap->vobu_start_sectors[cur_idx]; + if (find_sector < cur_sector) adj = -1; + else if (find_sector > cur_sector) adj = 1; + else if (find_sector == cur_sector) { + *vobu = cur_idx; + return 1; + } + if (cur_len == 1) {/* no smaller intervals left */ + if (adj == -1) {/* last comparison was greater; take lesser */ + cur_idx -= 1; + cur_sector = admap->vobu_start_sectors[cur_idx]; + } + *vobu = cur_idx; + return 1; + } + prv_len = cur_len; + prv_pos = cur_idx; + } +} + +/* Do a binary search for the earlier tmap entry near find_sector */ +static int32_t dvdnav_tmap_search(dvdnav_t *this, + const vts_tmap_t *tmap, uint32_t tmap_len, + uint32_t find_sector, int32_t *tmap_idx, uint32_t *sector) { + int32_t adj = 1; + int32_t prv_pos = 0; + int32_t prv_len = tmap_len; + int32_t result = 0; + int32_t cur_len = 0; + int32_t cur_idx = 0; + uint32_t cur_sector = 0; + while (1) { + cur_len = prv_len / 2; + /* need to add 1 when prv_len == 3 (cur_len should go to 2, not 1) */ + if (prv_len % 2 == 1) ++cur_len; + cur_idx = prv_pos + (cur_len * adj); + if (cur_idx < 0) + cur_idx = 0; + else if (cur_idx >= (int32_t)tmap_len) + cur_idx = tmap_len - 1; + cur_sector = 0; + result = dvdnav_tmap_get_entry(this, tmap, tmap_len, cur_idx, &cur_sector); + if (!result) return 0; + if (find_sector < cur_sector) adj = -1; + else if (find_sector > cur_sector) adj = 1; + else if (find_sector == cur_sector) { + *tmap_idx = cur_idx; + *sector = cur_sector; + return 1; + } + if (cur_len == 1) {/* no smaller intervals left */ + if (adj == -1) {/* last comparison was greater; take lesser */ + if (cur_idx == 0) { /* fake tmap index for sector 0 */ + cur_idx = TMAP_IDX_EDGE_BGN; + cur_sector = 0; + } + else { + cur_idx -= 1; + result = dvdnav_tmap_get_entry(this, tmap, tmap_len, cur_idx, &cur_sector); + if (!result) return 0; + } + } + *tmap_idx = cur_idx; + *sector = cur_sector; + return 1; + } + prv_len = cur_len; + prv_pos = cur_idx; + } +} + +/* Find the cell for a given time */ +static int32_t dvdnav_cell_find(dvdnav_t *this, dvd_state_t *state, + uint64_t find_val, dvdnav_cell_data_t *cell_data) { + uint32_t cells_len = 0; + uint32_t cells_bgn = 0; + uint32_t cells_end = 0; + uint32_t cell_idx = 0; + const pgc_t *pgc = NULL; + int pgN = 0; + const cell_playback_t *cell = NULL; + int found = 0; + + pgc = state->pgc; + if (pgc == NULL) return 0; + cells_len = pgc->nr_of_cells; + if (cells_len == 0) { + Log1(this, "cells_len == 0"); + return 0; + } + + /* get cells_bgn, cells_end */ + if (this->pgc_based) { + cells_bgn = 1; + cells_end = cells_len; + } + else { + pgN = state->pgN; + cells_bgn = pgc->program_map[pgN - 1]; /* -1 b/c pgN is 1 based? */ + if (pgN < pgc->nr_of_programs) { + cells_end = pgc->program_map[pgN] - 1; + } + else { + cells_end = cells_len; + } + } + + /* search cells */ + for (cell_idx = cells_bgn; cell_idx <= cells_end; cell_idx++) { + cell = &(pgc->cell_playback[cell_idx - 1]); /* -1 b/c cell is base1 */ + /* if angle block, only consider first angleBlock + * (others are "redundant" for purpose of search) */ + if ( cell->block_type == BLOCK_TYPE_ANGLE_BLOCK + && cell->block_mode != BLOCK_MODE_FIRST_CELL) { + continue; + } + cell_data->bgn->sector = cell->first_sector; + cell_data->end->sector = cell->last_sector; + + /* 90 pts to ms */ + cell_data->end->time += (dvdnav_convert_time(&cell->playback_time) / 90); + if ( find_val >= cell_data->bgn->time + && find_val <= cell_data->end->time) { + found = 1; + break; + } + cell_data->bgn->time = cell_data->end->time; + } + + /* found cell: set var */ + if (found) { + cell_data->idx = cell_idx; + } + else + Log1(this, "cell not found; find=%"PRId64, find_val); + return found; +} + +/* Given two sectors and a fraction, calc the corresponding vobu */ +static int32_t dvdnav_admap_interpolate_vobu(dvdnav_t *this, + dvdnav_jump_args_t *args, + dvdnav_pos_data_t *bgn, dvdnav_pos_data_t *end, uint32_t fraction, + uint32_t *jump_sector) { + int32_t result = 0; + uint32_t vobu_len = 0; + uint32_t vobu_adj = 0; + uint32_t vobu_idx = 0; + + /* get bgn->vobu_idx */ + result = dvdnav_admap_search(args->admap, args->admap_len, + bgn->sector, &bgn->vobu_idx); + if (!result) { + Log1(this, "admap_interpolate: could not find sector_bgn"); + return 0; + } + + /* get end->vobu_idx */ + result = dvdnav_admap_search(args->admap, args->admap_len, + end->sector, &end->vobu_idx); + if (!result) { + Log1(this, "admap_interpolate: could not find sector_end"); + return 0; + } + + vobu_len = end->vobu_idx - bgn->vobu_idx; + /* +500 to round up else 74% of a 4 sec interval = 2 sec */ + vobu_adj = ((fraction * vobu_len) + 500) / 1000; + /* HACK: need to add +1, or else will land too soon (not sure why) */ + vobu_adj++; + vobu_idx = bgn->vobu_idx + vobu_adj; + if ((int32_t)vobu_idx >= args->admap_len) { + Log1(this, "admap_interpolate: vobu_idx >= admap_len"); + return 0; + } + *jump_sector = args->admap->vobu_start_sectors[vobu_idx]; + return 1; +} + +/* Given two tmap entries and a time, calc the time for the lo tmap entry */ +static int32_t dvdnav_tmap_calc_time_for_tmap_entry(dvdnav_t *this, + dvdnav_jump_args_t *args, + dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi, + dvdnav_pos_data_t *pos, uint64_t *out_time) { + int32_t result = 0; + int32_t vobu_pct = 0; + uint64_t time_adj = 0; + + if (lo->sector == hi->sector) { + Log1(this, "lo->sector == hi->sector: %i", lo->sector); + return 0; + } + + /* get vobus corresponding to lo, hi, pos */ + result = dvdnav_admap_search(args->admap, args->admap_len, + lo->sector, &lo->vobu_idx); + if (!result) { + Log1(this, "lo->vobu: lo->sector=%i", lo->sector); + return 0; + } + result = dvdnav_admap_search(args->admap, args->admap_len, + hi->sector, &hi->vobu_idx); + if (!result) { + Log1(this, "hi->vobu: hi->sector=%i", hi->sector); + return 0; + } + result = dvdnav_admap_search(args->admap, args->admap_len, + pos->sector, &pos->vobu_idx); + if (!result) { + Log1(this, "pos->vobu: pos->sector=%i", pos->sector); + return 0; + } + + /* calc position of cell relative to lo */ + if (hi->vobu_idx == lo->vobu_idx) { + /* We are at the very end - pos should also equal lo so force that + * rather than hit the divide-by-zero. */ + vobu_pct = 0; + } else { + vobu_pct = ((pos->vobu_idx - lo->vobu_idx) * 1000) + / ( hi->vobu_idx - lo->vobu_idx); + } + if (vobu_pct < 0 || vobu_pct > 1000) { + Log1(this, "vobu_pct must be between 0 and 1000"); + return 0; + } + + /* calc time of lo */ + time_adj = ((uint64_t)args->tmap_interval * vobu_pct) / 1000; + *out_time = pos->time - time_adj; + return 1; +} + +/* Find the tmap entries on either side of a given sector */ +static int32_t dvdnav_tmap_get_entries_for_sector( + dvdnav_t *this, + dvdnav_jump_args_t *args, + dvdnav_cell_data_t *cell_data, uint32_t find_sector, + dvdnav_pos_data_t *lo, dvdnav_pos_data_t *hi) { + int32_t result = 0; + + result = dvdnav_tmap_search(this, + args->tmap, args->tmap_len, find_sector, + &lo->tmap_idx, &lo->sector); + if (!result) { + Log1(this, "could not find lo idx: %i", find_sector); + return 0; + } + + /* HACK: Most DVDs have a tmap that starts at sector 0 + * However, some have initial dummy cells that are not seekable + * (restricted = y). + * These cells will throw off the tmap calcs when in the first playable cell. + * For now, assume that lo->sector is equal to the cell->bgn->sector + * Note that for most DVDs this will be 0 + * (Since they will have no dummy cells and cell 1 will start at sector 0) + */ + if (lo->tmap_idx == TMAP_IDX_EDGE_BGN) { + lo->sector = cell_data->bgn->sector; + } + + if (lo->tmap_idx == args->tmap_len - 1) { + /* lo is last tmap entry; "fake" entry for one beyond + * and mark it with cell_end_sector */ + hi->tmap_idx = TMAP_IDX_EDGE_END; + hi->sector = cell_data->end->sector; + } + else { + hi->tmap_idx = lo->tmap_idx + 1; + result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, + hi->tmap_idx, &hi->sector); + if (!result) { + Log1(this, "could not find hi idx: %i", find_sector); + return 0; + } + } + return 1; +} + +/* Find the nearest vobu by using the tmap */ +static int32_t dvdnav_find_vobu_by_tmap(dvdnav_t *this, dvd_state_t *state, + dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data, + dvdnav_pos_data_t *jump) { + uint64_t seek_offset = 0; + uint32_t seek_idx = 0; + int32_t result = 0; + dvdnav_pos_data_t *cell_bgn_lo = NULL; + dvdnav_pos_data_t *cell_bgn_hi = NULL; + dvdnav_pos_data_t *jump_lo = NULL; + dvdnav_pos_data_t *jump_hi = NULL; + + /* get tmap, tmap_len, tmap_interval */ + args->tmap = dvdnav_tmap_get(this, state, + &args->tmap_len, &args->tmap_interval); + if (args->tmap == NULL) return 0; + + /* get tmap entries on either side of cell_bgn */ + cell_bgn_lo = &(dvdnav_pos_data_t){0}; + cell_bgn_hi = &(dvdnav_pos_data_t){0}; + result = dvdnav_tmap_get_entries_for_sector(this, args, cell_data, + cell_data->bgn->sector, cell_bgn_lo, cell_bgn_hi); + if (!result) return 0; + + /* calc time of cell_bgn_lo */ + result = dvdnav_tmap_calc_time_for_tmap_entry(this, + args, cell_bgn_lo, cell_bgn_hi, + cell_data->bgn, &cell_bgn_lo->time); + if (!result) return 0; + + /* calc time of jump_time relative to cell_bgn_lo */ + seek_offset = jump->time - cell_bgn_lo->time; + seek_idx = (uint32_t)(seek_offset / args->tmap_interval); + uint32_t seek_remainder = seek_offset - (seek_idx * args->tmap_interval); + uint32_t seek_pct = (seek_remainder * 1000) / args->tmap_interval; + + /* get tmap entries on either side of jump_time */ + jump_lo = &(dvdnav_pos_data_t){0}; + jump_hi = &(dvdnav_pos_data_t){0}; + + /* if seek_idx == 0, then tmap_indexes are the same, do not re-get + * also, note cell_bgn_lo will already have sector if TMAP_IDX_EDGE_BGN */ + if (seek_idx == 0) { + jump_lo = cell_bgn_lo; + jump_hi = cell_bgn_hi; + } + else { + jump_lo->tmap_idx = (uint32_t)(cell_bgn_lo->tmap_idx + seek_idx); + result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, + jump_lo->tmap_idx, &jump_lo->sector); + if (!result) return 0; + + /* +1 handled by dvdnav_tmap_get_entry */ + jump_hi->tmap_idx = jump_lo->tmap_idx + 1; + result = dvdnav_tmap_get_entry(this, args->tmap, args->tmap_len, + jump_hi->tmap_idx, &jump_hi->sector); + if (!result) return 0; + } + + /* interpolate sector */ + result = dvdnav_admap_interpolate_vobu(this, args, jump_lo, jump_hi, + seek_pct, &jump->sector); + + return result; +} + +/* Find the nearest vobu by using the cell boundaries */ +static int32_t dvdnav_find_vobu_by_cell_boundaries(dvdnav_t *this, + dvdnav_jump_args_t *args, dvdnav_cell_data_t *cell_data, + dvdnav_pos_data_t *jump) { + int64_t jump_offset = 0; + int64_t cell_len = 0; + uint32_t jump_pct = 0; + int32_t result = 0; + + /* get jump_offset */ + jump_offset = jump->time - cell_data->bgn->time; + if (jump_offset < 0) { + Log1(this, "jump_offset < 0"); + return 0; + } + cell_len = cell_data->end->time - cell_data->bgn->time; + if (cell_len < 0) { + Log1(this, "cell_len < 0"); + return 0; + } + jump_pct = (jump_offset * 1000) / cell_len; + + /* get sector */ + /* NOTE: end cell sector in VTS_PGC is last sector of cell + * this last sector is not the start of a VOBU + * +1 to get sector that is the start of a VOBU + * start of a VOBU is needed in order to index into admap */ + cell_data->end->sector += 1; + result = dvdnav_admap_interpolate_vobu(this, args, + cell_data->bgn, cell_data->end, jump_pct, &jump->sector); + if (!result) { + Log1(this, "find_by_admap.interpolate"); + return 0; + } + return 1; +} + +/* Jump to sector by time */ +/* NOTE: Mode is currently unimplemented. Only 0 should be passed. */ +/* 1 and -1 are for future implementation */ +/* 0: Default. Jump to a time which may be either <> time_in_pts_ticks */ +/* 1: After. Always jump to a time that is > time_in_pts_ticks */ +/* -1: Before. Always jump to a time that is < time_in_pts_ticks */ +dvdnav_status_t dvdnav_jump_to_sector_by_time(dvdnav_t *this, + uint64_t time_in_pts_ticks, int32_t mode) { + if (mode != JUMP_MODE_TIME_DEFAULT) return DVDNAV_STATUS_ERR; + int32_t result = DVDNAV_STATUS_ERR; + dvd_state_t *state = NULL; + uint32_t sector_off = 0; + dvdnav_pos_data_t *jump = NULL; + dvdnav_cell_data_t *cell_data = NULL; + dvdnav_jump_args_t *args = NULL; + + jump = &(dvdnav_pos_data_t){0}; + /* convert time to milliseconds */ + jump->time = time_in_pts_ticks / 90; + + /* get variables that will be used across both functions */ + state = &(this->vm->state); + if (state == NULL) goto exit; + + /* get cell info */ + cell_data = &(dvdnav_cell_data_t){0}; + cell_data->bgn = &(dvdnav_pos_data_t){0}; + cell_data->end = &(dvdnav_pos_data_t){0}; + result = dvdnav_cell_find(this, state, jump->time, cell_data); + if (!result) goto exit; + + /* get admap */ + args = &(dvdnav_jump_args_t){0}; + args->admap = dvdnav_admap_get(this, state, &args->admap_len); + if (args->admap == NULL) goto exit; + + /* find sector */ + result = dvdnav_find_vobu_by_tmap(this, state, args, cell_data, jump); + if (!result) {/* bad tmap; interpolate over cell */ + result = dvdnav_find_vobu_by_cell_boundaries(this, args, cell_data, jump); + if (!result) { + goto exit; + } + } + + /* jump to sector */ + sector_off = jump->sector - cell_data->bgn->sector; + result = vm_jump_cell_block(this->vm, cell_data->idx, sector_off); + pthread_mutex_lock(&this->vm_lock); + this->cur_cell_time = 0; + if (result) { /* vm_jump_cell_block was successful */ + this->vm->hop_channel += HOP_SEEK; + } + pthread_mutex_unlock(&this->vm_lock); + +exit: + return result; +} + diff --git a/libdvdnav-embedded/src/settings.c b/libdvdnav-embedded/src/settings.c new file mode 100644 index 0000000..297b812 --- /dev/null +++ b/libdvdnav-embedded/src/settings.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <inttypes.h> +#include <limits.h> +#include <string.h> +#include <sys/time.h> +#include "dvdnav/dvdnav.h" +#include <dvdread/nav_types.h> +#include "vm/decoder.h" +#include "vm/vm.h" +#include "dvdnav_internal.h" + +/* Characteristics/setting API calls */ + +dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *this, int32_t *region) { + (*region) = this->vm->state.registers.SPRM[20]; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *this, int32_t mask) { + 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) { + this->use_read_ahead = use_readahead; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *this, int32_t *flag) { + (*flag) = this->use_read_ahead; + return DVDNAV_STATUS_OK; +} + +static dvdnav_status_t set_language_register(dvdnav_t *this, char *code, int reg) { + 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) { + this->pgc_based = pgc; + return DVDNAV_STATUS_OK; +} + +dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *this, int32_t *flag) { + (*flag) = this->pgc_based; + return DVDNAV_STATUS_OK; +} diff --git a/libdvdnav-embedded/src/vm/decoder.c b/libdvdnav-embedded/src/vm/decoder.c new file mode 100644 index 0000000..7dd8da2 --- /dev/null +++ b/libdvdnav-embedded/src/vm/decoder.c @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2000, 2001 Martin Norbäck, Håkan 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <limits.h> +#include <string.h> /* For memset */ +#include <sys/time.h> +#include <assert.h> + +#include <dvdread/nav_types.h> + +#include "dvdnav/dvdnav.h" +#include "decoder.h" +#include "vm.h" +#include "vmcmd.h" +#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) ; + } +} + +#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 + +/* 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 system 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) { + static 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(const 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"); + assert(0); + } + 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, always 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 type */ + fprintf(MSG_OUT, "libdvdnav: WARNING: Unknown Command Type=%x\n", vm_getbits(&command, 63, 3)); + } + /* 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, " %08"PRIx64, (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(const 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; +} + diff --git a/libdvdnav-embedded/src/vm/decoder.h b/libdvdnav-embedded/src/vm/decoder.h new file mode 100644 index 0000000..82cd6ed --- /dev/null +++ b/libdvdnav-embedded/src/vm/decoder.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2000, 2001 Martin Norbäck, Håkan 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBDVDNAV_DECODER_H +#define LIBDVDNAV_DECODER_H + +#include <sys/time.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(const 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 /* LIBDVDNAV_DECODER_H */ diff --git a/libdvdnav-embedded/src/vm/getset.c b/libdvdnav-embedded/src/vm/getset.c new file mode 100644 index 0000000..4132b1c --- /dev/null +++ b/libdvdnav-embedded/src/vm/getset.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <inttypes.h> + +#include <dvdread/nav_types.h> +#include <dvdread/ifo_read.h> +#include "dvdnav/dvdnav.h" + +#include "decoder.h" +#include "vm.h" +#include "getset.h" +#include "dvdnav_internal.h" +#include "logger.h" + +#include "getset.h" +/* Set functions */ + +int set_TT(vm_t *vm, int tt) { + return set_PTT(vm, tt, 1); +} + +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); +} + +int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn) { + return set_VTS_PTT(vm, vtsN, vts_ttn, 1); +} + +int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part) { + int pgcN, pgN, res; + + (vm->state).domain = DVD_DOMAIN_VTSTitle; + + 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); + if( (vm->state.TTN_REG) == 0 ) + return 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; +} + +int set_PROG(vm_t *vm, int tt, int pgcn, int pgn) { + assert(tt <= vm->vmgi->tt_srpt->nr_of_srpts); + return set_VTS_PROG(vm, vm->vmgi->tt_srpt->title[tt - 1].title_set_nr, + vm->vmgi->tt_srpt->title[tt - 1].vts_ttn, pgcn, pgn); +} + +int set_VTS_PROG(vm_t *vm, int vtsN, int vts_ttn, int pgcn, int pgn) { + int pgcN, pgN, res, title, part = 0; + + (vm->state).domain = DVD_DOMAIN_VTSTitle; + + 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)) { + return 0; + } + + pgcN = pgcn; + pgN = pgn; + + (vm->state).TT_PGCN_REG = pgcN; + (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; + vm_get_current_title_part(vm, &title, &part); + (vm->state).PTTN_REG = part; + return res; +} + +int set_FP_PGC(vm_t *vm) { + if (!vm || !vm->vmgi) + return 1; + (vm->state).domain = DVD_DOMAIN_FirstPlay; + 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; +} + + +int set_MENU(vm_t *vm, int menu) { + assert((vm->state).domain == DVD_DOMAIN_VMGM || (vm->state).domain == DVD_DOMAIN_VTSMenu); + return set_PGCN(vm, get_ID(vm, menu)); +} + +int set_PGCN(vm_t *vm, int pgcN) { + const pgcit_t *pgcit; + + pgcit = get_PGCIT(vm); + if (pgcit == NULL) + return 0; + + if(pgcN < 1 || pgcN > pgcit->nr_of_pgci_srp) { +#ifdef TRACE + Log3(vm, "** No such pgcN = %d", 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 == DVD_DOMAIN_VTSTitle) + (vm->state).TT_PGCN_REG = pgcN; + + return 1; +} + +/* Figure out the correct pgN from the cell and update (vm->state). */ +int set_PGN(vm_t *vm) { + int new_pgN = 0; + int dummy, part = 0; + + if ((vm->state).pgc == NULL) + return 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 == DVD_DOMAIN_VTSTitle) { + if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts) + return 0; /* ?? */ + + vm_get_current_title_part(vm, &dummy, &part); + (vm->state).PTTN_REG = part; + } + return 1; +} + +/* Must be called before domain is changed (set_PGCN()) */ +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 DVD_DOMAIN_VTSTitle */ + + for(i = 0; i < 5; i++) { + (vm->state).rsm_regs[i] = (vm->state).registers.SPRM[4 + i]; + } +} + + +/* Force the highlight button number register */ +void set_HL_BTN(vm_t *vm, int btnn) +{ + (vm->state).HL_BTNN_REG = btnn << 10; +} + +/* Get functions */ + +/* Searches the TT tables, to find the current TT. + * returns the current TT. + * returns 0 if not found. + */ +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. + */ +int get_ID(vm_t *vm, int id) { + int pgcN, i; + const pgcit_t *pgcit; + + /* Relies on state to get the correct pgcit. */ + pgcit = get_PGCIT(vm); + if(pgcit == NULL) { + Log1(vm, "PGCIT null!"); + return 0; + } +#ifdef TRACE + Log3(vm, "** Searching for menu (0x%x) entry PGC", 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 + Log3(vm, "Found menu."); +#endif + return pgcN; + } + } +#ifdef TRACE + Log3(vm, "** No such id/menu (0x%02x) entry PGC", id & 0x7f); + for(i = 0; i < pgcit->nr_of_pgci_srp; i++) { + if ( (pgcit->pgci_srp[i].entry_id & 0x80) == 0x80) { + Log3(vm, "Available menus: 0x%x", + 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 */ +int get_PGCN(vm_t *vm) { + const pgcit_t *pgcit; + int pgcN = 1; + + if ((vm->state).pgc == NULL) { + return 0; /* error */ + } + + 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++; + } + } + Log0(vm, "get_PGCN failed. Was trying to find pgcN in domain %d", + (vm->state).domain); + return 0; /* error */ +} + +const pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang) { + int i; + + if(h == NULL || h->pgci_ut == NULL) { + Log0(vm, "*** pgci_ut handle is NULL ***"); + 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) { + Log1(vm, "Language '%c%c' not found, using '%c%c' instead", + (char)(lang >> 8), (char)(lang & 0xff), + (char)(h->pgci_ut->lu[0].lang_code >> 8), + (char)(h->pgci_ut->lu[0].lang_code & 0xff)); + char *buffer = malloc(3 * h->pgci_ut->nr_of_lus + 1); + if(buffer) + { + buffer[3 * h->pgci_ut->nr_of_lus] = 0; + for(i = 0; i < h->pgci_ut->nr_of_lus; i++) { + sprintf(&buffer[3*i], "%c%c ", + (char)(h->pgci_ut->lu[i].lang_code >> 8), + (char)(h->pgci_ut->lu[i].lang_code & 0xff)); + } + Log2(vm, "Menu Languages available: %s", buffer); + free(buffer); + } + i = 0; /* error? */ + } + + return h->pgci_ut->lu[i].pgcit; +} + +/* Uses state to decide what to return */ +const pgcit_t* get_PGCIT(vm_t *vm) { + const pgcit_t *pgcit = NULL; + + switch ((vm->state).domain) { + case DVD_DOMAIN_VTSTitle: + if(!vm->vtsi) return NULL; + pgcit = vm->vtsi->vts_pgcit; + break; + case DVD_DOMAIN_VTSMenu: + if(!vm->vtsi) return NULL; + pgcit = get_MENU_PGCIT(vm, vm->vtsi, (vm->state).registers.SPRM[0]); + break; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + pgcit = get_MENU_PGCIT(vm, vm->vmgi, (vm->state).registers.SPRM[0]); + break; + default: + abort(); + } + + return pgcit; +} + diff --git a/libdvdnav-embedded/src/vm/getset.h b/libdvdnav-embedded/src/vm/getset.h new file mode 100644 index 0000000..5e80ffe --- /dev/null +++ b/libdvdnav-embedded/src/vm/getset.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* get_XYZ returns a value. + * set_XYZ sets state using passed parameters. + * returns success/failure. + */ + +/* Set */ +int set_TT(vm_t *vm, int tt); +int set_PTT(vm_t *vm, int tt, int ptt); +int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn); +int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part); +int set_PROG(vm_t *vm, int tt, int pgcn, int pgn); +int set_VTS_PROG(vm_t *vm, int vtsN, int vts_ttn, int pgcn, int pgn); +int set_FP_PGC(vm_t *vm); +int set_MENU(vm_t *vm, int menu); +int set_PGCN(vm_t *vm, int pgcN); +int set_PGN(vm_t *vm); /* Set PGN based on (vm->state).CellN */ +void set_RSMinfo(vm_t *vm, int cellN, int blockN); +void set_HL_BTN(vm_t *vm, int btnn); + +/* Get */ +int get_TT(vm_t *vm, int vtsN, int vts_ttn); +int get_ID(vm_t *vm, int id); +int get_PGCN(vm_t *vm); + +const pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang); +const pgcit_t* get_PGCIT(vm_t *vm); + diff --git a/libdvdnav-embedded/src/vm/play.c b/libdvdnav-embedded/src/vm/play.c new file mode 100644 index 0000000..8830883 --- /dev/null +++ b/libdvdnav-embedded/src/vm/play.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +#include <dvdread/nav_types.h> +#include <dvdread/ifo_read.h> +#include "dvdnav/dvdnav.h" + +#include "decoder.h" +#include "vm.h" +#include "play.h" +#include "vm/getset.h" + +#include "dvdnav_internal.h" +#include "logger.h" + +/* Playback control */ + +link_t play_PGC(vm_t *vm) { + link_t link_values; + +#ifdef TRACE + if((vm->state).domain != DVD_DOMAIN_FirstPlay) { + Log3(vm, "play_PGC: (vm->state).pgcN (%i)", get_PGCN(vm)); + } else { + Log3(vm, "play_PGC: first_play_pgc"); + } +#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; + + /* Handle random playback mode by choosing our initial program + * number randomly - some discs end up jumping to nowhere if you + * always choose the first cell. + */ + if ((vm->state).pgc->pg_playback_mode!=0 && + ((vm->state).pgc->pg_playback_mode & 0x80)==0) { + int pgCnt = ((vm->state).pgc->pg_playback_mode & 0x7f) + 1; + if (pgCnt > (vm->state).pgc->nr_of_programs) { + pgCnt = (vm->state).pgc->nr_of_programs; + } + if (pgCnt>1) { + (vm->state).pgN = 1 + ((int) ((float) pgCnt * rand()/(RAND_MAX+1.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 + Log3(vm, "PGC pre commands didn't do a Jump, Link or Call"); +#endif + } + } + return play_PG(vm); +} + +link_t play_PGC_PG(vm_t *vm, int pgN) { + link_t link_values; + +#ifdef TRACE + if((vm->state).domain != DVD_DOMAIN_FirstPlay) { + Log3(vm, "play_PGC_PG: (vm->state).pgcN (%i)", get_PGCN(vm)); + } else { + Log3(vm, "play_PGC_PG: first_play_pgc"); + } +#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 + Log3(vm, "PGC pre commands didn't do a Jump, Link or Call"); +#endif + } + } + return play_PG(vm); +} + +link_t play_PGC_post(vm_t *vm) { + link_t link_values = { LinkNoLink, 0, 0, 0 }; + +#ifdef TRACE + Log3(vm, "play_PGC_post:"); +#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 + Log3(vm, "** Fell of the end of the pgc, continuing in NextPGC"); +#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); +} + +link_t play_PG(vm_t *vm) { +#ifdef TRACE + Log3(vm, "play_PG: (vm->state).pgN (%i)", (vm->state).pgN); +#endif + + assert((vm->state).pgN > 0); + if((vm->state).pgN > (vm->state).pgc->nr_of_programs) { +#ifdef TRACE + Log3(vm, "play_PG: (vm->state).pgN (%i) > pgc->nr_of_programs (%i)", + (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); +} + +link_t play_Cell(vm_t *vm) { + static const link_t play_this = {PlayThis, /* Block in Cell */ 0, 0, 0}; + +#ifdef TRACE + Log3(vm, "play_Cell: (vm->state).cellN (%i)", (vm->state).cellN); +#endif + + assert((vm->state).cellN > 0); + if((vm->state).cellN > (vm->state).pgc->nr_of_cells) { +#ifdef TRACE + Log3(vm, "(vm->state).cellN (%i) > pgc->nr_of_cells (%i)", + (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 DVDNAV_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)) { + Log1(vm, "Invalid angle block"); + (vm->state).cellN -= (vm->state).AGL_REG - 1; + } +#endif + break; + case 2: /* reserved */ + case 3: /* reserved */ + default: + Log1(vm, "Invalid? Cell block_mode (%d), block_type (%d)", + (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: + Log1(vm, "Cell is in block but did not enter at first cell!"); + } + + /* 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 + Log3(vm, "Cell should restart here"); +#endif + return play_this; +} + +link_t play_Cell_post(vm_t *vm) { + const cell_playback_t *cell; + +#ifdef TRACE + Log3(vm, "play_Cell_post: (vm->state).cellN (%i)", (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; + + if ((vm->state).pgc->command_tbl != NULL && + (vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr) { +#ifdef TRACE + Log3(vm, "Cell command present, executing"); +#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 + Log3(vm, "Cell command didn't do a Jump, Link or Call"); +#endif + } + } else { +#ifdef TRACE + Log3(vm, "Invalid Cell command"); +#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: /* reserved */ + case 3: /* reserved */ + default: + Log1(vm, "Invalid? Cell block_mode (%d), block_type (%d)", + (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 + Log3(vm, "last cell in this PGC"); +#endif + return play_PGC_post(vm); + } + return play_Cell(vm); +} diff --git a/libdvdnav-embedded/src/vm/play.h b/libdvdnav-embedded/src/vm/play.h new file mode 100644 index 0000000..7d76471 --- /dev/null +++ b/libdvdnav-embedded/src/vm/play.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Play */ +link_t play_PGC(vm_t *vm); +link_t play_PGC_PG(vm_t *vm, int pgN); +link_t play_PGC_post(vm_t *vm); +link_t play_PG(vm_t *vm); +link_t play_Cell(vm_t *vm); +link_t play_Cell_post(vm_t *vm); + diff --git a/libdvdnav-embedded/src/vm/vm.c b/libdvdnav-embedded/src/vm/vm.c new file mode 100644 index 0000000..9276c91 --- /dev/null +++ b/libdvdnav-embedded/src/vm/vm.c @@ -0,0 +1,1177 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <limits.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <fcntl.h> +#include <ctype.h> + +#include <dvdread/nav_types.h> +#include <dvdread/ifo_read.h> +#include "dvdnav/dvdnav.h" + +#include "decoder.h" +#include "vm.h" +#include "play.h" +#include "getset.h" +#include "dvdnav_internal.h" +#include "logger.h" + +#ifdef _MSC_VER +#include <io.h> /* read() */ +#endif /* _MSC_VER */ + +#ifdef __OS2__ +#define INCL_DOS +#include <os2safe.h> +#include <os2.h> +#include <io.h> /* setmode() */ +#include <fcntl.h> /* O_BINARY */ +#endif + +#if DVDREAD_VERSION >= DVDREAD_VERSION_CODE(6,1,0) +static void dvd_reader_logger_handler( void *priv, dvd_logger_level_t level, + const char *fmt, va_list list ) +{ + vm_t *vm = priv; + if(vm->logcb.pf_log) + vm->logcb.pf_log(vm->priv, (dvdnav_logger_level_t) level, fmt, list ); +} +#endif + +static int dvd_reader_seek_handler(void *priv, uint64_t pos) +{ + vm_t *vm = priv; + if(vm->streamcb.pf_seek) + return vm->streamcb.pf_seek(vm->priv, pos); + return 1; +} + +static int dvd_reader_read_handler(void *priv, void *buffer, int count) +{ + vm_t *vm = priv; + if(vm->streamcb.pf_read) + return vm->streamcb.pf_read(vm->priv, buffer, count); + return 1; +} + +static int dvd_reader_readv_handler(void *priv, void *iovec, int count) +{ + vm_t *vm = priv; + if(vm->streamcb.pf_readv) + return vm->streamcb.pf_readv(vm->priv, iovec, count); + return 1; +} + +/* +#define DVDNAV_STRICT +*/ + +/* Local prototypes */ + +/* Process link - returns 1 if a hop has been performed */ +static int process_command(vm_t *vm,link_t link_values); + +/* Helper functions */ +static void vm_close(vm_t *vm); + +/* Debug functions */ + +#ifdef TRACE +void vm_position_print(vm_t *vm, vm_position_t *position) { + Log3(vm, "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", + 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); +} + +static void vm_print_current_domain_state(vm_t *vm) { + const char *domain; + + switch(vm->state.domain) { + case DVD_DOMAIN_VTSTitle: domain = "Video Title"; break; + case DVD_DOMAIN_VTSMenu: domain = "Video Title Menu"; break; + case DVD_DOMAIN_VMGM: domain = "Video Manager Menu"; break; + case DVD_DOMAIN_FirstPlay: domain = "First Play"; break; + default: domain = "Unknown"; break; + } + Log3(vm, "%s Domain: VTS:%d PGC:%d PG:%u CELL:%u BLOCK:%u VTS_TTN:%u TTN:%u TT_PGCN:%u", + domain, + 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 + +#ifdef __OS2__ +#define open os2_open + +static int os2_open(const char *name, int oflag) +{ + HFILE hfile; + ULONG ulAction; + ULONG rc; + + rc = DosOpenL(name, &hfile, &ulAction, 0, FILE_NORMAL, + OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW, + OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE | OPEN_FLAGS_DASD, + NULL); + + if(rc) + return -1; + + setmode(hfile, O_BINARY); + + return (int)hfile; +} +#endif + +static void escaped_strcpy(char *dst, const char *src, size_t len) +{ + for(size_t i=0; i < len; i++ ) + { + if(src[i] == 0) + { + dst[i] = 0; + break; + } + else if(isprint(src[i])) + { + dst[i] = src[i]; + } else { + dst[i] = ' '; + } + } +} + +static int dvd_read_name(const vm_t *vm, char *name, char *serial, const char *device) { + /* Because we are compiling with _FILE_OFFSET_BITS=64 + * all off_t are 64bit. + */ + off_t off; + ssize_t read_size = 0; + int fd = -1; + uint8_t data[DVD_VIDEO_LB_LEN]; + + /* Read DVD name */ + if (device == NULL) { + Log0(vm, "Device name string NULL"); + goto fail; + } + if ((fd = open(device, O_RDONLY)) == -1) { + Log0(vm, "Unable to open device file %s.", device); + goto fail; + } + + if ((off = lseek( fd, 32 * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET )) == (off_t) - 1) { + Log0(vm, "Unable to seek to the title block %u.", 32); + goto fail; + } + + if( off != ( 32 * (off_t) DVD_VIDEO_LB_LEN ) ) { + Log0(vm, "Can't seek to block %u", 32 ); + goto fail; + } + + if ((read_size = read( fd, data, DVD_VIDEO_LB_LEN )) == -1) { + Log0(vm, "Can't read name block. Probably not a DVD-ROM device."); + goto fail; + } + + close(fd); + fd = -1; + if (read_size != DVD_VIDEO_LB_LEN) { + Log0(vm, "Can't read name block. Probably not a DVD-ROM device."); + goto fail; + } + + char buffer[49] = {0}; + strncpy(name, (const char*) &data[25], 48); + name[48] = 0; + escaped_strcpy(buffer, name, 48); + Log2(vm, "DVD Title: %s", buffer); + + strncpy(serial, (const char*) &data[73], 14); + serial[14] = 0; + escaped_strcpy(buffer, serial, 14); + Log2(vm, "DVD Serial Number: %s", buffer); + + escaped_strcpy(buffer, (const char *) &data[89], 128 - 89); + Log2(vm, "DVD Title (Alternative): %s", buffer); + + return 1; + +fail: + if (fd >= 0) + close(fd); + + return 0; +} + +int ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN) { + if(vm->state.vtsN == vtsN) { + return 1; /* We already have it */ + } + + if(vm->vtsi != NULL) + ifoClose(vm->vtsi); + + vm->vtsi = ifoOpenVTSI(dvd, vtsN); + if(vm->vtsi == NULL) { + Log0(vm, "ifoOpenVTSI failed"); + return 0; + } + if(!ifoRead_VTS_PTT_SRPT(vm->vtsi)) { + Log0(vm, "ifoRead_VTS_PTT_SRPT failed"); + return 0; + } + if(!ifoRead_PGCIT(vm->vtsi)) { + Log0(vm, "ifoRead_PGCIT failed"); + return 0; + } + if(!ifoRead_PGCI_UT(vm->vtsi)) { + Log0(vm, "ifoRead_PGCI_UT failed"); + return 0; + } + if(!ifoRead_VOBU_ADMAP(vm->vtsi)) { + Log0(vm, "ifoRead_VOBU_ADMAP vtsi failed"); + return 0; + } + if(!ifoRead_TITLE_VOBU_ADMAP(vm->vtsi)) { + Log0(vm, "ifoRead_TITLE_VOBU_ADMAP vtsi failed"); + return 0; + } + vm->state.vtsN = vtsN; + + return 1; +} + + +/* Initialisation & Destruction */ + +vm_t* vm_new_vm(void *priv, const dvdnav_logger_cb *logcb) { + vm_t *vm = calloc(1, sizeof(vm_t)); + if(vm) + { + vm->priv = priv; + if(logcb) + vm->logcb = *logcb; + } + return vm; +} + +void vm_free_vm(vm_t *vm) { + vm_close(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) { + if (vm->stopped) { + if (!vm_reset(vm, NULL, NULL, NULL)) + return 0; + + vm->stopped = 0; + } + /* 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) { + vm->stopped = 1; +} + +static void vm_close(vm_t *vm) { + if(!vm) + return; + if(vm->vmgi) { + ifoClose(vm->vmgi); + vm->vmgi=NULL; + } + if(vm->vtsi) { + ifoClose(vm->vtsi); + vm->vtsi=NULL; + } + if(vm->dvd) { + DVDClose(vm->dvd); + vm->dvd=NULL; + } + vm->stopped = 1; +} + +int vm_reset(vm_t *vm, const char *dvdroot, + void *priv, dvdnav_stream_cb *stream_cb) { + /* 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 Language 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.registers.SPRM[15] = 0x7CFC; /* Audio capabilities - All defined audio types */ + + vm->state.pgN = 0; + vm->state.cellN = 0; + vm->state.cell_restart = 0; + + vm->state.domain = DVD_DOMAIN_FirstPlay; + vm->state.rsm_vtsN = 0; + vm->state.rsm_cellN = 0; + vm->state.rsm_blockN = 0; + + vm->state.vtsN = -1; + + vm->hop_channel = 0; + + /* save target callbacks */ + if(stream_cb) + vm->streamcb = *stream_cb; + else + vm->streamcb = (dvdnav_stream_cb) { NULL, NULL, NULL }; + + /* bind local callbacks */ + vm->dvdstreamcb.pf_seek = vm->streamcb.pf_seek ? dvd_reader_seek_handler : NULL; + vm->dvdstreamcb.pf_read = vm->streamcb.pf_read ? dvd_reader_read_handler : NULL; + vm->dvdstreamcb.pf_readv = vm->streamcb.pf_readv ? dvd_reader_readv_handler : NULL; + + if (vm->dvd && (dvdroot || (priv && stream_cb))) { + /* a new dvd device has been requested */ + vm_close(vm); + } + if (!vm->dvd) { + /* dvdread stream callback handlers for redirection */ +#if DVDREAD_VERSION >= DVDREAD_VERSION_CODE(6,1,0) + dvd_logger_cb dvdread_logcb = { .pf_log = dvd_reader_logger_handler }; + /* Only install log handler if we have one ourself */ + dvd_logger_cb *p_dvdread_logcb = vm->logcb.pf_log ? &dvdread_logcb : NULL; + if(dvdroot) + vm->dvd = DVDOpen2(vm, p_dvdread_logcb, dvdroot); + else if(vm->priv && vm->dvdstreamcb.pf_read) + vm->dvd = DVDOpenStream2(vm, p_dvdread_logcb, &vm->dvdstreamcb); +#else + if(dvdroot) + vm->dvd = DVDOpen(dvdroot); + else if(vm->priv && vm->dvdstreamcb.pf_read) + vm->dvd = DVDOpenStream(vm, &vm->dvdstreamcb); +#endif + if(!vm->dvd) { + Log0(vm, "vm: failed to open/read the DVD"); + return 0; + } + vm->vmgi = ifoOpenVMGI(vm->dvd); + if(!vm->vmgi) { + Log0(vm, "vm: vm: failed to read VIDEO_TS.IFO"); + return 0; + } + if(!ifoRead_FP_PGC(vm->vmgi)) { + Log0(vm, "vm: vm: ifoRead_FP_PGC failed"); + return 0; + } + if(!ifoRead_TT_SRPT(vm->vmgi)) { + Log0(vm, "vm: vm: ifoRead_TT_SRPT failed"); + return 0; + } + if(!ifoRead_PGCI_UT(vm->vmgi)) { + Log0(vm, "vm: vm: ifoRead_PGCI_UT failed"); + return 0; + } + if(!ifoRead_PTL_MAIT(vm->vmgi)) { + Log0(vm, "vm: ifoRead_PTL_MAIT failed"); + /* return 0; Not really used for now.. */ + } + if(!ifoRead_VTS_ATRT(vm->vmgi)) { + Log0(vm, "vm: ifoRead_VTS_ATRT failed"); + /* return 0; Not really used for now.. */ + } + if(!ifoRead_VOBU_ADMAP(vm->vmgi)) { + Log0(vm, "vm: ifoRead_VOBU_ADMAP vgmi failed"); + /* return 0; Not really used for now.. */ + } + /* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */ + if(dvd_read_name(vm, vm->dvd_name, vm->dvd_serial, dvdroot) != 1) { + Log1(vm, "vm: dvd_read_name failed"); + } + } + if (vm->vmgi) { + int i, mask; + char buffer[8 * 3 + 1]; + char *p = buffer; + for (i = 1, mask = 1; i <= 8; i++, mask <<= 1) + { + if (((vm->vmgi->vmgi_mat->vmg_category >> 16) & mask) == 0) + { + sprintf(p, " %02d", i); + p +=3; + } + } + *p = 0; + Log2(vm, "DVD disk reports itself with Region mask 0x%08x. Regions:%s", + vm->vmgi->vmgi_mat->vmg_category, buffer); + } + return 1; +} + + +/* copying and merging */ + +vm_t *vm_new_copy(vm_t *source) { + vm_t *target = vm_new_vm(source->priv, &source->logcb); + int vtsN; + int pgcN = get_PGCN(source); + int pgN = (source->state).pgN; + + if (target == NULL || pgcN == 0) + goto fail; + + 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)) + goto fail; + + /* restore pgc pointer into the new vtsi */ + if (!set_PGCN(target, pgcN)) + goto fail; + + (target->state).pgN = pgN; + } + + return target; + +fail: + if (target != NULL) + vm_free_vm(target); + + return NULL; +} + +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_program(vm_t *vm, int title, int pgcn, int pgn) { + link_t link; + + if(!set_PROG(vm, title, pgcn, pgn)) + 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 occurred -> ignore it and play the PG anyway */ + process_command(vm, play_PG(vm)); + else + process_command(vm, link); + 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 occurred -> 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) { + DVDDomain_t old_domain = vm->state.domain; + + switch (vm->state.domain) { + case DVD_DOMAIN_FirstPlay: /* FIXME XXX $$$ What should we do here? */ + break; + case DVD_DOMAIN_VTSTitle: + set_RSMinfo(vm, 0, vm->state.blockN); + /* FALL THROUGH */ + case DVD_DOMAIN_VTSMenu: + case DVD_DOMAIN_VMGM: + switch(menuid) { + case DVD_MENU_Title: + case DVD_MENU_Escape: + if(vm->vmgi == NULL || vm->vmgi->pgci_ut == NULL) + return 0; + vm->state.domain = DVD_DOMAIN_VMGM; + break; + case DVD_MENU_Root: + case DVD_MENU_Subpicture: + case DVD_MENU_Audio: + case DVD_MENU_Angle: + case DVD_MENU_Part: + if(vm->vtsi == NULL || vm->vtsi->pgci_ut == NULL) + return 0; + vm->state.domain = DVD_DOMAIN_VTSMenu; + 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; + } + + 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; + return !!process_command(vm, link_values); +} + +int vm_exec_cmd(vm_t *vm, const 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 that's all... */ +} + +/* link processing */ + +static int process_command(vm_t *vm, link_t link_values) { + + while(link_values.command != PlayThis) { + +#ifdef TRACE + Log3(vm, "Before printout starts:"); + vm_print_link(link_values); + Log3(vm, "Link values %i %i %i %i", link_values.command, + link_values.data1, link_values.data2, link_values.data3); + vm_print_current_domain_state(vm); + Log3(vm, "Before printout ends."); +#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: + /* Check and see if there is any rsm info!! */ + if (!vm->state.rsm_vtsN) { + Log2(vm, "trying to resume without any resume info set"); + link_values.command = Exit; + break; + } + + vm->state.domain = DVD_DOMAIN_VTSTitle; + 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; ?? */ + int i; + 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 == DVD_DOMAIN_VTSTitle); + 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)) + link_values.command = Exit; + else + 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 == DVD_DOMAIN_VMGM || vm->state.domain == DVD_DOMAIN_FirstPlay); /* ?? */ + 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 == DVD_DOMAIN_VTSMenu || vm->state.domain == DVD_DOMAIN_VTSTitle); /* ?? */ + if(!set_VTS_TT(vm, vm->state.vtsN, link_values.data1)) + link_values.command = Exit; + else + 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 == DVD_DOMAIN_VTSMenu || vm->state.domain == DVD_DOMAIN_VTSTitle); /* ?? */ + if(!set_VTS_PTT(vm, vm->state.vtsN, link_values.data1, link_values.data2)) + link_values.command = Exit; + else + 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 == DVD_DOMAIN_VMGM || vm->state.domain == DVD_DOMAIN_VTSMenu); /* ?? */ + if (!set_FP_PGC(vm)) + assert(0); + link_values = play_PGC(vm); + break; + + case JumpSS_VMGM_MENU: + /* Jump to Video Manager 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 != DVD_DOMAIN_VTSTitle); /* ?? */ + if(vm->vmgi == NULL || vm->vmgi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + vm->state.domain = DVD_DOMAIN_VMGM; + 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) { + assert(vm->state.domain == DVD_DOMAIN_VTSMenu || + vm->state.domain == DVD_DOMAIN_VMGM || vm->state.domain == DVD_DOMAIN_FirstPlay); /* ?? */ + if (link_values.data1 != vm->state.vtsN) { + /* the normal case */ + assert(vm->state.domain != DVD_DOMAIN_VTSMenu); + if (!ifoOpenNewVTSI(vm, vm->dvd, link_values.data1)) /* Also sets vm->state.vtsN */ + vm->vtsi = NULL; + } else { + /* This happens on some discs like "Captain Scarlet & the Mysterons" or + * the German RC2 of "Anatomie" in VTSM. */ + } + + if(vm->vtsi == NULL || vm->vtsi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + vm->state.domain = DVD_DOMAIN_VTSMenu; + } else { + /* This happens on 'The Fifth Element' region 2. */ + assert(vm->state.domain == DVD_DOMAIN_VTSMenu); + } + /* 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 != DVD_DOMAIN_VTSTitle); /* ?? */ + if(vm->vmgi == NULL || vm->vmgi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + vm->state.domain = DVD_DOMAIN_VMGM; + 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 == DVD_DOMAIN_VTSTitle); /* ?? */ + /* 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 == DVD_DOMAIN_VTSTitle); /* ?? */ + /* Must be called before domain is changed */ + if(vm->vmgi == NULL || vm->vmgi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + vm->state.domain = DVD_DOMAIN_VMGM; + 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 == DVD_DOMAIN_VTSTitle); /* ?? */ + /* Must be called before domain is changed */ + if(vm->vtsi == NULL || vm->vtsi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + vm->state.domain = DVD_DOMAIN_VTSMenu; + 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 == DVD_DOMAIN_VTSTitle); /* ?? */ + /* Must be called before domain is changed */ + if(vm->vmgi == NULL || vm->vmgi->pgci_ut == NULL) { + link_values.command = Exit; + break; + } + set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0); + vm->state.domain = DVD_DOMAIN_VMGM; + 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 + Log3(vm, "After printout starts:"); + vm_print_current_domain_state(vm); + Log3(vm, "After printout ends."); +#endif + + } + + vm->state.blockN = link_values.data1 | (link_values.data2 << 16); + return 1; +} + +//return the ifo_handle_t describing required title, used to +//identify chapters +ifo_handle_t *vm_get_title_ifo(vm_t *vm, uint32_t title) +{ + uint8_t titleset_nr; + if((title < 1) || (title > vm->vmgi->tt_srpt->nr_of_srpts)) + return NULL; + titleset_nr = vm->vmgi->tt_srpt->title[title-1].title_set_nr; + return ifoOpen(vm->dvd, titleset_nr); +} + +void vm_ifo_close(ifo_handle_t *ifo) +{ + ifoClose(ifo); +} diff --git a/libdvdnav-embedded/src/vm/vm.h b/libdvdnav-embedded/src/vm/vm.h new file mode 100644 index 0000000..bada9f0 --- /dev/null +++ b/libdvdnav-embedded/src/vm/vm.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBDVDNAV_VM_H +#define LIBDVDNAV_VM_H + +/** + * State: SPRM, GPRM, Domain, pgc, pgN, cellN, ? + */ +typedef struct { + registers_t registers; + + DVDDomain_t domain; + int vtsN; /* 0 is vmgm? */ + const pgc_t *pgc; /* either this or 'int pgcN' is enough? */ + int pgcN; /* but provide pgcN for quick lookup */ + int pgN; /* is this needed? can always 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 */ + DVDDomain_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 { + void *priv; + dvdnav_logger_cb logcb; + dvdnav_stream_cb streamcb; + dvd_reader_t *dvd; + dvd_reader_stream_cb dvdstreamcb; + ifo_handle_t *vmgi; + ifo_handle_t *vtsi; + dvd_state_t state; + int32_t hop_channel; + char dvd_name[50]; + char dvd_serial[15]; + 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 *, const dvdnav_logger_cb *); +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, void *priv, + dvdnav_stream_cb *stream_cb); + +/* 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_title_program(vm_t *vm, int title, int pgcn, int pgn); +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, const 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); +#endif +void vm_get_video_res(vm_t *vm, int *width, int *height); +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); +ifo_handle_t *vm_get_title_ifo(vm_t *vm, uint32_t title); +void vm_ifo_close(ifo_handle_t *ifo); + +/* Uncomment for VM command tracing */ +/* #define TRACE */ +#ifdef TRACE +/* Debug */ +void vm_position_print(vm_t *vm, vm_position_t *position); +#endif + +int ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN); + + +#endif /* LIBDVDNAV_VM_H */ diff --git a/libdvdnav-embedded/src/vm/vmcmd.c b/libdvdnav-embedded/src/vm/vmcmd.c new file mode 100644 index 0000000..557a8a1 --- /dev/null +++ b/libdvdnav-embedded/src/vm/vmcmd.c @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2000, 2001 Martin Norbäck, Håkan 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <ctype.h> +#include <inttypes.h> +#include <limits.h> +#include <sys/time.h> + +#include "dvdnav/dvdnav.h" +#include <dvdread/nav_types.h> +#include "decoder.h" +#include "vm.h" +#include "vmcmd.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[][4] = { + "", "&", "==", "!=", ">=", ">", "<=", "<" +}; +static const char set_op_table[][4] = { + "", "=", "<->", "+=", "-=", "*=", "/=", "%=", "rnd", "&=", "|=", "^=" +}; + +static const char link_table[][16] = { + "LinkNoLink", "LinkTopC", "LinkNextC", "LinkPrevC", + "", "LinkTopPG", "LinkNextPG", "LinkPrevPG", + "", "LinkTopPGC", "LinkNextPGC", "LinkPrevPGC", + "LinkGoUpPGC", "LinkTailPGC", "", "", + "RSM" +}; + +static const char *const 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[][8] = { + "", + "ASTN", + "SPSTN", + "AGLN", + "TTN", + "VTS_TTN", + "TT_PGCN", + "PTTN", + "HL_BTNN", + "NVTMR", + "NV_PGCN", + "", + "CC_PLT", + "PLT", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +}; + +static void print_system_reg(uint16_t reg) { + if(reg < sizeof(system_reg_abbr_table) / sizeof(system_reg_abbr_table[0])) + 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(cmp_op_table[0])) + 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(cmp_op_table[0])) + 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(link_table[0])) + 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 present */ + } + 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, " %08"PRIx64, (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-embedded/src/vm/vmcmd.h b/libdvdnav-embedded/src/vm/vmcmd.h new file mode 100644 index 0000000..1e28c11 --- /dev/null +++ b/libdvdnav-embedded/src/vm/vmcmd.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2000, 2001 Martin Norbäck, Håkan 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LIBDVDNAV_VMCMD_H +#define LIBDVDNAV_VMCMD_H + +void vm_print_mnemonic(vm_cmd_t *command); +void vm_print_cmd(int row, vm_cmd_t *command); + +#endif /* LIBDVDNAV_VMCMD_H */ diff --git a/libdvdnav-embedded/src/vm/vmget.c b/libdvdnav-embedded/src/vm/vmget.c new file mode 100644 index 0000000..5816f2c --- /dev/null +++ b/libdvdnav-embedded/src/vm/vmget.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2000, 2001 Håkan Hjort + * Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net> + * 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 libdvdnav; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dvdread/nav_types.h> +#include <dvdread/ifo_read.h> +#include "dvdnav/dvdnav.h" + +#include "decoder.h" +#include "vm.h" +#include "getset.h" +#include "dvdnav_internal.h" +#include "logger.h" + +/* getting information */ + +int vm_get_current_menu(vm_t *vm, int *menuid) { + const pgcit_t* pgcit; + int pgcn; + pgcn = (vm->state).pgcN; + pgcit = get_PGCIT(vm); + if(pgcit==NULL) return 0; + *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) { + Log1(vm, "chapter NOT FOUND!"); + return 0; + } + + title = get_TT(vm, vm->state.vtsN, vts_ttn); + +#ifdef TRACE + if (title) { + Log3(vm, "************ this chapter FOUND!"); + Log3(vm, "VTS_PTT_SRPT - Title %3i part %3i: PGC: %3i PG: %3i", + 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 != DVD_DOMAIN_VTSTitle) + 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 != DVD_DOMAIN_VTSTitle && 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 != DVD_DOMAIN_VTSTitle) + subpN = 0; + + if(subpN < 32) { /* a valid logical stream */ + /* Is this logical stream present */ + if((vm->state).pgc->subp_control[subpN] & (1u<<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 != DVD_DOMAIN_VTSTitle && 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 == DVD_DOMAIN_VTSTitle && !((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 == DVD_DOMAIN_VTSTitle) { + const title_info_t *title; + /* TTN_REG does not always 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 DVD_DOMAIN_VTSTitle: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_audio_streams; + *current = (vm->state).AST_REG; + break; + case DVD_DOMAIN_VTSMenu: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_audio_streams; /* 1 */ + *current = 1; + break; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + *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 DVD_DOMAIN_VTSTitle: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vts_subp_streams; + *current = (vm->state).SPST_REG; + break; + case DVD_DOMAIN_VTSMenu: + *num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_subp_streams; /* 1 */ + *current = 0x41; + break; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + *num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_subp_streams; /* 1 */ + *current = 0x41; + break; + } +} +#endif + +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; + } +} + +int vm_get_video_aspect(vm_t *vm) { + int aspect = vm_get_video_attr(vm).display_aspect_ratio; + + if(aspect != 0 && aspect != 3) { + Log1(vm, "display aspect ratio is unexpected: %d!", aspect); + return -1; + } + + (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 DVD_DOMAIN_VTSTitle: + return vm->vtsi->vtsi_mat->vts_video_attr; + case DVD_DOMAIN_VTSMenu: + return vm->vtsi->vtsi_mat->vtsm_video_attr; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + return vm->vmgi->vmgi_mat->vmgm_video_attr; + default: + assert(0); + } +} + +audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN) { + switch ((vm->state).domain) { + case DVD_DOMAIN_VTSTitle: + return vm->vtsi->vtsi_mat->vts_audio_attr[streamN]; + case DVD_DOMAIN_VTSMenu: + return vm->vtsi->vtsi_mat->vtsm_audio_attr; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + return vm->vmgi->vmgi_mat->vmgm_audio_attr; + default: + assert(0); + } +} + +subp_attr_t vm_get_subp_attr(vm_t *vm, int streamN) { + switch ((vm->state).domain) { + case DVD_DOMAIN_VTSTitle: + return vm->vtsi->vtsi_mat->vts_subp_attr[streamN]; + case DVD_DOMAIN_VTSMenu: + return vm->vtsi->vtsi_mat->vtsm_subp_attr; + case DVD_DOMAIN_VMGM: + case DVD_DOMAIN_FirstPlay: + return vm->vmgi->vmgi_mat->vmgm_subp_attr; + default: + assert(0); + } +} |