在linux系统中,有的系统没有dsp 设备文件,所以只能使用alsa提供的接口来读取声音设备。alsa 又许多的声卡和声卡驱动组成,它可以直接读取声卡采集到的数据。如同摄像头数据一般,采集到的原始数据都是都是非常大的,不利于我们正常存储和使用,所以出现了很多的压缩方法。本文采用的是国内使用最广的mp3压缩方式。先上代码:
/*=============================================================================
# FileName: pcm_encoder_mp3.c
# Desc: use lame encode pcm data to mp3 format, the pcm data
# read from alsa
# Author: licaibiao
# LastChange: 2017-03-27
=============================================================================*/
#define ALSA_PCM_NEW_HW_PARAMS_API
#include
#include
#include
#include
#define INBUFSIZE 128
#define MP3BUFSIZE (int) (1.25 * INBUFSIZE) + 7200
lame_global_flags *gfp;
short *input_buffer;
char *mp3_buffer;
char *outPath = "out.mp3";
FILE *infp;
FILE *outfp;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
snd_pcm_t *handle;
int size;
short *alsa_buffer;
void lame_init_set(void)
{
int ret_code;
gfp = lame_init();
if (gfp == NULL)
{
printf("lame_init failed/n");
}
ret_code = lame_init_params(gfp);
if (ret_code < 0)
{
printf("lame_init_params returned %d/n",ret_code);
}
outfp = fopen(outPath, "wb");
}
void lame_alloc_buffer(void)
{
input_buffer = (short*)malloc(INBUFSIZE*2);
mp3_buffer = (char*)malloc(MP3BUFSIZE);
}
void lame_release(void)
{
free(mp3_buffer);
free(input_buffer);
fclose(outfp);
lame_close(gfp);
}
void alsa_init(void){
unsigned int val;
int dir;
int ret;
ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
if (ret < 0) {
fprintf(stderr, "unable to open pcm device: %s
", snd_strerror(ret));
exit(1);
}
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, params, 2);
val = 44100;
snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
frames = 32;
snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
ret = snd_pcm_hw_params(handle, params);
if (ret < 0) {
fprintf(stderr,"unable to set hw parameters: %s
", snd_strerror(ret));
exit(1);
}
}
void alsa_alloc_buffer(void){
int dir;
snd_pcm_hw_params_get_period_size(params, &frames, &dir);
size = frames * 4;
alsa_buffer = (short *) malloc(size);
}
void alsa_release(void){
snd_pcm_drain(handle);
snd_pcm_close(handle);
free(alsa_buffer);
}
void lame_encode(void)
{
int input_samples;
int mp3_bytes;
int status = 0;
int ret = 0;
int loop = 10000;
while(loop--){
ret = snd_pcm_readi(handle, alsa_buffer, frames);
if (ret == -EPIPE){
fprintf(stderr, "overrun occurred
");
snd_pcm_prepare(handle);
}else if(ret == -EBADFD){
printf("PCM is not in the right state
");
}
else if(ret == -ESTRPIPE){
printf("stream is suspended and waiting for an application recovery
");
}
else if (ret < 0){
fprintf(stderr, "error from read: %s
",snd_strerror(ret));
}
else if (ret != (int)frames){
fprintf(stderr, "short read, read %d frames
", ret);
}
else if (ret == 0){
printf(" pcm read 0 frame deta
");
}
memcpy(input_buffer, alsa_buffer, size);
mp3_bytes = lame_encode_buffer_interleaved(gfp, input_buffer, size/4, mp3_buffer, MP3BUFSIZE);
if (mp3_bytes < 0)
{
printf("lame_encode_buffer_interleaved returned %d
", mp3_bytes);
}
else if(mp3_bytes > 0)
{
fwrite(mp3_buffer, 1, mp3_bytes, outfp);
}
}
mp3_bytes = lame_encode_flush(gfp, mp3_buffer, sizeof(mp3_buffer));
if (mp3_bytes > 0)
{
printf("writing %d mp3 bytes
", mp3_bytes);
fwrite(mp3_buffer, 1, mp3_bytes, outfp);
}
}
int main(int argc, char** argv)
{
lame_init_set();
lame_alloc_buffer();
alsa_init();
alsa_alloc_buffer();
lame_encode();
lame_release();
alsa_release();
}
程序是通过alsa采集pcm接口的声音原始数据,然后使用LAME 编码库把pcm 的原始数据编码成MP3文件。Makefile 文件:
APP = test
LINK_LIBS = -lmp3lame -lasound
LaINK_OPTS = -lm
LINK_OBJ = pcm_encoder_mp3.o
C = c
OBJ = o
C_FLAGS = $(CPPFLAGS) $(CFLAGS)
.$(C).$(OBJ):
cc -c -g $(C_FLAGS) $<
$(APP): $(LINK_OBJ)
gcc -o $@ $(LINK_OBJ) $(LINK_LIBS) $(LINK_OPTS)
clean:
rm -rf *o $(APP) *mp3
在Makefile文件中需要注意,需要链接alsa和LAME的相应的库-lmp3lame -lasound 。 上面的程序在运行之前,需要确定已经正确安装了ALSA库和LAME库。可以参考
linux 音频子系统学习及软件安装 编译运行上面的代码生成out.mp3文件,可以通过GoldWave.exe 工具查看音频情况。
调试注意事项: 1.使用Audacity 或是GoldWave工具查看编码后的数据,如果发现边长或是缩短了一倍或是几倍,那么需要查看mp3_bytes = lame_encode_buffer_interleaved(gfp, input_buffer, size/4, mp3_buffer, MP3BUFSIZE); 中的第三个参数是否设置正确。 2.LAME 编码库的输入buffer 不能直接使用ALSA 的输出buffer。在我的测试中发现会出现异常,所以我独立申请了buffer,采集到数据后再拷贝一次。
遗留问题: 在使用ALSA采集数据的时候,snd_pcm_readi 读取数据的时候会出现采集不到数据的问题。它采集不到数据返回值并没有返回异常,而是返回0。出现该问题需要重新打开设备再读取数据。至于这个问题是由何引起,具体还没有定位。这里做个记录,以后有时间再定位。
这里再提供一个ALSA采集数据和LAME 编码的一个应用实例。可以在这里下载:
alsa数据编码mp3格式