AlsaLib的基本使用,不涉及到Alsa驱动
基于AlsaLib 捕获声音
capture.c 捕获声音
捕获声音格式如下:
SND_PCM_ACCESS_RW_INTERLEAVED
SND_PCM_FORMAT_S16_LE
44100 Hz
2 chanels
buffer_frames:128 frames
period_frames:32 frames
捕获时长5s
基本流程如下:
- 按照要求设置硬件参数(访问数据方式,数据格式,channels数,采样率)
- 配置捕获buffer_size(驱动能够容纳的总帧数)
period_frames (驱动每 period_frames 产生一个中断通知应用层有period_frames已经准备好了) - 循环捕获数据直到退出
1. 硬件参数的设置
/* Set the desired hardware parameters. */
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
/* Two channels (stereo) */
snd_pcm_hw_params_set_channels(handle, params, 2);
/* 44100 bits/second sampling rate (CD quality) */
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
2.buffer及period设置
/* Set buffer size to 128 frames. */
buffer_frames = 128;
snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_frames);
/* Set period size to buffer_frames/4 frames. */
period_frames = buffer_frames / 4;
snd_pcm_hw_params_set_period_size_near(handle, params, &period_frames, &dir);
3. 数据捕获
循环捕获数据直到退出
/*Read data from codec and write to one file*/
/*每次读period_frames帧数据到buffer中,直到退出*/
while (loops > 0) {
rc = snd_pcm_readi(handle, buffer, period_frames);
if (rc == -EPIPE) {
/* EPIPE means overrun */
fprintf(stderr, "overrun occurred/n");
snd_pcm_prepare(handle); //overrun occurred 驱动没用新数据可以读取了,让驱动继续准备好新数据
}
else if (rc < 0) {
fprintf(stderr,
"error from read: %s\n",
snd_strerror(rc));
}
else if (rc != (int)period_frames) {
fprintf(stderr, "short read, read %d frames\n", rc);
}
rc = fwrite(buffer, 1, buff_size, fp); //将读取到的数据保存到文件中
if (rc != buff_size) {
fprintf(stderr, "short write: wrote %d bytes/n", rc);
}
loops--;
++i;
}
基于AlsaLib 播放声音(普通模式)
playback.c 播放声音(普通模式)
主要流程如下:
- 按照要求设置硬件参数(访问数据方式,数据格式,channels数,采样率)
- 配置捕获buffer_size(驱动能够容纳的总帧数)
period_frames (驱动每 period_frames 产生一个中断通知应用层有period_frames已经准备好了) - 从文件读取数据,送到codec,直到读取数据结束
1,2设置参数的过程同capture类似,不同的是打开pcm设备的方式
playback时候的打开方式:
snd_pcm_open(&playback_handle, snd_card_name, SND_PCM_STREAM_PLAYBACK, 0);
capture时候的打开方式:
snd_pcm_open(&handle, snd_card_name, SND_PCM_STREAM_CAPTURE, 0);
步骤3如下:
/*Read data from file and send to codec*/
while ((rc = fread(period_buff, FRAME_SIZE, period_frames, fp)) != 0) {
send_frames = rc;
rc = snd_pcm_writei(handle, period_buff, send_frames);
if (rc == -EPIPE) {
/* EPIPE means overrun */
fprintf(stderr, "underrun occurred\n");
snd_pcm_prepare(handle);
} else if (rc < 0) {
fprintf(stderr,
"error from write: %s\n",
snd_strerror(rc));
}
else if (rc != (int)send_frames) {
fprintf(stderr, "short write, read %d frames\n", rc);
}
}
基于AlsaLib 播放声音(中断模式)
playback_interrupt.c 播放声音(中断方式)
主要流程如下:
- 按照要求设置硬件参数(访问数据方式,数据格式,channels数,采样率)
- 配置捕获buffer_size(驱动能够容纳的总帧数)
period_frames (驱动每 period_frames 产生一个中断通知应用层有period_frames已经准备好了) - 设置sw参数,配置产生中断的参数
- 从文件读取数据直到读取数据结束
1,2的设置同capture.c中的设置
3 sw设置如下:
当buffer有PERIOD_SIZE 帧数据可用时产生一个中断:
snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, PERIOD_SIZE)
当buufer有数据时,就开始frame的计数,到了PERIOD_SIZE就产生一个中断
snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U) //0为开始计数帧的值
这个例子中的sw的参数的作用就是用于start_frame的计数值及产生中断的frames的周期间隔
4 基于中断方式的播放流程如下
/*Read data from codec and write to one file*/
while ((rc = fread(period_buff, FRAME_SIZE, period_frames, fp)) != 0) {
send_frames = rc;
/* wait till the interface is ready for data, or 1 second
has elapsed.
等到有需要的数据或者超时都会退出来
*/
if ((rc = snd_pcm_wait (playback_handle, 1000)) < 0) {
fprintf (stderr, "poll failed (%s)\n", strerror (errno));
break;
}
/* find out how much space is available for playback data
看下有多少帧数据准备好了
*/
if ((frames_to_deliver = snd_pcm_avail_update (playback_handle)) < 0) {
if (frames_to_deliver == -EPIPE) {
fprintf (stderr, "an xrun occured\n");
break;
} else {
fprintf (stderr, "unknown ALSA avail update return value (%d)\n",
(int)frames_to_deliver);
break;
}
}
/*
决定最终发送多少frames数据到codec,发送的实际帧数为
frames_to_deliver ,PERIOD_SIZE, send_frames(period_buff对应帧数) 最小值
*/
frames_to_deliver = frames_to_deliver > PERIOD_SIZE ? PERIOD_SIZE : frames_to_deliver;
frames_to_deliver = frames_to_deliver > send_frames ? send_frames : frames_to_deliver;
/* deliver the data
数据送到解码器播放
*/
if (playback_callback ((short *)period_buff, frames_to_deliver) != frames_to_deliver) {
fprintf (stderr, "playback callback failed\n");
break;
}
++i;
}
基于AlsaMixer 控制播放声音的大小
通过mixer 接口设置不同mixer elem的1种方式是:
通过SimpleConrtl 的名称获取mixer_elem,然后通过mixer_elem设置对应elem的值
通过mixer控制播放声音的主要代码如下:
int set_playbak_volume(long volume)
{
long min, max;
snd_mixer_t *handle;
snd_mixer_selem_id_t *sid;
const char *card = "default"; //sound card名称
const char *selem_name = "amba-playback"; //控制Playback声音的SimpleControl 对应的elem名称 不同平台这个名字可能不一样
/*
下面流程对于设置不同的参数基本流程一致
*/
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, card);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, selem_name); //mixer_elem 同一个sid关联起来
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid); //通过mixer_elem关联的sid查找到对应的mixer_elem
/*
获取声音的min,max,因为函数输入参数volume为(0-100)要映射到(min,max)
*/
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);
snd_mixer_close(handle);
}
问题:如何确定使用平台的selem_name 对应名字?
以控制Playback音量的例子为例说明下:
执行如下命令, 获取所有SimpleControl的内容
amixer scontents
Simple mixer control 'amba-playback',0
Capabilities: volume
Playback channels: Front Left - Front Right
Capture channels: Front Left - Front Right
Limits: 0 - 255
Front Left: 127 [50%]
Front Right: 127 [50%]
找到类似输出的内容, amba-playback对应字段就是要找的selem_name。Limits对应参数的范围。要通过mixer设置其它参数方法类似
代码的编译及测试
代码使用的Makefile这里简单的讲解下(这个仅针对初学者,大佬可以忽略这个Makefile解析章节)
Makefile 很简单就几行而已。
srcs = $(wildcard *.c) #获取到当前目录所有.c文件列表
targets = $(srcs:%.c=%) #每个.c 对应的前缀部分部分作为最终targets
CC = /usr/local/linaro-aarch64-2018.08-gcc8.2/bin/aarch64-linux-gnu-gcc #编译器绝路径
INC_PATH += -I /cvdump/lwua/SDK/cv_master_local/ambarella/prebuild/oss/armv8-a/alsa-lib/include #改成自己alsalib 头文件目录
CFLAGS += -Wall
LD_FLAGS += -L /cvdump/lwua/SDK/cv_master_local/ambarella/prebuild/oss/armv8-a/alsa-lib/usr/lib \ #改成自己的AlasLib路径
-lasound
%:%.c #target中每个元素,依赖对应.c文件对应,例如a <->a.c, b<->b.c
$(CC) $(CFLAGS) $(LD_FLAGS) $(INC_PATH) $< -o $@
all:$(targets)
.PHONY:clean
clean:
rm $(targets)
这种格式Makefile的好处是每次文件夹新建一个xxx.c文件,xxx.c 对应的target xxx会自动编译,不用每次都手动修改Makefile
依次再命令行执行如下测试命令,可以测试所有的例子。
# ./capture default test.raw
[86360.321414] simple-card: Ambarella fix DAI clock at 12288000.
----->System configure info
Alsa lib version 1.2.4 //使用的AlsaLib代码版本号
channels: 2
rate: 44100 bps
period time: 725 us
period size: 32 frames
buffer time: 2902 us
buffer size: 128 frames
----->Alreay capture 100% //显示进度
Capure finished, capture 220672 frames, save data in test.raw
# ./test_amixer_playback_vol 50
set_playbak_volume vol_val:50 ok //音量设置成功打印ok
# ./playback_interrupt default test.raw
[86382.135874] simple-card: Ambarella fix DAI clock at 12288000.
----->System configure info
Alsa lib version 1.2.4
----->hw_params:
channels: 2
rate: 44100 bps
period time: 2902 us
period size: 128 frames
buffer time: 11609 us
buffer size: 512 frames
----->Alreay Playback 100%
Playback finished, playback 220672 frames, data from test.raw
# ./playback default test.raw
[86392.744021] simple-card: Ambarella fix DAI clock at 12288000.
----->System configure info
Alsa lib version 1.2.4
channels: 2
rate: 44100 bps
period time: 725 us
period size: 32 frames
buffer time: 2902 us
buffer size: 128 frames
----->Alreay Playback 100%
Playback finished, playback 220672 frames, data from test.raw
#
源代码要就拿去用用,不需要就直接略过^_ ^
https://gitee.com/wllw7176/open-source-third-part/tree/master/test_alsa_src