xref: /haiku/src/add-ons/media/media-add-ons/esound_sink/ESDEndpoint.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
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 <arpa/inet.h>
37 #include <netdb.h>
38 #include <netinet/in.h>
39 #include <netinet/tcp.h>
40 #include <errno.h>
41 #include <stdlib.h>
42 #include "compat.h"
43 //#undef DEBUG
44 //#define DEBUG 4
45 #include "debug.h"
46 #include <Debug.h>
47 #include "ESDEndpoint.h"
48 
49 
50 ESDEndpoint::ESDEndpoint()
51  : BDataIO()
52  , fHost(NULL)
53  , fPort(ESD_DEFAULT_PORT)
54  , fSocket(-1)
55 {
56 	CALLED();
57 	Reset();
58 }
59 
60 
61 ESDEndpoint::~ESDEndpoint()
62 {
63 	CALLED();
64 	if (fSocket > -1)
65 		closesocket(fSocket);
66 	fSocket = -1;
67 }
68 
69 
70 status_t
71 ESDEndpoint::InitCheck() const
72 {
73 	return fInitStatus;
74 }
75 
76 
77 void
78 ESDEndpoint::Reset()
79 {
80 	fInitStatus = B_NO_INIT;
81 	fDefaultCommand = ESD_PROTO_STREAM_PLAY;
82 	fDefaultCommandSent = false;
83 	fDefaultFormat = ESD_BITS8 | ESD_MONO;
84 	fDefaultRate = ESD_DEFAULT_RATE;
85 	fLatency = 0LL;
86 }
87 
88 
89 status_t
90 ESDEndpoint::SendAuthKey()
91 {
92 	CALLED();
93 	BPath kfPath;
94 	status_t err;
95 	off_t size;
96 	char key[ESD_MAX_KEY];
97 	err = find_directory(B_USER_SETTINGS_DIRECTORY, &kfPath);
98 	kfPath.Append("esd_auth");
99 	BFile keyFile(kfPath.Path(), B_READ_WRITE|B_CREATE_FILE);
100 	err = keyFile.GetSize(&size);
101 	if (err < 0)
102 		return err;
103 	if (size < ESD_MAX_KEY) {
104 		keyFile.Seek(0LL, SEEK_SET);
105 		srand(time(NULL));
106 		for (int i = 0; i < ESD_MAX_KEY; i++)
107 			key[i] = (char)(rand() % 256);
108 		err = keyFile.Write(key, ESD_MAX_KEY);
109 		if (err < 0)
110 			return err;
111 		if (err < ESD_MAX_KEY)
112 			return EIO;
113 	}
114 	err = keyFile.Read(key, ESD_MAX_KEY);
115 	if (err < 0)
116 		return err;
117 	if (err < ESD_MAX_KEY)
118 		return EIO;
119 	memcpy(fAuthKey, key, sizeof(esd_key_t));
120 	return write(fSocket, fAuthKey, ESD_MAX_KEY);
121 }
122 
123 
124 bool
125 ESDEndpoint::Connected() const
126 {
127 	return (fInitStatus == B_OK);
128 }
129 
130 
131 status_t
132 ESDEndpoint::Connect(const char *host, uint16 port)
133 {
134 	status_t err;
135 	// set up connection asynchronously
136 	fHost = host;
137 	fPort = port;
138 
139 	err = fConnectThread = spawn_thread(_ConnectThread, "ESDEndpoint Connection", B_LOW_PRIORITY, this);
140 	if (err < B_OK)
141 		return err;
142 	err = resume_thread(fConnectThread);
143 
144 	// TODO: return now instead and move Connect() call
145 	wait_for_thread(fConnectThread, &err);
146 
147 	return err;
148 }
149 
150 
151 int32
152 ESDEndpoint::_ConnectThread(void *_arg)
153 {
154 	ESDEndpoint *_this = (ESDEndpoint *)_arg;
155 	return _this->ConnectThread();
156 }
157 
158 int32
159 ESDEndpoint::ConnectThread(void)
160 {
161 	const char *host = fHost.String();
162 	uint16 port = fPort;
163 	status_t err;
164 	int flag;
165 	CALLED();
166 
167 	struct hostent *he;
168 	struct sockaddr_in sin;
169 	he = gethostbyname(host);
170 	PRINT(("gethostbyname(%s) = %p\n", host, he));
171 	if (!he)
172 		return ENOENT;
173 	memcpy((struct in_addr *)&sin.sin_addr, he->h_addr, sizeof(struct in_addr));
174 
175 	/* close old connection */
176 	if (fSocket > -1)
177 		closesocket(fSocket);
178 	Reset();
179 
180 	fSocket = socket(AF_INET, SOCK_STREAM, 0);
181 	if (fSocket < 0)
182 		return errno;
183 	sin.sin_family = AF_INET;
184 	sin.sin_port = htons( port );
185 
186 	/*
187 	flag = 128*1024;
188 	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
189 	setsockopt(fSocket, SOL_SOCKET, SO_RCVBUF, &flag, sizeof(flag));
190 	*/
191 
192 	err = connect(fSocket, (struct sockaddr *) &sin, sizeof(sin));
193 	PRINT(("connect: %ld, %s\n", err, strerror(errno)));
194 	if (err < 0)
195 		return errno;
196 
197 	uint32 cmd;
198 	uint32 result;
199 /*	uint32 cmd = ESD_PROTO_CONNECT;
200 	err = write(fSocket, &cmd, sizeof(cmd));
201 	if (err < 0)
202 		return errno;
203 	if (err < sizeof(cmd))
204 		return EIO;
205 */
206 	/* send authentification key */
207 	err = SendAuthKey();
208 	if (err < 0)
209 		return errno;
210 
211 	/* send endian tag, wait for 'ok' and measure the round-trip time
212 	 * to account for the network latency
213 	 */
214 	bigtime_t ping = system_time();
215 
216 	cmd = ESD_ENDIAN_TAG;
217 	err = write(fSocket, &cmd, sizeof(cmd));
218 	if (err < 0)
219 		return errno;
220 	if (err < sizeof(cmd))
221 		return EIO;
222 
223 	read(fSocket, &result, sizeof(result));
224 	if (result != 1)
225 		fprintf(stderr, "ESDEndpoint::Connect: didn't get ok from ESD_PROTO_INIT (%ld)!\n", result);
226 
227 	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
228 	fLatency = ping;
229 
230 	/* ask the server for its own latency
231 	 * sadly it seems to only give a fixed value,
232 	 * not counting the audio card's buffering into account.
233 	 * we take another measurement of the round-trip,
234 	 * and use the mean of the 2.
235 	 */
236 
237 	ping = system_time();
238 
239 	cmd = ESD_PROTO_LATENCY;
240 	err = write(fSocket, &cmd, sizeof(cmd));
241 	if (err < 0)
242 		return errno;
243 	if (err < sizeof(cmd))
244 		return EIO;
245 
246 	read(fSocket, &result, sizeof(result));
247 	fprintf(stderr, "ESDEndpoint::Connect: ESD_PROTO_LATENCY: %ld\n", result);
248 
249 	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
250 
251 	bigtime_t serverLatency = result * 1000000LL / (ESD_DEFAULT_RATE * 2/*16b*/ * 2/*stereo*/ );
252 	bigtime_t netLatency = (fLatency + ping) / 2; /* mean of both */
253 	fprintf(stderr, "ESDEndpoint::Connect: Latency: server: %Ld, net1: %Ld, net2: %Ld\n", serverLatency, fLatency, ping);
254 
255 	fLatency = netLatency + serverLatency;
256 
257 
258 
259 	flag = 1;
260 	int len;
261 	/* disable Nagle */
262 	setsockopt(fSocket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
263 	//setsockopt(fSocket, SOL_SOCKET, SO_NONBLOCK, &flag, sizeof(flag));
264 	/*
265 	len = sizeof(flag);
266 	getsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, &len);
267 	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
268 	flag = MIN(TCP_MAXWIN, MAX(flag, (4 * netLatency * (ESD_DEFAULT_RATE*2*2) / 1000000LL)));
269 	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
270 	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
271 	*/
272 
273 
274 
275 	// TODO: get default format
276 
277 	fInitStatus = B_OK;
278 
279 	return B_OK;
280 }
281 
282 status_t
283 ESDEndpoint::Disconnect()
284 {
285 	CALLED();
286 	if (fSocket > -1)
287 		closesocket(fSocket);
288 	fSocket = -1;
289 	return B_OK;
290 }
291 
292 status_t
293 ESDEndpoint::SetCommand(esd_command_t cmd)
294 {
295 	CALLED();
296 	if (fDefaultCommandSent)
297 		return EALREADY;
298 	fDefaultCommand = cmd;
299 	return B_OK;
300 }
301 
302 status_t
303 ESDEndpoint::SetFormat(int bits, int channels, float rate)
304 {
305 	esd_format_t fmt = 0;
306 	CALLED();
307 	if (fDefaultCommandSent)
308 		return EALREADY;
309 	PRINT(("SetFormat(%d,%d,%d)\n", bits, channels, rate));
310 	switch (bits) {
311 	case 8:
312 		fmt |= ESD_BITS8;
313 		break;
314 	case 16:
315 		fmt |= ESD_BITS16;
316 		break;
317 	default:
318 		return EINVAL;
319 	}
320 	switch (channels) {
321 	case 1:
322 		fmt |= ESD_MONO;
323 		break;
324 	case 2:
325 		fmt |= ESD_STEREO;
326 		break;
327 	default:
328 		return EINVAL;
329 	}
330 	fmt |= ESD_STREAM | ESD_FUNC_PLAY;
331 	PRINT(("SetFormat: %08lx\n", fmt));
332 	fDefaultFormat = fmt;
333 	fDefaultRate = rate;
334 	return B_OK;
335 }
336 
337 status_t
338 ESDEndpoint::GetServerInfo()
339 {
340 	CALLED();
341 	struct serverinfo {
342 		uint32 ver;
343 		uint32 rate;
344 		uint32 fmt;
345 	} si;
346 	status_t err;
347 	err = SendCommand(ESD_PROTO_SERVER_INFO, (const uint8 *)&si, 0, (uint8 *)&si, sizeof(si));
348 	if (err < 0)
349 		return err;
350 	PRINT(("err %d, version: %lu, rate: %lu, fmt: %lu\n", err, si.ver, si.rate, si.fmt));
351 	return B_OK;
352 }
353 
354 bool
355 ESDEndpoint::CanSend()
356 {
357 	CALLED();
358 	return fDefaultCommandSent;
359 }
360 
361 ssize_t
362 ESDEndpoint::Read(void *buffer, size_t size)
363 {
364 	CALLED();
365 	return EINVAL;
366 }
367 
368 ssize_t
369 ESDEndpoint::Write(const void *buffer, size_t size)
370 {
371 	status_t err = B_OK;
372 	CALLED();
373 	if (!fDefaultCommandSent)
374 		err = SendDefaultCommand();
375 	if (err < B_OK)
376 		return err;
377 	//PRINT(("write(fSocket, buffer, %d)\n", size));
378 	//fprintf(stderr, "ESDEndpoint::Write(, %d) %s\n", size, (size%2)?"ODD BUFFER SIZE":"");
379 	if (fDefaultFormat & ESD_BITS16) {
380 		size /= 2;
381 		size *= 2;
382 	}
383 	err = write(fSocket, buffer, size);
384 	if (err != size) {
385 		fprintf(stderr, "ESDEndpoint::Write: sent only %d of %d!\n", err, size);
386 		if (err < 0)
387 			fprintf(stderr, "ESDEndpoint::Write: %s\n", strerror(errno));
388 	}
389 	//PRINT(("write(fSocket, buffer, %d): %s\n", size, strerror(err)));
390 	if (err < B_OK)
391 		return errno;
392 	return err;
393 }
394 
395 status_t
396 ESDEndpoint::SendCommand(esd_command_t cmd, const uint8 *obuf, size_t olen, uint8 *ibuf, size_t ilen)
397 {
398 	status_t err;
399 	CALLED();
400 	err = send(fSocket, &cmd, sizeof(cmd), 0);
401 	if (err < B_OK)
402 		return errno;
403 	if (obuf && olen) {
404 		err = send(fSocket, obuf, olen, 0);
405 		if (err < B_OK)
406 			return errno;
407 	}
408 	err = B_OK;
409 	if (ibuf && ilen) {
410 		err = recv(fSocket, ibuf, ilen, 0);
411 		if (err < B_OK)
412 			return errno;
413 		/* return received len */
414 	}
415 	return err;
416 }
417 
418 status_t
419 ESDEndpoint::SendDefaultCommand()
420 {
421 	status_t err;
422 	struct {
423 		esd_format_t format;
424 		esd_rate_t rate;
425 		char name[ESD_MAX_NAME];
426 	} c;
427 	CALLED();
428 	if (fDefaultCommandSent)
429 		return EALREADY;
430 	c.format = fDefaultFormat;
431 	c.rate = fDefaultRate;
432 	strcpy(c.name, "BeOS/Haiku/ZETA Media Kit output");
433 	err = SendCommand(fDefaultCommand, (uint8 *)&c, sizeof(c), NULL, 0);
434 	if (err < B_OK)
435 		return err;
436 	PRINT(("SendCommand: %s\n", strerror(err)));
437 	fDefaultCommandSent = true;
438 	return B_OK;
439 }
440 
441