Fix transcode_via_sln option with SIP calls and improve PLC usage.
authorMark Michelson <mmichelson@digium.com>
Wed, 19 May 2010 21:29:08 +0000 (21:29 +0000)
committerMark Michelson <mmichelson@digium.com>
Wed, 19 May 2010 21:29:08 +0000 (21:29 +0000)
From reviewboard:
The problem here is a bit complex, so try to bear with me...

It was noticed by a Digium customer that generic PLC (as configured in
codecs.conf) did not appear to actually be having any sort of benefit when
packet loss was introduced on an RTP stream. I reproduced this issue myself
by streaming a file across an RTP stream and dropping approx. 5% of the
RTP packets. I saw no real difference between when PLC was enabled or disabled
when using wireshark to analyze the RTP streams.

After analyzing what was going on, it became clear that one of the problems
faced was that when running my tests, the translation paths were being set
up in such a way that PLC could not possibly work as expected. To illustrate,
if packets are lost on channel A's read stream, then we expect that PLC will
be applied to channel B's write stream. The problem is that generic PLC can
only be done when there is a translation path that moves from some codec to
SLINEAR. When I would run my tests, I found that every single time, read
and write translation paths would be set up on channel A instead of channel
B. There appeared to be no real way to predict which channel the translation
paths would be set up on.

This is where Kevin swooped in to let me know about the transcode_via_sln
option in asterisk.conf. It is supposed to work by placing a read translation
path on both channels from the channel's rawreadformat to SLINEAR. It also
will place a write translation path on both channels from SLINEAR to the
channel's rawwriteformat. Using this option allows one to predictably set up
translation paths on all channels. There are two problems with this, though.
First and foremost, the transcode_via_sln option did not appear to be working
properly when I was placing a SIP call between two endpoints which did not
share any common formats. Second, even if this option were to work, for PLC
to be applied, there had to be a write translation path that would go from
some format to SLINEAR. It would not work properly if the starting format
of translation was SLINEAR.

The one-line change presented in this review request in chan_sip.c fixed the
first issue for me. The problem was that in sip_request_call, the
jointcapability of the outbound channel was being set to the format passed to
sip_request_call. This is nativeformats of the inbound channel. Because of this,
when ast_channel_make_compatible was called by app_dial, both channels already
had compatibly read and write formats. Thus, no translation path was set up at
the time. My change is to set the jointcapability of the sip_pvt created during
sip_request_call to the intersection of the inbound channel's nativeformats and
the configured peer capability that we determined during the earlier call to
create_addr. Doing this got the translation paths set up as expected when using
transcode_via_sln.

The changes presented in channel.c fixed the second issue for me. First and
foremost, when Asterisk is started, we'll read codecs.conf to see the value of
the genericplc option. If this option is set, and ast_write is called for a
frame with no data, then we will attempt to fill in the missing samples for
the frame. The implementation uses a channel datastore for maintaining the
PLC state and for creating a buffer to store PLC samples in. Even when we
receive a frame with data, we'll call plc_rx so that the PLC state will have
knowledge of the previous voice frame, which it can use as a basis for when
it comes time to actually do a PLC fill-in.

So, reviewers, now I ask for your help. First off, there's the one line change
in chan_sip that I have put in. Is it right? By my logic it seems correct, but
I'm sure someone can tell me why it is not going to work. This is probably the
change I'm least concerned about, though. What concerns me much more is the
set of changes in channel.c. First off, am I even doing it right? When I run
tests, I can clearly see that when PLC is activated, I see a significant increase
in RTP traffic where I would expect it to be. However, in my humble opinion, the
audio sounds kind of crappy whenever the PLC fill-in is done. It sounds worse to
me than when no PLC is used at all. I need someone to review the logic I have used
to be sure that I'm not misusing anything. As far as I can see my pointer arithmetic
is correct, and my use of AST_FRIENDLY_OFFSET should be correct as well, but I'm
sure someone can point out somewhere where I've done something incorrectly.

As I was writing this review request up, I decided to give the code a test run under
valgrind, and I find that for some reason, calls to plc_rx are causing some invalid
reads. Apparently I'm reading past the end of a buffer somehow. I'll have to dig around
a bit to see why that is the case. If it's obvious to someone reviewing, speak up!

Finally, I have one other proposal that is not reflected in my code review. Since
without transcode_via_sln set, one cannot predict or control where a translation
path will be up, it seems to me that the current practice of using PLC only when
transcoding to SLINEAR is not useful. I recommend that once it has been determined
that the method used in this code review is correct and works as expected, then
the code in translate.c that invokes PLC should be removed.

Review: https://reviewboard.asterisk.org/r/622/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@264452 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_sip.c
include/asterisk/_private.h
include/asterisk/options.h
main/asterisk.c
main/channel.c
main/loader.c

index 689ed47..6c31841 100644 (file)
@@ -24324,7 +24324,7 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
        printf("Setting up to call extension '%s' at '%s'\n", ext ? ext : "<none>", host);
 #endif
        p->prefcodec = oldformat;                               /* Format for this call */
-       p->jointcapability = oldformat;
+       p->jointcapability = oldformat & p->capability;
        sip_pvt_lock(p);
        tmpc = sip_new(p, AST_STATE_DOWN, host, requestor ? requestor->linkedid : NULL);        /* Place the call */
        if (sip_cfg.callevents)
index 435ee09..560c8c1 100644 (file)
@@ -83,4 +83,11 @@ void ast_process_pending_reloads(void);
  */
 int ast_xmldoc_load_documentation(void);
 
+/*!
+ * \brief Reload genericplc configuration value from codecs.conf
+ *
+ * Implementation is in main/channel.c
+ */
+int ast_plc_reload(void);
+
 #endif /* _ASTERISK__PRIVATE_H */
index e6147aa..2509f46 100644 (file)
@@ -94,6 +94,8 @@ enum ast_option_flags {
        AST_OPT_FLAG_HIDE_CONSOLE_CONNECT = (1 << 28),
        /*! Protect the configuration file path with a lock */
        AST_OPT_FLAG_LOCK_CONFIG_DIR = (1 << 29),
+       /*! Generic PLC */
+       AST_OPT_FLAG_GENERIC_PLC = (1 << 30),
 };
 
 /*! These are the options that set by default when Asterisk starts */
@@ -131,6 +133,7 @@ enum ast_option_flags {
 #define ast_opt_force_black_background ast_test_flag(&ast_options, AST_OPT_FLAG_FORCE_BLACK_BACKGROUND)
 #define ast_opt_hide_connect           ast_test_flag(&ast_options, AST_OPT_FLAG_HIDE_CONSOLE_CONNECT)
 #define ast_opt_lock_confdir           ast_test_flag(&ast_options, AST_OPT_FLAG_LOCK_CONFIG_DIR)
+#define ast_opt_generic_plc         ast_test_flag(&ast_options, AST_OPT_FLAG_GENERIC_PLC)
 
 extern struct ast_flags ast_options;
 
index 6751456..e81840f 100644 (file)
@@ -466,6 +466,7 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c
        ast_cli(a->fd, "  Transcode via SLIN:          %s\n", ast_test_flag(&ast_options, AST_OPT_FLAG_TRANSCODE_VIA_SLIN) ? "Enabled" : "Disabled");
        ast_cli(a->fd, "  Internal timing:             %s\n", ast_test_flag(&ast_options, AST_OPT_FLAG_INTERNAL_TIMING) ? "Enabled" : "Disabled");
        ast_cli(a->fd, "  Transmit silence during rec: %s\n", ast_test_flag(&ast_options, AST_OPT_FLAG_TRANSMIT_SILENCE) ? "Enabled" : "Disabled");
+       ast_cli(a->fd, "  Generic PLC:                 %s\n", ast_test_flag(&ast_options, AST_OPT_FLAG_GENERIC_PLC) ? "Enabled" : "Disabled");
 
        ast_cli(a->fd, "\n* Subsystems\n");
        ast_cli(a->fd, "  -------------\n");
index 5850c89..7866b14 100644 (file)
@@ -4059,6 +4059,86 @@ int ast_write_video(struct ast_channel *chan, struct ast_frame *fr)
        return res;
 }
 
+struct plc_ds {
+       /* A buffer in which to store SLIN PLC
+        * samples generated by the generic PLC
+        * functionality in plc.c
+        */
+       int16_t *samples_buf;
+       /* The current number of samples in the
+        * samples_buf
+        */
+       size_t num_samples;
+       plc_state_t plc_state;
+};
+
+static void plc_ds_destroy(void *data)
+{
+       struct plc_ds *plc = data;
+       ast_free(plc->samples_buf);
+       ast_free(plc);
+}
+
+static struct ast_datastore_info plc_ds_info = {
+       .type = "plc",
+       .destroy = plc_ds_destroy,
+};
+
+static void adjust_frame_for_plc(struct ast_channel *chan, struct ast_frame *frame, struct ast_datastore *datastore)
+{
+       int num_new_samples = frame->samples;
+       struct plc_ds *plc = datastore->data;
+
+       /* First, we need to be sure that our buffer is large enough to accomodate
+        * the samples we need to fill in. This will likely only occur on the first
+        * frame we write.
+        */
+       if (plc->num_samples < num_new_samples) {
+               ast_free(plc->samples_buf);
+               plc->samples_buf = ast_calloc(num_new_samples, sizeof(*plc->samples_buf));
+               if (!plc->samples_buf) {
+                       ast_channel_datastore_remove(chan, datastore);
+                       ast_datastore_free(datastore);
+                       return;
+               }
+               plc->num_samples = num_new_samples;
+       }
+
+       if (frame->datalen == 0) {
+               plc_fillin(&plc->plc_state, plc->samples_buf, frame->samples);
+               frame->data.ptr = plc->samples_buf;
+               frame->datalen = num_new_samples * 2;
+       } else {
+               plc_rx(&plc->plc_state, frame->data.ptr, frame->samples);
+       }
+}
+
+static void apply_plc(struct ast_channel *chan, struct ast_frame *frame)
+{
+       struct ast_datastore *datastore;
+       struct plc_ds *plc;
+
+       datastore = ast_channel_datastore_find(chan, &plc_ds_info, NULL);
+       if (datastore) {
+               plc = datastore->data;
+               adjust_frame_for_plc(chan, frame, datastore);
+               return;
+       }
+
+       datastore = ast_datastore_alloc(&plc_ds_info, NULL);
+       if (!datastore) {
+               return;
+       }
+       plc = ast_calloc(1, sizeof(*plc));
+       if (!plc) {
+               ast_datastore_free(datastore);
+               return;
+       }
+       datastore->data = plc;
+       ast_channel_datastore_add(chan, datastore);
+       adjust_frame_for_plc(chan, frame, datastore);
+}
+
 int ast_write(struct ast_channel *chan, struct ast_frame *fr)
 {
        int res = -1;
@@ -4178,6 +4258,10 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                if (chan->tech->write == NULL)
                        break;  /*! \todo XXX should return 0 maybe ? */
 
+               if (ast_opt_generic_plc && fr->subclass.codec == AST_FORMAT_SLINEAR) {
+                       apply_plc(chan, fr);
+               }
+
                /* If the frame is in the raw write format, then it's easy... just use the frame - otherwise we will have to translate */
                if (fr->subclass.codec == chan->rawwriteformat)
                        f = fr;
@@ -4911,10 +4995,12 @@ static int ast_channel_make_compatible_helper(struct ast_channel *from, struct a
        }
 
        /* if the best path is not 'pass through', then
-          transcoding is needed; if desired, force transcode path
-          to use SLINEAR between channels, but only if there is
-          no direct conversion available */
-       if ((src != dst) && ast_opt_transcode_via_slin &&
+        * transcoding is needed; if desired, force transcode path
+        * to use SLINEAR between channels, but only if there is
+        * no direct conversion available. If generic PLC is
+        * desired, then transcoding via SLINEAR is a requirement
+        */
+       if ((src != dst) && (ast_opt_generic_plc || ast_opt_transcode_via_slin) &&
            (ast_translate_path_steps(dst, src) != 1))
                dst = AST_FORMAT_SLINEAR;
        if (ast_set_read_format(from, dst) < 0) {
@@ -6567,6 +6653,22 @@ static int ast_channel_hash_cb(const void *obj, const int flags)
        return ast_str_case_hash(chan->name);
 }
 
+int ast_plc_reload(void)
+{
+       struct ast_variable *var;
+       struct ast_flags config_flags = { 0 };
+       struct ast_config *cfg = ast_config_load("codecs.conf", config_flags);
+       if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID)
+               return 0;
+       for (var = ast_variable_browse(cfg, "plc"); var; var = var->next) {
+               if (!strcasecmp(var->name, "genericplc")) {
+                       ast_set2_flag(&ast_options, ast_true(var->value), AST_OPT_FLAG_GENERIC_PLC);
+               }
+       }
+       ast_config_destroy(cfg);
+       return 0;
+}
+
 /*!
  * \internal
  * \brief Implements the channels provider.
@@ -6649,6 +6751,8 @@ void ast_channels_init(void)
        ast_cli_register_multiple(cli_channel, ARRAY_LEN(cli_channel));
 
        ast_data_register_multiple_core(channel_providers, ARRAY_LEN(channel_providers));
+
+       ast_plc_reload();
 }
 
 /*! \brief Print call group and pickup group ---*/
index 3c27357..f44cef9 100644 (file)
@@ -260,6 +260,7 @@ static struct reload_classes {
        { "udptl",      ast_udptl_reload },
        { "indications", ast_indications_reload },
        { "cel",        ast_cel_engine_reload },
+       { "plc",        ast_plc_reload },
        { NULL,         NULL }
 };