DAHDI 驱动学习笔记(一)与Asterisk之间的PCM数据交互

编程入门 行业动态 更新时间:2024-10-09 08:37:36

DAHDI 驱动<a href=https://www.elefans.com/category/jswz/34/1770117.html style=学习笔记(一)与Asterisk之间的PCM数据交互"/>

DAHDI 驱动学习笔记(一)与Asterisk之间的PCM数据交互

Q1. 多个si3050 模块,共用PCM 数据线,数据是怎么组装的?

在PCM 中各通道的数据组装后格式如下:

chn1chn2chn3chn4chn8

那模块是怎么知道自己数据要放到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数据交互

本文发布于:2024-03-12 02:19:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1730469.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:学习笔记   数据   DAHDI   PCM   Asterisk

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!