add support for drive speed to vdr-dvd

Message ID 200711111000.34518.sebastian_ml@gmx.net
State New
Headers

Commit Message

Sebastian Nov. 11, 2007, 9 a.m. UTC
  Hello all,

here's a proposal for speed control for vdr-dvd. It uses SG_IO and 
GPCMD_SET_STREAMING so there's no need for VDR to run as root. The user 
needs rw permissions on the drive, though. When the plug-in is 
terminated the drive will be set to its initial speed (vacuum cleaner 
mode).
The code is heavily based on mplayer (thank you). I tested it with 
vdr-1.4.6 and a vdr-dvd snapshot on a NEC ND-3500 and it works just 
fine. The user can set the speed between 1x and 4x - I figure that's 
enough - in the OSD plug-in setup. If set to 0 the patch won't kick in. 
In case the users sets it to zero _while_ he/she is watching a dvd the 
speed will still be reset because of a DvdSetSpeedActive flag.

Kernel and headers have to support the commands for the patch to work 
but the plug-in should compile and work even on older kernels.

All feedback is very welcome.

Regards
Sebastian
  

Patch

diff -Nur dvd.old/i18n.c dvd/i18n.c
--- dvd.old/i18n.c	2007-09-16 18:36:41.000000000 +0200
+++ dvd/i18n.c	2007-11-10 14:43:11.000000000 +0100
@@ -280,6 +280,32 @@ 
 #endif
     },
     {
+     "Setup.DVD$DVD-ROM Speed",
+        "DVD-ROM-Geschwindigkeit",                          // Deutsch
+        "DVD-ROM Speed",                                    // Slovenski
+        "DVD-ROM Speed",                                    // Italiano
+        "DVD-ROM Speed",                                    // Nederlands
+        "DVD-ROM Speed",                                    // Português
+        "DVD-ROM Speed",                                    // Français
+        "DVD-ROM Speed",                                    // Norsk
+        "DVD-ROM Speed",                                    // suomi
+        "DVD-ROM Speed",                                    // Polski
+        "DVD-ROM Speed",                                    // Español
+        "DVD-ROM Speed",                                    // ÅëëçíéêÜ (Greek)
+        "DVD-ROM Speed",                                    // Svenska
+        "DVD-ROM Speed",                                    // Romaneste
+        "DVD-ROM Speed",                                    // Magyar
+        "DVD-ROM Speed",                                    // Català
+        "DVD-ROM Speed",                                    // ÀãááÚØÙ (Russian)
+        "DVD-ROM Speed",                                    // Hrvatski (Croatian)
+        "DVD-ROM Speed",                                    // Eesti
+        "DVD-ROM Speed",                                    // Dansk
+        "DVD-ROM Speed",                                    // Czech
+#if VDRVERSNUM >= 10502
+        "DVD-ROM Speed"                                     // Türkçe
+#endif
+    },
+    {
     "Setup.DVD$Gain (analog)",
         "Verstärkung (analog)",                             // Deutsch
         "Ojaèanje (analogno)",                              // Slovenski
diff -Nur dvd.old/player-dvd.c dvd/player-dvd.c
--- dvd.old/player-dvd.c	2007-09-17 21:04:43.000000000 +0200
+++ dvd/player-dvd.c	2007-11-10 17:40:57.000000000 +0100
@@ -35,6 +35,11 @@ 
 #include "control-dvd.h"
 #include "dvd.h"
 
+/* Needed for DvdSetSpeed() */
+#include <linux/cdrom.h>
+#include <scsi/sg.h>
+#include <sys/ioctl.h>
+
 /**
  * this was "weak"'s solution of a forced
  * SPU only stream choice,
@@ -252,6 +257,7 @@ 
 bool cDvdPlayer::HasBitStreamOut = false;
 bool cDvdPlayer::HasSoftDeviceOut = false;
 bool cDvdPlayer::SoftDeviceOutActive = false;
+bool cDvdPlayer::DvdSetSpeedActive = false;
 
 const int cDvdPlayer::MaxAudioTracks    = 0x20;
 const int cDvdPlayer::AudioTrackMask    = 0x1F;
@@ -565,6 +571,93 @@ 
 #endif
 }
 
+/* This function was ripped off of mplayer without remorse ;-) */
+void cDvdPlayer::DvdSetSpeed(const char *device, int speed)
+{
+#if defined(SG_IO) && defined(GPCMD_SET_STREAMING)
+  int fd;
+  unsigned char buffer[28];
+  unsigned char cmd[16];
+  unsigned char sense[16];
+  struct sg_io_hdr sghdr;
+  struct stat st;
+
+  memset(&sghdr, 0, sizeof(sghdr));
+  memset(buffer, 0, sizeof(buffer));
+  memset(sense, 0, sizeof(sense));
+  memset(cmd, 0, sizeof(cmd));
+  memset(&st, 0, sizeof(st));
+
+  if (stat(device, &st) == -1) {
+    esyslog("ERROR: dvd-plugin: DVD device %s doesn't exist", device);
+    return;
+  }
+
+  if (!S_ISBLK(st.st_mode)) {
+    esyslog("ERROR: dvd-plugin: DVD device %s is not a block device", device);
+    return;
+  }
+
+  if ((fd = open(device, O_RDWR | O_NONBLOCK)) == -1) {
+    esyslog("ERROR: dvd-plugin: Failed to open DVD device %s O_RDWR | O_NONBLOCK", device);
+    return;
+  }
+
+  if (speed < 100 && speed > 0) { /* speed times 1350KB/s (DVD single speed) */
+    speed *= 1350;
+  }
+
+  switch (speed) {
+  case 0: /* don't touch speed setting */
+    close(fd);
+    return;
+  case -1: /* restore default value */
+    speed = 0;
+    buffer[0] = 4; /* restore default */
+    isyslog("dvd-plugin: Restoring initial DVD drive speed");
+    break;
+  default: /* limit to <speed> KB/s */
+    isyslog("dvd-plugin: Limiting speed to %d KB/s", speed);
+    break;
+  }
+
+  sghdr.interface_id = 'S';
+  sghdr.timeout = 5000;
+  sghdr.dxfer_direction = SG_DXFER_TO_DEV;
+  sghdr.mx_sb_len = sizeof(sense);
+  sghdr.dxfer_len = sizeof(buffer);
+  sghdr.cmd_len = sizeof(cmd);
+  sghdr.sbp = sense;
+  sghdr.dxferp = buffer;
+  sghdr.cmdp = cmd;
+
+  cmd[0] = GPCMD_SET_STREAMING;
+  cmd[10] = sizeof(buffer);
+
+  buffer[8] = 0xff;  /* first sector 0, last sector 0xffffffff */
+  buffer[9] = 0xff;
+  buffer[10] = 0xff;
+  buffer[11] = 0xff;
+
+  buffer[12] = buffer[20] = (speed >> 24) & 0xff; /* <speed> kilobyte */
+  buffer[13] = buffer[21] = (speed >> 16) & 0xff;
+  buffer[14] = buffer[22] = (speed >> 8)  & 0xff;
+  buffer[15] = buffer[23] = speed & 0xff;
+
+  buffer[18] = buffer[26] = 0x03; /* 1 second */
+  buffer[19] = buffer[27] = 0xe8;
+
+  if (ioctl(fd, SG_IO, &sghdr) < 0) {
+    esyslog("ERROR: dvd-plugin: DVD speed limiting failed");
+    close(fd);
+    return;
+  }
+  isyslog("dvd-plugin: DVD speed limiting successful");
+  DvdSetSpeedActive = true;
+  close(fd);
+#endif
+}
+
 void cDvdPlayer::Action(void) {
     memset(event_buf, 0, sizeof(uint8_t)*4096);
 
@@ -598,6 +691,11 @@ 
         fflush(NULL);
         return;
     }
+
+    /* Try to reduce drive speed if the user wants us to */
+    if (DVDSetup.Speed)
+    DvdSetSpeed(const_cast<char *>(cDVD::getDVD()->DeviceName()), DVDSetup.Speed);
+
     dvdnav_set_readahead_flag(nav, DVDSetup.ReadAHead);
     if (DVDSetup.PlayerRCE != 0)
         dvdnav_set_region_mask(nav, 1 << (DVDSetup.PlayerRCE - 1));
@@ -1203,6 +1301,12 @@ 
   dvdnav_close(nav);
   nav=NULL;
 
+  /* Try to restore drive speed if it was previously changed */
+  if (DvdSetSpeedActive) {
+  DvdSetSpeed(const_cast<char *>(cDVD::getDVD()->DeviceName()), -1);
+  DvdSetSpeedActive = false;
+  }
+
   DEBUGDVD("%s:%d: input thread ended (pid=%d)\n", __FILE__, __LINE__, getpid());
   fflush(NULL);
 }
diff -Nur dvd.old/player-dvd.h dvd/player-dvd.h
--- dvd.old/player-dvd.h	2007-09-17 21:04:43.000000000 +0200
+++ dvd/player-dvd.h	2007-11-10 14:50:09.000000000 +0100
@@ -168,6 +168,7 @@ 
     static bool HasBitStreamOut;
     static bool SoftDeviceOutActive; // currently used to switch for xine
     static bool HasSoftDeviceOut;    // currently used to switch for xine
+    static bool DvdSetSpeedActive;
 
     //dvd stuff
     int currButtonN;
@@ -229,6 +230,7 @@ 
     void DrawSPU();
     void HideSPU();
     void EmptySPU();
+    void DvdSetSpeed(const char*, int);
 
     void Pause(void);
     void Play(void);
diff -Nur dvd.old/setup-dvd.c dvd/setup-dvd.c
--- dvd.old/setup-dvd.c	2007-08-12 18:57:22.000000000 +0200
+++ dvd/setup-dvd.c	2007-11-10 14:52:06.000000000 +0100
@@ -35,6 +35,7 @@ 
     Gain          = 4;
 
     AC3dynrng     = 0;
+    Speed         = 0;
 }
 
 bool cDVDSetup::SetupParse(const char *Name, const char *Value)
@@ -47,6 +48,7 @@ 
     else if (!strcasecmp(Name, "ShowSubtitles")) ShowSubtitles = atoi(Value);
     else if (!strcasecmp(Name, "HideMainMenu"))  HideMainMenu  = atoi(Value);
     else if (!strcasecmp(Name, "ReadAHead"))     ReadAHead     = atoi(Value);
+    else if (!strcasecmp(Name, "Speed"))         Speed         = atoi(Value);
     else if (!strcasecmp(Name, "Gain"))          Gain          = atoi(Value);
     else
 	return false;
@@ -72,6 +74,7 @@ 
     Add(new cMenuEditBoolItem(tr("Setup.DVD$Display subtitles"),           &data.ShowSubtitles));
     Add(new cMenuEditBoolItem(tr("Setup.DVD$Hide Mainmenu Entry"),         &data.HideMainMenu));
     Add(new cMenuEditBoolItem(tr("Setup.DVD$ReadAHead"),                   &data.ReadAHead));
+    Add(new cMenuEditIntItem( tr("Setup.DVD$DVD-ROM Speed"),               &data.Speed, 0, 4));
     Add(new cMenuEditIntItem( tr("Setup.DVD$Gain (analog)"),               &data.Gain, 0, 10));
 }
 
@@ -85,6 +88,7 @@ 
     SetupStore("ShowSubtitles", DVDSetup.ShowSubtitles );
     SetupStore("HideMainMenu",  DVDSetup.HideMainMenu  );
     SetupStore("ReadAHead",     DVDSetup.ReadAHead  );
+    SetupStore("Speed",         DVDSetup.Speed  );
     SetupStore("Gain",          DVDSetup.Gain  );
 }
 
diff -Nur dvd.old/setup-dvd.h dvd/setup-dvd.h
--- dvd.old/setup-dvd.h	2005-01-05 17:32:21.000000000 +0100
+++ dvd/setup-dvd.h	2007-11-10 14:52:51.000000000 +0100
@@ -23,6 +23,7 @@ 
     int HideMainMenu;
     int ReadAHead;
     int Gain;
+    int Speed;
 
     // AC3 stuff
     int AC3dynrng;