Mercurial > mplayer.hg
changeset 15605:904787c80946
New ao_jack without bio2jack dependency.
Since I rewrote ao_jack.c from scratch, the diff is unreadable, sorry.
author | reimar |
---|---|
date | Wed, 01 Jun 2005 09:20:46 +0000 |
parents | 5c23a21cc78c |
children | 2b6652a5cae3 |
files | AUTHORS DOCS/tech/MAINTAINERS configure libao2/ao_jack.c |
diffstat | 4 files changed, 312 insertions(+), 236 deletions(-) [+] |
line wrap: on
line diff
--- a/AUTHORS Wed Jun 01 09:08:15 2005 +0000 +++ b/AUTHORS Wed Jun 01 09:20:46 2005 +0000 @@ -209,6 +209,7 @@ * various fixes all over the place * reworked live changing playback speed * made MMS over HTTP stream selection work and modified ASF header parsing + * reimplemented ao_jack without bio2jack dependency Dolbeau, Romain <romain@dolbeau.org> * random AltiVec (PowerPC multimedia extensions) stuff @@ -671,6 +672,9 @@ * EWMH fullscreen * suboption parser +Strzelecki, Kamil < esack at browarek.net > + * first version of ao_jack, using bio2jack + Svoboda, Jiri (zar) <Jiri.Svoboda@seznam.cz> * AQT type subtitles support * CRTC2 YUV support in mga_vid
--- a/DOCS/tech/MAINTAINERS Wed Jun 01 09:08:15 2005 +0000 +++ b/DOCS/tech/MAINTAINERS Wed Jun 01 09:20:46 2005 +0000 @@ -171,6 +171,7 @@ * ao_arts.c - None * ao_dxr2.c - None * ao_esd.c - None + * ao_jack.c - Reimar Döffinger * ao_mpegpes.c - None * ao_nas.c - Tobias Diedrich * ao_null.c - None
--- a/configure Wed Jun 01 09:08:15 2005 +0000 +++ b/configure Wed Jun 01 09:20:46 2005 +0000 @@ -356,7 +356,6 @@ --with-toolamedir=DIR path to Toolame library and include file --with-xmmsplugindir=DIR XMMS plugins in DIR --with-xmmslibdir=DIR libxmms.so.1 in DIR - --with-bio2jack=DIR libbio2jack.a in DIR --with-cdparanoiaincdir=DIR cdparanoia headers in DIR (*) --with-cdparanoialibdir=DIR cdparanoia libraries (libcdda_*) in DIR (*) --with-xvmclib=NAME name of adapter-specific library (e.g. XvMCNVIDIA) @@ -1809,10 +1808,6 @@ _xmmsplugindir=`echo $ac_option | cut -d '=' -f 2` ;; - --with-bio2jack=*) - _bio2jackdir=`echo $ac_option | cut -d '=' -f 2` - ;; - --enable-profile) _profile='-p' ;; @@ -4693,16 +4688,9 @@ cat > $TMPC << EOF #include <jack/jack.h> -int main(void) { JACK_Init(); return 0; } -EOF - # This test only checks the minor version number. - if ( ( test `bio2jack-config --version | cut -d '.' -f 2` -ge 3 ) ) >/dev/null 2>&1 ; then - if test -z "$_bio2jackdir" ; then - cc_check -lbio2jack `pkg-config --libs --cflags jack` && ( "$TMPO" >> "$TMPLOG" 2>&1 ) && _jack=yes - else - cc_check -L "$_bio2jackdir" -lbio2jack `pkg-config --libs --cflags jack` && ( "$TMPO" >> "$TMPLOG" 2>&1 ) && _jack=yes - fi - fi +int main(void) { jack_client_new("test"); return 0; } +EOF + cc_check `pkg-config --libs --cflags jack` && ( "$TMPO" >> "$TMPLOG" 2>&1 ) && _jack=yes fi fi @@ -4710,11 +4698,7 @@ _def_jack='#define USE_JACK 1' _aosrc="$_aosrc ao_jack.c" _aomodules="jack $_aomodules" - if test -z "$_bio2jackdir" ; then - _ld_jack="-lbio2jack `pkg-config --libs jack`" - else - _ld_jack="-L \"$_bio2jackdir\" -lbio2jack `pkg-config --libs jack`" - fi + _ld_jack="`pkg-config --libs jack`" _inc_jack=`pkg-config --cflags jack` else _noaomodules="jack $_noaomodules"
--- a/libao2/ao_jack.c Wed Jun 01 09:08:15 2005 +0000 +++ b/libao2/ao_jack.c Wed Jun 01 09:20:46 2005 +0000 @@ -1,258 +1,345 @@ -/* - * ao_jack - JACK audio output driver for MPlayer - * - * Kamil Strzelecki < esack at browarek.net > +/* + * ao_jack.c - libao2 JACK Audio Output Driver for MPlayer * - * This driver is distribuited under terms of GPL + * This driver is under the same license as MPlayer. + * (http://www.mplayerhq.hu) * - * It uses bio2jack (http://bio2jack.sf.net/). - * + * Copyleft 2001 by Felix Bünemann (atmosfear@users.sf.net) + * and Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de) */ #include <stdio.h> -#include <jack/jack.h> +#include <stdlib.h> +#include <string.h> #include "config.h" +#include "mp_msg.h" +#include "help_mp.h" + #include "audio_out.h" #include "audio_out_internal.h" #include "libaf/af_format.h" -#include "mp_msg.h" - -//#include "bio2jack.h" +#include "osdep/timer.h" +#include "subopt-helper.h" -static int driver = 0; -long latency = 0; -long approx_bytes_in_jackd = 0; +#include "libvo/fastmemcpy.h" + +#include <jack/jack.h> -//bio2jack stuff: -#define ERR_SUCCESS 0 -#define ERR_OPENING_JACK 1 -#define ERR_RATE_MISMATCH 2 -#define ERR_BYTES_PER_FRAME_INVALID 3 -enum status_enum { PLAYING, PAUSED, STOPPED, CLOSED, RESET }; -void JACK_Init(void); -int JACK_Open(int* deviceID, unsigned int bits_per_sample, unsigned long *rate, int channels); -int JACK_Close(int deviceID); /* return 0 for success */ -void JACK_Reset(int deviceID); /* free all buffered data and reset several values in the device */ -long JACK_Write(int deviceID, char *data, unsigned long bytes); /* returns the number of bytes written */ -long JACK_GetJackLatency(int deviceID); /* return the latency in milliseconds of jack */ -int JACK_SetState(int deviceID, enum status_enum state); /* playing, paused, stopped */ -int JACK_SetAllVolume(int deviceID, unsigned int volume); -int JACK_SetVolumeForChannel(int deviceID, unsigned int channel, unsigned int volume); -void JACK_GetVolumeForChannel(int deviceID, unsigned int channel, unsigned int *volume); -// - - -static ao_info_t info = +static ao_info_t info = { - "JACK audio output", - "jack", - "Kamil Strzelecki <esack@browarek.net>", - "" + "JACK audio output", + "jack", + "Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>", + "based on ao_sdl.c" }; - LIBAO_EXTERN(jack) +//! maximum number of channels supported, avoids lots of mallocs +#define MAX_CHANS 6 +static jack_port_t *ports[MAX_CHANS]; +static int num_ports; ///< Number of used ports == number of channels +static jack_client_t *client; +static volatile int paused = 0; ///< set if paused +static volatile int underrun = 0; ///< signals if an underrun occured -static int control(int cmd, void *arg) -{ - switch(cmd) { - case AOCONTROL_GET_VOLUME: - { - ao_control_vol_t *vol = (ao_control_vol_t *)arg; - unsigned int l, r; - - JACK_GetVolumeForChannel(driver, 0, &l); - JACK_GetVolumeForChannel(driver, 1, &r); - vol->left = (float )l; - vol->right = (float )r; - - return CONTROL_OK; - } - case AOCONTROL_SET_VOLUME: - { - ao_control_vol_t *vol = (ao_control_vol_t *)arg; - unsigned int l = (int )vol->left, - r = (int )vol->right, - avg_vol = (l + r) / 2, - err = 0; +//! If this is defined try to make a more precise delay estimation. Will be slower. +#undef JACK_ESTIMATE_DELAY +#ifdef JACK_ESTIMATE_DELAY +static volatile int callback_samples = 0; +static volatile unsigned int callback_time = 0; +#endif + +//! size of one chunk, if this is too small MPlayer will start to "stutter" +//! after a short time of playback +#define CHUNK_SIZE (16 * 1024) +//! number of "virtual" chunks the buffer consists of +#define NUM_CHUNKS 8 +// This type of ring buffer may never fill up completely, at least +// one byte must always be unused. +// For performance reasons (alignment etc.) one whole chunk always stays +// empty, not only one byte. +#define BUFFSIZE ((NUM_CHUNKS + 1) * CHUNK_SIZE) + +//! buffer for audio data +static unsigned char *buffer = NULL; + +//! buffer read position, may only be modified by playback thread or while it is stopped +static volatile int read_pos; +//! buffer write position, may only be modified by MPlayer's thread +static volatile int write_pos; + +/** + * \brief get the number of free bytes in the buffer + * \return number of free bytes in buffer + * + * may only be called by MPlayer's thread + * return value may change between immediately following two calls, + * and the real number of free bytes might be larger! + */ +static int buf_free() { + int free = read_pos - write_pos - CHUNK_SIZE; + if (free < 0) free += BUFFSIZE; + return free; +} + +/** + * \brief get amount of data available in the buffer + * \return number of bytes available in buffer + * + * may only be called by the playback thread + * return value may change between immediately following two calls, + * and the real number of buffered bytes might be larger! + */ +static int buf_used() { + int used = write_pos - read_pos; + if (used < 0) used += BUFFSIZE; + return used; +} - switch (ao_data.channels) { - case 6: - if((err = JACK_SetVolumeForChannel(driver, 5, avg_vol))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting lfe volume failed, error %d\n",err); - return CONTROL_ERROR; - } - case 5: - if((err = JACK_SetVolumeForChannel(driver, 4, avg_vol))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting center volume failed, error %d\n",err); - return CONTROL_ERROR; - } - case 4: - if((err = JACK_SetVolumeForChannel(driver, 3, r))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting rear right volume failed, error %d\n",err); - return CONTROL_ERROR; - } - case 3: - if((err = JACK_SetVolumeForChannel(driver, 2, l))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting rear left volume failed, error %d\n",err); - return CONTROL_ERROR; - } - case 2: - if((err = JACK_SetVolumeForChannel(driver, 1, r))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting right volume failed, error %d\n",err); - return CONTROL_ERROR; - } - case 1: - if((err = JACK_SetVolumeForChannel(driver, 0, l))) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] Setting left volume failed, error %d\n",err); - return CONTROL_ERROR; - } - } +/** + * \brief insert len bytes into buffer + * \param data data to insert + * \param len length of data + * \return number of bytes inserted into buffer + * + * If there is not enough room, the buffer is filled up + */ +static int write_buffer(unsigned char* data, int len) { + int first_len = BUFFSIZE - write_pos; + int free = buf_free(); + if (len > free) len = free; + if (first_len > len) first_len = len; + // till end of buffer + memcpy (&buffer[write_pos], data, first_len); + if (len > first_len) { // we have to wrap around + // remaining part from beginning of buffer + memcpy (buffer, &data[first_len], len - first_len); + } + write_pos = (write_pos + len) % BUFFSIZE; + return len; +} - return CONTROL_OK; - } - } +/** + * \brief read data from buffer and splitting it into channels + * \param bufs num_bufs float buffers, each will contain the data of one channel + * \param cnt number of samples to read per channel + * \param num_bufs number of channels to split the data into + * \return number of samples read per channel, equals cnt unless there was too + * little data in the buffer + * + * Assumes the data in the buffer is of type float, the number of bytes + * read is res * num_bufs * sizeof(float), where res is the return value. + */ +static int read_buffer(float **bufs, int cnt, int num_bufs) { + int first_len = BUFFSIZE - read_pos; + int buffered = buf_used(); + int i, j; + if (cnt * sizeof(float) * num_bufs > buffered) + cnt = buffered / sizeof(float) / num_bufs; + for (i = 0; i < cnt; i++) { + for (j = 0; j < num_bufs; j++) { + bufs[j][i] = *((float *)(&buffer[read_pos])); + read_pos = (read_pos + sizeof(float)) % BUFFSIZE; + } + } + return cnt; +} - return(CONTROL_UNKNOWN); +// end ring buffer stuff + +static int control(int cmd, void *arg) { + return CONTROL_UNKNOWN; +} + +/** + * \brief fill the buffers with silence + * \param bufs num_bufs float buffers, each will contain the data of one channel + * \param cnt number of samples in each buffer + * \param num_bufs number of buffers + */ +static void silence(float **bufs, int cnt, int num_bufs) { + int i, j; + for (i = 0; i < cnt; i++) + for (j = 0; j < num_bufs; j++) + bufs[j][i] = 0; } - -static int init(int rate_hz, int channels, int format, int flags) -{ - int err, m, frag_spec; - unsigned long rate; - unsigned int bits_per_sample; - - unsigned long jack_port_flags=JackPortIsPhysical; - unsigned int jack_port_name_count=0; - const char *jack_port_name=NULL; - - mp_msg(MSGT_AO, MSGL_INFO, "AO: [Jack] Initialising library.\n"); - JACK_Init(); - - if (ao_subdevice) { - jack_port_flags = 0; - jack_port_name_count = 1; - jack_port_name = ao_subdevice; - mp_msg(MSGT_AO, MSGL_INFO, "AO: [Jack] Trying to use jack device:%s.\n", ao_subdevice); - } - - switch (format) { - case AF_FORMAT_U8: - case AF_FORMAT_S8: - format = AF_FORMAT_U8; - bits_per_sample = 8; - m = 1; - break; - default: - format = AF_FORMAT_S16_NE; - bits_per_sample = 16; - m = 2; - break; - } +/** + * \brief JACK Callback function + * \param nframes number of frames to fill into buffers + * \param arg unused + * \return currently always 0 + * + * Write silence into buffers if paused or an underrun occured + */ +static int outputaudio(jack_nframes_t nframes, void *arg) { + float *bufs[MAX_CHANS]; + int i; + for (i = 0; i < num_ports; i++) + bufs[i] = jack_port_get_buffer(ports[i], nframes); + if (!paused && !underrun) + if (read_buffer(bufs, nframes, num_ports) < nframes) + underrun = 1; + if (paused || underrun) + silence(bufs, nframes, num_ports); +#ifdef JACK_ESTIMATE_DELAY + callback_samples = nframes; + callback_time = GetTimer(); +#endif + return 0; +} - rate = rate_hz; - - err = JACK_OpenEx(&driver, bits_per_sample, &rate, channels, channels, - &jack_port_name, jack_port_name_count, jack_port_flags); - - /* if sample rates doesn't match try to open device with jack's rate and - * let mplayer convert it (rate now contains that which jackd use) */ - if(err == ERR_RATE_MISMATCH) { - mp_msg(MSGT_AO, MSGL_INFO, - "AO: [Jack] Sample rate mismatch, trying to resample.\n"); - - err = JACK_OpenEx(&driver, bits_per_sample, &rate, channels, channels, - &jack_port_name, jack_port_name_count, jack_port_flags); - } - - /* any other error */ - if(err != ERR_SUCCESS) { - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] JACK_Open() failed, error %d\n", err); - return 0; - } - - err = JACK_SetAllVolume(driver, 100); - if(err != ERR_SUCCESS) { - // This is not fatal, but would be peculiar... - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] JACK_SetAllVolume() failed, error %d\n", err); - } - - latency = JACK_GetJackLatency(driver); - - ao_data.format = format; - ao_data.channels = channels; - ao_data.samplerate = rate; - ao_data.bps = ( rate * channels * m ); - - // Rather rough way to find out the rough number of bytes buffered - approx_bytes_in_jackd = JACK_GetJackBufferedBytes(driver) * 2; - - mp_msg(MSGT_AO, MSGL_INFO, - "AO: [Jack] OK. I'm ready to go (%d Hz/%d channels/%d bit)\n", - ao_data.samplerate, ao_data.channels, bits_per_sample); - - return 1; +/** + * \brief print suboption usage help + */ +static void print_help () +{ + mp_msg (MSGT_AO, MSGL_FATAL, + "\n-ao jack commandline help:\n" + "Example: mplayer -ao jack:port=myout\n" + " connects MPlayer to the jack ports named myout\n" + "\nOptions:\n" + " port=<port name>\n" + " Connects to the given ports instead of the default physical ones\n"); } +static int init(int rate, int channels, int format, int flags) { + const char **matching_ports = NULL; + char *port_name = NULL; + opt_t subopts[] = { + {"port", OPT_ARG_MSTRZ, &port_name, NULL}, + {NULL} + }; + int port_flags = JackPortIsInput; + int i; + if (subopt_parse(ao_subdevice, subopts) != 0) { + print_help(); + return 0; + } + if (channels > MAX_CHANS) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] Invalid number of channels: %i\n", channels); + goto err_out; + } + client = jack_client_new("MPlayer"); + if (!client) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] cannot open server\n"); + goto err_out; + } + jack_set_process_callback(client, outputaudio, 0); -static void uninit(int immed) -{ - int errval = 0; - - if((errval = JACK_Close(driver))) - mp_msg(MSGT_AO, MSGL_ERR, - "AO: [Jack] error closing device, error %d\n", errval); -} + // list matching ports + if (!port_name) + port_flags |= JackPortIsPhysical; + matching_ports = jack_get_ports(client, port_name, NULL, port_flags); + for (num_ports = 0; matching_ports && matching_ports[num_ports]; num_ports++) ; + if (!num_ports) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] no physical ports available\n"); + goto err_out; + } + if (channels > num_ports) channels = num_ports; + num_ports = channels; + // create out output ports + for (i = 0; i < num_ports; i++) { + char pname[30]; + snprintf(pname, 30, "out_%d", i); + ports[i] = jack_port_register(client, pname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (!ports[i]) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] not enough ports available\n"); + goto err_out; + } + } + if (jack_activate(client)) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] activate failed\n"); + goto err_out; + } + for (i = 0; i < num_ports; i++) { + if (jack_connect(client, jack_port_name(ports[i]), matching_ports[i])) { + mp_msg(MSGT_AO, MSGL_FATAL, "[JACK] connecting failed\n"); + goto err_out; + } + } + buffer = (unsigned char *) malloc(BUFFSIZE); -static int play(void* data,int len,int flags) -{ - return JACK_Write(driver, data, len); -} + ao_data.channels = channels; + ao_data.samplerate = rate = jack_get_sample_rate(client); + ao_data.format = AF_FORMAT_FLOAT_NE; + ao_data.bps = channels * rate * sizeof(float); + ao_data.buffersize = jack_port_get_total_latency(client, ports[0]) * channels; + ao_data.outburst = CHUNK_SIZE; + free(matching_ports); + free(port_name); + return 1; - -static void audio_pause() -{ - JACK_SetState(driver, PAUSED); +err_out: + free(matching_ports); + free(port_name); + if (client) + jack_client_close(client); + free(buffer); + buffer = NULL; + return 0; } - -static void audio_resume() -{ - JACK_SetState(driver, PLAYING); +// close audio device +static void uninit(int immed) { + if (!immed) + usec_sleep(get_delay() * 1000 * 1000); + // HACK, make sure jack doesn't loop-output dirty buffers + paused = 1; + usec_sleep(100 * 1000); + jack_client_close(client); + free(buffer); + buffer = NULL; } +/** + * \brief stop playing and empty buffers (for seeking/pause) + */ +static void reset() { + paused = 1; + read_pos = 0; + write_pos = 0; + paused = 0; +} -static void reset() -{ - JACK_Reset(driver); - latency = JACK_GetJackLatency(driver); - // Rather rough way to find out the rough number of bytes buffered - approx_bytes_in_jackd = JACK_GetJackBufferedBytes(driver) * 2; +/** + * \brief stop playing, keep buffers (for pause) + */ +static void audio_pause() { + paused = 1; } +/** + * \brief resume playing, after audio_pause() + */ +static void audio_resume() { + paused = 0; +} -static int get_space() -{ - return JACK_GetBytesFreeSpace(driver); +static int get_space() { + return buf_free(); } - -static float get_delay() -{ - float ret = 0; - ret = (JACK_GetBytesStored(driver) + approx_bytes_in_jackd + latency) / (float)ao_data.bps; - return ret; +/** + * \brief write data into buffer and reset underrun flag + */ +static int play(void *data, int len, int flags) { + len -= len % ao_data.outburst; + underrun = 0; + return write_buffer(data, len); } +static float get_delay() { + int buffered = BUFFSIZE - CHUNK_SIZE - buf_free(); // could be less + float in_jack = (float)ao_data.buffersize / (float)ao_data.bps; +#ifdef JACK_ESTIMATE_DELAY + unsigned int elapsed = GetTimer() - callback_time; + in_jack += (float)callback_samples / (float)ao_data.samplerate - (float)elapsed / 1000.0 / 1000.0; + if (in_jack < 0) in_jack = 0; +#endif + return (float)buffered / (float)ao_data.bps + in_jack; +} +