For each item: - prove that the approach is correct - write the code - check that all instance of the bug are fixed - explain how it works and how to test - drop me a mail if you plan big changes --- make mixerctl use /dev/mixer0 and remove unused /dev/{audio,sound,mixer} links --- remove unused volume argument of midi_send_vol() --- simplify jack backend (the mainloop), prevent jack from choosing the period size, since this can deadlock it. --- sndio-ify vlc, look at jack backend --- remove the generic aucat framework (aproc & abuf structures) and hardcode things. Indeed experimentation is over and we don't need flexibility any longer --- in aucat, add a SOCK_DRAIN state and enter it whenever sio_stop() is called. Then block sio_write() until the buffer is actually drained. This is to prevent one client from creating multiple streams. --- make sure uaudio can run reliably with other block sizes than 44100 / 15 and remove this quirk from aucat. Possibly make libsndio use the magic block size as default. --- move _all_ initializations (including struct file allocation) in listen_init() and wav_init() and maintain lists in main() rather than using global lists with references to volatile structures. --- Seeking while recording fills the file with sparse blocks of zeros, but zero is not silence with unsigned samples --- libsndio doesn't handle SIGPIPE (we don't want libs to mess with signals), on the other hand we write to the socket that may be closed by aucat, resulting in a SIGPIPE. Possibly, change the protocol to use more acks To reproduce, try to set to invalid parameters, like par.bps = 100 --- bug: sometimes the stream is not attached at the right position. Example, first start aucat sudo aucat -l -r 48000 -z 120 -b 240 start any player: ogg123 /path/file.ogg Now start a low latency app (eg. a softsynth) and observe that the latency is very high. If ogg123 is killed and the softsynth restarted the latency drops back to its normal value. --- sio_aucat.c, sio_sun.c and sio_alsa.c work with an integer number of frames, while sio_read() and sio_write() use bytes and thus should be able to handle partial samples. Handle partial samples in sio.c and make sio_xxx.c backends use frames. --- AMSG_ISSET() tests only the most significant bit, which doesn't play very well with endiannes. Find a smarter macro. --- the following hangs (because device stays open, which is ok), but it makes hard to quickly send a file on a port echo -n | aucat -M -i - --- mplayer could use the "new" sio_setvol() interface --- $ cat *.c |wc -l 11847 this is ridiculous. Since the server structure is very simple, hardcode processing codepath rather than using the "struct aproc" framework --- use a union in the file structure (as we do for aproc and abuf structures) and remove pipe.c and pipe.h functions. --- for wav files if new mmcpos and oldmmc pos are the same, then don't seek to a new position --- in sock.c, call file_close() to terminate, instead of aproc_del() on one of the endpoints. Same in wav_exit() --- in libsndio, the actual volume change is delayed until the stream starts. This prevents a program to connect, change its volume and disconnect (volume change is lost). --- once in playback and/or recording is started, make socket code store packet headers in the abufs. This way a single write() syscall could be used for both samples and headers, which seems more efficient. --- find a method to log errors while in server mode (syslog?). Possibly use dbg_xxx() code, and add a syslog mode or whatever --- could we add a aproc_quit() routine (possibly new aproc_ops entry member) that raises the APROC_QUIT flag, and if the aproc doesn't contain data, then call aproc_del(). This could be used in dev_done() to terminated mix, sub, submon --- change pipe_read(...) into file_fdread(struct file *, int fd, ...), convert sock.c and wav.c to use file_fdxxx() and delete pipe.c --- Verify that ABUF_ROK() is _always_ called after a abuf_fill(). The input terminal (rfile) can be readable, but the samples of the intermediate converter buffers may be empty. This breaks mix_eof() in not calling abuf_run(). --- We have code that does: if (f->mode & PLAY) aproc_del(f->pipe.file.rproc); else aproc_del(f->pipe.file.wproc); return; couldn't we just do: file_close(f); return; --- In sun backend, onmove() callback is invoked from sio_revents() before the program starts reading. If reading is "slow", then the program may read more data that the callback said is readable, this may cause aucat (as client to sun) to get confused, and in turn to break it's clients. --- sio_onmove() callback is invoked from sio_revents(), but if the user is interested in neither POLLIN nor POLLOUT, then sio_revents() is not invoked and thus the user misses its callback. couldn't sio_pollfd() be extended to be called with a zero argument (and return 0), in a way allowing sio_revents() to be called without risking a busy loop ? --- can we start file_new() with WOK | ROK, so abuf_fill() doesn't fail before the first poll() --- there are zero-length ticks sent by aucat, especially during underruns. Fix it to never send a zero-length tick lock sock_{io}pos --- we often use if (!WOK) return if (!ROK) fill(ibuf) if (!ROK) return copy(obuf, ibuf); could we modify fill() to do nothing if the ibuf is not empty? Similarly, could we fix the followin: if (!ROK) return if (!WOK) flush(obuf) if (!WOK) return; --- we use (dev_bufsz * MTC_SEC), which can overflow, limit max buffer size --- aucat seems unable to work with 1-block device buffer (single block buffers are allowed when client to aucat). Either fix aucat to work, or disable signle block buffers. --- midicat dumps core when exitting, if one of the devices (eg. aucat:0) is destroyed before the exit, caused by abuf_done() being called on a non-empty buffer --- if we're not trying to write to a file/socket but WOK is set, then the peer can close the connection, and we will not notice it because the file is not polled and thus, we don't get the POLLHUP event --- it's not acceptable to merge real-time messages. Elect a master based on start, continue, tick and mtc messages and disable it after a stop or a timeout. Transmit only messages from the master. --- blocksize must be 15-bit, for resampling to work we don't verify this. --- par->bps and par->bits are not independent. This makes impossible to define a range of parameters by the product of the ranges of each parameters. Try to simplify that, for instance by replacing bps by a "nopad" flag. --- par->rate and par->round are not independent too, because the block duration is expressed in units of par->rate: if the rate changes, the block duration is streatched with it. This makes hard for the app to negotiate the rate maintaining the latency constant. --- are all encodings necessary? would that hurt us while improving conversion code? --- in struct abuf, replace bpf -> blksz; switch to block i/o approach, for files, if necessary set blksz to 1 frame ================================================================ check that CHAN_MAX is properly used everywhere => ok --- async messages: add isig() and osig() handlers to aprocs to handle and propagate ``deltas''. Delta should indicate the absolute position on the hardware => ok --- sub_eof(): abuf_eof() could destroy the buffer, should we call sub_rm() first ? => ok, abuf_eof() does removes it from the list. --- libsa: switch to simpler API not depending on errno => ok --- use always non-blocking read()/write() and call poll() in sa_read() and sa_write() to simulate blocking mode => ok --- libsa: device is opened O_RDWR but the mode can be changed later. There are two incompatible ways of changing the mode, which is bad. => ok, we set the mode once at sa_open() --- all non-DATA transfers require ACKs, add ACK packets to protocol => ok, actually only STOP requires it. --- server seems to allow different play and rec frequencies, it should fail => actually not, it was an azalia bug --- handle SETINFO() returning EINVAL => ok --- after few hours, aucat -l can no more dev_attach() new streams, probably because badly handled syncronization math (modulo 2^32 is hard to get right). Hint: => ok, fixed sun_revents() in libsa --- make aucat stop sending zeros and suspend when not used, so it can be started at system/session startup. => ok --- add volume knob, ex. sa_setvol() and sa_onvol() => ok --- split aconv to: * dec (s16 -> any) * enc (any -> s16) * resamp (resample s16->s16 only) => ok --- when a stream is attached or detached other stream levels are recalculated. When using around 2-3 streams it's annoying. Find a better algorithm to share dynamic. => ok --- aucat -o /tmp/xxx deadlock if server desappears => ok --- allow server to be play-only or record-only => ok -m, flag solves this --- make the -l option take the socket path as argument, allowing to listen on multiple sockets, allowing to define different devices ex: /tmp/aucat.front, /tmp/aucat.rear => added -s flag to do so --- add a ``channel shift'' to the listening socket, so that connecting clients see only upper channels. This would allow a simple app using stereo output to play on channels 6:7 instead of 0:1 => ok, made -Ccv apply to sockets --- make channel conversions in sub and mix, and leave aconv only for format and sample rates => ok, but only if client channels < server channels --- 'aucat -o file.wav' doesn't work on big endian machines => ok --- sock_read(), may call dev_attach(), that in turm may call sock_read() again. So, sock_read() should at any point be reentrant. => dev_attach() doesn't start processing, so sock_read() cannot be called --- are we sure to send the initial timestamp when recording? with sun and aucat backends => ok, fixed --- add low-pass filter so resampling becomes usable => bad idea, resampling is sinc() interpolation --- use the abuf->duplex field to link input and output MIDI streams, and thus avoid sending data back => ok --- can we use the midithru box with subscribed MIDI devices for -i and -o ? => ok --- implement flow control on inputs to avoid output overflow: for instance limit rate to 90% of 3125 for inputs or whatever. Perhaps implement a boost-mode for soft-only thru boxes => ok, throtteling works, now; but boost-mode for soft only devices is not possible, since we don't know whether the application is trying to resend the data to a hardware port --- put drop/silence logic in abuf manipulation functions instead of flush/fill wrappers only => ok, make them specific to mix/sub --- rename ibuflist/obuflist -> ins/outs to cut long lines => ok, from tpfaff --- convert file.c to setitimer(), see midish/mdep.c => ok, copy & pasted midish --- in aucat, there's a single clock source (sio_onmove() callback). So are ``abspos'' counters necessary? We should have: dev_mix->lat - dev_sub->lat == dev_mix->abspos - dev_sub->abspos if so dev_sync(), dev_getpos() could be simplified a lot. => ok, also remove the delta < 0 case, and attach the stream at (plat + rlat + ptodo) --- we need to fill with silence play buffers just before start playback to avoid underruns? the reason is that after sio_start, the device is writable and might accept a lot of data in a very short burst, resulting in underrun. => ok, we ensure initial buffers are large enough --- In server mode, dev_bufsz buffer is primed because it's not allowed to xrun. This is wrong because we insert silence. This doesn't hurt because synchronization between clients is preserved, but is error prone => ok, starting with large buffers --- add simple monitor of the output: just for debugging evaluation purposes (peaks, mean level, etc..), without exposing it to apps => ok, see -m mon --- detect busy loops and abort() to avoid blocking the machine if aucat is running with a high scheduling priority => ok --- could we use a ``cookie'' sent by aucat clients (eg. stored in $HOME/.sndio/cookie) instead of getpeereid; this would help for network transparency later => ok --- change the protocol, to allow adding newer packets (not known yet by the library) to be ignored. => nope, we don't support aucat and libsndio at different levels --- Use dev_mode and hide dev_mix et dev_sub from socket and wav code => ok --- the following fails (resampling and/or channel mapping is wrong) poc:~$ aucat -l -Llocalhost poc:~$ ssh -R11025:127.0.0.1:11025 moule moule:~$ aucat -l -u -c0:0 -mplay -r8000 -f aucat:localhost/0 moule:~$ mpg321 /file.mp3 => ok it's libao's fault --- when used with aucat, the play.c example shows the first tick is not sent => ok, cb() invoked by AMSG_POS --- replace AMSG_MIDIIN | AMSG_MIDIOUT, by a new AMSG_MIDIMASK macro => not useful anymore --- libsndio doesn't handle EINTR while calling connect() and friends, causing sio_open() to fail randomly if the application is using a timer posting SIGALRM => ok --- device names: rename "sun" -> "hw" => ok, renamed to rsnd --- try to use the sample rate stored in .wav files, rather than the default 44.1kHz and possibly resampling. => ok --- does the HELLO message work for midi (hint: think multiple device support) => ok, probably fixed in last proto change --- dev_done() sets dev_midi to NULL, which prevents sock_done() from calling ctl_slotdel(). Add a new aproc_ref() routine to handle that. Possibly add aproc_quit() and call it when p->refs reach 0. => ok --- If -aoff is used, ctl is destroyed and volume settings are lost. Make them persistent => ok, moved settings to struct dev --- move slot allocation, and control code in dev.c, merge ctl_slotnew() in dev_ref() => no, dev_ref() is used by midi as well and we don't wan't a new slot to be allocated in this case --- make "default" the default device instead of NULL => ok, see SIO_DEVANY, MIO_PORTANY