Branch data Line data Source code
1 : : /* GStreamer - Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
2 : : *
3 : : * RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
4 : : * This interface accepts RAW PCM data and set it as AES encrypted ALAC while performing emission.
5 : : *
6 : : * Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
7 : : *
8 : : * gstapexraop.c
9 : : *
10 : : * This library is free software; you can redistribute it and/or
11 : : * modify it under the terms of the GNU Library General Public
12 : : * License as published by the Free Software Foundation; either
13 : : * version 2 of the License, or (at your option) any later version.
14 : : *
15 : : * This library is distributed in the hope that it will be useful,
16 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 : : * Library General Public License for more details.
19 : : *
20 : : * You should have received a copy of the GNU Library General Public
21 : : * License along with this library; if not, write to the
22 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 : : * Boston, MA 02111-1307, USA.
24 : : *
25 : : */
26 : :
27 : : #ifdef HAVE_CONFIG_H
28 : : #include "config.h"
29 : : #endif
30 : :
31 : : #include <string.h>
32 : :
33 : : #include "gstapexraop.h"
34 : :
35 : : /* private constants */
36 : : #define GST_APEX_RAOP_VOLUME_MIN -144
37 : : #define GST_APEX_RAOP_VOLUME_MAX 0
38 : :
39 : : #define GST_APEX_RAOP_HDR_DEFAULT_LENGTH 1024
40 : : #define GST_APEX_RAOP_SDP_DEFAULT_LENGTH 2048
41 : :
42 : : const static gchar GST_APEX_RAOP_RSA_PUBLIC_MOD[] =
43 : : "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
44 : : "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
45 : : "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
46 : : "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
47 : : "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
48 : : "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
49 : :
50 : : const static gchar GST_APEX_RAOP_RSA_PUBLIC_EXP[] = "AQAB";
51 : :
52 : : const static gchar GST_APEX_RAOP_USER_AGENT[] =
53 : : "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)";
54 : :
55 : : const static guchar GST_APEX_RAOP_FRAME_HEADER[] = {
56 : : 0x24, 0x00, 0x00, 0x00,
57 : : 0xF0, 0xFF, 0x00, 0x00,
58 : : 0x00, 0x00, 0x00, 0x00,
59 : : 0x00, 0x00, 0x00, 0x00
60 : : };
61 : :
62 : : const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16;
63 : :
64 : : const static int GST_APEX_RAOP_ALAC_HEADER_SIZE = 3;
65 : :
66 : : /* string extra utility */
67 : : static gint
68 : 0 : g_strdel (gchar * str, gchar rc)
69 : : {
70 : 0 : int i = 0, j = 0, len, num = 0;
71 : 0 : len = strlen (str);
72 [ # # ]: 0 : while (i < len) {
73 [ # # ]: 0 : if (str[i] == rc) {
74 [ # # ]: 0 : for (j = i; j < len; j++)
75 : 0 : str[j] = str[j + 1];
76 : 0 : len--;
77 : 0 : num++;
78 : : } else {
79 : 0 : i++;
80 : : }
81 : : }
82 : 0 : return num;
83 : : }
84 : :
85 : : /* socket utilities */
86 : : static int
87 : 0 : gst_apexraop_send (int desc, void *data, size_t len)
88 : : {
89 : 0 : int total = 0, bytesleft = len, n = 0;
90 : :
91 [ # # ]: 0 : while (total < len) {
92 : 0 : n = send (desc, ((const char *) data) + total, bytesleft, 0);
93 [ # # ]: 0 : if (n == -1)
94 : 0 : break;
95 : 0 : total += n;
96 : 0 : bytesleft -= n;
97 : : }
98 : :
99 [ # # ]: 0 : return n == -1 ? -1 : total;
100 : : }
101 : :
102 : : static int
103 : 0 : gst_apexraop_recv (int desc, void *data, size_t len)
104 : : {
105 : 0 : memset (data, 0, len);
106 : 0 : return recv (desc, data, len, 0);
107 : : }
108 : :
109 : : /* public opaque handle resolution */
110 : : typedef struct
111 : : {
112 : : guchar aes_ky[AES_BLOCK_SIZE]; /* AES random key */
113 : : guchar aes_iv[AES_BLOCK_SIZE]; /* AES random initial vector */
114 : :
115 : : guchar url_abspath[16]; /* header url random absolute path addon, ANNOUNCE id */
116 : : gint cseq; /* header rtsp inc cseq */
117 : : guchar cid[24]; /* header client instance id */
118 : : gchar *session; /* header raop negotiated session id, once SETUP performed */
119 : : gchar *ua; /* header user agent */
120 : :
121 : : GstApExJackType jack_type; /* APEX connected jack type, once ANNOUNCE performed */
122 : : GstApExJackStatus jack_status; /* APEX connected jack status, once ANNOUNCE performed */
123 : :
124 : : gchar *host; /* APEX target ip */
125 : : guint ctrl_port; /* APEX target control port */
126 : : guint data_port; /* APEX negotiated data port, once SETUP performed */
127 : :
128 : : int ctrl_sd; /* control socket */
129 : : struct sockaddr_in ctrl_sd_in;
130 : :
131 : : int data_sd; /* data socket */
132 : : struct sockaddr_in data_sd_in;
133 : : }
134 : : _GstApExRAOP;
135 : :
136 : : /* raop apex struct allocation */
137 : : GstApExRAOP *
138 : 0 : gst_apexraop_new (const gchar * host, const guint16 port)
139 : : {
140 : : _GstApExRAOP *apexraop;
141 : :
142 : 0 : apexraop = (_GstApExRAOP *) g_malloc0 (sizeof (_GstApExRAOP));
143 : :
144 : 0 : apexraop->host = g_strdup (host);
145 : 0 : apexraop->ctrl_port = port;
146 : 0 : apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT);
147 : 0 : apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
148 : 0 : apexraop->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
149 : :
150 : 0 : return (GstApExRAOP *) apexraop;
151 : : }
152 : :
153 : : /* raop apex struct freeing */
154 : : void
155 : 0 : gst_apexraop_free (GstApExRAOP * con)
156 : : {
157 : : _GstApExRAOP *conn;
158 : 0 : conn = (_GstApExRAOP *) con;
159 : :
160 : 0 : g_free (conn->host);
161 : 0 : g_free (conn->session);
162 : 0 : g_free (conn->ua);
163 : 0 : g_free (conn);
164 : 0 : }
165 : :
166 : : /* host affectation */
167 : : void
168 : 0 : gst_apexraop_set_host (GstApExRAOP * con, const gchar * host)
169 : : {
170 : : _GstApExRAOP *conn;
171 : 0 : conn = (_GstApExRAOP *) con;
172 : :
173 : 0 : g_free (conn->host);
174 : 0 : conn->host = g_strdup (host);
175 : 0 : }
176 : :
177 : : /* host reader */
178 : : gchar *
179 : 0 : gst_apexraop_get_host (GstApExRAOP * con)
180 : : {
181 : : _GstApExRAOP *conn;
182 : 0 : conn = (_GstApExRAOP *) con;
183 : :
184 : 0 : return g_strdup (conn->host);
185 : : }
186 : :
187 : : /* control port affectation */
188 : : void
189 : 0 : gst_apexraop_set_port (GstApExRAOP * con, const guint16 port)
190 : : {
191 : : _GstApExRAOP *conn;
192 : 0 : conn = (_GstApExRAOP *) con;
193 : :
194 : 0 : conn->ctrl_port = port;
195 : 0 : }
196 : :
197 : : /* control port reader */
198 : : guint16
199 : 0 : gst_apexraop_get_port (GstApExRAOP * con)
200 : : {
201 : : _GstApExRAOP *conn;
202 : 0 : conn = (_GstApExRAOP *) con;
203 : :
204 : 0 : return conn->ctrl_port;
205 : : }
206 : :
207 : : /* user agent affectation */
208 : : void
209 : 0 : gst_apexraop_set_useragent (GstApExRAOP * con, const gchar * useragent)
210 : : {
211 : : _GstApExRAOP *conn;
212 : 0 : conn = (_GstApExRAOP *) con;
213 : :
214 : 0 : g_free (conn->ua);
215 : 0 : conn->ua = g_strdup (useragent);
216 : 0 : }
217 : :
218 : : /* user agent reader */
219 : : gchar *
220 : 0 : gst_apexraop_get_useragent (GstApExRAOP * con)
221 : : {
222 : : _GstApExRAOP *conn;
223 : 0 : conn = (_GstApExRAOP *) con;
224 : :
225 : 0 : return g_strdup (conn->ua);
226 : : }
227 : :
228 : : /* raop apex connection sequence */
229 : : GstRTSPStatusCode
230 : 0 : gst_apexraop_connect (GstApExRAOP * con)
231 : : {
232 : : gchar *ac, *ky, *iv, *s, inaddr[INET_ADDRSTRLEN],
233 : : creq[GST_APEX_RAOP_SDP_DEFAULT_LENGTH],
234 : : hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH], *req;
235 : : RSA *rsa;
236 : : guchar *mod, *exp, rsakey[512];
237 : : union gst_randbytes
238 : : {
239 : : struct asvals
240 : : {
241 : : gulong url_key;
242 : : guint64 conn_id;
243 : : guchar challenge[16];
244 : : } v;
245 : : guchar buf[4 + 8 + 16];
246 : : } randbuf;
247 : : gsize size;
248 : : struct sockaddr_in ioaddr;
249 : : socklen_t iolen;
250 : : GstRTSPStatusCode res;
251 : : _GstApExRAOP *conn;
252 : :
253 : 0 : conn = (_GstApExRAOP *) con;
254 : :
255 [ # # ]: 0 : if ((conn->ctrl_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
256 : 0 : return GST_RTSP_STS_DESTINATION_UNREACHABLE;
257 : :
258 : 0 : conn->ctrl_sd_in.sin_family = AF_INET;
259 : 0 : conn->ctrl_sd_in.sin_port = htons (conn->ctrl_port);
260 : :
261 [ # # ]: 0 : if (!inet_aton (conn->host, &conn->ctrl_sd_in.sin_addr)) {
262 : 0 : struct hostent *hp = (struct hostent *) gethostbyname (conn->host);
263 [ # # ]: 0 : if (hp == NULL)
264 : 0 : return GST_RTSP_STS_DESTINATION_UNREACHABLE;
265 : 0 : memcpy (&conn->ctrl_sd_in.sin_addr, hp->h_addr, hp->h_length);
266 : : }
267 : :
268 [ # # ]: 0 : if (connect (conn->ctrl_sd, (struct sockaddr *) &conn->ctrl_sd_in,
269 : : sizeof (conn->ctrl_sd_in)) < 0)
270 : 0 : return GST_RTSP_STS_DESTINATION_UNREACHABLE;
271 : :
272 : 0 : RAND_bytes (randbuf.buf, sizeof (randbuf));
273 : 0 : sprintf ((gchar *) conn->url_abspath, "%lu", randbuf.v.url_key);
274 : 0 : sprintf ((char *) conn->cid, "%16" G_GINT64_MODIFIER "x", randbuf.v.conn_id);
275 : :
276 : 0 : RAND_bytes (conn->aes_ky, AES_BLOCK_SIZE);
277 : 0 : RAND_bytes (conn->aes_iv, AES_BLOCK_SIZE);
278 : :
279 : 0 : rsa = RSA_new ();
280 : 0 : mod = g_base64_decode (GST_APEX_RAOP_RSA_PUBLIC_MOD, &size);
281 : 0 : rsa->n = BN_bin2bn (mod, size, NULL);
282 : 0 : exp = g_base64_decode (GST_APEX_RAOP_RSA_PUBLIC_EXP, &size);
283 : 0 : rsa->e = BN_bin2bn (exp, size, NULL);
284 : 0 : size =
285 : 0 : RSA_public_encrypt (AES_BLOCK_SIZE, conn->aes_ky, rsakey, rsa,
286 : : RSA_PKCS1_OAEP_PADDING);
287 : :
288 : 0 : ky = g_base64_encode (rsakey, size);
289 : 0 : iv = g_base64_encode (conn->aes_iv, AES_BLOCK_SIZE);
290 : 0 : g_strdel (ky, '=');
291 : 0 : g_strdel (iv, '=');
292 : :
293 : 0 : iolen = sizeof (struct sockaddr);
294 : 0 : getsockname (conn->ctrl_sd, (struct sockaddr *) &ioaddr, &iolen);
295 : 0 : inet_ntop (AF_INET, &(ioaddr.sin_addr), inaddr, INET_ADDRSTRLEN);
296 : :
297 : 0 : ac = g_base64_encode (randbuf.v.challenge, 16);
298 : 0 : g_strdel (ac, '=');
299 : :
300 : 0 : sprintf (creq,
301 : : "v=0\r\n"
302 : : "o=iTunes %s 0 IN IP4 %s\r\n"
303 : : "s=iTunes\r\n"
304 : : "c=IN IP4 %s\r\n"
305 : : "t=0 0\r\n"
306 : : "m=audio 0 RTP/AVP 96\r\n"
307 : : "a=rtpmap:96 AppleLossless\r\n"
308 : : "a=fmtp:96 %d 0 %d 40 10 14 %d 255 0 0 %d\r\n"
309 : : "a=rsaaeskey:%s\r\n"
310 : : "a=aesiv:%s\r\n",
311 : 0 : conn->url_abspath,
312 : : inaddr,
313 : : conn->host,
314 : : GST_APEX_RAOP_SAMPLES_PER_FRAME,
315 : : GST_APEX_RAOP_BYTES_PER_CHANNEL * 8,
316 : : GST_APEX_RAOP_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv);
317 : :
318 : 0 : sprintf (hreq,
319 : : "ANNOUNCE rtsp://%s/%s RTSP/1.0\r\n"
320 : : "CSeq: %d\r\n"
321 : : "Client-Instance: %s\r\n"
322 : : "User-Agent: %s\r\n"
323 : : "Content-Type: application/sdp\r\n"
324 : : "Content-Length: %u\r\n"
325 : : "Apple-Challenge: %s\r\n",
326 : : conn->host,
327 : 0 : conn->url_abspath, ++conn->cseq, conn->cid, conn->ua,
328 : 0 : (guint) strlen (creq), ac);
329 : :
330 : 0 : RSA_free (rsa);
331 : 0 : g_free (ky);
332 : 0 : g_free (iv);
333 : 0 : g_free (ac);
334 : 0 : g_free (mod);
335 : 0 : g_free (exp);
336 : :
337 : 0 : req = g_strconcat (hreq, "\r\n", creq, NULL);
338 : :
339 [ # # ]: 0 : if (gst_apexraop_send (conn->ctrl_sd, req, strlen (req)) <= 0) {
340 : 0 : g_free (req);
341 : 0 : return GST_RTSP_STS_GONE;
342 : : }
343 : :
344 : 0 : g_free (req);
345 : :
346 [ # # ]: 0 : if (gst_apexraop_recv (conn->ctrl_sd, hreq,
347 : : GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
348 : 0 : return GST_RTSP_STS_GONE;
349 : :
350 : : {
351 : : int tmp;
352 : 0 : sscanf (hreq, "%*s %d", &tmp);
353 : 0 : res = (GstRTSPStatusCode) tmp;
354 : : }
355 : :
356 [ # # ]: 0 : if (res != GST_RTSP_STS_OK)
357 : 0 : return res;
358 : :
359 : 0 : s = g_strrstr (hreq, "Audio-Jack-Status");
360 : :
361 [ # # ]: 0 : if (s != NULL) {
362 : : gchar status[128];
363 : 0 : sscanf (s, "%*s %s", status);
364 : :
365 [ # # ]: 0 : if (strcmp (status, "connected;") == 0)
366 : 0 : conn->jack_status = GST_APEX_JACK_STATUS_CONNECTED;
367 [ # # ]: 0 : else if (strcmp (status, "disconnected;") == 0)
368 : 0 : conn->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
369 : : else
370 : 0 : conn->jack_status = GST_APEX_JACK_STATUS_UNDEFINED;
371 : :
372 : 0 : s = g_strrstr (s, "type=");
373 : :
374 [ # # ]: 0 : if (s != NULL) {
375 : 0 : strtok (s, "=");
376 : 0 : s = strtok (NULL, "\n");
377 : :
378 [ # # ]: 0 : if (strcmp (s, "analog"))
379 : 0 : conn->jack_type = GST_APEX_JACK_TYPE_ANALOG;
380 [ # # ]: 0 : else if (strcmp (s, "digital"))
381 : 0 : conn->jack_type = GST_APEX_JACK_TYPE_DIGITAL;
382 : : else
383 : 0 : conn->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
384 : : }
385 : : }
386 : :
387 : 0 : sprintf (hreq,
388 : : "SETUP rtsp://%s/%s RTSP/1.0\r\n"
389 : : "CSeq: %d\r\n"
390 : : "Client-Instance: %s\r\n"
391 : : "User-Agent: %s\r\n"
392 : : "Transport: RTP/AVP/TCP;unicast;interleaved=0-1;mode=record\r\n"
393 : 0 : "\r\n", conn->host, conn->url_abspath, ++conn->cseq, conn->cid, conn->ua);
394 : :
395 [ # # ]: 0 : if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
396 : 0 : return GST_RTSP_STS_GONE;
397 : :
398 [ # # ]: 0 : if (gst_apexraop_recv (conn->ctrl_sd, hreq,
399 : : GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
400 : 0 : return GST_RTSP_STS_GONE;
401 : :
402 : : {
403 : : int tmp;
404 : 0 : sscanf (hreq, "%*s %d", &tmp);
405 : 0 : res = (GstRTSPStatusCode) tmp;
406 : : }
407 : :
408 [ # # ]: 0 : if (res != GST_RTSP_STS_OK)
409 : 0 : return res;
410 : :
411 : 0 : s = g_strrstr (hreq, "Session");
412 : :
413 [ # # ]: 0 : if (s != NULL) {
414 : : gchar session[128];
415 : 0 : sscanf (s, "%*s %s", session);
416 : 0 : conn->session = g_strdup (session);
417 : : } else
418 : 0 : return GST_RTSP_STS_PRECONDITION_FAILED;
419 : :
420 : 0 : s = g_strrstr (hreq, "server_port");
421 [ # # ]: 0 : if (s != NULL) {
422 : 0 : sscanf (s, "server_port=%d", &conn->data_port);
423 : : } else
424 : 0 : return GST_RTSP_STS_PRECONDITION_FAILED;
425 : :
426 : 0 : sprintf (hreq,
427 : : "RECORD rtsp://%s/%s RTSP/1.0\r\n"
428 : : "CSeq: %d\r\n"
429 : : "Client-Instance: %s\r\n"
430 : : "User-Agent: %s\r\n"
431 : : "Session: %s\r\n"
432 : : "Range: npt=0-\r\n"
433 : : "RTP-Info: seq=0;rtptime=0\r\n"
434 : : "\r\n",
435 : : conn->host,
436 : 0 : conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
437 : :
438 [ # # ]: 0 : if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
439 : 0 : return GST_RTSP_STS_GONE;
440 : :
441 [ # # ]: 0 : if (gst_apexraop_recv (conn->ctrl_sd, hreq,
442 : : GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
443 : 0 : return GST_RTSP_STS_GONE;
444 : :
445 : : {
446 : : int tmp;
447 : 0 : sscanf (hreq, "%*s %d", &tmp);
448 : 0 : res = (GstRTSPStatusCode) tmp;
449 : : }
450 : :
451 [ # # ]: 0 : if (res != GST_RTSP_STS_OK)
452 : 0 : return res;
453 : :
454 [ # # ]: 0 : if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
455 : 0 : return GST_RTSP_STS_DESTINATION_UNREACHABLE;
456 : :
457 : 0 : conn->data_sd_in.sin_family = AF_INET;
458 : 0 : conn->data_sd_in.sin_port = htons (conn->data_port);
459 : :
460 : 0 : memcpy (&conn->data_sd_in.sin_addr, &conn->ctrl_sd_in.sin_addr,
461 : : sizeof (conn->ctrl_sd_in.sin_addr));
462 : :
463 [ # # ]: 0 : if (connect (conn->data_sd, (struct sockaddr *) &conn->data_sd_in,
464 : : sizeof (conn->data_sd_in)) < 0)
465 : 0 : return GST_RTSP_STS_DESTINATION_UNREACHABLE;
466 : :
467 : 0 : return res;
468 : : }
469 : :
470 : : /* raop apex jack type access */
471 : : GstApExJackType
472 : 1 : gst_apexraop_get_jacktype (GstApExRAOP * con)
473 : : {
474 : : _GstApExRAOP *conn;
475 : :
476 : 1 : conn = (_GstApExRAOP *) con;
477 : :
478 [ + - ]: 1 : if (!conn)
479 : 1 : return GST_APEX_JACK_TYPE_UNDEFINED;
480 : :
481 : 1 : return conn->jack_type;
482 : : }
483 : :
484 : : /* raop apex jack status access */
485 : : GstApExJackStatus
486 : 1 : gst_apexraop_get_jackstatus (GstApExRAOP * con)
487 : : {
488 : : _GstApExRAOP *conn;
489 : :
490 : 1 : conn = (_GstApExRAOP *) con;
491 : :
492 [ + - ]: 1 : if (!conn)
493 : 1 : return GST_APEX_JACK_STATUS_UNDEFINED;
494 : :
495 : 1 : return conn->jack_status;
496 : : }
497 : :
498 : : /* raop apex sockets close */
499 : : void
500 : 0 : gst_apexraop_close (GstApExRAOP * con)
501 : : {
502 : : gchar hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH];
503 : : _GstApExRAOP *conn;
504 : :
505 : 0 : conn = (_GstApExRAOP *) con;
506 : :
507 : 0 : sprintf (hreq,
508 : : "TEARDOWN rtsp://%s/%s RTSP/1.0\r\n"
509 : : "CSeq: %d\r\n"
510 : : "Client-Instance: %s\r\n"
511 : : "User-Agent: %s\r\n"
512 : : "Session: %s\r\n"
513 : : "\r\n",
514 : : conn->host,
515 : 0 : conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
516 : :
517 : 0 : gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq));
518 : 0 : gst_apexraop_recv (conn->ctrl_sd, hreq, GST_APEX_RAOP_HDR_DEFAULT_LENGTH);
519 : :
520 [ # # ]: 0 : if (conn->ctrl_sd != 0)
521 : 0 : close (conn->ctrl_sd);
522 [ # # ]: 0 : if (conn->data_sd != 0)
523 : 0 : close (conn->data_sd);
524 : 0 : }
525 : :
526 : : /* raop apex volume set */
527 : : GstRTSPStatusCode
528 : 0 : gst_apexraop_set_volume (GstApExRAOP * con, const guint volume)
529 : : {
530 : : gint v;
531 : : gchar creq[GST_APEX_RAOP_SDP_DEFAULT_LENGTH],
532 : : hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH], *req, vol[128];
533 : : GstRTSPStatusCode res;
534 : : _GstApExRAOP *conn;
535 : :
536 : 0 : conn = (_GstApExRAOP *) con;
537 : :
538 : 0 : v = GST_APEX_RAOP_VOLUME_MIN + (GST_APEX_RAOP_VOLUME_MAX -
539 : 0 : GST_APEX_RAOP_VOLUME_MIN) * volume / 100.;
540 : 0 : sprintf (vol, "volume: %d.000000\r\n", v);
541 : :
542 : 0 : sprintf (creq, "%s\r\n", vol);
543 : :
544 : 0 : sprintf (hreq,
545 : : "SET_PARAMETER rtsp://%s/%s RTSP/1.0\r\n"
546 : : "CSeq: %d\r\n"
547 : : "Client-Instance: %s\r\n"
548 : : "User-Agent: %s\r\n"
549 : : "Session: %s\r\n"
550 : : "Content-Type: text/parameters\r\n"
551 : : "Content-Length: %u\r\n",
552 : : conn->host,
553 : 0 : conn->url_abspath,
554 : 0 : ++conn->cseq, conn->cid, conn->ua, conn->session, (guint) strlen (creq)
555 : : );
556 : :
557 : 0 : req = g_strconcat (hreq, "\r\n", creq, NULL);
558 : :
559 [ # # ]: 0 : if (gst_apexraop_send (conn->ctrl_sd, req, strlen (req)) <= 0) {
560 : 0 : g_free (req);
561 : 0 : return GST_RTSP_STS_GONE;
562 : : }
563 : :
564 : 0 : g_free (req);
565 : :
566 [ # # ]: 0 : if (gst_apexraop_recv (conn->ctrl_sd, hreq,
567 : : GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
568 : 0 : return GST_RTSP_STS_GONE;
569 : :
570 : : {
571 : : int tmp;
572 : 0 : sscanf (hreq, "%*s %d", &tmp);
573 : 0 : res = (GstRTSPStatusCode) tmp;
574 : : }
575 : :
576 : 0 : return res;
577 : : }
578 : :
579 : : /* raop apex raw data alac encapsulation, encryption and emission, http://wiki.multimedia.cx/index.php?title=Apple_Lossless_Audio_Coding */
580 : : static void inline
581 : 0 : gst_apexraop_write_bits (guchar * buffer, int data, int numbits,
582 : : int *bit_offset, int *byte_offset)
583 : : {
584 : : const static guchar masks[] =
585 : : { 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
586 : :
587 [ # # ][ # # ]: 0 : if (((*bit_offset) != 0) && (((*bit_offset) + numbits) > 8)) {
588 : : gint numwritebits;
589 : : guchar bitstowrite;
590 : :
591 : 0 : numwritebits = 8 - (*bit_offset);
592 : 0 : bitstowrite =
593 : 0 : (guchar) ((data >> (numbits - numwritebits)) << (8 - (*bit_offset) -
594 : : numwritebits));
595 : 0 : buffer[(*byte_offset)] |= bitstowrite;
596 : 0 : numbits -= numwritebits;
597 : 0 : (*bit_offset) = 0;
598 : 0 : (*byte_offset)++;
599 : : }
600 : :
601 [ # # ]: 0 : while (numbits >= 8) {
602 : : guchar bitstowrite;
603 : :
604 : 0 : bitstowrite = (guchar) ((data >> (numbits - 8)) & 0xFF);
605 : 0 : buffer[(*byte_offset)] |= bitstowrite;
606 : 0 : numbits -= 8;
607 : 0 : (*bit_offset) = 0;
608 : 0 : (*byte_offset)++;
609 : : }
610 : :
611 [ # # ]: 0 : if (numbits > 0) {
612 : : guchar bitstowrite;
613 : 0 : bitstowrite =
614 : 0 : (guchar) ((data & masks[numbits]) << (8 - (*bit_offset) - numbits));
615 : 0 : buffer[(*byte_offset)] |= bitstowrite;
616 : 0 : (*bit_offset) += numbits;
617 [ # # ]: 0 : if ((*bit_offset) == 8) {
618 : 0 : (*byte_offset)++;
619 : 0 : (*bit_offset) = 0;
620 : : }
621 : : }
622 : 0 : }
623 : :
624 : : guint
625 : 0 : gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
626 : : {
627 : : guchar *buffer, *frame_data;
628 : : gushort len;
629 : : gint bit_offset, byte_offset, i, out_len, res;
630 : : EVP_CIPHER_CTX aes_ctx;
631 : : _GstApExRAOP *conn;
632 : :
633 : 0 : conn = (_GstApExRAOP *) con;
634 : :
635 : 0 : buffer =
636 : 0 : (guchar *) g_malloc0 (GST_APEX_RAOP_FRAME_HEADER_SIZE +
637 : 0 : GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
638 : :
639 : 0 : memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, GST_APEX_RAOP_FRAME_HEADER_SIZE);
640 : :
641 : 0 : len =
642 : : length + GST_APEX_RAOP_FRAME_HEADER_SIZE +
643 : : GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
644 : 0 : buffer[2] = len >> 8;
645 : 0 : buffer[3] = len & 0xff;
646 : :
647 : 0 : bit_offset = 0;
648 : 0 : byte_offset = 0;
649 : 0 : frame_data = buffer + GST_APEX_RAOP_FRAME_HEADER_SIZE;
650 : :
651 : 0 : gst_apexraop_write_bits (frame_data, 1, 3, &bit_offset, &byte_offset); /* channels, 0 mono, 1 stereo */
652 : 0 : gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset); /* unknown */
653 : 0 : gst_apexraop_write_bits (frame_data, 0, 8, &bit_offset, &byte_offset); /* unknown (12 bits) */
654 : 0 : gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset);
655 : 0 : gst_apexraop_write_bits (frame_data, 0, 1, &bit_offset, &byte_offset); /* has size flag */
656 : 0 : gst_apexraop_write_bits (frame_data, 0, 2, &bit_offset, &byte_offset); /* unknown */
657 : 0 : gst_apexraop_write_bits (frame_data, 1, 1, &bit_offset, &byte_offset); /* no compression flag */
658 : :
659 [ # # ]: 0 : for (i = 0; i < length; i += 2) {
660 : 0 : gst_apexraop_write_bits (frame_data, ((guchar *) rawdata)[i + 1], 8,
661 : : &bit_offset, &byte_offset);
662 : 0 : gst_apexraop_write_bits (frame_data, ((guchar *) rawdata)[i], 8,
663 : : &bit_offset, &byte_offset);
664 : : }
665 : :
666 : 0 : EVP_CIPHER_CTX_init (&aes_ctx);
667 : 0 : EVP_CipherInit_ex (&aes_ctx, EVP_aes_128_cbc (), NULL, conn->aes_ky,
668 : 0 : conn->aes_iv, AES_ENCRYPT);
669 : 0 : EVP_CipherUpdate (&aes_ctx, frame_data, &out_len, frame_data, /*( */
670 : 0 : GST_APEX_RAOP_ALAC_HEADER_SIZE +
671 : : length /*) / AES_BLOCK_SIZE * AES_BLOCK_SIZE */ );
672 : 0 : EVP_CIPHER_CTX_cleanup (&aes_ctx);
673 : :
674 : 0 : res =
675 : 0 : gst_apexraop_send (conn->data_sd, buffer,
676 : 0 : GST_APEX_RAOP_FRAME_HEADER_SIZE + GST_APEX_RAOP_ALAC_HEADER_SIZE +
677 : : length);
678 : :
679 : 0 : g_free (buffer);
680 : :
681 [ # # ]: 0 : return (guint) ((res >=
682 : 0 : (GST_APEX_RAOP_FRAME_HEADER_SIZE +
683 : 0 : GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res -
684 : 0 : GST_APEX_RAOP_FRAME_HEADER_SIZE -
685 : : GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
686 : : }
687 : :
688 : : /* raop apex buffer flush */
689 : : GstRTSPStatusCode
690 : 0 : gst_apexraop_flush (GstApExRAOP * con)
691 : : {
692 : : gchar hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH];
693 : : GstRTSPStatusCode res;
694 : : _GstApExRAOP *conn;
695 : :
696 : 0 : conn = (_GstApExRAOP *) con;
697 : :
698 : 0 : sprintf (hreq,
699 : : "FLUSH rtsp://%s/%s RTSP/1.0\r\n"
700 : : "CSeq: %d\r\n"
701 : : "Client-Instance: %s\r\n"
702 : : "User-Agent: %s\r\n"
703 : : "Session: %s\r\n"
704 : : "RTP-Info: seq=0;rtptime=0\r\n"
705 : : "\r\n",
706 : : conn->host,
707 : 0 : conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
708 : :
709 [ # # ]: 0 : if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
710 : 0 : return GST_RTSP_STS_GONE;
711 : :
712 [ # # ]: 0 : if (gst_apexraop_recv (conn->ctrl_sd, hreq,
713 : : GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
714 : 0 : return GST_RTSP_STS_GONE;
715 : :
716 : : {
717 : : int tmp;
718 : 0 : sscanf (hreq, "%*s %d", &tmp);
719 : 0 : res = (GstRTSPStatusCode) tmp;
720 : : }
721 : :
722 : 0 : return res;
723 : : }
|