用于录音,可以录指定通道的音频数据。
# 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(®istration_mutex);
chan = dahdi_alloc_pseudo(file);
mutex_unlock(®istration_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 |
|----------|
| |
+----------+