diff options
| -rw-r--r-- | alsa/pcm_a2dp.c | 267 | 
1 files changed, 203 insertions, 64 deletions
diff --git a/alsa/pcm_a2dp.c b/alsa/pcm_a2dp.c index fcacce75..f5dff96a 100644 --- a/alsa/pcm_a2dp.c +++ b/alsa/pcm_a2dp.c @@ -29,7 +29,6 @@  #include <errno.h>  #include <malloc.h>  #include <signal.h> -#include <sys/time.h>  #include <sys/ioctl.h>  #include <sys/socket.h> @@ -50,6 +49,7 @@ static void a2dp_exit(void) __attribute__ ((destructor));  typedef struct snd_pcm_a2dp {  	snd_pcm_ioplug_t io;  	int refcnt; +	int timeout;  	unsigned long state;  	bdaddr_t src;  	bdaddr_t dst; @@ -61,6 +61,20 @@ typedef struct snd_pcm_a2dp {  	unsigned int frame_bytes;  } snd_pcm_a2dp_t; +static void inline a2dp_get(snd_pcm_a2dp_t *a2dp) +{ +	a2dp->refcnt++; +	a2dp->timeout = 0; +} + +static void inline a2dp_put(snd_pcm_a2dp_t *a2dp) +{ +	a2dp->refcnt--; + +	if (a2dp->refcnt <= 0) +		a2dp->timeout = 2; +} +  static int a2dp_start(snd_pcm_ioplug_t *io)  {  	snd_pcm_a2dp_t *a2dp = io->private_data; @@ -100,20 +114,18 @@ static snd_pcm_sframes_t a2dp_transfer(snd_pcm_ioplug_t *io,  	buf = (unsigned char *) areas->addr + (areas->first + areas->step * offset) / 8; -	size *= a2dp->frame_bytes; - -	len = sbc_encode(&a2dp->sbc, buf, size); +	len = sbc_encode(&a2dp->sbc, buf, size * a2dp->frame_bytes);  	if (len <= 0)  		return len; -	memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len); -	a2dp->len += a2dp->sbc.len; - -	if (a2dp->len > 700) { +	if (a2dp->len + a2dp->sbc.len > sizeof(a2dp->buf)) {  		write(a2dp->sk, a2dp->buf, a2dp->len);  		a2dp->len = 0;  	} +	memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len); +	a2dp->len += a2dp->sbc.len; +  	if (a2dp->state == BT_CONNECTED)  		a2dp->num += len / a2dp->frame_bytes; @@ -128,7 +140,7 @@ static int a2dp_close(snd_pcm_ioplug_t *io)  	a2dp->len = 0; -	a2dp->refcnt--; +	a2dp_put(a2dp);  	return 0;  } @@ -180,40 +192,64 @@ static int a2dp_drain(snd_pcm_ioplug_t *io)  	return 0;  } -static int a2dp_poll(snd_pcm_ioplug_t *io, struct pollfd *ufds, -				unsigned int nfds, unsigned short *revents) +static int a2dp_descriptors_count(snd_pcm_ioplug_t *io)  {  	snd_pcm_a2dp_t *a2dp = io->private_data; -	struct timeval tv; -	*revents = ufds[0].revents; +	if (a2dp->state == BT_CLOSED) +		return 0; + +	return 1; +} + +static int a2dp_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space) +{ +	snd_pcm_a2dp_t *a2dp = io->private_data;  	if (a2dp->state == BT_CLOSED)  		return 0; -	if (ufds[0].revents & POLLHUP) { -		a2dp->state = BT_CLOSED; -		a2dp->io.poll_fd = -1; -		a2dp->io.poll_events = POLLIN; -		snd_pcm_ioplug_reinit_status(&a2dp->io); +	if (space < 1) { +		SNDERR("Can't fill in descriptors"); +		return 0;  	} -	if (gettimeofday(&tv, NULL) < 0) +	pfds[0].fd = a2dp->sk; +	pfds[0].events = POLLOUT; + +	return 1; +} + +static int a2dp_poll(snd_pcm_ioplug_t *io, struct pollfd *pfds, +			unsigned int nfds, unsigned short *revents) +{ +	snd_pcm_a2dp_t *a2dp = io->private_data; + +	*revents = pfds[0].revents; + +	if (a2dp->state == BT_CLOSED)  		return 0; +	if (pfds[0].revents & POLLHUP) { +		a2dp->state = BT_CLOSED; +		snd_pcm_ioplug_reinit_status(&a2dp->io); +	} +  	return 0;  }  static snd_pcm_ioplug_callback_t a2dp_callback = { -	.start		= a2dp_start, -	.stop		= a2dp_stop, -	.pointer	= a2dp_pointer, -	.transfer	= a2dp_transfer, -	.close		= a2dp_close, -	.hw_params	= a2dp_params, -	.prepare	= a2dp_prepare, -	.drain		= a2dp_drain, -	.poll_revents	= a2dp_poll, +	.start			= a2dp_start, +	.stop			= a2dp_stop, +	.pointer		= a2dp_pointer, +	.transfer		= a2dp_transfer, +	.close			= a2dp_close, +	.hw_params		= a2dp_params, +	.prepare		= a2dp_prepare, +	.drain			= a2dp_drain, +	.poll_descriptors_count	= a2dp_descriptors_count, +	.poll_descriptors	= a2dp_descriptors, +	.poll_revents		= a2dp_poll,  };  static int a2dp_connect(snd_pcm_a2dp_t *a2dp) @@ -257,6 +293,8 @@ static int a2dp_connect(snd_pcm_a2dp_t *a2dp)  	bacpy(&a2dp->src, &addr.rc_bdaddr); +	fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK); +  	a2dp->sk = sk;  	return 0; @@ -298,7 +336,7 @@ static int a2dp_constraint(snd_pcm_a2dp_t *a2dp)  	if (err < 0)  		return err; -	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 2048, 2048); +	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 8192, 8192);  	if (err < 0)  		return err; @@ -306,8 +344,6 @@ static int a2dp_constraint(snd_pcm_a2dp_t *a2dp)  	if (err < 0)  		return err; -	sbc_init(&a2dp->sbc, SBC_NULL); -  	return 0;  } @@ -315,9 +351,45 @@ static int a2dp_constraint(snd_pcm_a2dp_t *a2dp)  static snd_pcm_a2dp_t *connections[MAX_CONNECTIONS]; +static snd_timer_t *timer = NULL; + +static volatile sig_atomic_t __locked = 0; + +static inline void a2dp_lock(void) +{ +	while (__locked) +		usleep(100); + +	__locked = 1; +} + +static inline void a2dp_unlock(void) +{ +	__locked = 0; +} + +static inline snd_pcm_a2dp_t *a2dp_alloc(void) +{ +	snd_pcm_a2dp_t *a2dp; + +	a2dp = malloc(sizeof(*a2dp)); +	if (!a2dp) +		return NULL; + +	memset(a2dp, 0, sizeof(*a2dp)); + +	a2dp->refcnt = 1; + +	a2dp->state = BT_OPEN; + +	sbc_init(&a2dp->sbc, SBC_NULL); + +	return a2dp; +} +  static inline void a2dp_free(snd_pcm_a2dp_t *a2dp)  { -	if (a2dp->sk >= 0) +	if (a2dp->sk > fileno(stderr))  		close(a2dp->sk);  	sbc_finish(&a2dp->sbc); @@ -325,37 +397,111 @@ static inline void a2dp_free(snd_pcm_a2dp_t *a2dp)  	free(a2dp);  } -static inline void a2dp_delete(snd_pcm_a2dp_t *a2dp) +static void a2dp_timer(snd_async_handler_t *async)  { -	int i; +	snd_timer_t *handle = snd_async_handler_get_timer(async); +	snd_timer_read_t tr; +	int i, ticks = 0; -	for (i = 0; i < MAX_CONNECTIONS; i++) -		if (connections[i] == a2dp) { -			connections[i] = NULL; -			a2dp_free(a2dp); +	while (snd_timer_read(handle, &tr, sizeof(tr)) == sizeof(tr)) +		ticks += tr.ticks; + +	a2dp_lock(); + +	for (i = 0; i < MAX_CONNECTIONS; i++) { +		snd_pcm_a2dp_t *a2dp = connections[i]; + +		if (a2dp && a2dp->refcnt <= 0) { +			a2dp->timeout = ((a2dp->timeout * 1000) - ticks) / 1000; +			if (a2dp->timeout <= 0) { +				connections[i] = NULL; +				a2dp_free(a2dp); +			}  		} +	} + +	a2dp_unlock();  }  static void a2dp_init(void)  { -	int i; +	snd_async_handler_t *async; +	snd_timer_info_t *info; +	snd_timer_params_t *params; +	long resolution; +	char timername[64]; +	int err, i; + +	a2dp_lock();  	for (i = 0; i < MAX_CONNECTIONS; i++)  		connections[i] = NULL; + +	a2dp_unlock(); + +	snd_timer_info_alloca(&info); +	snd_timer_params_alloca(¶ms); + +	sprintf(timername, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i", +		SND_TIMER_CLASS_GLOBAL, SND_TIMER_CLASS_NONE, 0, +					SND_TIMER_GLOBAL_SYSTEM, 0); + +	err = snd_timer_open(&timer, timername, SND_TIMER_OPEN_NONBLOCK); +	if (err < 0) { +		SNDERR("Can't open global timer"); +		return; +	} + +	err = snd_timer_info(timer, info); +	if (err < 0) { +		SNDERR("Can't get global timer info"); +		return; +	} + +	snd_timer_params_set_auto_start(params, 1); + +	resolution = snd_timer_info_get_resolution(info); +	snd_timer_params_set_ticks(params, 1000000000 / resolution); +	if (snd_timer_params_get_ticks(params) < 1) +		snd_timer_params_set_ticks(params, 1); + +	err = snd_timer_params(timer, params); +	if (err < 0) { +		SNDERR("Can't set global timer parameters"); +		snd_timer_close(timer); +		return; +	} + +	err = snd_async_add_timer_handler(&async, timer, a2dp_timer, NULL); +	if (err < 0) { +		SNDERR("Can't create global async callback"); +		snd_timer_close(timer); +		return; +	} + +	err = snd_timer_start(timer);  }  static void a2dp_exit(void)  { -	int i; +	int err, i; + +	err = snd_timer_stop(timer); + +	err = snd_timer_close(timer); + +	a2dp_lock();  	for (i = 0; i < MAX_CONNECTIONS; i++) {  		snd_pcm_a2dp_t *a2dp = connections[i]; -		if (!a2dp) -			continue; -		connections[i] = NULL; -		a2dp_free(a2dp); +		if (a2dp) { +			connections[i] = NULL; +			a2dp_free(a2dp); +		}  	} + +	a2dp_unlock();  }  SND_PCM_PLUGIN_DEFINE_FUNC(a2dp) @@ -402,13 +548,15 @@ SND_PCM_PLUGIN_DEFINE_FUNC(a2dp)  		return -EINVAL;  	} +	a2dp_lock(); +  	for (n = 0; n < MAX_CONNECTIONS; n++) {  		if (connections[n]) {  			if (!bacmp(&connections[n]->dst, &dst) &&  					(!bacmp(&connections[n]->src, &src) ||  						!bacmp(&src, BDADDR_ANY))) {  				a2dp = connections[n]; -				a2dp->refcnt++; +				a2dp_get(a2dp);  				break;  			}  		} else if (pos < 0) @@ -421,42 +569,36 @@ SND_PCM_PLUGIN_DEFINE_FUNC(a2dp)  			return -ENOMEM;  		} -		a2dp = malloc(sizeof(*a2dp)); +		a2dp = a2dp_alloc();  		if (!a2dp) { -			SNDERR("Cannot allocate"); +			SNDERR("Can't allocate");  			return -ENOMEM;  		} -		memset(a2dp, 0, sizeof(*a2dp)); -  		connections[pos] = a2dp; -		a2dp->refcnt = 1;  		a2dp->state  = BT_CONNECT;  		bacpy(&a2dp->src, &src);  		bacpy(&a2dp->dst, &dst);  	} +	a2dp_unlock(); +  	if (a2dp->state != BT_CONNECTED) {  		err = a2dp_connect(a2dp);  		if (err < 0) { -			SNDERR("Cannot connect"); +			SNDERR("Can't connect");  			goto error;  		}  		a2dp->state = BT_CONNECTED;  	} -#ifdef SND_PCM_IOPLUG_VERSION -	a2dp->io.version = SND_PCM_IOPLUG_VERSION; -#endif - -	a2dp->io.name = "Bluetooth Advanced Audio Distribution"; -	a2dp->io.poll_fd = a2dp->sk; -	a2dp->io.poll_events = POLLOUT; -	a2dp->io.mmap_rw = 0; -	a2dp->io.callback = &a2dp_callback; +	a2dp->io.version      = SND_PCM_IOPLUG_VERSION; +	a2dp->io.name         = "Bluetooth Advanced Audio Distribution"; +	a2dp->io.mmap_rw      = 0; +	a2dp->io.callback     = &a2dp_callback;  	a2dp->io.private_data = a2dp;  	err = snd_pcm_ioplug_create(&a2dp->io, name, stream, mode); @@ -473,10 +615,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(a2dp)  	return 0;  error: -	a2dp->refcnt--; - -	if (!a2dp->refcnt) -		a2dp_delete(a2dp); +	a2dp_put(a2dp);  	return err;  }  | 
