xref: /haiku/src/add-ons/media/media-add-ons/esound_sink/ESDEndpoint.cpp (revision 22440f4105cafc95cc1d49f9bc65bb395c527d86)
1 /*
2  * ESounD media addon for BeOS
3  *
4  * Copyright (c) 2006 François Revol (revol@free.fr)
5  *
6  * Based on Multi Audio addon for Haiku,
7  * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
8  *
9  * All rights reserved.
10  * Redistribution and use in source and binary forms, with or without modification,
11  * are permitted provided that the following conditions are met:
12  *
13  * - Redistributions of source code must retain the above copyright notice,
14  *   this list of conditions and the following disclaimer.
15  * - Redistributions in binary form must reproduce the above copyright notice,
16  *   this list of conditions and the following disclaimer in the documentation
17  *   and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
25  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  */
31 #define _ZETA_TS_FIND_DIR_ 1
32 #include <FindDirectory.h>
33 #include <File.h>
34 #include <Path.h>
35 #include <sys/socket.h>
36 #include <sys/time.h>
37 #include <arpa/inet.h>
38 #include <netdb.h>
39 #include <netinet/in.h>
40 #include <netinet/tcp.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include "compat.h"
44 //#undef DEBUG
45 //#define DEBUG 4
46 #include "debug.h"
47 #include <Debug.h>
48 #include "ESDEndpoint.h"
49 
50 
51 ESDEndpoint::ESDEndpoint()
52  : BDataIO()
53  , fHost(NULL)
54  , fPort(ESD_DEFAULT_PORT)
55  , fSocket(-1)
56 {
57 	CALLED();
58 	Reset();
59 }
60 
61 
62 ESDEndpoint::~ESDEndpoint()
63 {
64 	CALLED();
65 	if (fSocket > -1)
66 		closesocket(fSocket);
67 	fSocket = -1;
68 }
69 
70 
71 status_t
72 ESDEndpoint::InitCheck() const
73 {
74 	return fInitStatus;
75 }
76 
77 
78 void
79 ESDEndpoint::Reset()
80 {
81 	fInitStatus = B_NO_INIT;
82 	fDefaultCommand = ESD_PROTO_STREAM_PLAY;
83 	fDefaultCommandSent = false;
84 	fDefaultFormat = ESD_BITS8 | ESD_MONO;
85 	fDefaultRate = ESD_DEFAULT_RATE;
86 	fLatency = 0LL;
87 }
88 
89 
90 status_t
91 ESDEndpoint::SendAuthKey()
92 {
93 	CALLED();
94 	BPath kfPath;
95 	status_t err;
96 	off_t size;
97 	char key[ESD_MAX_KEY];
98 	err = find_directory(B_USER_SETTINGS_DIRECTORY, &kfPath);
99 	kfPath.Append("esd_auth");
100 	BFile keyFile(kfPath.Path(), B_READ_WRITE|B_CREATE_FILE);
101 	err = keyFile.GetSize(&size);
102 	if (err < 0)
103 		return err;
104 	if (size < ESD_MAX_KEY) {
105 		keyFile.Seek(0LL, SEEK_SET);
106 		srand(time(NULL));
107 		for (int i = 0; i < ESD_MAX_KEY; i++)
108 			key[i] = (char)(rand() % 256);
109 		err = keyFile.Write(key, ESD_MAX_KEY);
110 		if (err < 0)
111 			return err;
112 		if (err < ESD_MAX_KEY)
113 			return EIO;
114 	}
115 	err = keyFile.Read(key, ESD_MAX_KEY);
116 	if (err < 0)
117 		return err;
118 	if (err < ESD_MAX_KEY)
119 		return EIO;
120 	memcpy(fAuthKey, key, sizeof(esd_key_t));
121 	return write(fSocket, fAuthKey, ESD_MAX_KEY);
122 }
123 
124 
125 bool
126 ESDEndpoint::Connected() const
127 {
128 	return (fInitStatus == B_OK);
129 }
130 
131 
132 status_t
133 ESDEndpoint::Connect(const char *host, uint16 port)
134 {
135 	status_t err;
136 	// set up connection asynchronously
137 	fHost = host;
138 	fPort = port;
139 
140 	err = fConnectThread = spawn_thread(_ConnectThread, "ESDEndpoint Connection", B_LOW_PRIORITY, this);
141 	if (err < B_OK)
142 		return err;
143 	err = resume_thread(fConnectThread);
144 
145 	// TODO: return now instead and move Connect() call
146 	//wait_for_thread(fConnectThread, &err);
147 
148 	return err;
149 }
150 
151 
152 status_t
153 ESDEndpoint::WaitForConnect()
154 {
155 	status_t err;
156 	int32 ret;
157 	err = wait_for_thread(fConnectThread, &ret);
158 	if (err < B_OK)
159 		return err;
160 
161 	return ret;
162 }
163 
164 
165 int32
166 ESDEndpoint::_ConnectThread(void *_arg)
167 {
168 	ESDEndpoint *_this = (ESDEndpoint *)_arg;
169 	return _this->ConnectThread();
170 }
171 
172 int32
173 ESDEndpoint::ConnectThread(void)
174 {
175 	const char *host = fHost.String();
176 	uint16 port = fPort;
177 	status_t err;
178 	int flag;
179 	struct timeval oldTimeout;
180 	socklen_t oldTimeoutLen = sizeof(struct timeval);
181 	struct timeval timeout = { 10, 0 }; // 10s should be enough on a LAN
182 	CALLED();
183 
184 	struct hostent *he;
185 	struct sockaddr_in sin;
186 	he = gethostbyname(host);
187 	PRINT(("gethostbyname(%s) = %p\n", host, he));
188 	if (!he)
189 		return ENOENT;
190 	memcpy((struct in_addr *)&sin.sin_addr, he->h_addr, sizeof(struct in_addr));
191 
192 	/* close old connection */
193 	if (fSocket > -1)
194 		closesocket(fSocket);
195 	Reset();
196 
197 	fSocket = socket(AF_INET, SOCK_STREAM, 0);
198 	if (fSocket < 0)
199 		return errno;
200 	sin.sin_family = AF_INET;
201 	sin.sin_port = htons( port );
202 
203 	/*
204 	flag = 128*1024;
205 	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
206 	setsockopt(fSocket, SOL_SOCKET, SO_RCVBUF, &flag, sizeof(flag));
207 	*/
208 
209 	if (getsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, &oldTimeoutLen) >= 0) {
210 		setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
211 	}
212 
213 	err = connect(fSocket, (struct sockaddr *) &sin, sizeof(sin));
214 	PRINT(("connect: %ld, %s\n", err, strerror(errno)));
215 	setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, sizeof(struct timeval));
216 	if (err < 0)
217 		return errno;
218 
219 	uint32 cmd;
220 	uint32 result;
221 /*	uint32 cmd = ESD_PROTO_CONNECT;
222 	err = write(fSocket, &cmd, sizeof(cmd));
223 	if (err < 0)
224 		return errno;
225 	if (err < sizeof(cmd))
226 		return EIO;
227 */
228 	/* send authentification key */
229 	err = SendAuthKey();
230 	if (err < 0)
231 		return errno;
232 
233 	/* send endian tag, wait for 'ok' and measure the round-trip time
234 	 * to account for the network latency
235 	 */
236 	bigtime_t ping = system_time();
237 
238 	cmd = ESD_ENDIAN_TAG;
239 	err = write(fSocket, &cmd, sizeof(cmd));
240 	if (err < 0)
241 		return errno;
242 	if ((unsigned)err < sizeof(cmd))
243 		return EIO;
244 
245 	read(fSocket, &result, sizeof(result));
246 	if (result != 1)
247 		fprintf(stderr, "ESDEndpoint::Connect: didn't get ok from ESD_PROTO_INIT (%ld)!\n", result);
248 
249 	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
250 	fLatency = ping;
251 
252 	/* ask the server for its own latency
253 	 * sadly it seems to only give a fixed value,
254 	 * not counting the audio card's buffering into account.
255 	 * we take another measurement of the round-trip,
256 	 * and use the mean of the 2.
257 	 */
258 
259 	ping = system_time();
260 
261 	cmd = ESD_PROTO_LATENCY;
262 	err = write(fSocket, &cmd, sizeof(cmd));
263 	if (err < 0)
264 		return errno;
265 	if ((unsigned)err < sizeof(cmd))
266 		return EIO;
267 
268 	read(fSocket, &result, sizeof(result));
269 	fprintf(stderr, "ESDEndpoint::Connect: ESD_PROTO_LATENCY: %ld\n", result);
270 
271 	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
272 
273 	bigtime_t serverLatency = result * 1000000LL / (ESD_DEFAULT_RATE * 2/*16b*/ * 2/*stereo*/ );
274 	bigtime_t netLatency = (fLatency + ping) / 2; /* mean of both */
275 	fprintf(stderr, "ESDEndpoint::Connect: Latency: server: %Ld, net1: %Ld, net2: %Ld\n", serverLatency, fLatency, ping);
276 
277 	fLatency = netLatency + serverLatency;
278 
279 
280 
281 	flag = 1;
282 	//int len;
283 	/* disable Nagle */
284 	setsockopt(fSocket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
285 	//setsockopt(fSocket, SOL_SOCKET, SO_NONBLOCK, &flag, sizeof(flag));
286 	/*
287 	len = sizeof(flag);
288 	getsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, &len);
289 	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
290 	flag = MIN(TCP_MAXWIN, MAX(flag, (4 * netLatency * (ESD_DEFAULT_RATE*2*2) / 1000000LL)));
291 	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
292 	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
293 	*/
294 
295 
296 
297 	// TODO: get default format
298 
299 	fInitStatus = B_OK;
300 
301 	return B_OK;
302 }
303 
304 status_t
305 ESDEndpoint::Disconnect()
306 {
307 	CALLED();
308 	if (fSocket > -1)
309 		closesocket(fSocket);
310 	fSocket = -1;
311 	return B_OK;
312 }
313 
314 status_t
315 ESDEndpoint::SetCommand(esd_command_t cmd)
316 {
317 	CALLED();
318 	if (fDefaultCommandSent)
319 		return EALREADY;
320 	fDefaultCommand = cmd;
321 	return B_OK;
322 }
323 
324 status_t
325 ESDEndpoint::SetFormat(int bits, int channels, float rate)
326 {
327 	esd_format_t fmt = 0;
328 	CALLED();
329 	if (fDefaultCommandSent)
330 		return EALREADY;
331 	PRINT(("SetFormat(%d,%d,%f)\n", bits, channels, rate));
332 	switch (bits) {
333 	case 8:
334 		fmt |= ESD_BITS8;
335 		break;
336 	case 16:
337 		fmt |= ESD_BITS16;
338 		break;
339 	default:
340 		return EINVAL;
341 	}
342 	switch (channels) {
343 	case 1:
344 		fmt |= ESD_MONO;
345 		break;
346 	case 2:
347 		fmt |= ESD_STEREO;
348 		break;
349 	default:
350 		return EINVAL;
351 	}
352 	fmt |= ESD_STREAM | ESD_FUNC_PLAY;
353 	PRINT(("SetFormat: %08lx\n", (long)fmt));
354 	fDefaultFormat = fmt;
355 	fDefaultRate = rate;
356 	return B_OK;
357 }
358 
359 status_t
360 ESDEndpoint::GetServerInfo()
361 {
362 	CALLED();
363 	struct serverinfo {
364 		uint32 ver;
365 		uint32 rate;
366 		uint32 fmt;
367 	} si;
368 	status_t err;
369 	err = SendCommand(ESD_PROTO_SERVER_INFO, (const uint8 *)&si, 0, (uint8 *)&si, sizeof(si));
370 	if (err < 0)
371 		return err;
372 	PRINT(("err 0x%08lx, version: %lu, rate: %lu, fmt: %lu\n", err, si.ver, si.rate, si.fmt));
373 	return B_OK;
374 }
375 
376 
377 void
378 ESDEndpoint::GetFriendlyName(BString &name)
379 {
380 	name = "ESounD Out";
381 	name << " (" << Host();
382 	name << ":" << Port() << ")";
383 
384 }
385 
386 bool
387 ESDEndpoint::CanSend()
388 {
389 	CALLED();
390 	return fDefaultCommandSent;
391 }
392 
393 ssize_t
394 ESDEndpoint::Read(void *buffer, size_t size)
395 {
396 	CALLED();
397 	return EINVAL;
398 }
399 
400 ssize_t
401 ESDEndpoint::Write(const void *buffer, size_t size)
402 {
403 	status_t err = B_OK;
404 	CALLED();
405 	if (!fDefaultCommandSent)
406 		err = SendDefaultCommand();
407 	if (err < B_OK)
408 		return err;
409 	//PRINT(("write(fSocket, buffer, %d)\n", size));
410 	//fprintf(stderr, "ESDEndpoint::Write(, %d) %s\n", size, (size%2)?"ODD BUFFER SIZE":"");
411 	if (fDefaultFormat & ESD_BITS16) {
412 		size /= 2;
413 		size *= 2;
414 	}
415 	err = write(fSocket, buffer, size);
416 	if ((unsigned)err != size) {
417 		fprintf(stderr, "ESDEndpoint::Write: sent only %ld of %ld!\n", err, size);
418 		if (err < 0)
419 			fprintf(stderr, "ESDEndpoint::Write: %s\n", strerror(errno));
420 	}
421 	//PRINT(("write(fSocket, buffer, %d): %s\n", size, strerror(err)));
422 	if (err < B_OK)
423 		return errno;
424 	return err;
425 }
426 
427 status_t
428 ESDEndpoint::SendCommand(esd_command_t cmd, const uint8 *obuf, size_t olen, uint8 *ibuf, size_t ilen)
429 {
430 	status_t err;
431 	CALLED();
432 	err = send(fSocket, &cmd, sizeof(cmd), 0);
433 	if (err < B_OK)
434 		return errno;
435 	if (obuf && olen) {
436 		err = send(fSocket, obuf, olen, 0);
437 		if (err < B_OK)
438 			return errno;
439 	}
440 	err = B_OK;
441 	if (ibuf && ilen) {
442 		err = recv(fSocket, ibuf, ilen, 0);
443 		if (err < B_OK)
444 			return errno;
445 		/* return received len */
446 	}
447 	return err;
448 }
449 
450 status_t
451 ESDEndpoint::SendDefaultCommand()
452 {
453 	status_t err;
454 	struct {
455 		esd_format_t format;
456 		esd_rate_t rate;
457 		char name[ESD_MAX_NAME];
458 	} c;
459 	CALLED();
460 	if (fDefaultCommandSent)
461 		return EALREADY;
462 	c.format = fDefaultFormat;
463 	c.rate = fDefaultRate;
464 	strcpy(c.name, "BeOS/Haiku/ZETA Media Kit output");
465 	err = SendCommand(fDefaultCommand, (uint8 *)&c, sizeof(c), NULL, 0);
466 	if (err < B_OK)
467 		return err;
468 	PRINT(("SendCommand: %s\n", strerror(err)));
469 	fDefaultCommandSent = true;
470 	return B_OK;
471 }
472 
473