分布式麦克风的开发及ros中的实现

相对于目前集中式的麦克风阵列,分布式阵列的优势也是非常明显的。首先分布式麦克风阵列(尤其无线传输)的尺寸的限制就不存在了;另外,阵列的节点可以覆盖很大的面积——总会有一个阵列的节点距离声源很近,录音信噪比大幅度提升,算法处理难度也会降低,总体的信号处理的效果也会有非常显著的提升。

这一篇博客会详细描述实际开发分布式麦克风的整个过程,闲话不说,直奔主题。
放上该工程的链接:TRX
首先将osi七层模型的具体协议放在这里看一下:

OSI 中的层                           功能                                           TCP/IP协议族 
应 用层                 文件传输,电子邮件,文件服务,虚拟终 端         TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 
表示层                 数据格式化,代码转换,数据加密                               没有协议 
会话 层                 解除或建立与别的接点的联系                                  没有协议 
传输层                 提供端对端的接口                                      TCP,UDP (RTP)
网 络层                 为数据包选择路由                              IP,ICMP,RIP,OSPF,BGP,IGMP 
数据链路层           传输有地址的帧以及错误检测功能                   SLIP,CSLIP,PPP,ARP,RARP,MTU 
物 理层                 以二进制数据形式在物理媒体上传输数据                  ISO2110,IEEE802,IEEE802.2

在传输层我们可以看到关于rtp协议的一些知识:
RTP全名是Real-time Transport Protocol(实时传输协议)。该协议提供的信息包括:时间戳(用于同步)、序列号(用于丢包和重排序检测)、以及负载格式(用于说明数据的编码格式)。
该项目是用RTP/UDP去解决处理丢失的数据包和网络拥塞,因此更适合实时传输音频数据。查看了关于RTP和UDP(User Datagram Protocol(用户数据报协议))协议的资料,简单描述一下:

RTP位于UDP之上,UDP虽然没有TCP那么可靠,并且无法保证实时业务的服务质量,需要RTCP实时
监控数据传输和服务质量,但是,由于UDP的传输时延低于TCP,能与视频和音频很好匹配。因此,
在实际应用中,RTP/RTCP/UDP用于音频/视频媒体,而TCP用于数据和控制信令的传输。

UDP和TCP协议的主要区别是两者在如何实现信息的可靠传递方面不同。TCP协议中包含了专门的传递
保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收
到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。

所以TCP必UDP多了建立连接的时间。相对UDP而言,TCP具有更高的安全性和可靠性。TCP协议传输
的大小不限制,一旦连接被建立,双方可以按照一定的格式传输大量的数据,而UDP是一个不可靠的
协议,大小有限制,每次不能超过64K。

相对于TCP协议,UDP协议的另外一个不同之处在于如何接收突法性的多个数据报。不同于TCP,
UDP并不能确保数据的发送和接收顺序。

所用到的编解码为OPUS:Opus 编解码器。
opus的接口说明在opus/opus.h头文件中,主要有下面几个函数:

// 创建编码器
OpusEncoder *opus_encoder_create(
    opus_int32 Fs, 
    int channels, 
    int application,
    int *error  
)

// 修改编码器参数
int opus_encoder_ctl(
    OpusEncoder *st, 
    int request, ...
)

// 创建解码器
OpusDecoder *opus_decoder_create(
    opus_int32 Fs,  //采样率,可以设置的大小为8000, 12000, 16000, 24000, 48000.
    int channels, //声道数,网络中实时音频一般为单声道
    int *error //是否创建失败,返回0表示创建成功
)

// 将PCM编码成opus
opus_int32 opus_encode(
    OpusEncoder *st,
    const opus_int16 *pcm,
    int frame_size,
    unsigned char *data,
    opus_int32 max_data_bytes
) 

// 从opus中译码出PCM
int opus_decode(
    OpusDecoder *st,  //上一步的返回值
    const unsigned char *data,  //要解码的数据
    opus_int32 len,   //数据长度
    opus_int16 *pcm, //解码后的数据,注意是一个以16位长度为基本单位的数组
    int frame_size,  //每个声道给pcm数组的长度
    int decode_fec  //是否需要fec,设置0为不需要,1为需要
) 

因为专业非研究语音编解码,只是拿来用用,知道这些函数和如何调用的就可以了。

该工程实现的是单通道的,采样率都行的分布式麦克风音频传输,一端采集音频数据并通过ip传输,另一端接收语音信号的同时并进行语音识别操作。通信具体用法如下所示:

发送端:./tx -r 16000 -c 1 -h 192.168.***.*** -d plughw:*,*,* -m 256
接受端:./rx -m 256

上面r代表采样率,c代表通道,h代表传输音频ip地址,d代表ALSA驱动端口,m表示缓存空间。

这是发送音频数据的主函数:

#include <netdb.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <opus/opus.h>
#include <ortp/ortp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "defaults.h"
#include "device.h"
#include "notice.h"
#include "sched.h"

static unsigned int verbose = DEFAULT_VERBOSE;

static RtpSession* create_rtp_send(const char *addr_desc, const int port)
{
	RtpSession *session;
	session = rtp_session_new(RTP_SESSION_SENDONLY);
	assert(session != NULL);
	rtp_session_set_scheduling_mode(session, 0);
	rtp_session_set_blocking_mode(session, 0);
	rtp_session_set_connected_mode(session, FALSE);
	if (rtp_session_set_remote_addr(session, addr_desc, port) != 0)
		abort();
	if (rtp_session_set_payload_type(session, 0) != 0)
		abort();
	if (rtp_session_set_multicast_ttl(session, 16) != 0)
		abort();

	return session;
}

static int send_one_frame(snd_pcm_t *snd,
		const unsigned int channels,
		const snd_pcm_uframes_t samples,
		OpusEncoder *encoder,
		const size_t bytes_per_frame,
		const unsigned int ts_per_frame,
		RtpSession *session)
{
	float *pcm;
	void *packet;
	ssize_t z;
	snd_pcm_sframes_t f;
	static unsigned int ts = 0;

	pcm = alloca(sizeof(float) * samples * channels);
	packet = alloca(bytes_per_frame);

	f = snd_pcm_readi(snd, pcm, samples);
	if (f < 0) {
		f = snd_pcm_recover(snd, f, 0);
		if (f < 0) {
			aerror("snd_pcm_readi", f);
			return -1;
		}
		return 0;
	}

	if (f < samples) {
		fprintf(stderr, "Short read, %ld\n", f);
		return 0;
	}

	z = opus_encode_float(encoder, pcm, samples, packet, bytes_per_frame);
	if (z < 0) {
		fprintf(stderr, "opus_encode_float: %s\n", opus_strerror(z));
		return -1;
	}

	rtp_session_send_with_ts(session, packet, z, ts);
	ts += ts_per_frame;

	return 0;
}

static int run_tx(snd_pcm_t *snd,
		const unsigned int channels,
		const snd_pcm_uframes_t frame,
		OpusEncoder *encoder,
		const size_t bytes_per_frame,
		const unsigned int ts_per_frame,
		RtpSession *session)
{
	for (;;) {
		int r;

		r = send_one_frame(snd, channels, frame,
				encoder, bytes_per_frame, ts_per_frame,
				session);
		if (r == -1)
			return -1;

		if (verbose > 1)
			fputc('>', stderr);
	}
}

int main(int argc, char *argv[])
{
	int r, error;
	size_t bytes_per_frame;
	unsigned int ts_per_frame;
	snd_pcm_t *snd;
	OpusEncoder *encoder;
	RtpSession *session;

	/* command-line options */
	const char *device = DEFAULT_DEVICE,
		*addr = DEFAULT_ADDR,
		*pid = NULL;
	unsigned int buffer = DEFAULT_BUFFER,
		rate = DEFAULT_RATE,
		channels = DEFAULT_CHANNELS,
		frame = DEFAULT_FRAME,
		kbps = DEFAULT_BITRATE,
		port = DEFAULT_PORT;

	fputs(COPYRIGHT "\n", stderr);

	for (;;) {
		int c;

		c = getopt(argc, argv, "b:c:d:f:h:m:p:r:v:D:");
		if (c == -1)
			break;

		switch (c) {
		case 'b':
			kbps = atoi(optarg);
			break;
		case 'c':
			channels = atoi(optarg);
			break;
		case 'd':
			device = optarg;
			break;
		case 'f':
			frame = atol(optarg);
			break;
		case 'h':
			addr = optarg;
			break;
		case 'm':
			buffer = atoi(optarg);
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 'r':
			rate = atoi(optarg);
			break;
		case 'v':
			verbose = atoi(optarg);
			break;
		case 'D':
			pid = optarg;
			break;
		default:
			usage(stderr);
			return -1;
		}
	}

	encoder = opus_encoder_create(rate, channels, OPUS_APPLICATION_AUDIO,
				&error);
	if (encoder == NULL) {
		fprintf(stderr, "opus_encoder_create: %s\n",
			opus_strerror(error));
		return -1;
	}

	bytes_per_frame = kbps * 1024 * frame / rate / 8;

	ts_per_frame = frame * 8000 / rate;

	ortp_init();
	ortp_scheduler_init();
	ortp_set_log_level_mask(ORTP_WARNING|ORTP_ERROR);
	session = create_rtp_send(addr, port);
	assert(session != NULL);

	r = snd_pcm_open(&snd, device, SND_PCM_STREAM_CAPTURE, 0);
	if (r < 0) {
		aerror("snd_pcm_open", r);
		return -1;
	}
	if (set_alsa_hw(snd, rate, channels, buffer * 1000) == -1)
		return -1;
	if (set_alsa_sw(snd) == -1)
		return -1;

	if (pid)
		go_daemon(pid);

	go_realtime();
	r = run_tx(snd, channels, frame, encoder, bytes_per_frame,
		ts_per_frame, session);

	if (snd_pcm_close(snd) < 0)
		abort();

	rtp_session_destroy(session);
	ortp_exit();
	ortp_global_stats_display();

	opus_encoder_destroy(encoder);

	return r;
}

这是接受音频数据的主函数:

#include <netdb.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <opus/opus.h>
#include <ortp/ortp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "defaults.h"
#include "device.h"
#include "notice.h"
#include "sched.h"
#include <stdio.h>
static unsigned int verbose = DEFAULT_VERBOSE;
FILE *pcm_file;
static void timestamp_jump(RtpSession *session, ...)
{
	if (verbose > 1)
		fputc('|', stderr);
	rtp_session_resync(session);
}

static RtpSession* create_rtp_recv(const char *addr_desc, const int port,
		unsigned int jitter)
{
	RtpSession *session;

	session = rtp_session_new(RTP_SESSION_RECVONLY);
	rtp_session_set_scheduling_mode(session, FALSE);
	rtp_session_set_blocking_mode(session, FALSE);
	rtp_session_set_local_addr(session, addr_desc, port, -1);
	rtp_session_set_connected_mode(session, FALSE);
	rtp_session_enable_adaptive_jitter_compensation(session, TRUE);
	rtp_session_set_jitter_compensation(session, jitter); // ms 
	rtp_session_set_time_jump_limit(session, jitter * 16); // ms 

	if (rtp_session_set_payload_type(session, 0) != 0)
		abort();
	if (rtp_session_signal_connect(session, "timestamp_jump",
					timestamp_jump, 0) != 0)
	{
		abort();
	}

	return session;
}

int play_one_frame(void *packet,
		size_t len,
		OpusDecoder *decoder,
		snd_pcm_t *snd,
		const unsigned int channels)
{
	int r;
	float *pcm;
	snd_pcm_sframes_t f, samples = 1920;

	pcm_file = fopen("/home/yf415/b.pcm", "wb");	
	pcm = alloca(sizeof(float) * samples * channels);

	if (packet == NULL) {
		r = opus_decode_float(decoder, NULL, 0, pcm, samples, 1);//会返回从每一组样本中解码的样本数(每个通道),返回负值时,发生错误
	} else {
		r = opus_decode_float(decoder, packet, len, pcm, samples, 0);//packet 包含压缩数据的字节数组,len为字节包的实际长度,decoded是解码数据
	}
	if (r < 0) {
		fprintf(stderr, "opus_decode: %s\n", opus_strerror(r));
		return -1;
	}

	f = snd_pcm_writei(snd, pcm, r);

	fwrite(pcm,sizeof(float),r,pcm_file);
	if (f < 0) {
		f = snd_pcm_recover(snd, f, 0);
		if (f < 0) {
			aerror("snd_pcm_writei", f);
			return -1;
		}
		return 0;
	}
	if (f < r)
		fprintf(stderr, "Short write %ld\n", f);
	fclose(pcm_file);
	return r;
}

static int run_rx(RtpSession *session,
		OpusDecoder *decoder,
		snd_pcm_t *snd,
		const unsigned int channels,
		const unsigned int rate)
{
//	char buf[32767];
	int ts = 0;

	for (;;) {
		int s,r, have_more;
		char buf[32767];
		void *packet;

		s = rtp_session_recv_with_ts(session, (uint8_t*)buf,
				sizeof(buf), ts, &have_more);
	
		assert(s >= 0);
		assert(have_more == 0);
		if (r == 0) {
			packet = NULL;
			if (verbose > 1)
				fputc('#', stderr);
//				printf("%d\n", r);
			    }
		 else {
			packet = buf;
			//printf ("***");
			if (verbose > 1)
				fputc('.', stderr);

			}	
		r = play_one_frame(packet, s, decoder, snd, channels);
		if (r == -1)
			return -1;

		/* Follow the RFC, payload 0 has 8kHz reference rate */

		ts += r * 8000 / rate;
	
}
}

static void usage(FILE *fd)
{
	fprintf(fd, "Usage: rx [<parameters>]\n"
		"Real-time audio receiver over IP\n");

	fprintf(fd, "\nAudio device (ALSA) parameters:\n");
	fprintf(fd, "  -d <dev>    Device name (default '%s')\n",
		DEFAULT_DEVICE);
	fprintf(fd, "  -m <ms>     Buffer time (default %d milliseconds)\n",
		DEFAULT_BUFFER);

	fprintf(fd, "\nNetwork parameters:\n");
	fprintf(fd, "  -h <addr>   IP address to listen on (default %s)\n",
		DEFAULT_ADDR);
	fprintf(fd, "  -p <port>   UDP port number (default %d)\n",
		DEFAULT_PORT);
	fprintf(fd, "  -j <ms>     Jitter buffer (default %d milliseconds)\n",
		DEFAULT_JITTER);

	fprintf(fd, "\nEncoding parameters (must match sender):\n");
	fprintf(fd, "  -r <rate>   Sample rate (default %dHz)\n",
		DEFAULT_RATE);
	fprintf(fd, "  -c <n>      Number of channels (default %d)\n",
		DEFAULT_CHANNELS);

	fprintf(fd, "\nProgram parameters:\n");
	fprintf(fd, "  -v <n>      Verbosity level (default %d)\n",
		DEFAULT_VERBOSE);
	fprintf(fd, "  -D <file>   Run as a daemon, writing process ID to the given file\n");
}

int main(int argc, char *argv[])
{
	int r, error;
	snd_pcm_t *snd;
	OpusDecoder *decoder;
	RtpSession *session;

	/* command-line options */
	const char *device = DEFAULT_DEVICE,
		*addr = DEFAULT_ADDR,
		*pid = NULL;
	unsigned int buffer = DEFAULT_BUFFER,
		rate = DEFAULT_RATE,
		jitter = DEFAULT_JITTER,
		channels = DEFAULT_CHANNELS,
		port = DEFAULT_PORT;

	fputs(COPYRIGHT "\n", stderr);

	for (;;) {
		int c;

		c = getopt(argc, argv, "c:d:h:j:m:p:r:v:");
		if (c == -1)
			break;
		switch (c) {
		case 'c':
			channels = atoi(optarg);
			break;
		case 'd':
			device = optarg;
			break;
		case 'h':
			addr = optarg;
			break;
		case 'j':
			jitter = atoi(optarg);
			break;
		case 'm':
			buffer = atoi(optarg);
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 'r':
			rate = atoi(optarg);
			break;
		case 'v':
			verbose = atoi(optarg);
			break;
		case 'D':
			pid = optarg;
			break;
		default:
			usage(stderr);
			return -1;
		}
	}

	decoder = opus_decoder_create(rate, channels, &error);
	if (decoder == NULL) {
		fprintf(stderr, "opus_decoder_create: %s\n",
			opus_strerror(error));
		return -1;
	}

	ortp_init();
	ortp_scheduler_init();
	session = create_rtp_recv(addr, port, jitter);
	assert(session != NULL);

	r = snd_pcm_open(&snd, device, SND_PCM_STREAM_PLAYBACK, 0);
	if (r < 0) {
		aerror("snd_pcm_open", r);
		return -1;
	}
	if (set_alsa_hw(snd, rate, channels, buffer * 1000) == -1)
		return -1;
	if (set_alsa_sw(snd) == -1)
		return -1;

	if (pid)
		go_daemon(pid);

	go_realtime();
	r = run_rx(session, decoder, snd, channels, rate);

	if (snd_pcm_close(snd) < 0)
		abort();

	rtp_session_destroy(session);
	ortp_exit();
	ortp_global_stats_display();

	opus_decoder_destroy(decoder);
	fclose(pcm_file);
	return r;
}

上面注释的过程都是调试的过程,由于发端和收端用到的都是同一套rtp协议和同一套音频编解码标准,因此我们主要以接受端作为解析案例分析。

接下来我们只介绍一些关于音频编解码和传输过程用到的一些函数:
OPUS支持的包间隔从20ms到120ms, 视频会议对实时性要求比较高, 所以我们采用的20ms, 16k采样率的ts递增值为960

解码器初始化:

decoder = opus_decoder_create(rate, channels, &error);
rate 是采样率;
channels是通道数;
error是失败状态下的错误代码好(成功则返回OPUS_OK)

紧接着如下:

session = create_rtp_recv(addr, port, jitter);

这个时候调用create_rtp_recv函数,该函数详细内容讲解如下:

session=rtp_session_new(RTP_SESSION_RECVONLY); 
rtp_session_set_scheduling_mode(session,1);  
//设置会话的调度模式。当第二个参数为真时,标示会话可以使用调度模式,例如:阻塞模式。
rtp_session_set_blocking_mode(session,1);
//当第二个参数为真,则使用能调度模式。直接影响函数rtp_session_recv_with_ts()与rtp_session_send_with_ts()的行为!
rtp_session_set_local_addr(session,"0.0.0.0",atoi(argv[2]),-1);
//设置本地网络地址,端口号。
rtp_session_set_connected_mode(session,TRUE);
//一个connect()在第一个数据包接收后将对源地址进行使用。
//连接一个socket会造成拒绝所有不是connect()里指定的地址发来的数据。
rtp_session_set_symmetric_rtp(session,TRUE);
//穿越防火墙
rtp_session_enable_adaptive_jitter_compensation(session,adapt);
//自适应补偿功能
rtp_session_set_jitter_compensation(session,jittcomp);
//设置补偿时间
rtp_session_set_payload_type(session,0);
//设置负载的类型。H.264的类型为:payload_type_h264

以下可以参考,但是本文未用到:
rtp_session_signal_connect(session,"ssrc_changed",(RtpCallback)ssrc_cb,0);
rtp_session_signal_connect(session,"ssrc_changed",(RtpCallback)rtp_session_reset,0);
//有指定的信号出现时,将调用执行函数。同一个信号可定义多个执行函数。
//第四个参数为执行函数的参数。
s = rtp_session_recv_with_ts(session, (uint8_t*)buf, sizeof(buf), ts, &have_more);
//have_more标识缓冲区是否还有数据没有接收。当用户缓冲区不够大,数据为读取完时,
//则标识have_more指向的数据为1,希望用户再次调用本函数。

这个解码器会分配到连续的内存块中,可以通过浅拷贝获取他(例如memcpy),在调用opus_decode()或者opus_decode_float()时必须是一个完整的音频数据

opus_decode(dec, packet, len, decoded, max_size, 0);
packet是包含压缩数据的字节数组
len为字节包的实际长度
decoded是在opus_int16解码音频数据(或opus_decode_float())
MAX_SIZE是decoded_frame数组大小
opus_decode()和opus_decode_float()会返回从每一组样本中解码的样本数(每个通道)。如果该值为负,则发生了错误。这可能发生,如果该分组被损坏或如果音频缓冲区太小不能保存完整的解码音频。

Opus是一个可复用的状态编码,并且作为结果的Opus数据包不能分开编码。数据包必须传递到解码器,并连续按顺序地进解码。丢失的数据包可以通过调用一个空指针和长度为零的数据包来替换。

在ros或者linux开发环境可能会用到创建进程来实现一些工程,但是解码器不能被多个线程同时调用。分离的流必须被单独的解码器进行解码,并且可以并行地解码。

下面给出移植到ros中的主函数:

#include "rx.h"

#include <netdb.h>
#include <string.h>
#include <alsa/asoundlib.h>
#include <opus/opus.h>
#include <ortp/ortp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include "defaults.h"
#include "device.h"
#include "notice.h"
#include "sched_trx.h"
#include ""
#include ""
#include <signal.h>
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <>

#include <rosjack/Audio.h>

FILE *pcm_file = NULL;

void sig_handler( int sig )
{
    exit(0); 
}

int float2char(float *pcm,char *pcm_char,int len){

	int ii = 0;
	for(ii = 0; ii <len; ii ++){
		*(pcm_char+ii) = (char)(*(pcm+ii));
	}

return 0;
}
static int run_rx(RtpSession *session,
		OpusDecoder *decoder,
		snd_pcm_t *snd,
		const unsigned int channels,
		const unsigned int rate,float *pcm,ros::Publisher Pub ,jack_msgs::JackAudio msg)
{
	int ts = 0;
	short in_shorts[1920];
	for (;;) 
	{
	int r;
	int have_more;
	char buf[32767];
	void *packet;
		r = rtp_session_recv_with_ts(session, (uint8_t *)buf,
                                sizeof(buf), ts, &have_more);
		assert(r >= 0);
		assert(have_more == 0);
		if (r == 0) {
			packet = NULL;
			if (0)//(verbose > 1)
				fputc('#', stderr);
		}
		else {
			
			packet = buf;
			if(0)//(verbose > 1)
				fputc('.', stderr);
		}
		r = play_one_frame(packet, r, decoder, snd, channels , pcm);

		if (r == -1)
			return -1;

		// Follow the RFC, payload 0 has 8kHz reference rate 

		ts += r * 8000 / rate;
		for(int ii = 0;ii<msg.size;ii++){
			msg.data.resize(msg.size);
			msg.data[ii] = pcm[ii] ;

		}
		for(int i  = 0; i <r; i++){
		in_shorts[i] = (short)(*(pcm+i)*65535);
		}
		Pub.publish(msg);
	}
}

int main(int argc, char *argv[])
{
	ros::init(argc, argv, "rx3");
	ros::NodeHandle n;
	ros::Rate loop_rate(1);
	ros::Publisher Pub = n.advertise<jack_msgs::JackAudio>("/jackaudio", 1000);
	signal( SIGINT, sig_handler );
	int r, error,verbose;
	snd_pcm_t *snd;
	OpusDecoder *decoder;
	RtpSession *session;
	float *pcm = NULL;
	pcm =  (float *)alloca(sizeof(float) * 1920 * 1);

	jack_msgs::JackAudio msg;
	msg.size = 960;
	const char *device = DEFAULT_DEVICE,
		*addr = DEFAULT_ADDR,
		*pid = NULL;
	unsigned int buffer = DEFAULT_BUFFER,
		rate = DEFAULT_RATE,
		jitter = DEFAULT_JITTER,
		channels = DEFAULT_CHANNELS,
		port = DEFAULT_PORT;
	fputs(COPYRIGHT "\n", stderr);
	channels = 1;
	buffer = 256;
	verbose = atoi("3");

	rate = 16000;	
	decoder = opus_decoder_create(rate, channels, &error);
	if (decoder == NULL) {
		fprintf(stderr, "opus_decoder_create: %s\n",
			opus_strerror(error));
		return -1;
	}

	ortp_init();
	ortp_scheduler_init();
	session = create_rtp_recv(addr, port, jitter);
	assert(session != NULL);
	if (r < 0) {
		aerror("snd_pcm_open", r);
		return -1;
	}
	if (pid)
		go_daemon(pid);

	go_realtime();

	r = run_rx(session, decoder, snd, channels, rate,pcm,Pub,msg);
		return r;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值