ALSA 接口编程实例——语音聊天

/* 
本程序维护一个 256bytes*4 缓冲区,两个信号量保护(读和写)。创建两
个线程,一个用于采集声卡数据并写到缓冲区,数据采集线程使用ALSA接口
编程,设置采样率 22333,周期帧数 128,帧格式 U8,声道数 2,每个周期
大约 5.73ms,每个周期 256bytes。另外一个将缓冲区数据广播到网络,每
次发送 256bytes。
*/

#define ALSA_PCM_NEW_HW_PARAMS_API 

#include <alsa/asoundlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#include <sys/types.h>	/* basic system data types */
#include <sys/socket.h>	/* basic socket definitions */
#include <netinet/in.h>	/* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h>	/* inet(3) functions */

#define RATE 22333
#define CHANNEL 2
#define FORMAT	SND_PCM_FORMAT_U8
#define FRAMES	128

#define SIZE CHANNEL*FRAMES*1

#define NBUFF    4  

// 套接字端口
#define PORT 10000	
#define	SA	struct sockaddr

// 数据缓冲区及信号量
struct {    
	char   buffer[1024];  
	sem_t mutex, nempty, nstored;  
} shared;

char* pRead = shared.buffer; //读指针
char* pWrite = shared.buffer; //写指针
void* sendData(void *arg); //线程函数,广播数据
void* generateData(void *arg); //线程函数,读声卡

// 计数变量
long produce=0;
long consume=0;
long totalTime = 0;

int main() 
{ 	
	pthread_t   tid_generateData, tid_sendData;

	// 初始化信号量
    sem_init(&shared.mutex, 0, 1);  //未用到
    sem_init(&shared.nempty, 0, NBUFF);  
    sem_init(&shared.nstored, 0, 0);  
   
    // 创建读声卡线程,将数据保存到缓冲区
    pthread_create(&tid_generateData, NULL, generateData, NULL);
	// 创建广播线程,将缓冲区数据发送到网络
    pthread_create(&tid_sendData, NULL, sendData, NULL);  
  
    pthread_join(tid_sendData, NULL);  
    pthread_join(tid_generateData, NULL);  
  
    sem_destroy(&shared.mutex);  
    sem_destroy(&shared.nempty);  
    sem_destroy(&shared.nstored);  
    exit(0);  
} 

void* sendData(void *arg)
{
	int			sockfd;
	struct 		sockaddr_in		servaddr;
	
	/* socket 初始化 */
	const int on = 1;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(PORT);
	inet_pton(AF_INET, "192.168.1.255", &servaddr.sin_addr);
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
	
	int n;
	const SA *pservaddr = (SA*)(&servaddr);	
	socklen_t servlen = sizeof(SA);
	printf("\n\n\n\n\nData generating starts, Broadcasting ...\n\n\n\n\n\n\n\n\n\n");
	printf("|------------------------------------------------------------|\t0\tmin |\n\033[1A|");
	while(1)
	{
		// 获取nstored信号量
		sem_wait(&shared.nstored);
		
		// 发送数据
		n = sendto(sockfd, pRead, 256, 0, pservaddr, servlen);
		if(n!=256) //printf("send short: send %d\n",n)
		{
			sem_post(&shared.nstored);
			continue;
		}
		
		// 更新缓冲区读指针
		pRead += 256;
		if(pRead-1024 == shared.buffer)
			pRead = shared.buffer;
			
		// 释放nempty信号量
		sem_post(&shared.nempty);
		
		// 计数器
		if(0 == ++consume % 175)
		{
			++totalTime;
			printf("-");
			fflush(stdout);
			if(0 == totalTime %60)
				printf("|\t%ld\tmin |\n\033[1A|", totalTime/60),fflush(stdout);
		}
	}
}

void* generateData(void *arg)
{
	// 设备打开初始化配置
	int rc;  
	snd_pcm_t *handle; 
	snd_pcm_hw_params_t *params; 
	unsigned int val = RATE;  // 采样率 22333
	int dir; 
	snd_pcm_uframes_t frames;  
	 
	rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_CAPTURE, 0); // 打开方式为“抓取数据”
	
	if (rc < 0) 
	{
		fprintf(stdout, "unable to open pcm device: %s\n",snd_strerror(rc));
		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_U8); //帧数据格式,每帧1byte
	snd_pcm_hw_params_set_channels(handle, params, 2);  			// 声道数 2
	snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);  	// 获取真实采样率 22333
	snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); // 获取每周期帧数 64
	rc = snd_pcm_hw_params(handle, params); 
	if (rc < 0)
	{     
		fprintf(stdout,"unable to set hw parameters: %s\n", snd_strerror(rc)); 
		exit(1); 
	}  
	snd_pcm_hw_params_get_period_size(params, &frames, &dir); 
	snd_pcm_hw_params_get_period_time(params, &val, &dir);

	while (1) 
	{     
		frames = FRAMES; // 更改每周期的帧数,设置为128
		sem_wait(&shared.nempty);
		
		rc = snd_pcm_readi(handle, pWrite, frames); // 获取256 Bytes 数据
		
		if (rc != (int)frames) 
		{
			usleep(1000);   
			snd_pcm_prepare(handle);
			sem_post(&shared.nempty);
			continue;
		}

		// 更新缓冲区写指针
		pWrite += 256;
		if(pWrite-1024 == shared.buffer)
			pWrite = shared.buffer;
		sem_post(&shared.nstored);
		// 计数器
		++produce;
	}  
	snd_pcm_drain(handle); 
	snd_pcm_close(handle);
}

/* 
本程序维护一个256bytes*4缓冲区,两个信号量保护(读和写)。创建两
个线程,一个监听广播消息并将获取的数据写到缓冲区,另外一个线程将
缓冲区数据写到声卡。写声卡编程使用ALSA接口编程,采样率 22333,周
期帧数128,帧格式U8,声道数2,计算下来,每个周期大约 5.73ms,每个
周期256bytes。
*/

#define ALSA_PCM_NEW_HW_PARAMS_API 

#include <alsa/asoundlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#include	<sys/types.h>	/* basic system data types */
#include	<sys/socket.h>	/* basic socket definitions */
#include	<netinet/in.h>	/* sockaddr_in{} and other Internet defns */
#include	<arpa/inet.h>	/* inet(3) functions */

// 声卡配置参数
#define RATE 22333
#define CHANNEL 2
#define FORMAT	SND_PCM_FORMAT_U8
#define FRAMES	128

#define SIZE CHANNEL*FRAMES*1

#define NBUFF    4  
#define SEM_MUTEX   "mutex"       
#define SEM_NEMPTY  "nempty"  
#define SEM_NSTORED "nstored"

#define PORT 10000
#define	SA	struct sockaddr

// 缓冲区及信号量
struct {    
  char   buffer[1024];  
  sem_t mutex, nempty, nstored;  
} shared;

// 缓冲区读写指针
char* pRead = shared.buffer;
char* pWrite = shared.buffer;
void* recvData(void *arg);
void* generateSnd(void *arg);

// 计数变量
long produce=0;
long consume=0;
long totalTime = 0;

int main() 
{ 	
	pthread_t   tid_generateSnd, tid_recvData; 

	// 信号量初始化
    sem_init(&shared.mutex, 0, 1);  //未使用
    sem_init(&shared.nempty, 0, NBUFF);  
    sem_init(&shared.nstored, 0, 0);  
   
	// 创建发声线程
    pthread_create(&tid_generateSnd, NULL, generateSnd, NULL);  
	// 创建数据接收线程
    pthread_create(&tid_recvData, NULL, recvData, NULL);  
  
    pthread_join(tid_recvData, NULL);  
    pthread_join(tid_generateSnd, NULL);  
  
    sem_destroy(&shared.mutex);  
    sem_destroy(&shared.nempty);  
    sem_destroy(&shared.nstored);  
    exit(0);  
} 

void* recvData(void *arg)
{
	// 配置socket
	int			sockfd;
	struct 		sockaddr_in		servaddr;
	/* initialization for socket */
	const int on = 1;
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(PORT);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
	
	int n;
	SA	*preply_addr = (SA*)malloc(sizeof(SA));
	socklen_t len = sizeof(SA);
	
	// 计时器
	printf("\n\n\n\n\nData receiving starts, voice generating ...\n\n\n\n\n\n\n\n\n\n");
	printf("|------------------------------------------------------------|\t0\tmin |\n\033[1A|");
	
	for (;;) 
	{
		// 获取写信号量
		sem_wait(&shared.nempty);
		
		// 监听网络,并将数据写到缓冲区
		n = recvfrom(sockfd, pWrite, 256, 0, preply_addr, &len);
		if (n < 0) {
			if (errno == EINTR)
				break;		/* waited long enough for replies */
		} 
		else if(n != 256) 
		{
			sem_post(&shared.nempty);
			continue;
		}
		else
		{
			// 更新写指针
			pWrite += 256;
			if(pWrite-1024 == shared.buffer)
				pWrite = shared.buffer;
			// 释放读信号量
			sem_post(&shared.nstored);
			if(0 == ++produce % 175)
			{
				++totalTime;
				printf("-");
				fflush(stdout);
				if(0 == totalTime %60)
					printf("|\t%ld\tmin |\n\033[1A|", totalTime/60),fflush(stdout);
			}
		}
	}
	free(preply_addr);
}

void* generateSnd(void *arg)
{
	// 声卡配置变量
	int rc;  
	snd_pcm_t *handle; 
	snd_pcm_hw_params_t *params; 
	unsigned int val = RATE; 
	int dir; 
	snd_pcm_uframes_t frames;  
	
	rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_PLAYBACK, 0); // 播放模式打开
	
	if (rc < 0) 
	{
		fprintf(stderr, "unable to open pcm device: %s\n",snd_strerror(rc));
		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_U8); 
	snd_pcm_hw_params_set_channels(handle, params, 2); 
	snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); 
	snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); 
	rc = snd_pcm_hw_params(handle, params); 
	if (rc < 0)
	{     
		fprintf(stderr,"unable to set hw parameters: %s\n", snd_strerror(rc)); 
		exit(1); 
	}  
	snd_pcm_hw_params_get_period_size(params, &frames, &dir); 
	snd_pcm_hw_params_get_period_time(params, &val, &dir);

	while (1) 
	{     
		frames = FRAMES;
		// 获取读信号量
		sem_wait(&shared.nstored);
    
		// 向声卡写数据
		rc = snd_pcm_writei(handle, pRead, frames);
		if (rc != (int)frames) 
		{
			usleep(1000);
			snd_pcm_prepare(handle);
			sem_post(&shared.nstored);
			continue;
		}
		
		// 更新读指针
		pRead += 256;
		if(pRead-1024 == shared.buffer)
			pRead = shared.buffer;
		// 释放写信号量
		sem_post(&shared.nempty);
		++consume;
	}  
	snd_pcm_drain(handle); 
	snd_pcm_close(handle);
}

版权所有,转载请注明本博客地址!


  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值