1 
2 /*
3  * Argyll Color Correction System
4  * ChromCast support.
5  *
6  * Author: Graeme W. Gill
7  * Date:   10/9/2014
8  *
9  * Copyright 2014 Graeme W. Gill
10  * All rights reserved.
11  *
12  * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
13  * see the License2.txt file for licencing details.
14  *
15  */
16 
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <math.h>
20 #include <signal.h>
21 #include <sys/types.h>
22 #include <time.h>
23 #include "copyright.h"
24 #include "aconfig.h"
25 #ifndef SALONEINSTLIB
26 #include "numlib.h"
27 #else
28 #include "numsup.h"
29 #endif
30 #include "conv.h"
31 #include "base64.h"
32 #include "yajl.h"
33 #include "ccpacket.h"
34 #include "ccmes.h"
35 #include "ccast.h"
36 
37 #undef DEBUG				/* [und] */
38 #undef USE_DEF_RECIEVER		/* [und] */
39 #undef CHECK_JSON			/* [und] */
40 
41 #ifdef DEBUG
42 # define DBG(xxx) a1logd xxx ;
43 #else
44 # define DBG(xxx) ;
45 #endif  /* DEBUG */
46 
47 #define START_TRIES 6
48 #define LOAD_TRIES 4
49 
50 /* - - - - - - - - - - - - - - - - - - - - - - - - */
51 
52 #ifdef CHECK_JSON
53 
54 /* Check if JSON is invalid */
55 /* Exits if invalid */
check_json(char * mesbuf)56 static void check_json(char *mesbuf) {
57 	yajl_val node;
58 	char errbuf[1024];
59 
60 	if ((node = yajl_tree_parse(mesbuf, errbuf, sizeof(errbuf))) == NULL) {
61 		fprintf(g_log,0,"yajl_tree_parse of send message failed with '%s'\n",errbuf);
62 		fprintf(g_log,0,"JSON = '%s'\n",mesbuf);
63 		exit(1);
64 	}
65 	yajl_tree_free(node);
66 }
67 #endif
68 
69 /* ============================================================ */
70 /* Receive thread */
71 
72 /* Read messages. If they are ones we deal with, send a reply */
73 /* If they are anonomous (sessionId == 0), then ignore them */
74 /* (Could save last known anonomous message if they prove useful) */
75 /* and if they are numbered, keep then in a sorted list. */
76 
cc_rec_thread(void * context)77 static int cc_rec_thread(void *context) {
78 	ccast *p = (ccast *)context;
79 	ccmessv *sv = p->messv;
80 	ccmessv_err merr;
81 	ccmes mes, smes;
82 	int errc = 0;
83 	char errbuf[1024];
84 	yajl_val tyn, idn;
85 	int rv = 0;
86 
87 	DBG((g_log,0,"ccthread starting\n"))
88 
89 	ccmes_init(&mes);
90 	ccmes_init(&smes);
91 
92 	/* Preset PONG message */
93 	smes.source_id      = "sender-0";
94 	smes.destination_id = "receiver-0";
95 	smes.namespace       = "urn:x-cast:com.google.cast.tp.heartbeat";
96 	smes.binary         = 0;
97 	smes.data   = (ORD8 *)"{ \"type\": \"PONG\" }";
98 
99 	for(;!p->stop;) {
100 
101 		if ((merr = sv->receive(sv, &mes)) != ccmessv_OK) {
102 			if (merr == ccmessv_timeout) {
103 				DBG((g_log,0,"ccthread: got receive timeout (OK)\n"))
104 				msec_sleep(100);
105 			} else {
106 				DBG((g_log,0,"ccthread: messv->receive failed with '%s'\n",ccmessv_emes(merr)))
107 				msec_sleep(100);
108 #ifdef NEVER
109 //This won't work - we need to re-join the session etc.,
110 				if (p->messv->pk->reconnect(p->messv->pk)) {
111 					DBG((g_log,0,"ccthread: reconnect after error failed\n"))
112 					rv = 1;
113 					break;
114 				}
115 #endif
116 				if (errc++ > 20) {		/* Too many failures */
117 					DBG((g_log,0,"ccthread: too many errors - giving up\n"))
118 					/* Hmm. The connection seems to have gone down ? */
119 					rv = 1;
120 					break;
121 				}
122 			}
123 			continue;
124 		}
125 
126 		errc = 0;
127 
128 		/* Got status query */
129 		if (mes.mtype != NULL && strcmp(mes.mtype, "CLOSE") == 0) {
130 			/* Hmm. That indicates an error */
131 
132 			DBG((g_log,0,"ccthread: got CLOSE message - giving up\n"))
133 			rv = 1;
134 			break;
135 
136 		} else if (mes.mtype != NULL && strcmp(mes.mtype, "PING") == 0) {
137 			if ((merr = sv->send(sv, &smes)) != ccmessv_OK) {
138 				DBG((g_log,0,"ccthread: send PONG failed with '%s'\n",ccmessv_emes(merr)))
139 			}
140 
141 		/* Got reply - add to linked list */
142 		} else {
143 			int found;
144 #ifdef DEBUG
145 			if (p->w_rq) {
146 				DBG((g_log,0,"ccthread: waiting for ns '%s' and mes->ns '%s'\n",
147 				                                 p->w_rqns, mes.namespace))
148 				DBG((g_log,0,"ccthread: waiting for id %d and mes->id %d\n",
149 				                                 p->w_rqid, mes.rqid))
150 			} else {
151 				DBG((g_log,0,"ccthread: has no client waiting\n"))
152 			}
153 #endif
154 
155 			/* Is it the one the client is waiting for ? */
156 			found = (p->w_rq != 0
157 				 && (p->w_rqns == NULL || strcmp(p->w_rqns, mes.namespace) == 0)
158 				 && (p->w_rqid == 0 || p->w_rqid == mes.rqid));
159 
160 			if (found || mes.rqid != 0) {
161 				ccmes *nmes;
162 				if ((nmes = (ccmes *)calloc(1, sizeof(ccmes))) == NULL) {
163 					DBG((g_log,0,"ccthread: calloc failed\n"))
164 				} else {
165 					ccmes_transfer(nmes, &mes);
166 
167 					DBG((g_log,0,"ccthread: adding message type '%s' id %d to list (found %d)\n",nmes->mtype,nmes->rqid,found))
168 					amutex_lock(p->rlock);	/* We're modifying p->rmes list */
169 					nmes->next = p->rmes;	/* Put at start of list */
170 					p->rmes = nmes;
171 
172 					/* Client is waiting for this */
173 					if (found) {
174 						DBG((g_log,0,"ccthread: client was waiting for this message\n"))
175 						acond_signal(p->rcond);
176 					}
177 					amutex_unlock(p->rlock);		/* We've finished modifying p->rmes list */
178 				}
179 			}
180 		}
181 
182 		/* Got anonomous status message */
183 		ccmes_empty(&mes);
184 	}
185 	DBG((g_log,0, "ccthread: about to exit - stop = %d\n",p->stop))
186 
187 	/* We're bailing out or stopping */
188 	p->stopped = 1;
189 
190 	/* Release client if it was waiting */
191 	amutex_lock(p->rlock);
192 	if (p->w_rq != 0) {
193 		DBG((g_log,0,"ccthread: client was waiting for message - abort it\n"))
194 		acond_signal(p->rcond);
195 	}
196 	amutex_unlock(p->rlock);
197 
198 	DBG((g_log,0,"ccthread returning %d\n",rv))
199 
200 	return rv;
201 }
202 
203 /* Wait for a specific message rqid on a specific channel. */
204 /* Use rqid = 0 to get first message rather than specific one. */
205 /* Use namespace = NULL to ignore channel */
206 /* Return 1 on an error */
207 /* Return 2 on a timeout */
get_a_reply_id(ccast * p,char * namespace,int rqid,ccmes * rmes,int to)208 static int get_a_reply_id(ccast *p, char *namespace, int rqid, ccmes *rmes, int to) {
209 	ccmes *nlist = NULL;
210 	ccmes *mes, *xmes, *fmes = NULL;
211 	int rv = 0;
212 
213 	DBG((g_log,0," get_a_reply_id getting namespace '%s' id %d\n",
214 	            namespace == NULL ? "(none)" : namespace, rqid))
215 
216 	amutex_lock(p->rlock);		/* We're modifying p->rmes list */
217 
218 	if (p->stop || p->stopped) {
219 		amutex_unlock(p->rlock);		/* Allow thread to modify p->rmes list */
220 		DBG((g_log,0," get_a_reply_id: thread is stopping or stopped\n"))
221 		return 1;
222 	}
223 
224 	/* Setup request to thread */
225 	p->w_rq   = 1;
226 	p->w_rqns = namespace;
227 	p->w_rqid = rqid;
228 
229 	/* Until we've got our message, we time out, or the thread is being stopped */
230 	for (;!p->stop && !p->stopped;) {
231 
232 		/* Check if the message has already been received */
233 		for (mes = p->rmes; mes != NULL; mes = xmes) {
234 			int ins = (namespace == NULL || strcmp(namespace, mes->namespace) == 0);
235 			xmes = mes->next;
236 			if (ins && rqid != 0 && mes->rqid < rqid) {
237 				ccmes_del(mes);			/* Too old - throw away */
238 			} else if (ins && (rqid == 0 || mes->rqid == rqid)) {
239 				fmes = mes;				/* The one we want */
240 			} else {
241 				mes->next = nlist;		/* Keep in list */
242 				nlist = mes;
243 			}
244 		}
245 		p->rmes = nlist;
246 
247 		if (fmes != NULL)
248 			break;					/* Got it */
249 
250 
251 #ifndef NEVER
252 		/* We need to wait until it turns up */
253 		/* Allow thread to modify p->rmes list and signal us */
254 		if (acond_timedwait(p->rcond, p->rlock, to) != 0) {
255 			DBG((g_log,0," get_a_reply_id timed out after %f secs\n",to/1000.0))
256 			rv = 2;
257 			break;
258 		}
259 #else
260 		acond_wait(p->rcond, p->rlock);
261 #endif
262 		DBG((g_log,0," get_a_reply_id got released\n"))
263 	}
264 	p->w_rq = 0;					/* We're not looking for anything now */
265 	amutex_unlock(p->rlock);		/* Allow thread to modify p->rmes list */
266 
267 	if (p->stop || p->stopped) {
268 		DBG((g_log,0," get_a_reply_id failed because thread is stopped or stopping\n"))
269 		ccmes_init(rmes);
270 		return 1;
271 	}
272 
273 	if (rv != 0) {
274 		DBG((g_log,0," get_a_reply_id returning error %d\n",rv))
275 	} else {
276 		ccmes_transfer(rmes, fmes);
277 		DBG((g_log,0," get_a_reply_id returning type '%s' id %d\n",rmes->mtype,rmes->rqid))
278 	}
279 
280 	return rv;
281 }
282 
283 /* ============================================================ */
284 
285 void ccast_delete_from_cleanup_list(ccast *p);
286 
287 /* Cleanup any created objects */
cleanup_ccast(ccast * p)288 static void cleanup_ccast(ccast *p) {
289 
290 	DBG((g_log,0," cleanup_ccast() called\n"))
291 
292 	p->stop = 1;		/* Tell the thread to exit */
293 
294 	/* Wait for thread (could use semaphore) */
295 	/* and then delete it */
296 	if (p->rmesth != NULL) {
297 
298 		while (!p->stopped) {
299 			msec_sleep(10);
300 		}
301 		p->rmesth->del(p->rmesth);
302 		p->rmesth = NULL;
303 	}
304 
305 	if (p->sessionId != NULL) {
306 		free(p->sessionId);
307 		p->sessionId = NULL;
308 	}
309 
310 	if (p->transportId != NULL) {
311 		free(p->transportId);
312 		p->transportId = NULL;
313 	}
314 
315 	p->mediaSessionId = 0;
316 
317 	if (p->messv != NULL) {
318 		p->messv->del(p->messv);
319 		p->messv = NULL;
320 	}
321 
322 	/* Clean up linked list */
323 	{
324 		ccmes *mes, *xmes;
325 		for (mes = p->rmes; mes != NULL; mes = xmes) {
326 			xmes = mes->next;
327 			ccmes_del(mes);
328 		}
329 		p->rmes = NULL;
330 	}
331 }
332 
333 /* Shut down the connection, in such a way that we can */
334 /* try and re-connect. */
shutdown_ccast(ccast * p)335 static void shutdown_ccast(ccast *p) {
336 	ccmes mes;
337 
338 	DBG((g_log,0," shutdown_ccast() called\n"))
339 
340 	ccmes_init(&mes);
341 
342 	p->stop = 1;		/* Tell the thread to exit */
343 
344 	/* Close the media channel */
345 	if (p->transportId != NULL && p->messv != NULL) {
346 		mes.source_id      = "sender-0";
347 		mes.destination_id = p->transportId;
348 		mes.namespace      = "urn:x-cast:com.google.cast.tp.connection";
349 		mes.binary         = 0;
350 		mes.data   = (ORD8 *)"{ \"type\": \"CLOSE\" }";
351 		p->messv->send(p->messv, &mes);
352 	}
353 
354 	/* Stop the application */
355 	if (p->sessionId != NULL && p->messv != NULL) {
356 		int reqid = ++p->requestId;
357 		char mesbuf[1024];
358 		sprintf(mesbuf, "{ \"requestId\": %d, \"type\": \"STOP\", \"sessionId\": \"%s\" }",
359 			reqid, p->sessionId);
360 		mes.source_id      = "sender-0";
361 		mes.destination_id = "receiver-0";
362 		mes.namespace      = "urn:x-cast:com.google.cast.receiver";
363 		mes.binary         = 0;
364 		mes.data   = (ORD8 *)mesbuf;
365 		p->messv->send(p->messv, &mes);
366 	}
367 
368 	/* Close the platform channel */
369 	if (p->messv != NULL) {
370 		mes.source_id      = "sender-0";
371 		mes.destination_id = "receiver-0";
372 		mes.namespace      = "urn:x-cast:com.google.cast.receiver";
373 		mes.binary         = 0;
374 		mes.data   = (ORD8 *)"{ \"type\": \"CLOSE\" }";
375 		p->messv->send(p->messv, &mes);
376 	}
377 
378 	cleanup_ccast(p);
379 }
380 
del_ccast(ccast * p)381 static void del_ccast(ccast *p) {
382 	if (p != NULL) {
383 
384 		shutdown_ccast(p);
385 
386 		amutex_del(p->rlock);
387 		acond_del(p->rcond);
388 
389 		free(p);
390 	}
391 }
392 
393 static int load_ccast(ccast *p, char *url, unsigned char *ibuf, size_t ilen,
394                       double bg[3], double x, double y, double w, double h);
395 void ccast_install_signal_handlers(ccast *p);
396 
397 /* Startup a ChromCast session */
398 /* Return nz on error */
start_ccast(ccast * p)399 static int start_ccast(ccast *p) {
400 	ccpacket *pk = NULL;
401 	ccpacket_err perr;
402 	ccmessv_err merr;
403 	ccmes mes, rmes;
404 	char mesbuf[1024];
405 	int reqid, tries, maxtries = START_TRIES;
406 	char *connection_chan = "urn:x-cast:com.google.cast.tp.connection";
407 	char *heartbeat_chan = "urn:x-cast:com.google.cast.tp.heartbeat";
408 	char *receiver_chan = "urn:x-cast:com.google.cast.receiver";
409 
410 	/* Try this a few times if we fail in some way */
411 	for (tries = 0; tries < maxtries; tries++) {
412 		int app = 0, naps = 2;
413 
414 		/* Use the default receiver rather than pattern generator */
415 		if (p->forcedef || getenv("ARGYLL_CCAST_DEFAULT_RECEIVER") != NULL)
416 			app = 1;
417 
418 		ccmes_init(&mes);
419 		ccmes_init(&rmes);
420 
421 		p->stop = 0;
422 		p->stopped = 0;
423 //		p->requestId = 0;
424 
425 		amutex_init(p->rlock);
426 		acond_init(p->rcond);
427 
428 		/* Hmm. Could put creation of pk inside new_ccmessv() ? */
429 		if ((pk = new_ccpacket()) == NULL) {
430 			DBG((g_log,0,"start_ccast: new_ccpacket() failed\n"))
431 			goto retry;
432 		}
433 
434 		if ((perr = pk->connect(pk, p->id.ip, 8009)) != ccpacket_OK) {
435 			DBG((g_log,0,"start_ccast: ccpacket connect failed with '%s'\n",ccpacket_emes(perr)))
436 			goto retry;
437 		}
438 
439 		DBG((g_log,0,"Got TLS connection to '%s\n'",p->id.name))
440 
441 		if ((p->messv = new_ccmessv(pk)) == NULL) {
442 			DBG((g_log,0,"start_ccast: new_ccmessv() failed\n"))
443 			goto retry;
444 		}
445 		pk = NULL;		/* Will get deleted with messv now */
446 
447 		/* Attempt a connection */
448 		mes.source_id      = "sender-0";
449 		mes.destination_id = "receiver-0";
450 		mes.namespace      = connection_chan;
451 		mes.binary         = 0;
452 		mes.data   = (ORD8 *)"{ \"type\": \"CONNECT\" }";
453 		if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
454 			DBG((g_log,0,"start_ccast: CONNECT failed with '%s'\n",ccmessv_emes(merr)))
455 			goto retry;
456 		}
457 
458 		/* Start the thread. */
459 		/* We don't want to start this until the TLS negotiations and */
460 		/* the synchronous ssl_readi()'s it uses are complete. */
461 		if ((p->rmesth = new_athread(cc_rec_thread, (void *)p)) == NULL) {
462 			DBG((g_log,0,"start_ccast: creating message thread failed\n"))
463 			goto retry;
464 		}
465 
466 #ifdef NEVER
467 		/* Send a ping */
468 		mes.namespace      = heartbeat_chan;
469 		mes.data   = (ORD8 *)"{ \"type\": \"PING\" }";
470 		if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
471 			DBG((g_log,0,"start_ccast: PING failed with '%s'\n",ccmessv_emes(merr)))
472 			return 1;
473 		}
474 
475 		/* Wait for a PONG */
476 //	get_a_reply(p->messv, NULL);
477 #endif
478 
479 		/* Try and find an app we can work with */
480 		for (; app < naps; app++) {
481 			char *appid;
482 
483 			reqid = ++p->requestId;
484 
485 			if (app == 0) {
486 				appid = "B5C2CBFC";		/* Pattern generator reciever */
487 				p->patgenrcv = 1;
488 				p->load_delay = 350.0;
489 			} else {
490 				appid = "CC1AD845";		/* Default Receiver */
491 				p->patgenrcv = 0;
492 				p->load_delay = 1500.0;	/* Actually about 600msec, but fade produces a soft */
493 					/* transition that instrument meas_delay() doesn't cope with accurately. */
494 			}
495 
496 			/* Attempt to launch the Default receiver */
497 			sprintf(mesbuf, "{ \"requestId\": %d, \"type\": \"LAUNCH\", \"appId\": \"%s\" }",
498 			              reqid, appid);
499 
500 			DBG((g_log,0,"start_ccast: about to do LAUNCH\n"))
501 			/* Launch the default application */
502 			/* (Presumably we would use the com.google.cast.receiver channel */
503 			/*  for monitoring and controlling the reciever) */
504 			mes.namespace      = receiver_chan;
505 			mes.data   = (ORD8 *)mesbuf;
506 			if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
507 				DBG((g_log,0,"start_ccast: LAUNCH failed with '%s'\n",ccmessv_emes(merr)))
508 				goto retry;
509 			}
510 
511 			/* Receive the RECEIVER_STATUS status messages until it is ready to cast */
512 			/* and get the sessionId and transportId */
513 			/* We get periodic notification messages (requestId=0) as well as */
514 			/* a response messages to our requestId */
515 
516 			/* Wait for a reply to the LAUNCH (15 sec) */
517 			if (get_a_reply_id(p, receiver_chan, reqid, &rmes, 15000) != 0) {
518 				DBG((g_log,0,"start_ccast: LAUNCH failed to get a reply\n"))
519 				goto retry;
520 			}
521 
522 			if (rmes.mtype != NULL
523 			 && strcmp(rmes.mtype, "RECEIVER_STATUS") == 0
524 			 && rmes.tnode != NULL) {
525 				break;			/* Launched OK */
526 			}
527 
528 			if (rmes.mtype == NULL
529 			 || strcmp(rmes.mtype, "LAUNCH_ERROR") != 0
530 			 || rmes.tnode == NULL
531 			 || (app+1) >= naps) {
532 				DBG((g_log,0,"start_ccast: LAUNCH failed to get a RECEIVER_STATUS or LAUNCH ERROR reply\n"))
533 				ccmes_empty(&rmes);
534 				goto retry;
535 			}
536 
537 			/* Try the next application */
538 		}
539 
540 		DBG((g_log,0,"start_ccast: LAUNCH soceeded, load delay = %d msec\n",p->load_delay))
541 		{
542 			yajl_val idn, tpn;
543 			if ((idn = yajl_tree_get_first(rmes.tnode, "sessionId", yajl_t_string)) == NULL
544 			 || (tpn = yajl_tree_get_first(rmes.tnode, "transportId", yajl_t_string)) == NULL) {
545 				DBG((g_log,0,"start_ccast: LAUNCH failed to get sessionId & transportId\n"))
546 				ccmes_empty(&rmes);
547 				goto retry;
548 			}
549 			p->sessionId = YAJL_GET_STRINGDUP(idn);
550 			p->transportId = YAJL_GET_STRINGDUP(tpn);
551 			if (p->sessionId == NULL || p->transportId == NULL) {
552 				DBG((g_log,0,"start_ccast: strdup failed\n"))
553 				ccmes_empty(&rmes);
554 				goto retry;
555 			}
556 		}
557 		ccmes_empty(&rmes);
558 
559 		DBG((g_log,0,"### Got sessionId = '%s', transportId = '%s'\n",p->sessionId, p->transportId))
560 
561 		/* Connect up to the reciever media channels */
562 		mes.destination_id = p->transportId;
563 		mes.namespace      = connection_chan;
564 		mes.data   = (ORD8 *)"{ \"type\": \"CONNECT\" }";
565 		if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
566 			DBG((g_log,0,"messv->send CONNECT failed with '%s'\n",ccmessv_emes(merr)))
567 			goto retry;
568 		}
569 
570 		// Hmm. Should we wait for a RECEIVER_STATUS message with id 0 here ?
571 
572 		/* If we get here, we assume that we've suceeded */
573 		break;
574 
575       retry:;
576 
577 		/* If we get here, we're going around again, so start again from scratch */
578 		if (pk != NULL) {
579 			pk->del(pk);
580 			pk = NULL;
581 		}
582 
583 		cleanup_ccast(p);
584 
585 	}
586 
587 	if (tries >= maxtries) {
588 		DBG((g_log,0,"Failed to start ChromeCast\n"))
589 		return 1;
590 	}
591 
592 	DBG((g_log,0,"Succeeded in starting ChromeCast\n"))
593 
594 	ccast_install_signal_handlers(p);
595 
596 	return 0;
597 }
598 
599 
600 
601 /* Get the extra load delay */
get_load_delay(ccast * p)602 static int get_load_delay(ccast *p) {
603 	return p->load_delay;
604 }
605 
606 /* Return nz if we can send PNG directly as base64 + bg RGB, */
607 /* else have to setup webserver and send URL */
get_direct_send(ccast * p)608 static int get_direct_send(ccast *p) {
609 	return p->patgenrcv;
610 }
611 
612 /* Create a new ChromeCast */
613 /* Return NULL on error */
new_ccast(ccast_id * id,int forcedef)614 ccast *new_ccast(ccast_id *id,
615 int forcedef) {				// Force default reciever
616 	ccast *p = NULL;
617 
618 	if ((p = (ccast *)calloc(1, sizeof(ccast))) == NULL) {
619 		DBG((g_log,0, "new_ccast: calloc failed\n"))
620 		return NULL;
621 	}
622 
623 	/* Init method pointers */
624 	p->del             = del_ccast;
625 	p->load            = load_ccast;
626 	p->shutdown        = shutdown_ccast;
627 	p->get_load_delay  = get_load_delay;
628 	p->get_direct_send = get_direct_send;
629 
630 #ifdef USE_DEF_RECIEVER
631 	forcedef = 1;
632 #endif
633 	p->forcedef = forcedef;
634 
635 	ccast_id_copy(&p->id, id);
636 
637 	/* Establish communications */
638 	if (start_ccast(p)) {
639 		del_ccast(p);
640 		return NULL;
641 	}
642 
643 	return p;
644 }
645 
646 /* Load up a URL */
647 /* Returns nz on error: */
648 /*	1 send error */
649 /*	2 receieve error */
650 /*	3 invalid player state/load failed/load cancelled */
load_ccast(ccast * p,char * url,unsigned char * ibuf,size_t ilen,double bg[3],double x,double y,double w,double h)651 static int load_ccast(
652 	ccast *p,
653 	char *url,						/* URL to load, NULL if sent in-line */
654 	unsigned char *ibuf, size_t ilen,/* PNG image to load, NULL if url */
655 	double bg[3],					/* Background color RGB */
656 	double x, double y,				/* Window location and size as prop. of display */
657 	double w, double h				/* Size as multiplier of default 10% width */
658 ) {
659 	ccmessv_err merr;
660 	int reqid, firstid, lastid;
661 	ccmes mes;
662 	char *media_chan = "urn:x-cast:com.google.cast.media";
663 	char *direct_chan = "urn:x-cast:net.hoech.cast.patterngenerator";
664 	char *receiver_chan = "urn:x-cast:com.google.cast.receiver";
665 //	char *player_message_chan = "urn:x-cast:com.google.cast.player.message";
666 	int dchan = 0;		/* Using direct channel */
667 	int i, maxtries = LOAD_TRIES, rv = 0;
668 
669 	ccmes_init(&mes);
670 
671 	/* Retry loop */
672 	for (i = 0; ; i++) {
673 		unsigned char *iibuf = ibuf;
674 		size_t iilen = ilen;
675 		firstid = lastid = reqid = ++p->requestId;
676 
677 		DBG((g_log,0,"##### load_ccast try %d/%d\n",i+1,maxtries))
678 
679 		if (p->messv == NULL) {
680 			DBG((g_log,0,"mes->send LOAD failed due to lost connection\n"))
681 
682 		} else {
683 
684 			if (url == NULL && !p->patgenrcv) {
685 				DBG((g_log,0,"mes->send not given URL\n"))
686 				return 1;
687 			}
688 
689 			/* Send the LOAD URL command */
690 			if (url != NULL) {
691 				char *xl, mesbuf[1024];
692 
693 				xl = strrchr(url, '.');		// Get extension location
694 
695 				if (xl != NULL && stricmp(xl, ".webm") == 0) {
696 					sprintf(mesbuf, "{ \"requestId\": %d, \"type\": \"LOAD\", \"media\": "
697 					                "{ \"contentId\": \"%s\",", reqid, url);
698 					strcat(mesbuf,
699 					  "\"contentType\": \"video/webm\" },"
700 					  "\"autplay\": \"true\" }");
701 
702 				} else if (xl != NULL && stricmp(xl, ".mp4") == 0) {
703 					sprintf(mesbuf, "{ \"requestId\": %d, \"type\": \"LOAD\", \"media\": "
704 					                "{ \"contentId\": \"%s\",", reqid, url);
705 					strcat(mesbuf,
706 					  "\"contentType\": \"video/mp4\" },"
707 					  "\"autplay\": \"true\" }");
708 
709 				} else {	/* Assume PNG */
710 					sprintf(mesbuf, "{ \"requestId\": %d, \"type\": \"LOAD\", \"media\": "
711 					                "{ \"contentId\": \"%s\",", reqid, url);
712 					strcat(mesbuf,
713 					"\"contentType\": \"image/png\" } }");
714 				}
715 
716 				mes.source_id      = "sender-0";
717 				mes.destination_id = p->transportId;
718 				mes.namespace      = media_chan;
719 				mes.binary         = 0;
720 				mes.data   = (ORD8 *)mesbuf;
721 #ifdef CHECK_JSON
722 				check_json((char *)mes.data);
723 #endif
724 				if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
725 					DBG((g_log,0,"mes->send LOAD failed with '%s'\n",ccmessv_emes(merr)))
726 					rv = 1;
727 					goto retry;		/* Failed */
728 				}
729 
730 #ifdef NEVER
731 			/* Send base64 PNG image & background color definition in one message */
732 			/* This will fail if the message is > 64K */
733 			} else if (iibuf != NULL) {
734 				char *mesbuf, *cp;
735 				int dlen;
736 
737 				if ((mesbuf = malloc(1024 + EBASE64LEN(iilen))) == NULL) {
738 					DBG((g_log,0,"mes->send malloc failed\n"))
739 					return 1;
740 				}
741 
742 				cp = mesbuf;
743 				cp += sprintf(cp, "{ \"requestId\": %d, \"type\": \"LOAD\", \"media\": "
744 				"{ \"contentId\": \"data:image/png;base64,",reqid);
745 				ebase64(&dlen, cp, iibuf, iilen);
746 				cp += dlen;
747 				DBG((g_log,0,"base64 encoded PNG = %d bytes\n",dlen))
748 				sprintf(cp, "|rgb(%d, %d, %d)|%f|%f|%f|%f\","
749 				"\"streamType\": \"LIVE\",\"contentType\": \"text/plain\" } }",
750 				(int)(bg[0] * 255.0 + 0.5), (int)(bg[1] * 255.0 + 0.5), (int)(bg[2] * 255.0 + 0.5),
751 				x, y, w, h);
752 
753 				mes.source_id      = "sender-0";
754 				mes.destination_id = p->transportId;
755 				mes.namespace      = media_chan;
756 				mes.binary         = 0;
757 				mes.data   = (ORD8 *)mesbuf;
758 #ifdef CHECK_JSON
759 				check_json((char *)mes.data);
760 #endif
761 				if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
762 					DBG((g_log,0,"mes->send LOAD failed with '%s'\n",ccmessv_emes(merr)))
763 					free(mesbuf);
764 					rv = 1;
765 					goto retry;		/* Failed */
766 				}
767 				free(mesbuf);
768 
769 #else	/* !NEVER */
770 			/* Send base64 PNG image & background color definition in multiple packets */
771 			} else if (iibuf != NULL) {
772 				char *mesbuf, *cp;
773 				size_t maxlen, meslen;
774 				size_t enclen, senclen = 0;
775 				int dlen;			/* Encoded length to send */
776 
777 				if ((mesbuf = malloc(61 * 1024)) == NULL) {
778 					DBG((g_log,0,"mes->send malloc failed\n"))
779 					return 1;
780 				}
781 
782 				dchan = 1;
783 
784 				enclen = EBASE64LEN(ilen);			/* Encoded length of whole image */
785 				maxlen = DBASE64LEN(60 * 1024);		/* Maximum image bytes to send per message */
786 				meslen = iilen;
787 				if (meslen > maxlen)
788 					meslen = maxlen;
789 
790 				cp = mesbuf;
791 				cp += sprintf(cp, "{ ");
792 				cp += sprintf(cp, "\"requestId\": %d,",reqid);
793 				cp += sprintf(cp, "\"foreground\": { ");
794 				cp += sprintf(cp, "\"contentType\": \"image/png\",");
795 				cp += sprintf(cp, "\"encoding\": \"base64\",");
796 				cp += sprintf(cp, "\"data\": \"");
797 				ebase64(&dlen, cp, iibuf, meslen);
798 				DBG((g_log,0,"part base64 encoded PNG = %d bytes\n",dlen))
799 				iibuf += meslen;
800 				iilen -= meslen;
801 				senclen += dlen;
802 				cp += dlen;
803 				cp += sprintf(cp, "\",");
804 				cp += sprintf(cp, "\"size\": %lu",(unsigned long)EBASE64LEN(ilen));
805 				cp += sprintf(cp, " },");
806 
807 				cp += sprintf(cp, "\"background\": \"rgb(%d, %d, %d)\",",
808 				                               (int)(bg[0] * 255.0 + 0.5),
809 				                               (int)(bg[1] * 255.0 + 0.5),
810 				                               (int)(bg[2] * 255.0 + 0.5));
811 
812 				cp += sprintf(cp, "\"offset\": [%f, %f],",x,y);
813 				cp += sprintf(cp, "\"scale\": [%f, %f]",w,h);
814 				cp += sprintf(cp, " }");
815 
816 				mes.source_id      = "sender-0";
817 				mes.destination_id = p->transportId;
818 				mes.namespace      = direct_chan;
819 				mes.binary         = 0;
820 				mes.data   = (ORD8 *)mesbuf;
821 #ifdef CHECK_JSON
822 				check_json((char *)mes.data);
823 #endif
824 
825 				if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
826 					DBG((g_log,0,"mes->send LOAD failed with '%s'\n",ccmessv_emes(merr)))
827 					free(mesbuf);
828 					rv = 1;
829 					goto retry;		/* Failed */
830 				}
831 
832 				/* If we didn't send the whole image in one go */
833 				while (iilen >  0) {
834 
835 					/* Send the next chunk */
836 					meslen = iilen;
837 					if (meslen > maxlen)
838 						meslen = maxlen;
839 
840 					DBG((g_log,0,"Sending %d bytes of %d remaining in image\n",meslen,iilen))
841 
842 					lastid = reqid = ++p->requestId;
843 
844 					cp = mesbuf;
845 					cp += sprintf(cp, "{ ");
846 					cp += sprintf(cp, "\"requestId\": %d,",reqid);
847 					cp += sprintf(cp, "\"foreground\": \"");
848 					ebase64(&dlen, cp, iibuf, meslen);
849 					DBG((g_log,0,"part base64 encoded PNG = %d bytes\n",dlen))
850 					iibuf += meslen;
851 					iilen -= meslen;
852 					senclen += dlen;
853 
854 
855 					cp += dlen;
856 					cp += sprintf(cp, "\" }");
857 
858 					mes.source_id      = "sender-0";
859 					mes.destination_id = p->transportId;
860 					mes.namespace      = direct_chan;
861 					mes.binary         = 0;
862 					mes.data   = (ORD8 *)mesbuf;
863 #ifdef CHECK_JSON
864 					check_json((char *)mes.data);
865 #endif
866 					if ((merr = p->messv->send(p->messv, &mes)) != ccmessv_OK) {
867 						DBG((g_log,0,"mes->send LOAD failed with '%s'\n",ccmessv_emes(merr)))
868 						free(mesbuf);
869 						rv = 1;
870 						goto retry;		/* Failed */
871 					}
872 
873 					if ((lastid - firstid) > 0) {
874 
875 						/* Wait for an ACK, to make sure the channel doesn't get choked up */
876 						if (get_a_reply_id(p, direct_chan, firstid, &mes, 10000) != 0) {
877 							DBG((g_log,0,"load_ccast: failed to get reply\n"))
878 							rv = 2;
879 							goto retry;		/* Failed */
880 						}
881 						if (mes.mtype == NULL) {
882 							DBG((g_log,0,"load_ccast: mtype == NULL\n"))
883 							rv = 2;
884 							goto retry;		/* Failed */
885 
886 						} else if (dchan && strcmp(mes.mtype, "ACK") == 0) {
887 							DBG((g_log,0,"load_ccast: got ACK\n"))
888 
889 						} else if (dchan && strcmp(mes.mtype, "NACK") == 0) {
890 							/* Failed. Get error status */
891 							yajl_val errors;
892 							if ((errors = yajl_tree_get_first(mes.tnode, "errors", yajl_t_array)) != NULL) {
893 								DBG((g_log,0,"NACK returned errors:\n"))
894 								if (YAJL_IS_ARRAY(errors)) {
895 									for (i = 0; i < errors->u.array.len; i++) {
896 										yajl_val error = errors->u.array.values[i];
897 										DBG((g_log,0,"%s\n",error->u.string))
898 									}
899 
900 								} else {
901 									DBG((g_log,0,"NACK errors is not an array!\n"))
902 								}
903 							} else {
904 								DBG((g_log,0,"NACK failed to return errors\n"))
905 							}
906 							rv = 2;
907 							goto retry;		/* Failed */
908 
909 						} else {
910 							rv = 3;
911 							DBG((g_log,0,"load_ccast: got mtype '%s'\n",mes.mtype))
912 							goto retry;		/* Failed */
913 						}
914 						ccmes_empty(&mes);
915 						firstid++;
916 					}
917 				};
918 				free(mesbuf);
919 
920 				/* This would be bad... */
921 				if (iilen == 0 && senclen != enclen)
922 					fprintf(stderr,"ccast load finished but senclen %lu != enclen %lu\n",
923 					                        (unsigned long)senclen,(unsigned long)enclen);
924 #endif /* !NEVER */
925 
926 			} else {
927 				DBG((g_log,0,"mes->send not given URL or png data\n"))
928 				return 1;
929 			}
930 		}
931 
932 		/* Wait for a reply to each load message */
933 		for (reqid = firstid; reqid <= lastid; reqid++) {
934 
935 			if (get_a_reply_id(p, dchan ? direct_chan : media_chan, reqid, &mes, 5000) != 0) {
936 				DBG((g_log,0,"load_ccast: failed to get reply\n"))
937 				rv = 2;
938 				goto retry;		/* Failed */
939 			}
940 			/* Reply could be:
941 				MEDIA_STATUS
942 				INVALID_PLAYER_STATE,
943 				LOAD_FAILED,
944 				LOAD_CANCELLED
945 
946 				For net.hoech.cast.patterngenerator
947 				ACK,
948 				NACK
949 		 	*/
950 			if (mes.mtype == NULL) {
951 				DBG((g_log,0,"load_ccast: mtype == NULL\n"))
952 				rv = 2;
953 				goto retry;		/* Failed */
954 
955 			} else if (dchan && strcmp(mes.mtype, "ACK") == 0) {
956 				DBG((g_log,0,"load_ccast: got ACK\n"))
957 
958 			} else if (dchan && strcmp(mes.mtype, "NACK") == 0) {
959 				/* Failed. Get error status */
960 				yajl_val errors;
961 				if ((errors = yajl_tree_get_first(mes.tnode, "errors", yajl_t_array)) != NULL) {
962 					DBG((g_log,0,"NACK returned errors:\n"))
963 					if (YAJL_IS_ARRAY(errors)) {
964 						for (i = 0; i < errors->u.array.len; i++) {
965 							yajl_val error = errors->u.array.values[i];
966 							DBG((g_log,0,"%s\n",error->u.string))
967 						}
968 
969 					} else {
970 						DBG((g_log,0,"NACK errors is not an array!\n"))
971 					}
972 				} else {
973 					DBG((g_log,0,"NACK failed to return errors\n"))
974 				}
975 				rv = 2;
976 				goto retry;		/* Failed */
977 
978 			} else if (strcmp(mes.mtype, "MEDIA_STATUS") == 0) {
979 				yajl_val node, i;
980 				if ((i = yajl_tree_get_first(mes.tnode, "mediaSessionId", yajl_t_number)) != NULL) {
981 					p->mediaSessionId = YAJL_GET_INTEGER(i);
982 					DBG((g_log,0,"MEDIA_STATUS returned mediaSessionId %d\n",p->mediaSessionId))
983 				} else {
984 					DBG((g_log,0,"MEDIA_STATUS failed to return mediaSessionId\n"))
985 				}
986 				/* Suceeded */
987 
988 			} else {
989 				rv = 3;
990 				DBG((g_log,0,"load_ccast: got mtype '%s'\n",mes.mtype))
991 				goto retry;		/* Failed */
992 			}
993 			ccmes_empty(&mes);
994 		}
995 		/* If we got here, we succeeded */
996 		break;
997 
998 	  retry:;
999 
1000 //		if (!p->loaded1 || (i+1) >= maxtries)
1001 		if ((i+1) >= maxtries) {
1002 			return rv;		/* Too many tries - give up */
1003 		}
1004 
1005 		DBG((g_log,0,"load_ccast: failed on try %d/%d - re-connecting to chrome cast\n",i+1,maxtries))
1006 		shutdown_ccast(p);	/* Tear connection down */
1007 		if (start_ccast(p)) {	/* Set it up again */
1008 			DBG((g_log,0,"load_ccast: re-connecting failed\n"))
1009 			return 1;
1010 		}
1011 		/* And retry */
1012 		rv = 0;
1013 	}	/* Retry loop */
1014 
1015 	/* Success */
1016 	p->loaded1 = 1;		/* Loaded at least once */
1017 
1018 	/* Currently there is a 1.5 second fade up delay imposed by */
1019 	/* the base ChromeCast software or default receiver. */
1020 	if (p->load_delay > 0.0)
1021 		msec_sleep(p->load_delay);
1022 
1023 	return rv;
1024 }
1025 
1026 /*  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
1027 
1028 /* Static list so that all open ChromCast connections can be closed on a SIGKILL */
1029 static ccast *ccast_list = NULL;
1030 
1031 /* Clean up any open ChromeCast connections */
ccast_cleanup()1032 static void ccast_cleanup() {
1033 	ccast *pp, *np;
1034 
1035 	for (pp = ccast_list; pp != NULL; pp = np) {
1036 		np = pp->next;
1037 		a1logd(g_log, 2, "ccast_cleanup: closing 0x%x\n",pp);
1038 		pp->shutdown(pp);
1039 	}
1040 }
1041 
1042 #ifdef NT
1043 static void (__cdecl *ccast_int)(int sig) = SIG_DFL;
1044 static void (__cdecl *ccast_term)(int sig) = SIG_DFL;
1045 #endif
1046 #ifdef UNIX
1047 static void (*ccast_hup)(int sig) = SIG_DFL;
1048 static void (*ccast_int)(int sig) = SIG_DFL;
1049 static void (*ccast_term)(int sig) = SIG_DFL;
1050 #endif
1051 
1052 /* On something killing our process, deal with USB cleanup */
ccast_sighandler(int arg)1053 static void ccast_sighandler(int arg) {
1054 	static amutex_static(lock);
1055 
1056 	a1logd(g_log, 2, "ccast_sighandler: invoked with arg = %d\n",arg);
1057 
1058 	/* Make sure we don't re-enter */
1059 	if (amutex_trylock(lock)) {
1060 		return;
1061 	}
1062 
1063 	ccast_cleanup();
1064 	a1logd(g_log, 2, "ccast_sighandler: done ccast_sighandler()\n");
1065 
1066 	/* Call the existing handlers */
1067 #ifdef UNIX
1068 	if (arg == SIGHUP && ccast_hup != SIG_DFL && ccast_hup != SIG_IGN)
1069 		ccast_hup(arg);
1070 #endif /* UNIX */
1071 	if (arg == SIGINT && ccast_int != SIG_DFL && ccast_int != SIG_IGN)
1072 		ccast_int(arg);
1073 	if (arg == SIGTERM && ccast_term != SIG_DFL && ccast_term != SIG_IGN)
1074 		ccast_term(arg);
1075 
1076 	a1logd(g_log, 2, "ccast_sighandler: calling exit()\n");
1077 
1078 	amutex_unlock(lock);
1079 	exit(0);
1080 }
1081 
1082 
1083 /* Install the cleanup signal handlers */
ccast_install_signal_handlers(ccast * p)1084 void ccast_install_signal_handlers(ccast *p) {
1085 
1086 	if (ccast_list == NULL) {
1087 		a1logd(g_log, 2, "ccast_install_signal_handlers: called\n");
1088 #if defined(UNIX)
1089 		ccast_hup = signal(SIGHUP, ccast_sighandler);
1090 #endif /* UNIX */
1091 		ccast_int = signal(SIGINT, ccast_sighandler);
1092 		ccast_term = signal(SIGTERM, ccast_sighandler);
1093 	}
1094 
1095 	/* Add it to our static list, to allow automatic cleanup on signal */
1096 	p->next = ccast_list;
1097 	ccast_list = p;
1098 	a1logd(g_log, 6, "ccast_install_signal_handlers: done\n");
1099 }
1100 
1101 /* Delete an ccast from our static signal cleanup list */
ccast_delete_from_cleanup_list(ccast * p)1102 void ccast_delete_from_cleanup_list(ccast *p) {
1103 
1104 	/* Find it and delete it from our static cleanup list */
1105 	if (ccast_list != NULL) {
1106 		a1logd(g_log, 6, "ccast_install_signal_handlers: called\n");
1107 		if (ccast_list == p) {
1108 			ccast_list = p->next;
1109 			if (ccast_list == NULL) {
1110 #if defined(UNIX)
1111 				signal(SIGHUP, ccast_hup);
1112 #endif /* UNIX */
1113 				signal(SIGINT, ccast_int);
1114 				signal(SIGTERM, ccast_term);
1115 			}
1116 		} else {
1117 			ccast *pp;
1118 			for (pp = ccast_list; pp != NULL; pp = pp->next) {
1119 				if (pp->next == p) {
1120 					pp->next = p->next;
1121 					break;
1122 				}
1123 			}
1124 		}
1125 		a1logd(g_log, 6, "ccast_install_signal_handlers: done\n");
1126 	}
1127 }
1128 
1129 /*  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
1130 /* Quantization model of ChromCast and TV */
1131 /* ctx is a placeholder, and can be NULL */
1132 
1133 #define QUANT(xx, qq) (floor((xx) * (qq) + 0.5)/(qq))
1134 
1135 /* Input & output RGB 0.0 - 1.0 levels */
ccastQuant(void * ctx,double out[3],double in[3])1136 void ccastQuant(void *ctx, double out[3], double in[3]) {
1137 	double r = in[0], g = in[1], b = in[2];
1138 	double Y, Cb, Cr;
1139 
1140 double or = r, og = g, ob = b;
1141 //	printf("ccastQuant: %f %f %f",r,g,b);
1142 
1143 	/* Scale RGB to 8 bit then quantize, since that's the limit of the frame buffer */
1144 	r = floor(r * 255.0 + 0.5);
1145 	g = floor(g * 255.0 + 0.5);
1146 	b = floor(b * 255.0 + 0.5);
1147 
1148 	/* ChromCast hardware seems to use 9 bit coeficients */
1149 	Y = r * QUANT((235.0 - 16.0) * 0.2126/255.0, 512.0)
1150 	  + g * QUANT((235.0 - 16.0) * 0.7152/255.0, 512.0)
1151 	  + b * QUANT((235.0 - 16.0) * 0.0722/255.0, 512.0);
1152 
1153 	Cb = r * -QUANT((240.0 - 16.0) *        0.2126 /(255.0 * 1.8556), 512.0)
1154 	   + g * -QUANT((240.0 - 16.0) *        0.7152 /(255.0 * 1.8556), 512.0)
1155 	   + b *  QUANT((240.0 - 16.0) * (1.0 - 0.0722)/(255.0 * 1.8556), 512.0);
1156 
1157 	Cr = r *  QUANT((240.0 - 16.0) * (1.0 - 0.2126)/(255.0 * 1.5748), 512.0)
1158 	   + g * -QUANT((240.0 - 16.0) *        0.7152 /(255.0 * 1.5748), 512.0)
1159 	   + b * -QUANT((240.0 - 16.0) *        0.0722 /(255.0 * 1.5748), 512.0);
1160 
1161 	/* (Don't bother with offsets, since they don't affect quantization) */
1162 
1163 	/* Quantize YCbCr to 8 bit, since that's what ChromCast HDMI delivers */
1164 	Y  = floor(Y  + 0.5);
1165 	Cb = floor(Cb + 0.5);
1166 	Cr = floor(Cr + 0.5);
1167 
1168 	/* We simply assume that the TV decoding is perfect, */
1169 	/* but is limited to 8 bit full range RGB output */
1170     r = Y  * 255.0/(235.0 - 16.0)
1171 	  + Cr * (1.574800000 * 255.0)/(240.0 - 16.0);
1172 
1173     g = Y  * 255.0/(235.0 - 16.0)
1174 	  + Cb * (-0.187324273 * 255.0)/(240.0 - 16.0)
1175 	  + Cr * (-0.468124273 * 255.0)/(240.0 - 16.0);
1176 
1177     b = Y  * 255.0/(235.0 - 16.0)
1178 	  + Cb * (1.855600000 * 255.0)/(240.0 - 16.0);
1179 
1180 	/* Clip */
1181 	if (r > 255.0)
1182 		r = 255.0;
1183 	else if (r < 0.0)
1184 		r = 0.0;
1185 	if (g > 255.0)
1186 		g = 255.0;
1187 	else if (g < 0.0)
1188 		g = 0.0;
1189 	if (b > 255.0)
1190 		b = 255.0;
1191 	else if (b < 0.0)
1192 		b = 0.0;
1193 
1194 	/* We assume that the TV is 8 bit, so */
1195 	/* quantize to 8 bits and return to 0.0 - 1.0 range. */
1196 	r = floor(r + 0.5)/255.0;
1197 	g = floor(g + 0.5)/255.0;
1198 	b = floor(b + 0.5)/255.0;
1199 
1200 	out[0] = r;
1201 	out[1] = g;
1202 	out[2] = b;
1203 
1204 if (fabs(or - r) > 3.0
1205  || fabs(og - g) > 3.0
1206  || fabs(ob - b) > 3.0) {
1207 	printf("%f %f %f -> %f %f %f\n",or,og,ob,r,g,b);
1208 }
1209 //	printf(" -> %f %f %f\n",r,g,b);
1210 }
1211 
1212 /* Input RGB 8 bit 0 - 255 levels, output YCbCr 8 bit 16 - 235 levels */
1213 /* Non-clipping, non-8 bit quantizing */
ccast2YCbCr_nq(void * ctx,double out[3],double in[3])1214 void ccast2YCbCr_nq(void *ctx, double out[3], double in[3]) {
1215 	double r = in[0], g = in[1], b = in[2];
1216 	double Y, Cb, Cr;
1217 
1218 //	printf("ccast2YCbCr_nq: %f %f %f",r,g,b);
1219 
1220 	/* ChromCast hardware seems to use 9 bit coeficients */
1221 	Y = r * QUANT((235.0 - 16.0) * 0.2126/255.0, 512.0)
1222 	  + g * QUANT((235.0 - 16.0) * 0.7152/255.0, 512.0)
1223 	  + b * QUANT((235.0 - 16.0) * 0.0722/255.0, 512.0)
1224 	  + 16.0;
1225 
1226 	Cb = r * -QUANT((240.0 - 16.0) *        0.2126 /(255.0 * 1.8556), 512.0)
1227 	   + g * -QUANT((240.0 - 16.0) *        0.7152 /(255.0 * 1.8556), 512.0)
1228 	   + b *  QUANT((240.0 - 16.0) * (1.0 - 0.0722)/(255.0 * 1.8556), 512.0)
1229 	   + 16.0;
1230 
1231 	Cr = r *  QUANT((240.0 - 16.0) * (1.0 - 0.2126)/(255.0 * 1.5748), 512.0)
1232 	   + g * -QUANT((240.0 - 16.0) *        0.7152 /(255.0 * 1.5748), 512.0)
1233 	   + b * -QUANT((240.0 - 16.0) *        0.0722 /(255.0 * 1.5748), 512.0)
1234 	   + 16.0;
1235 
1236 	out[0] = Y;
1237 	out[1] = Cb;
1238 	out[2] = Cr;
1239 
1240 //	printf(" -> %f %f %f\n",out[0],out[1],out[2]);
1241 }
1242 
1243 /* Input RGB 8 bit 0 - 255 levels, output YCbCr 8 bit 16 - 235 levels */
ccast2YCbCr(void * ctx,double out[3],double in[3])1244 void ccast2YCbCr(void *ctx, double out[3], double in[3]) {
1245 	double rgb[3];
1246 
1247 //	printf("ccast2YCbCr: %f %f %f",r,g,b);
1248 
1249 	/* Quantize RGB to 8 bit, since that's the limit of the frame buffer */
1250 	rgb[0] = floor(in[0] + 0.5);
1251 	rgb[1] = floor(in[1] + 0.5);
1252 	rgb[2] = floor(in[2] + 0.5);
1253 
1254 	ccast2YCbCr_nq(ctx, out, rgb);
1255 
1256 	/* Quantize YCbCr to 8 bit, since that's what ChromCast HDMI delivers */
1257 	out[0] = floor(out[0] + 0.5);
1258 	out[1] = floor(out[1] + 0.5);
1259 	out[2] = floor(out[2] + 0.5);
1260 //	printf(" -> %f %f %f\n",out[0],out[1],out[2]);
1261 }
1262 
1263 /* Input YCbCr 8 bit 16 - 235 levels output RGB 8 bit 0 - 255 levels. */
1264 /* Non-clipping, non-8 bit quantizing */
YCbCr2ccast_nq(void * ctx,double out[3],double in[3])1265 void YCbCr2ccast_nq(void *ctx, double out[3], double in[3]) {
1266 	double Y = in[0], Cb = in[1], Cr = in[2];
1267 	double r, g, b;
1268 
1269 //	printf("YCbCr2ccast_nq: %f %f %f",Y,Cb,Cr);
1270 
1271 	Y  -= 16.0;
1272 	Cb -= 16.0;
1273 	Cr -= 16.0;
1274 
1275 	/* We simply assume that the TV decoding is perfect, */
1276 	/* but is limited to 8 bit full range RGB output */
1277     r = Y  * 255.0/(235.0 - 16.0)
1278 	  + Cr * (1.574800000 * 255.0)/(240.0 - 16.0);
1279 
1280     g = Y  * 255.0/(235.0 - 16.0)
1281 	  + Cb * (-0.187324273 * 255.0)/(240.0 - 16.0)
1282 	  + Cr * (-0.468124273 * 255.0)/(240.0 - 16.0);
1283 
1284     b = Y  * 255.0/(235.0 - 16.0)
1285 	  + Cb * (1.855600000 * 255.0)/(240.0 - 16.0);
1286 
1287 	out[0] = r;
1288 	out[1] = g;
1289 	out[2] = b;
1290 
1291 //	printf(" -> %f %f %f\n",out[0],out[1],out[2]);
1292 }
1293 
1294 /* Input YCbCr 8 bit 16 - 235 levels output RGB 8 bit 0 - 255 levels. */
1295 /* Quantize input, and quantize and clip output. */
1296 /* Return nz if the output was clipped */
YCbCr2ccast(void * ctx,double out[3],double in[3])1297 int YCbCr2ccast(void *ctx, double out[3], double in[3]) {
1298 	double YCbCr[3];
1299 	int k, rv = 0;
1300 
1301 //	printf("YCbCr2ccast: %f %f %f",Y,Cb,Cr);
1302 
1303 	/* Quantize YCbCr to 8 bit, since that's what ChromCast HDMI delivers */
1304 	YCbCr[0] = floor(in[0] + 0.5);
1305 	YCbCr[1] = floor(in[1] + 0.5);
1306 	YCbCr[2] = floor(in[2] + 0.5);
1307 
1308 	YCbCr2ccast_nq(ctx, out, YCbCr);
1309 
1310 	/* Quantize and clip to RGB, since we assume the TV does this */
1311 	for (k = 0; k < 3; k++) {
1312 		out[k] = floor(out[k] + 0.5);
1313 
1314 		if (out[k] > 255.0) {
1315 			out[k] = 255.0;
1316 			rv = 1;
1317 		} else if (out[k] < 0.0) {
1318 			out[k] = 0.0;
1319 			rv = 1;
1320 		}
1321 	}
1322 
1323 //	printf(" -> %f %f %f, clip %d\n",out[0],out[1],out[2],rv);
1324 
1325 	return rv;
1326 }
1327 
1328