平台:ubuntu 16.04,kernel版本是4.15.0, 理论任何平台都可以,甚至是android,只要能编译通过。

需要完成的功能:传说中的回采,做过语音方案的童鞋应该能懂,就是播放的音频,录音录回去。因为是虚拟的声卡,不涉及硬件操作,也只能这样看点效果。

目的:当然是为了能更直观的理解alsa驱动框架。虚拟出一个声卡,不涉及复杂的硬件操作,不涉及复杂的硬件调试,只关心数据流怎么一步一步传给应用的。

1.数据是怎么交互的

以playback为例

  1. 驱动程序分配一个buffer

  2. APP不断写入一个period的数据到buffer
    一个period含有多个frame
    一个frame就是一个采样数据

  3. 驱动不断从buffer里取出一个period,并发送给codec

  4. app更新appl_ptr指针, 驱动更新hw_ptr指针,当指针更新到buffer尾部,从头开始。

2.分配DMA内存

分配dma内存的是.pcm_new

static int vplat_pcm_new(struct snd_soc_pcm_runtime *rtd) {struct snd_card *card = rtd->card->snd_card;struct snd_pcm *pcm = rtd->pcm;struct snd_pcm_substream *substream;struct snd_dma_buffer *buf;int ret = 0;if (!card->dev->dma_mask)card->dev->dma_mask = &dma_mask;if (!card->dev->coherent_dma_mask)card->dev->coherent_dma_mask = 0xffffffff;if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {playback_info.buf_max_size = vplat_pcm_hardware.buffer_bytes_max;substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;playback_info.substream = substream;buf = &substream->dma_buffer;buf->area = dma_alloc_coherent(pcm->card->dev, playback_info.buf_max_size,&buf->addr, GFP_KERNEL);if (!buf->area) {printk(KERN_ERR"plaback alloc dma error!!!\n");return -ENOMEM;}buf->dev.type = SNDRV_DMA_TYPE_DEV;buf->dev.dev = pcm->card->dev;buf->private_data = NULL;buf->bytes = playback_info.buf_max_size;playback_info.addr = buf->area;}if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {capture_info.buf_max_size = vplat_pcm_hardware.buffer_bytes_max;substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;capture_info.substream = substream;buf = &substream->dma_buffer;buf->area = dma_alloc_coherent(pcm->card->dev, capture_info.buf_max_size,&buf->addr, GFP_KERNEL);if (!buf->area) {printk(KERN_ERR"catpure alloc dma error!!!\n");return -ENOMEM;}buf->dev.type = SNDRV_DMA_TYPE_DEV;buf->dev.dev = pcm->card->dev;buf->private_data = NULL;buf->bytes = capture_info.buf_max_size; capture_info.addr = buf->area;}return ret;
}

调用dma_alloc_coherent()分配dma buffer,其实就是一块连续的物理内存。

dma在声卡的使用可以看:
[RK3288][Android6.0] Audio的DMA调用实例流程
[IMX6DL][Android4.4] Linux dmaengine 使用方法

3.启动定时器,模拟数据传输中断

定时器在vplat_pcm_trigger()启动

static int vplat_pcm_trigger(struct snd_pcm_substream *substream, int cmd) {int ret = 0;static u8 is_timer_run = 0;if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {switch (cmd) {case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:/* 启动定时器, 模拟数据传输 */printk("playback running...\n");playback_info.is_running = 1;if(!is_timer_run) {is_timer_run = 1;start_timer();}break;case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:case SNDRV_PCM_TRIGGER_PAUSE_PUSH:/* 停止定时器 */printk("playback stop...\n");playback_info.is_running = 0;if(!capture_info.is_running){is_timer_run = 0;del_timer(&vtimer);}break;default:ret = -EINVAL;break;}} else {switch (cmd) {case SNDRV_PCM_TRIGGER_START:case SNDRV_PCM_TRIGGER_RESUME:case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:/* catpure开始接收数据 */      printk("capture running...\n");capture_info.is_running = 1;if(!is_timer_run) {is_timer_run = 1;start_timer();}break;case SNDRV_PCM_TRIGGER_STOP:case SNDRV_PCM_TRIGGER_SUSPEND:case SNDRV_PCM_TRIGGER_PAUSE_PUSH:/* catpure停止接收数据 */printk("capture stop...\n");capture_info.is_running = 0;if(!playback_info.is_running){is_timer_run = 0;del_timer(&vtimer);}break;default:ret = -EINVAL;break;}}return ret;
}

定时器处理函数就是模拟DMA传输完成的中断处理函数,如下:

static void vplat_timer_function(struct timer_list *t) {schedule_work(&vplat_work);
}

启动一个work,work的处理函数如下:

static void work_function(struct work_struct *work){struct snd_pcm_substream *pb_substream = playback_info.substream;if (capture_info.is_running) {load_buff_period();}// 更新状态信息if(playback_info.is_running){playback_info.buf_pos += playback_info.period_size;if (playback_info.buf_pos >= playback_info.buffer_size)playback_info.buf_pos = 0;// 更新hw_ptr等信息,// 并且判断:如果buffer里没有数据了,则调用trigger来停止DMA snd_pcm_period_elapsed(pb_substream); }if (playback_info.is_running || capture_info.is_running) {//再次启动定时器mod_timer(&vtimer, jiffies + HZ/10);}
}

如果此时正在录音,调用load_buff_period()将playback的buffer数据拷贝到capture的buffer。
调用snd_pcm_period_elapsed()更新hw_ptr指针;
load_buff_period()实现如下:

static int load_buff_period(void) {struct snd_pcm_substream *cp_substream = capture_info.substream;int size = 0;if(capture_info.addr == NULL) {printk(KERN_ERR"catpure addr error!!!\n");return -1;}if (playback_info.is_running) {if(capture_info.period_size != playback_info.period_size) {printk(KERN_ERR"capture_info.period_size(%d) != playback_info.period_size(%d)\n",capture_info.period_size,playback_info.period_size);}size = capture_info.period_size <= playback_info.period_size ?capture_info.period_size :playback_info.period_size;//复制playback的一帧数据到catpurememcpy(capture_info.addr+capture_info.buf_pos,playback_info.addr+playback_info.buf_pos,size);} else {memset(capture_info.addr+capture_info.buf_pos,0x00,capture_info.period_size);}//更新capture当前buffer指针位置capture_info.buf_pos += capture_info.period_size;if (capture_info.buf_pos >= capture_info.buffer_size)capture_info.buf_pos = 0;snd_pcm_period_elapsed(cp_substream);return 0;
}

通过memcpy()复制playback的一帧数据到catpure,完事之后调用snd_pcm_period_elapsed()更新hw_ptr同时唤醒等待的录音进程。

4.测试

和上一篇一样安装完驱动之后,
一个终端执行命令:aplay -D hw:1,0 play.wav

参数解析:
-D 指定了录音设备,1,0 是card 1 device 0的意思

不知道是card几,可以执行aplay -l查看

一个终端执行命令:arecord -D hw:1,0 -d 10 -r 48000 -f S16_LE test.wav

参数解析
-D 指定了录音设备,1,0 是card 1 device 0的意思
-d 指定录音的时长,单位时秒(如果不加,可以使用Ctrl + C结束录音)
-f 指定录音格式
-r 指定了采样率,单位时Hz
-t 指定生成的文件格式

注意:录音采样率、格式必须和play.wav相同!!
不然录出来的音频和play.wav的不一样,就是失真。

window可以用Audacity打开test.wav查看,如下图,Audacity同样可以导入原始的pcm数据,具体操作问度娘。

5.问题记录

(1) aplay: set_params:1403: Unable to install hw params
出现这问题原因是底层驱动不支持aplay设置的hw params,参照kernel其他asoc分别设置codec dai、cpu dai和platform格式相关的参数。

(2) underrun!!! (at least 660.013 ms long)
出现这问题原因是应用准备的音频数据不够,比如,驱动需要播放需要 1026 帧数据,但应用只准备好了 1024 帧。
这里设计的是虚拟声卡,不涉及硬件传输,所以用定时器模拟数据传输完成产生的中断,如果定时时间太快,就会产生此问题,时间加大点就好了,这样应用就能准备好更多音频数据。
但是笔者的电脑本身就卡,运行虚拟机跑ubuntu 16.04,再运行qemu,导致在qemu测试,也容易出现此问题。
在ubuntu 16.04直接insmod驱动,就不会出现此问题。

(3) pcm_read:2145: read error: Input/output error
这是aplay报的错,刚开始很诧异,因为驱动没有报任何错。
没有aplay源码,不好定位问题,怎么办呢?
使用strace跟踪一下系统调用,用法是: “strace -o strace.log [命令]”, 最终生成strace.log,打开看一下,发现

ioctl(4, SNDRV_PCM_IOCTL_READI_FRAMES, 0x7e9199d4) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x1a26cb0) = 0
write(3,"\377\377\377\377\377\377\377\377\377\3\377\377\377\377\377\377"…, 12000) = 12000
ioctl(4, SNDRV_PCM_IOCTL_READI_FRAMES, 0x7e9199d4) = -1 EIO (Input/output error)
write(2, "arecord: pcm_read:2145: ", 24) = 24
write(2, “read error: Input/output error”, 30) = 30

可见,执行了两次SNDRV_PCM_IOCTL_READI_FRAMES ioctl,第一次成功,第二次失败。
查看代码发现,在只执行录音操作时,定时器只执行了一遍。第二次驱动没有及时执行snd_pcm_period_elapsed()函数,导致应用等待超时。
最终修改看最新代码,链接在本文末。

(4)驱动获取不到playback数据
驱动代码基本完成,开一个终端aplay播放,开一个终端arecord录音,看能不能录到正在播放的音频。结果录出来的音频如下图:

这根本就不是aplay播放的音频数据,猜想是因为buf没有初始化,所以是乱码,那就给它个初始化吧。
emm…, 猜想是对的,改了之后直接是0了,录不到数据。
继续分析,查看内核打印,没报错,aplay和arecord也没报错。哦豁。
要么是playback有问题,要么是capture有问题。那就先检查playback,使用vfs_write()把应用传下来的数据写到一个pcm文件里,然后在电脑上使用Audacity工具导入,发现压根没数据,所以确定playback有问题。
playback怎么会有问题呢?内核又没报错。老规矩,strace跟踪一下系统调用,打开strace.log一看,发现一大堆这种打印:

ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x4e7220) = 0

怎么没有write呢?在往上走发现了一条这样的打印:

mmap2(NULL, 98304, PROT_READ|PROT_WRITE, MAP_SHARED, 4, 0) = 0x76d16000

由此看来aplay并没有调write系统调用往驱动写数据,而是通过mmap的方式,但是驱动并没有实现mmap,问题找到了。
具体修改看最新代码,链接在本文末。

6.代码

代码位置:https://codechina.csdn.net/u014056414/myalsa
后续会增加其他的功能,完成本文的提交是:5.完成回采功能
初学者可以按照此提交学习,以免新功能干扰;
后续有新功能,一般是DAPM,会有相应的文章。

8.声卡驱动03-自己实现alsa驱动-虚拟声卡-缓存相关推荐

  1. ubuntu14.04安装oss音频驱动,替换掉alsa驱动

    环境: ubuntu14.04 64位 前言: 由于不知道怎么操作alsa驱动下的音频设备,所以使用oss替换掉alsa.替换之后,就可以使用open函数打开"/dev/dsp"设 ...

  2. 虚拟服务器声卡,如何使用虚拟声卡?虚拟声卡安装教程!

    在没有声卡的机器上播放音频和视频文件时,将出现诸如"找不到音频设备"的提示,并且无法播放. 虚拟声卡是一种软件的名称,可用于在没有声卡的机器上实现诸如声音回放之类的功能. 如何使用 ...

  3. 用ALSA驱动声卡流程详解

    作者:北南南北 来自:LinuxSir.Org 提要:目前大多数发行版都已经支持主流声卡,声卡的驱动无非是用ALSA:本文主要讲述声卡驱动的流程:目的是帮助遇到声卡的驱动问题的弟兄来弄清楚解决问题的流 ...

  4. Linux中用ALSA驱动声卡流程详解

    一.什么是ALSA : Advanced Linux Sound Architecture 的简称为 ALSA ,译成中文的意思是 Linux 高级声音体系(这是我直译的,可能译的不对):一谈到体系就 ...

  5. Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建

    (1)ALSA简介 (1) Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音.录音.控制 ALSA ...

  6. 使用最新 ALSA 驱动解决 UBUNTU LINUX INTEL 集成声卡问题

    刚开始学习alsa驱动,刚开始装就出现了一大堆问题,所以在网上找解决方案,看到一篇不错的文章,转载以供大家分享. 目前用户所抱怨的 Ubuntu 系列的声卡问题,基本上归结为几类:一,找不到声音设备: ...

  7. arm linux alsa驱动使用 usb 声卡

    这里写目录标题 一:添加alsa驱动 二:添加alsa-lib和alsa-utils 三:录音测试 四:播放测试 五:alsa 库文件与头文件 一:添加alsa驱动 alsa内核配置选项: 二:添加a ...

  8. arm linux免驱usb声卡,arm linux利用alsa驱动并使用usb音频设备

    一.背景: arm linux的内核版本是3.13.0 二.准备工作 添加alsa驱动到内核中,也就是在编译内核的时候加入以下选项: 接下来就重新编译内核即可 三.交叉编译alsa-lib和alsa- ...

  9. linux系统声卡安装教程,Linux系统下如何安装声卡驱动?

    装了几次Linux OS,当然也装了几次声卡驱动,一般来说都是安装ALSA(Adcance Linux Sound Architecture)驱动,多装几次以后就会发现非常的简单的. 首先,先决条件, ...

  10. alsa 驱动介绍及user层到hw层文件ioctl操作流程分析

    您当前位置:首页 > php开源 > 综合技术 > alsa 驱动介绍 alsa 驱动介绍 来源:程序员人生   发布时间:2016-07-02 13:40:22 阅读次数:6838 ...

最新文章

  1. JSP内置对象-pageContext
  2. bluecam连接步骤说明_磊科路由器登录设置及默认密码说明
  3. 检测msmq里消息的数量
  4. Premiere Pro CS4软件安装教程
  5. Dubbo监控中心配置
  6. 五十一、微信小程序云开发中的云函数
  7. Oracle GoldenGate简介
  8. 仿minecraft游戏 linux,【图片】【Codea制作仿MineCraft3D游戏】Craft Ver. 0.1发布(开源)【codea吧】_百度贴吧...
  9. 我决定不能贱卖自己(2010年11月13日志)
  10. Glide修改本地图片缓存路径
  11. SQL注入盲注——布尔注入
  12. matlab的句柄 图形对象 gca gco gcf set get
  13. 【Redis 系列】redis 学习十五,redis sds数据结构和底层设计原理
  14. Bitcherry BCHC:阿里收购考拉成跨境电商一哥 仍面平台临信任问题
  15. 用网线给服务器装系统,只用网线从零开始安装系统
  16. Navicat Premium 16 隆重登场
  17. 网易互动直播2.0 开发 十二 视频设备管理
  18. Java同步器之AbstractOwnableSynchronizer详解
  19. JAVA的使用idea导包
  20. vulnhub之FirstBlood: 1

热门文章

  1. miRNA数据库篇——Rfam数据库
  2. ADS添加路由失败的处理办法
  3. 好用的多桌面间共享鼠标键盘的软件
  4. AD14插入原理图模板
  5. (二)4.逻辑函数的化简
  6. Windows系统下快速安装、配置Aira2,及图形界面配置、度盘、B站视频下载
  7. Linux系统可视化界面与Shell界面切换
  8. HTML教程(完整版).pdf,HTML教程详解(完整版).pdf
  9. RocketMQ的底层通信模块remoting 源码解析
  10. bmc156 linux驱动源码,BMC156 BOSCH博世 加速+电子罗盘 全新电子罗盘传感器 只做原装...