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