在Linux上软件实现3G Modem数字语音传输的方法(2010/06/21)
-------------------------------------------------------
Author:FergusZeng Email:cblock@126.com QQ:57004294
-------------------------------------------------------
前段时间完成了个项目,其中有个难题困扰了我很久。
项目中要求使用TD Modem语音链路完成通话录音和播放功能,
由于该款Modem只提供USB接口,AT指令和数据业务及PCM语音通
道都是通过USB虚拟出的串口来完成数据的传输。理论上这几项
功能都比较容易实现。但是我们所使用的嵌入式系统是Linux,
对于像语音传输这种实时性要求较强的应用处理起来比较棘手
。
首先是因为整个应用是多线程的大中型程序,系统要处理的
任务很多很繁重,而且很多任务对时间是有要求的。实时性在
Linux下本身就是个瓶颈,除非你用uClinux。同时还受ARM性能
的限制,总不可能为了这个买个ARM11来跑吧,太浪费了...
当然了,如果Modem本身能提供硬件的PCM语音接口的话就相当
方便了,只要写个驱动就OK,采用ALSA架构的驱动对于这种简单
的接口来说并不复杂,而且应用上可以直接移植ALSA库来实现。
底层采用DMA完成数据的传输,一般的语音数据吞吐量实时性能
是完全能够保证的。这个方法我已在另一款带有硬件接口的Modem
上实现。
唯一的途径还是得从应用层软件上着手,同时改善内核的实时性能,
使其满足PCM数据传输的时间要求。目前系统采用的Linux时钟节拍
为100,即进程切换是以1/100秒(10ms)为单位。而8K采样率,16位的
PCM语音帧频率是20ms,即每20ms就要发送一帧数据。这样算来程序
只有在三个线程的情况下才能基本满足实时性要求。对于要求更高的
精度,只有调整时钟节拍,在嵌入式下我们通常采用100HZ的频率,
这样才能保证系统的可靠性。我采用的方法的调整时钟节拍为1000。
这样线程切换就被调整到1ms了,如果线程不超过20个的话,基本上
能满足语音传输的实时性要求。但是这样却给系统带了更承重的负担
,它必须花更多的时间在线程之间的切换上面。而且这样调整后很可
能会影响到系统的稳定性。经过一段时间测试后,目前看来系统还算
很稳定,没出现什么大问题。
在具体程序的实现上,对USB串口的读写采用了非阻塞的方式。一开
始我是直接在open()函数中设置NON_BLOCK属性,但是发现这样设置根
本无效,这个问题至今还不清楚在哪。于是我很自然地想到了select
这个函数,对设备读写设置超时时间,这样问题也就迎刃而解了。
下面简单介绍下select的用法(Google里搜的范例)。
-----------------------------------------------------
#include
#include
#include
定义函数 int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct
timeval * timeout);
函数说明 select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds
、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供
了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
参数 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值 如果参数timeout设为NULL则表示select()没有timeout。
错误代码 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过
timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,
exceptfds和timeout的值变成不可预测。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
范例 常见的程序片段:
-------------------------------------
fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)
{……}
------------------------------------
下面是linux环境下select的一个简单用法
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
#i nclude
int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1)
{
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd))
{
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("hehethe input is %c\n",c);
if ('q'==c)
break;
}
}
}
阅读(1501) | 评论(1) | 转发(0) |