Linux教程

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

本文主要是介绍【嵌入式流媒体开发】Linux ALSA 声卡数据采集与播放,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • ALSA框架
  • 环境搭建
    • ALSA 交叉编译移植
    • alsa-lib
    • alsa-util
    • 配置USB声卡
    • 查看声卡设备并测试
      • 查看音卡:
      • 录音测试
      • 播放测试
  • 音频采集常见参数
    • WAV文件头
  • 编程实现录音的播放
    • audio.cpp
    • audio.h
    • audio_wav.cpp
    • audio_wav.h
  • 播放录音测试代码
    • 运行方式
    • audio_record
    • audio_play

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;j<frame_size;j++){
            data[i][j*2]=buffer_in[j*4+2*i];
            data[i][j*2+1]=buffer_in[j*4+2*i+1];
        }
    }
}
/**
 * @description: audio 麦克风 初始化
 * @param {string} audio_tag
 * @param {int} channel
 * @param {int} sample_rate
 * @param {snd_pcm_format_t} pcm_format
 * @return {*}
 * @author: YURI
 */
audio::audio(AUDIO_TYPE type,string audio_tag,int channel,int sample_rate,snd_pcm_format_t pcm_format,int frame_size)
{
	this->audio_type=type;
    //设置采集参数
    this->audio_path=audio_tag;
    this->channel=channel;
    this->sample_rate=sample_rate;
    this->format=pcm_format;
    this->frame_size=frame_size;
}
/**
 * @description: 释放空间关闭硬件
 * @param {*}
 * @return {*}
 * @author: YURI
 */
audio::~audio()
{
	if(audio_type=AUDIO_MICPHONE){
		// 释放数据缓冲区
		free(buffer_in);
		fprintf(stdout, "buffer_in freed\n");
	}
	// 关闭音频采集卡硬件
}


audio.h

/*
 * @Description: 
 * @Autor: YURI
 * @Date: 2022-01-26 18:08:08
 * @LastEditors: YURI
 * @LastEditTime: 2022-02-04 20:06:39
*/
#ifndef AUDIO_H
#define AUDIO_H
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#define FRAME_INIT           1024 //划分帧 
#define MAX_CHANNEL          4

using namespace std;

typedef enum {AUDIO_MICPHONE=0,AUDIO_SPEAKER=1}AUDIO_TYPE;
class audio
{
private:
    string audio_path;          //ALSA音频设备
    
	int channel;                //采集设备的通道数
    unsigned int sample_rate; 
    snd_pcm_t *capture_handle;			// 一个指向PCM设备的句柄
    unsigned char *buffer_in;           //麦克风采集用的数据
    unsigned char *buffer_out;          //话筒播放的数据
	snd_pcm_hw_params_t *hw_params;		// 此结构包含有关硬件的信息,可用于指定PCM流的配置
	snd_pcm_format_t format ;		    // 采样位数:16bit、LE格式
    AUDIO_TYPE audio_type;
public:
	int frame_size;
    audio(AUDIO_TYPE type,string audio_tag="default",int channel=2,int sample_rate=48000,snd_pcm_format_t pcm_format=SND_PCM_FORMAT_S16_LE,int frame_size=FRAME_INIT);
    ~audio();
    void audio_open_micophone(void);
    unsigned char* audio_read_frame(void);//从麦克风读一帧出来
	void audio_close_micophone(void);
	void audio_channel_split(unsigned char ** data);//分离声道数据,两个数据空间需要在外部申请
    void audio_open_speaker(void);//打开麦克风
    void audio_close_speaker(void);//关闭麦克风
    void audio_write_frame(unsigned char* data);//写一帧数据
};

#endif

wav文件读取接口

audio_wav.cpp

/*
 * @Description: 
 * @Autor: YURI
 * @Date: 2022-01-28 00:39:32
 * @LastEditors: YURI
 * @LastEditTime: 2022-02-04 08:03:03
 */
#include "audio_wave.h"

/**
 * @description: 保存wav文件时使用的初始化
 * @param {string} filepath
 * @param {int} rate
 * @param {int} bit_rate
 * @param {int} channel
 * @return {*}
 * @author: YURI
 */
audio_wave::audio_wave(WAVE_FILE_MODE mode,string filepath,int rate,int bit_rate,int channel)
{
    if(mode!=WAVE_FILE_WRITE_MODE){
        fprintf(stderr,"DON'T SUPPORT THIS MODE \r\n");
        return;
    }
    this->sample_rate=rate;
    this->bit_rate=bit_rate;
    this->channel=channel;
    this->file_path=filepath;
    this->data_length=0;

    audio_wave_head.bit_rate=bit_rate;
    audio_wave_head.bits_per_sample=bit_rate;
    audio_wave_head.channel=channel;
    audio_wave_head.sample_rate=sample_rate;
    audio_wave_head.byte_rate=sample_rate*(bit_rate/8)*channel;
    audio_wave_head.block_align=(bit_rate/8)*channel;    
}
/**
 * @description: 打开文件
 * @param {*}
 * @return {*}
 * @author: YURI
 */
void audio_wave::audio_write_start(){
    file_fd=fopen(file_path.c_str(),"wb");
    fseek(file_fd,44,SEEK_SET);
    data_length=0;
}

/**
 * @description: 向文件中写入一帧
 * @param {unsigned char*} frame
 * @param {int} size
 * @return {*}
 * @author: YURI
 */
void audio_wave::audio_write_frame(unsigned char* frame,int size){
    fwrite(frame,size,1,file_fd);
    data_length+=size;
}

/**
 * @description: 结束wav文件
 * @param {*}
 * @return {*}
 * @author: YURI
 */
void audio_wave::audio_write_end(void)
{
    audio_wave_head.data_length=data_length;
    audio_wave_head.wave_length=data_length+44-8;
    fseek(file_fd,0,SEEK_SET);
    fwrite(&audio_wave_head,sizeof(audio_wave_head),1,file_fd);
    fclose(file_fd);
}
/**
 * @description: 读取文件情况下的初始化函数
 * @param {string} filepath
 * @return {*}
 * @author: YURI
 */
audio_wave::audio_wave(WAVE_FILE_MODE mode,string filepath)
{
    if(mode!=WAVE_FILE_READ_MODE){
        fprintf(stderr,"DON'T SUPPORT THIS MODE \r\n");
        return;
    }
    this->file_path=filepath;
    file_fd =fopen(file_path.c_str(),"rb");
}
/**
 * @description: 开始读取,读取文件头
 * @param {*}
 * @return {*}
 * @author: YURI
 */
void audio_wave::audio_read_start(void)
{
    fread((void*)&audio_wave_head,44,1,file_fd);
    if(audio_wave_head.wave_header[0]!='R' ){
        fprintf(stderr,"THE FILE IS NOT WAV \r\n");
        return;
    }
    printf("CHANNEL %d \r\n",audio_wave_head.channel);
    printf("SAMPLE_RATE %d \r\n",audio_wave_head.sample_rate);
}
/**
 * @description: 从文件读一帧出来
 * @param {unsigned char*} frame
 * @param {int} size
 * @return {*}
 * @author: YURI
 */
int audio_wave::audio_read_frame(unsigned char* frame,int size)
{
    int ret= fread((void*)frame,size,1,file_fd);
    return ret;
}
/**
 * @description: 结束读取关闭文件
 * @param {*}
 * @return {*}
 * @author: YURI
 */
void audio_wave::audio_read_end(void){
    fclose(file_fd);
}

audio_wav.h

/*
 * @Description: 提供音频保存为wav文件 和读取wav文件的接口
 * @Autor: YURI
 * @Date: 2022-01-28 00:39:43
 * @LastEditors: YURI
 * @LastEditTime: 2022-02-04 07:09:18
 */
#ifndef AUDIO_WAVE_H
#define AUDIO_WAVE_H
#include "stdio.h"
#include <string>
using namespace std;
typedef struct _wave_header_t {
    char    wave_header[4];     //WAVE 头1
    int     wave_length;        //音频数据的长度 +44 -8
    char    format[8];          //WAVE+fmt
    int     bit_rate;           //一个采样点占几个bit
    short   pcm;                //音频数据编码方式             
    short   channel;            //通道数
    int     sample_rate;        //采样率
    int     byte_rate;          //采样率×每次采样大小
    short   block_align;        //每次采样的大小 = 采样精度*声道数/8(单位是字节) 譬如 16bit 立体声在这里的值是 4 字节
    short   bits_per_sample;    //每个声道的采样精度; 譬如 16bit 在这里的值就是16
    char    fix_data[4];        //"data"
    int     data_length;        //音频数据的长度
} wave_t;
typedef enum {WAVE_FILE_READ_MODE=0,WAVE_FILE_WRITE_MODE=1}WAVE_FILE_MODE;

class audio_wave
{
private:
    FILE* file_fd;
    string file_path;
    int channel;
    int data_length;
    int sample_rate;
    int bit_rate;
    wave_t audio_wave_head={
        {'R', 'I', 'F', 'F'},
        ( int)-1,
        {'W', 'A', 'V', 'E', 'f', 'm', 't', ' '},
        bit_rate,
        0x01,
        channel,
        sample_rate,
        sample_rate*(bit_rate/8)*channel,
        (bit_rate/8)*channel,
        bit_rate,
        {'d', 'a', 't', 'a'},
        (int)-1          
    };
public:
    audio_wave(WAVE_FILE_MODE mode,string filepath,int rate,int bit_rate=16,int channel=2);//写数据的初始化函数
    audio_wave(WAVE_FILE_MODE mode,string filepath);//读WAV的初始化函数
    ~audio_wave(){};
    //打开文件
    void audio_write_start();
    //向文件中写入一帧
    void audio_write_frame(unsigned char* frame,int size);
    //结束wav文件
    void audio_write_end(void);
    //开始准备读入wav文件
    void audio_read_start(void);
    //读入一帧文件
    int audio_read_frame(unsigned char* frame,int size);
    //结束读入
    void audio_read_end(void);
};

#endif

播放录音测试代码

运行方式

./xxx hw:0,0 ./file

audio_record

/*
 * @Description: 录音并且分别保存为两路WAV文件
 * @Autor: YURI
 * @Date: 2022-01-27 07:33:35
 * @LastEditors: YURI
 * @LastEditTime: 2022-02-04 20:38:57
 */
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include "audio.h"
#include "audio_wave.h"
#include "signal.h"
#include <string>
using namespace std;
audio_wave *audio_wave1,*audio_wave2,*audio_wave3;
void record_stop(int signo)
{
	printf("end \r\n");
	audio_wave1->audio_write_end();
	audio_wave2->audio_write_end();
	audio_wave3->audio_write_end();
}
int main (int argc, char *argv[])
{
	int i;
	int err;
	string mico_path=string(argv[1]);
	string save_path=string(argv[2]);
	int channel =2;
	unsigned int rate = 48000;			// 采样频率:	44100Hz
	snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;		// 采样位数:16bit、LE格式
	signal(SIGINT, record_stop); 
	audio* aud=new audio(AUDIO_MICPHONE,mico_path,channel,rate,format);
	aud->audio_open_micophone();
	unsigned char * buffer;
	unsigned char * data[2];
	data[0]=(unsigned char*)malloc(2*aud->frame_size) ;
	data[1]=(unsigned char*)malloc(2*aud->frame_size) ;
	char file[20];
	sprintf(file,"%s_0.wav",save_path.c_str());
	audio_wave1=new audio_wave(WAVE_FILE_WRITE_MODE,string(file),48000,16,1);
	audio_wave1->audio_write_start();
	sprintf(file,"%s_1.wav",save_path.c_str());
	audio_wave2=new audio_wave(WAVE_FILE_WRITE_MODE,string(file),48000,16,1);
	audio_wave2->audio_write_start();
	sprintf(file,"%s.wav",save_path.c_str());
	audio_wave3=new audio_wave(WAVE_FILE_WRITE_MODE,string(file),48000,16,2);
	audio_wave3->audio_write_start();
		
	// 开始采集音频pcm数据
	for(int i=0;i<300;i++)
	{
		buffer=aud->audio_read_frame();
		aud->audio_channel_split(data);
		audio_wave1->audio_write_frame(data[0],2*aud->frame_size);
		audio_wave2->audio_write_frame(data[1],2*aud->frame_size);
		audio_wave3->audio_write_frame(buffer,4*aud->frame_size);
	}
	audio_wave1->audio_write_end();
	audio_wave2->audio_write_end();
	audio_wave3->audio_write_end();
	return 0;
}

audio_play

/*
 * @Description: 读取WAV文件并且播放
 * @Autor: YURI
 * @Date: 2022-02-04 06:55:42
 * @LastEditors: YURI
 * @LastEditTime: 2022-02-04 20:51:15
 */
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include "audio.h"
#include "audio_wave.h"
#include "signal.h"
#include <string>
using namespace std;
audio_wave *audio_wave1;
unsigned char* buffer;
audio *aud;
int main(int argc,void** argv)
{
    string speaker_path=string(( char*)argv[1]);
    int channel =2;
    unsigned int rate = 48000;			// 采样频率:	48000
    snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;		// 采样位数:16bit、LE格式
    aud=new audio(AUDIO_SPEAKER,speaker_path,channel,rate,format);
    aud->audio_open_speaker();
    audio_wave1=new audio_wave(WAVE_FILE_READ_MODE,string((char*)argv[2]));
    audio_wave1->audio_read_start();
    
    buffer=(unsigned char*)malloc(4*aud->frame_size);
    while(audio_wave1->audio_read_frame(buffer,4*aud->frame_size) >0){
        aud->audio_write_frame(buffer);
    }
    aud->audio_close_speaker();
    audio_wave1->audio_read_end();
    return 0;
}


这篇关于【嵌入式流媒体开发】Linux ALSA 声卡数据采集与播放的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!