fix previous commit
[vlc.git] / extras / package / macosx / eyetvplugin / eyetvplugin.c
1 /*****************************************************************************
2 * eyetvplugin.c: Plug-In for the EyeTV software to connect to VLC
3 *****************************************************************************
4 * Copyright (C) 2006-2007 the VideoLAN team
5 * $Id$
6 *
7 * Authors: Felix K├╝hne <fkuehne at videolan dot org>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
23
24 #include "eyetvplugin.h"
25
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31
32 #define MAX_PIDS            256
33 #define MAX_ACTIVE_PIDS     256
34 #define MAX_DEVICES         16
35 #define VLC_NOTIFICATION_OBJECT "VLCEyeTVSupport"
36
37 #pragma push
38 #pragma pack(1)
39
40 /* Structure for TS-Packets */
41 typedef struct 
42 {
43     uint32_t    sync_byte : 8,
44                 transport_error_indicator : 1,
45                 payload_unit_start_indicator : 1,
46                 transport_priority : 1,
47                 PID : 13,
48                 transport_scrambling_control : 2,
49                 adaptation_field_control : 2,
50                 continuity_counter : 4;
51     uint8_t     payload[184];
52
53 } TransportStreamPacket;
54
55 #pragma pop
56
57
58 /* Structure to hold global data to communicate with EyeTV */
59 typedef struct 
60 {
61     EyeTVPluginCallbackProc     callback;
62     /* Structure to hold current active service */
63     EyeTVPluginDeviceID         activeDeviceID;
64     long                        activePIDsCount; 
65     EyeTVPluginPIDInfo          activePIDs[MAX_ACTIVE_PIDS];
66 } VLCEyeTVPluginGlobals_t;
67
68 /* following globals limits us to one VLC instance using EyeTV */
69 static int i_deviceCount;
70 static int i_vlcSock;
71
72 #pragma mark -
73
74 /* initialise the plug-in */
75 static long VLCEyeTVPluginInitialize(VLCEyeTVPluginGlobals_t** globals, long apiVersion, EyeTVPluginCallbackProc callback)
76 {
77     printf("VLC media player Plug-In: Initialize\n");
78     long result = 0;
79     
80     /* init our own storage */
81     i_deviceCount = 0;
82     i_vlcSock = -1;
83     
84     /* notify a potential VLC instance about our initialisation */
85     CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter(),
86                                           CFSTR("PluginInit"), 
87                                           CFSTR(VLC_NOTIFICATION_OBJECT), 
88                                           /*userInfo*/ NULL, 
89                                           TRUE );
90     
91     /* init our notification support */
92     CFNotificationCenterAddObserver( CFNotificationCenterGetDistributedCenter(),
93                                      /* observer */ NULL, 
94                                      /* callBack */ VLCEyeTVPluginGlobalNotificationReceived,
95                                      /* name, NULL==all */ NULL,
96                                      CFSTR(VLC_NOTIFICATION_OBJECT), 
97                                      CFNotificationSuspensionBehaviorDeliverImmediately );
98     
99     *globals = (VLCEyeTVPluginGlobals_t *) calloc(1, sizeof( VLCEyeTVPluginGlobals_t ) );
100     ( *globals )->callback = callback;
101         
102     return result;
103 }
104
105 /* we will be terminated soon, clean up */
106 static long VLCEyeTVPluginTerminate(VLCEyeTVPluginGlobals_t *globals)
107 {
108     long result = 0;
109     
110     printf("VLC media player Plug-In: Terminate\n");
111     
112     /* notify a potential VLC instance about our termination */
113     CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter (),
114                                           CFSTR("PluginQuit"), 
115                                           CFSTR(VLC_NOTIFICATION_OBJECT), 
116                                           /*userInfo*/ NULL, 
117                                           TRUE );
118     
119     /* remove us from the global notification centre */
120     CFNotificationCenterRemoveEveryObserver( CFNotificationCenterGetDistributedCenter(),
121                                              (void *)VLCEyeTVPluginGlobalNotificationReceived );
122     
123     /* close data connection */
124     if( i_vlcSock != -1 )
125     {
126         close( i_vlcSock );
127         i_vlcSock = -1;
128     }
129     
130     free( globals );
131     return result;
132 }
133
134 /* called when EyeTV asks various stuff about us */
135 static long VLCEyeTVPluginGetInformation(VLCEyeTVPluginGlobals_t *globals, long* outAPIVersion, char* outName, char *outDescription)
136 {
137     printf("VLC media player Plug-In: GetInfo\n");
138     long result = 0;
139     
140     if( globals ) 
141     {
142         if( outAPIVersion )
143         {
144             *outAPIVersion = EYETV_PLUGIN_API_VERSION;
145         }
146         
147         if( outName )
148         {
149             strcpy( outName, "VLC media player Plug-In");
150         }
151         
152         if( outDescription )
153         {
154             strcpy( outDescription, "This Plug-In connects EyeTV to the VLC media player for streaming purposes.");
155         }
156     }
157     
158     return result;
159 }
160
161 /* called if we received a global notification */
162 void VLCEyeTVPluginGlobalNotificationReceived( CFNotificationCenterRef center, 
163                                               void *observer, 
164                                               CFStringRef name, 
165                                               const void *object, 
166                                               CFDictionaryRef userInfo )
167 {
168     /* when VLC launches after us, we need to inform it about our existance and the current state of available devices */
169     if( CFStringCompare( name, CFSTR( "VLCOSXGUIInit" ), 0) == kCFCompareEqualTo )
170     {
171         /* we're here */
172         CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter (),
173                                               CFSTR("PluginInit"), 
174                                               CFSTR(VLC_NOTIFICATION_OBJECT), 
175                                               /*userInfo*/ NULL, 
176                                               TRUE );
177         if( i_deviceCount > 0 )
178         {
179             /* at least one device is apparently connected */
180             CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter (),
181                                                   CFSTR("DeviceAdded"), 
182                                                   CFSTR(VLC_NOTIFICATION_OBJECT), 
183                                                   /*userInfo*/ NULL, 
184                                                   TRUE );
185         }
186     }
187     
188     /* VLC wants us to start sending data */
189     if( CFStringCompare( name, CFSTR( "VLCAccessStartDataSending" ), 0) == kCFCompareEqualTo )
190     {
191         if( i_vlcSock == -1 )
192         {
193             int peerSock;
194          
195             /* set-up data socket */
196             peerSock = socket(AF_UNIX, SOCK_STREAM, 0);
197             if( peerSock != -1 )
198             {
199                 struct sockaddr_un peerAddr;
200                 /* set-up connection address */
201                 memset(&peerAddr, 0, sizeof(peerAddr));
202                 peerAddr.sun_family = AF_UNIX;
203                 strncpy(peerAddr.sun_path, "/tmp/.vlc-eyetv-bridge", sizeof(peerAddr.sun_path)-1);
204                 
205                 /* connect */
206                 printf("data connect in progess...\n");
207                 if( connect(peerSock, (struct sockaddr *)&peerAddr, sizeof(struct sockaddr_un)) != -1 )
208                 {
209                     printf("data sending switched on\n");
210                                         
211                     i_vlcSock = peerSock;
212                 }
213                 else
214                     printf("connect data socket failed (errno=%d)\n", errno );
215             }
216             else
217                 printf("create data socket failed (errno=%d)\n", errno );
218         }
219     }
220     
221     /* VLC wants us to stop sending data */
222     if( CFStringCompare( name, CFSTR( "VLCAccessStopDataSending" ), 0) == kCFCompareEqualTo )
223     {
224         if( i_vlcSock != -1 )
225         {
226             close( i_vlcSock );
227             i_vlcSock = -1;
228             printf( "data sending switched off\n" );
229         }
230     }
231 }
232
233 /* called if a device is added */
234 static long VLCEyeTVPluginDeviceAdded(VLCEyeTVPluginGlobals_t *globals, EyeTVPluginDeviceID deviceID, EyeTVPluginDeviceType deviceType)
235 {
236     printf("VLC media player Plug-In: Device with type %i and ID %i added\n", (int)deviceType, (int)deviceID);
237     
238     long result = 0;
239     
240     if( globals ) 
241     {
242         ++i_deviceCount;
243         if( 1 == i_deviceCount )
244         {                
245             /* notify a potential VLC instance about the addition */
246             CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter(),
247                                                   CFSTR("DeviceAdded"), 
248                                                   CFSTR(VLC_NOTIFICATION_OBJECT), 
249                                                   /*userInfo*/ NULL, 
250                                                   TRUE );
251         }
252     }
253     return result;
254 }
255
256 /* called if a device is removed */
257 static long VLCEyeTVPluginDeviceRemoved(VLCEyeTVPluginGlobals_t *globals, EyeTVPluginDeviceID deviceID)
258 {
259     printf("VLC media player Plug-In: DeviceRemoved\n");
260     
261     long result = 0;
262         
263     --i_deviceCount;
264     if( 0 == i_deviceCount )
265     {                
266         /* notify a potential VLC instance about the removal */
267         CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter(),
268                                               CFSTR("DeviceRemoved"), 
269                                               CFSTR(VLC_NOTIFICATION_OBJECT), 
270                                               /*userInfo*/ NULL, 
271                                               TRUE );
272     }
273     if( (i_vlcSock != -1) && (deviceID == globals->activeDeviceID) )
274     {
275         close(i_vlcSock);
276         i_vlcSock = -1;
277         printf( "data sending switched off\n" );
278     }
279     
280     return result;
281 }
282
283 /* This function is called, whenever packets are received by EyeTV. For reasons of performance,
284  * the data is the original data, not a copy. That means, EyeTV waits until this method is 
285  * finished. Therefore all in this method should be as fast as possible. */
286 static long VLCEyeTVPluginPacketsArrived(VLCEyeTVPluginGlobals_t *globals, EyeTVPluginDeviceID deviceID, long **packets, long packetsCount)
287 {
288     if( globals ) 
289     {
290         /* check if data connection is active */
291         if( i_vlcSock != -1 )
292         {
293             if( deviceID == globals->activeDeviceID ) 
294             {
295                 long pidCount = globals->activePIDsCount;
296                 if( pidCount )
297                 {
298                     uint8_t packetBuffer[sizeof(TransportStreamPacket)*20];
299                     int packetBufferSize = 0;
300                     while( packetsCount )
301                     {
302                         /* apply PID filtering, only PIDs in active service for device are sent through */
303                         long pid = ntohl(**packets)>>8 & 0x1FFFL;
304                         /* ignore NULL packets */
305                         if( 0x1FFFL != pid )
306                         {
307                             long i;
308                             for( i=0; i<pidCount; ++i )
309                             {
310                                 if( globals->activePIDs[i].pid == pid )
311                                 {
312                                     if( packetBufferSize <= (sizeof(packetBuffer)-sizeof(TransportStreamPacket)) )
313                                     {
314                                         /* copy packet in our buffer */
315                                         memcpy(packetBuffer+packetBufferSize, *packets, sizeof(TransportStreamPacket));
316                                         packetBufferSize += sizeof(TransportStreamPacket);
317                                     }
318                                     else
319                                     {
320                                         /* flush buffer to VLC */
321                                         ssize_t sent = write(i_vlcSock, packetBuffer, packetBufferSize);
322                                         if( sent != packetBufferSize )
323                                         {
324                                             if( sent == -1 )
325                                                 printf("data sending failed (errno=%d)\n", errno);
326                                             else
327                                                 printf("data sending incomplete (sent=%d)\n", sent);
328                                             close(i_vlcSock);
329                                             i_vlcSock = -1;
330                                             return 0;
331                                         }
332                                         packetBufferSize = 0;
333                                     }
334                                     if( i > 0 )
335                                     {
336                                        /* if we assume that consecutive packets would have the same PID in most cases,
337                                           it would therefore speed up filtering to reorder activePIDs list based on pid
338                                           occurrences */
339                                         EyeTVPluginPIDInfo swap = globals->activePIDs[i];
340                                         do
341                                         {
342                                             register int c = i--;
343                                             globals->activePIDs[c] = globals->activePIDs[i];
344                                         }
345                                         while( i );
346                                         globals->activePIDs[i] = swap;
347                                     }
348
349                                     if( pid && globals->activePIDs[0].pidType != kEyeTVPIDType_PMT )
350                                     {
351                                         /* to save on CPU, prevent EyeTV from mirroring that program by blocking video & audio packets
352                                            by changing all packets but PAT and PMT to NULL PID */
353 #if defined(WORDS_BIGENDIAN)
354                                         **packets |= 0x001FFF00L;
355 #else
356                                         **packets |= 0x00FFF800L;
357 #endif
358                                     }
359                                     /* done filtering on this packet, move on to next packet */
360                                     break;
361                                 }
362                             }
363                         }
364                         --packetsCount;
365                         ++packets;
366                     }
367                     if( packetBufferSize )
368                     {
369                         /* flush buffer to VLC */
370                         ssize_t sent = write(i_vlcSock, packetBuffer, packetBufferSize);
371                         if( sent != packetBufferSize )
372                         {
373                             if( sent == -1 )
374                                 printf("data sending failed (errno=%d)\n", errno);
375                             else
376                                 printf("data sending incomplete (sent=%d)\n", sent);
377                             close(i_vlcSock);
378                             i_vlcSock = -1;
379                             return 0;
380                         }
381                     }
382                 }
383             }
384         }
385     }
386     return 0;
387 }
388
389 /*  VLCEyeTVPluginServiceChanged,
390  *
391  *  - *globals      : The plug-in Globals
392  *  - deviceID      : Identifies the active Device
393  *   - headendID        : The HeadendID, for e300 it's the orbital position of the satelite in 
394  *                    tenth degrees east
395  *   - transponderID : The Frequency in kHz
396  *   - serviceID        : original ServiceID from the DVB-Stream (e300, e400)
397  *  - pidList       : List of active PIDs   
398  *
399  *  Whenever a service changes, this function is called. Service-related plug-in data should be updated here.
400  */
401 static long VLCEyeTVPluginServiceChanged(VLCEyeTVPluginGlobals_t *globals, 
402                                             EyeTVPluginDeviceID deviceID, 
403                                             long headendID, 
404                                             long transponderID, 
405                                             long serviceID, 
406                                             EyeTVPluginPIDInfo *pidList, 
407                                             long pidsCount)
408 {
409     long result = 0;
410     int i;
411     
412     printf("\nVLC media player Plug-In: ServiceChanged:\n");
413     printf(  "=====================================\n");
414     
415     if( globals ) 
416     {
417         printf("DeviceID: %ld, ", deviceID);
418         printf("HeadendID: %ld, ", headendID);
419         printf("TransponderID: %ld, ", transponderID);
420         printf("ServiceID: %ld\n\n", serviceID);
421         
422         globals->activeDeviceID = deviceID;
423         globals->activePIDsCount = pidsCount;
424
425         /* need active PIDs for packet filtering */
426         for( i = 0; i < pidsCount; i++ )
427         {
428             globals->activePIDs[i] = pidList[i];
429             printf("Active PID: %ld, type: %ld\n", pidList[i].pid, pidList[i].pidType);
430         }
431     }
432     printf(  "=====================================\n");
433     
434     /* notify a potential VLC instance about the service change */
435     CFNotificationCenterPostNotification( CFNotificationCenterGetDistributedCenter(),
436                                           CFSTR("ServiceChanged"), 
437                                           CFSTR(VLC_NOTIFICATION_OBJECT), 
438                                           /*userInfo*/ NULL, 
439                                           TRUE );
440     
441     return result;
442 }
443
444
445 #pragma mark -
446 /* EyeTVPluginDispatcher,
447  *
448  *  - selector : See 'EyeTVPluginDefs.h'
449  *  - *refCon :  The RefCon to the plug-in-related Data
450  *  - deviceID : Identifies the Device
451  *  - params : Parameters for functioncall
452  *
453  * This function is a part of the interface for the communication with EyeTV. If something happens,
454  * EyeTV thinks, we should know of, it calls this function with the corresponding selector. */
455
456 #pragma export on
457
458 long EyeTVPluginDispatcher( EyeTVPluginParams* params )
459 {
460     long result = 0;
461
462     switch( params->selector ) 
463     {
464         case kEyeTVPluginSelector_Initialize:
465             result = VLCEyeTVPluginInitialize((VLCEyeTVPluginGlobals_t**)params->refCon, 
466                                     params->initialize.apiVersion, params->initialize.callback);
467             break;
468             
469         case kEyeTVPluginSelector_Terminate:
470             result = VLCEyeTVPluginTerminate((VLCEyeTVPluginGlobals_t*)params->refCon);
471             break;
472
473         case kEyeTVPluginSelector_GetInfo:
474             result = VLCEyeTVPluginGetInformation((VLCEyeTVPluginGlobals_t*)params->refCon, 
475                                     params->info.pluginAPIVersion, params->info.pluginName, params->info.description);
476             break;
477
478         case kEyeTVPluginSelector_DeviceAdded:
479             result = VLCEyeTVPluginDeviceAdded((VLCEyeTVPluginGlobals_t*)params->refCon, 
480                                     params->deviceID, params->deviceAdded.deviceType);
481             break;
482         
483         case kEyeTVPluginSelector_DeviceRemoved:
484             result = VLCEyeTVPluginDeviceRemoved((VLCEyeTVPluginGlobals_t*)params->refCon, params->deviceID);
485             break;
486
487         case kEyeTVPluginSelector_PacketsArrived:
488             result = VLCEyeTVPluginPacketsArrived((VLCEyeTVPluginGlobals_t*)params->refCon, params->deviceID, 
489                                     params->packetsArrived.packets, params->packetsArrived.packetCount);
490             break;
491
492         case kEyeTVPluginSelector_ServiceChanged:
493             result = VLCEyeTVPluginServiceChanged((VLCEyeTVPluginGlobals_t*)params->refCon, 
494                                     params->deviceID, params->serviceChanged.headendID, 
495                                     params->serviceChanged.transponderID, params->serviceChanged.serviceID, 
496                                     params->serviceChanged.pidList, params->serviceChanged.pidCount);
497             break;
498     }
499
500     return result;
501 }