学习笔记(一)与Asterisk之间的PCM数据交互"/>
DAHDI 驱动学习笔记(一)与Asterisk之间的PCM数据交互
Q1. 多个si3050 模块,共用PCM 数据线,数据是怎么组装的?
在PCM 中各通道的数据组装后格式如下:
chn1 | chn2 | chn3 | chn4 | … | chn8 |
---|
那模块是怎么知道自己数据要放到PCM的那个位置的呢?
找到相应代码如下:
数据手册相应的寄存器作用如下:
该寄存器设置了从FSYNC 信号后的第几个PCLK开始传输/接收数据,由此它知道了它的数据是要在PCM 中的哪个位置。
Q2. DAHDI 驱动中的PCM数据是如何与asterisk进行交换的?
也就是,asterisk 中是如何读写 DAHDI的PCM数据的?
asterisk 代码
load_moduleres = setup_dahdi(0);res = setup_dahdi_int(reload, &default_conf, &base_conf, &conf);process_dahdi(...) build_channels(confp, v->value, reload, v->lineno)tmp = mkintf(x, conf, reload);snprintf(fn, sizeof(fn), "%d", channel);/* Open non-blocking */tmp->subs[SUB_REAL].dfd = dahdi_open(fn); // opendahdi_writemy_dahdi_writefd = p->subs[idx].dfd;res = write(fd, buf, size); // writedahdi_readres = read(p->subs[idx].dfd, readbuf, p->subs[idx].linear ? READ_SIZE * 2 : READ_SIZE);// read
static int dahdi_open(char *fn)
{int fd;int isnum;int chan = 0;int bs;int x;isnum = 1;for (x = 0; x < strlen(fn); x++) {if (!isdigit(fn[x])) {isnum = 0;break;}}if (isnum) {chan = atoi(fn);if (chan < 1) {ast_log(LOG_WARNING, "Invalid channel number '%s'\n", fn);return -1;}fn = "/dev/dahdi/channel";}fd = open(fn, O_RDWR | O_NONBLOCK);if (fd < 0) {ast_log(LOG_WARNING, "Unable to open '%s': %s\n", fn, strerror(errno));return -1;}if (chan) {if (ioctl(fd, DAHDI_SPECIFY, &chan)) {x = errno;close(fd);errno = x;ast_log(LOG_WARNING, "Unable to specify channel %d: %s\n", chan, strerror(errno));return -1;}}bs = READ_SIZE;if (ioctl(fd, DAHDI_SET_BLOCKSIZE, &bs) == -1) {ast_log(LOG_WARNING, "Unable to set blocksize '%d': %s\n", bs, strerror(errno));x = errno;close(fd);errno = x;return -1;}return fd;
}
从代码中看出是通过open “/dev/dahdi/channel” 文件,然后
- ioctl(fd, DAHDI_SPECIFY, &chan) 指定打开哪个通道,这里DAHDI_SPECIFY很关键
- ioctl(fd, DAHDI_SET_BLOCKSIZE, &bs) 设置Block 大小 160B ,即20ms的采样(8K采样率,8B /ms)
dahdi 代码
static int dahdi_open(struct inode *inode, struct file *file) //openif (unit == DAHDI_CHANNEL)return dahdi_chan_open(file); // dahdi_chan_open 什么也不做,直接返回0 dahdi_ioctl //ioctldahdi_unlocked_ioctlif (unit == DAHDI_CHANNEL) {if (file->private_data)ret = dahdi_chan_ioctl(file, cmd, data); //DAHDI_SPECIFY 之后从这进入elseret = dahdi_prechan_ioctl(file, cmd, data); //DAHDI_SPECIFY 时调用,很关键goto exit;}
static int dahdi_prechan_ioctl(struct file *file, unsigned int cmd, unsigned long data)
{int channo;int res;if (file->private_data) {module_printk(KERN_NOTICE, "Huh? Prechan already has private data??\n");}switch(cmd) {case DAHDI_SPECIFY: // 只响应 DAHDI_SPECIFY get_user(channo, (int __user *)data);file->private_data = chan_from_num(channo);if (!file->private_data)return -EINVAL;res = dahdi_specchan_open(file);if (res)file->private_data = NULL;return res;default:return -ENOSYS;}return 0;
}
static int dahdi_specchan_open(struct file *file)const struct dahdi_span_ops *const ops =(!is_pseudo_chan(chan)) ? chan->span->ops : NULL;file->f_op = &dahdi_chan_fops; /*重定向f_op ,dahdi_chan_fops 重新定义了 open read writeioctl 等函数 下次开始read write 这些系统调用实际会使用dahdi_chan_fops里的函数 */if (ops && ops->open) {res = ops->open(chan); // 这里调用 chan->span->ops-open 函数,即 /*dahdi_register_device 调用之前会指定chan->span->ops-open 函数,参见wctdm.c 的wctdm_span_ops*/
dahdi_chan_fops 定义如下
static const struct file_operations dahdi_chan_fops = {.owner = THIS_MODULE,.open = dahdi_open,.release = dahdi_release,
#ifdef HAVE_UNLOCKED_IOCTL.unlocked_ioctl = dahdi_unlocked_ioctl,
#ifdef HAVE_COMPAT_IOCTLpat_ioctl = dahdi_ioctl_compat,
#endif
#else.ioctl = dahdi_ioctl,
#endif.read = dahdi_chan_read,.write = dahdi_chan_write,.poll = dahdi_chan_poll,
};
先看 dahdi_chan_write
dahdi_chan_writeres = chan->inwritebuf;copy_from_user(chan->writebuf[res], usrbuf, amnt); // 把数据写入了chan->writebuf[res]chan->writen[res] = amnt;chan->writeidx[res] = 0;oldbuf = res;chan->inwritebuf = (res + 1) % chan->numbufs;if (chan->inwritebuf == chan->outwritebuf) {/* Don't stomp on the transmitter, just wait for them towake us up */chan->inwritebuf = -1;/* Make sure the transmitter is transmitting in case of POLICY_WHEN_FULL */chan->txdisable = 0;}if (chan->outwritebuf < 0) {/* Okay, the interrupt handler has been waiting for us. Give them a buffer */chan->outwritebuf = oldbuf;
}
到现在可以看出,asterisk 里 write 的数据最终会写入到 chan->writebuf[res] 中,那么肯定会有地方从chan->writebuf[res] 中取出数据,在sourceinsight 中搜索一下:
搜到一个地方:__dahdi_getbuf_chunk 函数,提取关键代码
来看一下:
static inline void __dahdi_getbuf_chunk(struct dahdi_chan *ss, unsigned char *txb)int bytes = DAHDI_CHUNKSIZE; // 8 while(bytes){if ((ms->outwritebuf > -1) && !ms->txdisable){buf= ms->writebuf[ms->outwritebuf];left = ms->writen[ms->outwritebuf] - ms->writeidx[ms->outwritebuf];if (left > bytes)left = bytes;memcpy(txb, buf + ms->writeidx[ms->outwritebuf], left);ms->writeidx[ms->outwritebuf]+=left;txb += left;bytes -= left;}....}
__dahdi_getbuf_chunk 函数主要作用是,从ms->writebuf[ms->outwritebuf] 中读取8个字节的数据
来看一下 __dahdi_getbuf_chunk 的调用过程
static inline int dahdi_transmit(struct dahdi_span *span)ret = _dahdi_transmit(span);__dahdi_real_transmit(chan);__dahdi_transmit_chunk(chan, chan->writechunk);__dahdi_transmit_chunk(chan, chan->writechunk); // 数据最终写到chan->writechunk中__dahdi_getbuf_chunk(chan, buf);
dahdi_transmit 由驱动子模块的中断处理函数调用,参见wctdm.c
chan->writechunk 是指向 chan->swritechunk 的指针:
chan->swritechunk 是一个大小为8字节的数组。
到此千辛万苦把数据写入到了 chan->writechunk ,但是还没完,数据的最终目的是要传给si3050模块。也就是一定有地方把chan->writechunk 中的数据取出。
类似伪代码如下
for(i=0;i<8;i++)
{for(chn = 0;chn < 8;chn++){buf[chn*8+i] = wc->chans[chn]->writechunk[i]}
}
// 然后将buf 写入pcm 总线。
总结:asterisk 写pcm 数据到 dahdi 驱动过程
- fd = dahdi_open(“n”) //n为通道号
- write(fd,buf ,buflen) ,最终将buf 写入chan->writebuf[res]
- 子模块驱动的中断响应函数调用 dahdi_transmit ,会将chan->writebuf[res] 中的数据读到 chan->writechunk
- 最后子模块驱动将chan->writechunk 的数据取出组装成自定义PCM格式,写入PCM总线
- PCM 总线的另一端,如si3050,根据预先设定的偏移值,取出PCM数据中的指定位置数据
再来看read过程,
可以猜到 read 过程其实就是write 过程的反向。这里同样还是从应用层asterisk出发来跟踪一下数据的来龙去脉。
前面已经知道write 会调用 dahdi_chan_write,
同样,read 其实是调用 dahdi_chan_read
来看一下 dahdi_chan_read
dahdi_chan_read res = chan->outreadbuf;if (copy_to_user(usrbuf, chan->readbuf[res], amnt)) // 从chan->readbuf 读取数据return -EFAULT;chan->readidx[res] = 0;chan->readn[res] = 0;oldbuf = res;chan->outreadbuf = (res + 1) % chan->numbufs;
找到 __putbuf_chunk 函数中对chan->readbuf[res] 的处理
static void __putbuf_chunk(struct dahdi_chan *ss, unsigned char *rxb, int bytes)while(bytes) { if (ms->inreadbuf > -1){/* Read into the current buffer */buf = ms->readbuf[ms->inreadbuf];left = ms->blocksize - ms->readidx[ms->inreadbuf];if (left > bytes)left = bytes;memcpy(buf + ms->readidx[ms->inreadbuf], rxb, left); //将 rxb 中数据读入 ms->readbufrxb += left;ms->readidx[ms->inreadbuf] += left;bytes -= left;}
__putbuf_chunk 的调用关系:
dahdi_receiveret = _dahdi_receive(span);__dahdi_real_receive(chan);__dahdi_receive_chunk(chan, chan->readchunk); // 将 chan->readchunk 中的数据 读入chan->readbuf__dahdi_putbuf_chunk(chan, buf);__putbuf_chunk(ss, rxb, DAHDI_CHUNKSIZE);
同样,dahdi_receive 由子模块驱动的中断处理函数调用。
chan->readchunk 中的数据由子模块填充,一般就是读取PCM中的数据,取出对应通道的数据进行填充。
伪代码如下:
for(i=0;i<8;i++)
{for(chn = 0;chn < 8;chn++){wc->chans[chn]->readchunk[i] = buf[chn*8+i];}
}
dahdi_receive(&wc->span);
总结:asterisk 从chan_dahdi 读 pcm 数据过程
- 数据来源:PCM总线,如接的si3050
- dahdi 子驱动,中断程序读取PCM 数据并解析格式再写入到 wc->chans[chn]->readchunk
- 子驱动中断程序调用 dahdi_receive ,会将chans->readchunk 的数据写入chan->readbuf
- asterisk 调用 read 函数,其实最终是调用dahdi_chan_read ,将chan->readbuf 的数据copy到用户空间
更多推荐
DAHDI 驱动学习笔记(一)与Asterisk之间的PCM数据交互
发布评论