translate: Enables native Packet-Loss Concealment (PLC) for supporting codecs.
authorAlexander Traud <pabstraud@compuserve.com>
Sun, 17 Jul 2016 12:43:37 +0000 (14:43 +0200)
committerAlexander Traud <pabstraud@compuserve.com>
Sun, 17 Jul 2016 12:46:26 +0000 (14:46 +0200)
ASTERISK-25629 #close

Change-Id: I66c0086e6c17764b8141ec60a3e2aaefe088eb78

main/translate.c

index 43e6e29..45113aa 100644 (file)
 
 ASTERISK_REGISTER_FILE()
 
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <math.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/translate.h"
-#include "asterisk/module.h"
-#include "asterisk/frame.h"
-#include "asterisk/sched.h"
-#include "asterisk/cli.h"
-#include "asterisk/term.h"
-#include "asterisk/format.h"
-#include "asterisk/linkedlists.h"
+#include <sys/resource.h>               /* for rusage, getrusage, etc */
+#include <sys/time.h>                   /* for timeval */
+
+#include "asterisk/astobj2.h"           /* for ao2_ref, ao2_cleanup, etc */
+#include "asterisk/cli.h"               /* for ast_cli_args, ast_cli, etc */
+#include "asterisk/codec.h"             /* for ast_codec, etc */
+#include "asterisk/format.h"            /* for ast_format_get_name, etc */
+#include "asterisk/format_cache.h"      /* for ast_format_cache_get, etc */
+#include "asterisk/format_cap.h"        /* for ast_format_cap_count, etc */
+#include "asterisk/frame.h"             /* for ast_frame, etc */
+#include "asterisk/linkedlists.h"       /* for AST_RWLIST_UNLOCK, etc */
+#include "asterisk/lock.h"              /* for ast_rwlock_unlock, etc */
+#include "asterisk/logger.h"            /* for ast_log, LOG_WARNING, etc */
+#include "asterisk/module.h"            /* for ast_module_unref, etc */
+#include "asterisk/strings.h"           /* for ast_str_append, etc */
+#include "asterisk/term.h"              /* for term_color, COLOR_BLACK, etc */
+#include "asterisk/time.h"              /* for ast_tvzero, ast_tv, etc */
+#include "asterisk/translate.h"         /* for ast_translator, etc */
+#include "asterisk/utils.h"             /* for ast_free, RAII_VAR, etc */
 
 /*! \todo
  * TODO: sample frames for each supported input format.
@@ -358,6 +363,7 @@ static struct ast_trans_pvt *newpvt(struct ast_translator *t, struct ast_format
        pvt->f.offset = AST_FRIENDLY_OFFSET;
        pvt->f.src = pvt->t->name;
        pvt->f.data.ptr = pvt->outbuf.c;
+       pvt->f.seqno = 0x10000;
 
        /*
         * If the translator has not provided a format
@@ -524,13 +530,58 @@ struct ast_trans_pvt *ast_translator_build_path(struct ast_format *dst, struct a
 /*! \brief do the actual translation */
 struct ast_frame *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f, int consume)
 {
-       struct ast_trans_pvt *p = path;
-       struct ast_frame *out;
+       const unsigned int rtp_seqno_max_value = 0xffff;
+       struct ast_frame *out_last, *out = NULL;
+       struct ast_trans_pvt *step;
        struct timeval delivery;
        int has_timing_info;
        long ts;
        long len;
-       int seqno;
+       int seqno, frames_missing;
+
+       /* Determine the amount of lost packets for PLC */
+       /* But not when Signed Linear is involved = frame created internally */
+       /* But not at start with first frame = path->f.seqno is still 0x10000 */
+       /* But not when there is no sequence number = frame created internally */
+       if (!ast_format_cache_is_slinear(f->subclass.format) &&
+               path->f.seqno <= rtp_seqno_max_value &&
+               path->f.seqno != f->seqno) {
+               if (f->seqno < path->f.seqno) { /* seqno overrun situation */
+                       frames_missing = rtp_seqno_max_value + f->seqno - path->f.seqno - 1;
+               } else {
+                       frames_missing = f->seqno - path->f.seqno - 1;
+               }
+               /* Out-of-order packet - more precise: late packet */
+               if ((rtp_seqno_max_value + 1) / 2 < frames_missing) {
+                       if (consume) {
+                               ast_frfree(f);
+                       }
+                       /*
+                        * Do not pass late packets to any transcoding module, because that
+                        * confuses the state of any library (packets inter-depend). With
+                        * the next packet, this one is going to be treated as lost packet.
+                        */
+                       if (frames_missing > 96) { /* with 20 msec per frame, around 2 seconds late */
+                               struct ast_str *str = ast_str_alloca(256);
+
+                               /* Might indicate an error in the detection of Late Frames, report! */
+                               ast_log(LOG_NOTICE, "Late Frame; got Sequence Number %d expected %d %s\n",
+                                                 f->seqno, rtp_seqno_max_value & (path->f.seqno + 1),
+                                                 ast_translate_path_to_str(path, &str));
+                       }
+                       return NULL;
+               }
+
+               if (frames_missing > 96) {
+                       struct ast_str *str = ast_str_alloca(256);
+
+                       /* not DEBUG but NOTICE because of WARNING in main/cannel.c:__ast_queue_frame */
+                       ast_log(LOG_NOTICE, "%d lost frame(s) %d/%d %s\n", frames_missing,
+                                       f->seqno, path->f.seqno, ast_translate_path_to_str(path, &str));
+               }
+       } else {
+               frames_missing = 0;
+       }
 
        has_timing_info = ast_test_flag(f, AST_FRFLAG_HAS_TIMING_INFO);
        ts = f->ts;
@@ -560,18 +611,93 @@ struct ast_frame *ast_translate(struct ast_trans_pvt *path, struct ast_frame *f,
                         f->samples, ast_format_get_sample_rate(f->subclass.format)));
        }
        delivery = f->delivery;
-       for (out = f; out && p ; p = p->next) {
-               struct ast_frame *current = out;
-
-               do {
-                       framein(p, current);
-                       current = AST_LIST_NEXT(current, frame_list);
-               } while (current);
-               if (out != f) {
-                       ast_frfree(out);
+
+       for (out_last = NULL; frames_missing + 1; frames_missing--) {
+               struct ast_frame *frame_to_translate, *inner_head;
+               struct ast_frame missed = {
+                       .frametype = AST_FRAME_VOICE,
+                       .subclass.format = f->subclass.format,
+                       .datalen = 0,
+                       /* In RTP, the amount of samples might change anytime  */
+                       /* If that happened while frames got lost, what to do? */
+                       .samples = f->samples, /* FIXME */
+                       .src = __FUNCTION__,
+                       .data.uint32 = 0,
+                       .delivery.tv_sec = 0,
+                       .delivery.tv_usec = 0,
+                       .flags = 0,
+                       /* RTP sequence number is between 0x0001 and 0xffff */
+                       .seqno = (rtp_seqno_max_value + 1 + f->seqno - frames_missing) & rtp_seqno_max_value,
+               };
+
+               if (frames_missing) {
+                       frame_to_translate = &missed;
+               } else {
+                       frame_to_translate = f;
+               }
+
+               /* The translation path from one format to another might contain several steps */
+               /* out* collects the result for missed frame(s) and input frame(s) */
+               /* out is the result of the conversion of all frames, translated into the destination format */
+               /* out_last is the last frame in that list, to add frames faster */
+               for (step = path, inner_head = frame_to_translate; inner_head && step; step = step->next) {
+                       struct ast_frame *current, *inner_last, *inner_prev = frame_to_translate;
+
+                       /* inner* collects the result of each conversion step, the input for the next step */
+                       /* inner_head is a list of frames created by each conversion step */
+                       /* inner_last is the last frame in that list, to add frames faster */
+                       for (inner_last = NULL, current = inner_head; current; current = AST_LIST_NEXT(current, frame_list)) {
+                               struct ast_frame *tmp;
+
+                               framein(step, current);
+                               tmp = step->t->frameout(step);
+
+                               if (!tmp) {
+                                       continue;
+                               } else if (inner_last) {
+                                       struct ast_frame *t;
+
+                                       /* Determine the last frame of the list before appending to it */
+                                       while ((t = AST_LIST_NEXT(inner_last, frame_list))) {
+                                               inner_last = t;
+                                       }
+                                       AST_LIST_NEXT(inner_last, frame_list) = tmp;
+                               } else {
+                                       inner_prev = inner_head;
+                                       inner_head = tmp;
+                                       inner_last = tmp;
+                               }
+                       }
+
+                       /* The current step did not create any frames = no frames for the next step */
+                       /* The steps are not lost because framein buffered those for the next input frame */
+                       if (!inner_last) {
+                               inner_prev = inner_head;
+                               inner_head = NULL;
+                       }
+                       if (inner_prev != frame_to_translate) {
+                               ast_frfree(inner_prev); /* Frees just the intermediate lists */
+                       }
+               }
+
+               /* This frame created no frames after translation = continue with next frame */
+               /* The frame is not lost because framein buffered it to be combined with the next frame */
+               if (!inner_head) {
+                       continue;
+               } else if (out_last) {
+                       struct ast_frame *t;
+
+                       /* Determine the last frame of the list before appending to it */
+                       while ((t = AST_LIST_NEXT(out_last, frame_list))) {
+                               out_last = t;
+                       }
+                       AST_LIST_NEXT(out_last, frame_list) = inner_head;
+               } else {
+                       out = inner_head;
+                       out_last = inner_head;
                }
-               out = p->t->frameout(p);
        }
+
        if (out) {
                /* we have a frame, play with times */
                if (!ast_tvzero(delivery)) {