changeset 23244:e5e8ac0bd7fc

support for PVR channel navigation (patch by Sven Gothel <sgothel at jausoft dot com>)
author ben
date Tue, 08 May 2007 12:20:46 +0000
parents 0ec346252484
children 40a82b61fd79
files command.c stream/pvr.h stream/stream_pvr.c
diffstat 3 files changed, 873 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/command.c	Tue May 08 10:29:47 2007 +0000
+++ b/command.c	Tue May 08 12:20:46 2007 +0000
@@ -32,6 +32,9 @@
 #ifdef USE_RADIO
 #include "stream/stream_radio.h"
 #endif
+#ifdef HAVE_PVR
+#include "stream/pvr.h"
+#endif
 #ifdef HAS_DVBIN_SUPPORT
 #include "stream/dvbin.h"
 #endif
@@ -1939,12 +1942,30 @@
 	    if (mpctx->file_format == DEMUXER_TYPE_TV)
 		tv_set_freq((tvi_handle_t *) (mpctx->demuxer->priv),
 			    cmd->args[0].v.f * 16.0);
+#ifdef HAVE_PVR
+            else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR)
+            {
+              pvr_set_freq (mpctx->stream, ROUND (cmd->args[0].v.f));
+              set_osd_msg (OSD_MSG_TV_CHANNEL, 1, osd_duration, "%s: %s",
+                           pvr_get_current_channelname (mpctx->stream),
+                           pvr_get_current_stationname (mpctx->stream));
+        }
+#endif /* HAVE_PVR */
 	    break;
 
 	case MP_CMD_TV_STEP_FREQ:
 	    if (mpctx->file_format == DEMUXER_TYPE_TV)
 		tv_step_freq((tvi_handle_t *) (mpctx->demuxer->priv),
 			    cmd->args[0].v.f * 16.0);
+#ifdef HAVE_PVR
+            else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR)
+            {
+              pvr_force_freq_step (mpctx->stream, ROUND (cmd->args[0].v.f));
+              set_osd_msg (OSD_MSG_TV_CHANNEL, 1, osd_duration, "%s: f %d",
+                           pvr_get_current_channelname (mpctx->stream),
+                           pvr_get_current_frequency (mpctx->stream));
+            }
+#endif /* HAVE_PVR */
 	    break;
 
 	case MP_CMD_TV_SET_NORM:
@@ -1971,6 +1992,16 @@
 			//vo_osd_changed(OSDTYPE_SUBTITLE);
 		    }
 		}
+#ifdef HAVE_PVR
+                else if (mpctx->stream &&
+                         mpctx->stream->type == STREAMTYPE_PVR)
+                {
+                  pvr_set_channel_step (mpctx->stream, cmd->args[0].v.i);
+                  set_osd_msg (OSD_MSG_TV_CHANNEL, 1, osd_duration, "%s: %s",
+                               pvr_get_current_channelname (mpctx->stream),
+                               pvr_get_current_stationname (mpctx->stream));
+                }
+#endif /* HAVE_PVR */
 	    }
 #ifdef HAS_DVBIN_SUPPORT
 	    if ((mpctx->stream->type == STREAMTYPE_DVB)
@@ -2004,6 +2035,15 @@
 		    //vo_osd_changed(OSDTYPE_SUBTITLE);
 		}
 	    }
+#ifdef HAVE_PVR
+            else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR)
+            {
+              pvr_set_channel (mpctx->stream, cmd->args[0].v.s);
+              set_osd_msg (OSD_MSG_TV_CHANNEL, 1, osd_duration, "%s: %s",
+                           pvr_get_current_channelname (mpctx->stream),
+                           pvr_get_current_stationname (mpctx->stream));
+            }
+#endif /* HAVE_PVR */
 	    break;
 
 #ifdef HAS_DVBIN_SUPPORT
@@ -2034,6 +2074,15 @@
 		    //vo_osd_changed(OSDTYPE_SUBTITLE);
 		}
 	    }
+#ifdef HAVE_PVR
+            else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR)
+            {
+              pvr_set_lastchannel (mpctx->stream);
+              set_osd_msg (OSD_MSG_TV_CHANNEL, 1, osd_duration, "%s: %s",
+                           pvr_get_current_channelname (mpctx->stream),
+                           pvr_get_current_stationname (mpctx->stream));
+            }
+#endif /* HAVE_PVR */
 	    break;
 
 	case MP_CMD_TV_STEP_NORM:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/stream/pvr.h	Tue May 08 12:20:46 2007 +0000
@@ -0,0 +1,64 @@
+#ifndef PVR_H
+#define PVR_H
+
+#include "tv.h"
+
+/**
+ * @brief Get the current station name.
+ *        The pointer is valid, till the stream is closed.
+ * @return The stream's station name
+ */
+const char *pvr_get_current_stationname (stream_t *stream);
+
+/**
+ * @brief Get the current channel name.
+ *        The pointer is valid, till the stream is closed.
+ * @return The stream's channel name 
+ */
+const char *pvr_get_current_channelname (stream_t *stream);
+
+/**
+ * @brief Get the current frequency.
+ * @return frequency
+ */
+int pvr_get_current_frequency (stream_t *stream);
+
+/**
+ * @brief Set the current station using the channel name.
+ *        This function will fail,
+ *        if the channel does not exist, or the station is not enabled
+ * @return 0 if the station is available, otherwise -1
+ */
+int pvr_set_channel (stream_t *stream, const char *channel);
+
+/**
+ * @brief Set the current station using to the last set channel
+ * @return 0 if the station is available, otherwise -1
+ */
+int pvr_set_lastchannel (stream_t *stream);
+
+/**
+ * @brief Set the current channel using the frequency.
+ *        This function will fail,
+ *        if the frequency does not exist, or the station is not enabled
+ * @return 0 if the station is available, otherwise -1
+ */
+int pvr_set_freq (stream_t *stream, int freq);
+
+/**
+ * @brief Set the current station while stepping.
+ *        This function will fail,
+ *        if the station does not exist, or the station is not enabled
+ * @return 0 if the station is available, otherwise -1
+ */
+int pvr_set_channel_step (stream_t *stream, int step);
+
+/**
+ * @brief Set the current frequency while stepping
+ *        This function will fail,
+ *        if the frequency is invalid, i.e. <0
+ * @return 0 if success, otherwise -1
+ */
+int pvr_force_freq_step (stream_t *stream, int step);
+
+#endif /* PVR_H */
--- a/stream/stream_pvr.c	Tue May 08 10:29:47 2007 +0000
+++ b/stream/stream_pvr.c	Tue May 08 12:20:46 2007 +0000
@@ -40,7 +40,10 @@
 #include "help_mp.h"
 
 #include "stream.h"
-#include "tv.h"
+#include "pvr.h"
+
+#include "frequencies.h"
+#include "libavutil/common.h"
 
 #define PVR_DEFAULT_DEVICE "/dev/video0"
 #define PVR_MAX_CONTROLS 10
@@ -68,6 +71,8 @@
 #define PVR_VIDEO_STREAM_TYPE_VCD                              "vcd"
 #define PVR_VIDEO_STREAM_TYPE_SVCD                             "svcd"
 
+#define PVR_STATION_NAME_SIZE 256
+
 /* command line arguments */
 int pvr_param_aspect_ratio = 0;
 int pvr_param_sample_rate = 0;
@@ -79,6 +84,21 @@
 int pvr_param_bitrate_peak = 0;
 char *pvr_param_stream_type = NULL;
 
+typedef struct station_elem_s {
+  char name[8];
+  int freq;
+  char station[PVR_STATION_NAME_SIZE];
+  int enabled;
+} station_elem_t;
+
+typedef struct stationlist_s {
+  char name[PVR_STATION_NAME_SIZE];
+  station_elem_t *list;
+  int total; /* total number */
+  int used; /* used number */
+  int enabled; /* enabled number */
+} stationlist_t;
+
 struct pvr_t {
   int dev_fd;
   char *video_dev;
@@ -93,7 +113,12 @@
   int saturation;
   int width;
   int height;
-  char *freq;
+  int freq;
+  int chan_idx;
+  int chan_idx_last;
+  stationlist_t stationlist;
+  /* dups the tv_param_channel, or the url's channel param */
+  char *param_channel;
 
   /* encoder params */
   int aspect;
@@ -112,7 +137,7 @@
 {
   struct pvr_t *pvr = NULL;
 
-  pvr = malloc (sizeof (struct pvr_t)); 
+  pvr = calloc (1, sizeof (struct pvr_t)); 
   pvr->dev_fd = -1;
   pvr->video_dev = strdup (PVR_DEFAULT_DEVICE);
 
@@ -126,7 +151,9 @@
   pvr->saturation = 0;
   pvr->width = -1;
   pvr->height = -1;
-  pvr->freq = NULL;
+  pvr->freq = -1;
+  pvr->chan_idx = -1;
+  pvr->chan_idx_last = -1;
 
   /* set default encoding settings
    * may be overlapped by user parameters
@@ -158,11 +185,602 @@
   
   if (pvr->video_dev)
     free (pvr->video_dev);
-  if (pvr->freq)
-    free (pvr->freq);
+
+  if (pvr->stationlist.list)
+    free (pvr->stationlist.list);
+
+  if (pvr->param_channel)
+    free (pvr->param_channel);
+  
   free (pvr);
 }
 
+/**
+ * @brief Copy Constructor for stationlist
+ *
+ * @see parse_setup_stationlist
+ */
+static int
+copycreate_stationlist (stationlist_t *stationlist, int num)
+{
+  int i;
+
+  if (chantab < 0 || !stationlist)
+    return -1;
+
+  num = FFMAX (num, chanlists[chantab].count);
+
+  if (stationlist->list)
+  {
+    free (stationlist->list);
+    stationlist->list = NULL;
+  }
+  
+  stationlist->total = 0;
+  stationlist->enabled = 0;
+  stationlist->used = 0;
+  stationlist->list = calloc (num, sizeof (station_elem_t));
+
+  if (!stationlist->list)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR,
+            "%s No memory allocated for station list, giving up\n",
+            LOG_LEVEL_V4L2);
+    return -1;
+  }
+  
+  /* transport the channel list data to our extented struct */
+  stationlist->total = num;
+  strlcpy (stationlist->name, chanlists[chantab].name, PVR_STATION_NAME_SIZE);
+
+  for (i = 0; i < chanlists[chantab].count; i++)
+  {
+    stationlist->list[i].station[0]= '\0'; /* no station name yet */
+    strlcpy (stationlist->list[i].name,
+             chanlists[chantab].list[i].name, PVR_STATION_NAME_SIZE);
+    stationlist->list[i].freq = chanlists[chantab].list[i].freq;
+    stationlist->list[i].enabled = 1; /* default enabled */
+    stationlist->enabled++;
+    stationlist->used++;
+  }
+
+  return 0;
+}
+
+static int
+print_all_stations (struct pvr_t *pvr)
+{
+  int i;
+
+  if (!pvr || !pvr->stationlist.list) 
+    return -1;
+
+  for (i = 0; i < pvr->stationlist.total; i++)
+  {
+    mp_msg (MSGT_OPEN, MSGL_V,
+            "%s %3d: [%c] channel: %8s - freq: %8d - station: %s\n",
+            LOG_LEVEL_V4L2, i, (pvr->stationlist.list[i].enabled) ? 'X' : ' ',
+            pvr->stationlist.list[i].name, pvr->stationlist.list[i].freq,
+            pvr->stationlist.list[i].station);
+  }
+
+  return 0;
+}
+
+/**
+ * Disables all stations
+ *
+ * @see parse_setup_stationlist
+ */
+static void
+disable_all_stations (struct pvr_t *pvr)
+{
+  int i;
+
+  for (i = 0; i < pvr->stationlist.total; i++)
+    pvr->stationlist.list[i].enabled = 0;
+  pvr->stationlist.enabled = 0;
+}
+
+/**
+ * Update or add a station
+ *
+ * @see parse_setup_stationlist
+ */
+static int
+set_station (struct pvr_t *pvr, const char *station,
+             const char *channel, int freq)
+{
+  int i;
+
+  if (!pvr || !pvr->stationlist.list) 
+    return -1;
+
+  if (0 >= pvr->stationlist.total || (!channel && !freq))
+    return -1;
+
+  /* select channel */
+  for (i = 0; i < pvr->stationlist.used; i++)
+  {
+    if (channel && !strcasecmp (pvr->stationlist.list[i].name, channel))
+      break; /* found existing channel entry */
+
+    if (freq > 0 && pvr->stationlist.list[i].freq == freq)
+      break; /* found existing frequency entry */
+  }
+
+  if (i < pvr->stationlist.used)
+  {
+    /**
+     * found an existing entry,
+     * which is about to change with the user data.
+     * it is also enabled ..
+     */
+    if (!pvr->stationlist.list[i].enabled)
+    {
+      pvr->stationlist.list[i].enabled = 1;
+      pvr->stationlist.enabled++;
+    }
+    
+    if (station)
+      strlcpy (pvr->stationlist.list[i].station,
+               station, PVR_STATION_NAME_SIZE);
+    else if (channel)
+      strlcpy (pvr->stationlist.list[i].station,
+               channel, PVR_STATION_NAME_SIZE);
+    else
+      snprintf (pvr->stationlist.list[i].station,
+                PVR_STATION_NAME_SIZE, "F %d", freq);
+
+    mp_msg (MSGT_OPEN, MSGL_DBG2,
+            "%s Set user station channel: %8s - freq: %8d - station: %s\n",
+            LOG_LEVEL_V4L2, pvr->stationlist.list[i].name,
+            pvr->stationlist.list[i].freq,
+            pvr->stationlist.list[i].station);
+    return 0;
+  }
+
+  /* from here on, we have to create a new entry, frequency is mandatory */
+  if (freq < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR,
+            "%s Cannot add new station/channel without frequency\n",
+            LOG_LEVEL_V4L2);
+    return -1;
+  }
+
+  if (pvr->stationlist.total < i)
+  {
+    /**
+     * we have to extend the stationlist about 
+     * an arbitrary size, even though this path is not performance critical
+     */
+    pvr->stationlist.total += 10;
+    pvr->stationlist.list =
+      realloc (pvr->stationlist.list,
+               pvr->stationlist.total * sizeof (station_elem_t));
+
+    if (!pvr->stationlist.list)
+    {
+      mp_msg (MSGT_OPEN, MSGL_ERR,
+              "%s No memory allocated for station list, giving up\n",
+              LOG_LEVEL_V4L2);
+      return -1;
+    }
+
+    /* clear the new space ..*/
+    memset (&(pvr->stationlist.list[pvr->stationlist.used]), 0,
+            (pvr->stationlist.total - pvr->stationlist.used)
+            * sizeof (station_elem_t));
+  }
+
+  /* here we go, our actual new entry */
+  pvr->stationlist.used++;
+  pvr->stationlist.list[i].enabled = 1;
+  pvr->stationlist.enabled++;
+
+  if (station)
+    strlcpy (pvr->stationlist.list[i].station,
+             station, PVR_STATION_NAME_SIZE);
+  if (channel)
+    strlcpy (pvr->stationlist.list[i].name, channel, PVR_STATION_NAME_SIZE);
+  else
+    snprintf (pvr->stationlist.list[i].name,
+              PVR_STATION_NAME_SIZE, "F %d", freq);
+
+  pvr->stationlist.list[i].freq = freq;
+
+  mp_msg (MSGT_OPEN, MSGL_DBG2,
+          "%s Add user station channel: %8s - freq: %8d - station: %s\n",
+          LOG_LEVEL_V4L2, pvr->stationlist.list[i].name,
+          pvr->stationlist.list[i].freq,
+          pvr->stationlist.list[i].station);
+
+  return 0;
+}
+
+/**
+ * Here we set our stationlist, as follow
+ *  - choose the frequency channel table, e.g. ntsc-cable
+ *  - create our stationlist, same element size as the channellist
+ *  - copy the channellist content to our stationlist
+ *  - IF the user provides his channel-mapping, THEN:
+ *    - disable all stations
+ *    - update and/or create entries in the stationlist and enable them
+ */
+static int
+parse_setup_stationlist (struct pvr_t *pvr)
+{
+  int i;
+
+  if (!pvr) 
+    return -1;
+
+  /* Create our station/channel list */
+  if (tv_param_chanlist)
+  {
+    /* select channel list */
+    for (i = 0; chanlists[i].name != NULL; i++)
+    {
+      if (!strcasecmp (chanlists[i].name, tv_param_chanlist))
+      {
+        chantab = i;
+        break;
+      }
+    }
+    if (!chanlists[i].name)
+    {
+      mp_msg (MSGT_OPEN, MSGL_ERR,
+              "%s unable to find channel list %s, using default %s\n",
+              LOG_LEVEL_V4L2, tv_param_chanlist, chanlists[chantab].name);
+    }
+    else
+    {
+      mp_msg (MSGT_OPEN, MSGL_INFO,
+              "%s select channel list %s, entries %d\n", LOG_LEVEL_V4L2, 
+              chanlists[chantab].name, chanlists[chantab].count);
+    }
+  }
+  
+  if (0 > chantab)
+  {
+    mp_msg (MSGT_OPEN, MSGL_FATAL,
+            "%s No channel list selected, giving up\n", LOG_LEVEL_V4L2);
+    return -1;
+  }
+
+  if (copycreate_stationlist (&(pvr->stationlist), -1) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_FATAL,
+            "%s No memory allocated for station list, giving up\n",
+            LOG_LEVEL_V4L2);
+    return -1;
+  }
+
+  /* Handle user channel mappings */
+  if (tv_param_channels) 
+  {
+    char channel[PVR_STATION_NAME_SIZE];
+    char station[PVR_STATION_NAME_SIZE];
+    char **channels = tv_param_channels;
+
+    disable_all_stations (pvr);
+
+    while (*channels) 
+    {
+      char *tmp = *(channels++);
+      char *sep = strchr (tmp, '-');
+      int freq=-1;
+
+      if (!sep)
+        continue; /* Wrong syntax, but mplayer should not crash */
+
+      strlcpy (station, sep + 1, PVR_STATION_NAME_SIZE);
+
+      sep[0] = '\0';
+      strlcpy (channel, tmp, PVR_STATION_NAME_SIZE);
+
+      while ((sep = strchr (station, '_')))
+        sep[0] = ' ';
+
+      /* if channel number is a number and larger than 1000 treat it as
+       * frequency tmp still contain pointer to null-terminated string with
+       * channel number here
+       */
+      if ((freq = atoi (channel)) <= 1000)
+        freq = -1; 
+
+      if (set_station (pvr, station, (freq <= 0) ? channel : NULL, freq) < 0)
+      {
+        mp_msg (MSGT_OPEN, MSGL_ERR,
+                "%s Unable to set user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+                channel, freq, station);
+      }
+    }
+  }
+  
+  return print_all_stations (pvr);
+}
+
+static int
+get_v4l2_freq (struct pvr_t *pvr)
+{
+  int freq;
+  struct v4l2_frequency vf;
+  struct v4l2_tuner vt;
+
+  if (!pvr)
+    return -1;
+  
+  if (pvr->dev_fd < 0)
+    return -1;
+
+  memset (&vt, 0, sizeof (vt));
+  memset (&vf, 0, sizeof (vf));
+
+  if (ioctl (pvr->dev_fd, VIDIOC_G_TUNER, &vt) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't set tuner (%s).\n",
+            LOG_LEVEL_V4L2, strerror (errno));
+    return -1;
+  }
+
+  if (ioctl (pvr->dev_fd, VIDIOC_G_FREQUENCY, &vf) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't get frequency %d.\n",
+            LOG_LEVEL_V4L2, errno);
+    return -1;
+  }
+  freq = vf.frequency;
+  if (!(vt.capability & V4L2_TUNER_CAP_LOW))
+    freq *= 1000;
+  freq /= 16;
+
+  return freq;
+}
+
+static int
+set_v4l2_freq (struct pvr_t *pvr)
+{
+  struct v4l2_frequency vf;
+  struct v4l2_tuner vt;
+  
+  if (!pvr)
+    return -1;
+  
+  if (0 >= pvr->freq)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR,
+            "%s Frequency invalid %d !!!\n", LOG_LEVEL_V4L2, pvr->freq);
+    return -1;
+  }
+
+  /* don't set the frequency, if it's already set.
+   * setting it here would interrupt the stream.
+   */
+  if (get_v4l2_freq (pvr) == pvr->freq)
+  {
+    mp_msg (MSGT_OPEN, MSGL_STATUS,
+            "%s Frequency %d already set.\n", LOG_LEVEL_V4L2, pvr->freq);
+    return 0;
+  }
+
+  if (pvr->dev_fd < 0)
+    return -1;
+
+  memset (&vf, 0, sizeof (vf));
+  memset (&vt, 0, sizeof (vt));
+
+  if (ioctl (pvr->dev_fd, VIDIOC_G_TUNER, &vt) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't get tuner (%s).\n",
+            LOG_LEVEL_V4L2, strerror (errno));
+    return -1;
+  }
+
+  vf.type = vt.type;
+  vf.frequency = pvr->freq * 16;
+
+  if (!(vt.capability & V4L2_TUNER_CAP_LOW))
+    vf.frequency /= 1000;
+
+  if (ioctl (pvr->dev_fd, VIDIOC_S_FREQUENCY, &vf) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't set frequency (%s).\n",
+            LOG_LEVEL_V4L2, strerror (errno));
+    return -1;
+  }
+
+  memset (&vt, 0, sizeof(vt));
+  if (ioctl (pvr->dev_fd, VIDIOC_G_TUNER, &vt) < 0)
+  {
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't set tuner (%s).\n",
+            LOG_LEVEL_V4L2, strerror (errno));
+    return -1;
+  }
+
+  /* just a notification */
+  if (!vt.signal)
+    mp_msg (MSGT_OPEN, MSGL_ERR, "%s NO SIGNAL at frequency %d (%d)\n",
+            LOG_LEVEL_V4L2, pvr->freq, vf.frequency);
+  else
+    mp_msg (MSGT_OPEN, MSGL_STATUS, "%s Got signal at frequency %d (%d)\n",
+            LOG_LEVEL_V4L2, pvr->freq, vf.frequency);
+
+  return 0;
+}
+
+static int
+set_station_by_step (struct pvr_t *pvr, int step, int v4lAction) 
+{
+  if (!pvr || !pvr->stationlist.list) 
+    return -1;
+
+  if (pvr->stationlist.enabled >= abs (step))
+  {
+    int gotcha = 0;
+    int chidx = pvr->chan_idx + step;
+
+    while (!gotcha)
+    {
+      chidx = (chidx + pvr->stationlist.used) % pvr->stationlist.used;
+
+      mp_msg (MSGT_OPEN, MSGL_DBG2,
+              "%s Offset switch: current %d, enabled %d, step %d -> %d\n",
+              LOG_LEVEL_V4L2, pvr->chan_idx,
+              pvr->stationlist.enabled, step, chidx);
+
+      if (!pvr->stationlist.list[chidx].enabled)
+      {
+        mp_msg (MSGT_OPEN, MSGL_DBG2,
+                "%s Switch disabled to user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+                pvr->stationlist.list[chidx].name,
+                pvr->stationlist.list[chidx].freq,
+                pvr->stationlist.list[chidx].station);
+        chidx += FFSIGN (step);
+      }
+      else
+        gotcha = 1;
+    }
+
+    pvr->freq = pvr->stationlist.list[chidx].freq;
+    pvr->chan_idx_last = pvr->chan_idx;
+    pvr->chan_idx = chidx;
+
+    mp_msg (MSGT_OPEN, MSGL_INFO,
+            "%s Switch to user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+            pvr->stationlist.list[chidx].name,
+            pvr->stationlist.list[chidx].freq,
+            pvr->stationlist.list[chidx].station);
+
+    if (v4lAction)
+      return set_v4l2_freq (pvr);
+
+    return (pvr->freq > 0) ? 0 : -1;
+  }
+
+  mp_msg (MSGT_OPEN, MSGL_ERR,
+          "%s Ooops couldn't set freq by channel entry step %d to current %d, enabled %d\n", LOG_LEVEL_V4L2, 
+          step, pvr->chan_idx, pvr->stationlist.enabled);
+
+  return -1;
+}
+
+static int
+set_station_by_channelname_or_freq (struct pvr_t *pvr, const char *channel,
+                                    int freq, int v4lAction)
+{
+  int i = 0;
+
+  if (!pvr || !pvr->stationlist.list) 
+    return -1;
+
+  if (0 >= pvr->stationlist.enabled)
+  {
+    mp_msg (MSGT_OPEN, MSGL_WARN,
+            "%s No enabled station, cannot switch channel/frequency\n",
+            LOG_LEVEL_V4L2);
+    return -1;
+  }
+
+  if (channel)
+  {
+    /* select by channel */
+    for (i = 0; i < pvr->stationlist.used ; i++)
+    {
+      if (!strcasecmp (pvr->stationlist.list[i].name, channel))
+      {
+        if (!pvr->stationlist.list[i].enabled)
+        {
+          mp_msg (MSGT_OPEN, MSGL_WARN,
+                  "%s Switch disabled to user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+                  pvr->stationlist.list[i].name,
+                  pvr->stationlist.list[i].freq,
+                  pvr->stationlist.list[i].station);
+
+          return -1;
+        }
+
+        pvr->freq = pvr->stationlist.list[i].freq;
+        pvr->chan_idx_last = pvr->chan_idx;
+        pvr->chan_idx = i;
+        break;
+      }
+    }
+  }
+  else if (freq >= 0)
+  {
+    /* select by freq */
+    for (i = 0; i < pvr->stationlist.used; i++)
+    {
+      if (pvr->stationlist.list[i].freq == freq)
+      {
+        if (!pvr->stationlist.list[i].enabled)
+        {
+          mp_msg (MSGT_OPEN, MSGL_WARN,
+                  "%s Switch disabled to user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+                  pvr->stationlist.list[i].name,
+                  pvr->stationlist.list[i].freq,
+                  pvr->stationlist.list[i].station);
+          
+          return -1;
+        }
+
+        pvr->freq = pvr->stationlist.list[i].freq;
+        pvr->chan_idx_last = pvr->chan_idx;
+        pvr->chan_idx = i;
+        break;
+      }
+    }
+  }
+
+  if (i >= pvr->stationlist.used)
+  {
+    if (channel)
+      mp_msg (MSGT_OPEN, MSGL_WARN,
+              "%s unable to find channel %s\n", LOG_LEVEL_V4L2, channel);
+    else
+      mp_msg (MSGT_OPEN, MSGL_WARN,
+              "%s unable to find frequency %d\n", LOG_LEVEL_V4L2, freq);
+    return -1;
+  }
+
+  mp_msg (MSGT_OPEN, MSGL_INFO,
+          "%s Switch to user station channel: %8s - freq: %8d - station: %s\n", LOG_LEVEL_V4L2, 
+          pvr->stationlist.list[i].name,
+          pvr->stationlist.list[i].freq,
+          pvr->stationlist.list[i].station);
+
+  if (v4lAction)
+    return set_v4l2_freq (pvr);
+
+  return (pvr->freq > 0) ? 0 : -1;
+}
+
+static int 
+force_freq_step (struct pvr_t *pvr, int step)
+{
+  int freq;
+
+  if (!pvr) 
+    return -1;
+
+  freq = pvr->freq+step;
+
+  if (freq)
+  {
+    mp_msg (MSGT_OPEN, MSGL_INFO,
+            "%s Force Frequency %d + %d = %d \n", LOG_LEVEL_V4L2, 
+            pvr->freq, step, freq);
+
+    pvr->freq = freq;
+
+    return set_v4l2_freq (pvr);
+  }
+  
+  return -1;
+}
+
 static void
 parse_encoder_options (struct pvr_t *pvr)
 {
@@ -494,6 +1112,37 @@
 {
   if (!pvr)
     return;
+
+  /* Create our station/channel list */
+  parse_setup_stationlist (pvr);
+
+  if (pvr->param_channel)
+  {
+    if (set_station_by_channelname_or_freq (pvr, pvr->param_channel,
+                                            -1, 0) >= 0)
+    {
+      if (tv_param_freq)
+      {
+        mp_msg (MSGT_OPEN, MSGL_HINT,
+                "%s tv param freq %s is overwritten by channel setting freq %d\n", LOG_LEVEL_V4L2, 
+                tv_param_freq, pvr->freq);
+      }
+    }
+  }
+  
+  if (pvr->freq < 0 && tv_param_freq)
+  {
+    mp_msg (MSGT_OPEN, MSGL_HINT, "%s tv param freq %s is used directly\n",
+            LOG_LEVEL_V4L2, tv_param_freq);
+
+    if (set_station_by_channelname_or_freq (pvr, NULL,
+                                            atoi (tv_param_freq), 0)<0)
+      {
+        mp_msg (MSGT_OPEN, MSGL_WARN,
+                "%s tv param freq %s invalid to set station\n",
+                LOG_LEVEL_V4L2, tv_param_freq);
+      }
+  }
   
   if (tv_param_device)
   {
@@ -528,9 +1177,6 @@
 
   if (tv_param_height)
     pvr->height = tv_param_height;
-
-  if (tv_param_freq)
-    pvr->freq = strdup (tv_param_freq);
 }
 
 static int
@@ -692,24 +1338,20 @@
     }
   }
 
-  /* -tv freq=x */
-  if (pvr->freq)
+  if (pvr->freq < 0)
   {
-    struct v4l2_frequency vf;
-    vf.tuner = 0;
-    vf.type = 0;
-    vf.frequency = strtol (pvr->freq, 0L, 0);
+    int freq = get_v4l2_freq (pvr);
     mp_msg (MSGT_OPEN, MSGL_INFO,
-            "%s setting frequency to %d\n", LOG_LEVEL_V4L2, vf.frequency);
-    
-    if (ioctl (pvr->dev_fd, VIDIOC_S_FREQUENCY, &vf) < 0)
-    {
-      mp_msg (MSGT_OPEN, MSGL_ERR, "%s can't set frequency (%s).\n",
-              LOG_LEVEL_V4L2, strerror (errno));
-      return -1;
-    }
+            "%s Using current set frequency %d, to set channel\n",
+            LOG_LEVEL_V4L2, freq);
+
+    if (0 < freq)
+      return set_station_by_channelname_or_freq (pvr, NULL, freq, 1);
   }
 
+  if (0 < pvr->freq)
+    return set_v4l2_freq (pvr) ;
+  
   return 0;
 }
 
@@ -933,6 +1575,15 @@
   
   pvr = pvr_init ();
 
+  /**
+   * if the url, i.e. 'pvr://8', contains the channel, use it,
+   * else use the tv parameter.
+   */
+  if (stream->url && strlen (stream->url) > 6 && stream->url[6] != '\0')
+    pvr->param_channel = strdup (stream->url + 6);
+  else if (tv_param_channel && strlen (tv_param_channel))
+    pvr->param_channel = strdup (tv_param_channel);
+  
   parse_v4l2_tv_options (pvr);
   parse_encoder_options (pvr);
   
@@ -1027,6 +1678,92 @@
   return STREAM_OK;
 }
 
+/* PVR Public API access */
+
+const char *
+pvr_get_current_stationname (stream_t *stream)
+{
+  struct pvr_t *pvr;
+
+  if (!stream || stream->type != STREAMTYPE_PVR)
+    return NULL;
+  
+  pvr = (struct pvr_t *) stream->priv;
+
+  if (pvr->stationlist.list &&
+      pvr->stationlist.used > pvr->chan_idx &&
+      pvr->chan_idx >= 0)
+    return pvr->stationlist.list[pvr->chan_idx].station;
+
+  return NULL;
+}
+
+const char *
+pvr_get_current_channelname (stream_t *stream)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  if (pvr->stationlist.list &&
+      pvr->stationlist.used > pvr->chan_idx &&
+      pvr->chan_idx >= 0)
+    return pvr->stationlist.list[pvr->chan_idx].name;
+
+  return NULL;
+}
+
+int
+pvr_get_current_frequency (stream_t *stream)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  return pvr->freq;
+}
+
+int
+pvr_set_channel (stream_t *stream, const char * channel)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  return set_station_by_channelname_or_freq (pvr, channel, -1, 1);
+}
+
+int
+pvr_set_lastchannel (stream_t *stream)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  if (pvr->stationlist.list &&
+      pvr->stationlist.used > pvr->chan_idx_last &&
+      pvr->chan_idx_last >= 0)
+    return set_station_by_channelname_or_freq (pvr, pvr->stationlist.list[pvr->chan_idx_last].name, -1, 1);
+
+  return -1;
+}
+
+int
+pvr_set_freq (stream_t *stream, int freq)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  return set_station_by_channelname_or_freq (pvr, NULL, freq, 1);
+}
+
+int
+pvr_set_channel_step (stream_t *stream, int step)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  return set_station_by_step (pvr, step, 1);
+}
+
+int
+pvr_force_freq_step (stream_t *stream, int step)
+{
+  struct pvr_t *pvr = (struct pvr_t *) stream->priv;
+
+  return force_freq_step (pvr, step);
+}
+
 stream_info_t stream_info_pvr = {
   "V4L2 MPEG Input (a.k.a PVR)",
   "pvr",