source: roaraudio/libroar/vs.c @ 5025:35982a4396fb

Last change on this file since 5025:35982a4396fb was 5025:35982a4396fb, checked in by phi, 13 years ago

prepare VS API for SYNC streams (FILTER), adding a flag for defaulting to PAUSE.

File size: 31.8 KB
Line 
1//vs.c:
2
3/*
4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2010-2011
5 *
6 *  This file is part of libroar a part of RoarAudio,
7 *  a cross-platform sound system for both, home and professional use.
8 *  See README for details.
9 *
10 *  This file is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License version 3
12 *  as published by the Free Software Foundation.
13 *
14 *  libroar is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this software; see the file COPYING.  If not, write to
21 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
22 *  Boston, MA 02110-1301, USA.
23 *
24 *  NOTE for everyone want's to change something and send patches:
25 *  read README and HACKING! There a addition information on
26 *  the license of this document you need to read before you send
27 *  any patches.
28 *
29 *  NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc
30 *  or libpulse*:
31 *  The libs libroaresd, libroararts and libroarpulse link this lib
32 *  and are therefore GPL. Because of this it may be illigal to use
33 *  them with any software that uses libesd, libartsc or libpulse*.
34 */
35
36#include "libroar.h"
37
38#if defined(ROAR_HAVE_GETSOCKOPT) && defined(ROAR_HAVE_SETSOCKOPT)
39#define _HAVE_SOCKOPT
40#endif
41
42#define FLAG_NONE      0x0000
43#define FLAG_STREAM    0x0001
44#define FLAG_NONBLOCK  0x0002
45#define FLAG_BUFFERED  0x0004
46#define FLAG_CLOSEFILE 0x0008
47#define FLAG_FREE_VOL  0x0010
48#define FLAG_DEF_PAUSE 0x0020
49#define FLAG_DIR_IN    0x1000
50#define FLAG_DIR_OUT   0x2000
51
52#define _initerr()  roar_err_clear_all()
53#define _seterr(x)  do { if ( error != NULL ) *error = (x); roar_err_set((x)); ROAR_DBG("roar_vs_*(*): *error=%s(%i)", roar_vs_strerr((x)), (x)); } while(0)
54#define _seterrre() do { _seterr(roar_error); } while(0)
55#define _seterrse() do { roar_err_from_errno(); _seterr(roar_error); } while(0)
56#define _ckvss(ret) do { if ( vss == NULL ) { _seterr(ROAR_ERROR_INVAL); return (ret); } } while(0)
57
58struct roar_vs {
59 int flags;
60 struct roar_connection con_store;
61 struct roar_connection * con;
62 struct roar_stream       stream;
63 struct roar_vio_calls    vio;
64 struct roar_audio_info   info;
65 size_t                   readc, writec;
66 int                      mixerid;
67 int                      first_primid;
68 struct roar_buffer     * readbuffer, * writebuffer;
69 struct roar_vio_calls    file_store;
70 struct roar_vio_calls  * file;
71 struct roar_buffer     * readring, * writering;
72#ifdef _HAVE_SOCKOPT
73 struct {
74  float target;
75  float window;
76  float minlag;
77  float p;
78 } latc;
79#endif
80};
81
82static int _roar_vs_find_first_prim(roar_vs_t * vss);
83
84const char * roar_vs_strerr(int error) {
85 const char * ret = roar_error2str(error);
86
87 if ( ret == NULL )
88  return "(unknown)";
89
90 return ret;
91}
92
93static roar_vs_t * roar_vs_init(int * error) {
94 roar_vs_t * vss = roar_mm_malloc(sizeof(roar_vs_t));
95
96 if ( vss == NULL ) {
97  _seterrse();
98  return NULL;
99 }
100
101 memset(vss, 0, sizeof(roar_vs_t));
102
103 vss->mixerid      = -1;
104 vss->first_primid = -1;
105
106#ifdef _HAVE_SOCKOPT
107 vss->latc.target = -2.; // must be less than latc.minlag!
108 vss->latc.window = -1.;
109 vss->latc.minlag = -1.;
110 vss->latc.p      =  0.005;
111#endif
112
113 return vss;
114}
115
116roar_vs_t * roar_vs_new_from_con(struct roar_connection * con, int * error) {
117 roar_vs_t * vss = roar_vs_init(error);
118
119 if ( vss == NULL )
120  return NULL;
121
122 vss->con = con;
123
124 return vss;
125}
126
127roar_vs_t * roar_vs_new(const char * server, const char * name, int * error) {
128 roar_vs_t * vss = roar_vs_init(error);
129 int ret;
130
131 if ( vss == NULL )
132  return NULL;
133
134 vss->con = &(vss->con_store);
135
136 _initerr();
137
138 ret = roar_simple_connect(vss->con, (char*)server, (char*)name);
139
140 if ( ret == -1 ) {
141  roar_vs_close(vss, ROAR_VS_TRUE, NULL);
142  _seterrre();
143  return NULL;
144 }
145
146 return vss;
147}
148
149int roar_vs_stream(roar_vs_t * vss, const struct roar_audio_info * info, int dir, int * error) {
150 struct roar_stream_info sinfo;
151 int ret;
152
153 _ckvss(-1);
154
155 if ( vss->flags & FLAG_STREAM ) {
156  _seterr(ROAR_ERROR_INVAL);
157  return -1;
158 }
159
160 if ( dir == ROAR_DIR_FILTER )
161  vss->flags |= FLAG_DEF_PAUSE;
162
163 _initerr();
164
165 if ( info != &(vss->info) )
166  memcpy(&(vss->info), info, sizeof(struct roar_audio_info));
167
168 ret = roar_vio_simple_new_stream_obj(&(vss->vio), vss->con, &(vss->stream),
169                                      info->rate, info->channels, info->bits, info->codec,
170                                      dir
171                                     );
172
173 if ( ret == -1 ) {
174  _seterrre();
175  return -1;
176 }
177
178 if ( roar_stream_get_info(vss->con, &(vss->stream), &sinfo) != -1 ) {
179  // TODO: fix this:
180  // as we currently do not support to select mixer we just check if we hit the
181  // right one.
182  if ( vss->mixerid != -1 && vss->mixerid != sinfo.mixer ) {
183   _seterr(ROAR_ERROR_INVAL); // TODO: should we maybe use a diffrent value?
184   roar_vio_close(&(vss->vio));
185   return -1;
186  }
187
188  vss->mixerid = sinfo.mixer;
189  _roar_vs_find_first_prim(vss);
190 }
191
192 vss->flags |= FLAG_STREAM;
193
194 switch (dir) {
195  case ROAR_DIR_PLAY: vss->flags |= FLAG_DIR_OUT; break;
196 }
197
198 return 0;
199}
200
201roar_vs_t * roar_vs_new_simple(const char * server, const char * name, int rate, int channels, int codec, int bits, int dir, int * error) {
202 roar_vs_t * vss = roar_vs_new(server, name, error);
203 int ret;
204
205 if (vss == NULL)
206  return NULL;
207
208 memset(&(vss->info), 0, sizeof(vss->info));
209
210 vss->info.rate     = rate;
211 vss->info.channels = channels;
212 vss->info.codec    = codec;
213 vss->info.bits     = bits;
214
215 ret = roar_vs_stream(vss, &(vss->info), dir, error);
216
217 if (ret == -1) {
218  roar_vs_close(vss, ROAR_VS_TRUE, NULL);
219  return NULL;
220 }
221
222 return vss;
223}
224
225int roar_vs_file(roar_vs_t * vss, struct roar_vio_calls * vio, int closefile, int * error) {
226 _ckvss(-1);
227
228 if ( vio == NULL || (closefile != ROAR_VS_TRUE && closefile != ROAR_VS_FALSE)) {
229  _seterr(ROAR_ERROR_INVAL);
230  return -1;
231 }
232
233 if ( vss->file != NULL ) {
234  _seterr(ROAR_ERROR_INVAL);
235  return -1;
236 }
237
238 vss->file = vio;
239 if ( closefile == ROAR_VS_TRUE )
240  vss->flags |= FLAG_CLOSEFILE;
241
242 return 0;
243}
244
245int roar_vs_file_simple(roar_vs_t * vss, char * filename, int * error) {
246 struct roar_vio_defaults def;
247 struct roar_vio_calls * file;
248 char buf[64];
249 ssize_t ret;
250 int dir = O_RDONLY;
251 int codec = -1;
252 const char * content_type;
253
254 _ckvss(-1);
255
256 if ( vss->file != NULL ) {
257  _seterr(ROAR_ERROR_INVAL);
258  return -1;
259 }
260
261 if ( vss->flags & FLAG_STREAM ) {
262  switch (vss->flags & (FLAG_DIR_IN|FLAG_DIR_OUT)) {
263   case FLAG_DIR_IN:  dir = O_WRONLY; break;
264   case FLAG_DIR_OUT: dir = O_RDONLY; break;
265   case FLAG_DIR_IN|FLAG_DIR_OUT: dir = O_RDWR; break;
266   default:
267     _seterr(ROAR_ERROR_INVAL);
268     return -1;
269  }
270 }
271
272 file = &(vss->file_store);
273
274 _initerr();
275
276 if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, dir, 0644) == -1 ) {
277  _seterrre();
278  return -1;
279 }
280
281 if ( roar_vio_open_dstr(file, filename, &def, 1) == -1 ) {
282  _seterrre();
283  return -1;
284 }
285
286 if ( !(vss->flags & FLAG_STREAM) ) {
287  if ( roar_vio_ctl(file, ROAR_VIO_CTL_GET_MIMETYPE, &content_type) != -1 ) {
288   codec = roar_mime2codec(content_type);
289  }
290
291  if ( codec == -1 ) {
292   ret = roar_vio_read(file, buf, sizeof(buf));
293
294   codec = roar_file_codecdetect(buf, ret);
295
296   if ( codec == -1 ) {
297    roar_vio_close(file);
298    _seterr(ROAR_ERROR_INVAL); // Other value?
299    return -1;
300   }
301
302   if ( roar_vio_lseek(file, 0, SEEK_SET) != 0 ) {
303    roar_vio_close(file);
304   _seterrre();
305    return -1;
306   }
307  }
308
309  memset(&(vss->info), 0, sizeof(vss->info));
310
311  vss->info.rate     = ROAR_RATE_DEFAULT;
312  vss->info.channels = ROAR_CHANNELS_DEFAULT;
313  vss->info.codec    = codec;
314  vss->info.bits     = ROAR_BITS_DEFAULT;
315
316  ret = roar_vs_stream(vss, &(vss->info), ROAR_DIR_PLAY, error);
317
318  if ( ret == -1 ) {
319   roar_vio_close(file);
320   return -1;
321  }
322 }
323
324 if ( roar_vs_file(vss, file, ROAR_VS_TRUE, error) == -1 ) {
325  roar_vio_close(file);
326  return -1;
327 }
328
329 return 0;
330}
331
332roar_vs_t * roar_vs_new_from_file(const char * server, const char * name, char * filename, int * error) {
333 roar_vs_t * vss = roar_vs_new(server, name, error);
334
335 if ( vss == NULL )
336  return NULL;
337
338 if ( roar_vs_file_simple(vss, filename, error) != 0 ) {
339  roar_vs_close(vss, ROAR_VS_TRUE, NULL);
340  return NULL;
341 }
342
343 return vss;
344}
345
346int roar_vs_buffer(roar_vs_t * vss, size_t buffer, int * error) {
347 _ckvss(-1);
348
349 if ( vss->flags & FLAG_BUFFERED )
350  return -1;
351
352 if ( roar_buffer_ring_new(&(vss->readring), buffer, 0) == -1 ) {
353  _seterrre();
354  return -1;
355 }
356
357 if ( roar_buffer_ring_new(&(vss->writering), buffer, 0) == -1 ) {
358  _seterrre();
359  roar_buffer_free(vss->readring);
360  vss->readring = NULL;
361  return -1;
362 }
363
364 vss->flags |= FLAG_BUFFERED;
365
366 return 0;
367}
368
369int roar_vs_close(roar_vs_t * vss, int killit, int * error) {
370 if ( killit != ROAR_VS_TRUE && killit != ROAR_VS_FALSE ) {
371  _seterr(ROAR_ERROR_INVAL);
372  return -1;
373 }
374
375 _ckvss(-1);
376
377 if ( vss->readbuffer != NULL )
378  roar_buffer_free(vss->readbuffer);
379 if ( vss->writebuffer != NULL )
380  roar_buffer_free(vss->writebuffer);
381
382 if ( vss->readring != NULL )
383  roar_buffer_free(vss->readring);
384 if ( vss->writering != NULL )
385  roar_buffer_free(vss->writering);
386
387 if ( vss->file != NULL && vss->flags & FLAG_CLOSEFILE )
388  roar_vio_close(vss->file);
389
390 if ( vss->flags & FLAG_STREAM ) {
391  if ( killit == ROAR_VS_TRUE ) {
392   roar_kick(vss->con, ROAR_OT_STREAM, roar_stream_get_id(&(vss->stream)));
393  }
394
395  roar_vio_close(&(vss->vio));
396 }
397
398 if ( vss->con == &(vss->con_store) ) {
399  roar_disconnect(vss->con);
400 }
401
402 roar_mm_free(vss);
403 return 0;
404}
405
406static ssize_t roar_vs_write_direct(roar_vs_t * vss, const void * buf, size_t len, int * error) {
407 ssize_t ret = roar_vio_write(&(vss->vio), (void*)buf, len);
408
409 if ( ret == -1 ) {
410#ifdef EAGAIN
411  if ( roar_error == ROAR_ERROR_AGAIN )
412   return 0;
413#endif
414
415  _seterrre();
416 } else {
417  if ( !(vss->flags & FLAG_BUFFERED) ) {
418   //printf("A: vss->writec=%zu, ret=%zi\n", vss->writec, ret);
419   vss->writec += ret;
420  }
421 }
422
423 //printf("B: vss->writec=%zu, ret=%zi\n", vss->writec, ret);
424 return ret;
425}
426
427ssize_t roar_vs_write(roar_vs_t * vss, const void * buf, size_t len, int * error) {
428 ssize_t ret;
429 size_t writelen;
430
431 _ckvss(-1);
432
433 if ( !(vss->flags & FLAG_STREAM) ) {
434  _seterr(ROAR_ERROR_INVAL);
435  return -1;
436 }
437
438 _initerr();
439
440 if ( vss->flags & FLAG_BUFFERED ) {
441  writelen = len;
442
443  if ( roar_buffer_ring_write(vss->writering, (void*)buf, &writelen) == -1 ) {
444   _seterrre();
445   return -1;
446  }
447
448  ret = writelen;
449  vss->writec += ret;
450 } else {
451  ret = roar_vs_write_direct(vss, buf, len, error);
452 }
453
454 return ret;
455}
456
457ssize_t roar_vs_read (roar_vs_t * vss,       void * buf, size_t len, int * error) {
458 ssize_t ret;
459
460 _ckvss(-1);
461
462 if ( !(vss->flags & FLAG_STREAM) ) {
463  _seterr(ROAR_ERROR_INVAL);
464  return -1;
465 }
466
467 _initerr();
468
469 ret = roar_vio_read(&(vss->vio), buf, len);
470
471 if ( ret == -1 ) {
472  _seterrre();
473 } else {
474  vss->readc += ret;
475 }
476
477 return ret;
478}
479
480int     roar_vs_sync (roar_vs_t * vss, int wait, int * error) {
481 struct roar_event waits, triggered;
482
483 _ckvss(-1);
484
485 if ( !(vss->flags & FLAG_STREAM) ) {
486  _seterr(ROAR_ERROR_INVAL);
487  return -1;
488 }
489
490 if ( wait != ROAR_VS_NOWAIT && wait != ROAR_VS_WAIT ) {
491  _seterr(ROAR_ERROR_INVAL);
492  return -1;
493 }
494
495 _initerr();
496
497 if ( roar_vio_sync(&(vss->vio)) == -1 ) {
498  _seterrre();
499  return -1;
500 }
501
502 if ( wait == ROAR_VS_WAIT ) {
503  memset(&waits, 0, sizeof(waits));
504  waits.event       = ROAR_OE_STREAM_XRUN;
505  waits.emitter     = -1;
506  waits.target      = roar_stream_get_id(&(vss->stream));
507  waits.target_type = ROAR_OT_STREAM;
508
509  if ( roar_wait(vss->con, &triggered, &waits, 1) == -1 ) {
510   _seterrre();
511   return -1;
512  }
513 }
514
515 return 0;
516}
517
518int     roar_vs_blocking (roar_vs_t * vss, int val, int * error) {
519 int old = -1;
520
521 _ckvss(-1);
522
523  if ( !(vss->flags & FLAG_STREAM) ) {
524  _seterr(ROAR_ERROR_INVAL);
525  return -1;
526 }
527
528 old = vss->flags & FLAG_NONBLOCK ? ROAR_VS_FALSE : ROAR_VS_TRUE;
529
530 _initerr();
531
532 switch (val) {
533  case ROAR_VS_TRUE:
534    if ( roar_vio_nonblock(&(vss->vio), ROAR_SOCKET_BLOCK) == -1 ) {
535     _seterrre();
536     return -1;
537    }
538    vss->flags |= FLAG_NONBLOCK;
539    vss->flags -= FLAG_NONBLOCK;
540    return old;
541   break;
542  case ROAR_VS_FALSE:
543    if ( roar_vio_nonblock(&(vss->vio), ROAR_SOCKET_NONBLOCK) == -1 ) {
544     _seterrre();
545     return -1;
546    }
547    vss->flags |= FLAG_NONBLOCK;
548    return old;
549   break;
550  case ROAR_VS_TOGGLE:
551    if ( old == ROAR_VS_TRUE ) {
552     return roar_vs_blocking(vss, ROAR_VS_FALSE, error);
553    } else {
554     return roar_vs_blocking(vss, ROAR_VS_TRUE, error);
555    }
556   break;
557  case ROAR_VS_ASK:
558    return old;
559   break;
560 }
561
562 _seterr(ROAR_ERROR_INVAL);
563 return -1;
564}
565
566static int _roar_vs_find_first_prim(roar_vs_t * vss) {
567 struct roar_stream stream;
568 struct roar_stream_info info;
569 int id[ROAR_STREAMS_MAX];
570 int num;
571 int i;
572
573 if ( vss->first_primid != -1 )
574  return vss->first_primid;
575
576 if ( vss->mixerid == -1 )
577  return -1;
578
579 if ( (num = roar_list_streams(vss->con, id, ROAR_STREAMS_MAX)) == -1 ) {
580  return -1;
581 }
582
583 for (i = 0; i < num; i++) {
584  if ( roar_get_stream(vss->con, &stream, id[i]) == -1 )
585   continue;
586
587  if ( stream.dir != ROAR_DIR_OUTPUT )
588   continue;
589
590  if ( roar_stream_get_info(vss->con, &stream, &info) == -1 )
591   continue;
592
593  if ( info.mixer == vss->mixerid ) {
594   vss->first_primid = id[i];
595   return id[i];
596  }
597 }
598
599 return -1;
600}
601
602ssize_t roar_vs_position(roar_vs_t * vss, int backend, int * error) {
603 struct roar_stream stream;
604 struct roar_stream      out_stream;
605 struct roar_stream_info out_info;
606 size_t offset = 0;
607
608 _ckvss(-1);
609
610 if ( !(vss->flags & FLAG_STREAM) ) {
611  _seterr(ROAR_ERROR_INVAL);
612  return -1;
613 }
614
615 _initerr();
616
617 if ( roar_get_stream(vss->con, &stream, roar_stream_get_id(&(vss->stream))) == -1 ) {
618  _seterrre();
619  return -1;
620 }
621
622 if ( backend == ROAR_VS_BACKEND_DEFAULT ) {
623  backend = ROAR_VS_BACKEND_FIRST;
624 }
625
626 switch (backend) {
627  case ROAR_VS_BACKEND_NONE:
628    return stream.pos;
629   break;
630  case ROAR_VS_BACKEND_FIRST:
631    if ( vss->first_primid == -1 ) {
632     _seterr(ROAR_ERROR_UNKNOWN);
633     return -1;
634    }
635
636    backend = vss->first_primid;
637   break;
638  default:
639    if ( backend < 0 ) {
640     _seterr(ROAR_ERROR_INVAL);
641     return -1;
642    }
643   break;
644 }
645
646
647 if ( backend >= 0 ) {
648  roar_stream_new_by_id(&out_stream, backend);
649
650  if ( roar_stream_get_info(vss->con, &out_stream, &out_info) == -1 ) {
651   _seterrre();
652   return -1;
653  }
654
655  offset  = out_info.delay * vss->info.rate;
656  offset /= 1000000;
657 }
658
659 return stream.pos + offset;
660}
661
662#ifdef _HAVE_SOCKOPT
663static void roar_vs_latency_managed(roar_vs_t * vss, roar_mus_t lat) {
664 struct roar_vio_sysio_sockopt sockopt;
665 float tmp = ((float)lat/1000.0) - vss->latc.target;
666 int val;
667
668 tmp *= vss->latc.p;
669
670 sockopt.level   = SOL_SOCKET;
671 sockopt.optname = SO_SNDBUF;
672 sockopt.optval  = &val;
673 sockopt.optlen  = sizeof(val);
674
675 roar_vio_ctl(&(vss->vio), ROAR_VIO_CTL_GET_SYSIO_SOCKOPT, &sockopt);
676
677 val /= 2;
678
679 tmp = 1.0 - tmp;
680
681 val = (float)val*tmp;
682
683 sockopt.optlen  = sizeof(val);
684
685 roar_vio_ctl(&(vss->vio), ROAR_VIO_CTL_SET_SYSIO_SOCKOPT, &sockopt);
686}
687#endif
688
689roar_mus_t roar_vs_latency(roar_vs_t * vss, int backend, int * error) {
690 ssize_t pos  = roar_vs_position(vss, backend, error);
691 ssize_t bps;  // byte per sample
692 size_t  lioc; // local IO (byte) counter
693 size_t  lpos; // local possition
694 signed long long int lag;
695
696// printf("pos=%zi\n", pos);
697
698 _initerr();
699
700 _ckvss(-1);
701
702 if (pos == -1) {
703  _seterrre();
704  return 0;
705 }
706
707 if ( !(vss->flags & FLAG_STREAM) ) {
708  _seterr(ROAR_ERROR_INVAL);
709  return 0;
710 }
711
712 if ( vss->writec == 0 ) {
713  lioc = vss->readc;
714 } else {
715  //printf("writec=%zu\n", vss->writec);
716  lioc = vss->writec;
717 }
718
719 bps = roar_info2samplesize(&(vss->info));
720
721 if ( bps == -1 ) {
722  _seterrre();
723  return 0;
724 }
725
726 lpos = (lioc*8) / bps;
727
728 //printf("pos=%zi, lpos=%zi, bps=%zi, diff[lpos-pos]=%zi\n", pos, lpos, bps, (lpos - pos));
729
730 lag = (signed long long int)lpos - (signed long long int)pos;
731 lag /= vss->info.channels;
732
733 // we now have the lag in frames
734 // return value are mus
735 // so we need to multiply with 1s/mus and
736 // multiply by 1/rate
737
738 lag *= 1000000; // 1s/mus
739 lag /= vss->info.rate;
740
741 if ( lag == 0 ) {
742  _seterr(ROAR_ERROR_NONE);
743 }
744
745#ifdef _HAVE_SOCKOPT
746 if (vss->latc.target > vss->latc.minlag) {
747  roar_vs_latency_managed(vss, lag);
748 }
749#endif
750
751 return lag;
752}
753
754static int roar_vs_flag(roar_vs_t * vss, int flag, int val, int * error) {
755 struct roar_stream_info info;
756 int old = -1;
757
758 _ckvss(-1);
759
760 if ( !(vss->flags & FLAG_STREAM) ) {
761  _seterr(ROAR_ERROR_INVAL);
762  return -1;
763 }
764
765 if ( val != ROAR_VS_ASK )
766  old = roar_vs_flag(vss, flag, ROAR_VS_ASK, error);
767
768 _initerr();
769
770 switch (val) {
771  case ROAR_VS_TRUE:
772  case ROAR_VS_FALSE:
773    if ( roar_stream_set_flags(vss->con, &(vss->stream), flag,
774                               val == ROAR_VS_TRUE ? ROAR_SET_FLAG : ROAR_RESET_FLAG) == -1 ) {
775     _seterrre();
776     return -1;
777    }
778    return old;
779   break;
780  case ROAR_VS_TOGGLE:
781    return roar_vs_flag(vss, flag, old == ROAR_VS_TRUE ? ROAR_VS_FALSE : ROAR_VS_TRUE, error);
782   break;
783  case ROAR_VS_ASK:
784    if ( roar_stream_get_info(vss->con, &(vss->stream), &info) == -1 ) {
785     _seterrre();
786     return -1;
787    }
788    return info.flags & flag ? ROAR_VS_TRUE : ROAR_VS_FALSE;
789   break;
790 }
791
792 _seterr(ROAR_ERROR_INVAL);
793 return -1;
794}
795
796int     roar_vs_pause(roar_vs_t * vss, int val, int * error) {
797 return roar_vs_flag(vss, ROAR_FLAG_PAUSE, val, error);
798}
799
800int     roar_vs_mute (roar_vs_t * vss, int val, int * error) {
801 return roar_vs_flag(vss, ROAR_FLAG_MUTE, val, error);
802}
803
804static int roar_vs_volume (roar_vs_t * vss, float * c, size_t channels, int * error) {
805 struct roar_mixer_settings mixer;
806 size_t i;
807 register float s, max_s = -100.0, scale = 65535.0;
808 int oldchannels;
809 int mode = ROAR_SET_VOL_ALL;
810
811 _ckvss(-1);
812
813 if ( !(vss->flags & FLAG_STREAM) ) {
814  _seterr(ROAR_ERROR_INVAL);
815  return -1;
816 }
817
818 if ( channels > ROAR_MAX_CHANNELS ) {
819  _seterr(ROAR_ERROR_INVAL);
820  return -1;
821 }
822
823 _initerr();
824
825 if ( roar_get_vol(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, &oldchannels) == -1 ) {
826  _seterrre();
827  return -1;
828 }
829
830 if ( vss->flags & FLAG_FREE_VOL ) {
831  for (i = 0; i < channels; i++) {
832   if ( c[i] > max_s ) {
833    max_s = c[i];
834   } else if ( c[i] < -0.01 ) {
835    _seterr(ROAR_ERROR_RANGE);
836    return -1;
837   }
838  }
839
840  if ( max_s > 1.00 ) {
841   scale = 65535.0 / max_s;
842  }
843 }
844
845 for (i = 0; i < channels; i++) {
846  if ( vss->flags & FLAG_FREE_VOL ) {
847   s = c[i] * scale;
848   if ( s < 0.0 )
849    s = 0.0;
850  } else {
851   s = c[i] * 65535.0;
852   if ( s > 66190.0 || s < -655.0 ) {
853    _seterr(ROAR_ERROR_RANGE);
854    return -1;
855   } else if ( s > 65535.0 ) {
856    s = 65535.0;
857   } else if ( s <     0.0 ) {
858    s = 0.0;
859   }
860  }
861  mixer.mixer[i] = s;
862 }
863
864 if ( vss->flags & FLAG_FREE_VOL ) {
865  mixer.scale = scale;
866 } else {
867  mixer.scale = 65535;
868 }
869
870 if ( channels != oldchannels )
871  mode = ROAR_SET_VOL_UNMAPPED;
872
873 if ( roar_set_vol2(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, channels, mode) == 0 )
874  return 0;
875
876 if ( mode == ROAR_SET_VOL_ALL ) {
877  _seterrre();
878  return -1;
879 }
880
881 if ( roar_conv_volume(&mixer, &mixer, oldchannels, channels) == -1 ) {
882  _seterrre();
883  return -1;
884 }
885
886 channels = oldchannels;
887 mode     = ROAR_SET_VOL_ALL;
888
889 if ( roar_set_vol2(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, channels, mode) == -1 ) {
890  _seterrre();
891  return -1;
892 }
893
894 return 0;
895}
896
897int     roar_vs_volume_mono   (roar_vs_t * vss, float c, int * error) {
898 return roar_vs_volume(vss, &c, 1, error);
899}
900
901int     roar_vs_volume_stereo (roar_vs_t * vss, float l, float r, int * error) {
902 float c[2] = {l, r};
903 return roar_vs_volume(vss, c, 2, error);
904}
905
906int     roar_vs_volume_get    (roar_vs_t * vss, float * l, float * r, int * error) {
907 struct roar_mixer_settings mixer;
908 int channels;
909
910 if ( vss == NULL || l == NULL || r == NULL ) {
911  _seterr(ROAR_ERROR_INVAL);
912  return -1;
913 }
914
915 if ( !(vss->flags & FLAG_STREAM) ) {
916  _seterr(ROAR_ERROR_INVAL);
917  return -1;
918 }
919
920 _initerr();
921
922 if ( roar_get_vol(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, &channels) == -1 ) {
923  _seterrre();
924  return -1;
925 }
926
927 if ( channels == 1 )
928  mixer.mixer[1] = mixer.mixer[0];
929
930 *l = mixer.mixer[0] / (float)mixer.scale;
931 *r = mixer.mixer[1] / (float)mixer.scale;
932
933 return 0;
934}
935
936int     roar_vs_meta          (roar_vs_t * vss, struct roar_keyval * kv, size_t len, int * error) {
937 struct roar_meta meta;
938 size_t i;
939 int type;
940 int ret = 0;
941
942 _ckvss(-1);
943
944 if ( !(vss->flags & FLAG_STREAM) ) {
945  _seterr(ROAR_ERROR_INVAL);
946  return -1;
947 }
948
949 meta.type   = ROAR_META_TYPE_NONE;
950 meta.key[0] = 0;
951 meta.value  = NULL;
952
953 _initerr();
954
955 if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_CLEAR, &meta) == -1 ) {
956  _seterrre();
957  ret = -1;
958 }
959
960 for (i = 0; i < len; i++) {
961  type = roar_meta_inttype(kv[i].key);
962  meta.type  = type;
963  meta.value = kv[i].value;
964
965  if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_ADD, &meta) == -1 ) {
966   _seterrre();
967   ret = -1;
968  }
969 }
970
971 meta.type   = ROAR_META_TYPE_NONE;
972 meta.key[0] = 0;
973 meta.value  = NULL;
974 if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_FINALIZE, &meta) == -1 ) {
975  _seterrre();
976  ret = -1;
977 }
978
979 return ret;
980}
981
982int     roar_vs_role          (roar_vs_t * vss, int role, int * error) {
983 _ckvss(-1);
984
985 if ( !(vss->flags & FLAG_STREAM) ) {
986  _seterr(ROAR_ERROR_INVAL);
987  return -1;
988 }
989
990 _initerr();
991
992 if ( roar_stream_set_role(vss->con, &(vss->stream), role) == -1 ) {
993  _seterrre();
994  return -1;
995 }
996
997 return 0;
998}
999
1000
1001int     roar_vs_iterate       (roar_vs_t * vss, int wait, int * error) {
1002 struct roar_vio_select vios[3];
1003 struct roar_vio_selecttv rtv = {.sec = 0, .nsec = 1};
1004 size_t len = 0;
1005 ssize_t i;
1006 ssize_t ret;
1007 int can_read = 0, can_write = 0;
1008 int can_flush_stream = 0, can_flush_file = 0;
1009 int is_eof = 0;
1010 void * data;
1011 size_t tmp;
1012
1013 // TODO: fix error handling below.
1014
1015 _ckvss(-1);
1016
1017 if ( wait != ROAR_VS_WAIT && wait != ROAR_VS_NOWAIT ) {
1018  _seterr(ROAR_ERROR_INVAL);
1019  return -1;
1020 }
1021
1022 ROAR_VIO_SELECT_SETVIO(&(vios[len]), &(vss->vio), ((vss->flags & FLAG_DIR_IN  ? ROAR_VIO_SELECT_READ  : 0) |
1023                                                    (vss->flags & FLAG_DIR_OUT ? ROAR_VIO_SELECT_WRITE : 0)));
1024 vios[len].ud.vp = &(vss->vio);
1025 len++;
1026
1027 ROAR_VIO_SELECT_SETVIO(&(vios[len]), roar_get_connection_vio2(vss->con), ROAR_VIO_SELECT_READ);
1028 vios[len].ud.vp = vss->con;
1029 len++;
1030
1031
1032// TODO: FIXME: need to do two select()s so we can sleep more efficently and test for both directions.
1033// for the moment we disable file direction and hope it will not block anyway...
1034/*
1035 if ( vss->file != NULL ) {
1036  ROAR_VIO_SELECT_SETVIO(&(vios[len]), vss->file, ((vss->flags & FLAG_DIR_IN  ? ROAR_VIO_SELECT_WRITE : 0) |
1037                                                   (vss->flags & FLAG_DIR_OUT ? ROAR_VIO_SELECT_READ  : 0)));
1038  vios[len].ud.vp = vss->file;
1039  len++;
1040 }
1041*/
1042
1043 ret = roar_vio_select(vios, len, (wait == ROAR_VS_NOWAIT ? &rtv : NULL), NULL);
1044
1045// part 2 of above hack:
1046// emulate read handle.
1047  if ( vss->file != NULL ) {
1048   vios[len].ud.vp   = vss->file;
1049   vios[len].eventsa = ROAR_VIO_SELECT_WRITE|ROAR_VIO_SELECT_READ;
1050   len++;
1051  }
1052
1053 // no error here nor EOF.
1054 if ( ret == 0 )
1055  return 1;
1056
1057 for (i = 0; i < len; i++) {
1058  if ( !vios[i].eventsa )
1059   continue;
1060
1061  if ( vios[i].ud.vp == &(vss->vio) ) {
1062   if ( vios[i].eventsa & ROAR_VIO_SELECT_READ )
1063    can_read++;
1064
1065   if ( vios[i].eventsa & ROAR_VIO_SELECT_WRITE ) {
1066    can_write++;
1067    can_flush_stream = 1;
1068   }
1069  } else if ( vios[i].ud.vp == vss->con ) {
1070   roar_sync(vss->con);
1071  } else if ( vss->file != NULL && vios[i].ud.vp == vss->file ) {
1072   if ( vios[i].eventsa & ROAR_VIO_SELECT_READ )
1073    can_write++;
1074
1075   if ( vios[i].eventsa & ROAR_VIO_SELECT_WRITE ) {
1076    can_read++;
1077    can_flush_file = 1;
1078   }
1079  }
1080 }
1081
1082 if ( vss->flags & FLAG_BUFFERED ) {
1083  if ( roar_buffer_ring_avail(vss->readring, NULL, &tmp) != -1 )
1084   if ( tmp > 0 )
1085    can_read++;
1086
1087// no check here to just let the read return zero and we do EOF handling.
1088/*
1089  if ( roar_buffer_ring_avail(vss->writering, &tmp, NULL) != -1 )
1090   if ( tmp > 0 )
1091*/
1092    can_write++;
1093 }
1094
1095 ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p): can_read=%i, can_write=%i", vss, wait, error, can_read, can_write);
1096
1097 // TODO: FIXME: Need to correct error handling here!
1098
1099 if ( can_flush_stream && vss->writebuffer != NULL ) {
1100  if ( roar_buffer_get_data(vss->writebuffer, &data) == -1 )
1101   return -1;
1102
1103  if ( roar_buffer_get_len(vss->writebuffer, &len) == -1 )
1104   return -1;
1105
1106  ret = roar_vs_write_direct(vss, data, len, error);
1107
1108  if ( ret == -1 ) {
1109   return -1;
1110  } else if ( ret == len ) {
1111   roar_buffer_free(vss->writebuffer);
1112   vss->writebuffer = NULL;
1113  } else {
1114   if ( roar_buffer_set_offset(vss->writebuffer, ret) == -1 )
1115    return -1;
1116  }
1117 }
1118
1119 if ( can_flush_file && vss->readbuffer != NULL ) {
1120  if ( roar_buffer_get_data(vss->readbuffer, &data) == -1 )
1121   return -1;
1122
1123  if ( roar_buffer_get_len(vss->readbuffer, &len) == -1 )
1124   return -1;
1125
1126  ret = roar_vio_write(vss->file, data, len);
1127
1128  if ( ret == -1 ) {
1129   return -1;
1130  } else if ( ret == len ) {
1131   roar_buffer_free(vss->readbuffer);
1132   vss->readbuffer = NULL;
1133  } else {
1134   if ( roar_buffer_set_offset(vss->readbuffer, ret) == -1 )
1135    return -1;
1136  }
1137 }
1138
1139#define _READ_SIZE 1024
1140
1141 if ( can_read == 2 && vss->readbuffer == NULL ) {
1142  if ( roar_buffer_new_data(&(vss->readbuffer), (len = _READ_SIZE), &data) == -1 )
1143   return -1;
1144
1145  ret = roar_vs_read(vss, data, len, error);
1146
1147  if ( ret == -1 ) {
1148   roar_buffer_free(vss->readbuffer);
1149   vss->readbuffer = NULL;
1150   return -1;
1151  } else if ( ret == 0 ) {
1152   is_eof = 1;
1153   roar_buffer_free(vss->readbuffer);
1154   vss->readbuffer = NULL;
1155  } else {
1156   len = ret;
1157   if ( roar_buffer_set_len(vss->readbuffer, len) == -1 )
1158    return -1;
1159
1160   if ( vss->flags & FLAG_BUFFERED ) {
1161    tmp = len;
1162    if ( roar_buffer_ring_write(vss->readring, data, &tmp) == -1 ) {
1163     ret = -1;
1164    } else {
1165     ret = tmp;
1166    }
1167   } else {
1168    ret = roar_vio_write(vss->file, data, len);
1169   }
1170
1171   if ( ret == -1 ) {
1172    return -1;
1173   } else if ( ret == len ) {
1174    roar_buffer_free(vss->readbuffer);
1175    vss->readbuffer = NULL;
1176   } else {
1177    if ( roar_buffer_set_offset(vss->readbuffer, ret) == -1 )
1178     return -1;
1179   }
1180  }
1181 }
1182
1183 if ( can_write == 2 && vss->writebuffer == NULL ) {
1184  if ( roar_buffer_new_data(&(vss->writebuffer), (len = _READ_SIZE), &data) == -1 )
1185   return -1;
1186
1187  if ( vss->flags & FLAG_BUFFERED ) {
1188   tmp = len;
1189   if ( roar_buffer_ring_read(vss->writering, data, &tmp) == -1 ) {
1190    ret = -1;
1191   } else {
1192    ret = tmp;
1193   }
1194  } else {
1195   ret = roar_vio_read(vss->file, data, len);
1196  }
1197
1198  ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p): ret=%lli", vss, wait, error, (long long int)ret);
1199
1200  if ( ret == -1 ) {
1201   ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error);
1202
1203   roar_buffer_free(vss->writebuffer);
1204   vss->writebuffer = NULL;
1205   return -1;
1206  } else if ( ret == 0 ) {
1207   ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error);
1208
1209   is_eof = 1;
1210   roar_buffer_free(vss->writebuffer);
1211   vss->writebuffer = NULL;
1212  } else {
1213   ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error);
1214
1215   if ( len != ret ) {
1216    len = ret;
1217    if ( roar_buffer_set_len(vss->writebuffer, len) == -1 )
1218     return -1;
1219   }
1220
1221   ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error);
1222
1223   ret = roar_vs_write_direct(vss, data, len, error);
1224
1225   if ( ret == -1 ) {
1226    return -1;
1227   } else if ( ret == len ) {
1228    roar_buffer_free(vss->writebuffer);
1229    vss->writebuffer = NULL;
1230   } else {
1231    if ( roar_buffer_set_offset(vss->writebuffer, ret) == -1 )
1232     return -1;
1233   }
1234  }
1235 }
1236
1237 return is_eof ? 0 : 2;
1238}
1239
1240int     roar_vs_run           (roar_vs_t * vss, int * error) {
1241 int ret;
1242
1243 _ckvss(-1);
1244
1245 while ((ret = roar_vs_iterate(vss, ROAR_VS_WAIT, error)) > 0);
1246
1247 ROAR_DBG("roar_vs_run(vss=%p, error=%p): ret=%i", vss, error, ret);
1248
1249 if ( ret == 0 ) {
1250  // flush buffers:
1251  roar_vs_iterate(vss, ROAR_VS_WAIT, error);
1252 }
1253
1254 if ( roar_vs_sync(vss, ROAR_VS_WAIT, error) == -1 )
1255  return -1;
1256
1257 return ret;
1258}
1259
1260ssize_t roar_vs_get_avail_read(roar_vs_t * vss, int * error) {
1261 size_t len;
1262
1263 _ckvss(-1);
1264
1265 if ( !(vss->flags & FLAG_BUFFERED) ) {
1266  _seterr(ROAR_ERROR_INVAL);
1267  return -1;
1268 }
1269
1270 if ( roar_buffer_ring_avail(vss->readring, &len, NULL) == -1 ) {
1271  _seterrre();
1272  return -1;
1273 }
1274
1275 return len;
1276}
1277
1278ssize_t roar_vs_get_avail_write(roar_vs_t * vss, int * error) {
1279 size_t len;
1280
1281 _ckvss(-1);
1282
1283 if ( !(vss->flags & FLAG_BUFFERED) ) {
1284  _seterr(ROAR_ERROR_INVAL);
1285  return -1;
1286 }
1287
1288 if ( roar_buffer_ring_avail(vss->writering, NULL, &len) == -1 ) {
1289  _seterrre();
1290  return -1;
1291 }
1292
1293 return len;
1294}
1295
1296int     roar_vs_reset_buffer(roar_vs_t * vss, int writering, int readring, int * error) {
1297 _ckvss(-1);
1298
1299 if ( !(vss->flags & FLAG_BUFFERED) ) {
1300  _seterr(ROAR_ERROR_INVAL);
1301  return -1;
1302 }
1303
1304 if ( writering != ROAR_VS_TRUE || writering != ROAR_VS_FALSE ||
1305      readring  != ROAR_VS_TRUE || readring  != ROAR_VS_FALSE ) {
1306  _seterr(ROAR_ERROR_INVAL);
1307  return -1;
1308 }
1309
1310 if ( writering ) {
1311  if ( roar_buffer_ring_reset(vss->writering) == -1 ) {
1312   _seterrre();
1313   return -1;
1314  }
1315 }
1316
1317 if ( readring ) {
1318  if ( roar_buffer_ring_reset(vss->readring) == -1 ) {
1319   _seterrre();
1320   return -1;
1321  }
1322 }
1323
1324 return 0;
1325}
1326
1327int     roar_vs_ctl           (roar_vs_t * vss, roar_vs_ctlcmd cmd, void * argp, int * error) {
1328 _ckvss(-1);
1329
1330 switch (cmd) {
1331  case ROAR_VS_CMD_NOOP:
1332   break;
1333  case ROAR_VS_CMD_SET_MIXER:
1334    vss->mixerid = *(int*)argp;
1335   break;
1336  case ROAR_VS_CMD_GET_MIXER:
1337    *(int*)argp = vss->mixerid;
1338   break;
1339  case ROAR_VS_CMD_SET_FIRST_PRIM:
1340    vss->first_primid = *(int*)argp;
1341   break;
1342  case ROAR_VS_CMD_GET_FIRST_PRIM:
1343    *(int*)argp = vss->first_primid;
1344   break;
1345#ifdef _HAVE_SOCKOPT
1346  case ROAR_VS_CMD_SET_LATC_P:
1347    vss->latc.p = *(float*)argp;
1348   break;
1349  case ROAR_VS_CMD_GET_LATC_P:
1350    *(float*)argp = vss->latc.p;
1351   break;
1352  case ROAR_VS_CMD_SET_LATC_TARGET:
1353    vss->latc.target = *(float*)argp;
1354   break;
1355  case ROAR_VS_CMD_GET_LATC_TARGET:
1356    *(float*)argp = vss->latc.target;
1357   break;
1358  case ROAR_VS_CMD_SET_LATC_WINDOW:
1359    vss->latc.window = *(float*)argp;
1360   break;
1361  case ROAR_VS_CMD_GET_LATC_WINDOW:
1362    *(float*)argp = vss->latc.window;
1363   break;
1364  case ROAR_VS_CMD_SET_LATC_MINLAG:
1365    vss->latc.minlag = *(float*)argp;
1366   break;
1367  case ROAR_VS_CMD_GET_LATC_MINLAG:
1368    *(float*)argp = vss->latc.minlag;
1369   break;
1370#endif
1371  case ROAR_VS_CMD_SET_FREE_VOLUME:
1372    switch (*(int*)argp) {
1373     case ROAR_VS_TRUE:
1374       vss->flags |= FLAG_FREE_VOL;
1375      break;
1376     case ROAR_VS_FALSE:
1377       vss->flags |= FLAG_FREE_VOL;
1378       vss->flags -= FLAG_FREE_VOL;
1379      break;
1380     case ROAR_VS_TOGGLE:
1381       if ( vss->flags & FLAG_FREE_VOL ) {
1382        vss->flags -= FLAG_FREE_VOL;
1383       } else {
1384        vss->flags |= FLAG_FREE_VOL;
1385       }
1386      break;
1387     default:
1388       _seterr(ROAR_ERROR_INVAL);
1389       return -1;
1390      break;
1391    }
1392   break;
1393  case ROAR_VS_CMD_GET_FREE_VOLUME:
1394    *(int*)argp = vss->flags & FLAG_FREE_VOL ? ROAR_VS_TRUE : ROAR_VS_FALSE;
1395   break;
1396  case ROAR_VS_CMD_SET_DEFAULT_PAUSED:
1397    switch (*(int*)argp) {
1398     case ROAR_VS_TRUE:
1399       vss->flags |= FLAG_DEF_PAUSE;
1400      break;
1401     case ROAR_VS_FALSE:
1402       vss->flags |= FLAG_DEF_PAUSE;
1403       vss->flags -= FLAG_DEF_PAUSE;
1404      break;
1405     case ROAR_VS_TOGGLE:
1406       if ( vss->flags & FLAG_DEF_PAUSE ) {
1407        vss->flags -= FLAG_DEF_PAUSE;
1408       } else {
1409        vss->flags |= FLAG_DEF_PAUSE;
1410       }
1411      break;
1412     default:
1413       _seterr(ROAR_ERROR_INVAL);
1414       return -1;
1415      break;
1416    }
1417   break;
1418  case ROAR_VS_CMD_GET_DEFAULT_PAUSED:
1419    *(int*)argp = vss->flags & FLAG_DEF_PAUSE ? ROAR_VS_TRUE : ROAR_VS_FALSE;
1420   break;
1421// use ifndef here so warnings of unhandled enum values will be shown in DEBUG mode.
1422#ifndef DEBUG
1423  default:
1424    _seterr(ROAR_ERROR_INVAL);
1425    return -1;
1426   break;
1427#endif
1428 }
1429
1430 return 0;
1431}
1432
1433struct roar_connection * roar_vs_connection_obj(roar_vs_t * vss, int * error) {
1434 _ckvss(NULL);
1435
1436 return vss->con;
1437}
1438
1439struct roar_stream     * roar_vs_stream_obj    (roar_vs_t * vss, int * error) {
1440 _ckvss(NULL);
1441
1442 if ( !(vss->flags & FLAG_STREAM) ) {
1443  _seterr(ROAR_ERROR_INVAL);
1444  return NULL;
1445 }
1446
1447 return &(vss->stream);
1448}
1449
1450struct roar_vio_calls  * roar_vs_vio_obj       (roar_vs_t * vss, int * error) {
1451 _ckvss(NULL);
1452
1453 if ( !(vss->flags & FLAG_STREAM) ) {
1454  _seterr(ROAR_ERROR_INVAL);
1455  return NULL;
1456 }
1457
1458 return &(vss->vio);
1459}
1460
1461//ll
Note: See TracBrowser for help on using the repository browser.