📄 caiaq-audio.c
字号:
/* * Copyright (c) 2006,2007 Daniel Mack, Karsten Wiese * * 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*/#include <sound/driver.h>#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/interrupt.h>#include <linux/usb.h>#include <linux/spinlock.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/pcm.h>#include <sound/rawmidi.h>#ifdef CONFIG_SND_USB_CAIAQ_INPUT#include <linux/input.h>#endif#include "caiaq-device.h"#include "caiaq-audio.h"#define N_URBS 32#define CLOCK_DRIFT_TOLERANCE 5#define FRAMES_PER_URB 8#define BYTES_PER_FRAME 512#define CHANNELS_PER_STREAM 2#define BYTES_PER_SAMPLE 3#define BYTES_PER_SAMPLE_USB 4#define MAX_BUFFER_SIZE (128*1024) #define ENDPOINT_CAPTURE 2#define ENDPOINT_PLAYBACK 6#define MAKE_CHECKBYTE(dev,stream,i) \ (stream << 1) | (~(i / (dev->n_streams * BYTES_PER_SAMPLE_USB)) & 1)static struct snd_pcm_hardware snd_usb_caiaq_pcm_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = SNDRV_PCM_FMTBIT_S24_3BE, .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000), .rate_min = 44100, .rate_max = 0, /* will overwrite later */ .channels_min = CHANNELS_PER_STREAM, .channels_max = CHANNELS_PER_STREAM, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = 4096, .period_bytes_max = MAX_BUFFER_SIZE, .periods_min = 1, .periods_max = 1024,};static voidactivate_substream(struct snd_usb_caiaqdev *dev, struct snd_pcm_substream *sub){ if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) dev->sub_playback[sub->number] = sub; else dev->sub_capture[sub->number] = sub;}static void deactivate_substream(struct snd_usb_caiaqdev *dev, struct snd_pcm_substream *sub){ if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) dev->sub_playback[sub->number] = NULL; else dev->sub_capture[sub->number] = NULL;}static intall_substreams_zero(struct snd_pcm_substream **subs){ int i; for (i = 0; i < MAX_STREAMS; i++) if (subs[i] != NULL) return 0; return 1;}static int stream_start(struct snd_usb_caiaqdev *dev){ int i, ret; debug("stream_start(%p)\n", dev); spin_lock_irq(&dev->spinlock); if (dev->streaming) { spin_unlock_irq(&dev->spinlock); return -EINVAL; } dev->input_panic = 0; dev->output_panic = 0; dev->first_packet = 1; dev->streaming = 1; for (i = 0; i < N_URBS; i++) { ret = usb_submit_urb(dev->data_urbs_in[i], GFP_ATOMIC); if (ret) { log("unable to trigger initial read #%d! (ret = %d)\n", i, ret); dev->streaming = 0; spin_unlock_irq(&dev->spinlock); return -EPIPE; } } spin_unlock_irq(&dev->spinlock); return 0;}static void stream_stop(struct snd_usb_caiaqdev *dev){ int i; debug("stream_stop(%p)\n", dev); if (!dev->streaming) return; dev->streaming = 0; for (i = 0; i < N_URBS; i++) { usb_unlink_urb(dev->data_urbs_in[i]); usb_unlink_urb(dev->data_urbs_out[i]); }}static int snd_usb_caiaq_substream_open(struct snd_pcm_substream *substream){ struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream); debug("snd_usb_caiaq_substream_open(%p)\n", substream); substream->runtime->hw = dev->pcm_info; snd_pcm_limit_hw_rates(substream->runtime); return 0;}static int snd_usb_caiaq_substream_close(struct snd_pcm_substream *substream){ struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream); debug("snd_usb_caiaq_substream_close(%p)\n", substream); if (all_substreams_zero(dev->sub_playback) && all_substreams_zero(dev->sub_capture)) { /* when the last client has stopped streaming, * all sample rates are allowed again */ stream_stop(dev); dev->pcm_info.rates = dev->samplerates; } return 0;}static int snd_usb_caiaq_pcm_hw_params(struct snd_pcm_substream *sub, struct snd_pcm_hw_params *hw_params){ debug("snd_usb_caiaq_pcm_hw_params(%p)\n", sub); return snd_pcm_lib_malloc_pages(sub, params_buffer_bytes(hw_params));}static int snd_usb_caiaq_pcm_hw_free(struct snd_pcm_substream *sub){ struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub); debug("snd_usb_caiaq_pcm_hw_free(%p)\n", sub); spin_lock_irq(&dev->spinlock); deactivate_substream(dev, sub); spin_unlock_irq(&dev->spinlock); return snd_pcm_lib_free_pages(sub);}/* this should probably go upstream */#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12#error "Change this table"#endifstatic unsigned int rates[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000 };static int snd_usb_caiaq_pcm_prepare(struct snd_pcm_substream *substream){ int bytes_per_sample, bpp, ret, i; int index = substream->number; struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; debug("snd_usb_caiaq_pcm_prepare(%p)\n", substream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) dev->audio_out_buf_pos[index] = BYTES_PER_SAMPLE + 1; else dev->audio_in_buf_pos[index] = 0; if (dev->streaming) return 0; /* the first client that opens a stream defines the sample rate * setting for all subsequent calls, until the last client closed. */ for (i=0; i < ARRAY_SIZE(rates); i++) if (runtime->rate == rates[i]) dev->pcm_info.rates = 1 << i; snd_pcm_limit_hw_rates(runtime); bytes_per_sample = BYTES_PER_SAMPLE; if (dev->spec.data_alignment == 2) bytes_per_sample++; bpp = ((runtime->rate / 8000) + CLOCK_DRIFT_TOLERANCE) * bytes_per_sample * CHANNELS_PER_STREAM * dev->n_streams; ret = snd_usb_caiaq_set_audio_params(dev, runtime->rate, runtime->sample_bits, bpp); if (ret) return ret; ret = stream_start(dev); if (ret) return ret; dev->output_running = 0; wait_event_timeout(dev->prepare_wait_queue, dev->output_running, HZ); if (!dev->output_running) { stream_stop(dev); return -EPIPE; } return 0;}static int snd_usb_caiaq_pcm_trigger(struct snd_pcm_substream *sub, int cmd){ struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: spin_lock(&dev->spinlock); activate_substream(dev, sub); spin_unlock(&dev->spinlock); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: spin_lock(&dev->spinlock); deactivate_substream(dev, sub); spin_unlock(&dev->spinlock); break; default: return -EINVAL; } return 0;}static snd_pcm_uframes_tsnd_usb_caiaq_pcm_pointer(struct snd_pcm_substream *sub){ int index = sub->number; struct snd_usb_caiaqdev *dev = snd_pcm_substream_chip(sub); if (dev->input_panic || dev->output_panic) return SNDRV_PCM_POS_XRUN; if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) return bytes_to_frames(sub->runtime, dev->audio_out_buf_pos[index]); else return bytes_to_frames(sub->runtime, dev->audio_in_buf_pos[index]);}/* operators for both playback and capture */static struct snd_pcm_ops snd_usb_caiaq_ops = { .open = snd_usb_caiaq_substream_open, .close = snd_usb_caiaq_substream_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_usb_caiaq_pcm_hw_params, .hw_free = snd_usb_caiaq_pcm_hw_free, .prepare = snd_usb_caiaq_pcm_prepare, .trigger = snd_usb_caiaq_pcm_trigger, .pointer = snd_usb_caiaq_pcm_pointer}; static void check_for_elapsed_periods(struct snd_usb_caiaqdev *dev, struct snd_pcm_substream **subs){ int stream, pb, *cnt; struct snd_pcm_substream *sub; for (stream = 0; stream < dev->n_streams; stream++) { sub = subs[stream]; if (!sub) continue; pb = frames_to_bytes(sub->runtime, sub->runtime->period_size); cnt = (sub->stream == SNDRV_PCM_STREAM_PLAYBACK) ? &dev->period_out_count[stream] : &dev->period_in_count[stream]; if (*cnt >= pb) { snd_pcm_period_elapsed(sub); *cnt %= pb; } }}static void read_in_urb_mode0(struct snd_usb_caiaqdev *dev, const struct urb *urb, const struct usb_iso_packet_descriptor *iso){ unsigned char *usb_buf = urb->transfer_buffer + iso->offset; struct snd_pcm_substream *sub; int stream, i; if (all_substreams_zero(dev->sub_capture)) return; spin_lock(&dev->spinlock); for (i = 0; i < iso->actual_length;) { for (stream = 0; stream < dev->n_streams; stream++, i++) { sub = dev->sub_capture[stream]; if (sub) { struct snd_pcm_runtime *rt = sub->runtime; char *audio_buf = rt->dma_area; int sz = frames_to_bytes(rt, rt->buffer_size); audio_buf[dev->audio_in_buf_pos[stream]++] = usb_buf[i]; dev->period_in_count[stream]++; if (dev->audio_in_buf_pos[stream] == sz) dev->audio_in_buf_pos[stream] = 0; } } } spin_unlock(&dev->spinlock);}static void read_in_urb_mode2(struct snd_usb_caiaqdev *dev,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -