dahdi_tools 分析 (五) dahdi_monitor

dahdi_tools 分析 (五) dahdi_monitor

作用

用于录音,可以录指定通道的音频数据。

用法

# dahdi_monitor -h
Usage: dahdi_monitor <channel num> [-v[v]] [-m] [-o] [-l limit] [-f FILE | -s FILE | -r FILE1 -t FILE2] [-F FILE | -S FILE | -R FILE1 -T FILE2]
Options:
        -v: Visual mode.  Implies -m.
        -vv: Visual/Verbose mode.  Implies -m.
        -l LIMIT: Stop after reading LIMIT bytes
        -m: Separate rx/tx streams.
        -o: Output audio via OSS.  Note: Only 'normal' combined rx/tx streams are output via OSS.
        -f FILE: Save combined rx/tx stream to mono FILE. Cannot be used with -m.
        -r FILE: Save rx stream to FILE. Implies -m.
        -t FILE: Save tx stream to FILE. Implies -m.
        -s FILE: Save stereo rx/tx stream to FILE. Implies -m.
        -F FILE: Save combined pre-echocanceled rx/tx stream to FILE. Cannot be used with -m.
        -R FILE: Save pre-echocanceled rx stream to FILE. Implies -m.
        -T FILE: Save pre-echocanceled tx stream to FILE. Implies -m.
        -S FILE: Save pre-echocanceled stereo rx/tx stream to FILE. Implies -m.
Examples:
Save a stream to a file
        dahdi_monitor 1 -f stream.raw
Visualize an rx/tx stream and save them to separate files.
        dahdi_monitor 1 -v -r streamrx.raw -t streamtx.raw
Play a combined rx/tx stream via OSS and save it to a file
        dahdi_monitor 1 -o -f stream.raw
Save a combined normal rx/tx stream and a combined 'preecho' rx/tx stream to files
        dahdi_monitor 1 -f stream.raw -F streampreecho.raw
Save a normal rx/tx stream and a 'preecho' rx/tx stream to separate files
        dahdi_monitor 1 -m -r streamrx.raw -t streamtx.raw -R streampreechorx.raw -T streampreechotx.raw

代码分析

	case 'r':
			
			if ((ofh[MON_BRX] = fopen(optarg, "w")) == NULL) {    // 打开输出文件
				fprintf(stderr, "Could not open %s for writing: %s\n", optarg, strerror(errno));
				exit(EXIT_FAILURE);
			}
			fprintf(stderr, "Writing receive stream to %s\n", optarg);
			file_is_wav[MON_BRX] = filename_is_wav(optarg);      // 判断输出文件是否是.wav后缀
			if (file_is_wav[MON_BRX]) {
				wavheader_init(&wavheaders[MON_BRX], 1);         // 是wav 的话先写wav文件头,文件头里的长度信息最后改写
				if (fwrite(&wavheaders[MON_BRX], 1, sizeof(struct wavheader), ofh[MON_BRX]) != sizeof(struct wavheader)) {
					fprintf(stderr, "Could not write wav header to %s: %s\n", optarg, strerror(errno));
					exit(EXIT_FAILURE);
				}
			}
			multichannel = 1;
			savefile = 1;
			break;
	/* Open Pseudo device */
	if ((pfd[MON_BRX] = pseudo_open()) < 0)                    //  打开 /dev/dahdi/pseudo 
		exit(1);
	if (multichannel && ((pfd[MON_TX] = pseudo_open()) < 0))
		exit(1);
	if (preecho) {
		if ((pfd[MON_PRE_BRX] = pseudo_open()) < 0)
			exit(1);
		if (multichannel && ((pfd[MON_PRE_TX] = pseudo_open()) < 0))
			exit(1);
	}

首先打开 /dev/dahdi/pseudo 当同时指定 -R -r -T -t 参数时,打开多个。

	/* Conference them */
	if (multichannel) {
		memset(&zc, 0, sizeof(zc));
		zc.chan = 0;
		zc.confno = chan;
		/* Two pseudo's, one for tx, one for rx */
		zc.confmode = DAHDI_CONF_MONITOR;
		if (ioctl(pfd[MON_BRX], DAHDI_SETCONF, &zc) < 0) {                      //  DAHDI_SETCONF
			fprintf(stderr, "Unable to monitor: %s\n", strerror(errno));
			exit(1);
		}
		memset(&zc, 0, sizeof(zc));
		zc.chan = 0;
		zc.confno = chan;
		zc.confmode = DAHDI_CONF_MONITORTX;
		if (ioctl(pfd[MON_TX], DAHDI_SETCONF, &zc) < 0) {
			fprintf(stderr, "Unable to monitor: %s\n", strerror(errno));
			exit(1);
		}
		if (preecho) {
			memset(&zc, 0, sizeof(zc));
			zc.chan = 0;
			zc.confno = chan;
			/* Two pseudo's, one for tx, one for rx */
			zc.confmode = DAHDI_CONF_MONITOR_RX_PREECHO;
			if (ioctl(pfd[MON_PRE_BRX], DAHDI_SETCONF, &zc) < 0) {
				fprintf(stderr, "Unable to monitor: %s\n", strerror(errno));
				exit(1);
			}
			memset(&zc, 0, sizeof(zc));
			zc.chan = 0;
			zc.confno = chan;
			zc.confmode = DAHDI_CONF_MONITOR_TX_PREECHO;
			if (ioctl(pfd[MON_PRE_TX], DAHDI_SETCONF, &zc) < 0) {
				fprintf(stderr, "Unable to monitor: %s\n", strerror(errno));
				exit(1);
			}
		}
	}

对每一个 fd 执行 ioctl DAHDI_SETCONF ,设定监听模式。

	/* Now, copy from pseudo to audio */
	while (run) {
		res_brx = read(pfd[MON_BRX], buf_brx, sizeof(buf_brx));    // 从 pseudo 中读取数据
		if (res_brx < 1)
			break;
		readcount += res_brx;
		if (ofh[MON_BRX])
			bytes_written[MON_BRX] += fwrite(buf_brx, 1, res_brx, ofh[MON_BRX]); // 将数据写入文件中

		if (multichannel) {
			res_tx = read(pfd[MON_TX], buf_tx, res_brx);                    // 后面的都是类似操作
			if (res_tx < 1)
				break;
			if (ofh[MON_TX])
				bytes_written[MON_TX] += fwrite(buf_tx, 1, res_tx, ofh[MON_TX]);
			
			}
		}

		if (preecho) {
			res_brx = read(pfd[MON_PRE_BRX], buf_brx, sizeof(buf_brx));
			if (res_brx < 1)
				break;
			if (ofh[MON_PRE_BRX])
				bytes_written[MON_PRE_BRX] += fwrite(buf_brx, 1, res_brx, ofh[MON_PRE_BRX]);

			if (multichannel) {
				res_tx = read(pfd[MON_PRE_TX], buf_tx, res_brx);
				if (res_tx < 1)
					break;
				if (ofh[MON_PRE_TX])
					bytes_written[MON_PRE_TX] += fwrite(buf_tx, 1, res_tx, ofh[MON_PRE_TX]);
				
			}
		}		

		if (limit && readcount >= limit) {
			/* bail if we've read too much */
			break;
		}
	}

while (run) 里面一直从 fd 中读取数据,并直接写入到对应文件中。

	/* write filesize info */
	for (i = 0; i < MAX_OFH; i++) {
		if (NULL == ofh[i])
			continue;
		if (!(file_is_wav[i]))
			continue;

		wavheaders[i].riff_chunk_size = (bytes_written[i]) + sizeof(struct wavheader) - 8; /* filesize - 8 */
		wavheaders[i].data_data_size = bytes_written[i];

		rewind(ofh[i]);
		if (fwrite(&wavheaders[i], 1, sizeof(struct wavheader), ofh[i]) != sizeof(struct wavheader)) {
			fprintf(stderr, "Failed to write out a full wav header.\n");
		}
		fclose(ofh[i]);
	}

最后,如果是wav 格式,改写 文件头中的相关长度信息,再关闭文件。

思考

思考1.是如何做到从同一个文件 /dev/dahdi/pseudo 中读取不同数据流的?

这需要结合驱动层代码来理解:

static int dahdi_open(struct inode *inode, struct file *file)
{
	...
	if (unit == DAHDI_PSEUDO) {                // /dev/dahdi/pseudo
		mutex_lock(&registration_mutex);
		chan = dahdi_alloc_pseudo(file);
		mutex_unlock(&registration_mutex);
		if (unlikely(!chan))
			return -ENOMEM;
		return dahdi_specchan_open(file);
	}
}

可以看出,每打开一次 /dev/dahdi/pseudo , 都会 dahdi_alloc_pseudo ,

static struct dahdi_chan *dahdi_alloc_pseudo(struct file *file)
{
	struct pseudo_chan *pseudo;
	unsigned long flags;
	unsigned int channo;
	struct pseudo_chan *p;
	struct list_head *pos = &pseudo_chans;

	pseudo = kzalloc(sizeof(*pseudo), GFP_KERNEL);                 // 分配一个 struct pseudo_chan
	if (NULL == pseudo)
		return NULL;

	pseudo->chan.sig = DAHDI_SIG_CLEAR;
	pseudo->chan.sigcap = DAHDI_SIG_CLEAR;
	pseudo->chan.flags = DAHDI_FLAG_AUDIO;
	pseudo->chan.span = NULL; /* No span == psuedo channel */

	channo = FIRST_PSEUDO_CHANNEL;                              //  FIRST_PSEUDO_CHANNEL = 0x8000
	list_for_each_entry(p, &pseudo_chans, node) {
		if (channo != p->chan.channo)
			break;
		pos = &p->node; 
		++channo;                                              // 从 pseudo_chans 链表中查找可用的 channo
	}

	pseudo->chan.channo = channo;
	pseudo->chan.chanpos = channo - FIRST_PSEUDO_CHANNEL + 1;
	__dahdi_init_chan(&pseudo->chan);
	dahdi_chan_reg(&pseudo->chan);

	snprintf(pseudo->chan.name, sizeof(pseudo->chan.name)-1,
		 "Pseudo/%d", pseudo->chan.chanpos);

	file->private_data = &pseudo->chan;                                  // 绑定到 file->private_data

	/* Once we place the pseudo chan on the list...it's registered and
	 * live. */
	spin_lock_irqsave(&chan_lock, flags);
	++num_pseudo_channels;
	list_add(&pseudo->node, pos);                                       // 追加到 pseudo_chans 链表中, 
	spin_unlock_irqrestore(&chan_lock, flags);

	return &pseudo->chan;                                               // 返回 struct dahdi_chan*
}

所以每一次打开 /dev/dahdi/pseudo ,其 file->private_data 的数据是各自不同的。

static ssize_t dahdi_chan_read(struct file *file, char __user *usrbuf,
			       size_t count, loff_t *ppos)
{
	struct dahdi_chan *chan = file->private_data;           // 取到 file->private_data
	int amnt;
	int res, rv;
	int oldbuf,x;
	unsigned long flags;
	
	for (;;) {                                                // 等待有数据可以读
		spin_lock_irqsave(&chan->lock, flags);
		if (chan->eventinidx != chan->eventoutidx) {
			spin_unlock_irqrestore(&chan->lock, flags);
			return -ELAST /* - chan->eventbuf[chan->eventoutidx]*/;
		}
		res = chan->outreadbuf;
		spin_unlock_irqrestore(&chan->lock, flags);
		if (res >= 0)
			break;
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;

		/* Wake up when data is available or when the board driver
		 * unregistered the channel. */
		rv = wait_event_interruptible(chan->waitq,
			(!chan->file->private_data || chan->outreadbuf > -1));
		if (rv)
			return rv;		
	}                             
	amnt = count;
	if (chan->flags & DAHDI_FLAG_LINEAR) {
		if (amnt > (chan->readn[res] << 1))
			amnt = chan->readn[res] << 1;
		if (amnt) {
			/* There seems to be a max stack size, so we have
			   to do this in smaller pieces */
			short lindata[128];
			int left = amnt >> 1; /* amnt is in bytes */
			int pos = 0;
			int pass;
			while (left) {
				pass = left;
				if (pass > 128)
					pass = 128;
				for (x = 0; x < pass; x++)
					lindata[x] = DAHDI_XLAW(chan->readbuf[res][x + pos], chan);   // 从 chan->readbuf 中读取数据,并转换成liner格式
				if (copy_to_user(usrbuf + (pos << 1), lindata, pass << 1))
					return -EFAULT;
				left -= pass;
				pos += pass;
			}
		}
	} else {
		if (amnt > chan->readn[res])
			amnt = chan->readn[res];
		if (amnt) {
			if (copy_to_user(usrbuf, chan->readbuf[res], amnt))
				return -EFAULT;
		}
	}
	spin_lock_irqsave(&chan->lock, flags);
	chan->readidx[res] = 0;
	chan->readn[res] = 0;
	oldbuf = res;
	chan->outreadbuf = (res + 1) % chan->numbufs;
	if (chan->outreadbuf == chan->inreadbuf) {
		/* Out of stuff */
		chan->outreadbuf = -1;
	}
	if (chan->inreadbuf < 0) {
		/* Notify interrupt handler that we have some space now */
		chan->inreadbuf = oldbuf;
	}
	spin_unlock_irqrestore(&chan->lock, flags);

	return amnt;
}

思考2.虚拟通道和真实通道是如何建立关联的?

回顾前面,在 open /dev/dahdi/pseudo 之后,还做了 DAHDI_SETCONF

		zc.chan = 0;
		zc.confno = chan;		
		zc.confmode = DAHDI_CONF_MONITOR;
		if (ioctl(pfd[MON_BRX], DAHDI_SETCONF, &zc) < 0) { 

结合 驱动层代码:

static int dahdi_ioctl_setconf(struct file *file, unsigned long data)
{
	struct dahdi_confinfo conf;
	struct dahdi_chan *chan;
	struct dahdi_chan *conf_chan = NULL;
	
	if (copy_from_user(&conf, (void __user *)data, sizeof(conf)))
		return -EFAULT;
	chan = (conf.chan) ? chan_from_num(conf.chan) :
			     chan_from_file(file);                          // conf.chan = 0 , chan = 虚拟通道
	
    if ((DAHDI_CONF_DIGITALMON == confmode) ||
	    is_monitor_mode(conf.confmode)) {                       // 这里满足 is_monitor_mode
		conf_chan = chan_from_num(conf.confno);                 // conf_chan = 真实通道
		if (!conf_chan)
			return -EINVAL;
        
    oldconf = chan->confna;  /* save old conference number */
	chan->confna = conf.confno;   /* set conference number */
	chan->conf_chan = conf_chan;                                // 在这里关联了真实通道
	chan->confmode = conf.confmode;  /* set conference mode */
	chan->_confn = 0;		     /* Clear confn */
        
     
}

数据的关联:

在dahdhi-base 中有一个每 1ms 执行一次的函数 coretimer_func ,里面会遍历 pseudo_chans 链表执行,从真实通道中拷贝数据

coretimer_func -> _process_masterspan

static void _process_masterspan(void)
{
	/* do all the pseudo and/or conferenced channel receives (getbuf's) */
	list_for_each_entry(pseudo, &pseudo_chans, node) {
		spin_lock(&pseudo->chan.lock);
		__dahdi_transmit_chunk(&pseudo->chan, NULL);
		spin_unlock(&pseudo->chan.lock);
	}
    
    /* do all the pseudo/conferenced channel transmits (putbuf's) */
	list_for_each_entry(pseudo, &pseudo_chans, node) {
		pseudo_rx_audio(&pseudo->chan);
	}

}

pseudo_rx_audio ->__pseudo_rx_audio ->__dahdi_receive_chunk-> __dahdi_process_putaudio_chunk

static inline void __dahdi_process_putaudio_chunk(struct dahdi_chan *ss, unsigned char *rxb)
{
    ...
	if ((!ms->confmute && !ms->afterdialingtimer) || is_pseudo_chan(ms)) {  // 如果是虚拟通道
		struct dahdi_chan *const conf_chan = ms->conf_chan;                 // 从 ms->conf_chan 取出真实通道
		switch(ms->confmode & DAHDI_CONF_MODE_MASK) {
		
		case DAHDI_CONF_MONITOR:		/* Monitor a channel's rx mode */
			  /* if not a pseudo-channel, ignore */
			if (!is_pseudo_chan(ms))
				break;
			/* Add monitored channel */
			if (is_pseudo_chan(conf_chan))
				ACSS(putlin, conf_chan->getlin);
			else
				ACSS(putlin, conf_chan->putlin);                       // conf_chan->putlin 是"Last received raw data" 最后接收的数据 
			/* Convert back */
			for(x=0;x<DAHDI_CHUNKSIZE;x++)
				rxb[x] = DAHDI_LIN2X(putlin[x], ms);                   // 这里又将数据转回 xlaw 格式,为了匹配接口
			break;		
	...		
}

数据结构梳理:

                    struct                              真实的                           
                    dahdi_chan                         struct
                    +------------+                     dahdi_chan
                    | conf_chan -|------------------> +-----------+
                    | readbuf    |                    |           |
struct              |            |                    |           |
pseudo_chan      o->+------------+                    |  putlin   |
+-----------+    |                                    +-----------+
|           |    |
| chan     -|----o
| node     -|<---o
|           |    |                     
+-----------+    |  pseudo_chans list
                 |   +----------+
                 o<--|- node 1  |
                     |----------|
                     |  node 2  |
                     |----------|
                     |          |
                     +----------+

你可能感兴趣的:(VOIP,asterisk,dahdi_tools,dahdi_monitor,chan_dahdi)