ftmod_misdn: More reworking of b-channel audio tx handling.

Use the amount of audio data received in misdn_read() to determine how many
bytes we need to send to the b-channel (= how much free space is left
in the b-channel tx queue). (This is how libosmo-abis and LCR handle it too.)

A pipe is used as a poll()-able audio tx buffer (filled in misdn_write()):
FTDM_WRITE wait requests are currently poll()-ed on the input side of the pipe,
whereas FTDM_READ and _EVENT requests are poll()-ed on the b-channel socket itself.

For every N-bytes of audio data read from the b-channel in misdn_read(),
we try to get as much out of the tx pipe, convert it into the ISDN_P_B_RAW
format and send it to the b-channel socket.

If there's less than N-bytes left in the pipe, we fill the remaining buffer
with silence to avoid buffer underflows.

B-Channel handling overview:

  - misdn_wait(FTDM_WRITE) on audio pipe

  - misdn_write() put audio data into pipe

  - misdn_wait(FTDM_READ) for next incoming mISDN
    message on b-channel socket

  - misdn_read() handle mISDN event, for PH_DATA_IND:

      - Write data into channel buffer and convert
        to a/u-law using misdn_convert_audio_bits()

      - Try to fetch N-bytes from audio pipe

      - If not enough bytes in pipe: fill remaining space with silence

      - Convert audio to raw format

      - Send to b-channel (PH_DATA_REQ)

Known problems / bugs / further investigation:

   1. Bridge aborted by "Write Buffer 0 bytes Failed!" error from switch_core_io.c.
      This is "fixed" by _not_ setting the b-channel sockfd to non-blocking mode.

   2. Audio glitches (maybe caused by FTDM_WRITE misdn_wait() handling or blocking I/O on sockfd?)

   3. misdn_read() EBUSY error messages from sending data to b-channel sockfd after enabling channel.

Signed-off-by: Stefan Knoblich <stkn@openisdn.net>
This commit is contained in:
Stefan Knoblich 2012-03-28 23:09:42 +02:00
parent d5e7e8491c
commit a856d81a06

View File

@ -172,6 +172,63 @@ static const char *misdn_control2str(const int ctrl)
}
#endif
/***********************************************************************************
* mISDN <-> FreeTDM audio conversion
***********************************************************************************/
/*
* Code used to generate table values taken from
* Linux Call Router (LCR) http://www.linux-call-router.de/
*
* chan_lcr.c:3488 ff., load_module()
*/
static const unsigned char conv_audio_tbl[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};
/* Convert ISDN_P_B_RAW audio data to/from a-/u-law */
static inline void misdn_convert_audio_bits(char *buf, int buflen)
{
int i;
for (i = 0; i < buflen; i++) {
buf[i] = conv_audio_tbl[(unsigned char)buf[i]];
}
}
/***********************************************************************************
* mISDN <-> FreeTDM data structures
***********************************************************************************/
@ -205,6 +262,10 @@ struct misdn_chan_private {
/* hw addr of channel */
struct sockaddr_mISDN addr;
/* audio tx pipe */
int audio_pipe_in;
int audio_pipe_out;
/* counters */
unsigned long tx_cnt;
unsigned long tx_ack_cnt;
@ -1035,7 +1096,7 @@ static FIO_COMMAND_FUNCTION(misdn_command)
*/
static FIO_WAIT_FUNCTION(misdn_wait)
{
// struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
struct misdn_chan_private *chan_priv = ftdm_chan_io_private(ftdmchan);
struct pollfd pfds[2];
int nr_fds = 0;
int retval;
@ -1044,18 +1105,9 @@ static FIO_WAIT_FUNCTION(misdn_wait)
switch (ftdm_channel_get_type(ftdmchan)) {
case FTDM_CHAN_TYPE_B:
if (*flags & FTDM_READ)
pfds[0].events |= POLLIN;
if (*flags & FTDM_WRITE)
pfds[0].events |= /*POLLOUT |*/ POLLIN; /* NOTE: no write-poll support on mISDN b-channels */
if (*flags & FTDM_EVENTS)
pfds[0].events |= POLLPRI;
pfds[0].fd = ftdmchan->sockfd;
nr_fds++;
#if 0
if (*flags & FTDM_WRITE) {
pfds[nr_fds].fd = chan_priv->timerfd;
pfds[nr_fds].events = POLLIN;
pfds[nr_fds].fd = chan_priv->audio_pipe_in;
pfds[nr_fds].events = POLLOUT;
nr_fds++;
}
if (*flags & (FTDM_READ | FTDM_EVENTS)) {
@ -1064,7 +1116,6 @@ static FIO_WAIT_FUNCTION(misdn_wait)
pfds[nr_fds].events |= (*flags & FTDM_EVENTS) ? POLLPRI : 0;
nr_fds++;
}
#endif
break;
default:
if (*flags & FTDM_READ)
@ -1080,8 +1131,7 @@ static FIO_WAIT_FUNCTION(misdn_wait)
*flags = FTDM_NO_FLAGS;
// if (!(pfds[0].events || pfds[1].events))
if (!pfds[0].events) {
if (!(pfds[0].events || pfds[1].events)) {
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "mISDN poll(): no flags set!\n");
return FTDM_SUCCESS;
}
@ -1096,27 +1146,13 @@ static FIO_WAIT_FUNCTION(misdn_wait)
switch (ftdm_channel_get_type(ftdmchan)) {
case FTDM_CHAN_TYPE_B:
if (pfds[0].revents & POLLIN)
*flags |= FTDM_READ | FTDM_WRITE; /* NOTE: */
if (pfds[0].revents & POLLOUT)
*flags |= FTDM_WRITE;
if (pfds[0].revents & POLLPRI)
if ((pfds[0].revents & POLLIN) || (pfds[1].revents & POLLIN))
*flags |= FTDM_READ;
if ((pfds[0].revents & POLLPRI) || (pfds[1].revents & POLLPRI))
*flags |= FTDM_EVENTS;
break;
#if 0
if (pfds[0].fd == chan_priv->timerfd) {
if (pfds[0].revents & POLLIN) {
uint64_t tmp = 0; /* clear pending events on timerfd */
retval = read(pfds[0].fd, &tmp, sizeof(tmp));
*flags |= FTDM_WRITE;
}
if (pfds[1].revents & POLLIN)
*flags |= FTDM_READ;
if (pfds[1].revents & POLLPRI)
*flags |= FTDM_EVENTS;
break;
}
#endif
default:
if (pfds[0].revents & POLLIN)
*flags |= FTDM_READ;
@ -1252,7 +1288,6 @@ static FIO_READ_FUNCTION(misdn_read)
if (hh->prim == PH_DATA_IND) {
*datalen = CLAMP(retval - MISDN_HEADER_LEN, 0, bytes);
memcpy(data, rbuf + MISDN_HEADER_LEN, *datalen);
#ifdef MISDN_DEBUG_IO
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "misdn_read() received '%s', id: %#x, with %d bytes from channel socket %d [dev.ch: %d.%d]\n",
misdn_event2str(hh->prim), hh->id, retval - MISDN_HEADER_LEN, ftdmchan->sockfd, addr.dev, addr.channel);
@ -1263,6 +1298,68 @@ static FIO_READ_FUNCTION(misdn_read)
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN read data: %s\n", hbuf);
}
#endif
if (*datalen <= 0)
continue;
/*
* Copy data into ouput buffer (excluding the mISDN message header)
* NOTE: audio data needs to be converted to a-law / u-law!
*/
memcpy(data, rbuf + MISDN_HEADER_LEN, *datalen);
switch (ftdm_channel_get_type(ftdmchan)) {
case FTDM_CHAN_TYPE_B:
hh->prim = PH_DATA_REQ;
hh->id = MISDN_ID_ANY;
bytes = *datalen;
/* Convert incoming audio data to *-law */
misdn_convert_audio_bits(data, *datalen);
/*
* Fetch required amount of audio from tx pipe, using the amount
* of received bytes as an indicator for how much free space the
* b-channel tx buffer has available.
*
* (see misdn_write() for the part that fills the tx pipe)
*
* NOTE: can't use blocking I/O here since both parts are serviced
* from the same thread
*/
if ((retval = read(priv->audio_pipe_out, rbuf + MISDN_HEADER_LEN, bytes)) < 0) {
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN failed to read %d bytes of audio data: %s\n",
bytes, strerror(errno));
break;
}
/* Tx pipe is empty, completely fill buffer up to "bytes" with silence value */
retval = 0;
}
/*
* Use a-law / u-law silence to fill missing bytes,
* in case there was not enough audio data available in the
* tx pipe to satisfy the request.
*/
if (retval < bytes) {
memset(&rbuf[MISDN_HEADER_LEN + retval],
(ftdm_channel_get_codec(ftdmchan) == FTDM_CODEC_ALAW) ? 0x2a : 0xff,
bytes - retval);
}
/* Convert outgoing audio data to wire format */
misdn_convert_audio_bits(rbuf + MISDN_HEADER_LEN, bytes);
bytes += MISDN_HEADER_LEN;
/* Send converted audio to b-channel */
if ((retval = sendto(ftdmchan->sockfd, rbuf, bytes, 0, (struct sockaddr *)&priv->addr, sizeof(priv->addr))) < bytes) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN failed to send %d bytes of audio data: (%d) %s\n",
bytes, retval, strerror(errno));
}
break;
default:
break;
}
return FTDM_SUCCESS;
} else {
*datalen = 0;
@ -1309,34 +1406,56 @@ static FIO_WRITE_FUNCTION(misdn_write)
ftdm_log(FTDM_LOG_DEBUG, "mISDN write data: %s\n", hbuf);
}
#endif
hh->prim = PH_DATA_REQ;
hh->id = MISDN_ID_ANY;
*datalen = 0;
/* avoid buffer overflow */
size = MIN(size, MAX_DATA_MEM - MISDN_HEADER_LEN);
switch (ftdm_channel_get_type(ftdmchan)) {
case FTDM_CHAN_TYPE_B:
/*
* Write to audio pipe, misdn_read() will pull
* from there as needed and send it to the b-channel
*
* NOTE: can't use blocking I/O here since both parts are serviced
* from the same thread
*/
if ((retval = write(priv->audio_pipe_in, data, size)) < size) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN channel audio pipe write error: %s\n",
strerror(errno));
return FTDM_FAIL;
}
*datalen = retval;
break;
default:
hh->prim = PH_DATA_REQ;
hh->id = MISDN_ID_ANY;
memcpy(wbuf + MISDN_HEADER_LEN, data, size);
size += MISDN_HEADER_LEN;
/* Avoid buffer overflow */
size = MIN(size, MAX_DATA_MEM - MISDN_HEADER_LEN);
/* wait for channel to get ready */
wflags = FTDM_WRITE;
retval = misdn_wait(ftdmchan, &wflags, 20);
if (retval) {
/* timeout, io error */
*datalen = 0;
return FTDM_FAIL;
}
memcpy(wbuf + MISDN_HEADER_LEN, data, size);
size += MISDN_HEADER_LEN;
/* wait for channel to get ready */
wflags = FTDM_WRITE;
retval = misdn_wait(ftdmchan, &wflags, 20);
if (retval) {
/* timeout, io error */
*datalen = 0;
return FTDM_FAIL;
}
#ifdef MISDN_DEBUG_IO
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN writing %d bytes to channel socket %d [dev.ch: %d.%d]\n",
size, ftdmchan->sockfd, priv->addr.dev, priv->addr.channel);
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "mISDN writing %d bytes to channel socket %d [dev.ch: %d.%d]\n",
size, ftdmchan->sockfd, priv->addr.dev, priv->addr.channel);
#endif
if ((retval = sendto(ftdmchan->sockfd, wbuf, size, 0, NULL, 0)) != size) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN channel socket write error: %s\n",
strerror(errno));
return FTDM_FAIL;
if ((retval = sendto(ftdmchan->sockfd, wbuf, size, 0, NULL, 0)) < size) {
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "mISDN channel socket write error: %s\n",
strerror(errno));
return FTDM_FAIL;
}
*datalen = retval;
break;
}
*datalen = retval;
priv->tx_cnt++;
return FTDM_SUCCESS;
@ -1463,11 +1582,27 @@ static ftdm_status_t misdn_open_range(ftdm_span_t *span, ftdm_chan_type_t type,
ftdmchan->physical_chan_id = x;
if (ftdmchan->type == FTDM_CHAN_TYPE_B) {
int pipefd[2] = { -1, -1 };
ftdmchan->packet_len = 10 /* ms */ * (ftdmchan->rate / 1000);
ftdmchan->effective_interval = ftdmchan->native_interval = ftdmchan->packet_len / 8;
ftdmchan->native_codec = ftdmchan->effective_codec = FTDM_CODEC_ALAW;
ftdm_channel_set_feature(ftdmchan, FTDM_CHANNEL_FEATURE_INTERVAL);
/*
* Create audio tx pipe, use non-blocking I/O to avoid deadlock since both ends
* are used from the same thread
*/
if (pipe2(pipefd, O_NONBLOCK) < 0) {
ftdm_log(FTDM_LOG_ERROR, "Failed to create mISDN audio write pipe [%d:%d]: %s\n",
addr.dev, x, strerror(errno));
close(sockfd);
return FTDM_FAIL;
}
priv->audio_pipe_in = pipefd[1];
priv->audio_pipe_out = pipefd[0];
} else {
/* early activate D-Channel */
misdn_activate_channel(ftdmchan, 1);