]> git.sesse.net Git - vlc/commitdiff
* modules/access/dvb: Partial EN 50 221 implementation. This activates
authorChristophe Massiot <massiot@videolan.org>
Fri, 22 Oct 2004 13:53:18 +0000 (13:53 +0000)
committerChristophe Massiot <massiot@videolan.org>
Fri, 22 Oct 2004 13:53:18 +0000 (13:53 +0000)
   native support for CAM modules (without using an external program).
   When used in conjunction with --programs, it also allows to descramble
   several services with one professional CAM.

modules/access/dvb/Modules.am
modules/access/dvb/access.c
modules/access/dvb/dvb.h
modules/access/dvb/en50221.c [new file with mode: 0644]
modules/access/dvb/linux_dvb.c
modules/demux/ts.c

index eb5cc04abd8ff73056c3081d1ba6c6d694c9fbb0..af40f7cafe918da34e3944d8e0096f2ce129a344 100644 (file)
@@ -1,5 +1,6 @@
 SOURCES_dvb = \
        access.c \
        linux_dvb.c \
+       en50221.c \
        dvb.h \
        $(NULL)
index 8a3515af527fbb2ccfef728eb17f9a2afaf97b29..558974eaf07c7a6261c730131b828ee432b9e0b8 100644 (file)
@@ -58,9 +58,6 @@ static void Close( vlc_object_t *p_this );
 #define DEVICE_TEXT N_("Device number to use on adapter")
 #define DEVICE_LONGTEXT ""
 
-#define CAM_TEXT N_("Use CAM")
-#define CAM_LONGTEXT ""
-
 #define FREQ_TEXT N_("Transponder/multiplex frequency")
 #define FREQ_LONGTEXT N_("In kHz for DVB-S or Hz for DVB-C/T")
 
@@ -131,7 +128,6 @@ vlc_module_begin();
                  VLC_FALSE );
     add_integer( "dvb-device", 0, NULL, DEVICE_TEXT, DEVICE_LONGTEXT,
                  VLC_TRUE );
-    add_bool( "dvb-cam", 0, NULL, CAM_TEXT, CAM_LONGTEXT, VLC_FALSE );
     add_integer( "dvb-frequency", 11954000, NULL, FREQ_TEXT, FREQ_LONGTEXT,
                  VLC_FALSE );
     add_integer( "dvb-inversion", 2, NULL, INVERSION_TEXT, INVERSION_LONGTEXT,
@@ -190,7 +186,7 @@ vlc_module_end();
 static block_t *Block( access_t * );
 static int Control( access_t *, int, va_list );
 
-#define SATELLITE_READ_ONCE 3
+#define DVB_READ_ONCE 3
 #define TS_PACKET_SIZE 188
 
 static void FilterUnset( access_t *, int i_max );
@@ -274,13 +270,7 @@ static int Open( vlc_object_t *p_this )
         FilterSet( p_access, 0x0, OTHER_TYPE );
     }
 
-    p_sys->b_cam = var_GetBool( p_access, "dvb-cam" );
-    if ( p_sys->b_cam )
-    {
-        msg_Dbg( p_access, "initing CAM..." );
-        if ( E_(CAMOpen)( p_access ) < 0 )
-            p_sys->b_cam = VLC_FALSE;
-    }
+    E_(CAMOpen)( p_access );
 
     return VLC_SUCCESS;
 }
@@ -297,9 +287,7 @@ static void Close( vlc_object_t *p_this )
 
     E_(DVRClose)( p_access );
     E_(FrontendClose)( p_access );
-
-    if ( p_sys->b_cam )
-        E_(CAMClose)( p_access );
+    E_(CAMClose)( p_access );
 
     free( p_sys );
 }
@@ -310,40 +298,52 @@ static void Close( vlc_object_t *p_this )
 static block_t *Block( access_t *p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
-    struct timeval timeout;
-    fd_set fds;
-    int i_ret;
     block_t *p_block;
 
-    /* Initialize file descriptor set */
-    FD_ZERO( &fds );
-    FD_SET( p_sys->i_handle, &fds );
-
-    /* We'll wait 0.5 second if nothing happens */
-    timeout.tv_sec = 0;
-    timeout.tv_usec = 500000;
-
-    /* Find if some data is available */
-    while( (i_ret = select( p_sys->i_handle + 1, &fds, NULL, NULL, &timeout )) == 0 ||
-           (i_ret < 0 && errno == EINTR) )
+    for ( ; ; )
     {
+        struct timeval timeout;
+        fd_set fds;
+        int i_ret;
+
+        /* Initialize file descriptor set */
         FD_ZERO( &fds );
         FD_SET( p_sys->i_handle, &fds );
+
+        /* We'll wait 0.5 second if nothing happens */
         timeout.tv_sec = 0;
         timeout.tv_usec = 500000;
 
-        if( p_access->b_die )
+        /* Find if some data is available */
+        i_ret = select( p_sys->i_handle + 1, &fds, NULL, NULL, &timeout );
+
+        if ( p_access->b_die )
             return NULL;
-    }
 
-    if ( i_ret < 0 )
-    {
-        msg_Err( p_access, "select error (%s)", strerror(errno) );
-        return NULL;
+        if ( i_ret < 0 && errno == EINTR )
+            continue;
+
+        if ( i_ret < 0 )
+        {
+            msg_Err( p_access, "select error (%s)", strerror(errno) );
+            return NULL;
+        }
+
+        if ( p_sys->i_ca_handle && mdate() > p_sys->i_ca_next_event )
+        {
+            E_(CAMPoll)( p_access );
+            p_sys->i_ca_next_event = mdate() + p_sys->i_ca_timeout;
+        }
+
+        if ( FD_ISSET( p_sys->i_handle, &fds ) )
+        {
+            break;
+        }
     }
 
-    p_block = block_New( p_access, SATELLITE_READ_ONCE * TS_PACKET_SIZE );
-    if( ( p_block->i_buffer = read( p_sys->i_handle, p_block->p_buffer, SATELLITE_READ_ONCE * TS_PACKET_SIZE ) ) <= 0 )
+    p_block = block_New( p_access, DVB_READ_ONCE * TS_PACKET_SIZE );
+    if( ( p_block->i_buffer = read( p_sys->i_handle, p_block->p_buffer,
+                                    DVB_READ_ONCE * TS_PACKET_SIZE ) ) <= 0 )
     {
         msg_Err( p_access, "read failed (%s)", strerror(errno) );
         block_Release( p_block );
@@ -376,7 +376,7 @@ static int Control( access_t *p_access, int i_query, va_list args )
         /* */
         case ACCESS_GET_MTU:
             pi_int = (int*)va_arg( args, int * );
-            *pi_int = SATELLITE_READ_ONCE * TS_PACKET_SIZE;
+            *pi_int = DVB_READ_ONCE * TS_PACKET_SIZE;
             break;
 
         case ACCESS_GET_PTS_DELAY:
@@ -405,26 +405,16 @@ static int Control( access_t *p_access, int i_query, va_list args )
             break;
 
         case ACCESS_SET_PRIVATE_ID_CA:
-            if ( p_sys->b_cam )
-            {
-                int i_program;
-                uint16_t i_vpid, i_apid1, i_apid2, i_apid3;
-                uint8_t i_cad_length;
-                uint8_t *p_cad;
-
-                i_program = (int)va_arg( args, int );
-                i_vpid = (int16_t)va_arg( args, int );
-                i_apid1 = (uint16_t)va_arg( args, int );
-                i_apid2 = (uint16_t)va_arg( args, int );
-                i_apid3 = (uint16_t)va_arg( args, int );
-                i_cad_length = (uint8_t)va_arg( args, int );
-                p_cad = (uint8_t *)va_arg( args, uint8_t * );
-
-                E_(CAMSet)( p_access, i_program, i_vpid, i_apid1, i_apid2,
-                            i_apid3, i_cad_length, p_cad );
-            }
-            break;
+        {
+            uint8_t **pp_capmts;
+            int i_nb_capmts;
 
+            pp_capmts = (uint8_t **)va_arg( args, uint8_t ** );
+            i_nb_capmts = (int)va_arg( args, int );
+
+            E_(CAMSet)( p_access, pp_capmts, i_nb_capmts );
+            break;
+        }
         default:
             msg_Warn( p_access, "unimplemented query in control" );
             return VLC_EGENERIC;
@@ -509,7 +499,6 @@ static void VarInit( access_t *p_access )
     /* */
     var_Create( p_access, "dvb-adapter", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-device", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
-    var_Create( p_access, "dvb-cam", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-frequency", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-inversion", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-probe", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
@@ -575,7 +564,6 @@ static int ParseMRL( access_t *p_access )
     {
         GET_OPTION_INT("adapter")
         else GET_OPTION_INT("device")
-        else GET_OPTION_BOOL("cam")
         else GET_OPTION_INT("frequency")
         else GET_OPTION_INT("inversion")
         else GET_OPTION_BOOL("probe")
index 47ec46fa37db531a2709e8db9a989e5c23ef5bd3..d586dcfb2f55d47a852418139ce82f4ec48e5ec4 100644 (file)
@@ -30,6 +30,7 @@
 #define DMX      "/dev/dvb/adapter%d/demux%d"
 #define FRONTEND "/dev/dvb/adapter%d/frontend%d"
 #define DVR      "/dev/dvb/adapter%d/dvr%d"
+#define CA       "/dev/dvb/adapter%d/ca%d"
 
 /*****************************************************************************
  * Local structures
@@ -43,7 +44,19 @@ typedef struct
 
 typedef struct frontend_t frontend_t;
 
-#define MAX_DEMUX 24
+typedef struct
+{
+    int i_slot;
+    int i_resource_id;
+    void (* pf_handle)( access_t *, int, uint8_t *, int );
+    void (* pf_close)( access_t *, int );
+    void (* pf_manage)( access_t *, int );
+    void *p_sys;
+} en50221_session_t;
+
+#define MAX_DEMUX 48
+#define MAX_CI_SLOTS 16
+#define MAX_SESSIONS 32
 
 struct access_sys_t
 {
@@ -51,8 +64,16 @@ struct access_sys_t
     demux_handle_t p_demux_handles[MAX_DEMUX];
     frontend_t *p_frontend;
     vlc_bool_t b_budget_mode;
-    vlc_bool_t b_cam;
-    int i_cam_handle;
+
+    /* CA management */
+    int i_ca_handle;
+    int i_nb_slots;
+    vlc_bool_t pb_active_slot[MAX_CI_SLOTS];
+    vlc_bool_t pb_tc_has_data[MAX_CI_SLOTS];
+    en50221_session_t p_sessions[MAX_SESSIONS];
+    mtime_t i_ca_timeout, i_ca_next_event;
+    uint8_t **pp_capmts;
+    int i_nb_capmts;
 };
 
 #define VIDEO0_TYPE     1
@@ -77,6 +98,12 @@ int  E_(DVROpen)( access_t * );
 void E_(DVRClose)( access_t * );
 
 int  E_(CAMOpen)( access_t * );
-int  E_(CAMSet)( access_t *, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t,
-                uint16_t, uint8_t * );
+int  E_(CAMPoll)( access_t * );
+int  E_(CAMSet)( access_t *, uint8_t **, int );
 void E_(CAMClose)( access_t * );
+
+int E_(en50221_Init)( access_t * );
+int E_(en50221_Poll)( access_t * );
+int E_(en50221_SetCAPMT)( access_t *, uint8_t **, int );
+void E_(en50221_End)( access_t * );
+
diff --git a/modules/access/dvb/en50221.c b/modules/access/dvb/en50221.c
new file mode 100644 (file)
index 0000000..bcf7977
--- /dev/null
@@ -0,0 +1,1118 @@
+/*****************************************************************************
+ * en50221.c : implementation of the transport, session and applications
+ * layers of EN 50 221
+ *****************************************************************************
+ * Copyright (C) 2004 VideoLAN
+ *
+ * Authors: Christophe Massiot <massiot@via.ecp.fr>
+ * Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA    02111, USA.
+ *****************************************************************************/
+
+#include <vlc/vlc.h>
+#include <vlc/input.h>
+
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+
+#include "dvb.h"
+
+#undef DEBUG_TPDU
+
+static void ResourceManagerOpen( access_t * p_access, int i_session_id );
+static void ApplicationInformationOpen( access_t * p_access, int i_session_id );
+static void ConditionalAccessOpen( access_t * p_access, int i_session_id );
+static void DateTimeOpen( access_t * p_access, int i_session_id );
+static void MMIOpen( access_t * p_access, int i_session_id );
+
+/*****************************************************************************
+ * Utility functions
+ *****************************************************************************/
+#define SIZE_INDICATOR 0x80
+
+static uint8_t *GetLength( uint8_t *p_data, int *pi_length )
+{
+    *pi_length = *p_data++;
+
+    if ( (*pi_length & SIZE_INDICATOR) != 0 )
+    {
+        int l = *pi_length & ~SIZE_INDICATOR;
+        int i;
+
+        *pi_length = 0;
+        for ( i = 0; i < l; i++ )
+            *pi_length = (*pi_length << 8) | *p_data++;
+    }
+
+    return p_data;
+}
+
+static uint8_t *SetLength( uint8_t *p_data, int i_length )
+{
+    uint8_t *p = p_data;
+
+    if ( i_length < 128 )
+    {
+        *p++ = i_length;
+    }
+    else if ( i_length < 256 )
+    {
+        *p++ = SIZE_INDICATOR | 0x1;
+        *p++ = i_length;
+    }
+    else if ( i_length < 65536 )
+    {
+        *p++ = SIZE_INDICATOR | 0x2;
+        *p++ = i_length >> 8;
+        *p++ = i_length & 0xff;
+    }
+    else if ( i_length < 16777216 )
+    {
+        *p++ = SIZE_INDICATOR | 0x3;
+        *p++ = i_length >> 16;
+        *p++ = (i_length >> 8) & 0xff;
+        *p++ = i_length & 0xff;
+    }
+    else
+    {
+        *p++ = SIZE_INDICATOR | 0x4;
+        *p++ = i_length >> 24;
+        *p++ = (i_length >> 16) & 0xff;
+        *p++ = (i_length >> 8) & 0xff;
+        *p++ = i_length & 0xff;
+    }
+
+    return p;
+}
+
+
+/*
+ * Transport layer
+ */
+
+#define MAX_TPDU_SIZE  2048
+#define MAX_TPDU_DATA  (MAX_TPDU_SIZE - 4)
+
+#define DATA_INDICATOR 0x80
+
+#define T_SB           0x80
+#define T_RCV          0x81
+#define T_CREATE_TC    0x82
+#define T_CTC_REPLY    0x83
+#define T_DELETE_TC    0x84
+#define T_DTC_REPLY    0x85
+#define T_REQUEST_TC   0x86
+#define T_NEW_TC       0x87
+#define T_TC_ERROR     0x88
+#define T_DATA_LAST    0xA0
+#define T_DATA_MORE    0xA1
+
+static void Dump( vlc_bool_t b_outgoing, uint8_t *p_data, int i_size )
+{
+#ifdef DEBUG_TPDU
+    int i;
+#define MAX_DUMP 256
+    fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
+    for ( i = 0; i < i_size && i < MAX_DUMP; i++)
+        fprintf(stderr, "%02X ", p_data[i]);
+    fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : "");
+#endif
+}
+
+/*****************************************************************************
+ * TPDUSend
+ *****************************************************************************/
+static int TPDUSend( access_t * p_access, uint8_t i_slot, uint8_t i_tag,
+                     const uint8_t *p_content, int i_length )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    uint8_t i_tcid = i_slot + 1;
+    uint8_t p_data[MAX_TPDU_SIZE];
+    int i_size;
+
+    i_size = 0;
+    p_data[0] = i_slot;
+    p_data[1] = i_tcid;
+    p_data[2] = i_tag;
+
+    switch ( i_tag )
+    {
+    case T_RCV:
+    case T_CREATE_TC:
+    case T_CTC_REPLY:
+    case T_DELETE_TC:
+    case T_DTC_REPLY:
+    case T_REQUEST_TC:
+        p_data[3] = 1; /* length */
+        p_data[4] = i_tcid;
+        i_size = 5;
+        break;
+
+    case T_NEW_TC:
+    case T_TC_ERROR:
+        p_data[3] = 2; /* length */
+        p_data[4] = i_tcid;
+        p_data[5] = p_content[0];
+        i_size = 6;
+        break;
+
+    case T_DATA_LAST:
+    case T_DATA_MORE:
+    {
+        /* i_length <= MAX_TPDU_DATA */
+        uint8_t *p = p_data + 3;
+        p = SetLength( p, i_length + 1 );
+        *p++ = i_tcid;
+
+        if ( i_length )
+            memcpy( p, p_content, i_length );
+            i_size = i_length + (p - p_data);
+        }
+        break;
+
+    default:
+        break;
+    }
+    Dump( VLC_TRUE, p_data, i_size );
+
+    if ( write( p_sys->i_ca_handle, p_data, i_size ) != i_size )
+    {
+        msg_Err( p_access, "cannot write to CAM device (%s)",
+                 strerror(errno) );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * TPDURecv
+ *****************************************************************************/
+#define CAM_READ_TIMEOUT  3500 // ms
+
+static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag,
+                     uint8_t *p_data, int *pi_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    uint8_t i_tcid = i_slot + 1;
+    int i_size;
+    struct pollfd pfd[1];
+
+    pfd[0].fd = p_sys->i_ca_handle;
+    pfd[0].events = POLLIN;
+    if ( !(poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) )
+    {
+        msg_Err( p_access, "cannot poll from CAM device" );
+        return VLC_EGENERIC;
+    }
+
+    if ( pi_size == NULL )
+    {
+        p_data = malloc( MAX_TPDU_SIZE );
+    }
+
+    for ( ; ; )
+    {
+        i_size = read( p_sys->i_ca_handle, p_data, MAX_TPDU_SIZE );
+
+        if ( i_size >= 0 || errno != EINTR )
+            break;
+    }
+
+    if ( i_size < 5 )
+    {
+        msg_Err( p_access, "cannot read from CAM device (%d:%s)", i_size,
+                 strerror(errno) );
+        return VLC_EGENERIC;
+    }
+
+    if ( p_data[1] != i_tcid )
+    {
+        msg_Err( p_access, "invalid read from CAM device (%d instead of %d)",
+                 p_data[1], i_tcid );
+        return VLC_EGENERIC;
+    }
+
+    *pi_tag = p_data[2];
+    p_sys->pb_tc_has_data[i_slot] = (i_size >= 4
+                                      && p_data[i_size - 4] == T_SB
+                                      && p_data[i_size - 3] == 2
+                                      && (p_data[i_size - 1] & DATA_INDICATOR))
+                                        ?  VLC_TRUE : VLC_FALSE;
+
+    Dump( VLC_FALSE, p_data, i_size );
+
+    if ( pi_size == NULL )
+        free( p_data );
+    else
+        *pi_size = i_size;
+
+    return VLC_SUCCESS;
+}
+
+
+/*
+ * Session layer
+ */
+
+#define ST_SESSION_NUMBER           0x90
+#define ST_OPEN_SESSION_REQUEST     0x91
+#define ST_OPEN_SESSION_RESPONSE    0x92
+#define ST_CREATE_SESSION           0x93
+#define ST_CREATE_SESSION_RESPONSE  0x94
+#define ST_CLOSE_SESSION_REQUEST    0x95
+#define ST_CLOSE_SESSION_RESPONSE   0x96
+
+#define SS_OK             0x00
+#define SS_NOT_ALLOCATED  0xF0
+
+#define RI_RESOURCE_MANAGER            0x00010041
+#define RI_APPLICATION_INFORMATION     0x00020041
+#define RI_CONDITIONAL_ACCESS_SUPPORT  0x00030041
+#define RI_HOST_CONTROL                0x00200041
+#define RI_DATE_TIME                   0x00240041
+#define RI_MMI                         0x00400041
+
+static int ResourceIdToInt( uint8_t *p_data )
+{
+    return ((int)p_data[0] << 24) | ((int)p_data[1] << 16)
+            | ((int)p_data[2] << 8) | p_data[3];
+}
+
+/*****************************************************************************
+ * SPDUSend
+ *****************************************************************************/
+static int SPDUSend( access_t * p_access, int i_session_id,
+                     uint8_t *p_data, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    uint8_t *p_spdu = malloc( i_size + 4 );
+    uint8_t *p = p_spdu;
+    uint8_t i_tag;
+    uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+
+    *p++ = ST_SESSION_NUMBER;
+    *p++ = 0x02;
+    *p++ = (i_session_id >> 8);
+    *p++ = i_session_id & 0xff;
+
+    memcpy( p, p_data, i_size );
+
+    i_size += 4;
+    p = p_spdu;
+
+    while ( i_size > 0 )
+    {
+        if ( i_size > MAX_TPDU_DATA )
+        {
+            if ( TPDUSend( p_access, i_slot, T_DATA_MORE, p,
+                           MAX_TPDU_DATA ) != VLC_SUCCESS )
+            {
+                msg_Err( p_access, "couldn't send TPDU on session %d",
+                         i_session_id );
+                free( p_spdu );
+                return VLC_EGENERIC;
+            }
+            p += MAX_TPDU_DATA;
+            i_size -= MAX_TPDU_DATA;
+        }
+        else
+        {
+            if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p, i_size )
+                    != VLC_SUCCESS )
+            {
+                msg_Err( p_access, "couldn't send TPDU on session %d",
+                         i_session_id );
+                free( p_spdu );
+                return VLC_EGENERIC;
+            }
+            i_size = 0;
+        }
+
+        if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS
+               || i_tag != T_SB )
+        {
+            msg_Err( p_access, "couldn't recv TPDU on session %d",
+                     i_session_id );
+            free( p_spdu );
+            return VLC_EGENERIC;
+        }
+    }
+
+    free( p_spdu );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * SessionOpen
+ *****************************************************************************/
+static void SessionOpen( access_t * p_access, uint8_t i_slot,
+                         uint8_t *p_spdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_session_id;
+    int i_resource_id = ResourceIdToInt( &p_spdu[2] );
+    uint8_t p_response[16];
+    int i_status = SS_NOT_ALLOCATED;
+    uint8_t i_tag;
+
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( !p_sys->p_sessions[i_session_id - 1].i_resource_id )
+            break;
+    }
+    if ( i_session_id == MAX_SESSIONS )
+    {
+        msg_Err( p_access, "too many sessions !" );
+        return;
+    }
+    p_sys->p_sessions[i_session_id - 1].i_slot = i_slot;
+    p_sys->p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
+    p_sys->p_sessions[i_session_id - 1].pf_close = NULL;
+    p_sys->p_sessions[i_session_id - 1].pf_manage = NULL;
+
+    if ( i_resource_id == RI_RESOURCE_MANAGER
+          || i_resource_id == RI_APPLICATION_INFORMATION
+          || i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT
+          || i_resource_id == RI_DATE_TIME
+          || i_resource_id == RI_MMI )
+    {
+        i_status = SS_OK;
+    }
+
+    p_response[0] = ST_OPEN_SESSION_RESPONSE;
+    p_response[1] = 0x7;
+    p_response[2] = i_status;
+    p_response[3] = p_spdu[2];
+    p_response[4] = p_spdu[3];
+    p_response[5] = p_spdu[4];
+    p_response[6] = p_spdu[5];
+    p_response[7] = i_session_id >> 8;
+    p_response[8] = i_session_id & 0xff;
+
+    if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 9 ) !=
+            VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionOpen: couldn't send TPDU on slot %d", i_slot );
+        return;
+    }
+    if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionOpen: couldn't recv TPDU on slot %d", i_slot );
+        return;
+    }
+
+    switch ( i_resource_id )
+    {
+    case RI_RESOURCE_MANAGER:
+        ResourceManagerOpen( p_access, i_session_id ); break; 
+    case RI_APPLICATION_INFORMATION:
+        ApplicationInformationOpen( p_access, i_session_id ); break; 
+    case RI_CONDITIONAL_ACCESS_SUPPORT:
+        ConditionalAccessOpen( p_access, i_session_id ); break; 
+    case RI_DATE_TIME:
+        DateTimeOpen( p_access, i_session_id ); break; 
+    case RI_MMI:
+        MMIOpen( p_access, i_session_id ); break; 
+
+    case RI_HOST_CONTROL:
+    default:
+        msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id );
+        p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+    }
+}
+
+/*****************************************************************************
+ * SessionClose
+ *****************************************************************************/
+static void SessionClose( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    uint8_t p_response[16];
+    uint8_t i_tag;
+    uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+
+    if ( p_sys->p_sessions[i_session_id - 1].pf_close != NULL )
+        p_sys->p_sessions[i_session_id - 1].pf_close( p_access, i_session_id );
+    p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+
+    p_response[0] = ST_CLOSE_SESSION_RESPONSE;
+    p_response[1] = 0x3;
+    p_response[2] = SS_OK;
+    p_response[3] = i_session_id >> 8;
+    p_response[4] = i_session_id & 0xff;
+
+    if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 5 ) !=
+            VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionOpen: couldn't send TPDU on slot %d", i_slot );
+        return;
+    }
+    if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionOpen: couldn't recv TPDU on slot %d", i_slot );
+        return;
+    }
+}
+
+/*****************************************************************************
+ * SPDUHandle
+ *****************************************************************************/
+static void SPDUHandle( access_t * p_access, uint8_t i_slot,
+                        uint8_t *p_spdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_session_id;
+
+    switch ( p_spdu[0] )
+    {
+    case ST_SESSION_NUMBER:
+        if ( i_size <= 4 )
+            return;
+        i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
+        p_sys->p_sessions[i_session_id - 1].pf_handle( p_access, i_session_id,
+                                                       p_spdu + 4, i_size - 4 );
+        break;
+
+    case ST_OPEN_SESSION_REQUEST:
+        if ( i_size != 6 || p_spdu[1] != 0x4 )
+            return;
+        SessionOpen( p_access, i_slot, p_spdu, i_size );
+        break;
+
+    case ST_CLOSE_SESSION_REQUEST:
+        i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
+        SessionClose( p_access, i_session_id );
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+/*
+ * Application layer
+ */
+
+#define AOT_NONE                    0x000000
+#define AOT_PROFILE_ENQ             0x9F8010
+#define AOT_PROFILE                 0x9F8011
+#define AOT_PROFILE_CHANGE          0x9F8012
+#define AOT_APPLICATION_INFO_ENQ    0x9F8020
+#define AOT_APPLICATION_INFO        0x9F8021
+#define AOT_ENTER_MENU              0x9F8022
+#define AOT_CA_INFO_ENQ             0x9F8030
+#define AOT_CA_INFO                 0x9F8031
+#define AOT_CA_PMT                  0x9F8032
+#define AOT_CA_PMT_REPLY            0x9F8033
+#define AOT_TUNE                    0x9F8400
+#define AOT_REPLACE                 0x9F8401
+#define AOT_CLEAR_REPLACE           0x9F8402
+#define AOT_ASK_RELEASE             0x9F8403
+#define AOT_DATE_TIME_ENQ           0x9F8440
+#define AOT_DATE_TIME               0x9F8441
+#define AOT_CLOSE_MMI               0x9F8800
+#define AOT_DISPLAY_CONTROL         0x9F8801
+#define AOT_DISPLAY_REPLY           0x9F8802
+#define AOT_TEXT_LAST               0x9F8803
+#define AOT_TEXT_MORE               0x9F8804
+#define AOT_KEYPAD_CONTROL          0x9F8805
+#define AOT_KEYPRESS                0x9F8806
+#define AOT_ENQ                     0x9F8807
+#define AOT_ANSW                    0x9F8808
+#define AOT_MENU_LAST               0x9F8809
+#define AOT_MENU_MORE               0x9F880A
+#define AOT_MENU_ANSW               0x9F880B
+#define AOT_LIST_LAST               0x9F880C
+#define AOT_LIST_MORE               0x9F880D
+#define AOT_SUBTITLE_SEGMENT_LAST   0x9F880E
+#define AOT_SUBTITLE_SEGMENT_MORE   0x9F880F
+#define AOT_DISPLAY_MESSAGE         0x9F8810
+#define AOT_SCENE_END_MARK          0x9F8811
+#define AOT_SCENE_DONE              0x9F8812
+#define AOT_SCENE_CONTROL           0x9F8813
+#define AOT_SUBTITLE_DOWNLOAD_LAST  0x9F8814
+#define AOT_SUBTITLE_DOWNLOAD_MORE  0x9F8815
+#define AOT_FLUSH_DOWNLOAD          0x9F8816
+#define AOT_DOWNLOAD_REPLY          0x9F8817
+#define AOT_COMMS_CMD               0x9F8C00
+#define AOT_CONNECTION_DESCRIPTOR   0x9F8C01
+#define AOT_COMMS_REPLY             0x9F8C02
+#define AOT_COMMS_SEND_LAST         0x9F8C03
+#define AOT_COMMS_SEND_MORE         0x9F8C04
+#define AOT_COMMS_RCV_LAST          0x9F8C05
+#define AOT_COMMS_RCV_MORE          0x9F8C06
+
+/*****************************************************************************
+ * APDUGetTag
+ *****************************************************************************/
+static int APDUGetTag( const uint8_t *p_apdu, int i_size )
+{
+    if ( i_size >= 3 )
+    {
+        int i, t = 0;
+        for ( i = 0; i < 3; i++ )
+            t = (t << 8) | *p_apdu++;
+        return t;
+    }
+
+    return AOT_NONE;
+}
+
+/*****************************************************************************
+ * APDUGetLength
+ *****************************************************************************/
+static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size )
+{
+    return GetLength( &p_apdu[3], pi_size );
+}
+
+/*****************************************************************************
+ * APDUSend
+ *****************************************************************************/
+static int APDUSend( access_t * p_access, int i_session_id, int i_tag,
+                     uint8_t *p_data, int i_size )
+{
+    uint8_t *p_apdu = malloc( i_size + 12 );
+    uint8_t *p = p_apdu;
+    int i_ret;
+
+    *p++ = (i_tag >> 16);
+    *p++ = (i_tag >> 8) & 0xff;
+    *p++ = i_tag & 0xff;
+    p = SetLength( p, i_size );
+    if ( i_size )
+        memcpy( p, p_data, i_size );
+
+    i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu );
+    free( p_apdu );
+    return i_ret;
+}
+
+/*****************************************************************************
+ * ResourceManagerHandle
+ *****************************************************************************/
+static void ResourceManagerHandle( access_t * p_access, int i_session_id,
+                                   uint8_t *p_apdu, int i_size )
+{
+    int i_tag = APDUGetTag( p_apdu, i_size );
+
+    switch ( i_tag )
+    {
+    case AOT_PROFILE_ENQ:
+    {
+        int resources[] = { htonl(RI_RESOURCE_MANAGER),
+                            htonl(RI_APPLICATION_INFORMATION),
+                            htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
+                            htonl(RI_DATE_TIME),
+                            htonl(RI_MMI)
+                          };
+        APDUSend( p_access, i_session_id, AOT_PROFILE, (uint8_t*)resources,
+                  sizeof(resources) );
+        break;
+    }
+    case AOT_PROFILE:
+        APDUSend( p_access, i_session_id, AOT_PROFILE_CHANGE, NULL, 0 );
+        break;
+
+    default:
+        msg_Err( p_access, "unexpected tag in ResourceManagerHandle (0x%x)",
+                 i_tag );
+    }
+}
+
+/*****************************************************************************
+ * ResourceManagerOpen
+ *****************************************************************************/
+static void ResourceManagerOpen( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "opening ResourceManager session (%d)", i_session_id );
+
+    p_sys->p_sessions[i_session_id - 1].pf_handle = ResourceManagerHandle;
+
+    APDUSend( p_access, i_session_id, AOT_PROFILE_ENQ, NULL, 0 );
+}
+
+/*****************************************************************************
+ * ApplicationInformationHandle
+ *****************************************************************************/
+static void ApplicationInformationHandle( access_t * p_access, int i_session_id,
+                                          uint8_t *p_apdu, int i_size )
+{
+    int i_tag = APDUGetTag( p_apdu, i_size );
+
+    switch ( i_tag )
+    {
+    case AOT_APPLICATION_INFO:
+    {
+        int i_type, i_manufacturer, i_code;
+        int l = 0;
+        uint8_t *d = APDUGetLength( p_apdu, &l );
+
+        if ( l < 4 ) break;
+        p_apdu[l + 3] = '\0';
+
+        i_type = *d++;
+        i_manufacturer = ((int)d[0] << 8) | d[1];
+        d += 2;
+        i_code = ((int)d[0] << 8) | d[1];
+        d += 2;
+        d = GetLength( d, &l );
+        d[l] = '\0';
+        msg_Info( p_access, "CAM: %s, %02X, %04X, %04X",
+                  d, i_type, i_manufacturer, i_code );
+        break;
+    }
+    default:
+        msg_Err( p_access,
+                 "unexpected tag in ApplicationInformationHandle (0x%x)",
+                 i_tag );
+    }
+}
+
+/*****************************************************************************
+ * ApplicationInformationOpen
+ *****************************************************************************/
+static void ApplicationInformationOpen( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "opening ApplicationInformation session (%d)", i_session_id );
+
+    p_sys->p_sessions[i_session_id - 1].pf_handle = ApplicationInformationHandle;
+
+    APDUSend( p_access, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
+}
+
+/*****************************************************************************
+ * ConditionalAccessHandle
+ *****************************************************************************/
+static void ConditionalAccessHandle( access_t * p_access, int i_session_id,
+                                     uint8_t *p_apdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_tag = APDUGetTag( p_apdu, i_size );
+
+    switch ( i_tag )
+    {
+    case AOT_CA_INFO:
+    {
+        if ( p_sys->i_nb_capmts )
+        {
+            int i;
+            msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id );
+            for ( i = 0; i < p_sys->i_nb_capmts; i++ )
+            {
+                int i_size;
+                uint8_t *p;
+                p = GetLength( &p_sys->pp_capmts[i][3], &i_size );
+                SPDUSend( p_access, i_session_id, p_sys->pp_capmts[i],
+                          i_size + (p - p_sys->pp_capmts[i]) );
+            }
+
+            p_sys->i_ca_timeout = 100000;
+        }
+        break;
+    }
+    default:
+        msg_Err( p_access,
+                 "unexpected tag in ConditionalAccessHandle (0x%x)",
+                 i_tag );
+    }
+}
+
+/*****************************************************************************
+ * ConditionalAccessOpen
+ *****************************************************************************/
+static void ConditionalAccessOpen( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id );
+
+    p_sys->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle;
+
+    APDUSend( p_access, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 );
+}
+
+typedef struct
+{
+    int i_interval;
+    mtime_t i_last;
+} date_time_t;
+
+/*****************************************************************************
+ * DateTimeSend
+ *****************************************************************************/
+static void DateTimeSend( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    date_time_t *p_date =
+        (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+
+    time_t t = time(NULL);
+    struct tm tm_gmt;
+    struct tm tm_loc;
+
+    if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) )
+    {
+        int Y = tm_gmt.tm_year;
+        int M = tm_gmt.tm_mon + 1;
+        int D = tm_gmt.tm_mday;
+        int L = (M == 1 || M == 2) ? 1 : 0;
+        int MJD = 14956 + D + (int)((Y - L) * 365.25)
+                    + (int)((M + 1 + L * 12) * 30.6001);
+        uint8_t p_response[7];
+
+#define DEC2BCD(d) (((d / 10) << 4) + (d % 10))
+
+        p_response[0] = htons(MJD) >> 8;
+        p_response[1] = htons(MJD) & 0xff;
+        p_response[2] = DEC2BCD(tm_gmt.tm_hour);
+        p_response[3] = DEC2BCD(tm_gmt.tm_min);
+        p_response[4] = DEC2BCD(tm_gmt.tm_sec);
+        p_response[5] = htons(tm_loc.tm_gmtoff / 60) >> 8;
+        p_response[6] = htons(tm_loc.tm_gmtoff / 60) & 0xff;
+
+        APDUSend( p_access, i_session_id, AOT_DATE_TIME, p_response, 7 );
+
+        p_date->i_last = mdate();
+    }
+}
+
+/*****************************************************************************
+ * DateTimeHandle
+ *****************************************************************************/
+static void DateTimeHandle( access_t * p_access, int i_session_id,
+                            uint8_t *p_apdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    date_time_t *p_date =
+        (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+
+    int i_tag = APDUGetTag( p_apdu, i_size );
+
+    switch ( i_tag )
+    {
+    case AOT_DATE_TIME_ENQ:
+    {
+        int l;
+        const uint8_t *d = APDUGetLength( p_apdu, &l );
+
+        if ( l > 0 )
+        {
+            p_date->i_interval = *d;
+            msg_Dbg( p_access, "DateTimeHandle : interval set to %d",
+                     p_date->i_interval );
+        }
+        else
+            p_date->i_interval = 0;
+
+        DateTimeSend( p_access, i_session_id );
+        break;
+    }
+    default:
+        msg_Err( p_access, "unexpected tag in DateTimeHandle (0x%x)", i_tag );
+    }
+}
+
+/*****************************************************************************
+ * DateTimeManage
+ *****************************************************************************/
+static void DateTimeManage( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    date_time_t *p_date =
+        (date_time_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+
+    if ( p_date->i_interval
+          && mdate() > p_date->i_last + (mtime_t)p_date->i_interval * 1000000 )
+    {
+        DateTimeSend( p_access, i_session_id );
+    }
+}
+
+/*****************************************************************************
+ * DateTimeOpen
+ *****************************************************************************/
+static void DateTimeOpen( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "opening DateTime session (%d)", i_session_id );
+
+    p_sys->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle;
+    p_sys->p_sessions[i_session_id - 1].pf_manage = DateTimeManage;
+    p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(date_time_t));
+    memset( p_sys->p_sessions[i_session_id - 1].p_sys, 0, sizeof(date_time_t) );
+
+    DateTimeSend( p_access, i_session_id );
+}
+
+/*****************************************************************************
+ * MMIHandle
+ *****************************************************************************/
+static void MMIHandle( access_t * p_access, int i_session_id,
+                            uint8_t *p_apdu, int i_size )
+{
+    int i_tag = APDUGetTag( p_apdu, i_size );
+
+    switch ( i_tag )
+    {
+    default:
+        msg_Err( p_access, "unexpected tag in MMIHandle (0x%x)", i_tag );
+    }
+}
+
+/*****************************************************************************
+ * MMIOpen
+ *****************************************************************************/
+static void MMIOpen( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "opening MMI session (%d)", i_session_id );
+
+    p_sys->p_sessions[i_session_id - 1].pf_handle = MMIHandle;
+}
+
+
+/*
+ * External entry points
+ */
+
+/*****************************************************************************
+ * en50221_Init : Open the transport layer
+ *****************************************************************************/
+#define MAX_TC_RETRIES 20
+
+int E_(en50221_Init)( access_t * p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot, i_active_slots = 0;
+
+    for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+    {
+        int i;
+        if ( !p_sys->pb_active_slot[i_slot] )
+            continue;
+        p_sys->pb_active_slot[i_slot] = VLC_FALSE;
+
+        if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
+                != VLC_SUCCESS )
+        {
+            msg_Err( p_access, "en50221_Init: couldn't send TPDU on slot %d",
+                     i_slot );
+            continue;
+        }
+
+        /* This is out of the spec */
+        for ( i = 0; i < MAX_TC_RETRIES; i++ )
+        {
+            uint8_t i_tag;
+            if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) == VLC_SUCCESS
+                  && i_tag == T_CTC_REPLY )
+            {
+                p_sys->pb_active_slot[i_slot] = VLC_TRUE;
+                i_active_slots++;
+                break;
+            }
+
+            if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
+                    != VLC_SUCCESS )
+            {
+                msg_Err( p_access,
+                         "en50221_Init: couldn't send TPDU on slot %d",
+                         i_slot );
+                continue;
+            }
+        }
+    }
+    p_sys->i_ca_timeout = 1000;
+
+    return i_active_slots;
+}
+
+/*****************************************************************************
+ * en50221_Poll : Poll the CAM for TPDUs
+ *****************************************************************************/
+int E_(en50221_Poll)( access_t * p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot;
+    int i_session_id;
+
+    for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+    {
+        uint8_t i_tag;
+
+        if ( !p_sys->pb_active_slot[i_slot] )
+            continue;
+
+        if ( !p_sys->pb_tc_has_data[i_slot] )
+        {
+            if ( TPDUSend( p_access, i_slot, T_DATA_LAST, NULL, 0 ) !=
+                    VLC_SUCCESS )
+            {
+                msg_Err( p_access,
+                         "en50221_Poll: couldn't send TPDU on slot %d",
+                         i_slot );
+                continue;
+            }
+            if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) !=
+                    VLC_SUCCESS )
+            {
+                msg_Err( p_access,
+                         "en50221_Poll: couldn't recv TPDU on slot %d",
+                         i_slot );
+                continue;
+            }
+        }
+
+        while ( p_sys->pb_tc_has_data[i_slot] )
+        {
+            uint8_t p_tpdu[MAX_TPDU_SIZE];
+            int i_size, i_session_size;
+            uint8_t *p_session;
+
+            if ( TPDUSend( p_access, i_slot, T_RCV, NULL, 0 ) != VLC_SUCCESS )
+            {
+                msg_Err( p_access,
+                         "en50221_Poll: couldn't send TPDU on slot %d",
+                         i_slot );
+                continue;
+            }
+            if ( TPDURecv( p_access, i_slot, &i_tag, p_tpdu, &i_size ) !=
+                    VLC_SUCCESS )
+            {
+                msg_Err( p_access,
+                         "en50221_Poll: couldn't recv TPDU on slot %d",
+                         i_slot );
+                continue;
+            }
+
+            p_session = GetLength( &p_tpdu[3], &i_session_size );
+            if ( i_session_size <= 1 )
+                continue;
+
+            p_session++;
+            i_session_size--;
+
+            if ( i_tag != T_DATA_LAST )
+            {
+                msg_Err( p_access,
+                         "en50221_Poll: fragmented TPDU not supported" );
+                break;
+            }
+
+            SPDUHandle( p_access, i_slot, p_session, i_session_size );
+        }
+    }
+
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
+              && p_sys->p_sessions[i_session_id - 1].pf_manage )
+        {
+            p_sys->p_sessions[i_session_id - 1].pf_manage( p_access,
+                                                           i_session_id );
+        }
+    }
+
+    return VLC_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * en50221_SetCAPMT :
+ *****************************************************************************/
+int E_(en50221_SetCAPMT)( access_t * p_access, uint8_t **pp_capmts,
+                          int i_nb_capmts )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_session_id;
+
+    for ( i_session_id = 0; i_session_id < MAX_SESSIONS; i_session_id++ )
+    {
+        int i;
+
+        if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
+              != RI_CONDITIONAL_ACCESS_SUPPORT )
+            continue;
+
+        msg_Dbg( p_access, "sending CAPMT on session %d", i_session_id );
+        for ( i = 0; i < i_nb_capmts; i++ )
+        {
+            int i_size;
+            uint8_t *p;
+            p = GetLength( &pp_capmts[i][3], &i_size );
+            SPDUSend( p_access, i_session_id, pp_capmts[i],
+                      i_size + (p - pp_capmts[i]) );
+        }
+
+        p_sys->i_ca_timeout = 100000;
+    }
+
+    if ( p_sys->i_nb_capmts )
+    {
+        int i;
+        for ( i = 0; i < p_sys->i_nb_capmts; i++ )
+        {
+            free( p_sys->pp_capmts[i] );
+        }
+        free( p_sys->pp_capmts );
+    }
+    p_sys->pp_capmts = pp_capmts;
+    p_sys->i_nb_capmts = i_nb_capmts;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * en50221_End :
+ *****************************************************************************/
+void E_(en50221_End)( access_t * p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    if ( p_sys->i_nb_capmts )
+    {
+        int i;
+        for ( i = 0; i < p_sys->i_nb_capmts; i++ )
+        {
+            free( p_sys->pp_capmts[i] );
+        }
+        free( p_sys->pp_capmts );
+    }
+
+    /* TODO */
+}
index bee0e33a8c4a6396a6390add1f6e31c17cc4e44c..4658a9559e2a122a653a56ea17fafed24341e3c3 100644 (file)
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * dvb.c : functions to control a DVB card under Linux with v4l2
+ * linux_dvb.c : functions to control a DVB card under Linux with v4l2
  *****************************************************************************
  * Copyright (C) 1998-2004 VideoLAN
  *
 #include <linux/dvb/version.h>
 #include <linux/dvb/dmx.h>
 #include <linux/dvb/frontend.h>
-
-#include <linux/errno.h>
+#include <linux/dvb/ca.h>
 
 #include "dvb.h"
-#include "network.h"
 
 #define DMX_BUFFER_SIZE (1024 * 1024)
+#define CA_MAX_STATE_RETRY 5
 
 /*
  * Frontends
@@ -1108,60 +1107,121 @@ void E_(DVRClose)( access_t * p_access )
 
 /*
  * CAM device
- *
- * This uses the external cam_set program from libdvb-0.5.4
  */
 
 /*****************************************************************************
  * CAMOpen :
  *****************************************************************************/
-int E_(CAMOpen)( access_t * p_access )
+int E_(CAMOpen)( access_t *p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
+    char ca[128];
+    int i_adapter, i_device, i_slot, i_active_slots = 0;
+    ca_caps_t caps;
+
+    i_adapter = var_GetInteger( p_access, "dvb-adapter" );
+    i_device = var_GetInteger( p_access, "dvb-device" );
+
+    if( snprintf( ca, sizeof(ca), CA, i_adapter, i_device ) >= (int)sizeof(ca) )
+    {
+        msg_Err( p_access, "snprintf() truncated string for CA" );
+        ca[sizeof(ca) - 1] = '\0';
+    }
+
+    msg_Dbg( p_access, "Opening device %s", ca );
+    if( (p_sys->i_ca_handle = open(ca, O_RDWR | O_NONBLOCK)) < 0 )
+    {
+        msg_Err( p_access, "CAMInit: opening device failed (%s)",
+                 strerror(errno) );
+        return VLC_EGENERIC;
+    }
+
+    if ( ioctl( p_sys->i_ca_handle, CA_GET_CAP, &caps ) != 0
+          || caps.slot_num == 0 || caps.slot_type != CA_CI_LINK )
+    {
+        msg_Err( p_access, "CAMInit: no compatible CAM module" );
+        close( p_sys->i_ca_handle );
+        p_sys->i_ca_handle = 0;
+        return VLC_EGENERIC;
+    }
+
+    p_sys->i_nb_slots = caps.slot_num;
+    memset( p_sys->pb_active_slot, 0, sizeof(vlc_bool_t) * MAX_CI_SLOTS );
+
+    for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+    {
+        ca_slot_info_t sinfo;
+        int i;
+
+        if ( ioctl( p_sys->i_ca_handle, CA_RESET, 1 << i_slot) != 0 )
+        {
+            msg_Err( p_access, "CAMInit: couldn't reset slot %d", i_slot );
+            continue;
+        }
+
+        for ( i = 0; i < CA_MAX_STATE_RETRY; i++ )
+        {
+            msleep(100000);
+
+            sinfo.num = i_slot;
+            if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 )
+            {
+                msg_Err( p_access, "CAMInit: couldn't get info on slot %d",
+                         i_slot );
+                continue;
+            }
+
+            if ( sinfo.flags & CA_CI_MODULE_READY )
+            {
+                p_sys->pb_active_slot[i_slot] = VLC_TRUE;
+            }
+        }
+    }
 
-    p_sys->i_cam_handle = net_OpenTCP( p_access, "localhost", 4711 );
-    if ( p_sys->i_cam_handle < 0 )
+    i_active_slots = E_(en50221_Init)( p_access );
+
+    msg_Dbg( p_access, "CAMInit: found a CI handler with %d slots, %d active",
+             p_sys->i_nb_slots, i_active_slots );
+
+    if ( !i_active_slots )
     {
-        return -VLC_EGENERIC;
+        close( p_sys->i_ca_handle );
+        p_sys->i_ca_handle = 0;
+        return VLC_EGENERIC;
     }
 
     return VLC_SUCCESS;
 }
 
 /*****************************************************************************
- * CAMSet :
+ * CAMPoll :
  *****************************************************************************/
-int E_(CAMSet)( access_t * p_access, uint16_t i_program, uint16_t i_vpid,
-                uint16_t i_apid1, uint16_t i_apid2, uint16_t i_apid3,
-                uint16_t i_cad_length, uint8_t *p_cad )
+int E_(CAMPoll)( access_t * p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
-    uint8_t p_str[12];
-
-    memcpy( p_str, &i_program, 2 );
-    memcpy( p_str + 2, &i_vpid, 2 );
-    memcpy( p_str + 4, &i_apid1, 2 );
-    memcpy( p_str + 6, &i_apid2, 2 );
-    memcpy( p_str + 8, &i_apid3, 2 );
-    memcpy( p_str + 10, &i_cad_length, 2 );
 
-    if ( net_Write( p_access, p_sys->i_cam_handle, p_str, 12 ) != 12 )
+    if ( p_sys->i_ca_handle == 0 )
     {
-        msg_Err( p_access, "write 1 failed (%s)", strerror(errno) );
-        return -VLC_EGENERIC;
+        return VLC_EGENERIC;
     }
 
-    if ( i_cad_length )
+    return E_(en50221_Poll)( p_access );
+}
+
+/*****************************************************************************
+ * CAMSet :
+ *****************************************************************************/
+int E_(CAMSet)( access_t * p_access, uint8_t **pp_capmts, int i_nb_capmts )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    if ( p_sys->i_ca_handle == 0 )
     {
-        if ( net_Write( p_access, p_sys->i_cam_handle, p_cad, i_cad_length )
-              != i_cad_length )
-        {
-            msg_Err( p_access, "write 2 failed (%s) %d", strerror(errno),
-                     i_cad_length );
-            return -VLC_EGENERIC;
-        }
+        return VLC_EGENERIC;
     }
 
+    E_(en50221_SetCAPMT)( p_access, pp_capmts, i_nb_capmts );
+
     return VLC_SUCCESS;
 }
 
@@ -1172,9 +1232,11 @@ void E_(CAMClose)( access_t * p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
 
-    if ( p_sys->i_cam_handle )
+    E_(en50221_End)( p_access );
+
+    if ( p_sys->i_ca_handle )
     {
-        close( p_sys->i_cam_handle );
+        close( p_sys->i_ca_handle );
     }
 }
 
index 04a5f0b5f6ab851a011d00a51922f5de20af6872..63f9ab5d56128ff18428e6e13386920935ea7110 100644 (file)
@@ -183,8 +183,6 @@ typedef struct
 
 } iod_descriptor_t;
 
-#define MAX_CAD 10
-
 typedef struct
 {
     dvbpsi_handle   handle;
@@ -196,10 +194,9 @@ typedef struct
     /* IOD stuff (mpeg4) */
     iod_descriptor_t *iod;
 
-    /* Conditional Access descriptor */
-    int             i_nb_cad;
-    uint8_t         *cad[MAX_CAD];
-    uint8_t         i_cad_length[MAX_CAD];
+    /* Conditional Access PMT (EN 50 221) */
+    uint8_t         *p_capmt;
+    int             i_capmt_size;
 
 } ts_prg_psi_t;
 
@@ -277,6 +274,7 @@ struct demux_sys_t
 
     vlc_bool_t  b_dvb_control;
     int         i_dvb_program;
+    vlc_list_t  *p_programs_list;
 };
 
 static int Demux  ( demux_t *p_demux );
@@ -302,6 +300,8 @@ static void PCRHandle( demux_t *p_demux, ts_pid_t *, block_t * );
 static iod_descriptor_t *IODNew( int , uint8_t * );
 static void              IODFree( iod_descriptor_t * );
 
+static void DVBCAPMTSend( demux_t *p_demux );
+
 #define TS_PACKET_SIZE_188 188
 #define TS_PACKET_SIZE_192 192
 #define TS_PACKET_SIZE_204 204
@@ -472,7 +472,7 @@ static int Open( vlc_object_t *p_this )
         {
             ts_pid_t *pmt = &p_sys->pid[i_pid];
 
-            msg_Dbg( p_demux, "extra pmt specified (pid=0x%x)", i_pid );
+            msg_Dbg( p_demux, "extra pmt specified (pid=%d)", i_pid );
             PIDInit( pmt, VLC_TRUE, NULL );
             /* FIXME we should also ask for a number */
             pmt->psi->prg[0]->handle =
@@ -512,7 +512,7 @@ static int Open( vlc_object_t *p_this )
                             {
                                 pid->es->fmt.i_id = i_pid;
                             }
-                            msg_Dbg( p_demux, "  * es pid=0x%x type=0x%x "
+                            msg_Dbg( p_demux, "  * es pid=%d type=%d "
                                      "fcc=%4.4s", i_pid, i_stream_type,
                                      (char*)&pid->es->fmt.i_codec );
                             pid->es->id = es_out_Add( p_demux->out,
@@ -610,7 +610,7 @@ static void Close( vlc_object_t *p_this )
 
         if( pid->b_seen )
         {
-            msg_Dbg( p_demux, "  - pid[0x%x] seen", pid->i_pid );
+            msg_Dbg( p_demux, "  - pid[%d] seen", pid->i_pid );
         }
 
         if( p_sys->b_dvb_control && pid->i_pid > 0 )
@@ -632,6 +632,14 @@ static void Close( vlc_object_t *p_this )
     }
 
     if( p_sys->i_pmt ) free( p_sys->pmt );
+
+    if ( p_sys->p_programs_list )
+    {
+        vlc_value_t val;
+        val.p_list = p_sys->p_programs_list;
+        var_Change( p_demux, "programs", VLC_VAR_FREELIST, &val, NULL );
+    }
+
     free( p_sys );
 }
 
@@ -744,7 +752,7 @@ static int Demux( demux_t *p_demux )
         {
             if( !p_pid->b_seen )
             {
-                msg_Dbg( p_demux, "pid[0x%x] unknown", p_pid->i_pid );
+                msg_Dbg( p_demux, "pid[%d] unknown", p_pid->i_pid );
             }
             /* We have to handle PCR if present */
             PCRHandle( p_demux, p_pid, p_pkt );
@@ -829,9 +837,11 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
         {
             uint16_t i_vpid = 0, i_apid1 = 0, i_apid2 = 0, i_apid3 = 0;
             ts_prg_psi_t *p_prg = NULL;
+            vlc_list_t *p_list;
 
             i_int = (int)va_arg( args, int );
-            msg_Dbg( p_demux, "DEMUX_SET_GROUP %d", i_int );
+            p_list = (vlc_list_t *)va_arg( args, vlc_list_t * );
+            msg_Dbg( p_demux, "DEMUX_SET_GROUP %d %p", i_int, p_list );
 
             if( p_sys->b_dvb_control && i_int > 0 && i_int != p_sys->i_dvb_program )
             {
@@ -907,6 +917,10 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                     stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
                                     ACCESS_SET_PRIVATE_ID_STATE, i_pmt_pid,
                                     VLC_TRUE );
+                    stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
+                                    ACCESS_SET_PRIVATE_ID_STATE, p_prg->i_pid_pcr,
+                                    VLC_TRUE );
+
                     for( i = 2; i < 8192; i++ )
                     {
                         ts_pid_t *pid = &p_sys->pid[i];
@@ -937,22 +951,14 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                     }
 
                     /* Set CAM descrambling */
-                    for ( i = 0; i < p_prg->i_nb_cad; i++ )
-                    {
-                        stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                                        ACCESS_SET_PRIVATE_ID_CA, p_prg->i_number,
-                                        i_vpid, i_apid1, i_apid2, i_apid3,
-                                        p_prg->i_cad_length[i],
-                                        p_prg->cad[i] );
-                    }
-                    stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                                    ACCESS_SET_PRIVATE_ID_CA, p_prg->i_number,
-                                    0, 0, 0, 0, 0, NULL );
-                    stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                                    ACCESS_SET_PRIVATE_ID_CA, 0,
-                                    0, 0, 0, 0, 0, NULL );
+                    DVBCAPMTSend( p_demux );
                 }
             }
+            else
+            {
+                p_sys->i_dvb_program = -1;
+                p_sys->p_programs_list = p_list;
+            }
             return VLC_SUCCESS;
         }
 
@@ -996,7 +1002,8 @@ static void PIDInit( ts_pid_t *pid, vlc_bool_t b_psi, ts_psi_t *p_owner )
             prg->i_pid_pcr  = -1;
             prg->i_pid_pmt  = -1;
             prg->iod        = NULL;
-            prg->i_nb_cad   = 0;
+            prg->p_capmt    = NULL;
+            prg->i_capmt_size = 0;
             prg->handle     = NULL;
 
             TAB_APPEND( pid->psi->i_prg, pid->psi->prg, prg );
@@ -1027,11 +1034,10 @@ static void PIDClean( es_out_t *out, ts_pid_t *pid )
         if( pid->psi->handle ) dvbpsi_DetachPMT( pid->psi->handle );
         for( i = 0; i < pid->psi->i_prg; i++ )
         {
-            int j;
             if( pid->psi->prg[i]->iod )
                 IODFree( pid->psi->prg[i]->iod );
-            for ( j = 0; j < pid->psi->prg[i]->i_nb_cad; j++ )
-                free( pid->psi->prg[i]->cad[j] );
+            if ( pid->psi->prg[i]->i_capmt_size )
+                free( pid->psi->prg[i]->p_capmt );
             if( pid->psi->prg[i]->handle )
                 dvbpsi_DetachPMT( pid->psi->prg[i]->handle );
             free( pid->psi->prg[i] );
@@ -1098,7 +1104,7 @@ static void ParsePES( demux_t *p_demux, ts_pid_t *pid )
     if( header[0] != 0 || header[1] != 0 || header[2] != 1 )
     {
         if( !p_demux->p_sys->b_silent )
-            msg_Warn( p_demux, "invalid header [0x%x:%x:%x:%x] (pid: 0x%x)",
+            msg_Warn( p_demux, "invalid header [0x%x:%x:%x:%x] (pid: %d)",
                       header[0], header[1],header[2],header[3], pid->i_pid );
         block_ChainRelease( p_pes );
         return;
@@ -1335,7 +1341,7 @@ static vlc_bool_t GatherPES( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk )
     int         i_diff;
 
 #if 0
-    msg_Dbg( p_demux, "pid=0x%x unit_start=%d adaptation=%d payload=%d "
+    msg_Dbg( p_demux, "pid=%d unit_start=%d adaptation=%d payload=%d "
              "cc=0x%x", pid->i_pid, b_unit_start, b_adaptation,
              b_payload, i_cc );
 #endif
@@ -1346,7 +1352,7 @@ static vlc_bool_t GatherPES( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk )
 
     if( p[1]&0x80 )
     {
-        msg_Dbg( p_demux, "transport_error_indicator set (pid=0x%x)",
+        msg_Dbg( p_demux, "transport_error_indicator set (pid=%d)",
                  pid->i_pid );
     }
 
@@ -1369,7 +1375,7 @@ static vlc_bool_t GatherPES( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk )
         {
             if( p[5]&0x80 )
             {
-                msg_Warn( p_demux, "discontinuity_indicator (pid=0x%x) "
+                msg_Warn( p_demux, "discontinuity_indicator (pid=%d) "
                           "ignored", pid->i_pid );
             }
         }
@@ -1392,7 +1398,7 @@ static vlc_bool_t GatherPES( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk )
     {
         if( pid->i_cc == 0xff )
         {
-            msg_Warn( p_demux, "first packet for pid=0x%x cc=0x%x",
+            msg_Warn( p_demux, "first packet for pid=%d cc=0x%x",
                       pid->i_pid, i_cc );
             pid->i_cc = i_cc;
         }
@@ -1906,6 +1912,27 @@ static void IODFree( iod_descriptor_t *p_iod )
  ** libdvbpsi callbacks
  ****************************************************************************
  ****************************************************************************/
+static vlc_bool_t DVBProgramIsSelected( demux_t *p_demux, uint16_t i_pgrm )
+{
+    demux_sys_t          *p_sys = p_demux->p_sys;
+
+    if ( !p_sys->b_dvb_control ) return VLC_FALSE;
+    if ( p_sys->i_dvb_program == -1 && p_sys->p_programs_list == NULL )
+        return VLC_TRUE;
+    if ( p_sys->i_dvb_program == i_pgrm ) return VLC_TRUE;
+
+    if ( p_sys->p_programs_list != NULL )
+    {
+        int i;
+        for ( i = 0; i < p_sys->p_programs_list->i_count; i++ )
+        {
+            if ( i_pgrm == p_sys->p_programs_list->p_values[i].i_int )
+                return VLC_TRUE;
+        }
+    }
+    return VLC_FALSE;
+}
+
 static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
 {
     demux_sys_t          *p_sys = p_demux->p_sys;
@@ -1914,12 +1941,11 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
 
     ts_pid_t             *pmt = NULL;
     ts_prg_psi_t         *prg = NULL;
+
+    int                  i_cad_length = 0;
     ts_pid_t             **pp_clean = NULL;
     int                  i_clean = 0, i;
 
-    /* CA descriptor */
-    uint16_t             i_vpid = 0, i_apid1 = 0, i_apid2 = 0, i_apid3 = 0;
-
     msg_Dbg( p_demux, "PMTCallBack called" );
 
     /* First find this PMT declared in PAT */
@@ -1961,13 +1987,6 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         if( pid->b_valid && pid->p_owner == pmt->psi &&
             pid->i_owner_number == prg->i_number && pid->psi == NULL )
         {
-            if( p_sys->b_dvb_control && ( p_sys->i_dvb_program < 0 ||
-                p_sys->i_dvb_program == prg->i_number ) )
-            {
-                stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                                ACCESS_SET_PRIVATE_ID_STATE, i, VLC_FALSE );
-            }
-
             TAB_APPEND( i_clean, pp_clean, pid );
         }
     }
@@ -1976,15 +1995,29 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         IODFree( prg->iod );
         prg->iod = NULL;
     }
-    for ( i = 0; i < prg->i_nb_cad; i++ )
-        free( prg->cad[i] );
-    prg->i_nb_cad = 0;
+    if ( prg->i_capmt_size )
+        free( prg->p_capmt );
+    prg->i_capmt_size = 0;
 
-    msg_Dbg( p_demux, "new PMT program number=%d version=%d pid_pcr=0x%x",
+    msg_Dbg( p_demux, "new PMT program number=%d version=%d pid_pcr=%d",
              p_pmt->i_program_number, p_pmt->i_version, p_pmt->i_pcr_pid );
     prg->i_pid_pcr = p_pmt->i_pcr_pid;
     prg->i_version = p_pmt->i_version;
 
+    if( DVBProgramIsSelected( p_demux, prg->i_number ) )
+    {
+        /* Set demux filter */
+        stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
+                        ACCESS_SET_PRIVATE_ID_STATE, prg->i_pid_pcr,
+                        VLC_TRUE );
+    }
+    else if ( p_sys->b_dvb_control )
+    {
+        msg_Warn( p_demux, "skipping program (not selected)" );
+        dvbpsi_DeletePMT(p_pmt);
+        return;
+    }
+
     /* Parse descriptor */
     for( p_dr = p_pmt->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next )
     {
@@ -1998,13 +2031,7 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         else if( p_dr->i_tag == 0x9 )
         {
             msg_Dbg( p_demux, " * descriptor : CA (0x9)" );
-
-            prg->cad[prg->i_nb_cad] = malloc( p_dr->i_length + 2 );
-            prg->cad[prg->i_nb_cad][0] = 0x9;
-            prg->cad[prg->i_nb_cad][1] = p_dr->i_length;
-            memcpy( prg->cad[prg->i_nb_cad] + 2, p_dr->p_data, p_dr->i_length );
-            prg->i_cad_length[prg->i_nb_cad] = p_dr->i_length + 2;
-            prg->i_nb_cad++;
+            i_cad_length += p_dr->i_length + 2;
         }
         else
         {
@@ -2012,6 +2039,31 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         }
     }
 
+    if ( i_cad_length )
+    {
+        prg->p_capmt = malloc( 6 + i_cad_length );
+        prg->i_capmt_size = 6 + i_cad_length;
+
+        prg->p_capmt[0] = p_pmt->i_program_number >> 8;
+        prg->p_capmt[1] = p_pmt->i_program_number & 0xff;
+        prg->p_capmt[2] = (p_pmt->i_version << 1) | 0x1;
+        prg->p_capmt[3] = (i_cad_length + 1) >> 8;
+        prg->p_capmt[4] = (i_cad_length + 1) & 0xff;
+        prg->p_capmt[5] = 0x1; /* ok_descrambling */
+
+        i = 6;
+        for( p_dr = p_pmt->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next )
+        {
+            if( p_dr->i_tag == 0x9 )
+            {
+                prg->p_capmt[i] = 0x9;
+                prg->p_capmt[i+1] = p_dr->i_length;
+                memcpy( &prg->p_capmt[i+2], p_dr->p_data, p_dr->i_length );
+                i += p_dr->i_length + 2;
+            }
+        }
+    }
+
     for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
     {
         ts_pid_t tmp_pid, *old_pid = 0, *pid = &tmp_pid;
@@ -2028,7 +2080,7 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
 
         if( !old_pid && p_sys->pid[p_es->i_pid].b_valid )
         {
-            msg_Warn( p_demux, "pmt error: pid=0x%x already defined",
+            msg_Warn( p_demux, "pmt error: pid=%d already defined",
                       p_es->i_pid );
             continue;
         }
@@ -2039,15 +2091,6 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         pid->i_pid          = p_es->i_pid;
         pid->b_seen         = p_sys->pid[p_es->i_pid].b_seen;
 
-        if ( pid->es->fmt.i_cat == VIDEO_ES && !i_vpid )
-            i_vpid = p_es->i_pid;
-        if ( pid->es->fmt.i_cat == AUDIO_ES && !i_apid1 )
-            i_apid1 = p_es->i_pid;
-        else if ( pid->es->fmt.i_cat == AUDIO_ES && !i_apid2 )
-            i_apid2 = p_es->i_pid;
-        else if ( pid->es->fmt.i_cat == AUDIO_ES && !i_apid3 )
-            i_apid3 = p_es->i_pid;
-
         if( p_es->i_type == 0x10 || p_es->i_type == 0x11 ||
             p_es->i_type == 0x12 )
         {
@@ -2058,7 +2101,6 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
 
             if( p_dr && p_dr->i_length == 2 )
             {
-                int i;
                 int i_es_id = ( p_dr->p_data[0] << 8 ) | p_dr->p_data[1];
 
                 msg_Warn( p_demux, "found SL_descriptor es_id=%d", i_es_id );
@@ -2164,7 +2206,7 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
             for( p_dr = p_es->p_first_descriptor; p_dr != NULL;
                  p_dr = p_dr->p_next )
             {
-                msg_Dbg( p_demux, "  * es pid=0x%x type=0x%x dr->i_tag=0x%x",
+                msg_Dbg( p_demux, "  * es pid=%d type=%d dr->i_tag=0x%x",
                          p_es->i_pid, p_es->i_type, p_dr->i_tag );
 
                 if( p_dr->i_tag == 0x6a )
@@ -2191,6 +2233,10 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
                     msg_Dbg( p_demux, "  * Teletext descriptor" );
                     pid->es->fmt.i_cat = SPU_ES;
                     pid->es->fmt.i_codec = VLC_FOURCC( 't', 'e', 'l', 'x' );
+                    pid->es->fmt.i_extra = p_dr->i_length;
+                    pid->es->fmt.p_extra = malloc( p_dr->i_length );
+                    memcpy( pid->es->fmt.p_extra, p_dr->p_data,
+                            p_dr->i_length );
                 }
 #ifdef _DVBPSI_DR_59_H_
                 else if( p_dr->i_tag == 0x59 )
@@ -2314,14 +2360,12 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
         pid->es->fmt.i_group = p_pmt->i_program_number;
         if( pid->es->fmt.i_cat == UNKNOWN_ES )
         {
-            msg_Dbg( p_demux, "  * es pid=0x%x type=0x%x *unknown*",
+            msg_Dbg( p_demux, "  * es pid=%d type=%d *unknown*",
                      p_es->i_pid, p_es->i_type );
         }
         else if( !p_sys->b_udp_out )
         {
-            int i;
-
-            msg_Dbg( p_demux, "  * es pid=0x%x type=0x%x fcc=%4.4s",
+            msg_Dbg( p_demux, "  * es pid=%d type=%d fcc=%4.4s",
                      p_es->i_pid, p_es->i_type, (char*)&pid->es->fmt.i_codec );
 
             if( p_sys->b_es_id_pid ) pid->es->fmt.i_id = p_es->i_pid;
@@ -2365,6 +2409,7 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
             }
         }
 
+        i_cad_length = 0;
         /* Add ES to the list */
         if( old_pid )
         {
@@ -2379,14 +2424,7 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
             if( p_dr->i_tag == 0x9 )
             {
                 msg_Dbg( p_demux, "   * descriptor : CA (0x9)" );
-
-                prg->cad[prg->i_nb_cad] = malloc( p_dr->i_length + 2 );
-                prg->cad[prg->i_nb_cad][0] = 0x9;
-                prg->cad[prg->i_nb_cad][1] = p_dr->i_length;
-                memcpy( prg->cad[prg->i_nb_cad] + 2, p_dr->p_data,
-                        p_dr->i_length );
-                prg->i_cad_length[prg->i_nb_cad] = p_dr->i_length + 2;
-                prg->i_nb_cad++;
+                i_cad_length += p_dr->i_length + 2;
             }
             else
             {
@@ -2395,8 +2433,64 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
             }
         }
 
-        if( p_sys->b_dvb_control && ( p_sys->i_dvb_program < 0 ||
-            p_sys->i_dvb_program == prg->i_number ) )
+        if ( i_cad_length )
+        {
+            if ( !prg->i_capmt_size )
+            {
+                prg->p_capmt = malloc( 5 + 6 + i_cad_length );
+                prg->i_capmt_size = 5 + 6 + i_cad_length;
+
+                prg->p_capmt[0] = p_pmt->i_program_number >> 8;
+                prg->p_capmt[1] = p_pmt->i_program_number & 0xff;
+                prg->p_capmt[2] = (p_pmt->i_version << 1) | 0x1;
+                prg->p_capmt[3] = 0; /* cad length */
+                prg->p_capmt[4] = 0;
+
+                i = 5;
+            }
+            else
+            {
+                prg->p_capmt = realloc( prg->p_capmt,
+                                        prg->i_capmt_size + 6 + i_cad_length );
+                i = prg->i_capmt_size;
+                prg->i_capmt_size += 6 + i_cad_length;
+            }
+
+            prg->p_capmt[i] = p_es->i_type;
+            prg->p_capmt[i+1] = p_es->i_pid >> 8;
+            prg->p_capmt[i+2] = p_es->i_pid & 0xff;
+            prg->p_capmt[i+3] = (i_cad_length + 1) >> 8;
+            prg->p_capmt[i+4] = (i_cad_length + 1) & 0xff;
+            prg->p_capmt[i+5] = 0x1; /* ok_descrambling */
+            i += 6;
+
+            for( p_dr = p_es->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next )
+            {
+                if( p_dr->i_tag == 0x9 )
+                {
+                    prg->p_capmt[i] = 0x9;
+                    prg->p_capmt[i+1] = p_dr->i_length;
+                    memcpy( &prg->p_capmt[i+2], p_dr->p_data, p_dr->i_length );
+                    i += p_dr->i_length + 2;
+                }
+            }
+        }
+        else if ( prg->i_capmt_size )
+        {
+            prg->p_capmt = realloc( prg->p_capmt,
+                                    prg->i_capmt_size + 5 );
+            i = prg->i_capmt_size;
+            prg->i_capmt_size += 5;
+
+            prg->p_capmt[i] = p_es->i_type;
+            prg->p_capmt[i+1] = p_es->i_pid >> 8;
+            prg->p_capmt[i+2] = p_es->i_pid & 0xff;
+            prg->p_capmt[i+3] = 0;
+            prg->p_capmt[i+4] = 0;
+            i += 5;
+        }
+
+        if( DVBProgramIsSelected( p_demux, prg->i_number ) )
         {
             /* Set demux filter */
             stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
@@ -2407,26 +2501,23 @@ static void PMTCallBack( demux_t *p_demux, dvbpsi_pmt_t *p_pmt )
 
     dvbpsi_DeletePMT( p_pmt );
 
-    for( i = 0; i < i_clean; i++ ) PIDClean( p_demux->out, pp_clean[i] );
-    if( i_clean ) free( pp_clean );
-
-    if( p_sys->b_dvb_control &&
-        ( p_sys->i_dvb_program < 0 || p_sys->i_dvb_program == prg->i_number ) )
+    for ( i = 0; i < i_clean; i++ )
     {
-        /* Set CAM descrambling */
-        for ( i = 0; i < prg->i_nb_cad; i++ )
+        if( DVBProgramIsSelected( p_demux, prg->i_number ) )
         {
             stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                            ACCESS_SET_PRIVATE_ID_CA, prg->i_number,
-                            i_vpid, i_apid1, i_apid2, i_apid3,
-                            prg->i_cad_length[i], prg->cad[i] );
+                            ACCESS_SET_PRIVATE_ID_STATE, pp_clean[i]->i_pid,
+                            VLC_FALSE );
         }
-        stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                        ACCESS_SET_PRIVATE_ID_CA, prg->i_number,
-                        0, 0, 0, 0, 0, NULL );
-        stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
-                        ACCESS_SET_PRIVATE_ID_CA, 0,
-                        0, 0, 0, 0, 0, NULL );
+
+        PIDClean( p_demux->out, pp_clean[i] );
+    }
+    if( i_clean ) free( pp_clean );
+
+    if( DVBProgramIsSelected( p_demux, prg->i_number ) )
+    {
+        /* Set CAM descrambling */
+        DVBCAPMTSend( p_demux );
     }
 }
 
@@ -2447,7 +2538,7 @@ static void PATCallBack( demux_t *p_demux, dvbpsi_pat_t *p_pat )
         return;
     }
 
-    msg_Dbg( p_demux, "new PAT ts_id=0x%x version=%d current_next=%d",
+    msg_Dbg( p_demux, "new PAT ts_id=%d version=%d current_next=%d",
              p_pat->i_ts_id, p_pat->i_version, p_pat->b_current_next );
 
     /* Clean old */
@@ -2541,7 +2632,7 @@ static void PATCallBack( demux_t *p_demux, dvbpsi_pat_t *p_pat )
     for( p_program = p_pat->p_first_program; p_program != NULL;
          p_program = p_program->p_next )
     {
-        msg_Dbg( p_demux, "  * number=%d pid=0x%x", p_program->i_number,
+        msg_Dbg( p_demux, "  * number=%d pid=%d", p_program->i_number,
                  p_program->i_pid );
         if( p_program->i_number != 0 )
         {
@@ -2601,3 +2692,118 @@ static void PATCallBack( demux_t *p_demux, dvbpsi_pat_t *p_pat )
 
     dvbpsi_DeletePAT( p_pat );
 }
+
+static void DVBCAPMTSend( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    int i_nb_capmts = 0;
+    int i;
+
+    for( i = 0; i < p_sys->i_pmt; i++ )
+    {
+        ts_pid_t *pmt = p_sys->pmt[i];
+        int i_prg;
+
+        for( i_prg = 0; i_prg < pmt->psi->i_prg; i_prg++ )
+        {
+            if( DVBProgramIsSelected( p_demux, pmt->psi->prg[i_prg]->i_number )
+                 && pmt->psi->prg[i_prg]->i_capmt_size )
+            {
+                i_nb_capmts++;
+            }
+        }
+    }
+
+    if ( i_nb_capmts )
+    {
+        uint8_t **pp_capmts = malloc( i_nb_capmts * sizeof(uint8_t *) );
+        int i_current_capmt = 0;
+
+        for( i = 0; i < p_sys->i_pmt; i++ )
+        {
+            ts_pid_t *pmt = p_sys->pmt[i];
+            int i_prg;
+
+            for( i_prg = 0; i_prg < pmt->psi->i_prg; i_prg++ )
+            {
+                if( DVBProgramIsSelected( p_demux, pmt->psi->prg[i_prg]->i_number )
+                     && pmt->psi->prg[i_prg]->i_capmt_size )
+                {
+                    uint8_t *p_capmt = malloc( pmt->psi->prg[i_prg]->i_capmt_size + 10 );
+                    int i_pos = 0;
+                    pp_capmts[i_current_capmt] = p_capmt;
+
+                    p_capmt[i_pos] = 0x9F;
+                    p_capmt[i_pos+1] = 0x80;
+                    p_capmt[i_pos+2] = 0x32;
+                    i_pos += 3;
+
+                    if ( (pmt->psi->prg[i_prg]->i_capmt_size + 1) < 128 )
+                    {
+                        p_capmt[i_pos] = (pmt->psi->prg[i_prg]->i_capmt_size + 1);
+                        i_pos++;
+                    }
+                    else if ( (pmt->psi->prg[i_prg]->i_capmt_size + 1) < 256 )
+                    {
+                        p_capmt[i_pos] = 0x81;
+                        p_capmt[i_pos+1] = (pmt->psi->prg[i_prg]->i_capmt_size + 1);
+                        i_pos += 2;
+                    }
+                    else if ( (pmt->psi->prg[i_prg]->i_capmt_size + 1) < 65536 )
+                    {
+                        p_capmt[i_pos] = 0x82;
+                        p_capmt[i_pos+1] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 8;
+                        p_capmt[i_pos+2] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) & 0xff;
+                        i_pos += 3;
+                    }
+                    else if ( (pmt->psi->prg[i_prg]->i_capmt_size + 1) < 16777216 )
+                    {
+                        p_capmt[i_pos] = 0x83;
+                        p_capmt[i_pos+1] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 16;
+                        p_capmt[i_pos+2] =
+                            ((pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 8) & 0xff;
+                        p_capmt[i_pos+3] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) & 0xff;
+                        i_pos += 4;
+                    }
+                    else
+                    {
+                        p_capmt[i_pos] = 0x84;
+                        p_capmt[i_pos+1] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 24;
+                        p_capmt[i_pos+2] =
+                            ((pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 16) & 0xff;
+                        p_capmt[i_pos+3] =
+                            ((pmt->psi->prg[i_prg]->i_capmt_size + 1) >> 8) & 0xff;
+                        p_capmt[i_pos+4] =
+                            (pmt->psi->prg[i_prg]->i_capmt_size + 1) & 0xff;
+                        i_pos += 5;
+                    }
+
+                    if ( i_nb_capmts > 1 )
+                    {
+                        if ( i_current_capmt == 0 )
+                            p_capmt[i_pos] = 0x1; /* first */
+                        else if ( i_current_capmt == i_nb_capmts - 1 )
+                            p_capmt[i_pos] = 0x2; /* last */
+                        else
+                            p_capmt[i_pos] = 0x0; /* more */
+                    }
+                    else
+                        p_capmt[i_pos] = 0x3; /* only */
+                    i_pos++;
+                    i_current_capmt++;
+
+                    memcpy( &p_capmt[i_pos], pmt->psi->prg[i_prg]->p_capmt,
+                            pmt->psi->prg[i_prg]->i_capmt_size );
+                }
+            }
+        }
+
+        stream_Control( p_demux->s, STREAM_CONTROL_ACCESS,
+                        ACCESS_SET_PRIVATE_ID_CA, pp_capmts, i_nb_capmts );
+    }
+}