mirror of https://github.com/sipwise/rtpengine.git
Change-Id: I4552e07eee632fa730745410b08c3bf70ab67ab2changes/83/38283/37
parent
468fca8ead
commit
d872802ba2
@ -0,0 +1,726 @@
|
||||
#include "t38.h"
|
||||
|
||||
|
||||
|
||||
#ifdef WITH_TRANSCODING
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
#include <spandsp/t30.h>
|
||||
#include "codec.h"
|
||||
#include "call.h"
|
||||
#include "log.h"
|
||||
#include "str.h"
|
||||
#include "media_player.h"
|
||||
|
||||
|
||||
|
||||
struct udptl_packet {
|
||||
seq_packet_t p;
|
||||
str *s;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void __add_udptl_len(GString *s, const void *buf, unsigned int len) {
|
||||
if (len < 0x80) {
|
||||
g_string_append_c(s, len);
|
||||
if (len)
|
||||
g_string_append_len(s, buf, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 0x4000) {
|
||||
uint16_t enc_len = htons(0x8000 | len);
|
||||
g_string_append_len(s, (void *) &enc_len, 2);
|
||||
g_string_append_len(s, buf, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// fragmented - we don't support more than 65535 bytes
|
||||
unsigned int mult = len >> 14;
|
||||
g_string_append_c(s, 0xc0 | mult);
|
||||
mult <<= 14;
|
||||
// one portion
|
||||
g_string_append_len(s, buf, mult);
|
||||
// remainder - may be zero length
|
||||
__add_udptl_len(s, buf + mult, len - mult);
|
||||
}
|
||||
|
||||
static void __add_udptl_raw(GString *s, const char *buf, size_t len) {
|
||||
assert(len < 0x10000);
|
||||
|
||||
if (len == 0) {
|
||||
// add a single zero byte, length 1
|
||||
__add_udptl_len(s, "\x00", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
__add_udptl_len(s, buf, len);
|
||||
}
|
||||
|
||||
static void __add_udptl(GString *s, const str *buf) {
|
||||
__add_udptl_raw(s, buf->s, buf->len);
|
||||
}
|
||||
|
||||
|
||||
static void g_string_null_extend(GString *s, size_t len) {
|
||||
if (s->len >= len)
|
||||
return;
|
||||
|
||||
size_t oldb = s->len;
|
||||
size_t newb = len - s->len;
|
||||
g_string_set_size(s, len);
|
||||
memset(s->str + oldb, 0, newb);
|
||||
}
|
||||
|
||||
// call is locked in R or W
|
||||
static int t38_gateway_handler(t38_core_state_t *stat, void *user_data, const uint8_t *b, int len, int count) {
|
||||
struct t38_gateway *tg = user_data;
|
||||
|
||||
// cap the max length of packet we can handle
|
||||
if (len < 0 || len >= 0x10000) {
|
||||
ilog(LOG_ERR, "Received %i bytes from T.38 encoder - discarding", len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ilog(LOG_DEBUG, "Received %i bytes from T.38 encoder", len);
|
||||
|
||||
// build udptl packet: use a conservative guess for required buffer
|
||||
GString *s = g_string_sized_new(512);
|
||||
|
||||
// add seqnum
|
||||
uint16_t seq = htons(tg->seqnum);
|
||||
g_string_append_len(s, (void *) &seq, 2);
|
||||
|
||||
// add primary IFP packet
|
||||
str buf = STR_CONST_INIT_LEN((char *) b, len);
|
||||
__add_udptl(s, &buf);
|
||||
|
||||
// add error correction packets
|
||||
if (tg->options.fec_span > 1) {
|
||||
// forward error correction
|
||||
g_string_append_c(s, 0x80);
|
||||
|
||||
// figure out how many packets we have and which span to use
|
||||
unsigned int packets = tg->options.fec_span * tg->options.max_ec_entries;
|
||||
if (packets > tg->udptl_ec_out.length)
|
||||
packets = tg->udptl_ec_out.length;
|
||||
unsigned int span = packets / tg->options.max_ec_entries;
|
||||
if (!span)
|
||||
span = 1;
|
||||
packets = span * tg->options.max_ec_entries; // our own packets we use
|
||||
unsigned int entries = packets / span; // FEC entries in the output
|
||||
if (entries > tg->udptl_ec_out.length)
|
||||
entries = tg->udptl_ec_out.length;
|
||||
packets = entries * span;
|
||||
|
||||
assert(span < 0x80);
|
||||
assert(entries < 0x80);
|
||||
|
||||
g_string_append_c(s, 0x01);
|
||||
g_string_append_c(s, span);
|
||||
|
||||
// create needed number of FEC packet entries
|
||||
GQueue fec = G_QUEUE_INIT;
|
||||
for (int i = 0; i < entries; i++)
|
||||
g_queue_push_tail(&fec, g_string_new(""));
|
||||
|
||||
// take each input packet, going backwards in time, and XOR it into
|
||||
// the respective output FEC packet
|
||||
GList *inp = tg->udptl_ec_out.head;
|
||||
for (int i = 0; i < packets; i++) {
|
||||
assert(inp != NULL);
|
||||
str *ip = inp->data;
|
||||
// just keep shifting the list around
|
||||
GString *outp = g_queue_pop_head(&fec);
|
||||
|
||||
// extend string as needed
|
||||
g_string_null_extend(outp, ip->len);
|
||||
|
||||
for (size_t j = 0; j < ip->len; j++)
|
||||
outp->str[j] ^= ip->s[j];
|
||||
|
||||
g_queue_push_tail(&fec, outp);
|
||||
inp = inp->next;
|
||||
}
|
||||
|
||||
// output list is now complete, but in reverse. append it to output buffer
|
||||
GString *ec = g_string_sized_new(512);
|
||||
entries = 0;
|
||||
int going = 1;
|
||||
while (fec.length) {
|
||||
GString *outp = g_queue_pop_tail(&fec);
|
||||
if (going) {
|
||||
if (s->len + ec->len + outp->len > tg->options.max_datagram)
|
||||
going = 0;
|
||||
else {
|
||||
__add_udptl_raw(ec, outp->str, outp->len);
|
||||
entries++;
|
||||
}
|
||||
}
|
||||
g_string_free(outp, TRUE);
|
||||
}
|
||||
|
||||
g_string_append_c(s, entries);
|
||||
g_string_append_len(s, ec->str, ec->len);
|
||||
g_string_free(ec, TRUE);
|
||||
}
|
||||
else {
|
||||
// redundancy error correction
|
||||
g_string_append_c(s, 0x00);
|
||||
|
||||
GString *ec = g_string_sized_new(512);
|
||||
int entries = 0;
|
||||
|
||||
for (GList *l = tg->udptl_ec_out.head; l; l = l->next) {
|
||||
str *ec_s = l->data;
|
||||
// stop when we exceed max datagram length
|
||||
if (s->len + ec->len + ec_s->len > tg->options.max_datagram)
|
||||
break;
|
||||
// add redundancy packet
|
||||
__add_udptl(ec, ec_s);
|
||||
entries++;
|
||||
}
|
||||
|
||||
// number of entries - must be <0x80 as verified in settings
|
||||
g_string_append_c(s, entries);
|
||||
g_string_append_len(s, ec->str, ec->len);
|
||||
g_string_free(ec, TRUE);
|
||||
}
|
||||
|
||||
// done building our packet - add primary to our error correction buffer
|
||||
tg->seqnum++;
|
||||
unsigned int q_entries = tg->options.max_ec_entries * tg->options.fec_span;
|
||||
if (q_entries) {
|
||||
while (tg->udptl_ec_out.length >= q_entries) {
|
||||
str *ec_s = g_queue_pop_tail(&tg->udptl_ec_out);
|
||||
free(ec_s);
|
||||
}
|
||||
g_queue_push_head(&tg->udptl_ec_out, str_dup(&buf));
|
||||
}
|
||||
|
||||
// send our packet if we can
|
||||
struct packet_stream *ps = NULL;
|
||||
if (tg->t38_media && tg->t38_media->streams.head)
|
||||
ps = tg->t38_media->streams.head->data;
|
||||
if (ps)
|
||||
mutex_lock(&ps->out_lock);
|
||||
struct stream_fd *sfd = NULL;
|
||||
if (ps)
|
||||
sfd = ps->selected_sfd;
|
||||
if (sfd) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
ilog(LOG_DEBUG, "Sending %u UDPTL bytes", (unsigned int) s->len);
|
||||
socket_sendto(&sfd->socket, s->str, s->len, &ps->endpoint);
|
||||
}
|
||||
}
|
||||
else
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unable to send T.38 UDPTL packet due to lack of "
|
||||
"socket or stream");
|
||||
if (ps)
|
||||
mutex_unlock(&ps->out_lock);
|
||||
|
||||
g_string_free(s, TRUE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __t38_gateway_free(void *p) {
|
||||
struct t38_gateway *tg = p;
|
||||
ilog(LOG_DEBUG, "Destroying T.38 gateway");
|
||||
if (tg->gw)
|
||||
t38_gateway_free(tg->gw);
|
||||
if (tg->pcm_player) {
|
||||
media_player_stop(tg->pcm_player);
|
||||
media_player_put(&tg->pcm_player);
|
||||
}
|
||||
if (tg->udptl_fec)
|
||||
g_hash_table_destroy(tg->udptl_fec);
|
||||
g_queue_clear_full(&tg->udptl_ec_out, free);
|
||||
packet_sequencer_destroy(&tg->sequencer);
|
||||
}
|
||||
|
||||
// call is locked in R and mp is locked
|
||||
static void t38_pcm_player(struct media_player *mp) {
|
||||
if (!mp || !mp->media)
|
||||
return;
|
||||
|
||||
struct t38_gateway *tg = mp->media->t38_gateway;
|
||||
if (!tg)
|
||||
return;
|
||||
|
||||
ilog(LOG_DEBUG, "Generating T.38 PCM samples");
|
||||
|
||||
mutex_lock(&tg->lock);
|
||||
|
||||
int16_t smp[80];
|
||||
int num = t38_gateway_tx(tg->gw, smp, 80);
|
||||
if (num <= 0) {
|
||||
// use a fixed interval of 10 ms
|
||||
timeval_add_usec(&mp->next_run, 10000);
|
||||
timerthread_obj_schedule_abs(&mp->tt_obj, &mp->next_run);
|
||||
mutex_unlock(&tg->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
ilog(LOG_DEBUG, "Generated %i T.38 PCM samples", num);
|
||||
|
||||
// this reschedules our player as well
|
||||
media_player_add_packet(tg->pcm_player, (char *) smp, num * 2, num * 1000000 / 8000, tg->pts);
|
||||
|
||||
tg->pts += num;
|
||||
|
||||
mutex_unlock(&tg->lock);
|
||||
}
|
||||
|
||||
|
||||
static void __udptl_packet_free(struct udptl_packet *p) {
|
||||
if (p->s)
|
||||
free(p->s);
|
||||
g_slice_free1(sizeof(*p), p);
|
||||
}
|
||||
|
||||
|
||||
static void __t38_options_normalise(struct t38_options *opts) {
|
||||
if (opts->version < 0)
|
||||
opts->version = 0;
|
||||
if (opts->fec_span < 1)
|
||||
opts->fec_span = 1;
|
||||
if (opts->min_ec_entries < 0)
|
||||
opts->min_ec_entries = 0;
|
||||
if (opts->min_ec_entries >= 0x80)
|
||||
opts->min_ec_entries = 0x7f;
|
||||
if (opts->max_ec_entries < 0)
|
||||
opts->max_ec_entries = 0;
|
||||
if (opts->max_ec_entries >= 0x80)
|
||||
opts->max_ec_entries = 0x7f;
|
||||
if (opts->max_ifp <= 0 || opts->max_ifp >= 0x4000)
|
||||
opts->max_ifp = 0x3fff;
|
||||
if (opts->max_datagram <= 0 || opts->max_datagram >= 0x4000)
|
||||
opts->max_datagram = 0x3fff;
|
||||
}
|
||||
|
||||
// call is locked in W
|
||||
int t38_gateway_pair(struct call_media *t38_media, struct call_media *pcm_media,
|
||||
const struct t38_options *options)
|
||||
{
|
||||
const char *err = NULL;
|
||||
|
||||
if (!t38_media || !pcm_media || !options)
|
||||
return -1;
|
||||
|
||||
struct t38_options opts = *options;
|
||||
__t38_options_normalise(&opts);
|
||||
|
||||
// do we have one yet?
|
||||
if (t38_media->t38_gateway
|
||||
&& t38_media->t38_gateway == pcm_media->t38_gateway)
|
||||
{
|
||||
// XXX check options here?
|
||||
return 0;
|
||||
}
|
||||
|
||||
// release old structs, if any
|
||||
t38_gateway_put(&t38_media->t38_gateway);
|
||||
t38_gateway_put(&pcm_media->t38_gateway);
|
||||
|
||||
ilog(LOG_DEBUG, "Creating new T.38 gateway");
|
||||
|
||||
// create and init new
|
||||
struct t38_gateway *tg = obj_alloc0("t38_gateway", sizeof(*tg), __t38_gateway_free);
|
||||
|
||||
tg->t38_media = t38_media;
|
||||
tg->pcm_media = pcm_media;
|
||||
mutex_init(&tg->lock);
|
||||
tg->udptl_fec = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
|
||||
(GDestroyNotify) __udptl_packet_free);
|
||||
tg->options = opts;
|
||||
|
||||
tg->pcm_pt.payload_type = -1;
|
||||
str_init(&tg->pcm_pt.encoding, "PCM-S16LE");
|
||||
tg->pcm_pt.encoding_with_params = tg->pcm_pt.encoding;
|
||||
tg->pcm_pt.clock_rate = 8000;
|
||||
tg->pcm_pt.channels = 1;
|
||||
|
||||
err = "Failed to init PCM codec";
|
||||
ensure_codec_def(&tg->pcm_pt, pcm_media);
|
||||
if (!tg->pcm_pt.codec_def)
|
||||
goto err;
|
||||
|
||||
err = "Failed to create spandsp T.38 gateway";
|
||||
if (!(tg->gw = t38_gateway_init(NULL, t38_gateway_handler, tg)))
|
||||
goto err;
|
||||
|
||||
err = "Failed to create media player";
|
||||
if (!(tg->pcm_player = media_player_new(pcm_media->monologue)))
|
||||
goto err;
|
||||
// even though we call media_player_set_media() here, we need to call it again in
|
||||
// t38_gateway_start because our sink might not have any streams added here yet,
|
||||
// leaving the media_player setup incomplete
|
||||
media_player_set_media(tg->pcm_player, pcm_media);
|
||||
tg->pcm_player->run_func = t38_pcm_player;
|
||||
|
||||
// set options
|
||||
t38_core_state_t *t38 = t38_gateway_get_t38_core_state(tg->gw);
|
||||
t38_gateway_set_ecm_capability(tg->gw, TRUE);
|
||||
t38_gateway_set_transmit_on_idle(tg->gw, TRUE);
|
||||
t38_gateway_set_supported_modems(tg->gw, T30_SUPPORT_V17 | T30_SUPPORT_V27TER | T30_SUPPORT_V29
|
||||
| T30_SUPPORT_V34HDX | T30_SUPPORT_IAF);
|
||||
t38_set_t38_version(t38, opts.version);
|
||||
t38_set_data_rate_management_method(t38,
|
||||
opts.local_tcf ? 1 : 2);
|
||||
t38_set_fill_bit_removal(t38, opts.fill_bit_removal);
|
||||
t38_set_mmr_transcoding(t38, opts.transcoding_mmr);
|
||||
t38_set_jbig_transcoding(t38, opts.transcoding_jbig);
|
||||
t38_set_max_datagram_size(t38, opts.max_ifp);
|
||||
|
||||
packet_sequencer_init(&tg->sequencer, (GDestroyNotify) __udptl_packet_free);
|
||||
tg->sequencer.seq = 0;
|
||||
|
||||
// done - add references to media structs
|
||||
t38_media->t38_gateway = tg;
|
||||
pcm_media->t38_gateway = obj_get(tg);
|
||||
|
||||
// add SDP options for T38
|
||||
g_queue_clear_full(&t38_media->sdp_attributes, free);
|
||||
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxVersion:%i", tg->options.version));
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38MaxBitRate:14400"));
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxRateManagement:%s",
|
||||
tg->options.local_tcf ? "localTFC" : "transferredTCF"));
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxMaxBuffer:1800"));
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxMaxDatagram:512"));
|
||||
|
||||
if (tg->options.max_ec_entries == 0)
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxUdpEC:t38UDPNoEC"));
|
||||
else if (tg->options.fec_span > 1)
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxUdpEC:t38UDPFEC"));
|
||||
else
|
||||
g_queue_push_tail(&t38_media->sdp_attributes, str_sprintf("T38FaxUdpEC:t38UDPRedundancy"));
|
||||
// XXX more options possible here
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (err)
|
||||
ilog(LOG_ERR, "Failed to create T.38 gateway: %s", err);
|
||||
t38_gateway_put(&tg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// call is locked in W
|
||||
void t38_gateway_start(struct t38_gateway *tg) {
|
||||
if (!tg)
|
||||
return;
|
||||
|
||||
// set up our player first
|
||||
media_player_set_media(tg->pcm_player, tg->pcm_media);
|
||||
if (media_player_setup(tg->pcm_player, &tg->pcm_pt))
|
||||
return;
|
||||
|
||||
// now start our player if we can or should
|
||||
// already running?
|
||||
if (tg->pcm_player->next_run.tv_sec)
|
||||
return;
|
||||
|
||||
// only start our player only if we can send both ways
|
||||
if (!tg->pcm_media->codecs_prefs_send.length)
|
||||
return;
|
||||
if (!tg->pcm_media->streams.length)
|
||||
return;
|
||||
if (!tg->t38_media->streams.length)
|
||||
return;
|
||||
|
||||
struct packet_stream *ps;
|
||||
ps = tg->pcm_media->streams.head->data;
|
||||
if (!PS_ISSET(ps, FILLED))
|
||||
return;
|
||||
ps = tg->t38_media->streams.head->data;
|
||||
if (!PS_ISSET(ps, FILLED))
|
||||
return;
|
||||
|
||||
ilog(LOG_DEBUG, "Starting T.38 PCM player");
|
||||
|
||||
// start off PCM player
|
||||
tg->pcm_player->next_run = rtpe_now;
|
||||
timerthread_obj_schedule_abs(&tg->pcm_player->tt_obj, &tg->pcm_player->next_run);
|
||||
}
|
||||
|
||||
|
||||
// call is locked in R
|
||||
int t38_gateway_input_samples(struct t38_gateway *tg, int16_t amp[], int len) {
|
||||
if (!tg)
|
||||
return 0;
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
ilog(LOG_DEBUG, "Adding %i samples to T.38 encoder", len);
|
||||
|
||||
mutex_lock(&tg->lock);
|
||||
|
||||
int left = t38_gateway_rx(tg->gw, amp, len);
|
||||
if (left)
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "%i PCM samples were not processed by the T.38 gateway",
|
||||
left);
|
||||
|
||||
mutex_unlock(&tg->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t __get_udptl_len(str *s) {
|
||||
ssize_t ret;
|
||||
|
||||
if (s->len < 1)
|
||||
return -1;
|
||||
|
||||
if (!(s->s[0] & 0x80)) {
|
||||
ret = s->s[0];
|
||||
str_shift(s, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s->len < 2)
|
||||
return -1;
|
||||
|
||||
if (!(s->s[0] & 0x40)) {
|
||||
ret = ntohs(*((uint16_t *) s->s)) & 0x3fff;
|
||||
str_shift(s, 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ilog(LOG_INFO | LOG_FLAG_LIMIT, "Decoding UDPTL fragments is not supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int __get_udptl(str *piece, str *s) {
|
||||
ssize_t len = __get_udptl_len(s);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
|
||||
return str_shift_ret(s, len, piece);
|
||||
}
|
||||
|
||||
|
||||
static struct udptl_packet *__make_udptl_packet(const str *piece, uint16_t seq) {
|
||||
struct udptl_packet *up = g_slice_alloc0(sizeof(*up));
|
||||
up->p.seq = seq;
|
||||
up->s = str_dup(piece);
|
||||
return up;
|
||||
}
|
||||
|
||||
static void __fec_save(struct t38_gateway *tg, const str *piece, uint16_t seq) {
|
||||
struct udptl_packet *up = __make_udptl_packet(piece, seq);
|
||||
g_hash_table_insert(tg->udptl_fec, GUINT_TO_POINTER(seq), up);
|
||||
}
|
||||
|
||||
int t38_gateway_input_udptl(struct t38_gateway *tg, const str *buf) {
|
||||
const char *err = NULL;
|
||||
|
||||
if (!tg)
|
||||
return 0;
|
||||
if (!buf || !buf->len)
|
||||
return 0;
|
||||
|
||||
if (buf->len < 4) {
|
||||
ilog(LOG_INFO | LOG_FLAG_LIMIT, "Ignoring short UDPTL packet (%i bytes)", buf->len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ilog(LOG_DEBUG, "Processing %i UDPTL bytes", buf->len);
|
||||
|
||||
str s = *buf;
|
||||
str piece;
|
||||
|
||||
// get seq num
|
||||
uint16_t seq;
|
||||
if (str_shift_ret(&s, 2, &piece))
|
||||
goto err;
|
||||
seq = ntohs(*((uint16_t *) piece.s));
|
||||
|
||||
err = "Invalid primary UDPTL packet";
|
||||
if (__get_udptl(&piece, &s))
|
||||
goto err;
|
||||
|
||||
ilog(LOG_DEBUG, "Received primary IFP packet, len %i, seq %i", piece.len, seq);
|
||||
str primary = piece;
|
||||
struct udptl_packet *up = __make_udptl_packet(&primary, seq);
|
||||
|
||||
err = "Error correction mode byte missing";
|
||||
if (str_shift_ret(&s, 1, &piece))
|
||||
goto err;
|
||||
char fec = piece.s[0];
|
||||
|
||||
mutex_lock(&tg->lock);
|
||||
|
||||
// XXX possible short path here without going through the sequencer
|
||||
int ret = packet_sequencer_insert(&tg->sequencer, &up->p);
|
||||
if (ret < 0) {
|
||||
// main seq is dupe - everything else must be dupe too
|
||||
__udptl_packet_free(up);
|
||||
goto out;
|
||||
}
|
||||
|
||||
up = NULL;
|
||||
|
||||
if (!(fec & 0x80)) {
|
||||
// packet redundancy
|
||||
if (packet_sequencer_next_ok(&tg->sequencer))
|
||||
goto seq_ok;
|
||||
|
||||
// process EC packets as well as something's wrong
|
||||
ssize_t num_packets = __get_udptl_len(&s);
|
||||
err = "Invalid number of EC packets";
|
||||
if (num_packets < 0 || num_packets > 100)
|
||||
goto err;
|
||||
for (int i = 0; i < num_packets; i++) {
|
||||
if (__get_udptl(&piece, &s)) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT,
|
||||
"Invalid UDPTL error correction packet at index %i",
|
||||
i);
|
||||
break;
|
||||
}
|
||||
// ignore zero-length packets
|
||||
if (!piece.len)
|
||||
continue;
|
||||
ilog(LOG_DEBUG, "Received secondary IFP packet, len %i, seq %i", piece.len,
|
||||
seq - 1 - i);
|
||||
up = __make_udptl_packet(&piece, seq - 1 - i);
|
||||
packet_sequencer_insert(&tg->sequencer, &up->p);
|
||||
up = NULL;
|
||||
|
||||
// can we stop here?
|
||||
if (packet_sequencer_next_ok(&tg->sequencer))
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// FEC
|
||||
// start by saving the new packet
|
||||
__fec_save(tg, &primary, seq);
|
||||
|
||||
if (packet_sequencer_next_ok(&tg->sequencer))
|
||||
goto seq_ok;
|
||||
|
||||
// process all FEC packets
|
||||
err = "Invalid number of FEC packets";
|
||||
if (str_shift_ret(&s, 2, &piece))
|
||||
goto err;
|
||||
if (piece.s[0] != 0x01)
|
||||
goto err;
|
||||
unsigned int span = piece.s[1];
|
||||
if (span <= 0 || span >= 0x80)
|
||||
goto err;
|
||||
ssize_t entries = __get_udptl_len(&s);
|
||||
if (entries < 0 || entries > 100)
|
||||
goto err;
|
||||
|
||||
// first seq we can possibly recover
|
||||
uint16_t seq_start = seq - span * entries;
|
||||
|
||||
while (entries) {
|
||||
// get our entry
|
||||
if (__get_udptl(&piece, &s)) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT,
|
||||
"Invalid UDPTL error correction packet at index %i",
|
||||
seq_start);
|
||||
break;
|
||||
}
|
||||
// check each of the entries covered by `span`
|
||||
for (int i = 0; i < span; i++) {
|
||||
uint16_t seq_fec = seq_start + i * span;
|
||||
// skip if we already know this packet
|
||||
if (g_hash_table_lookup(tg->udptl_fec, GUINT_TO_POINTER(seq_fec)))
|
||||
continue;
|
||||
|
||||
// can we recover it? we need all other packets from the series
|
||||
GString *rec_s = g_string_new("");
|
||||
int complete = 1;
|
||||
|
||||
for (int j = 0; j < span; j++) {
|
||||
uint16_t seq_rec = seq_start + i * span;
|
||||
if (seq_rec == seq_fec)
|
||||
continue;
|
||||
struct udptl_packet *recp =
|
||||
g_hash_table_lookup(tg->udptl_fec, GUINT_TO_POINTER(seq_rec));
|
||||
if (!recp) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unable to recover UDPTL FEC "
|
||||
"packet with seq %i due to missing seq %i",
|
||||
seq_fec, seq_rec);
|
||||
complete = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// XOR in packet
|
||||
for (size_t j = 0; j < recp->s->len; j++)
|
||||
rec_s->str[j] ^= recp->s->s[j];
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
ilog(LOG_WARN | LOG_FLAG_LIMIT, "Recovered UDPTL "
|
||||
"packet with seq %i from FEC",
|
||||
seq_fec);
|
||||
|
||||
str rec_str = STR_CONST_INIT_LEN(rec_s->str, rec_s->len);
|
||||
__fec_save(tg, &rec_str, seq_fec);
|
||||
up = __make_udptl_packet(&rec_str, seq_fec);
|
||||
packet_sequencer_insert(&tg->sequencer, &up->p);
|
||||
up = NULL;
|
||||
}
|
||||
|
||||
g_string_free(rec_s, TRUE);
|
||||
|
||||
// no point in continuing further: one packet was missing, which means
|
||||
// that no other packet in this span can be recovered
|
||||
break;
|
||||
}
|
||||
|
||||
// proceed to next entry
|
||||
entries--;
|
||||
seq_start++;
|
||||
}
|
||||
}
|
||||
|
||||
seq_ok:;
|
||||
|
||||
t38_core_state_t *t38 = t38_gateway_get_t38_core_state(tg->gw);
|
||||
|
||||
// process any packets that we can
|
||||
while (1) {
|
||||
up = packet_sequencer_next_packet(&tg->sequencer);
|
||||
if (!up)
|
||||
break;
|
||||
|
||||
ilog(LOG_DEBUG, "Processing %i IFP bytes, seq %i", up->s->len, up->p.seq);
|
||||
|
||||
t38_core_rx_ifp_packet(t38, (uint8_t *) up->s->s, up->s->len, up->p.seq);
|
||||
|
||||
__udptl_packet_free(up);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&tg->lock);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (err)
|
||||
ilog(LOG_ERR | LOG_FLAG_LIMIT, "Failed to process UDPTL/T.38/IFP packet: %s", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void t38_gateway_stop(struct t38_gateway *tg) {
|
||||
if (!tg)
|
||||
return;
|
||||
if (tg->pcm_player)
|
||||
media_player_stop(tg->pcm_player);
|
||||
if (tg->t38_media)
|
||||
g_queue_clear_full(&tg->t38_media->sdp_attributes, free);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,97 @@
|
||||
#ifndef _T38_H_
|
||||
#define _T38_H_
|
||||
|
||||
|
||||
struct t38_gateway;
|
||||
|
||||
struct t38_options {
|
||||
int version;
|
||||
int fec_span; // 1 means no FEC
|
||||
int min_ec_entries; // currently ignored
|
||||
int max_ec_entries;
|
||||
int max_ifp;
|
||||
int max_datagram;
|
||||
|
||||
int local_tcf:1;
|
||||
int fill_bit_removal:1;
|
||||
int transcoding_mmr:1;
|
||||
int transcoding_jbig:1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#ifdef WITH_TRANSCODING
|
||||
|
||||
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
#include <spandsp/telephony.h>
|
||||
#include <spandsp/logging.h>
|
||||
#include <spandsp/t38_core.h>
|
||||
#include <spandsp/t38_gateway.h>
|
||||
|
||||
#include "rtplib.h"
|
||||
#include "aux.h"
|
||||
#include "obj.h"
|
||||
#include "codeclib.h"
|
||||
|
||||
|
||||
|
||||
struct call_media;
|
||||
struct media_packet;
|
||||
struct media_player;
|
||||
|
||||
|
||||
struct t38_gateway {
|
||||
struct obj obj; // use refcount as this struct is shared between two medias
|
||||
mutex_t lock;
|
||||
struct call_media *t38_media;
|
||||
struct call_media *pcm_media;
|
||||
struct rtp_payload_type pcm_pt; // PCM input for spandsp
|
||||
t38_gateway_state_t *gw;
|
||||
|
||||
struct t38_options options;
|
||||
|
||||
// udptl outgoing stuff
|
||||
uint16_t seqnum;
|
||||
GQueue udptl_ec_out; // seq, seq-1, seq-2, ...
|
||||
// udptl incoming stuff
|
||||
packet_sequencer_t sequencer;
|
||||
GHashTable *udptl_fec;
|
||||
|
||||
// player for PCM data
|
||||
struct media_player *pcm_player;
|
||||
unsigned long long pts;
|
||||
};
|
||||
|
||||
|
||||
|
||||
int t38_gateway_pair(struct call_media *t38_media, struct call_media *pcm_media, const struct t38_options *);
|
||||
void t38_gateway_start(struct t38_gateway *);
|
||||
int t38_gateway_input_samples(struct t38_gateway *, int16_t amp[], int len);
|
||||
int t38_gateway_input_udptl(struct t38_gateway *, const str *);
|
||||
void t38_gateway_stop(struct t38_gateway *);
|
||||
|
||||
|
||||
INLINE void t38_gateway_put(struct t38_gateway **tp) {
|
||||
if (!tp || !*tp)
|
||||
return;
|
||||
obj_put(*tp);
|
||||
*tp = NULL;
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
// stubs
|
||||
INLINE void t38_gateway_start(struct t38_gateway *tg) { }
|
||||
INLINE void t38_gateway_stop(struct t38_gateway *tg) { }
|
||||
INLINE void t38_gateway_put(struct t38_gateway **tp) { }
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,686 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use NGCP::Rtpengine::Test;
|
||||
use NGCP::Rtpclient::SRTP;
|
||||
use NGCP::Rtpengine::AutoTest;
|
||||
use Test::More;
|
||||
use IPC::Open3;
|
||||
|
||||
|
||||
autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1
|
||||
-n 2223 -c 12345 -f -L 7 -E -u 2222 --jitter-buffer=10))
|
||||
or die;
|
||||
|
||||
|
||||
my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp, $srtp_ctx_a, $srtp_ctx_b, @ret1, @ret2);
|
||||
|
||||
|
||||
|
||||
sub fec {
|
||||
my ($seq_out, $num_ec, $span, $packets) = @_;
|
||||
|
||||
my $ec_ents = 0;
|
||||
my $ec_list = '';
|
||||
|
||||
for my $ec_pack (0 .. ($num_ec-1)) {
|
||||
my $ec_seq = $seq_out - $num_ec * $span + $ec_pack;
|
||||
last if $ec_seq < 0 || !exists($packets->[$ec_seq]);
|
||||
my $xor = '';
|
||||
for my $fec_iter ((0 .. ($span-1))) {
|
||||
my $fec_seq = $ec_seq + $fec_iter * $num_ec;
|
||||
my $ecpkt = $packets->[$fec_seq];
|
||||
ok (defined $ecpkt, "FEC packet $fec_seq exists");
|
||||
ok (length($ecpkt) < 0x80, 'FEC packet short enough');
|
||||
$xor ^= $ecpkt;
|
||||
}
|
||||
$ec_list .= pack('Ca*', length($xor), $xor);
|
||||
$ec_ents++;
|
||||
}
|
||||
|
||||
return ($ec_ents, $ec_list);
|
||||
}
|
||||
|
||||
sub t38_gw_test {
|
||||
my ($testname, $pcm_cmd, $t38_cmd, %opts) = @_;
|
||||
|
||||
my ($pcm_pid, $pcm_src, $pcm_sink);
|
||||
ok($pcm_pid = open3($pcm_sink, $pcm_src, '>&STDERR', $pcm_cmd),
|
||||
"$testname - spandsp_send_fax_pcm");
|
||||
|
||||
unlink('out.tif');
|
||||
ok (! -e 'out.tif', 'output file does not exists');
|
||||
|
||||
my ($t38_pid, $t38_src, $t38_sink);
|
||||
ok($t38_pid = open3($t38_sink, $t38_src, '>&STDERR', $t38_cmd),
|
||||
"$testname - spandsp_recv_fax_t38");
|
||||
|
||||
my ($buf, $rin);
|
||||
my $seq = -1;
|
||||
my $t38_pkt = '';
|
||||
my $udptl_seq = 0;
|
||||
my @udptl_ec_in;
|
||||
my @udptl_ec_out;
|
||||
my $done = 0;
|
||||
my $sqo = 1000;
|
||||
my $tso = 3000;
|
||||
my $ts = -1;
|
||||
|
||||
my $rev = $opts{reverse} // 0;
|
||||
my $pcm_sock = $rev ? $sock_a : $sock_b;
|
||||
my $pcm_port = $rev ? $port_b : $port_a;
|
||||
my $t38_sock = $rev ? $sock_b : $sock_a;
|
||||
my $t38_port = $rev ? $port_a : $port_b;
|
||||
|
||||
my $num_ec = $opts{num_ec} // 3;
|
||||
my $span = $opts{span} // 1;
|
||||
my $fec = $span > 1;
|
||||
|
||||
# speed is controlled by the PCM generator
|
||||
while (!$done && sysread($pcm_src, $buf = '', 160) == 160) {
|
||||
# send generated PCM to rtpengine
|
||||
snd($pcm_sock, $pcm_port, rtp(8, $sqo += 1, $tso += 160, 0x1234, $buf));
|
||||
# it will also have generated a block of PCM
|
||||
if ($seq == -1) {
|
||||
($seq, $ts, $ssrc, $buf) = rcv($pcm_sock, $pcm_port, rtpmre(8 | 0x80, -1, -1, -1, '(' . ("." x 160) . ')'));
|
||||
}
|
||||
else {
|
||||
($buf) = rcv($pcm_sock, $pcm_port, rtpmre(8, $seq += 1, $ts += 160, $ssrc, '(' . ("." x 160) . ')'));
|
||||
}
|
||||
# write it back to our PCM endpoint
|
||||
is length($buf), 160, 'buf length ok';
|
||||
ok (syswrite($pcm_sink, $buf), 'PCM writeback');
|
||||
|
||||
# read from our local T.38 producer?
|
||||
$rin = '';
|
||||
vec($rin, fileno($t38_src), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0) == 1) {
|
||||
my $ret = sysread($t38_src, $buf = '', 1);
|
||||
ok (defined($ret), 'T.38 read ok');
|
||||
|
||||
if ($ret == 0) {
|
||||
# EOF
|
||||
$done = 1;
|
||||
ok (waitpid($t38_pid, 0), 'T.38 spandsp finished');
|
||||
undef($t38_pid);
|
||||
last;
|
||||
}
|
||||
|
||||
$t38_pkt .= $buf;
|
||||
# complete packet?
|
||||
my ($seq_out, $len, $pkt) = unpack('SSa*', $t38_pkt);
|
||||
next unless defined($pkt); # nope
|
||||
next if length($pkt) < $len; # nope
|
||||
|
||||
# extract...
|
||||
substr($t38_pkt, 0, $len + 4) = '';
|
||||
substr($pkt, $len) = '';
|
||||
|
||||
ok ($len > 0 && $len < 0x80, "local packet $seq_out short enough");
|
||||
|
||||
# save for EC
|
||||
$udptl_ec_out[$seq_out] = $pkt;
|
||||
|
||||
# redundancy:
|
||||
my $ec_method = 0x00;
|
||||
my $ec_span = '';
|
||||
my $ec_ents = 0;
|
||||
my $ec_list = '';
|
||||
if (!$fec) {
|
||||
for my $ec_seq (reverse(($seq_out - $num_ec) .. ($seq_out - 1))) {
|
||||
last if $ec_seq < 0 || !exists($udptl_ec_out[$ec_seq]);
|
||||
my $ecpkt = $udptl_ec_out[$ec_seq];
|
||||
ok (length($ecpkt) < 0x80, 'EC packet short enough');
|
||||
$ec_list .= pack('Ca*', length($ecpkt), $ecpkt);
|
||||
$ec_ents++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$ec_method = 0x80;
|
||||
$ec_span = pack('CC', 1, $span);
|
||||
($ec_ents, $ec_list) = fec($seq_out, $num_ec, $span, \@udptl_ec_out);
|
||||
}
|
||||
|
||||
# pack into UDPTL with redundancy
|
||||
my $udptl = pack('nCa*Ca*Ca*', $seq_out, length($pkt), $pkt, $ec_method,
|
||||
$ec_span, $ec_ents, $ec_list);
|
||||
|
||||
# send
|
||||
snd($t38_sock, $t38_port, $udptl);
|
||||
}
|
||||
|
||||
# read from our UDPTL source?
|
||||
$rin = '';
|
||||
vec($rin, fileno($t38_sock), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0) == 1) {
|
||||
my ($enc_seq, $len, $pkt) = rcv($t38_sock, $t38_port, qr/^(..)(.)(.*)$/s);
|
||||
|
||||
# allow for duplicates, as they're generated in some cases
|
||||
ok ($enc_seq == $udptl_seq || $enc_seq == $udptl_seq + 1, "UDPTL seq $enc_seq");
|
||||
$udptl_seq = $enc_seq;
|
||||
|
||||
$len = ord($len);
|
||||
ok ($len > 0 && $len < 0x80, 'remote packet short enough');
|
||||
|
||||
# extract...
|
||||
my $ifp = substr($pkt, 0, $len, '');
|
||||
ok (length($ifp) == $len, 'length matches');
|
||||
|
||||
$udptl_ec_in[$udptl_seq] = $ifp;
|
||||
|
||||
my $red = substr($pkt, 0, 1, '');
|
||||
ok ($red eq ($fec ? "\x80" : "\x00"), 'redundacy method');
|
||||
|
||||
if (!$fec) {
|
||||
my $nec = substr($pkt, 0, 1, '');
|
||||
ok ($nec eq chr($udptl_seq > 3 ? 3 : $udptl_seq), "num EC packets " . ord($nec));
|
||||
$nec = ord($nec);
|
||||
|
||||
# check EC packets
|
||||
for my $ec_seq (reverse(($udptl_seq - $nec) .. ($udptl_seq - 1))) {
|
||||
my $len = substr($pkt, 0, 1, '');
|
||||
$len = ord($len);
|
||||
ok ($len > 0 && $len < 0x80, 'EC packet short enough');
|
||||
my $ec = substr($pkt, 0, $len, '');
|
||||
if ($ec_seq == 0 && !exists($udptl_ec_in[$ec_seq])) {
|
||||
# this happens on T.38=force before the answer
|
||||
# was seen. seq 0 is generated but not sent as
|
||||
# we don't have an endpoint yet.
|
||||
# XXX can this be fixed? queue packet?
|
||||
;
|
||||
}
|
||||
else {
|
||||
ok ($ec eq $udptl_ec_in[$ec_seq], 'EC packet matches');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ok (substr($pkt, 0, 1, '') eq "\x01", 'FEC span header');
|
||||
my $nspan = substr($pkt, 0, 1, '');
|
||||
$nspan = ord($nspan);
|
||||
ok ($nspan >= 1, 'FEC span min');
|
||||
my $expspan = $span;
|
||||
my $expent = $num_ec;
|
||||
while ($udptl_seq < $expspan * $expent) {
|
||||
if ($expspan > 1) {
|
||||
$expspan--;
|
||||
next;
|
||||
}
|
||||
$expent--;
|
||||
}
|
||||
ok ($expspan == $nspan, "FEC span $expspan == $nspan");
|
||||
my $nec = ord(substr($pkt, 0, 1, ''));
|
||||
ok ($expent == $nec, "FEC num entries $expent == $nec");
|
||||
# extract all entries and compare with self-generated list
|
||||
my ($fec_entries, $fec_blob) = fec($udptl_seq, $nec, $nspan, \@udptl_ec_in);
|
||||
my $recv_blob = '';
|
||||
for (1 .. $nec) {
|
||||
my $len = substr($pkt, 0, 1, '');
|
||||
$len = ord($len);
|
||||
ok ($len > 0 && $len < 0x80, 'FEC packet short enough');
|
||||
my $ec = substr($pkt, 0, $len, '');
|
||||
$recv_blob .= pack('Ca*', $len, $ec);
|
||||
}
|
||||
ok ($fec_entries == $nec, "num actual FEC entries $fec_entries == $nec");
|
||||
ok ($recv_blob eq $fec_blob, 'FEC blob matches');
|
||||
}
|
||||
|
||||
# everything passed, write to T.38 end
|
||||
ok (syswrite($t38_sink, pack('SSa*', $udptl_seq, length($ifp), $ifp)), 'T.38 writeback');
|
||||
}
|
||||
}
|
||||
|
||||
# delete to stop PCM player
|
||||
rtpe_req('delete', "$testname delete", { 'from-tag' => ft() });
|
||||
|
||||
undef($t38_src);
|
||||
undef($t38_sink);
|
||||
undef($pcm_src);
|
||||
undef($pcm_sink);
|
||||
|
||||
if ($t38_pid) {
|
||||
ok (waitpid($t38_pid, 0), 'T.38 spandsp finished');
|
||||
undef($t38_pid);
|
||||
}
|
||||
if ($pcm_pid) {
|
||||
ok (waitpid($pcm_pid, 0), 'PCM spandsp finished');
|
||||
undef($pcm_pid);
|
||||
}
|
||||
|
||||
ok (-f 'out.tif', 'output file exists');
|
||||
ok (-s 'out.tif' > 10000, 'output file large enough');
|
||||
unlink('out.tif');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4020)], [qw(198.51.100.3 4022)]);
|
||||
|
||||
($port_a) = offer('T.38 after re-invite', { ICE => 'remove',
|
||||
}, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4020 RTP/AVP 8 0
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8 0
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('T.38 after re-invite', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4022 RTP/AVP 8 0
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8 0
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_a) = offer('T.38 after re-invite', { 'T.38' => [ 'force' ], ICE => 'remove',
|
||||
}, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4020 RTP/AVP 8 0
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
($port_b) = answer('T.38 after re-invite', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4022 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('T.38 after re-invite',
|
||||
'./spandsp_send_fax_pcm test.tif',
|
||||
'./spandsp_recv_fax_t38 out.tif',
|
||||
reverse => 1);
|
||||
|
||||
|
||||
|
||||
|
||||
done_testing();
|
||||
exit;
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4016)], [qw(198.51.100.3 4018)]);
|
||||
|
||||
($port_a) = offer('plain T.38, reverse invite', { 'T.38' => [ 'force' ], ICE => 'remove',
|
||||
}, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4016 RTP/AVP 8 0
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
($port_b) = answer('plain T.38, reverse invite', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4018 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('plain T.38, reverse invite',
|
||||
'./spandsp_send_fax_pcm test.tif',
|
||||
'./spandsp_recv_fax_t38 out.tif',
|
||||
reverse => 1);
|
||||
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4000)], [qw(198.51.100.3 4002)]);
|
||||
|
||||
($port_a) = offer('plain T.38, forward invite', { 'T.38' => [ 'decode' ], ICE => 'remove',
|
||||
'codec' => { 'transcode' => ['PCMA'] } }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4000 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('plain T.38, forward invite', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4002 RTP/AVP 8
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('plain T.38, forward invite',
|
||||
'./spandsp_send_fax_pcm test.tif',
|
||||
'./spandsp_recv_fax_t38 out.tif');
|
||||
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4004)], [qw(198.51.100.3 4006)]);
|
||||
|
||||
($port_a) = offer('plain T.38, forward invite, reverse receive', { 'T.38' => [ 'decode' ], ICE => 'remove',
|
||||
'codec' => { 'transcode' => ['PCMA'] } }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4004 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('plain T.38, forward invite, reverse receive', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4006 RTP/AVP 8
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPRedundancy
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('plain T.38, forward invite, reverse receive',
|
||||
'./spandsp_recv_fax_pcm out.tif',
|
||||
'./spandsp_send_fax_t38 test.tif');
|
||||
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4008)], [qw(198.51.100.3 4010)]);
|
||||
|
||||
($port_a) = offer('FEC', { 'T.38' => [ 'decode' ], ICE => 'remove',
|
||||
'codec' => { 'transcode' => ['PCMA'] } }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4008 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPFEC
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('FEC', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4010 RTP/AVP 8
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPFEC
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('FEC',
|
||||
'./spandsp_send_fax_pcm test.tif',
|
||||
'./spandsp_recv_fax_t38 out.tif',
|
||||
span => 3);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 4012)], [qw(198.51.100.3 4014)]);
|
||||
|
||||
($port_a) = offer('FEC span 5', { 'T.38' => [ 'decode' ], ICE => 'remove',
|
||||
'codec' => { 'transcode' => ['PCMA'] } }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image 4012 udptl t38
|
||||
c=IN IP4 198.51.100.1
|
||||
a=sendrecv
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:262
|
||||
a=T38FaxMaxDatagram:300
|
||||
a=T38FaxUdpEC:t38UDPFEC
|
||||
a=T38FaxUdpFECMaxSpan:5
|
||||
----------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio PORT RTP/AVP 8
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('FEC span 5', { ICE => 'remove' }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 4014 RTP/AVP 8
|
||||
c=IN IP4 198.51.100.3
|
||||
a=sendrecv
|
||||
--------------------------------------
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=image PORT udptl t38
|
||||
c=IN IP4 203.0.113.1
|
||||
a=T38FaxVersion:0
|
||||
a=T38MaxBitRate:14400
|
||||
a=T38FaxRateManagement:transferredTCF
|
||||
a=T38FaxMaxBuffer:1800
|
||||
a=T38FaxMaxDatagram:512
|
||||
a=T38FaxUdpEC:t38UDPFEC
|
||||
a=sendrecv
|
||||
SDP
|
||||
|
||||
|
||||
t38_gw_test('FEC span 5',
|
||||
'./spandsp_send_fax_pcm test.tif',
|
||||
'./spandsp_recv_fax_t38 out.tif',
|
||||
span => 5);
|
||||
|
||||
|
||||
|
||||
|
||||
# XXX packet loss tests
|
||||
# XXX tests of different SDP options
|
||||
|
||||
|
||||
|
||||
|
||||
done_testing();
|
||||
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open3;
|
||||
use IO::Socket;
|
||||
use IO::Socket::IP;
|
||||
|
||||
my $laddr = shift or die;
|
||||
my $lport = shift or die;
|
||||
my $raddr = shift or die;
|
||||
my $rport = shift or die;
|
||||
|
||||
my $sock = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp',
|
||||
LocalHost => $laddr, LocalPort => $lport,
|
||||
PeerHost => $raddr, PeerPort => $rport,
|
||||
)
|
||||
or die;
|
||||
|
||||
my ($src, $sink);
|
||||
my $pid = open3($sink, $src, '>&STDERR', @ARGV) or die;
|
||||
|
||||
my ($playsrc, $playsink);
|
||||
open($playsrc, '|-', qw(play -q -c 1 -e a-law -r 8000 -t raw -)) or die;
|
||||
open($playsink, '|-', qw(play -q -c 1 -e a-law -r 8000 -t raw -)) or die;
|
||||
|
||||
my $lseq = rand();
|
||||
my $lssrc = rand();
|
||||
my $lts = rand();
|
||||
my $lpt = 8; # PCMA
|
||||
my $lmark = 0x80;
|
||||
my $rseq = -1;
|
||||
my $rts = -1;
|
||||
|
||||
while (1) {
|
||||
my $buf;
|
||||
|
||||
last unless sysread($src, $buf = '', 160);
|
||||
syswrite($playsrc, $buf);
|
||||
|
||||
my $rtp = pack('CCnNN a*', 0x80, $lpt | $lmark, $lseq, $lts, $lssrc, $buf);
|
||||
last unless $sock->syswrite($rtp) or last;
|
||||
$lseq++;
|
||||
$lts += 160;
|
||||
$lmark = 0x00;
|
||||
|
||||
last unless $sock->sysread($buf = '', 0xffff);
|
||||
|
||||
my ($ver, $rpt, $seq, $ts, $rssrc, $payload) = unpack('CCnNN a*', $buf);
|
||||
die unless length($payload) == 160;
|
||||
die unless ($rpt & 0x7f) == $lpt;
|
||||
die unless ($rseq == -1 || (($rseq + 1) & 0xffff) == $seq);
|
||||
die unless ($rts == -1 || (($rts + 160) & 0xffffffff) == $ts);
|
||||
syswrite($playsink, $payload);
|
||||
$rseq = $seq;
|
||||
$rts = $ts;
|
||||
|
||||
last unless syswrite($sink, $payload);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
use POSIX ":sys_wait_h";
|
||||
|
||||
my ($send_src, $send_sink);
|
||||
my $send_pid = open2($send_src, $send_sink, './spandsp_send_fax_pcm test.tif') or die;
|
||||
|
||||
unlink('out.tif');
|
||||
|
||||
my ($recv_src, $recv_sink);
|
||||
my $recv_pid = open2($recv_src, $recv_sink, './spandsp_recv_fax_pcm out.tif') or die;
|
||||
|
||||
while ($send_pid && $recv_pid) {
|
||||
|
||||
my $buf;
|
||||
|
||||
if (sysread($send_src, $buf = '', 160)) {
|
||||
syswrite($recv_sink, $buf) or die;
|
||||
}
|
||||
|
||||
if (sysread($recv_src, $buf = '', 160)) {
|
||||
syswrite($send_sink, $buf) or die;
|
||||
}
|
||||
|
||||
undef($send_pid) if waitpid($send_pid, WNOHANG);
|
||||
undef($recv_pid) if waitpid($recv_pid, WNOHANG);
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
use POSIX ":sys_wait_h";
|
||||
|
||||
my ($send_src, $send_sink);
|
||||
my $send_pid = open2($send_src, $send_sink, './spandsp_send_fax_t38 test.tif') or die;
|
||||
|
||||
unlink('out.tif');
|
||||
|
||||
my ($recv_src, $recv_sink);
|
||||
my $recv_pid = open2($recv_src, $recv_sink, './spandsp_recv_fax_t38 out.tif') or die;
|
||||
|
||||
while ($send_pid && $recv_pid) {
|
||||
|
||||
my ($buf, $rin);
|
||||
|
||||
$rin = '';
|
||||
vec($rin, fileno($send_src), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0.02) == 1) {
|
||||
sysread($send_src, $buf = '', 1);
|
||||
syswrite($recv_sink, $buf) or last;
|
||||
}
|
||||
|
||||
$rin = '';
|
||||
vec($rin, fileno($recv_src), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0.02) == 1) {
|
||||
sysread($recv_src, $buf = '', 1);
|
||||
syswrite($send_sink, $buf) or last;
|
||||
}
|
||||
|
||||
undef($send_pid) if waitpid($send_pid, WNOHANG);
|
||||
undef($recv_pid) if waitpid($recv_pid, WNOHANG);
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
@ -0,0 +1,151 @@
|
||||
#undef NDEBUG
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <spandsp/telephony.h>
|
||||
#include <spandsp/logging.h>
|
||||
#include <spandsp/t38_core.h>
|
||||
#include <spandsp/t30.h>
|
||||
#include <spandsp/t30_api.h>
|
||||
#include <spandsp/fax.h>
|
||||
|
||||
|
||||
|
||||
#define SAMPLES_PER_CHUNK 160
|
||||
#define MICROSECONDS_PER_CHUNK 20000
|
||||
|
||||
|
||||
// from ITU G.191
|
||||
void alaw_compress (size_t lseg, int16_t *linbuf, uint8_t *logbuf) {
|
||||
short ix, iexp;
|
||||
long n;
|
||||
|
||||
for (n = 0; n < lseg; n++) {
|
||||
ix = linbuf[n] < 0 /* 0 <= ix < 2048 */
|
||||
? (~linbuf[n]) >> 4 /* 1's complement for negative values */
|
||||
: (linbuf[n]) >> 4;
|
||||
|
||||
/* Do more, if exponent > 0 */
|
||||
if (ix > 15) { /* exponent=0 for ix <= 15 */
|
||||
iexp = 1; /* first step: */
|
||||
while (ix > 16 + 15) { /* find mantissa and exponent */
|
||||
ix >>= 1;
|
||||
iexp++;
|
||||
}
|
||||
ix -= 16; /* second step: remove leading '1' */
|
||||
|
||||
ix += iexp << 4; /* now compute encoded value */
|
||||
}
|
||||
if (linbuf[n] >= 0)
|
||||
ix |= (0x0080); /* add sign bit */
|
||||
|
||||
logbuf[n] = ix ^ (0x0055); /* toggle even bits */
|
||||
}
|
||||
}
|
||||
void alaw_expand (size_t lseg, uint8_t *logbuf, int16_t *linbuf) {
|
||||
short ix, mant, iexp;
|
||||
long n;
|
||||
|
||||
for (n = 0; n < lseg; n++) {
|
||||
ix = logbuf[n] ^ (0x0055); /* re-toggle toggled bits */
|
||||
|
||||
ix &= (0x007F); /* remove sign bit */
|
||||
iexp = ix >> 4; /* extract exponent */
|
||||
mant = ix & (0x000F); /* now get mantissa */
|
||||
if (iexp > 0)
|
||||
mant = mant + 16; /* add leading '1', if exponent > 0 */
|
||||
|
||||
mant = (mant << 4) + (0x0008); /* now mantissa left justified and */
|
||||
/* 1/2 quantization step added */
|
||||
if (iexp > 1) /* now left shift according exponent */
|
||||
mant = mant << (iexp - 1);
|
||||
|
||||
linbuf[n] = logbuf[n] > 127 /* invert, if negative sample */
|
||||
? mant : -mant;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int done = 0;
|
||||
|
||||
static void phase_e_handler(t30_state_t *s, void *user_data, int result) {
|
||||
fprintf(stderr, "recv: phase E result %i\n", result);
|
||||
assert(result == T30_ERR_OK);
|
||||
done = 1;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 2);
|
||||
const char *output_file_name = argv[1];
|
||||
|
||||
fax_state_t *fax = fax_init(NULL, FALSE);
|
||||
assert(fax != NULL);
|
||||
|
||||
int use_transmit_on_idle = 1;
|
||||
int use_tep = 0;
|
||||
int supported_modems = T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17;
|
||||
int use_ecm = 0;
|
||||
|
||||
// taken from t38_gateway_tests.c
|
||||
t30_state_t *t30 = fax_get_t30_state(fax);
|
||||
fax_set_transmit_on_idle(fax, use_transmit_on_idle);
|
||||
fax_set_tep_mode(fax, use_tep);
|
||||
t30_set_supported_modems(t30, supported_modems);
|
||||
t30_set_tx_ident(t30, "11111111");
|
||||
t30_set_tx_nsf(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp\x00", 12);
|
||||
t30_set_rx_file(t30, output_file_name, -1);
|
||||
t30_set_phase_e_handler(t30, phase_e_handler, NULL);
|
||||
t30_set_ecm_capability(t30, use_ecm);
|
||||
if (use_ecm)
|
||||
t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
|
||||
t30_set_minimum_scan_line_time(t30, 40);
|
||||
|
||||
struct timeval now, next;
|
||||
|
||||
setbuf(stdout, NULL);
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
while (!done) {
|
||||
next = now;
|
||||
next.tv_usec += MICROSECONDS_PER_CHUNK;
|
||||
while (next.tv_usec >= 1000000) {
|
||||
next.tv_usec -= 1000000;
|
||||
next.tv_sec++;
|
||||
}
|
||||
|
||||
int16_t samples[SAMPLES_PER_CHUNK];
|
||||
|
||||
int ret = fax_tx(fax, samples, SAMPLES_PER_CHUNK);
|
||||
assert(ret == SAMPLES_PER_CHUNK);
|
||||
|
||||
uint8_t alaw[SAMPLES_PER_CHUNK];
|
||||
alaw_compress(SAMPLES_PER_CHUNK, samples, alaw);
|
||||
|
||||
ret = fwrite(alaw, SAMPLES_PER_CHUNK, 1, stdout);
|
||||
assert(ret == 1);
|
||||
|
||||
ret = fread(alaw, SAMPLES_PER_CHUNK, 1, stdin);
|
||||
assert(ret == 1);
|
||||
|
||||
alaw_expand(SAMPLES_PER_CHUNK, alaw, samples);
|
||||
|
||||
ret = fax_rx(fax, samples, SAMPLES_PER_CHUNK);
|
||||
assert(ret == 0);
|
||||
|
||||
while (1) {
|
||||
gettimeofday(&now, NULL);
|
||||
long long diff = ((long long) next.tv_sec - now.tv_sec) * 1000000
|
||||
+ ((long long) next.tv_usec - now.tv_usec);
|
||||
if (diff <= 0)
|
||||
break;
|
||||
usleep(diff);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
#undef NDEBUG
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <spandsp/telephony.h>
|
||||
#include <spandsp/logging.h>
|
||||
#include <spandsp/t38_core.h>
|
||||
#include <spandsp/t30.h>
|
||||
#include <spandsp/t30_api.h>
|
||||
#include <spandsp/t38_terminal.h>
|
||||
#include <spandsp/fax.h>
|
||||
|
||||
|
||||
|
||||
#define SAMPLES_PER_CHUNK 160
|
||||
|
||||
|
||||
static int packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count) {
|
||||
static uint16_t seq = 0;
|
||||
|
||||
fprintf(stderr, "recv: writing %i bytes %i times\n", len, count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint16_t hdr[2] = {seq, len};
|
||||
int ret = write(1, hdr, sizeof(hdr));
|
||||
assert(ret == sizeof(hdr));
|
||||
ret = write(1, buf, len);
|
||||
assert(ret == len);
|
||||
}
|
||||
|
||||
seq++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void phase_e_handler(t30_state_t *s, void *user_data, int result) {
|
||||
fprintf(stderr, "phase E result %i\n", result);
|
||||
assert(result == T30_ERR_OK);
|
||||
}
|
||||
|
||||
static size_t nb_read(int fd, void *b, size_t len) {
|
||||
size_t left = len;
|
||||
while (left) {
|
||||
ssize_t ret = read(fd, b + len - left, left);
|
||||
if (ret > 0) {
|
||||
left -= ret;
|
||||
continue;
|
||||
}
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
if (errno == EAGAIN && left != len) {
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 2);
|
||||
const char *output_file_name = argv[1];
|
||||
|
||||
t38_terminal_state_t *fax = t38_terminal_init(NULL, FALSE, packet_handler, NULL);
|
||||
assert(fax != NULL);
|
||||
|
||||
int use_tep = 0;
|
||||
int supported_modems = T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17;
|
||||
int use_ecm = 0;
|
||||
int t38_version = 0;
|
||||
int options = 0;
|
||||
|
||||
|
||||
// taken from t38_terminal_tests.c
|
||||
t30_state_t *t30 = t38_terminal_get_t30_state(fax);
|
||||
t38_core_state_t *t38 = t38_terminal_get_t38_core_state(fax);
|
||||
t38_set_t38_version(t38, t38_version);
|
||||
t38_terminal_set_config(fax, options);
|
||||
t38_terminal_set_tep_mode(fax, use_tep);
|
||||
|
||||
t30_set_supported_modems(t30, supported_modems);
|
||||
t30_set_tx_ident(t30, "22222222");
|
||||
t30_set_tx_nsf(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp\x00", 12);
|
||||
t30_set_rx_file(t30, output_file_name, -1);
|
||||
t30_set_ecm_capability(t30, use_ecm);
|
||||
t30_set_phase_e_handler(t30, phase_e_handler, NULL);
|
||||
|
||||
fcntl(0, F_SETFL, O_NONBLOCK);
|
||||
|
||||
while (1) {
|
||||
int done = t38_terminal_send_timeout(fax, SAMPLES_PER_CHUNK);
|
||||
if (done)
|
||||
break;
|
||||
|
||||
uint16_t hdr[2];
|
||||
int ret = nb_read(0, hdr, sizeof(hdr));
|
||||
if (ret < 0 && errno == EAGAIN) {
|
||||
usleep(20000);
|
||||
continue;
|
||||
}
|
||||
assert(ret == sizeof(hdr));
|
||||
uint8_t buf[512];
|
||||
assert(hdr[1] <= sizeof(buf));
|
||||
do
|
||||
ret = nb_read(0, buf, hdr[1]);
|
||||
while (ret < 0 && errno == EAGAIN);
|
||||
assert(ret == hdr[1]);
|
||||
fprintf(stderr, "recv: processing %u bytes, seq %u\n", hdr[1], hdr[0]);
|
||||
t38_core_rx_ifp_packet(t38, buf, hdr[1], hdr[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
#undef NDEBUG
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <spandsp/telephony.h>
|
||||
#include <spandsp/logging.h>
|
||||
#include <spandsp/t38_core.h>
|
||||
#include <spandsp/t30.h>
|
||||
#include <spandsp/t30_api.h>
|
||||
#include <spandsp/fax.h>
|
||||
|
||||
|
||||
|
||||
#define SAMPLES_PER_CHUNK 160
|
||||
#define MICROSECONDS_PER_CHUNK 20000
|
||||
|
||||
|
||||
// from ITU G.191
|
||||
void alaw_compress (size_t lseg, int16_t *linbuf, uint8_t *logbuf) {
|
||||
short ix, iexp;
|
||||
long n;
|
||||
|
||||
for (n = 0; n < lseg; n++) {
|
||||
ix = linbuf[n] < 0 /* 0 <= ix < 2048 */
|
||||
? (~linbuf[n]) >> 4 /* 1's complement for negative values */
|
||||
: (linbuf[n]) >> 4;
|
||||
|
||||
/* Do more, if exponent > 0 */
|
||||
if (ix > 15) { /* exponent=0 for ix <= 15 */
|
||||
iexp = 1; /* first step: */
|
||||
while (ix > 16 + 15) { /* find mantissa and exponent */
|
||||
ix >>= 1;
|
||||
iexp++;
|
||||
}
|
||||
ix -= 16; /* second step: remove leading '1' */
|
||||
|
||||
ix += iexp << 4; /* now compute encoded value */
|
||||
}
|
||||
if (linbuf[n] >= 0)
|
||||
ix |= (0x0080); /* add sign bit */
|
||||
|
||||
logbuf[n] = ix ^ (0x0055); /* toggle even bits */
|
||||
}
|
||||
}
|
||||
void alaw_expand (size_t lseg, uint8_t *logbuf, int16_t *linbuf) {
|
||||
short ix, mant, iexp;
|
||||
long n;
|
||||
|
||||
for (n = 0; n < lseg; n++) {
|
||||
ix = logbuf[n] ^ (0x0055); /* re-toggle toggled bits */
|
||||
|
||||
ix &= (0x007F); /* remove sign bit */
|
||||
iexp = ix >> 4; /* extract exponent */
|
||||
mant = ix & (0x000F); /* now get mantissa */
|
||||
if (iexp > 0)
|
||||
mant = mant + 16; /* add leading '1', if exponent > 0 */
|
||||
|
||||
mant = (mant << 4) + (0x0008); /* now mantissa left justified and */
|
||||
/* 1/2 quantization step added */
|
||||
if (iexp > 1) /* now left shift according exponent */
|
||||
mant = mant << (iexp - 1);
|
||||
|
||||
linbuf[n] = logbuf[n] > 127 /* invert, if negative sample */
|
||||
? mant : -mant;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int done = 0;
|
||||
|
||||
static void phase_e_handler(t30_state_t *s, void *user_data, int result) {
|
||||
fprintf(stderr, "send: phase E result %i\n", result);
|
||||
assert(result == T30_ERR_OK);
|
||||
done = 1;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 2);
|
||||
const char *input_file_name = argv[1];
|
||||
|
||||
fax_state_t *fax = fax_init(NULL, TRUE);
|
||||
assert(fax != NULL);
|
||||
|
||||
int use_transmit_on_idle = 1;
|
||||
int use_tep = 0;
|
||||
int supported_modems = T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17;
|
||||
int use_ecm = 0;
|
||||
|
||||
// taken from t38_gateway_tests.c
|
||||
t30_state_t *t30 = fax_get_t30_state(fax);
|
||||
fax_set_transmit_on_idle(fax, use_transmit_on_idle);
|
||||
fax_set_tep_mode(fax, use_tep);
|
||||
t30_set_supported_modems(t30, supported_modems);
|
||||
t30_set_tx_ident(t30, "11111111");
|
||||
t30_set_tx_nsf(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp\x00", 12);
|
||||
t30_set_tx_file(t30, input_file_name, -1, -1);
|
||||
t30_set_phase_e_handler(t30, phase_e_handler, NULL);
|
||||
t30_set_ecm_capability(t30, use_ecm);
|
||||
if (use_ecm)
|
||||
t30_set_supported_compressions(t30, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
|
||||
t30_set_minimum_scan_line_time(t30, 40);
|
||||
|
||||
struct timeval now, next;
|
||||
|
||||
setbuf(stdout, NULL);
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
while (!done) {
|
||||
next = now;
|
||||
next.tv_usec += MICROSECONDS_PER_CHUNK;
|
||||
while (next.tv_usec >= 1000000) {
|
||||
next.tv_usec -= 1000000;
|
||||
next.tv_sec++;
|
||||
}
|
||||
|
||||
int16_t samples[SAMPLES_PER_CHUNK];
|
||||
|
||||
int ret = fax_tx(fax, samples, SAMPLES_PER_CHUNK);
|
||||
assert(ret == SAMPLES_PER_CHUNK);
|
||||
|
||||
uint8_t alaw[SAMPLES_PER_CHUNK];
|
||||
alaw_compress(SAMPLES_PER_CHUNK, samples, alaw);
|
||||
|
||||
ret = fwrite(alaw, SAMPLES_PER_CHUNK, 1, stdout);
|
||||
if (ret < 1)
|
||||
break;
|
||||
|
||||
ret = fread(alaw, SAMPLES_PER_CHUNK, 1, stdin);
|
||||
if (ret == 0)
|
||||
break;
|
||||
assert(ret == 1);
|
||||
|
||||
alaw_expand(SAMPLES_PER_CHUNK, alaw, samples);
|
||||
|
||||
ret = fax_rx(fax, samples, SAMPLES_PER_CHUNK);
|
||||
assert(ret == 0);
|
||||
|
||||
while (1) {
|
||||
gettimeofday(&now, NULL);
|
||||
long long diff = ((long long) next.tv_sec - now.tv_sec) * 1000000
|
||||
+ ((long long) next.tv_usec - now.tv_usec);
|
||||
if (diff <= 0)
|
||||
break;
|
||||
usleep(diff);
|
||||
}
|
||||
}
|
||||
|
||||
// assert(done == 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
#undef NDEBUG
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <spandsp/telephony.h>
|
||||
#include <spandsp/logging.h>
|
||||
#include <spandsp/t38_core.h>
|
||||
#include <spandsp/t30.h>
|
||||
#include <spandsp/t30_api.h>
|
||||
#include <spandsp/t38_terminal.h>
|
||||
#include <spandsp/fax.h>
|
||||
|
||||
|
||||
|
||||
#define SAMPLES_PER_CHUNK 160
|
||||
|
||||
|
||||
static int packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count) {
|
||||
static uint16_t seq = 0;
|
||||
|
||||
fprintf(stderr, "send: writing %i bytes %i times, seq %u\n", len, count, seq);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint16_t hdr[2] = {seq, len};
|
||||
int ret = write(1, hdr, sizeof(hdr));
|
||||
assert(ret == sizeof(hdr));
|
||||
ret = write(1, buf, len);
|
||||
assert(ret == len);
|
||||
}
|
||||
|
||||
seq++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int g_done = 0;
|
||||
|
||||
static void phase_e_handler(t30_state_t *s, void *user_data, int result) {
|
||||
fprintf(stderr, "phase E result %i\n", result);
|
||||
assert(result == T30_ERR_OK);
|
||||
g_done = 1;
|
||||
}
|
||||
|
||||
static size_t nb_read(int fd, void *b, size_t len) {
|
||||
size_t left = len;
|
||||
while (left) {
|
||||
ssize_t ret = read(fd, b + len - left, left);
|
||||
if (ret > 0) {
|
||||
left -= ret;
|
||||
continue;
|
||||
}
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
if (errno == EAGAIN && left != len) {
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
assert(argc == 2);
|
||||
const char *input_file_name = argv[1];
|
||||
|
||||
t38_terminal_state_t *fax = t38_terminal_init(NULL, TRUE, packet_handler, NULL);
|
||||
assert(fax != NULL);
|
||||
|
||||
int use_tep = 0;
|
||||
int supported_modems = T30_SUPPORT_V27TER | T30_SUPPORT_V29 | T30_SUPPORT_V17;
|
||||
int use_ecm = 0;
|
||||
int t38_version = 0;
|
||||
int options = 0;
|
||||
|
||||
|
||||
// taken from t38_terminal_tests.c
|
||||
t30_state_t *t30 = t38_terminal_get_t30_state(fax);
|
||||
t38_core_state_t *t38 = t38_terminal_get_t38_core_state(fax);
|
||||
t38_set_t38_version(t38, t38_version);
|
||||
t38_terminal_set_config(fax, options);
|
||||
t38_terminal_set_tep_mode(fax, use_tep);
|
||||
|
||||
t30_set_supported_modems(t30, supported_modems);
|
||||
t30_set_tx_ident(t30, "11111111");
|
||||
t30_set_tx_nsf(t30, (const uint8_t *) "\x50\x00\x00\x00Spandsp\x00", 12);
|
||||
t30_set_tx_file(t30, input_file_name, -1, -1);
|
||||
t30_set_ecm_capability(t30, use_ecm);
|
||||
t30_set_phase_e_handler(t30, phase_e_handler, NULL);
|
||||
|
||||
fcntl(0, F_SETFL, O_NONBLOCK);
|
||||
|
||||
while (1) {
|
||||
int done = t38_terminal_send_timeout(fax, SAMPLES_PER_CHUNK);
|
||||
if (done)
|
||||
break;
|
||||
|
||||
uint16_t hdr[2];
|
||||
int ret = nb_read(0, hdr, sizeof(hdr));
|
||||
if (ret < 0 && errno == EAGAIN) {
|
||||
usleep(20000);
|
||||
continue;
|
||||
}
|
||||
if (ret == 0)
|
||||
break;
|
||||
assert(ret == sizeof(hdr));
|
||||
uint8_t buf[512];
|
||||
assert(hdr[1] <= sizeof(buf));
|
||||
do
|
||||
ret = nb_read(0, buf, hdr[1]);
|
||||
while (ret < 0 && errno == EAGAIN);
|
||||
assert(ret == hdr[1]);
|
||||
fprintf(stderr, "send: processing %u bytes, seq %u\n", hdr[1], hdr[0]);
|
||||
t38_core_rx_ifp_packet(t38, buf, hdr[1], hdr[0]);
|
||||
}
|
||||
|
||||
//assert(g_done == 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open3;
|
||||
use IO::Socket;
|
||||
use IO::Socket::IP;
|
||||
|
||||
my $laddr = shift or die;
|
||||
my $lport = shift or die;
|
||||
my $raddr = shift or die;
|
||||
my $rport = shift or die;
|
||||
|
||||
my $sock = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp',
|
||||
LocalHost => $laddr, LocalPort => $lport,
|
||||
PeerHost => $raddr, PeerPort => $rport,
|
||||
)
|
||||
or die;
|
||||
|
||||
my $devnull;
|
||||
die unless open($devnull, '>', '/dev/null');
|
||||
|
||||
my ($src, $sink);
|
||||
my $pid = open3($sink, $src, ">&".fileno($devnull), @ARGV) or die;
|
||||
|
||||
my $lseq = 0;
|
||||
my $rseq = 0;
|
||||
my $srcbuf = '';
|
||||
|
||||
local $| = 1;
|
||||
|
||||
while (1) {
|
||||
my $rin = '';
|
||||
vec($rin, fileno($src), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0.01) == 1) {
|
||||
my $ret = sysread($src, my $buf, 1);
|
||||
last unless $ret;
|
||||
$srcbuf .= $buf;
|
||||
my ($seq_out, $len, $pkt) = unpack('SSa*', $srcbuf);
|
||||
next unless defined($pkt);
|
||||
next if length($pkt) < $len;
|
||||
|
||||
substr($srcbuf, 0, $len + 4) = '';
|
||||
substr($pkt, $len) = '';
|
||||
|
||||
my $udptl = pack('nCa*Ca*Ca*', $seq_out, length($pkt), $pkt, 0x00,
|
||||
'', 0, '');
|
||||
|
||||
print('!');
|
||||
last unless $sock->syswrite($udptl);
|
||||
}
|
||||
|
||||
$rin = '';
|
||||
vec($rin, fileno($sock), 1) = 1;
|
||||
while (select(my $rout = $rin, undef, undef, 0.01) == 1) {
|
||||
my $ret = $sock->sysread(my $buf, 0xffff);
|
||||
my ($seq, $len, $pkt) = unpack('nCa*', $buf);
|
||||
my $t38 = substr($pkt, 0, $len);
|
||||
|
||||
print('.');
|
||||
last unless syswrite($sink, pack('SSa*', $seq, length($t38), $t38));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in new issue