经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Linux/Shell » 查看文章
Linux音频采集和在国产化平台中遇到的坑(一)
来源:cnblogs  作者:haibindev  时间:2023/2/1 9:38:38  对本文有异议

Linux音频采集和在国产化平台中遇到的坑(一)

最近在做一个国产化平台的软件项目的开发,是基于国产芯片的银河麒麟系统。其中有一个重要模块,是采集和播放音频数据,播放不用多说了,采集的话,包括采集麦克风和采集桌面系统声音。很多人都觉得银河麒麟不就是linux么,那不直接用ALSA就好了,我原本也是这么想的,但是实际开发下来才发现,还是有各种坑需要自己去趟的。这里我简单记录一下。

虽然都是linux,芯片也是基于同样的架构,同样的指令集,但是考虑到芯片的实现毕竟是不同的,于是所有涉及到硬件交互的软件部分,也会有所差异,最终会导致了有些应用层面的接口,不能按照普通linux的通常用法去使用。

linux ALSA音频采集

首先,银河麒麟既然是linux系统,那首先考虑到的是通过ALSA(Advanced Linux Sound Architecture)来进行采集,ALSA是linux的默认声卡驱动,同时在用户层还有一个ALSA Lib来供应用程序调用,它的整体上的结构图是这个样子的:

image

应用程序通常都是通过alsa-lib来使用,如果系统没有的话,可以通过命令安装开发库,就可以使用了。例如

sudo apt-get install libasound2-dev

另外需要注意一点的是,如果是android系统,那么系统里通常是不存在alsa的,而是它的简化版tiny-alsa,接口名称也不一样,但是大致调用流程是相同的。

alsa音频采集,有几个关键函数

  1. #include <sys/asoundlib.h>
  2. /***
  3. 创建alsa pcm handle去连接设备
  4. @param handle: 返回创建的PCM handle
  5. @param name: 设备名称,ASCII编码
  6. @param stream: 标明采集或者播放(SND_PCM_STREAM_CAPTURE, SND_PCM_STREAM_PLAYBACK)
  7. @param mode: 打开模式(see SND_PCM_NONBLOCK, SND_PCM_ASYNC)
  8. @return: 0表示成功,小于0表示错误
  9. */
  10. int snd_pcm_open( snd_pcm_t **handle, const char* name, int stream, int mode );
  11. /***
  12. 读取音频帧
  13. @param handle: PCM handle
  14. @param buffer: frames containing buffer
  15. @param size: frames to be read
  16. @return: 实际读取的音频帧个数,小于0表示错误
  17. */
  18. ssize_t snd_pcm_readi( snd_pcm_t *handle, void *buffer, size_t size );
  19. /***
  20. 关闭
  21. @param handle: PCM handle
  22. @return: 实际读取的音频帧个数,小于0表示错误
  23. */
  24. int snd_pcm_close( snd_pcm_t *handle );
  25. /***
  26. 准备使用PCM
  27. @param handle: PCM handle
  28. @return: 实际读取的音频帧个数,小于0表示错误
  29. */
  30. int snd_pcm_prepare( snd_pcm_t *handle );

接口简单,参数也少,所以使用起来很方便,基本上是linux下采集和播放的第一选择,下面写个简单的例子演示下如何调用

  1. 打开音频设备并设置参数
  1. SIMPLE_LOG("try open %s\n", device_name_.c_str());
  2. int ret = snd_pcm_open(&alsa_pcm_, device_name_.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
  3. if (!alsa_pcm_ || ret < 0)
  4. {
  5. SIMPLE_LOG("open %s failed, ret: %d\n", device_name_.c_str(), ret);
  6. return false;
  7. }
  8. snd_pcm_hw_params_t* params;
  9. snd_pcm_hw_params_alloca (&params);
  10. snd_pcm_hw_params_any (alsa_pcm_, params);
  11. snd_pcm_hw_params_set_access (alsa_pcm_, params,
  12. SND_PCM_ACCESS_RW_INTERLEAVED);
  13. snd_pcm_format_t format;
  14. switch (bits_per_sam_) {
  15. case 8:
  16. format = SND_PCM_FORMAT_S8;
  17. break;
  18. case 16:
  19. format = SND_PCM_FORMAT_S16_LE;
  20. break;
  21. case 24:
  22. format = SND_PCM_FORMAT_S24_LE;
  23. break;
  24. case 32:
  25. format = SND_PCM_FORMAT_S32_LE;
  26. break;
  27. default:
  28. format = SND_PCM_FORMAT_S16_LE;
  29. break;
  30. }
  31. snd_pcm_hw_params_set_format (alsa_pcm_, params, format);
  32. snd_pcm_hw_params_set_channels (alsa_pcm_, params, channel_count_);
  33. unsigned int rate = sample_rate_;
  34. snd_pcm_hw_params_set_rate_near (alsa_pcm_, params, &rate, NULL);
  35. sample_size_ = channel_count_ * (bits_per_sam_/8);
  36. /* Activate the parameters */
  37. ret = snd_pcm_hw_params (alsa_pcm_, params);
  38. if (ret < 0)
  39. {
  40. SIMPLE_LOG("set param failed, ret: %d\n", ret);
  41. snd_pcm_close (alsa_pcm_);
  42. alsa_pcm_ = NULL;
  43. return false;
  44. }
  1. 读取音频数据
  1. bool AlsaCapture::ReadData()
  2. {
  3. int read_size = 0;
  4. snd_pcm_uframes_t need_frames = real_sample_count_;
  5. for (;;)
  6. {
  7. if (read_size >= pcm_buf_.size())
  8. {
  9. break;
  10. }
  11. int ret = 0;
  12. while (true)
  13. {
  14. char* read_buf = &pcm_buf_[0] + read_size;
  15. ret = snd_pcm_readi(alsa_pcm_, read_buf, need_frames);
  16. if (ret >= 0)
  17. {
  18. break;
  19. }
  20. if (ret == -EAGAIN)
  21. {
  22. SIMPLE_LOG("snd_pcm_readi EAGAIN\n");
  23. return false;
  24. }
  25. if (AlsaXRunRecover(alsa_pcm_, ret) < 0)
  26. {
  27. SIMPLE_LOG("ALSA read error: %s\n", snd_strerror(ret));
  28. return false;
  29. }
  30. }
  31. read_size += ret * sample_size_;
  32. need_frames -= ret;
  33. }
  34. return true;
  35. }

这样就可以完成音频数据的采集,需要注意的是,在第二步读取数据之前,需要先调用snd_pcm_prepare,否则是无法驱动数据采集正常进行的。

在国产化芯片平台上出现的问题

在普通Linux下,这样写下来,就可以实现想要的音频采集功能了,后面对数据做进一步的规整和编码就可以发送了。但是在某个国产芯片平台的银河麒麟系统下,我却遇到了一个问题,那就是打开设备的函数调用以及所有的参数设置都是成功的,但是数据采集却总是异常,要么返回无意义噪声数据,要么read接口干脆就报EAGAIN错误。

刚开始我以为是ALSA默认设备的问题,因为出问题的国产化芯片平台,有两个声卡,其中一个是可以正常使用的,另一个是无效声卡。这些信息可以通过使用命令行来查看,例如:

查看声卡:

cat /proc/asound/cards

查看采集设备:

sudo arecord -l

查看播放设备:

cat aplay -l

于是我尝试通过系统配置的方式,来设置默认声卡,这里推荐一个工具“alsamixer”,是一个字符化界面的ALSA配置工具,可以通过如下命令安装:

sudo apt-get install alsa-utils

启动后就是一个这样的界面

image

然而,修改以后,发现默认设备的修改,并不能影响到alsa采集的结果。于是通过罗列所有录音设备,并且指定设备名称,但是仍然出现同样的结果。在多次尝试无果以后,最终只能放弃使用ALSA来进行音频设备的数据采集,而采用复杂一些的PulseAudio框架。最后的结果也证明,更加上层的PulseAudio还是正确的处理了有效的音频设备和无效音频设备,并正确返回了麦克风/桌面系统声音。具体过程我下一篇再写。

image

合作请加QQ或微信hbstream。(转载请注明作者和出处)


原文链接:https://www.cnblogs.com/haibindev/p/17077965.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号