# HG changeset patch
# User Tobias Rautenkranz <tobiasrautenkranz@web.de>
# Date 1180459700 -3600
# Node ID d5d52b9635561d9f7358fdabc1741121adfdf63d
# Parent  f24d0c57f42d3553cb94b66c491c744827612640
[PATCH] input_dvd: DVD slowdown
I'd like the DVD drive to spin slower to reduce noise (similar to input_cdda).
One can reduce the speed by the "set streaming" command
(e.g. http://noto.de/speed/speedcontrol.c)

When the speed is sufficiently low (i.e. the DVD drive is quiet),  4x fast
forward no longer runs smooth. I tried with adding some code in
dvd_plugin_read_block which adjusts the DVD speed (bitrate) in dependence on
this->stream->xine->clock->speed - which would work.

(Lightly modified to close the fd and avoid some segfaults. [DS])

diff --git a/src/input/input_dvd.c b/src/input/input_dvd.c
--- a/src/input/input_dvd.c
+++ b/src/input/input_dvd.c
@@ -205,6 +205,10 @@ typedef struct {
   int               mem_stack_max;
   unsigned char   **mem;
   int               freeing;
+
+#if defined (__linux__)
+  int               speed;        /* Current DVD speed */
+#endif
 } dvd_input_plugin_t;
 
 typedef struct {
@@ -231,6 +235,75 @@ typedef struct {
 
 static void dvd_handle_events(dvd_input_plugin_t *this);
 static void xine_dvd_send_button_update(dvd_input_plugin_t *this, int mode);
+
+#if defined (__linux__)
+static void set_dvd_speed(dvd_input_plugin_t *this, int speed) {
+  int fd;
+  unsigned long rw_size;
+  unsigned char buffer[28];
+
+  struct cdrom_generic_command cgc;
+  struct request_sense sense;
+
+  if (!this->current_dvd_device)
+    return;
+
+  fd = open(this->current_dvd_device, O_RDWR | O_NONBLOCK);
+  if (-1 == fd) {
+    xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
+        _("input_dvd: failed to open device %s for speed change: %s.\n"),
+        this->current_dvd_device, strerror(errno));
+    return;
+  }
+
+  memset(&cgc, 0, sizeof(cgc));
+  memset(&sense, 0, sizeof(sense));
+  memset(&buffer, 0, sizeof(buffer));
+
+  cgc.cmd[0] = GPCMD_SET_STREAMING; 
+  cgc.cmd[10] = sizeof(buffer); 
+
+  cgc.sense = &sense;
+  cgc.buffer = buffer;
+  cgc.buflen = sizeof(buffer);
+  cgc.data_direction = CGC_DATA_WRITE;
+
+  if (0 == speed)
+    buffer[0] = 4; /* set Restore Drive Defaults */  
+  
+  /* last sector: 0xffffffff */
+  buffer[8] = buffer[9] = buffer[10] = buffer[11] = 0xff;
+
+  /* Max. data rate in kByte/s */
+  unsigned long rate = 11080/8;
+  rw_size = rate * (unsigned long long)speed / XINE_FINE_SPEED_NORMAL; 
+
+  /* read/write size */  
+  buffer[12] = buffer[20] = (rw_size >> 24) & 0xff;
+  buffer[13] = buffer[21] = (rw_size >> 16) & 0xff;
+  buffer[14] = buffer[22] = (rw_size >>  8) & 0xff;
+  buffer[15] = buffer[23] = rw_size & 0xff;
+
+  /* read/write time 1 sec. */
+  buffer[18] = buffer[26] = 1000 >> 8;
+  buffer[19] = buffer[27] = 1000 & 0xff;
+
+  if (ioctl(fd, CDROM_SEND_PACKET, &cgc))       
+  {
+    xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
+        _("input_dvd: changing speed failed: %s\n."), strerror(errno));
+  }
+  else
+  {
+    this->speed = speed;
+    if (4 == buffer[0])
+      xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, _("input_dvd: disabled speed limit.\n"));
+    else
+      xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, _("input_dvd: changed speed to %d,\t rate %lu kbit/s.\n"), speed, rw_size*8);
+  }
+  close(fd);
+}
+#endif
 
 /* Callback on device name change */
 static void device_change_cb(void *data, xine_cfg_entry_t *cfg) {
@@ -325,6 +398,18 @@ static void play_single_chapter_cb(void 
 
   class->play_single_chapter = entry->num_value;
 }
+
+#if defined (__linux__)
+static void dvd_speed_cb(void *this_gen, xine_cfg_entry_t *entry) {
+ dvd_input_class_t *class = (dvd_input_class_t*)this_gen;
+
+  if(!class)
+   return;
+
+  if(!entry->num_value)
+    set_dvd_speed(class->ip, 0); 
+}
+#endif
 
 static void send_mouse_enter_leave_event(dvd_input_plugin_t *this, int direction) {
 
@@ -456,6 +541,9 @@ static void dvd_plugin_dispose (input_pl
   ((dvd_input_class_t *)this_gen->input_class)->ip = NULL;
   if (this->dvdnav)
     dvdnav_close(this->dvdnav);
+#if defined (__linux__)
+  set_dvd_speed(this, 0);
+#endif 
   
   pthread_mutex_lock(&this->buf_mutex);
   if (this->mem_stack) {
@@ -595,6 +683,21 @@ static buf_element_t *dvd_plugin_read_bl
   /* Read buffer */
   buf = fifo->buffer_pool_alloc (fifo);
   block = buf->mem;
+
+#if defined (__linux__)
+  if (this->stream &&
+      this->stream->xine->config->lookup_entry(this->stream->xine->config,
+      "media.dvd.drive_slowdown")->num_value) {
+    int speed = this->stream->xine->clock->speed;
+    if (speed < 0)
+      speed = 0;
+    else if (speed < XINE_FINE_SPEED_NORMAL)
+      speed = XINE_FINE_SPEED_NORMAL;
+
+    if (this->speed != speed && speed != 0)
+      set_dvd_speed(this, speed);
+  }
+#endif
 
   while(!finished) {
     dvd_handle_events(this);
@@ -1422,6 +1525,9 @@ static int dvd_parse_try_open(dvd_input_
     } else {
       /* Changing DVD device */
       dvdnav_close(this->dvdnav);
+#if defined (__linux__)
+      set_dvd_speed(this, 0);
+#endif
       this->dvdnav = NULL;
       this->opened = 0; 
     }
@@ -1524,6 +1630,12 @@ static int dvd_plugin_open (input_plugin
   if (xine_config_lookup_entry(this->stream->xine, "media.dvd.readahead",
 			       &cfg_entry))
     read_ahead_cb(class, &cfg_entry);
+
+#if defined (__linux__)
+  if (xine_config_lookup_entry(this->stream->xine, "media.dvd.drive_slowdown",
+				&cfg_entry))
+    dvd_speed_cb(class, &cfg_entry);
+#endif
   
   /* Set seek mode */
   if (xine_config_lookup_entry(this->stream->xine, "media.dvd.seek_behaviour",
@@ -1552,6 +1664,9 @@ static int dvd_plugin_open (input_plugin
       xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
 	      "input_dvd: Title %i is out of range (1 to %i).\n", tt, titles);
       dvdnav_close(this->dvdnav);
+#if defined (__linux__)
+      set_dvd_speed(this, 0);
+#endif
       this->dvdnav = NULL;
       free (locator_orig);
       return 0;
@@ -1566,6 +1681,9 @@ static int dvd_plugin_open (input_plugin
 	xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
 		"input_dvd: Part %i is out of range (1 to %i).\n", pr, parts);
 	dvdnav_close(this->dvdnav);
+#if defined (__linux__)
+      set_dvd_speed(this, 0);
+#endif
 	this->dvdnav = NULL;
 	free (locator_orig);
 	return 0;
@@ -1675,6 +1793,10 @@ static input_plugin_t *dvd_class_get_ins
   this->dvd_name               = NULL;
   this->mrl                    = strdup(data);
 
+#if defined (__linux__)
+  this->speed = 0;
+#endif
+
   pthread_mutex_init(&this->buf_mutex, NULL);
   this->freeing                = 0;
   
@@ -1729,6 +1851,9 @@ static void dvd_class_dispose(input_clas
   config->unregister_callback(config, "media.dvd.seek_behaviour");
   config->unregister_callback(config, "media.dvd.play_single_chapter");
 
+#if defined (__linux__)
+  set_dvd_speed((dvd_input_plugin_t*) this, 0);
+#endif
   free(this->eject_device);
   free(this);
 }
@@ -1882,6 +2007,18 @@ static void *init_class (xine_t *xine, v
 			  "play just the specified title/chapter and then stop"),
 			20, play_single_chapter_cb, this);
 
+#if defined (__linux__)
+    config->register_bool(config, "media.dvd.drive_slowdown", 1,
+               _("slow down DVD drive"),
+               _("Since some DVD drives make some really "
+             "loud noises because of the fast disc rotation, "
+             "xine will try to slow them down. With standard "
+             "DVD playback, the high data rates that "
+             "require the fast rotation are not needed.\n\n"
+             "May also improve playback of scratched DVDs."),
+               20, dvd_speed_cb, this);
+#endif
+
 #ifdef __sun
   check_solaris_vold_device(this);
 #endif
