嵌入式流媒体开发Linux ALSA 声卡数据采集与播放

Posted 与光同程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式流媒体开发Linux ALSA 声卡数据采集与播放相关的知识,希望对你有一定的参考价值。

文章目录

ALSA框架

ALSA 是 Advanced Linux Sound Architecture 的缩写,即高级 Linux声音架构,在 Linux 操作系统上提供了对音频和 MIDI(Musical InstrumentDigital Interface,音乐设备数字化接口)的支持。在 Linux2.6 版本内核以后,ALSA 已经成为默认的声音子系统,用来替换 2.4 版本内核中的OSS(Open Sound System,开放声音系统)。
ALSA 是一个完全开放源码的音频驱动程序集,是由志愿者维护的开源项目,而 OSS 则是由公司提供的商业产品。ALSA 系统包括驱动包alsa-driver(集成在内核源码),开发包 alsa-libs,开发包插件 alsalibplugins,设置管理工具包 alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包 alsa-firmware,OSS 接口兼容模拟层工具 alsa-oss 共 7 个子项目,其中只有 alsa-driver 是必须的。除了 alsa-driver,ALSA 包含在用户空间的 alsa-lib 函数库,具有更加友好的编程接口,并且完全兼容于 OSS,开发者可以通过这些高级 API 使用驱动,不必直接与内核驱动 API 进行交互。
ALSA 主要有如下特点:
1)支持多种声卡设备、
2)模块化的内核驱动程序 、
3)支持 SMP(对称多处理)和多线程、
4)提供应用开发函数库 、
5)兼容 OSS 应用程序 。

ALSA 在 Linux 系统中可以主要分两部分,在Kernel空间的设备驱动层,ALSA 提供了 ALSA-driver,它是整个 ALSA 框架的核心部分;同时在Linux User空间,ALSA 提供了alsa-lib,对 ALSA-driver的系统调用API进行封装,应用程序只要调用 alsa-lib 提供的 API,即可以完成对底层音频硬件的控制。

环境搭建

对于Linux下ALSA采集必须先配置内核支持ALSA,然后移植ALSA-LIB

ALSA 交叉编译移植

下载源码 alsa-lib alsa-util

alsa-lib

CC=arm-cortex_a9-linux-gnueabi-gcc ./configure --host=arm-linux –
prefix=/home/swann/SDK/EXYNOS6818/SDK/alsa-lib/
make
makeinstall

alsa-util

CC=arm-cortex_a9-linux-gnueabi-gcc ./configure --prefix=/home/swann/SDK/EXYNOS6818/SDK/alsa-util/ --host=arm-linux --with-alsa-incprefix=/home/swann/SDK/EXYNOS6818/SDK/alsa-lib/include --with-alsaprefix=/home/swann/SDK/EXYNOS6818/SDK/alsa-lib/lib --disable-alsamixer --disablexmlto --disable-nls
make
make install
将两个库考到板子
添加配置文件

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/alsa-lib/lib
export ALSA_CONFIG_PATH=/usr/lib/alsa-lib/share/alsa/alsa.conf
export ALSA_CONFIG_DIR=/usr/lib/alsa-lib/share/alsa

配置USB声卡

查看声卡设备并测试

查看音卡:

arecord -l

**** List of CAPTURE Hardware Devices ****
card 0: xxsndcard [xx-snd-card], device 1: TDM_Capture (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: xxsndcard [xx-snd-card], device 2: DMIC_Capture (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: xxsndcard [xx-snd-card], device 3: AWB_Record (*) []
  Subdevices: 1/1
  Subdevice #0: subdevice #0 

通过如上命令可以得到可用于录音的设备,比如card x device x

录音测试

根据上面的信息,如果我们要用TDM_Capture 录音,可以输入如下命令

arecord -Dhw:0,1 -d 10 -f cd -r 44100 -c 2 -t wav test.wav

参数解析
-D 指定了录音设备,0,1 是card 0 device 1的意思,也就是TDM_Capture
-d 指定录音的时长,单位时秒
-f 指定录音格式,通过上面的信息知道只支持 cd cdr dat
-r 指定了采样率,单位时Hz
-c 指定channel 个数
-t 指定生成的文件格式

播放测试

播放WAV
aplay test.wav

播放原始数据
aplay -t raw -r 44100 -f S16_LE -c 2 decoder.raw

音频采集常见参数

首先要理解一些音频处理的信息:采样率/位深度/通道数.

采样率:
以秒为单位,每秒采集多少声音数据的频率.

位深度:
上面我们说的采样率,每次会采集一次声音数据,这一次的声音数据的大小,既然是位深度,那么单位肯定是位了.

通道数:
和硬件参数有关,采集声音源的设备有几个.

OK,举个例子:

采样率48000,位深度 16bit ,通道数2

知道这三个参数,那么基本我们就知道了

设备1秒内可以采集到多少音频数据是:

48000 * 16 * 2 = 1536000 位

48000 * 16 * 2 / 8 = 192000 字节.

也就是我的设备在一秒内可以采集192000

接下来音频的帧率,怎么理解呢,每秒内采集48000次,这个是总的采集次数,也许我们要分为100次,每次也就采集4800,或者分为50次,每次采集9600,。

这个就要看具体的硬件呢,所以硬件肯定会开放一个接口的.

让你获取一个minbufsize,意思就是这个,每一次提取多少字节.

WAV文件头


//ckid:4字节 RIFF 标志,大写  

    wavHeader[0]  = 'R';  
    wavHeader[1]  = 'I';  
    wavHeader[2]  = 'F';  
    wavHeader[3]  = 'F';  

//cksize:4字节文件长度,这个长度不包括"RIFF"标志(4字节)和文件长度本身所占字节(4字节),即该长度等于整个文件长度 - 8  
    wavHeader[4]  = (byte)(totalDataLen & 0xff);  
    wavHeader[5]  = (byte)((totalDataLen >> 8) & 0xff);  
    wavHeader[6]  = (byte)((totalDataLen >> 16) & 0xff);  
    wavHeader[7]  = (byte)((totalDataLen >> 24) & 0xff);  

    //fcc type:4字节 "WAVE" 类型块标识, 大写  
    wavHeader[8]  = 'W';  
    wavHeader[9]  = 'A';  
    wavHeader[10] = 'V';  
    wavHeader[11] = 'E';  
//ckid:4字节 表示"fmt" chunk的开始,此块中包括文件内部格式信息,小写, 最后一个字符是空格  
    wavHeader[12] = 'f';  
    wavHeader[13] = 'm';  
    wavHeader[14] = 't';  
    wavHeader[15] = ' ';  

//cksize:4字节,文件内部格式信息数据的大小,过滤字节(一般为00000010H)  
    wavHeader[16] = 0x10;  
    wavHeader[17] = 0;  
    wavHeader[18] = 0;  
    wavHeader[19] = 0;  
//FormatTag:2字节,音频数据的编码方式,1:表示是PCM 编码  
    wavHeader[20] = 1;  
    wavHeader[21] = 0;  
//Channels:2字节,声道数,单声道为1,双声道为2  
    wavHeader[22] = (byte) channels;  
    wavHeader[23] = 0;  
//SamplesPerSec:4字节,采样率,如44100  
    wavHeader[24] = (byte)(sampleRate & 0xff);  
    wavHeader[25] = (byte)((sampleRate >> 8) & 0xff);  
    wavHeader[26] = (byte)((sampleRate >> 16) & 0xff);  
    wavHeader[27] = (byte)((sampleRate >> 24) & 0xff);  
//BytesPerSec:4字节,音频数据传送速率, 单位是字节。其值为采样率×每次采样大小。播放软件利用此值可以估计缓冲区的大小;  
//bytePerSecond = sampleRate * (bitsPerSample / 8) * channels  
    wavHeader[28] = (byte)(bytePerSecond & 0xff);  
    wavHeader[29] = (byte)((bytePerSecond >> 8) & 0xff);  
    wavHeader[30] = (byte)((bytePerSecond >> 16) & 0xff);  
    wavHeader[31] = (byte)((bytePerSecond >> 24) & 0xff);  
//BlockAlign:2字节,每次采样的大小 = 采样精度*声道数/8(单位是字节); //这也是字节对齐的最小单位, 譬如 16bit 立体声在这里的值是 4 字节。  
//播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整  
    wavHeader[32] = (byte)(bitsPerSample * channels / 8);  
    wavHeader[33] = 0;  
//BitsPerSample:2字节,每个声道的采样精度; 譬如 16bit 在这里的值就是16。如果有多个声道,则每个声道的采样精度大小都一样的;  
    wavHeader[34] = (byte) bitsPerSample;  
    wavHeader[35] = 0;  
//ckid:4字节,数据标志符(data),表示 "data" chunk的开始。此块中包含音频数据,小写;  
    wavHeader[36] = 'd';  
    wavHeader[37] = 'a';  
    wavHeader[38] = 't';  
    wavHeader[39] = 'a';  
//cksize:音频数据的长度,4字节,audioDataLen = totalDataLen - 36 = fileLenIncludeHeader - 44  
    wavHeader[40] = (byte)(audioDataLen & 0xff);  
    wavHeader[41] = (byte)((audioDataLen >> 8) & 0xff);  
    wavHeader[42] = (byte)((audioDataLen >> 16) & 0xff);  
    wavHeader[43] = (byte)((audioDataLen >> 24) & 0xff);  

编程实现录音的播放

audio.cpp

#include "audio.h"

void audio::audio_write_frame(unsigned char* data)

	int ret;
	if(audio_type!=AUDIO_SPEAKER)
		fprintf(stderr,"IT'S NOT A SPEAKER \\r\\n");
		return;
	
	buffer_out=data;
	ret = snd_pcm_writei(capture_handle, buffer_out, frame_size);

    if (ret == -EPIPE) 
      /* EPIPE means underrun */
      fprintf(stderr, "underrun occurred\\n");
      snd_pcm_prepare(capture_handle);
     else if (ret < 0) 
      fprintf(stderr,"error from writei: %s\\n",snd_strerror(ret));
    



void audio::audio_close_speaker(void)

	if(audio_type!=AUDIO_SPEAKER)
		fprintf(stderr,"IT'S NOT A SPEAKER \\r\\n");
		return;
	
	snd_pcm_drain(capture_handle);
  	snd_pcm_close(capture_handle);	


void audio::audio_open_speaker(void)

	if(audio_type!=AUDIO_SPEAKER)
		fprintf(stderr,"IT'S NOT A SPEAKER \\r\\n");
		return;
	
	int ret,dir;
	/* Open PCM device for playback. */
	ret = snd_pcm_open(&capture_handle, audio_path.c_str(),SND_PCM_STREAM_PLAYBACK, 0);
	if (ret < 0) 
		fprintf(stderr,"unable to open pcm device: %s\\n",snd_strerror(ret));
		exit(1);
	
	/* Allocate a hardware parameters object. */
	snd_pcm_hw_params_alloca(&hw_params);
	/* Fill it in with default values. */
	snd_pcm_hw_params_any(capture_handle, hw_params);
	/* Set the desired hardware parameters. */
	/* Interleaved mode */
	snd_pcm_hw_params_set_access(capture_handle, hw_params,SND_PCM_ACCESS_RW_INTERLEAVED);
	/* Signed 16-bit little-endian format */
	snd_pcm_hw_params_set_format(capture_handle, hw_params,format);
	/* Two channels (stereo) */
	snd_pcm_hw_params_set_channels(capture_handle, hw_params, channel);
	snd_pcm_hw_params_set_rate_near(capture_handle, hw_params,&sample_rate, &dir);
	/* Set period size to 32 frames. */
	ret = snd_pcm_hw_params(capture_handle, hw_params);
	if (ret < 0) 
		fprintf(stderr,"unable to set hw parameters: %s\\n",snd_strerror(ret));
		exit(1);
		


/**
 * @description: 关闭麦克风
 * @param *
 * @return *
 * @author: YURI
 */
void audio::audio_close_micophone(void)
	if(audio_type!=AUDIO_MICPHONE)
		fprintf(stderr,"IT'S NOT A MICPHONE \\r\\n");
		return;
	
	// 释放数据缓冲区
	free(buffer_in);
	fprintf(stdout, "buffer_in freed\\n");
	// 关闭音频采集卡硬件
	snd_pcm_close (capture_handle);
	fprintf(stdout, "audio interface closed\\n");

/**
 * @description: 打开麦克风 并设置参数
 * @param *
 * @return *
 * @author: YURI
 */
void audio::audio_open_micophone(void)
	if(audio_type!=AUDIO_MICPHONE)
		fprintf(stderr,"IT'S NOT A MICPHONE \\r\\n");
		return;
	
	int err;
	// 打开音频采集卡硬件,并判断硬件是否打开成功,若打开失败则打印出错误提示
	if ((err = snd_pcm_open (&capture_handle, audio_path.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) 
	
		fprintf (stderr, "cannot open audio device %s (%s)\\n",  audio_path.c_str(), snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "audio interface opened\\n");

	// 分配一个硬件变量对象,并判断是否分配成功
	if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) 
	
		fprintf (stderr, "cannot allocate hardware parameter structure (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params allocated\\n");
	
	// 按照默认设置对硬件对象进行设置,并判断是否设置成功
	if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) 
	
		fprintf (stderr, "cannot initialize hardware parameter structure (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params initialized\\n");
	/*
		设置数据为交叉模式,并判断是否设置成功
		interleaved/non interleaved:交叉/非交叉模式。
		表示在多声道数据传输的过程中是采样交叉的模式还是非交叉的模式。
		对多声道数据,如果采样交叉模式,使用一块buffer_in即可,其中各声道的数据交叉传输;
		如果使用非交叉模式,需要为各声道分别分配一个buffer_in,各声道数据分别传输。
	*/
	if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) 
	
		fprintf (stderr, "cannot set access type (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params access setted\\n");

	// 设置数据编码格式为PCM、有符号、16bit、LE格式,并判断是否设置成功
	if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, format)) < 0) 
	
		fprintf (stderr, "cannot set sample format (%s)\\n",  snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params format setted\\n");

	// 设置采样频率,并判断是否设置成功
	if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, &sample_rate, 0)) < 0) 
	
		fprintf (stderr, "cannot set sample rate (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params rate setted\\n");

	//  设置为双声道,并判断是否设置成功
	if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, 2)) < 0) 
	
		fprintf (stderr, "cannot set channel count (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params channels setted\\n");
	// 将配置写入驱动程序中,并判断是否配置成功
	if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) 
	
		fprintf (stderr, "cannot set parameters (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "hw_params setted\\n");
	// 使采集卡处于空闲状态
	snd_pcm_hw_params_free (hw_params);
	fprintf(stdout, "hw_params freed\\n");
	// 准备音频接口,并判断是否准备好
	if ((err = snd_pcm_prepare (capture_handle)) < 0) 
	
		fprintf (stderr, "cannot prepare audio interface for use (%s)\\n", snd_strerror (err));
		exit (1);
	
	fprintf(stdout, "audio interface prepared\\n");
	// 配置一个数据缓冲区用来缓冲数据
	buffer_in = (unsigned char*)malloc(frame_size*snd_pcm_format_width(format) / 8 * channel);
	fprintf(stdout, "buffer_in allocated\\n");

/**
 * @description: 读一帧数据出来
 * @param *
 * @return *
 * @author: YURI
 */
unsigned char *audio::audio_read_frame(void)

	if(audio_type!=AUDIO_MICPHONE)
		fprintf(stderr,"IT'S NOT A MICPHONE \\r\\n");
		return NULL;
	
	int err;
	// 读取
	if ((err = snd_pcm_readi (capture_handle, buffer_in, frame_size)) != frame_size) 
	
		fprintf (stderr, "read from audio interface failed (%s)\\n", err, snd_strerror (err));
		exit (1);
	
	return buffer_in;

/**
 * @description: 分离声道数据
 * @param unsigned char ** left
 * @return *
 * @author: YURI
 */
void audio::audio_channel_split(unsigned char ** data)

	if(audio_type!=AUDIO_MICPHONE)
		fprintf(stderr,"IT'S NOT A MICPHONE \\r\\n");
		return;
	
    for(int i=0;i<channel;i++)
        for(int j=0

以上是关于嵌入式流媒体开发Linux ALSA 声卡数据采集与播放的主要内容,如果未能解决你的问题,请参考以下文章

韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡)

韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之参数设置(基于优龙FS2410开发板,UDA1341声卡)

Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)

Linux ALSA声卡驱动之一:ALSA架构简介

Linux ALSA声卡驱动之二:声卡的创建

在 Linux 中驱动声卡