//vs.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2010-2011 * * This file is part of libroar a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * libroar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * NOTE for everyone want's to change something and send patches: * read README and HACKING! There a addition information on * the license of this document you need to read before you send * any patches. * * NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc * or libpulse*: * The libs libroaresd, libroararts and libroarpulse link this lib * and are therefore GPL. Because of this it may be illigal to use * them with any software that uses libesd, libartsc or libpulse*. */ #include "libroar.h" #if defined(ROAR_HAVE_GETSOCKOPT) && defined(ROAR_HAVE_SETSOCKOPT) #define _HAVE_SOCKOPT #endif #define FLAG_NONE 0x0000 #define FLAG_STREAM 0x0001 #define FLAG_NONBLOCK 0x0002 #define FLAG_BUFFERED 0x0004 #define FLAG_CLOSEFILE 0x0008 #define FLAG_FREE_VOL 0x0010 #define FLAG_DIR_IN 0x1000 #define FLAG_DIR_OUT 0x2000 #define _initerr() roar_err_clear_all() #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) #define _seterrre() do { _seterr(roar_error); } while(0) #define _seterrse() do { roar_err_from_errno(); _seterr(roar_error); } while(0) #define _ckvss(ret) do { if ( vss == NULL ) { _seterr(ROAR_ERROR_INVAL); return (ret); } } while(0) struct roar_vs { int flags; struct roar_connection con_store; struct roar_connection * con; struct roar_stream stream; struct roar_vio_calls vio; struct roar_audio_info info; size_t readc, writec; int mixerid; int first_primid; struct roar_buffer * readbuffer, * writebuffer; struct roar_vio_calls file_store; struct roar_vio_calls * file; struct roar_buffer * readring, * writering; #ifdef _HAVE_SOCKOPT struct { float target; float window; float minlag; float p; } latc; #endif }; static int _roar_vs_find_first_prim(roar_vs_t * vss); const char * roar_vs_strerr(int error) { const char * ret = roar_error2str(error); if ( ret == NULL ) return "(unknown)"; return ret; } static roar_vs_t * roar_vs_init(int * error) { roar_vs_t * vss = roar_mm_malloc(sizeof(roar_vs_t)); if ( vss == NULL ) { _seterrse(); return NULL; } memset(vss, 0, sizeof(roar_vs_t)); vss->mixerid = -1; vss->first_primid = -1; #ifdef _HAVE_SOCKOPT vss->latc.target = -2.; // must be less than latc.minlag! vss->latc.window = -1.; vss->latc.minlag = -1.; vss->latc.p = 0.005; #endif return vss; } roar_vs_t * roar_vs_new_from_con(struct roar_connection * con, int * error) { roar_vs_t * vss = roar_vs_init(error); if ( vss == NULL ) return NULL; vss->con = con; return vss; } roar_vs_t * roar_vs_new(const char * server, const char * name, int * error) { roar_vs_t * vss = roar_vs_init(error); int ret; if ( vss == NULL ) return NULL; vss->con = &(vss->con_store); _initerr(); ret = roar_simple_connect(vss->con, (char*)server, (char*)name); if ( ret == -1 ) { roar_vs_close(vss, ROAR_VS_TRUE, NULL); _seterrre(); return NULL; } return vss; } int roar_vs_stream(roar_vs_t * vss, const struct roar_audio_info * info, int dir, int * error) { struct roar_stream_info sinfo; int ret; _ckvss(-1); if ( vss->flags & FLAG_STREAM ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( info != &(vss->info) ) memcpy(&(vss->info), info, sizeof(struct roar_audio_info)); ret = roar_vio_simple_new_stream_obj(&(vss->vio), vss->con, &(vss->stream), info->rate, info->channels, info->bits, info->codec, dir ); if ( ret == -1 ) { _seterrre(); return -1; } if ( roar_stream_get_info(vss->con, &(vss->stream), &sinfo) != -1 ) { // TODO: fix this: // as we currently do not support to select mixer we just check if we hit the // right one. if ( vss->mixerid != -1 && vss->mixerid != sinfo.mixer ) { _seterr(ROAR_ERROR_INVAL); // TODO: should we maybe use a diffrent value? roar_vio_close(&(vss->vio)); return -1; } vss->mixerid = sinfo.mixer; _roar_vs_find_first_prim(vss); } vss->flags |= FLAG_STREAM; switch (dir) { case ROAR_DIR_PLAY: vss->flags |= FLAG_DIR_OUT; break; } return 0; } roar_vs_t * roar_vs_new_simple(const char * server, const char * name, int rate, int channels, int codec, int bits, int dir, int * error) { roar_vs_t * vss = roar_vs_new(server, name, error); int ret; if (vss == NULL) return NULL; memset(&(vss->info), 0, sizeof(vss->info)); vss->info.rate = rate; vss->info.channels = channels; vss->info.codec = codec; vss->info.bits = bits; ret = roar_vs_stream(vss, &(vss->info), dir, error); if (ret == -1) { roar_vs_close(vss, ROAR_VS_TRUE, NULL); return NULL; } return vss; } int roar_vs_file(roar_vs_t * vss, struct roar_vio_calls * vio, int closefile, int * error) { _ckvss(-1); if ( vio == NULL || (closefile != ROAR_VS_TRUE && closefile != ROAR_VS_FALSE)) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( vss->file != NULL ) { _seterr(ROAR_ERROR_INVAL); return -1; } vss->file = vio; if ( closefile == ROAR_VS_TRUE ) vss->flags |= FLAG_CLOSEFILE; return 0; } int roar_vs_file_simple(roar_vs_t * vss, char * filename, int * error) { struct roar_vio_defaults def; struct roar_vio_calls * file; char buf[64]; ssize_t ret; int dir = O_RDONLY; int codec = -1; const char * content_type; _ckvss(-1); if ( vss->file != NULL ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( vss->flags & FLAG_STREAM ) { switch (vss->flags & (FLAG_DIR_IN|FLAG_DIR_OUT)) { case FLAG_DIR_IN: dir = O_WRONLY; break; case FLAG_DIR_OUT: dir = O_RDONLY; break; case FLAG_DIR_IN|FLAG_DIR_OUT: dir = O_RDWR; break; default: _seterr(ROAR_ERROR_INVAL); return -1; } } file = &(vss->file_store); _initerr(); if ( roar_vio_dstr_init_defaults(&def, ROAR_VIO_DEF_TYPE_NONE, dir, 0644) == -1 ) { _seterrre(); return -1; } if ( roar_vio_open_dstr(file, filename, &def, 1) == -1 ) { _seterrre(); return -1; } if ( !(vss->flags & FLAG_STREAM) ) { if ( roar_vio_ctl(file, ROAR_VIO_CTL_GET_MIMETYPE, &content_type) != -1 ) { codec = roar_mime2codec(content_type); } if ( codec == -1 ) { ret = roar_vio_read(file, buf, sizeof(buf)); codec = roar_file_codecdetect(buf, ret); if ( codec == -1 ) { roar_vio_close(file); _seterr(ROAR_ERROR_INVAL); // Other value? return -1; } if ( roar_vio_lseek(file, 0, SEEK_SET) != 0 ) { roar_vio_close(file); _seterrre(); return -1; } } memset(&(vss->info), 0, sizeof(vss->info)); vss->info.rate = ROAR_RATE_DEFAULT; vss->info.channels = ROAR_CHANNELS_DEFAULT; vss->info.codec = codec; vss->info.bits = ROAR_BITS_DEFAULT; ret = roar_vs_stream(vss, &(vss->info), ROAR_DIR_PLAY, error); if ( ret == -1 ) { roar_vio_close(file); return -1; } } if ( roar_vs_file(vss, file, ROAR_VS_TRUE, error) == -1 ) { roar_vio_close(file); return -1; } return 0; } roar_vs_t * roar_vs_new_from_file(const char * server, const char * name, char * filename, int * error) { roar_vs_t * vss = roar_vs_new(server, name, error); if ( vss == NULL ) return NULL; if ( roar_vs_file_simple(vss, filename, error) != 0 ) { roar_vs_close(vss, ROAR_VS_TRUE, NULL); return NULL; } return vss; } int roar_vs_buffer(roar_vs_t * vss, size_t buffer, int * error) { _ckvss(-1); if ( vss->flags & FLAG_BUFFERED ) return -1; if ( roar_buffer_ring_new(&(vss->readring), buffer, 0) == -1 ) { _seterrre(); return -1; } if ( roar_buffer_ring_new(&(vss->writering), buffer, 0) == -1 ) { _seterrre(); roar_buffer_free(vss->readring); vss->readring = NULL; return -1; } vss->flags |= FLAG_BUFFERED; return 0; } int roar_vs_close(roar_vs_t * vss, int killit, int * error) { if ( killit != ROAR_VS_TRUE && killit != ROAR_VS_FALSE ) { _seterr(ROAR_ERROR_INVAL); return -1; } _ckvss(-1); if ( vss->readbuffer != NULL ) roar_buffer_free(vss->readbuffer); if ( vss->writebuffer != NULL ) roar_buffer_free(vss->writebuffer); if ( vss->readring != NULL ) roar_buffer_free(vss->readring); if ( vss->writering != NULL ) roar_buffer_free(vss->writering); if ( vss->file != NULL && vss->flags & FLAG_CLOSEFILE ) roar_vio_close(vss->file); if ( vss->flags & FLAG_STREAM ) { if ( killit == ROAR_VS_TRUE ) { roar_kick(vss->con, ROAR_OT_STREAM, roar_stream_get_id(&(vss->stream))); } roar_vio_close(&(vss->vio)); } if ( vss->con == &(vss->con_store) ) { roar_disconnect(vss->con); } roar_mm_free(vss); return 0; } static ssize_t roar_vs_write_direct(roar_vs_t * vss, const void * buf, size_t len, int * error) { ssize_t ret = roar_vio_write(&(vss->vio), (void*)buf, len); if ( ret == -1 ) { #ifdef EAGAIN if ( roar_error == ROAR_ERROR_AGAIN ) return 0; #endif _seterrre(); } else { if ( !(vss->flags & FLAG_BUFFERED) ) { //printf("A: vss->writec=%zu, ret=%zi\n", vss->writec, ret); vss->writec += ret; } } //printf("B: vss->writec=%zu, ret=%zi\n", vss->writec, ret); return ret; } ssize_t roar_vs_write(roar_vs_t * vss, const void * buf, size_t len, int * error) { ssize_t ret; size_t writelen; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( vss->flags & FLAG_BUFFERED ) { writelen = len; if ( roar_buffer_ring_write(vss->writering, (void*)buf, &writelen) == -1 ) { _seterrre(); return -1; } ret = writelen; vss->writec += ret; } else { ret = roar_vs_write_direct(vss, buf, len, error); } return ret; } ssize_t roar_vs_read (roar_vs_t * vss, void * buf, size_t len, int * error) { ssize_t ret; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); ret = roar_vio_read(&(vss->vio), buf, len); if ( ret == -1 ) { _seterrre(); } else { vss->readc += ret; } return ret; } int roar_vs_sync (roar_vs_t * vss, int wait, int * error) { struct roar_event waits, triggered; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( wait != ROAR_VS_NOWAIT && wait != ROAR_VS_WAIT ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( roar_vio_sync(&(vss->vio)) == -1 ) { _seterrre(); return -1; } if ( wait == ROAR_VS_WAIT ) { memset(&waits, 0, sizeof(waits)); waits.event = ROAR_OE_STREAM_XRUN; waits.emitter = -1; waits.target = roar_stream_get_id(&(vss->stream)); waits.target_type = ROAR_OT_STREAM; if ( roar_wait(vss->con, &triggered, &waits, 1) == -1 ) { _seterrre(); return -1; } } return 0; } int roar_vs_blocking (roar_vs_t * vss, int val, int * error) { int old = -1; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } old = vss->flags & FLAG_NONBLOCK ? ROAR_VS_FALSE : ROAR_VS_TRUE; _initerr(); switch (val) { case ROAR_VS_TRUE: if ( roar_vio_nonblock(&(vss->vio), ROAR_SOCKET_BLOCK) == -1 ) { _seterrre(); return -1; } vss->flags |= FLAG_NONBLOCK; vss->flags -= FLAG_NONBLOCK; return old; break; case ROAR_VS_FALSE: if ( roar_vio_nonblock(&(vss->vio), ROAR_SOCKET_NONBLOCK) == -1 ) { _seterrre(); return -1; } vss->flags |= FLAG_NONBLOCK; return old; break; case ROAR_VS_TOGGLE: if ( old == ROAR_VS_TRUE ) { return roar_vs_blocking(vss, ROAR_VS_FALSE, error); } else { return roar_vs_blocking(vss, ROAR_VS_TRUE, error); } break; case ROAR_VS_ASK: return old; break; } _seterr(ROAR_ERROR_INVAL); return -1; } static int _roar_vs_find_first_prim(roar_vs_t * vss) { struct roar_stream stream; struct roar_stream_info info; int id[ROAR_STREAMS_MAX]; int num; int i; if ( vss->first_primid != -1 ) return vss->first_primid; if ( vss->mixerid == -1 ) return -1; if ( (num = roar_list_streams(vss->con, id, ROAR_STREAMS_MAX)) == -1 ) { return -1; } for (i = 0; i < num; i++) { if ( roar_get_stream(vss->con, &stream, id[i]) == -1 ) continue; if ( stream.dir != ROAR_DIR_OUTPUT ) continue; if ( roar_stream_get_info(vss->con, &stream, &info) == -1 ) continue; if ( info.mixer == vss->mixerid ) { vss->first_primid = id[i]; return id[i]; } } return -1; } ssize_t roar_vs_position(roar_vs_t * vss, int backend, int * error) { struct roar_stream stream; struct roar_stream out_stream; struct roar_stream_info out_info; size_t offset = 0; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( roar_get_stream(vss->con, &stream, roar_stream_get_id(&(vss->stream))) == -1 ) { _seterrre(); return -1; } if ( backend == ROAR_VS_BACKEND_DEFAULT ) { backend = ROAR_VS_BACKEND_FIRST; } switch (backend) { case ROAR_VS_BACKEND_NONE: return stream.pos; break; case ROAR_VS_BACKEND_FIRST: if ( vss->first_primid == -1 ) { _seterr(ROAR_ERROR_UNKNOWN); return -1; } backend = vss->first_primid; break; default: if ( backend < 0 ) { _seterr(ROAR_ERROR_INVAL); return -1; } break; } if ( backend >= 0 ) { roar_stream_new_by_id(&out_stream, backend); if ( roar_stream_get_info(vss->con, &out_stream, &out_info) == -1 ) { _seterrre(); return -1; } offset = out_info.delay * vss->info.rate; offset /= 1000000; } return stream.pos + offset; } #ifdef _HAVE_SOCKOPT static void roar_vs_latency_managed(roar_vs_t * vss, roar_mus_t lat) { struct roar_vio_sysio_sockopt sockopt; float tmp = ((float)lat/1000.0) - vss->latc.target; int val; tmp *= vss->latc.p; sockopt.level = SOL_SOCKET; sockopt.optname = SO_SNDBUF; sockopt.optval = &val; sockopt.optlen = sizeof(val); roar_vio_ctl(&(vss->vio), ROAR_VIO_CTL_GET_SYSIO_SOCKOPT, &sockopt); val /= 2; tmp = 1.0 - tmp; val = (float)val*tmp; sockopt.optlen = sizeof(val); roar_vio_ctl(&(vss->vio), ROAR_VIO_CTL_SET_SYSIO_SOCKOPT, &sockopt); } #endif roar_mus_t roar_vs_latency(roar_vs_t * vss, int backend, int * error) { ssize_t pos = roar_vs_position(vss, backend, error); ssize_t bps; // byte per sample size_t lioc; // local IO (byte) counter size_t lpos; // local possition signed long long int lag; // printf("pos=%zi\n", pos); _initerr(); _ckvss(-1); if (pos == -1) { _seterrre(); return 0; } if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return 0; } if ( vss->writec == 0 ) { lioc = vss->readc; } else { //printf("writec=%zu\n", vss->writec); lioc = vss->writec; } bps = roar_info2samplesize(&(vss->info)); if ( bps == -1 ) { _seterrre(); return 0; } lpos = (lioc*8) / bps; //printf("pos=%zi, lpos=%zi, bps=%zi, diff[lpos-pos]=%zi\n", pos, lpos, bps, (lpos - pos)); lag = (signed long long int)lpos - (signed long long int)pos; lag /= vss->info.channels; // we now have the lag in frames // return value are mus // so we need to multiply with 1s/mus and // multiply by 1/rate lag *= 1000000; // 1s/mus lag /= vss->info.rate; if ( lag == 0 ) { _seterr(ROAR_ERROR_NONE); } #ifdef _HAVE_SOCKOPT if (vss->latc.target > vss->latc.minlag) { roar_vs_latency_managed(vss, lag); } #endif return lag; } static int roar_vs_flag(roar_vs_t * vss, int flag, int val, int * error) { struct roar_stream_info info; int old = -1; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( val != ROAR_VS_ASK ) old = roar_vs_flag(vss, flag, ROAR_VS_ASK, error); _initerr(); switch (val) { case ROAR_VS_TRUE: case ROAR_VS_FALSE: if ( roar_stream_set_flags(vss->con, &(vss->stream), flag, val == ROAR_VS_TRUE ? ROAR_SET_FLAG : ROAR_RESET_FLAG) == -1 ) { _seterrre(); return -1; } return old; break; case ROAR_VS_TOGGLE: return roar_vs_flag(vss, flag, old == ROAR_VS_TRUE ? ROAR_VS_FALSE : ROAR_VS_TRUE, error); break; case ROAR_VS_ASK: if ( roar_stream_get_info(vss->con, &(vss->stream), &info) == -1 ) { _seterrre(); return -1; } return info.flags & flag ? ROAR_VS_TRUE : ROAR_VS_FALSE; break; } _seterr(ROAR_ERROR_INVAL); return -1; } int roar_vs_pause(roar_vs_t * vss, int val, int * error) { return roar_vs_flag(vss, ROAR_FLAG_PAUSE, val, error); } int roar_vs_mute (roar_vs_t * vss, int val, int * error) { return roar_vs_flag(vss, ROAR_FLAG_MUTE, val, error); } static int roar_vs_volume (roar_vs_t * vss, float * c, size_t channels, int * error) { struct roar_mixer_settings mixer; size_t i; register float s, max_s = -100.0, scale = 65535.0; int oldchannels; int mode = ROAR_SET_VOL_ALL; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( channels > ROAR_MAX_CHANNELS ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( roar_get_vol(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, &oldchannels) == -1 ) { _seterrre(); return -1; } if ( vss->flags & FLAG_FREE_VOL ) { for (i = 0; i < channels; i++) { if ( c[i] > max_s ) { max_s = c[i]; } else if ( c[i] < -0.01 ) { _seterr(ROAR_ERROR_RANGE); return -1; } } if ( max_s > 1.00 ) { scale = 65535.0 / max_s; } } for (i = 0; i < channels; i++) { if ( vss->flags & FLAG_FREE_VOL ) { s = c[i] * scale; if ( s < 0.0 ) s = 0.0; } else { s = c[i] * 65535.0; if ( s > 66190.0 || s < -655.0 ) { _seterr(ROAR_ERROR_RANGE); return -1; } else if ( s > 65535.0 ) { s = 65535.0; } else if ( s < 0.0 ) { s = 0.0; } } mixer.mixer[i] = s; } if ( vss->flags & FLAG_FREE_VOL ) { mixer.scale = scale; } else { mixer.scale = 65535; } if ( channels != oldchannels ) mode = ROAR_SET_VOL_UNMAPPED; if ( roar_set_vol2(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, channels, mode) == 0 ) return 0; if ( mode == ROAR_SET_VOL_ALL ) { _seterrre(); return -1; } if ( roar_conv_volume(&mixer, &mixer, oldchannels, channels) == -1 ) { _seterrre(); return -1; } channels = oldchannels; mode = ROAR_SET_VOL_ALL; if ( roar_set_vol2(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, channels, mode) == -1 ) { _seterrre(); return -1; } return 0; } int roar_vs_volume_mono (roar_vs_t * vss, float c, int * error) { return roar_vs_volume(vss, &c, 1, error); } int roar_vs_volume_stereo (roar_vs_t * vss, float l, float r, int * error) { float c[2] = {l, r}; return roar_vs_volume(vss, c, 2, error); } int roar_vs_volume_get (roar_vs_t * vss, float * l, float * r, int * error) { struct roar_mixer_settings mixer; int channels; if ( vss == NULL || l == NULL || r == NULL ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( roar_get_vol(vss->con, roar_stream_get_id(&(vss->stream)), &mixer, &channels) == -1 ) { _seterrre(); return -1; } if ( channels == 1 ) mixer.mixer[1] = mixer.mixer[0]; *l = mixer.mixer[0] / (float)mixer.scale; *r = mixer.mixer[1] / (float)mixer.scale; return 0; } int roar_vs_meta (roar_vs_t * vss, struct roar_keyval * kv, size_t len, int * error) { struct roar_meta meta; size_t i; int type; int ret = 0; _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } meta.type = ROAR_META_TYPE_NONE; meta.key[0] = 0; meta.value = NULL; _initerr(); if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_CLEAR, &meta) == -1 ) { _seterrre(); ret = -1; } for (i = 0; i < len; i++) { type = roar_meta_inttype(kv[i].key); meta.type = type; meta.value = kv[i].value; if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_ADD, &meta) == -1 ) { _seterrre(); ret = -1; } } meta.type = ROAR_META_TYPE_NONE; meta.key[0] = 0; meta.value = NULL; if ( roar_stream_meta_set(vss->con, &(vss->stream), ROAR_META_MODE_FINALIZE, &meta) == -1 ) { _seterrre(); ret = -1; } return ret; } int roar_vs_role (roar_vs_t * vss, int role, int * error) { _ckvss(-1); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return -1; } _initerr(); if ( roar_stream_set_role(vss->con, &(vss->stream), role) == -1 ) { _seterrre(); return -1; } return 0; } int roar_vs_iterate (roar_vs_t * vss, int wait, int * error) { struct roar_vio_select vios[3]; struct roar_vio_selecttv rtv = {.sec = 0, .nsec = 1}; size_t len = 0; ssize_t i; ssize_t ret; int can_read = 0, can_write = 0; int can_flush_stream = 0, can_flush_file = 0; int is_eof = 0; void * data; size_t tmp; // TODO: fix error handling below. _ckvss(-1); if ( wait != ROAR_VS_WAIT && wait != ROAR_VS_NOWAIT ) { _seterr(ROAR_ERROR_INVAL); return -1; } ROAR_VIO_SELECT_SETVIO(&(vios[len]), &(vss->vio), ((vss->flags & FLAG_DIR_IN ? ROAR_VIO_SELECT_READ : 0) | (vss->flags & FLAG_DIR_OUT ? ROAR_VIO_SELECT_WRITE : 0))); vios[len].ud.vp = &(vss->vio); len++; ROAR_VIO_SELECT_SETVIO(&(vios[len]), roar_get_connection_vio2(vss->con), ROAR_VIO_SELECT_READ); vios[len].ud.vp = vss->con; len++; // TODO: FIXME: need to do two select()s so we can sleep more efficently and test for both directions. // for the moment we disable file direction and hope it will not block anyway... /* if ( vss->file != NULL ) { ROAR_VIO_SELECT_SETVIO(&(vios[len]), vss->file, ((vss->flags & FLAG_DIR_IN ? ROAR_VIO_SELECT_WRITE : 0) | (vss->flags & FLAG_DIR_OUT ? ROAR_VIO_SELECT_READ : 0))); vios[len].ud.vp = vss->file; len++; } */ ret = roar_vio_select(vios, len, (wait == ROAR_VS_NOWAIT ? &rtv : NULL), NULL); // part 2 of above hack: // emulate read handle. if ( vss->file != NULL ) { vios[len].ud.vp = vss->file; vios[len].eventsa = ROAR_VIO_SELECT_WRITE|ROAR_VIO_SELECT_READ; len++; } // no error here nor EOF. if ( ret == 0 ) return 1; for (i = 0; i < len; i++) { if ( !vios[i].eventsa ) continue; if ( vios[i].ud.vp == &(vss->vio) ) { if ( vios[i].eventsa & ROAR_VIO_SELECT_READ ) can_read++; if ( vios[i].eventsa & ROAR_VIO_SELECT_WRITE ) { can_write++; can_flush_stream = 1; } } else if ( vios[i].ud.vp == vss->con ) { roar_sync(vss->con); } else if ( vss->file != NULL && vios[i].ud.vp == vss->file ) { if ( vios[i].eventsa & ROAR_VIO_SELECT_READ ) can_write++; if ( vios[i].eventsa & ROAR_VIO_SELECT_WRITE ) { can_read++; can_flush_file = 1; } } } if ( vss->flags & FLAG_BUFFERED ) { if ( roar_buffer_ring_avail(vss->readring, NULL, &tmp) != -1 ) if ( tmp > 0 ) can_read++; // no check here to just let the read return zero and we do EOF handling. /* if ( roar_buffer_ring_avail(vss->writering, &tmp, NULL) != -1 ) if ( tmp > 0 ) */ can_write++; } ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p): can_read=%i, can_write=%i", vss, wait, error, can_read, can_write); // TODO: FIXME: Need to correct error handling here! if ( can_flush_stream && vss->writebuffer != NULL ) { if ( roar_buffer_get_data(vss->writebuffer, &data) == -1 ) return -1; if ( roar_buffer_get_len(vss->writebuffer, &len) == -1 ) return -1; ret = roar_vs_write_direct(vss, data, len, error); if ( ret == -1 ) { return -1; } else if ( ret == len ) { roar_buffer_free(vss->writebuffer); vss->writebuffer = NULL; } else { if ( roar_buffer_set_offset(vss->writebuffer, ret) == -1 ) return -1; } } if ( can_flush_file && vss->readbuffer != NULL ) { if ( roar_buffer_get_data(vss->readbuffer, &data) == -1 ) return -1; if ( roar_buffer_get_len(vss->readbuffer, &len) == -1 ) return -1; ret = roar_vio_write(vss->file, data, len); if ( ret == -1 ) { return -1; } else if ( ret == len ) { roar_buffer_free(vss->readbuffer); vss->readbuffer = NULL; } else { if ( roar_buffer_set_offset(vss->readbuffer, ret) == -1 ) return -1; } } #define _READ_SIZE 1024 if ( can_read == 2 && vss->readbuffer == NULL ) { if ( roar_buffer_new_data(&(vss->readbuffer), (len = _READ_SIZE), &data) == -1 ) return -1; ret = roar_vs_read(vss, data, len, error); if ( ret == -1 ) { roar_buffer_free(vss->readbuffer); vss->readbuffer = NULL; return -1; } else if ( ret == 0 ) { is_eof = 1; roar_buffer_free(vss->readbuffer); vss->readbuffer = NULL; } else { len = ret; if ( roar_buffer_set_len(vss->readbuffer, len) == -1 ) return -1; if ( vss->flags & FLAG_BUFFERED ) { tmp = len; if ( roar_buffer_ring_write(vss->readring, data, &tmp) == -1 ) { ret = -1; } else { ret = tmp; } } else { ret = roar_vio_write(vss->file, data, len); } if ( ret == -1 ) { return -1; } else if ( ret == len ) { roar_buffer_free(vss->readbuffer); vss->readbuffer = NULL; } else { if ( roar_buffer_set_offset(vss->readbuffer, ret) == -1 ) return -1; } } } if ( can_write == 2 && vss->writebuffer == NULL ) { if ( roar_buffer_new_data(&(vss->writebuffer), (len = _READ_SIZE), &data) == -1 ) return -1; if ( vss->flags & FLAG_BUFFERED ) { tmp = len; if ( roar_buffer_ring_read(vss->writering, data, &tmp) == -1 ) { ret = -1; } else { ret = tmp; } } else { ret = roar_vio_read(vss->file, data, len); } ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p): ret=%lli", vss, wait, error, (long long int)ret); if ( ret == -1 ) { ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error); roar_buffer_free(vss->writebuffer); vss->writebuffer = NULL; return -1; } else if ( ret == 0 ) { ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error); is_eof = 1; roar_buffer_free(vss->writebuffer); vss->writebuffer = NULL; } else { ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error); if ( len != ret ) { len = ret; if ( roar_buffer_set_len(vss->writebuffer, len) == -1 ) return -1; } ROAR_DBG("roar_vs_iterate(vss=%p, wait=%i, error=%p) = ?", vss, wait, error); ret = roar_vs_write_direct(vss, data, len, error); if ( ret == -1 ) { return -1; } else if ( ret == len ) { roar_buffer_free(vss->writebuffer); vss->writebuffer = NULL; } else { if ( roar_buffer_set_offset(vss->writebuffer, ret) == -1 ) return -1; } } } return is_eof ? 0 : 2; } int roar_vs_run (roar_vs_t * vss, int * error) { int ret; _ckvss(-1); while ((ret = roar_vs_iterate(vss, ROAR_VS_WAIT, error)) > 0); ROAR_DBG("roar_vs_run(vss=%p, error=%p): ret=%i", vss, error, ret); if ( ret == 0 ) { // flush buffers: roar_vs_iterate(vss, ROAR_VS_WAIT, error); } if ( roar_vs_sync(vss, ROAR_VS_WAIT, error) == -1 ) return -1; return ret; } ssize_t roar_vs_get_avail_read(roar_vs_t * vss, int * error) { size_t len; _ckvss(-1); if ( !(vss->flags & FLAG_BUFFERED) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( roar_buffer_ring_avail(vss->readring, &len, NULL) == -1 ) { _seterrre(); return -1; } return len; } ssize_t roar_vs_get_avail_write(roar_vs_t * vss, int * error) { size_t len; _ckvss(-1); if ( !(vss->flags & FLAG_BUFFERED) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( roar_buffer_ring_avail(vss->writering, NULL, &len) == -1 ) { _seterrre(); return -1; } return len; } int roar_vs_reset_buffer(roar_vs_t * vss, int writering, int readring, int * error) { _ckvss(-1); if ( !(vss->flags & FLAG_BUFFERED) ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( writering != ROAR_VS_TRUE || writering != ROAR_VS_FALSE || readring != ROAR_VS_TRUE || readring != ROAR_VS_FALSE ) { _seterr(ROAR_ERROR_INVAL); return -1; } if ( writering ) { if ( roar_buffer_ring_reset(vss->writering) == -1 ) { _seterrre(); return -1; } } if ( readring ) { if ( roar_buffer_ring_reset(vss->readring) == -1 ) { _seterrre(); return -1; } } return 0; } int roar_vs_ctl (roar_vs_t * vss, roar_vs_ctlcmd cmd, void * argp, int * error) { _ckvss(-1); switch (cmd) { case ROAR_VS_CMD_NOOP: break; case ROAR_VS_CMD_SET_MIXER: vss->mixerid = *(int*)argp; break; case ROAR_VS_CMD_GET_MIXER: *(int*)argp = vss->mixerid; break; case ROAR_VS_CMD_SET_FIRST_PRIM: vss->first_primid = *(int*)argp; break; case ROAR_VS_CMD_GET_FIRST_PRIM: *(int*)argp = vss->first_primid; break; #ifdef _HAVE_SOCKOPT case ROAR_VS_CMD_SET_LATC_P: vss->latc.p = *(float*)argp; break; case ROAR_VS_CMD_GET_LATC_P: *(float*)argp = vss->latc.p; break; case ROAR_VS_CMD_SET_LATC_TARGET: vss->latc.target = *(float*)argp; break; case ROAR_VS_CMD_GET_LATC_TARGET: *(float*)argp = vss->latc.target; break; case ROAR_VS_CMD_SET_LATC_WINDOW: vss->latc.window = *(float*)argp; break; case ROAR_VS_CMD_GET_LATC_WINDOW: *(float*)argp = vss->latc.window; break; case ROAR_VS_CMD_SET_LATC_MINLAG: vss->latc.minlag = *(float*)argp; break; case ROAR_VS_CMD_GET_LATC_MINLAG: *(float*)argp = vss->latc.minlag; break; #endif case ROAR_VS_CMD_SET_FREE_VOLUME: vss->flags |= FLAG_FREE_VOL; if ( !*(int*)argp ) { vss->flags -= FLAG_FREE_VOL; } break; case ROAR_VS_CMD_GET_FREE_VOLUME: *(int*)argp = vss->flags & FLAG_FREE_VOL ? 1 : 0; break; // use ifndef here so warnings of unhandled enum values will be shown in DEBUG mode. #ifndef DEBUG default: return -1; break; #endif } return 0; } struct roar_connection * roar_vs_connection_obj(roar_vs_t * vss, int * error) { _ckvss(NULL); return vss->con; } struct roar_stream * roar_vs_stream_obj (roar_vs_t * vss, int * error) { _ckvss(NULL); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return NULL; } return &(vss->stream); } struct roar_vio_calls * roar_vs_vio_obj (roar_vs_t * vss, int * error) { _ckvss(NULL); if ( !(vss->flags & FLAG_STREAM) ) { _seterr(ROAR_ERROR_INVAL); return NULL; } return &(vss->vio); } //ll