Hi,
The attached patch adds serialization support to libdvdnav, which
effectively allows DVD bookmarks to be saved and restored. The current
VM state is converted to/from an ASCII string.
This is a patch I created for MythTV, where it is currently in use.
As I wrote in the submit description for MythTV:
"The state snapshot code is borrowed/adapted from or inspired by XBMC
and Ogle."
Richard.
Index: src/vm/vm_serialize.c
===================================================================
--- src/vm/vm_serialize.c (revision 0)
+++ src/vm/vm_serialize.c (revision 0)
@@ -0,0 +1,217 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+
+#include <dvdread/nav_types.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/ifo_read.h>
+#include "dvdnav/dvdnav.h"
+#include "remap.h"
+#include "decoder.h"
+#include "vm.h"
+#include "vm_serialize.h"
+
+/*
+"navstate",<version>,<sprm x 24>,<gprm x 16>,
+<domain>,<vtsn>,<pgcn>,<pgn>,<celln>,<cell_restart>,<blockn>,
+<rsm_vts>,<rsm_blockn>,<rsm_pgcn>,<rsm_celln>,<rsm_sprm x 5>,"end"
+*/
+/* The serialized string starts with "navstate,1,", so that needs 11 chars.
+ * There are 10 integer fields (domain, vtsN, pgcN, pgN, cellN, cell_restart,
+ * blockN, rsm_vtsN, rsm_blockN, rsm_pgcN and rsm_cellN).
+ * None of the values can realistically be more than 32 bits (and most will
+ * be far less). In worst case, each field would need 11 characters
+ * (-2147483648) plus a trailing comma, so we need 10 * (11+1) = 120 chars
+ * for those 10 fields.
+ * Each SPRM is 16 bits, so thats 6 chars + 1 per register ("0xffff,") * 24
+ * registers = 168 chars.
+ * Each GPRM is 16 bits plus a mode character plus two 32 bit counter values.
+ * In the format "[0xffff;0;0xffffffff;0xffffffff],", each register needs 33
+ * chars. With 16 registers, that's 33 * 16 = 528
+ * There are five resume SPRM values, so 5 * (6 + !) = 42
+ * Finally, the string is terminated with "end" and a NULL char, so another 4 chars.
+ * In total then, 11 + 120 + 168 + 528 + 42 + 4 = 873 chars. Round that up to 1024
+ * and there should never be any issues. Just to be safe, we'll still check as
+ * we go along.
+ */
+
+#define BUFFER_SIZE 1024
+#define FORMAT_VERSION 1
+
+static void vm_serialize_int(int *stored, char **buf, size_t *remaining, int value)
+{
+ if(stored && buf && remaining && *stored > 0)
+ {
+ *stored = snprintf(*buf, *remaining, "%d,", value);
+ if(*stored > 0)
+ {
+ *remaining -= (size_t)(*stored);
+ *buf += (*stored);
+ }
+ }
+}
+
+static void vm_deserialize_int(int *consumed, char **buf, int* value)
+{
+ if(consumed && buf && *consumed > 0)
+ {
+ sscanf( *buf, "%d,%n", value, consumed);
+
+ if(*consumed > 0)
+ *buf += *consumed;
+ }
+}
+
+char *vm_serialize_dvd_state(const dvd_state_t *state)
+{
+ char *str_state = 0;
+ char *buf;
+ int stored;
+ int tmp;
+ int idx;
+ size_t remaining = BUFFER_SIZE;
+
+ if(state)
+ {
+ str_state = malloc(BUFFER_SIZE);
+ buf = str_state;
+
+ stored = snprintf(buf, remaining, "navstat,%d,", FORMAT_VERSION);
+
+ if(stored > 0)
+ {
+ remaining -= (size_t)stored;
+ buf += stored;
+ }
+
+ // SPRM
+ for(idx = 0; idx < 24 && stored > 0; idx++)
+ {
+ stored = snprintf(buf, remaining, "0x%hx,", state->registers.SPRM[idx]);
+ if(stored > 0)
+ {
+ remaining -= (size_t)stored;
+ buf += stored;
+ }
+ }
+
+ // GPRM
+ for(idx = 0; idx < 16 && stored > 0; idx++)
+ {
+ stored = snprintf(buf, remaining,
+ "[0x%hx;%d;0x%x;0x%x],", state->registers.GPRM[idx],
+ state->registers.GPRM_mode[idx],
+ state->registers.GPRM_time[idx].tv_sec,
+ state->registers.GPRM_time[idx].tv_usec);
+ if(stored > 0)
+ {
+ remaining -= (size_t)stored;
+ buf += stored;
+ }
+ }
+
+ vm_serialize_int(&stored, &buf, &remaining, state->domain);
+ vm_serialize_int(&stored, &buf, &remaining, state->vtsN);
+ vm_serialize_int(&stored, &buf, &remaining, state->pgcN);
+ vm_serialize_int(&stored, &buf, &remaining, state->pgN);
+ vm_serialize_int(&stored, &buf, &remaining, state->cellN);
+ vm_serialize_int(&stored, &buf, &remaining, state->cell_restart);
+ vm_serialize_int(&stored, &buf, &remaining, state->blockN);
+ vm_serialize_int(&stored, &buf, &remaining, state->rsm_vtsN);
+ vm_serialize_int(&stored, &buf, &remaining, state->rsm_blockN);
+ vm_serialize_int(&stored, &buf, &remaining, state->rsm_pgcN);
+ vm_serialize_int(&stored, &buf, &remaining, state->rsm_cellN);
+
+ // Resume SPRM
+ for(idx = 0; idx < 5 && stored > 0; idx++)
+ {
+ stored = snprintf(buf, remaining, "0x%hx,", state->rsm_regs[idx]);
+ if(stored > 0)
+ {
+ remaining -= (size_t)stored;
+ buf += stored;
+ }
+ }
+
+ if(stored > 0 && remaining >= 4)
+ {
+ // Done. Terminating the string.
+ strcpy(buf, "end");
+ }
+ else
+ {
+ // Error
+ free(str_state);
+ str_state = 0;
+ }
+ }
+ return str_state;
+}
+
+int vm_deserialize_dvd_state(const char* serialized, dvd_state_t *state)
+{
+ char *buf = serialized;
+ int consumed;
+ int version;
+ int tmp;
+ int idx;
+ int ret = 0; /* assume an error */
+ dvd_state_t new_state;
+
+ sscanf( buf, "navstat,%d,%n", &version, &consumed);
+ if(version == 1)
+ {
+ buf += consumed;
+
+ // SPRM
+ for(idx = 0; idx < 24 && consumed > 0; idx++)
+ {
+ sscanf(buf, "0x%hx,%n", &new_state.registers.SPRM[idx], &consumed);
+ buf += consumed;
+ }
+
+ // GPRM
+ for(idx = 0; idx < 16 && consumed > 0; idx++)
+ {
+ sscanf(buf, "[0x%hx;%d;0x%x;0x%x],%n", &new_state.registers.GPRM[idx],
+ &new_state.registers.GPRM_mode[idx],
+ &new_state.registers.GPRM_time[idx].tv_sec,
+ &new_state.registers.GPRM_time[idx].tv_usec,
+ &consumed);
+ buf += consumed;
+ }
+
+ vm_deserialize_int(&consumed, &buf, &new_state.domain);
+ vm_deserialize_int(&consumed, &buf, &new_state.vtsN);
+ vm_deserialize_int(&consumed, &buf, &new_state.pgcN);
+ vm_deserialize_int(&consumed, &buf, &new_state.pgN);
+ vm_deserialize_int(&consumed, &buf, &new_state.cellN);
+ vm_deserialize_int(&consumed, &buf, &tmp);
+ new_state.cell_restart = tmp;
+ vm_deserialize_int(&consumed, &buf, &new_state.blockN);
+ vm_deserialize_int(&consumed, &buf, &new_state.rsm_vtsN);
+ vm_deserialize_int(&consumed, &buf, &new_state.rsm_blockN);
+ vm_deserialize_int(&consumed, &buf, &new_state.rsm_pgcN);
+ vm_deserialize_int(&consumed, &buf, &new_state.rsm_cellN);
+
+ // Resume SPRM
+ for(idx = 0; idx < 5 && consumed > 0; idx++)
+ {
+ sscanf(buf, "0x%hx,%n", &new_state.rsm_regs[idx], &consumed);
+ buf += consumed;
+ }
+
+ if(strcmp(buf,"end") == 0)
+ {
+ /* Success! */
+ *state = new_state;
+ state->pgc = NULL;
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
Index: src/vm/vm_serialize.h
===================================================================
--- src/vm/vm_serialize.h (revision 0)
+++ src/vm/vm_serialize.h (revision 0)
@@ -0,0 +1,9 @@
+#ifndef LIBDVDNAV_VM_SERIALIZE_H
+#define LIBDVDNAV_VM_SERIALIZE_H
+
+#include "vm.h"
+
+char *vm_serialize_dvd_state(const dvd_state_t *state);
+int vm_deserialize_dvd_state(const char* serialized, dvd_state_t *state);
+
+#endif /* LIBDVDNAV_VM_SERIALIZE_H */
Index: src/vm/vm.c
===================================================================
--- src/vm/vm.c (revision 1274)
+++ src/vm/vm.c (working copy)
@@ -45,6 +45,7 @@
#include "decoder.h"
#include "remap.h"
#include "vm.h"
+#include "vm_serialize.h"
#include "dvdnav_internal.h"
#ifdef _MSC_VER
@@ -1981,6 +1982,62 @@
ifoClose(ifo);
}
+char *vm_get_state_str(vm_t *vm) {
+ char *str_state = NULL;
+
+ if(vm)
+ str_state = vm_serialize_dvd_state(&vm->state);
+
+ return str_state;
+}
+
+int vm_set_state(vm_t *vm, const char *state_str) {
+ /* restore state from save_state as taken from ogle */
+
+ dvd_state_t save_state;
+
+ if(state_str == NULL) {
+ return 0;
+ }
+
+ if(!vm_deserialize_dvd_state(state_str, &save_state)) {
+#ifdef TRACE
+ fprintf( MSG_OUT, "state_str invalid\n");
+#endif
+ return 0;
+ }
+
+ /* open the needed vts */
+ if( !ifoOpenNewVTSI(vm, vm->dvd, save_state.vtsN) ) return 0;
+ // sets state.vtsN
+
+ vm->state = save_state;
+ /* set state.domain before calling */
+ //calls get_pgcit()
+ // needs state.domain and sprm[0] set
+ // sets pgcit depending on state.domain
+ //writes: state.pgc
+ // state.pgN
+ // state.TT_PGCN_REG
+
+ if( !set_PGCN(vm, save_state.pgcN) ) return 0;
+ save_state.pgc = vm->state.pgc;
+
+ /* set the rest of state after the call */
+ vm->state = save_state;
+
+ /* if we are not in standard playback, we must get all data */
+ /* otherwise we risk loosing stillframes, and overlays */
+ if(vm->state.domain != VTS_DOMAIN)
+ vm->state.blockN = 0;
+
+ /* force a flush of data here */
+ /* we don't need a hop seek here as it's a complete state*/
+ vm->hop_channel++;
+
+ return 1;
+}
+
/* Debug functions */
#ifdef TRACE
Index: src/vm/vm.h
===================================================================
--- src/vm/vm.h (revision 1274)
+++ src/vm/vm.h (working copy)
@@ -169,6 +169,8 @@
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);
+char *vm_get_state_str(vm_t *vm);
+int vm_set_state(vm_t *vm, const char *state_str);
/* Uncomment for VM command tracing */
/* #define TRACE */
Index: src/dvdnav/dvdnav.h
===================================================================
--- src/dvdnav/dvdnav.h (revision 1274)
+++ src/dvdnav/dvdnav.h (working copy)
@@ -695,7 +695,22 @@
*/
int8_t dvdnav_is_domain_vts(dvdnav_t *self);
+/*********************************************************************
+ * Save/restore playback state *
+ *********************************************************************/
+/*
+ * Get a text string representing a snapshot of the current internal state
+ * The calling application is responsible for freeing the returned buffer.
+ */
+char* dvdnav_get_state(dvdnav_t *self);
+
+/*
+ * Set the current internal state to an earlier snapshot
+ */
+dvdnav_status_t dvdnav_set_state(dvdnav_t *self, const char *state_str);
+
+
#ifdef __cplusplus
}
#endif
Index: src/dvdnav.c
===================================================================
--- src/dvdnav.c (revision 1274)
+++ src/dvdnav.c (working copy)
@@ -1183,3 +1183,61 @@
return ops.ops_struct;
}
+
+char* dvdnav_get_state(dvdnav_t *this)
+{
+ char *state = NULL;
+
+ if(this && this->vm) {
+ pthread_mutex_lock(&this->vm_lock);
+
+ if( !(state = vm_get_state_str(this->vm)) )
+ printerr("Failed to get vm state.");
+
+ pthread_mutex_unlock(&this->vm_lock);
+ }
+
+ return state;
+}
+
+dvdnav_status_t dvdnav_set_state(dvdnav_t *this, const char *state_str)
+{
+ if(!this || !this->vm)
+ {
+ printerr("Passed a NULL pointer.");
+ return DVDNAV_STATUS_ERR;
+ }
+
+ if(!this->started) {
+ printerr("Virtual DVD machine not started.");
+ return DVDNAV_STATUS_ERR;
+ }
+
+ pthread_mutex_lock(&this->vm_lock);
+
+ /* reset the dvdnav state */
+ memset(&this->pci,0,sizeof(this->pci));
+ memset(&this->dsi,0,sizeof(this->dsi));
+ this->last_cmd_nav_lbn = SRI_END_OF_CELL;
+
+ /* Set initial values of flags */
+ this->position_current.still = 0;
+ this->skip_still = 0;
+ this->sync_wait = 0;
+ this->sync_wait_skip = 0;
+ this->spu_clut_changed = 0;
+
+
+ /* set the state. this will also start the vm on that state */
+ /* means the next read block should be comming from that new */
+ /* state */
+ if( !vm_set_state(this->vm, state_str) )
+ {
+ printerr("Failed to set vm state.");
+ pthread_mutex_unlock(&this->vm_lock);
+ return DVDNAV_STATUS_ERR;
+ }
+
+ pthread_mutex_unlock(&this->vm_lock);
+ return DVDNAV_STATUS_OK;
+}
_______________________________________________
DVDnav-discuss mailing list
[email protected]
https://lists.mplayerhq.hu/mailman/listinfo/dvdnav-discuss