来源:https://github.com/stadaki/alsa-minivosc-src
https://www.alsa-project.org/wiki/Minivosc
static int debug = 1; /* Use our own dbg macro http://www.n1ywb.com/projects/darts/darts-usb/darts-usb.c*/ #undef dbg #define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0) #define dbg2(format, arg...) do { if (debug) printk( ": " format "\n" , ## arg); } while (0) // copy from aloop-kernel.c: #include <linux/init.h> #include <linux/module.h> #include <linux/jiffies.h> #include <linux/slab.h> #include <linux/time.h> #include <linux/wait.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/control.h> #include <sound/pcm.h> #include <sound/initval.h> #include <linux/version.h> MODULE_AUTHOR("sdaau"); MODULE_DESCRIPTION("minivosc soundcard"); MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{ALSA,minivosc soundcard}}"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; static struct platform_device *devices[SNDRV_CARDS]; #define byte_pos(x) ((x) / HZ) #define frac_pos(x) ((x) * HZ) #define MAX_BUFFER (32 * 48) static struct snd_pcm_hardware minivosc_pcm_hw = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_U8, .rates = SNDRV_PCM_RATE_8000, .rate_min = 8000, .rate_max = 8000, .channels_min = 1, .channels_max = 1, .buffer_bytes_max = MAX_BUFFER, //(32 * 48) = 1536, .period_bytes_min = 48, .period_bytes_max = 48, .periods_min = 1, .periods_max = 32, }; struct minivosc_device { struct snd_card *card; struct snd_pcm *pcm; const struct minivosc_pcm_ops *timer_ops; /* * we have only one substream, so all data in this struct */ /* copied from struct loopback: */ struct mutex cable_lock; /* copied from struct loopback_cable: */ /* PCM parameters */ unsigned int pcm_period_size; unsigned int pcm_bps; /* bytes per second */ /* flags */ unsigned int valid; unsigned int running; unsigned int period_update_pending :1; /* timer stuff */ unsigned int irq_pos; /* fractional IRQ position */ unsigned int period_size_frac; unsigned long last_jiffies; struct timer_list timer; /* copied from struct loopback_pcm: */ struct snd_pcm_substream *substream; unsigned int pcm_buffer_size; unsigned int buf_pos; /* position in buffer */ unsigned int silent_size; /* added for waveform: */ unsigned int wvf_pos; /* position in waveform array */ unsigned int wvf_lift; /* lift of waveform array */ }; #define COPYALG_V3 // waveform #ifdef COPYALG_V3 static char wvfdat[]={ 20, 22, 24, 25, 24, 22, 21, 19, 17, 15, 14, 15, 17, 19, 20, 127, 22, 19, 17, 15, 19}; static unsigned int wvfsz=sizeof(wvfdat);//*sizeof(float) is included already #endif // * functions for driver/kernel module initialization static void minivosc_unregister_all(void); static int __init alsa_card_minivosc_init(void); static void __exit alsa_card_minivosc_exit(void); // * declare functions for this struct describing the driver (to be defined later): #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) static int __devinit minivosc_probe(struct platform_device *devptr); static int __devexit minivosc_remove(struct platform_device *devptr); #else static int minivosc_probe(struct platform_device *devptr); static int minivosc_remove(struct platform_device *devptr); #endif // * here declaration of functions that will need to be in _ops, before they are defined static int minivosc_hw_params(struct snd_pcm_substream *ss, struct snd_pcm_hw_params *hw_params); static int minivosc_hw_free(struct snd_pcm_substream *ss); static int minivosc_pcm_open(struct snd_pcm_substream *ss); static int minivosc_pcm_close(struct snd_pcm_substream *ss); static int minivosc_pcm_prepare(struct snd_pcm_substream *ss); static int minivosc_pcm_trigger(struct snd_pcm_substream *ss, int cmd); static snd_pcm_uframes_t minivosc_pcm_pointer(struct snd_pcm_substream *ss); static int minivosc_pcm_dev_free(struct snd_device *device); static int minivosc_pcm_free(struct minivosc_device *chip); // * declare timer functions - copied from aloop-kernel.c static void minivosc_timer_start(struct minivosc_device *mydev); static void minivosc_timer_stop(struct minivosc_device *mydev); static void minivosc_pos_update(struct minivosc_device *mydev); static void minivosc_timer_function(unsigned long data); static void minivosc_xfer_buf(struct minivosc_device *mydev, unsigned int count); static void minivosc_fill_capture_buf(struct minivosc_device *mydev, unsigned int bytes); // note snd_pcm_ops can usually be separate _playback_ops and _capture_ops static struct snd_pcm_ops minivosc_pcm_ops = { .open = minivosc_pcm_open, .close = minivosc_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = minivosc_hw_params, .hw_free = minivosc_hw_free, .prepare = minivosc_pcm_prepare, .trigger = minivosc_pcm_trigger, .pointer = minivosc_pcm_pointer, }; // specifies what func is called @ snd_card_free // used in snd_device_new static struct snd_device_ops dev_ops = { .dev_free = minivosc_pcm_dev_free, }; #define SND_MINIVOSC_DRIVER "snd_minivosc" // * we need a struct describing the driver: static struct platform_driver minivosc_driver = { .probe = minivosc_probe, #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) .remove = __devexit_p(minivosc_remove), #else .remove = minivosc_remove, #endif //~ #ifdef CONFIG_PM //~ .suspend = minivosc_suspend, //~ .resume = minivosc_resume, //~ #endif .driver = { .name = SND_MINIVOSC_DRIVER, .owner = THIS_MODULE }, }; /* * * Probe/remove functions * */ #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) static int __devinit minivosc_probe(struct platform_device *devptr) #else static int minivosc_probe(struct platform_device *devptr) #endif { struct snd_card *card; struct minivosc_device *mydev; int ret; int nr_subdevs; // how many capture substreams we want struct snd_pcm *pcm; int dev = devptr->id; // from aloop-kernel.c dbg("%s: probe", __func__); // no need to kzalloc minivosc_device separately, if the sizeof is included here ret = snd_card_create(index[dev], id[dev], THIS_MODULE, sizeof(struct minivosc_device), &card); if (ret < 0) goto __nodev; mydev = card->private_data; mydev->card = card; // MUST have mutex_init here - else crash on mutex_lock!! mutex_init(&mydev->cable_lock); dbg2("-- mydev %p", mydev); sprintf(card->driver, "my_driver-%s", SND_MINIVOSC_DRIVER); sprintf(card->shortname, "MySoundCard Audio %s", SND_MINIVOSC_DRIVER); sprintf(card->longname, "%s", card->shortname); snd_card_set_dev(card, &devptr->dev); // present in dummy, not in aloop though ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, mydev, &dev_ops); if (ret < 0) goto __nodev; nr_subdevs = 1; // how many capture substreams we want // * we want 0 playback, and 1 capture substreams (4th and 5th arg) .. ret = snd_pcm_new(card, card->driver, 0, 0, nr_subdevs, &pcm); if (ret < 0) goto __nodev; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &minivosc_pcm_ops); // in both aloop-kernel.c and dummy.c, after snd_pcm_new... pcm->private_data = mydev; //here it should be dev/card struct (the one containing struct snd_card *card) - this DOES NOT end up in substream->private_data pcm->info_flags = 0; strcpy(pcm->name, card->shortname); /* trid to add this - but it crashes here: //mydev->substream->private_data = mydev; Well, first time real substream comes in, is in _open - so that has to be handled there.. That is: at this point, mydev->substream is null, and we first have a chance to set it ... in _open! */ ret = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL), MAX_BUFFER, MAX_BUFFER); // in both aloop-kernel.c and dummy.c, after snd_pcm_set_ops... if (ret < 0) goto __nodev; // * will use the snd_card_register form from aloop-kernel.c/dummy.c here.. ret = snd_card_register(card); if (ret == 0) // or... (!ret) { platform_set_drvdata(devptr, card); return 0; // success } __nodev: // as in aloop/dummy... dbg("__nodev reached!!"); snd_card_free(card); // this will autocall .dev_free (= minivosc_pcm_dev_free) return ret; } // from dummy/aloop: #if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) static int __devexit minivosc_remove(struct platform_device *devptr) #else static int minivosc_remove(struct platform_device *devptr) #endif { dbg("%s", __func__); snd_card_free(platform_get_drvdata(devptr)); platform_set_drvdata(devptr, NULL); return 0; } /* * * hw alloc/free functions * */ static int minivosc_hw_params(struct snd_pcm_substream *ss, struct snd_pcm_hw_params *hw_params) { dbg("%s", __func__); return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); } static int minivosc_hw_free(struct snd_pcm_substream *ss) { dbg("%s", __func__); return snd_pcm_lib_free_pages(ss); } /* * * PCM functions * */ static int minivosc_pcm_open(struct snd_pcm_substream *ss) { struct minivosc_device *mydev = ss->private_data; //BREAKPOINT(); dbg("%s", __func__); // copied from aloop-kernel.c: mutex_lock(&mydev->cable_lock); ss->runtime->hw = minivosc_pcm_hw; mydev->substream = ss; //save (system given) substream *ss, in our structure field ss->runtime->private_data = mydev; mydev->wvf_pos = 0; //init mydev->wvf_lift = 0; //init // SETUP THE TIMER HERE: setup_timer(&mydev->timer, minivosc_timer_function, (unsigned long)mydev); mutex_unlock(&mydev->cable_lock); return 0; } static int minivosc_pcm_close(struct snd_pcm_substream *ss) { struct minivosc_device *mydev = ss->private_data; dbg("%s", __func__); // copied from aloop-kernel.c: // * even though mutexes are retrieved from ss->private_data, // * which will be set to null, // * lock the mutex here anyway: mutex_lock(&mydev->cable_lock); // * not much else to do here, but set to null: ss->private_data = NULL; mutex_unlock(&mydev->cable_lock); return 0; } static int minivosc_pcm_prepare(struct snd_pcm_substream *ss) { // copied from aloop-kernel.c // for one, we could get mydev from ss->private_data... // here we try it via ss->runtime->private_data instead. // turns out, this type of call via runtime->private_data // ends up with mydev as null pointer causing SIGSEGV // .. UNLESS runtime->private_data is assigned in _open? struct snd_pcm_runtime *runtime = ss->runtime; struct minivosc_device *mydev = runtime->private_data; unsigned int bps; dbg("%s", __func__); bps = runtime->rate * runtime->channels; // params requested by user app (arecord, audacity) bps *= snd_pcm_format_width(runtime->format); bps /= 8; if (bps <= 0) return -EINVAL; mydev->buf_pos = 0; mydev->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size); dbg2(" bps: %u; runtime->buffer_size: %lu; mydev->pcm_buffer_size: %u", bps, runtime->buffer_size, mydev->pcm_buffer_size); if (ss->stream == SNDRV_PCM_STREAM_CAPTURE) { /* clear capture buffer */ mydev->silent_size = mydev->pcm_buffer_size; //memset(runtime->dma_area, 0, mydev->pcm_buffer_size); // we're in char land here, so let's mark prepare buffer with value 45 (signature) // this turns out to set everything permanently throughout - not just first buffer, // even though it runs only at start? memset(runtime->dma_area, 45, mydev->pcm_buffer_size); } if (!mydev->running) { mydev->irq_pos = 0; mydev->period_update_pending = 0; } mutex_lock(&mydev->cable_lock); if (!(mydev->valid & ~(1 << ss->stream))) { mydev->pcm_bps = bps; mydev->pcm_period_size = frames_to_bytes(runtime, runtime->period_size); mydev->period_size_frac = frac_pos(mydev->pcm_period_size); } mydev->valid |= 1 << ss->stream; mutex_unlock(&mydev->cable_lock); dbg2(" pcm_period_size: %u; period_size_frac: %u", mydev->pcm_period_size, mydev->period_size_frac); return 0; } static int minivosc_pcm_trigger(struct snd_pcm_substream *ss, int cmd) { int ret = 0; //copied from aloop-kernel.c //here we do not get mydev from // ss->runtime->private_data; but from: struct minivosc_device *mydev = ss->private_data; dbg("%s - trig %d", __func__, cmd); switch (cmd) { case SNDRV_PCM_TRIGGER_START: // Start the hardware capture // from aloop-kernel.c: if (!mydev->running) { mydev->last_jiffies = jiffies; // SET OFF THE TIMER HERE: minivosc_timer_start(mydev); } mydev->running |= (1 << ss->stream); break; case SNDRV_PCM_TRIGGER_STOP: // Stop the hardware capture // from aloop-kernel.c: mydev->running &= ~(1 << ss->stream); if (!mydev->running) // STOP THE TIMER HERE: minivosc_timer_stop(mydev); break; default: ret = -EINVAL; } return ret; } static snd_pcm_uframes_t minivosc_pcm_pointer(struct snd_pcm_substream *ss) { //copied from aloop-kernel.c struct snd_pcm_runtime *runtime = ss->runtime; struct minivosc_device *mydev= runtime->private_data; dbg2("+minivosc_pointer "); minivosc_pos_update(mydev); dbg2("+ bytes_to_frames(: %lu, mydev->buf_pos: %d", bytes_to_frames(runtime, mydev->buf_pos),mydev->buf_pos); return bytes_to_frames(runtime, mydev->buf_pos); } /* * * Timer functions * */ static void minivosc_timer_start(struct minivosc_device *mydev) { unsigned long tick; dbg2("minivosc_timer_start: mydev->period_size_frac: %u; mydev->irq_pos: %u jiffies: %lu pcm_bps %u", mydev->period_size_frac, mydev->irq_pos, jiffies, mydev->pcm_bps); tick = mydev->period_size_frac - mydev->irq_pos; tick = (tick + mydev->pcm_bps - 1) / mydev->pcm_bps; mydev->timer.expires = jiffies + tick; add_timer(&mydev->timer); } static void minivosc_timer_stop(struct minivosc_device *mydev) { dbg2("minivosc_timer_stop"); del_timer(&mydev->timer); } static void minivosc_pos_update(struct minivosc_device *mydev) { unsigned int last_pos, count; unsigned long delta; if (!mydev->running) return; dbg2("*minivosc_pos_update: running "); delta = jiffies - mydev->last_jiffies; dbg2("* : jiffies %lu, ->last_jiffies %lu, delta %lu", jiffies, mydev->last_jiffies, delta); if (!delta) return; mydev->last_jiffies += delta; last_pos = byte_pos(mydev->irq_pos); mydev->irq_pos += delta * mydev->pcm_bps; count = byte_pos(mydev->irq_pos) - last_pos; dbg2("* : last_pos %d, c->irq_pos %d, count %d", last_pos, mydev->irq_pos, count); if (!count) return; // FILL BUFFER HERE minivosc_xfer_buf(mydev, count); if (mydev->irq_pos >= mydev->period_size_frac) { dbg2("* : mydev->irq_pos >= mydev->period_size_frac %d", mydev->period_size_frac); mydev->irq_pos %= mydev->period_size_frac; mydev->period_update_pending = 1; } } static void minivosc_timer_function(unsigned long data) { struct minivosc_device *mydev = (struct minivosc_device *)data; if (!mydev->running) return; dbg2("minivosc_timer_function: running "); minivosc_pos_update(mydev); // SET OFF THE TIMER HERE: minivosc_timer_start(mydev); if (mydev->period_update_pending) { mydev->period_update_pending = 0; if (mydev->running) { dbg2(" : calling snd_pcm_period_elapsed"); snd_pcm_period_elapsed(mydev->substream); } } } #define CABLE_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK) #define CABLE_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE) #define CABLE_BOTH (CABLE_PLAYBACK | CABLE_CAPTURE) static void minivosc_xfer_buf(struct minivosc_device *mydev, unsigned int count) { dbg2(">minivosc_xfer_buf: count: %d ", count ); switch (mydev->running) { case CABLE_CAPTURE: minivosc_fill_capture_buf(mydev, count); break; } if (mydev->running) { // activate this buf_pos calculation, either if V3 is defined, #ifdef COPYALG_V3 // here the (auto)increase of buf_pos is handled mydev->buf_pos += count; mydev->buf_pos %= mydev->pcm_buffer_size; dbg2("> : mydev->buf_pos: %d ", mydev->buf_pos); // */ #endif } } static void minivosc_fill_capture_buf(struct minivosc_device *mydev, unsigned int bytes) { char *dst = mydev->substream->runtime->dma_area; unsigned int dst_off = mydev->buf_pos; // buf_pos is in bytes, not in samples ! float wrdat; // was char - value to fill silent_size with unsigned int dpos = 0; //added dbg2("_ minivosc_fill_capture_buf ss %d bs %d bytes %d buf_pos %d sizeof %ld jiffies %lu", mydev->silent_size, mydev->pcm_buffer_size, bytes, dst_off, sizeof(*dst), jiffies); #if defined(COPYALG_V3) // as in copy_play_buf in aloop-kernel.c, where we had: //~ char *src = play->substream->runtime->dma_area; //~ char *dst = capt->substream->runtime->dma_area; // 'buf_pos' here is calculated in _xfer_buf, and // the waveform wrapping is not correct // using memcpy for copying/filling for (;;) { unsigned int size = bytes; if (mydev->wvf_pos + size > wvfsz) size = wvfsz - mydev->wvf_pos; if (dst_off + size > mydev->pcm_buffer_size) size = mydev->pcm_buffer_size - dst_off; memcpy(dst + dst_off, wvfdat + mydev->wvf_pos, size); if (size < mydev->silent_size) mydev->silent_size -= size; else mydev->silent_size = 0; bytes -= size; if (!bytes) break; mydev->wvf_pos = (mydev->wvf_pos + size) % wvfsz; dst_off = (dst_off + size) % mydev->pcm_buffer_size; } #endif //defined(COPYALG_V3) if (mydev->silent_size >= mydev->pcm_buffer_size) return; // NOTE: usually, the code returns by now - // - it doesn't even execute past this point! // from here on, apparently silent_size should be handled.. if (mydev->silent_size + bytes > mydev->pcm_buffer_size) bytes = mydev->pcm_buffer_size - mydev->silent_size; wrdat = -0.2; // value to copy, instead of 0 for silence (if needed) for (;;) { unsigned int size = bytes; dpos = 0; //added dbg2("_ clearrr.. %d", bytes); if (dst_off + size > mydev->pcm_buffer_size) size = mydev->pcm_buffer_size - dst_off; //memset(dst + dst_off, 255, size); //0, size); while (dpos < size) { memcpy(dst + dst_off + dpos, &wrdat, sizeof(wrdat)); dpos += sizeof(wrdat); if (dpos >= size) break; } mydev->silent_size += size; bytes -= size; if (!bytes) break; dst_off = 0; } } /* * * snd_device_ops free functions * */ // these should eventually get called by snd_card_free (via .dev_free) // however, since we do no special allocations, we need not free anything static int minivosc_pcm_free(struct minivosc_device *chip) { dbg("%s", __func__); return 0; } static int minivosc_pcm_dev_free(struct snd_device *device) { dbg("%s", __func__); return minivosc_pcm_free(device->device_data); } /* * * functions for driver/kernel module initialization * (_init, _exit) * copied from aloop-kernel.c (same in dummy.c) * */ static void minivosc_unregister_all(void) { int i; dbg("%s", __func__); for (i = 0; i < ARRAY_SIZE(devices); ++i) platform_device_unregister(devices[i]); platform_driver_unregister(&minivosc_driver); } static int __init alsa_card_minivosc_init(void) { int i, err, cards; dbg("%s", __func__); err = platform_driver_register(&minivosc_driver); if (err < 0) return err; cards = 0; for (i = 0; i < SNDRV_CARDS; i++) { struct platform_device *device; if (!enable[i]) continue; device = platform_device_register_simple(SND_MINIVOSC_DRIVER, i, NULL, 0); if (IS_ERR(device)) continue; if (!platform_get_drvdata(device)) { platform_device_unregister(device); continue; } devices[i] = device; cards++; } if (!cards) { #ifdef MODULE printk(KERN_ERR "minivosc-alsa: No enabled, not found or device busy\n"); #endif minivosc_unregister_all(); return -ENODEV; } return 0; } static void __exit alsa_card_minivosc_exit(void) { dbg("%s", __func__); minivosc_unregister_all(); } module_init(alsa_card_minivosc_init) module_exit(alsa_card_minivosc_exit)
Makefile
CONFIG_MODULE_FORCE_UNLOAD=y # debug build: # "CFLAGS was changed ... Fix it to use EXTRA_CFLAGS." EXTRA_CFLAGS=-Wall -Wmissing-prototypes -Wstrict-prototypes -g -O2 obj-m += snd-minivosc.o snd-minivosc-objs := minivosc.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
lnsmod ./snd-minivosc.ko
arecord -D hw:1,0 -d 2 foo.wav
rmmod snd-minivosc.ko
来源:https://www.cnblogs.com/fellow1988/p/12395797.html