Commit | Line | Data |
---|---|---|
1f0de05c | 1 | # coding=utf-8 |
094e4ad1 JVH |
2 | ''' |
3 | Copyright (C) 2012 Juho Vähä-Herttua | |
4 | ||
5 | This library is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU Lesser General Public | |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
9 | ||
10 | This library is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | Lesser General Public License for more details. | |
14 | ''' | |
15 | ||
8be1424a JVH |
16 | import os |
17 | import sys | |
18 | import platform | |
19 | ||
20 | from ctypes import * | |
21 | ||
22 | audio_init_prototype = CFUNCTYPE(py_object, c_void_p, c_int, c_int, c_int) | |
23 | audio_set_volume_prototype = CFUNCTYPE(None, c_void_p, c_void_p, c_float) | |
24 | audio_process_prototype = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_int) | |
25 | audio_flush_prototype = CFUNCTYPE(None, c_void_p, c_void_p) | |
26 | audio_destroy_prototype = CFUNCTYPE(None, c_void_p, c_void_p) | |
27 | ||
28 | class RaopNativeCallbacks(Structure): | |
29 | _fields_ = [("cls", py_object), | |
30 | ("audio_init", audio_init_prototype), | |
31 | ("audio_set_volume", audio_set_volume_prototype), | |
32 | ("audio_process", audio_process_prototype), | |
33 | ("audio_flush", audio_flush_prototype), | |
34 | ("audio_destroy", audio_destroy_prototype)] | |
35 | ||
36 | def LoadShairplay(path): | |
37 | if sys.maxsize < 2**32: | |
38 | libname = "shairplay32" | |
39 | else: | |
40 | libname = "shairplay64" | |
41 | ||
42 | if platform.system() == "Windows": | |
43 | libname = libname + ".dll" | |
44 | elif platform.system() == "Darwin": | |
45 | libname = "lib" + libname + ".dylib" | |
46 | else: | |
47 | libname = "lib" + libname + ".so" | |
48 | ||
49 | try: | |
50 | fullpath = os.path.join(path, libname) | |
51 | libshairplay = cdll.LoadLibrary(fullpath) | |
52 | except: | |
53 | raise RuntimeError("Couldn't load shairplay library " + libname) | |
54 | ||
55 | # Initialize dnssd related functions | |
56 | libshairplay.dnssd_init.restype = c_void_p | |
094e4ad1 | 57 | libshairplay.dnssd_init.argtypes = [POINTER(c_int)] |
8be1424a | 58 | libshairplay.dnssd_register_raop.restype = c_int |
094e4ad1 | 59 | libshairplay.dnssd_register_raop.argtypes = [c_void_p, c_char_p, c_ushort, POINTER(c_char), c_int] |
8be1424a | 60 | libshairplay.dnssd_register_airplay.restype = c_int |
094e4ad1 | 61 | libshairplay.dnssd_register_airplay.argtypes = [c_void_p, c_char_p, c_ushort, POINTER(c_char), c_int] |
8be1424a JVH |
62 | libshairplay.dnssd_unregister_raop.restype = None |
63 | libshairplay.dnssd_unregister_raop.argtypes = [c_void_p] | |
64 | libshairplay.dnssd_unregister_airplay.restype = None | |
65 | libshairplay.dnssd_unregister_airplay.argtypes = [c_void_p] | |
66 | libshairplay.dnssd_destroy.restype = None | |
67 | libshairplay.dnssd_destroy.argtypes = [c_void_p] | |
68 | ||
69 | # Initialize raop related functions | |
70 | libshairplay.raop_init.restype = c_void_p | |
71 | libshairplay.raop_init.argtypes = [POINTER(RaopNativeCallbacks), c_char_p] | |
72 | libshairplay.raop_start.restype = c_int | |
73 | libshairplay.raop_start.argtypes = [c_void_p, POINTER(c_ushort), POINTER(c_char), c_int] | |
74 | libshairplay.raop_stop.restype = None | |
75 | libshairplay.raop_stop.argtypes = [c_void_p] | |
76 | libshairplay.raop_destroy.restype = None | |
77 | libshairplay.raop_destroy.argtypes = [c_void_p] | |
78 | ||
79 | return libshairplay | |
80 | ||
81 | RSA_KEY = """ | |
82 | -----BEGIN RSA PRIVATE KEY----- | |
83 | MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt | |
84 | wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U | |
85 | wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf | |
86 | /+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/ | |
87 | UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW | |
88 | BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa | |
89 | LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5 | |
90 | NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm | |
91 | lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz | |
92 | aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu | |
93 | a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM | |
94 | oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z | |
95 | oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+ | |
96 | k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL | |
97 | AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA | |
98 | cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf | |
99 | 54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov | |
100 | 17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc | |
101 | 1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI | |
102 | LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ | |
103 | 2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY= | |
104 | -----END RSA PRIVATE KEY----- | |
105 | """ | |
106 | ||
107 | class RaopCallbacks: | |
108 | def audio_init(self, bits, channels, samplerate): | |
109 | raise NotImplementedError() | |
110 | ||
111 | def audio_set_volume(self, session, volume): | |
112 | pass | |
113 | ||
114 | def audio_process(self, session, buffer): | |
115 | raise NotImplementedError() | |
116 | ||
117 | def audio_flush(self, session): | |
118 | pass | |
119 | ||
120 | def audio_destroy(self, session): | |
121 | pass | |
122 | ||
123 | class RaopService: | |
124 | def audio_init_cb(self, cls, bits, channels, samplerate): | |
125 | session = self.callbacks.audio_init(bits, channels, samplerate) | |
1f0de05c JVH |
126 | self.sessions.append(session) |
127 | return session | |
8be1424a JVH |
128 | |
129 | def audio_set_volume_cb(self, cls, sessionptr, volume): | |
130 | session = cast(sessionptr, py_object).value | |
131 | self.callbacks.audio_set_volume(session, volume) | |
132 | ||
133 | def audio_process_cb(self, cls, sessionptr, buffer, buflen): | |
134 | session = cast(sessionptr, py_object).value | |
135 | strbuffer = string_at(buffer, buflen) | |
136 | self.callbacks.audio_process(session, strbuffer) | |
137 | ||
138 | def audio_flush_cb(self, cls, sessionptr): | |
139 | session = cast(sessionptr, py_object).value | |
140 | self.callbacks.audio_flush(session) | |
141 | ||
142 | def audio_destroy_cb(self, cls, sessionptr): | |
143 | session = cast(sessionptr, py_object).value | |
144 | self.callbacks.audio_destroy(session) | |
1f0de05c JVH |
145 | if session in self.sessions: |
146 | self.sessions.remove(session) | |
8be1424a JVH |
147 | |
148 | ||
149 | def __init__(self, libshairplay, callbacks): | |
150 | self.libshairplay = libshairplay | |
151 | self.callbacks = callbacks | |
1f0de05c | 152 | self.sessions = [] |
8be1424a JVH |
153 | self.instance = None |
154 | ||
155 | # We need to hold a reference to native_callbacks | |
156 | self.native_callbacks = RaopNativeCallbacks() | |
157 | self.native_callbacks.audio_init = audio_init_prototype(self.audio_init_cb) | |
158 | self.native_callbacks.audio_set_volume = audio_set_volume_prototype(self.audio_set_volume_cb) | |
159 | self.native_callbacks.audio_process = audio_process_prototype(self.audio_process_cb) | |
160 | self.native_callbacks.audio_flush = audio_flush_prototype(self.audio_flush_cb) | |
161 | self.native_callbacks.audio_destroy = audio_destroy_prototype(self.audio_destroy_cb) | |
162 | ||
163 | # Initialize the raop instance with our callbacks | |
164 | self.instance = self.libshairplay.raop_init(pointer(self.native_callbacks), RSA_KEY) | |
165 | if self.instance == None: | |
166 | raise RuntimeError("Initializing library failed") | |
167 | ||
168 | def __del__(self): | |
169 | if self.instance != None: | |
170 | self.libshairplay.raop_destroy(self.instance) | |
171 | self.instance = None | |
172 | ||
173 | def start(self, port, hwaddrstr): | |
174 | port = c_ushort(port) | |
175 | hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr)) | |
176 | ||
177 | ret = self.libshairplay.raop_start(self.instance, pointer(port), hwaddr, c_int(len(hwaddr))) | |
178 | if ret < 0: | |
179 | raise RuntimeError("Starting RAOP instance failed") | |
180 | return port.value | |
181 | ||
182 | def stop(self): | |
183 | self.libshairplay.raop_stop(self.instance) | |
184 | ||
185 | class DnssdService: | |
186 | def __init__(self, libshairplay): | |
187 | self.libshairplay = libshairplay | |
188 | self.instance = None | |
189 | ||
8be1424a JVH |
190 | error = c_int(0) |
191 | ||
094e4ad1 | 192 | self.instance = self.libshairplay.dnssd_init(pointer(error)) |
8be1424a JVH |
193 | if self.instance == None: |
194 | raise RuntimeError("Initializing library failed: " + str(error.value)) | |
195 | ||
196 | def __del__(self): | |
197 | if self.instance != None: | |
198 | self.libshairplay.dnssd_destroy(self.instance) | |
199 | self.instance = None | |
200 | ||
094e4ad1 JVH |
201 | def register_raop(self, name, port, hwaddrstr): |
202 | hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr)) | |
203 | self.libshairplay.dnssd_register_raop(self.instance, name, c_ushort(port), hwaddr, len(hwaddr)) | |
8be1424a JVH |
204 | |
205 | def unregister_raop(self): | |
206 | self.libshairplay.dnssd_unregister_raop(self.instance) | |
207 | ||
094e4ad1 JVH |
208 | def register_airplay(self, name, port, hwaddrstr): |
209 | hwaddr = create_string_buffer(hwaddrstr, len(hwaddrstr)) | |
210 | self.libshairplay.dnssd_register_airplay(self.instance, name, c_ushort(port), hwaddr, len(hwaddr)) | |
8be1424a JVH |
211 | |
212 | def unregister_airplay(self): | |
213 | self.libshairplay.dnssd_unregister_airplay(self.instance) | |
214 |