Change-Id: I4552e07eee632fa730745410b08c3bf70ab67ab2changes/83/38283/37
| @ -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)); | |||
| } | |||
| } | |||