📄 btsco.c
字号:
/* * Bluetooth SCO soundcard * Copyright (c) 2003, 2004 by Jonathan Paisley <jp@dcs.gla.ac.uk> * * Based on dummy.c which is * Copyright (c) by Jaroslav Kysela <perex@suse.cz> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */#define chip_t snd_card_bt_sco_t#include <sound/driver.h>#include <linux/version.h>#include <linux/init.h>#include <linux/jiffies.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/wait.h>#include <linux/socket.h>#include <linux/file.h>#include <linux/completion.h>#include <linux/smp_lock.h>#include <net/sock.h>#include <net/bluetooth/bluetooth.h>#include <sound/core.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/rawmidi.h>#include <sound/hwdep.h>#define SNDRV_GET_ID#include <sound/initval.h>#ifndef SNDRV_HWDEP_IFACE_BLUETOOTH#define SNDRV_HWDEP_IFACE_BLUETOOTH (SNDRV_HWDEP_IFACE_EMUX_WAVETABLE + 1)#endif#ifndef SNDRV_HWDEP_IFACE_BT_SCO#define SNDRV_HWDEP_IFACE_BT_SCO (SNDRV_HWDEP_IFACE_BLUETOOTH + 1)#endif#define SNDRV_BT_SCO_IOCTL_SET_SCO_SOCKET _IOW ('H', 0x10, int)#define SNDRV_BT_SCO_IOCTL_REQ_INFO _IO ('H', 0x11)MODULE_AUTHOR("Jonathan Paisley <jp@dcs.gla.ac.uk>");MODULE_DESCRIPTION("Bluetooth SCO Headset Soundcard");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{ALSA,Bluetooth SCO Soundcard}}");static char *mod_revision = "$Revision: 1.6 $";#undef dprintk#if 1#define dprintk(fmt...) printk(KERN_INFO "snd-bt-sco: " fmt)#else#define dprintk(fmt...) do {} while(0)#endif#define MAX_BUFFER_SIZE (32*1024)#define MIXER_ADDR_MASTER 0#define MIXER_ADDR_MIC 1#define MIXER_ADDR_LAST 1#define MIXER_MASK_MASTER 1#define MIXER_MASK_MIC 2#define MIXER_MIN_VOLUME 1#define MIXER_MAX_VOLUME 15struct snd_card_bt_sco_pcm;typedef struct snd_card_bt_sco_info { int mixer_volume[MIXER_ADDR_LAST + 1]; int playback_count, capture_count;} snd_card_bt_sco_info_t;typedef struct snd_card_bt_sco { snd_card_t *card; spinlock_t mixer_lock; int mixer_volume[MIXER_ADDR_LAST + 1]; snd_kcontrol_t *mixer_controls[MIXER_ADDR_LAST + 2]; /* also loopback */ volatile int loopback; atomic_t playback_count, capture_count; volatile int count_changed; spinlock_t count_changed_lock; spinlock_t mixer_changed_lock; volatile int mixer_changed; wait_queue_head_t hwdep_wait; int thread_pid; struct completion thread_done; volatile int thread_exit; struct semaphore thread_sem; volatile struct socket *sco_sock; struct semaphore sock_sem; wait_queue_head_t wait; struct semaphore playback_sem; struct snd_card_bt_sco_pcm *playback; struct semaphore capture_sem; struct snd_card_bt_sco_pcm *capture;} snd_card_bt_sco_t;typedef struct snd_card_bt_sco_pcm { snd_card_bt_sco_t *bt_sco; spinlock_t lock; unsigned int pcm_size; unsigned int pcm_count; unsigned int pcm_bps; /* bytes per second */ unsigned int pcm_irq_pos; /* IRQ position */ unsigned int pcm_buf_pos; /* position in buffer */ snd_pcm_substream_t *substream;} snd_card_bt_sco_pcm_t;static snd_card_t *snd_bt_sco_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;static int snd_card_bt_sco_playback_trigger(snd_pcm_substream_t * substream, int cmd){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); dprintk("playback_trigger %d\n", cmd); if (cmd == SNDRV_PCM_TRIGGER_START) { bt_sco->playback = bspcm; dprintk("setting playback to bspcm\n"); } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { bt_sco->playback = NULL; dprintk("setting playback to NULL\n"); } else { return -EINVAL; } return 0;}static int snd_card_bt_sco_capture_trigger(snd_pcm_substream_t * substream, int cmd){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); dprintk("capture_trigger %d\n", cmd); if (cmd == SNDRV_PCM_TRIGGER_START) { bt_sco->capture = bspcm; dprintk("setting capture to bspcm\n"); } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { bt_sco->capture = NULL; dprintk("setting capture to NULL\n"); } else { return -EINVAL; } return 0;}static int snd_card_bt_sco_pcm_prepare(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; unsigned int bps; bps = runtime->rate * runtime->channels; bps *= snd_pcm_format_width(runtime->format); bps /= 8; if (bps <= 0) return -EINVAL; bspcm->pcm_bps = bps; bspcm->pcm_size = snd_pcm_lib_buffer_bytes(substream); bspcm->pcm_count = snd_pcm_lib_period_bytes(substream); bspcm->pcm_irq_pos = 0; bspcm->pcm_buf_pos = 0; dprintk("prepare ok bps: %d size: %d count: %d\n", bspcm->pcm_bps, bspcm->pcm_size, bspcm->pcm_count); return 0;}static int snd_card_bt_sco_playback_prepare(snd_pcm_substream_t * substream){ return snd_card_bt_sco_pcm_prepare(substream);}static int snd_card_bt_sco_capture_prepare(snd_pcm_substream_t * substream){ dprintk("capture_prepare\n"); return snd_card_bt_sco_pcm_prepare(substream);}static void snd_card_bt_sco_pcm_receive(snd_card_bt_sco_pcm_t * bspcm, unsigned char *data, unsigned int len){ unsigned long flags; unsigned int oldptr; spin_lock_irqsave(&bspcm->lock, flags); oldptr = bspcm->pcm_buf_pos; bspcm->pcm_irq_pos += len; bspcm->pcm_buf_pos += len; bspcm->pcm_buf_pos %= bspcm->pcm_size; spin_unlock_irqrestore(&bspcm->lock, flags); /* copy a data chunk */ if (oldptr + len > bspcm->pcm_size) { unsigned int cnt = bspcm->pcm_size - oldptr; memcpy(bspcm->substream->runtime->dma_area + oldptr, data, cnt); memcpy(bspcm->substream->runtime->dma_area, data + cnt, len - cnt); } else { memcpy(bspcm->substream->runtime->dma_area + oldptr, data, len); } /* update the pointer, call callback if necessary */ spin_lock_irqsave(&bspcm->lock, flags); if (bspcm->pcm_irq_pos >= bspcm->pcm_count) { bspcm->pcm_irq_pos %= bspcm->pcm_count; spin_unlock_irqrestore(&bspcm->lock, flags); snd_pcm_period_elapsed(bspcm->substream); } else spin_unlock_irqrestore(&bspcm->lock, flags);}static void snd_card_bt_sco_pcm_send(snd_card_bt_sco_pcm_t * bspcm, unsigned char *data, unsigned int len){ unsigned long flags; unsigned int oldptr; spin_lock_irqsave(&bspcm->lock, flags); oldptr = bspcm->pcm_buf_pos; bspcm->pcm_irq_pos += len; bspcm->pcm_buf_pos += len; bspcm->pcm_buf_pos %= bspcm->pcm_size; spin_unlock_irqrestore(&bspcm->lock, flags); /* copy a data chunk */ if (oldptr + len > bspcm->pcm_size) { unsigned int cnt = bspcm->pcm_size - oldptr; memcpy(data, bspcm->substream->runtime->dma_area + oldptr, cnt); memcpy(data + cnt, bspcm->substream->runtime->dma_area, len - cnt); } else { memcpy(data, bspcm->substream->runtime->dma_area + oldptr, len); } /* update the pointer, call callback if necessary */ spin_lock_irqsave(&bspcm->lock, flags); if (bspcm->pcm_irq_pos >= bspcm->pcm_count) { bspcm->pcm_irq_pos %= bspcm->pcm_count; spin_unlock_irqrestore(&bspcm->lock, flags); snd_pcm_period_elapsed(bspcm->substream); } else spin_unlock_irqrestore(&bspcm->lock, flags);}static snd_pcm_uframes_tsnd_card_bt_sco_playback_pointer(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; return bytes_to_frames(runtime, bspcm->pcm_buf_pos);}static snd_pcm_uframes_tsnd_card_bt_sco_capture_pointer(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; return bytes_to_frames(runtime, bspcm->pcm_buf_pos);}static snd_pcm_hardware_t snd_card_bt_sco_playback = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_8000, .rate_min = 8000, .rate_max = 8000, .channels_min = 1, .channels_max = 1, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = 24, .period_bytes_max = MAX_BUFFER_SIZE, .periods_min = 1, .periods_max = 4 * 8000 / 24, .fifo_size = 0,};static snd_pcm_hardware_t snd_card_bt_sco_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_8000, .rate_min = 8000, .rate_max = 8000, .channels_min = 1, .channels_max = 1, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = 24, .period_bytes_max = MAX_BUFFER_SIZE, .periods_min = 1, .periods_max = 4 * 8000 / 24, .fifo_size = 0,};static void snd_card_bt_sco_runtime_free(snd_pcm_runtime_t * runtime){ snd_card_bt_sco_pcm_t *bspcm = runtime->private_data; kfree(bspcm);}static int snd_card_bt_sco_playback_open(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm; snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); dprintk("playback_open\n"); bspcm = kmalloc(sizeof(*bspcm), GFP_KERNEL); if (bspcm == NULL) return -ENOMEM; memset(bspcm, 0, sizeof(*bspcm)); if ((runtime->dma_area = snd_malloc_pages(MAX_BUFFER_SIZE, GFP_KERNEL)) == NULL) { kfree(bspcm); return -ENOMEM; } runtime->dma_bytes = MAX_BUFFER_SIZE; spin_lock_init(&bspcm->lock); bspcm->substream = substream; runtime->private_data = bspcm; runtime->private_free = snd_card_bt_sco_runtime_free; runtime->hw = snd_card_bt_sco_playback; atomic_inc(&bt_sco->playback_count); spin_lock_irq(&bt_sco->count_changed_lock); bt_sco->count_changed = 1; spin_unlock_irq(&bt_sco->count_changed_lock); wake_up(&bt_sco->hwdep_wait); return 0;}static int snd_card_bt_sco_capture_open(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_pcm_t *bspcm; snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); dprintk("capture_open\n"); bspcm = kmalloc(sizeof(*bspcm), GFP_KERNEL); if (bspcm == NULL) return -ENOMEM; memset(bspcm, 0, sizeof(*bspcm)); if ((runtime->dma_area = snd_malloc_pages(MAX_BUFFER_SIZE, GFP_KERNEL)) == NULL) { kfree(bspcm); return -ENOMEM; } runtime->dma_bytes = MAX_BUFFER_SIZE; memset(runtime->dma_area, 0, runtime->dma_bytes); spin_lock_init(&bspcm->lock); bspcm->substream = substream; runtime->private_data = bspcm; runtime->private_free = snd_card_bt_sco_runtime_free; runtime->hw = snd_card_bt_sco_capture; atomic_inc(&bt_sco->capture_count); spin_lock_irq(&bt_sco->count_changed_lock); bt_sco->count_changed = 1; spin_unlock_irq(&bt_sco->count_changed_lock); wake_up(&bt_sco->hwdep_wait); return 0;}static int snd_card_bt_sco_playback_close(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; snd_card_bt_sco_t *bt_sco = snd_pcm_substream_chip(substream); snd_assert(bt_sco->playback == NULL,; ); /* Ensure any references to this in our thread have finished */ down(&bt_sco->playback_sem); up(&bt_sco->playback_sem); atomic_dec(&bt_sco->playback_count); spin_lock_irq(&bt_sco->count_changed_lock); bt_sco->count_changed = 1; spin_unlock_irq(&bt_sco->count_changed_lock); wake_up(&bt_sco->hwdep_wait); snd_free_pages(runtime->dma_area, runtime->dma_bytes); return 0;}static int snd_card_bt_sco_capture_close(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; struct snd_card_bt_sco *bt_sco = (struct snd_card_bt_sco *)substream->private_data; snd_assert(bt_sco->capture == NULL,; ); /* Ensure any references to this in our thread have finished */ down(&bt_sco->capture_sem); up(&bt_sco->capture_sem); atomic_dec(&bt_sco->capture_count); spin_lock_irq(&bt_sco->count_changed_lock); bt_sco->count_changed = 1; spin_unlock_irq(&bt_sco->count_changed_lock); wake_up(&bt_sco->hwdep_wait); snd_free_pages(runtime->dma_area, runtime->dma_bytes); return 0;}static snd_pcm_ops_t snd_card_bt_sco_playback_ops = { .open = snd_card_bt_sco_playback_open, .close = snd_card_bt_sco_playback_close, .ioctl = snd_pcm_lib_ioctl, .prepare = snd_card_bt_sco_playback_prepare, .trigger = snd_card_bt_sco_playback_trigger, .pointer = snd_card_bt_sco_playback_pointer,};static snd_pcm_ops_t snd_card_bt_sco_capture_ops = { .open = snd_card_bt_sco_capture_open, .close = snd_card_bt_sco_capture_close, .ioctl = snd_pcm_lib_ioctl, .prepare = snd_card_bt_sco_capture_prepare, .trigger = snd_card_bt_sco_capture_trigger, .pointer = snd_card_bt_sco_capture_pointer,};static int __init snd_card_bt_sco_pcm(snd_card_bt_sco_t * bt_sco){ snd_pcm_t *pcm; int err; if ((err = snd_pcm_new(bt_sco->card, "Bluetooth SCO PCM", 0, 1, 1, &pcm)) < 0) return err; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_bt_sco_playback_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_bt_sco_capture_ops); pcm->private_data = bt_sco; pcm->info_flags = 0; strcpy(pcm->name, "BT SCO PCM"); return 0;}#define BT_SCO_VOLUME(xname, xindex, addr) \{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ .info = snd_bt_sco_volume_info, \ .get = snd_bt_sco_volume_get, .put = snd_bt_sco_volume_put, \ .private_value = addr }static int snd_bt_sco_volume_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo){ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = MIXER_MIN_VOLUME; uinfo->value.integer.max = MIXER_MAX_VOLUME; return 0;}static int snd_bt_sco_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol){ snd_card_bt_sco_t *bt_sco = snd_kcontrol_chip(kcontrol); unsigned long flags; int addr = kcontrol->private_value; spin_lock_irqsave(&bt_sco->mixer_lock, flags); ucontrol->value.integer.value[0] = bt_sco->mixer_volume[addr]; spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); return 0;}static int snd_bt_sco_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol){ snd_card_bt_sco_t *bt_sco = snd_kcontrol_chip(kcontrol); unsigned long flags; int changed, addr = kcontrol->private_value; int vol; vol = ucontrol->value.integer.value[0]; if (vol < MIXER_MIN_VOLUME) vol = MIXER_MIN_VOLUME; if (vol > MIXER_MAX_VOLUME) vol = MIXER_MAX_VOLUME; spin_lock_irqsave(&bt_sco->mixer_lock, flags); changed = bt_sco->mixer_volume[addr] != vol; bt_sco->mixer_volume[addr] = vol; spin_unlock_irqrestore(&bt_sco->mixer_lock, flags); if (changed) { spin_lock_irqsave(&bt_sco->mixer_changed_lock, flags); bt_sco->mixer_changed = 1; spin_unlock_irqrestore(&bt_sco->mixer_changed_lock, flags); wake_up(&bt_sco->hwdep_wait); } return changed;}static int snd_bt_sco_boolean_info(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -