linux下实现录音和播放

本文介绍了一段在Linux环境下使用ALSAAPI进行录音和播放的C语言代码示例。该代码通过设置硬件参数,如采样率、通道数等,实现从文件读取音频流并播放,以及捕获音频流并写入文件的功能。适用于音视频基础领域的学习和实践。

linux下编写本地录音和播放demo测试代码:

1. 通过读.wav格式的音频流,将音频流写入DMA缓存区,实现语音播放;

2. 通过读DMA缓存区的音频流,实现录音。

首先要配置硬件参数,包括设置采样位数、通道数、采样率等,然后向DMA缓存区写或者读,实现播放和录音,具体见下文:

/************************************************************************* 
  > File Name: local_player.c 
  > Desc.    : Read from file and playback, or capture and write to file.
  > Author   : horistudio@163.com
  > Date     : 2020/05/26
 ************************************************************************/ 
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <alsa/asoundlib.h>

int main(int argc, char *argv[]) {
#if 1
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

#if 0
  /* Open PCM device for playback. */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_PLAYBACK, 0);
#endif

  /* Open PCM device for capture. */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s/n",
            snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* 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, 8);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 48000;
  snd_pcm_hw_params_set_rate_near(handle, params,
                                  &val, &dir);

  /* Set period size to 32 frames. */
  frames = 32;
  snd_pcm_hw_params_set_period_size_near(handle,
                              params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s/n",
            snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params, &frames,
                                    &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);
  printf("------>frames:%d, size:%d\n",frames,size);
#endif

#if 1
  	//************************[3.文件操作]*******************************
	#if 0
  /*通过主函数传参,传入.wav格式文件名*/
	FILE *fp_r 	= NULL;
	int ret 	= 0;
	fp_r = fopen(argv[1], "rb");
	if(fp_r == NULL) {
		printf("open file fail!\n");
		exit(1);
	}
	#endif

	/* 打开一个文件,将录音音频流写入文件 */ 
	FILE *fp = NULL;
	int file_len = 0;

	fp = fopen("record_dump.raw", "a+");
	if(fp == NULL) {
		printf("open file fail!\n");
		exit(1);
	}

	printf("Configurations are finished......\n");
	
    while (1) {
	  	/* 1.read from file and playback */
		 #if 0
	    //read audio file 
	    memset(buffer,0,size);
		  rc = fread(buffer, 1, size, fp_r);
	    if (rc == 0) {
	      fprintf(stderr, "end of file on input/n");
	      break;
	    } else if (rc != size) {
	      fprintf(stderr,
	              "short read: read %d bytes/n", rc);
	    }
		
	    rc = snd_pcm_writei(handle, buffer, frames);
	    if (rc == -EPIPE) {
	      /* EPIPE means underrun */
	      fprintf(stderr, "underrun occurred/n");
	      snd_pcm_prepare(handle);
	    } else if (rc < 0) {
	      fprintf(stderr,
	              "error from writei: %s/n",
	              snd_strerror(rc));
	    }  else if (rc != (int)frames) {
	      fprintf(stderr,
	              "short write, write %d frames/n", rc);
	    }
		#endif

		/* 2.capture and write to file */
		#if 1
		/* capture */
		memset(buffer, 0, size);
		rc = snd_pcm_readi(handle, buffer, frames);
	    printf("snd_pcm_readi,frames:%d, buffer:%s\n",frames, buffer);
	    if (rc == -EPIPE)
	    {
	        /* EPIPE means overrun */
	        fprintf(stderr, "overrun occurred\n");
	        snd_pcm_prepare(handle);
	    } else if (rc < 0) {
	        fprintf(stderr,
	                "error from read: %s\n",
	                snd_strerror(rc));
	    } else if (rc != (int)frames) {
          fprintf(stderr, 
	        	"short read, read %d frames\n", rc);
	    }
		
		if (fp) {
	        file_len = fwrite(buffer, 1, size, fp);
			printf("fwrite:%d bytes \n", file_len);
		 	
		} else {
		  printf("%s[%d] file open fail\n",__func__,__LINE__);
		}
		#endif
  }
#endif

  fclose(fp);
  //close(fd_f);
  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}

*上述代码,可以根据需要解开注释或者打上注释,分别测试录音或者播放

编译时,需要引入alsa库,-L指定具体的alsa-utils-x.x.x类库的asound等,编译方法方法:

gcc -o local_player local_player.c -L XXX/alsa-utils-1.1.5 -lasound -lm -ldl

测试方法

播放 通过喇叭是否正常播放音频文件,确认是否播放成功;

录音 通过dump出来的文件,用audiocity或其他软件打开并播放,确认录音是否正常

 

      * 上文代码的意义是可以问语音通话做准备,读取打开的.wav音频流其实是模拟读取网络发来的音频流buffer,将录音音频流写入文件是模拟向网络发送buffer写入

 

<think> 我们正在讨论使用ALSA库在Linux下通过C语言实现音频的录音播放。用户之前的问题是关于共享内存的,但当前问题已经转向ALSA音频处理。因此,我们需要聚焦于ALSA录音播放实现方法。 根据引用[1]中的内容,我们得知: - ALSA库提供了用于音频播放的函数,如snd_pcm_writei()snd_pcm_mmap_writei()。 - 还提到了错误恢复函数snd_pcm_recover()。 现在,用户要求实现录音播放,因此我们需要同时考虑录音播放两个方向。 实现步骤一般包括: 1. 打开PCM设备(录音设备或播放设备) 2. 设置硬件参数(采样率、声道数、采样格式、缓冲区大小等) 3. 为音频数据分配缓冲区 4. 循环读取(录音)或写入(播放)数据 5. 关闭设备并释放资源 我们将分别提供录音播放的代码框架。 注意:为了简洁,省略了部分错误处理,实际使用中应检查每个ALSA函数调用的返回值。 ### 一、音频播放实现步骤(播放音频) 1. **打开PCM设备** ```c snd_pcm_t *pcm_handle; snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); ``` 2. **设置硬件参数** ```c snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_any(pcm_handle, hw_params); // 初始化参数 // 设置交错模式(interleaved)、16位有符号小端格式、2声道、44100Hz采样率 snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2); unsigned int sample_rate = 44100; snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &sample_rate, 0); ``` 3. **应用参数** ```c snd_pcm_hw_params(pcm_handle, hw_params); ``` 4. **准备设备** ```c snd_pcm_prepare(pcm_handle); ``` 5. **循环写入音频数据(播放)** 假设我们有一个缓冲区`buffer`,其中包含PCM数据: ```c while (有数据需要播放) { // 写入数据,frames为要写入的帧数(每帧包含所有声道的一个样本) int ret = snd_pcm_writei(pcm_handle, buffer, frames); if (ret == -EPIPE) { // 发生下溢,重新准备设备 snd_pcm_recover(pcm_handle, ret, 0); } else if (ret < 0) { // 其他错误处理 break; } // 更新buffer指针剩余帧数 } ``` 6. **关闭设备** ```c snd_pcm_close(pcm_handle); ``` ### 二、录音实现步骤(捕获音频) 1. **打开PCM设备** ```c snd_pcm_t *pcm_handle; snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_CAPTURE, 0); ``` 2. **设置硬件参数**(与播放类似,但注意使用捕获流) ```c snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_any(pcm_handle, hw_params); snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 2); unsigned int sample_rate = 44100; snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &sample_rate, 0); ``` 3. **应用参数** ```c snd_pcm_hw_params(pcm_handle, hw_params); ``` 4. **准备设备** ```c snd_pcm_prepare(pcm_handle); ``` 5. **循环读取音频数据(录音)** ```c while (需要录音) { // 读取数据,frames为要读取的帧数 int ret = snd_pcm_readi(pcm_handle, buffer, frames); if (ret == -EPIPE) { // 发生溢出,重新准备设备 snd_pcm_recover(pcm_handle, ret, 0); } else if (ret < 0) { // 其他错误处理 break; } // 此时buffer中有了录制的音频数据,可以保存或处理 } ``` 6. **关闭设备** ```c snd_pcm_close(pcm_handle); ``` ### 三、完整示例(播放正弦波) 下面是一个播放1秒440Hz正弦波的简单示例: ```c #include <alsa/asoundlib.h> #include <math.h> #define SAMPLE_RATE 44100 #define DURATION 1 #define FREQ 440 int main() { snd_pcm_t *pcm_handle; snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_any(pcm_handle, hw_params); snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(pcm_handle, hw_params, 1); // 单声道 unsigned int sample_rate = SAMPLE_RATE; snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &sample_rate, 0); snd_pcm_hw_params(pcm_handle, hw_params); snd_pcm_prepare(pcm_handle); // 生成正弦波数据 int num_samples = SAMPLE_RATE * DURATION; short *samples = malloc(num_samples * sizeof(short)); for (int i = 0; i < num_samples; i++) { samples[i] = 32760 * sin(2 * M_PI * FREQ * i / SAMPLE_RATE); } // 写入数据 int written = 0; while (written < num_samples) { int ret = snd_pcm_writei(pcm_handle, samples + written, num_samples - written); if (ret == -EPIPE) { snd_pcm_recover(pcm_handle, ret, 0); } else if (ret < 0) { break; } else { written += ret; } } free(samples); snd_pcm_close(pcm_handle); return 0; } ``` ### 四、编译运行 编译命令(需要链接ALSA库): ```bash gcc alsa_play.c -o playback -lasound -lm ``` ### 五、注意事项 1. **错误处理**:每个ALSA函数调用后都应检查返回值,并适当处理错误(如使用`snd_pcm_recover`)。 2. **缓冲区大小**:实际应用中需要合理设置缓冲区大小,避免欠载(播放)或溢出(录音)。 3. **格式匹配**:确保设置的格式与音频数据格式一致。 4. **多线程**:在高实时性要求场景下,可能需要多线程处理音频流。 --- **相关问题** 1. ALSA录音时如何保存为WAV文件? 2. ALSA如何处理多声道音频? 3. 如何实现录音的同时播放(全双工)? 4. ALSA与PulseAudio有何区别? [^1]: ALSA库提供了丰富的函数用于音频设备操作,包括设置参数、读写数据等。 </think> ### Linux下使用ALSA库实现录音播放(C语言指南) #### 一、核心步骤概览 | **阶段** | **录音实现** | **播放实现** | |----------|--------------|--------------| | 1. 设备初始化 | 打开**录音设备**(`SND_PCM_STREAM_CAPTURE`) | 打开**播放设备**(`SND_PCM_STREAM_PLAYBACK`) | | 2. 参数配置 | 设置**采样率/格式/声道** | 同录音配置 | | 3. 缓冲区管理 | 分配**输入缓冲区** | 分配**输出缓冲区** | | 4. 数据传输 | 循环调用`snd_pcm_readi()` | 循环调用`snd_pcm_writei()` | | 5. 错误处理 | 使用`snd_pcm_recover()`恢复 | 同录音处理 | | 6. 资源释放 | 关闭设备释放资源 | 同录音处理 | --- ### 二、完整代码实现 #### 1. 录音功能实现 ```c #include <alsa/asoundlib.h> #define SAMPLE_RATE 44100 #define CHANNELS 2 #define FORMAT SND_PCM_FORMAT_S16_LE #define BUFFER_FRAMES 1024 void record_audio(const char* filename) { snd_pcm_t *handle; FILE *fp; int rc; // 1. 打开录音设备 if ((rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) { fprintf(stderr, "录音设备打开失败: %s\n", snd_strerror(rc)); return; } // 2. 配置参数 snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca(&params); 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, FORMAT); snd_pcm_hw_params_set_channels(handle, params, CHANNELS); snd_pcm_hw_params_set_rate_near(handle, params, &SAMPLE_RATE, 0); snd_pcm_hw_params_set_period_size_near(handle, params, &BUFFER_FRAMES, 0); if ((rc = snd_pcm_hw_params(handle, params)) < 0) { fprintf(stderr, "参数设置失败: %s\n", snd_strerror(rc)); goto cleanup; } // 3. 准备设备缓冲区 snd_pcm_prepare(handle); short buf[BUFFER_FRAMES * CHANNELS]; // 4. 创建输出文件 fp = fopen(filename, "wb"); if (!fp) { perror("文件创建失败"); goto cleanup; } // 5. 录音循环(录5秒) for (int i = 0; i < SAMPLE_RATE*5/BUFFER_FRAMES; i++) { rc = snd_pcm_readi(handle, buf, BUFFER_FRAMES); if (rc == -EPIPE) { snd_pcm_recover(handle, rc, 0); // 处理溢出 } else if (rc < 0) { fprintf(stderr, "读取错误: %s\n", snd_strerror(rc)); break; } // 写入原始PCM数据 fwrite(buf, sizeof(short), BUFFER_FRAMES * CHANNELS, fp); } cleanup: // 6. 资源释放 fclose(fp); snd_pcm_close(handle); } ``` #### 2. 播放功能实现 ```c void play_audio(const char* filename) { snd_pcm_t *handle; FILE *fp; int rc; // 1. 打开播放设备 if ((rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "播放设备打开失败: %s\n", snd_strerror(rc)); return; } // 2. 配置参数(与录音相同) // ... 省略相同配置代码 ... // 3. 准备设备缓冲区 snd_pcm_prepare(handle); short buf[BUFFER_FRAMES * CHANNELS]; // 4. 打开音频文件 fp = fopen(filename, "rb"); if (!fp) { perror("文件打开失败"); goto cleanup; } // 5. 播放循环 while (1) { size_t read = fread(buf, sizeof(short), BUFFER_FRAMES * CHANNELS, fp); if (read <= 0) break; // 文件结束 int frames = read / CHANNELS; rc = snd_pcm_writei(handle, buf, frames); if (rc == -EPIPE) { snd_pcm_recover(handle, rc, 0); // 处理欠载 } else if (rc < 0) { fprintf(stderr, "写入错误: %s\n", snd_strerror(rc)); break; } else if (rc != frames) { fprintf(stderr, "写入不完整: %d/%d\n", rc, frames); } } cleanup: // 6. 资源释放 fclose(fp); snd_pcm_close(handle); } ``` --- ### 三、关键函数解析 1. **设备操作函数** - `snd_pcm_open()`: 打开PCM设备 ```c int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode); ``` - `snd_pcm_close()`: 关闭设备 2. **参数设置函数** ```c snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); // 16位小端 snd_pcm_hw_params_set_channels(handle, params, 2); // 立体声 snd_pcm_hw_params_set_rate_near(handle, params, &44100, 0); // 44.1kHz ``` 3. **数据传输函数** - `snd_pcm_readi()`: 从设备读取数据(录音) $$ \text{返回值} = \begin{cases} >0 & \text{读取的帧数} \\ -\text{EPIPE} & \text{需要恢复的硬件错误} \end{cases} $$ - `snd_pcm_writei()`: 向设备写入数据(播放) 4. **错误恢复函数** ```c snd_pcm_recover(handle, -EPIPE, 1); // 自动恢复硬件错误 ``` --- ### 四、编译与运行 1. **编译命令**(需安装`libasound2-dev`) ```bash gcc alsa_demo.c -o audio_demo -lasound ``` 2. **执行示例** ```bash # 录音(生成raw.pcm) ./audio_demo record raw.pcm # 播放录音文件 ./audio_demo play raw.pcm ``` --- ### 五、常见问题解决 1. **设备无法打开** - 检查用户权限:`sudo usermod -aG audio 用户名` - 验证设备名:`arecord -L` 查看可用设备 2. **数据对齐错误** ```c if (rc < 0 && rc != -EPIPE) { snd_pcm_recover(handle, rc, 1); // 强制恢复 snd_pcm_prepare(handle); // 重新准备 } ``` 3. **缓冲区欠载/溢出** - 增加缓冲区大小: ```c unsigned int buffer_time = 500000; // 500ms snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0); ``` --- **相关问题** 1. 如何实现实时音频处理(如回声消除)? 2. ALSA如何支持多声道音频(如5.1声道)? 3. 如何将录制的PCM数据转换为WAV文件? 4. ALSA与PulseAudio在音频架构中的关系是什么? [^1]: ALSA提供了`snd_pcm_writei()``snd_pcm_readi()`进行数据传输,并可通过`snd_pcm_recover()`处理硬件错误。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值