⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pcm_a2dp.c

📁 蓝牙blue tooth sco协议栈
💻 C
字号:
/* * *  Bluetooth Advanced Audio Distribution Profile (A2DP) plugin * *  Copyright (C) 2005  Brad Midgley <bmidgley@flamebot.com> * * *  This library is free software; you can redistribute it and/or *  modify it under the terms of the GNU Lesser General Public *  License as published by the Free Software Foundation; either *  version 2.1 of the License, or (at your option) any later version. * *  This library 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 *  Lesser General Public License for more details. * *  You should have received a copy of the GNU Lesser General Public *  License along with this library; if not, write to the Free Software *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA * */#include <byteswap.h>#include <sys/shm.h>#include <sys/types.h>#include <sys/socket.h>#include <bluetooth/bluetooth.h>#include <alsa/asoundlib.h>#include <alsa/pcm_external.h>#include "sbc.h"/* todo: remove this include */#include <linux/soundcard.h>typedef struct snd_pcm_a2dp {	snd_pcm_ioplug_t io;	bdaddr_t src, dst;	int fd;	int fragment_set;	int caps;	int format;	unsigned int period_shift;	unsigned int periods;	unsigned int frame_bytes;} snd_pcm_a2dp_t;#define DBG(fmt, arg...)  printf("%s: " fmt "\n" , __FUNCTION__ , ## arg)static snd_pcm_sframes_t a2dp_write(snd_pcm_ioplug_t *io,				   const snd_pcm_channel_area_t *areas,				   snd_pcm_uframes_t offset,				   snd_pcm_uframes_t size){	snd_pcm_a2dp_t *a2dp = io->private_data;	const char *buf;	ssize_t result;	/* we handle only an interleaved buffer */	buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;	size *= a2dp->frame_bytes;	result = write(a2dp->fd, buf, size);	if (result <= 0)		return result;	return result / a2dp->frame_bytes;}static snd_pcm_sframes_t a2dp_read(snd_pcm_ioplug_t *io,				  const snd_pcm_channel_area_t *areas,				  snd_pcm_uframes_t offset,				  snd_pcm_uframes_t size){	snd_pcm_a2dp_t *a2dp = io->private_data;	char *buf;	ssize_t result;	/* we handle only an interleaved buffer */	buf = (char *)areas->addr + (areas->first + areas->step * offset) / 8;	size *= a2dp->frame_bytes;	result = read(a2dp->fd, buf, size);	if (result <= 0)		return result;	return result / a2dp->frame_bytes;}static snd_pcm_sframes_t a2dp_pointer(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	struct count_info info;	int ptr;	if (ioctl(a2dp->fd, io->stream == SND_PCM_STREAM_PLAYBACK ?		  SNDCTL_DSP_GETOPTR : SNDCTL_DSP_GETIPTR, &info) < 0) {		fprintf(stderr, "*** A2DP: a2dp_pointer error\n");		return 0;	}	ptr = snd_pcm_bytes_to_frames(io->pcm, info.ptr);	return ptr;}static int a2dp_start(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	int tmp = io->stream == SND_PCM_STREAM_PLAYBACK ?		PCM_ENABLE_OUTPUT : PCM_ENABLE_INPUT;	if (ioctl(a2dp->fd, SNDCTL_DSP_SETTRIGGER, &tmp) < 0) {		fprintf(stderr, "*** A2DP: trigger failed\n");		if (io->stream == SND_PCM_STREAM_CAPTURE)			/* fake read to trigger */			read(a2dp->fd, &tmp, 0);	}	return 0;}static int a2dp_stop(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	int tmp = 0;	ioctl(a2dp->fd, SNDCTL_DSP_SETTRIGGER, &tmp);	return 0;}static int a2dp_drain(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	if (io->stream == SND_PCM_STREAM_PLAYBACK)		ioctl(a2dp->fd, SNDCTL_DSP_SYNC);	return 0;}static int a2dp_prepare(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	int tmp;	ioctl(a2dp->fd, SNDCTL_DSP_RESET);	tmp = io->channels;	if (ioctl(a2dp->fd, SNDCTL_DSP_CHANNELS, &tmp) < 0) {		perror("SNDCTL_DSP_CHANNELS");		return -EINVAL;	}	tmp = a2dp->format;	if (ioctl(a2dp->fd, SNDCTL_DSP_SETFMT, &tmp) < 0) {		perror("SNDCTL_DSP_SETFMT");		return -EINVAL;	}	tmp = io->rate;	if (ioctl(a2dp->fd, SNDCTL_DSP_SPEED, &tmp) < 0 ||	    tmp > io->rate * 1.01 || tmp < io->rate * 0.99) {		perror("SNDCTL_DSP_SPEED");		return -EINVAL;	}	return 0;}static int a2dp_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params){	snd_pcm_a2dp_t *a2dp = io->private_data;	int i, tmp, err;	unsigned int period_bytes;	long oflags, flags;	a2dp->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;	switch (io->format) {	case SND_PCM_FORMAT_U8:		a2dp->format = AFMT_U8;		break;	case SND_PCM_FORMAT_S16_LE:		a2dp->format = AFMT_S16_LE;		break;	case SND_PCM_FORMAT_S16_BE:		a2dp->format = AFMT_S16_BE;		break;	default:		fprintf(stderr, "*** A2DP: unsupported format %s\n", snd_pcm_format_name(io->format));		return -EINVAL;	}	period_bytes = io->period_size * a2dp->frame_bytes;	a2dp->period_shift = 0;	for (i = 31; i >= 4; i--) {		if (period_bytes & (1U << i)) {			a2dp->period_shift = i;			break;		}	}	if (! a2dp->period_shift) {		fprintf(stderr, "*** A2DP: invalid period size %d\n", (int)io->period_size);		return -EINVAL;	} _retry:	tmp = a2dp->period_shift | (a2dp->periods << 16);	if (ioctl(a2dp->fd, SNDCTL_DSP_SETFRAGMENT, &tmp) < 0) {		if (! a2dp->fragment_set) {			perror("SNDCTL_DSP_SETFRAGMENT");			fprintf(stderr, "*** period shift = %d, periods = %d\n", a2dp->period_shift, a2dp->periods);			return -EINVAL;		}		/* A2DP has no proper way to reinitialize the fragments */		/* try to reopen the device */		close(a2dp->fd);		a2dp->fd = 0;#if 0		a2dp->fd = open(a2dp->bdaddr, io->stream == SND_PCM_STREAM_PLAYBACK ?			       O_WRONLY : O_RDONLY);		if (a2dp->fd < 0) {			err = -errno;			SNDERR("Cannot reopen the device %s", a2dp->bdaddr);			return err;		}#endif		io->poll_fd = a2dp->fd;		io->poll_events = io->stream == SND_PCM_STREAM_PLAYBACK ?			POLLOUT : POLLIN;		snd_pcm_ioplug_reinit_status(io);		a2dp->fragment_set = 0;		goto _retry;	}	a2dp->fragment_set = 1;	if ((flags = fcntl(a2dp->fd, F_GETFL)) < 0) {		err = -errno;		perror("F_GETFL");	} else {		oflags = flags;		if (io->nonblock)			flags |= O_NONBLOCK;		else			flags &= ~O_NONBLOCK;		if (flags != oflags &&		    fcntl(a2dp->fd, F_SETFL, flags) < 0) {			err = -errno;			perror("F_SETFL");		}	}	return 0;}#define ARRAY_SIZE(ary)	(sizeof(ary)/sizeof(ary[0]))static int a2dp_hw_constraint(snd_pcm_a2dp_t *a2dp){	snd_pcm_ioplug_t *io = &a2dp->io; 	static snd_pcm_access_t access_list[] = {		SND_PCM_ACCESS_RW_INTERLEAVED,		SND_PCM_ACCESS_MMAP_INTERLEAVED	};	unsigned int nformats;	unsigned int format[5];	unsigned int nchannels;	unsigned int channel[6];	/* period and buffer bytes must be power of two */	static unsigned int bytes_list[] = {		1U<<8, 1U<<9, 1U<<10, 1U<<11, 1U<<12, 1U<<13, 1U<<14, 1U<<15,		1U<<16, 1U<<17, 1U<<18, 1U<<19, 1U<<20, 1U<<21, 1U<<22, 1U<<23	};	int i, err, tmp;	/* check trigger */	a2dp->caps = 0;	if (ioctl(a2dp->fd, SNDCTL_DSP_GETCAPS, &a2dp->caps) >= 0) {		if (! (a2dp->caps & DSP_CAP_TRIGGER))			fprintf(stderr, "*** A2DP: trigger is not supported!\n");	}	/* access type - interleaved only */	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,						 ARRAY_SIZE(access_list), access_list)) < 0)		return err;	/* supported formats */	tmp = 0;	ioctl(a2dp->fd, SNDCTL_DSP_GETFMTS, &tmp);	nformats = 0;	if (tmp & AFMT_U8)		format[nformats++] = SND_PCM_FORMAT_U8;	if (tmp & AFMT_S16_LE)		format[nformats++] = SND_PCM_FORMAT_S16_LE;	if (tmp & AFMT_S16_BE)		format[nformats++] = SND_PCM_FORMAT_S16_BE;	if (tmp & AFMT_MU_LAW)		format[nformats++] = SND_PCM_FORMAT_MU_LAW;	if (! nformats)		format[nformats++] = SND_PCM_FORMAT_S16;	if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,						 nformats, format)) < 0)		return err;		/* supported channels */	nchannels = 0;	for (i = 0; i < 6; i++) {		tmp = i + 1;		if (ioctl(a2dp->fd, SNDCTL_DSP_CHANNELS, &tmp) >= 0)			channel[nchannels++] = tmp;	}	if (! nchannels) /* assume 2ch stereo */		err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,						      2, 2);	else		err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS,						    nchannels, channel);	if (err < 0)		return err;	/* supported rates */	/* FIXME: should query? */	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 8000, 480000);	if (err < 0)		return err;	/* period size (in power of two) */	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,					    ARRAY_SIZE(bytes_list), bytes_list);	if (err < 0)		return err;	/* periods */	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 1024);	if (err < 0)		return err;	/* buffer size (in power of two) */	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,					    ARRAY_SIZE(bytes_list), bytes_list);	if (err < 0)		return err;	return 0;}static int a2dp_close(snd_pcm_ioplug_t *io){	snd_pcm_a2dp_t *a2dp = io->private_data;	close(a2dp->fd);	free(a2dp);	return 0;}static snd_pcm_ioplug_callback_t a2dp_playback_callback = {	.start = a2dp_start,	.stop = a2dp_stop,	.transfer = a2dp_write,	.pointer = a2dp_pointer,	.close = a2dp_close,	.hw_params = a2dp_hw_params,	.prepare = a2dp_prepare,	.drain = a2dp_drain,};static snd_pcm_ioplug_callback_t a2dp_capture_callback = {	.start = a2dp_start,	.stop = a2dp_stop,	.transfer = a2dp_read,	.pointer = a2dp_pointer,	.close = a2dp_close,	.hw_params = a2dp_hw_params,	.prepare = a2dp_prepare,	.drain = a2dp_drain,};SND_PCM_PLUGIN_DEFINE_FUNC(a2dp){	snd_config_iterator_t i, next;	bdaddr_t src, dst;	const char *addr;	int err;	snd_pcm_a2dp_t *a2dp;		bacpy(&src, BDADDR_ANY);	bacpy(&dst, BDADDR_ANY);	snd_config_for_each(i, next, conf) {		snd_config_t *n = snd_config_iterator_entry(i);		const char *id;		if (snd_config_get_id(n, &id) < 0)			continue;		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)			continue;		if (strcmp(id, "bdaddr") == 0 || strcmp(id, "dst") == 0) {			if (snd_config_get_string(n, &addr) < 0) {				SNDERR("Invalid type for %s", id);				return -EINVAL;			}			str2ba(addr, &dst);			continue;		}		if (strcmp(id, "src") == 0) {			if (snd_config_get_string(n, &addr) < 0) {				SNDERR("Invalid type for %s", id);				return -EINVAL;			}			str2ba(addr, &src);			continue;		}		SNDERR("Unknown field %s", id);		return -EINVAL;	}	a2dp = calloc(1, sizeof(*a2dp));#if 0	a2dp->bdaddr = strdup(bdaddr);	if (a2dp->bdaddr == NULL) {		SNDERR("cannot allocate");		free(a2dp);		return -ENOMEM;	}#endif	bacpy(&a2dp->src, &src);	bacpy(&a2dp->dst, &dst);		a2dp->fd = 0; /* todo: change fd to sbc calls */#if 0	a2dp->fd = open(bdaddr, stream == SND_PCM_STREAM_PLAYBACK ? O_WRONLY : O_RDONLY);	if (a2dp->fd < 0) {		err = -errno;		SNDERR("Cannot open device %s", bdaddr);		goto error;	}#endif	a2dp->io.name = "ALSA <-> A2DP PCM I/O Plugin";	a2dp->io.poll_fd = a2dp->fd;	a2dp->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;	a2dp->io.mmap_rw = 0;	a2dp->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?		&a2dp_playback_callback : &a2dp_capture_callback;	a2dp->io.private_data = a2dp;	err = snd_pcm_ioplug_create(&a2dp->io, name, stream, mode);	if (err < 0)		goto error;	if ((err = a2dp_hw_constraint(a2dp)) < 0) {		snd_pcm_ioplug_delete(&a2dp->io);		return err;	}	*pcmp = a2dp->io.pcm;	return 0; error:	if (a2dp->fd >= 0)		close(a2dp->fd);	free(a2dp);	return err;}SND_PCM_PLUGIN_SYMBOL(a2dp);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -