chan_pjsip/res_pjsip/bridge_softmix/core: Improve translation path choices.
authorRichard Mudgett <rmudgett@digium.com>
Fri, 10 Apr 2015 23:37:20 +0000 (23:37 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Fri, 10 Apr 2015 23:37:20 +0000 (23:37 +0000)
With this patch, chan_pjsip/res_pjsip now sets the native formats to the
codecs negotiated by a call.

* The changes in chan_pjsip.c and res_pjsip_sdp_rtp.c set the native
formats to include all the negotiated audio codecs instead of only the
initial preferred audio codec and later the currently received audio
codec.

* The audio frame handling in channel.c:ast_read() is more streamlined and
will automatically adjust to changes in received frame formats.  The new
policy is to remove translation and pass the new frame format to the
receiver except if the translation was to a signed linear format.  A more
long winded version is commented in ast_read() along with some caveats.

* The audio frame handling in channel.c:ast_write() is more streamlined
and will automatically adjust any needed translation to changes in the
frame formats sent.  Frame formats sent can change for many reasons such
as a recording is being played back or the bridged peer changed the format
it sends.  Since it is a normal expectation that sent formats can change,
the codec mismatch warning message is demoted to a debug message.

* Removed the short circuit check in
channel.c:ast_channel_make_compatible_helper().  Two party bridges need to
make channels compatible with each other.  However, transfers and moving
channels among bridges can result in otherwise compatible channels having
sub-optimal translation paths if the make compatible check is short
circuited.  A result of forcing the reevaluation of channel compatibility
is that the asterisk.conf:transcode_via_slin and codecs.conf:genericplc
options take effect consistently now.  It is unfortunate that these two
options are enabled by default and negate some of the benefits to the
changes in channel.c:ast_read() by forcing translation through signed
linear on a two party bridge.

* Improved the softmix bridge technology to better control the translation
of frames to the bridge.  All of the incoming translation is now normally
handled by ast_read() instead of splitting any translation steps between
ast_read() and the slin factory.  If any frame comes in with an unexpected
format then the translation path in ast_read() is updated for the next
frame and the slin factory handles the current frame translation.

This is the final patch in a series of patches aimed at improving
translation path choices.  The other patches are on the following reviews:
https://reviewboard.asterisk.org/r/4600/
https://reviewboard.asterisk.org/r/4605/

ASTERISK-24841 #close
Reported by: Matt Jordan

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

Merged revisions 434671 from http://svn.asterisk.org/svn/asterisk/branches/13

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

bridges/bridge_softmix.c
channels/chan_pjsip.c
include/asterisk/channel.h
main/channel.c
res/res_pjsip_sdp_rtp.c

index eae28af..b463f99 100644 (file)
@@ -57,6 +57,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #define MAX_DATALEN 8096
 
+/*! The minimum sample rate of the bridge. */
+#define SOFTMIX_MIN_SAMPLE_RATE 8000   /* 8 kHz sample rate */
+
 /*! \brief Interval at which mixing will take place. Valid options are 10, 20, and 40. */
 #define DEFAULT_SOFTMIX_INTERVAL 20
 
@@ -96,8 +99,8 @@ struct softmix_channel {
        struct ast_slinfactory factory;
        /*! Frame that contains mixed audio to be written out to the channel */
        struct ast_frame write_frame;
-       /*! Frame that contains mixed audio read from the channel */
-       struct ast_frame read_frame;
+       /*! Current expected read slinear format. */
+       struct ast_format *read_slin_format;
        /*! DSP for detecting silence */
        struct ast_dsp *dsp;
        /*!
@@ -353,7 +356,9 @@ static void softmix_translate_helper_cleanup(struct softmix_translate_helper *tr
 static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
 {
        struct softmix_channel *sc = bridge_channel->tech_pvt;
-       unsigned int channel_read_rate = ast_format_get_sample_rate(ast_channel_rawreadformat(bridge_channel->chan));
+       struct ast_format *slin_format;
+
+       slin_format = ast_format_cache_get_slin_by_rate(rate);
 
        ast_mutex_lock(&sc->lock);
        if (reset) {
@@ -369,31 +374,29 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
         * for the channel.  The translated format may not be a
         * static cached format.
         */
-       ao2_replace(sc->write_frame.subclass.format, ast_format_cache_get_slin_by_rate(rate));
+       ao2_replace(sc->write_frame.subclass.format, slin_format);
        sc->write_frame.data.ptr = sc->final_buf;
        sc->write_frame.datalen = SOFTMIX_DATALEN(rate, interval);
        sc->write_frame.samples = SOFTMIX_SAMPLES(rate, interval);
 
-       /* Setup read frame parameters */
-       sc->read_frame.frametype = AST_FRAME_VOICE;
        /*
-        * NOTE: The read_frame format does not hold a reference because it
+        * NOTE: The read_slin_format does not hold a reference because it
         * will always be a signed linear format.
         */
-       sc->read_frame.subclass.format = ast_format_cache_get_slin_by_rate(channel_read_rate);
-       sc->read_frame.data.ptr = sc->our_buf;
-       sc->read_frame.datalen = SOFTMIX_DATALEN(channel_read_rate, interval);
-       sc->read_frame.samples = SOFTMIX_SAMPLES(channel_read_rate, interval);
+       sc->read_slin_format = slin_format;
 
        /* Setup smoother */
-       ast_slinfactory_init_with_format(&sc->factory, sc->write_frame.subclass.format);
+       ast_slinfactory_init_with_format(&sc->factory, slin_format);
 
        /* set new read and write formats on channel. */
-       ast_set_read_format(bridge_channel->chan, sc->read_frame.subclass.format);
-       ast_set_write_format(bridge_channel->chan, sc->write_frame.subclass.format);
+       ast_channel_lock(bridge_channel->chan);
+       ast_set_read_format_path(bridge_channel->chan,
+               ast_channel_rawreadformat(bridge_channel->chan), slin_format);
+       ast_channel_unlock(bridge_channel->chan);
+       ast_set_write_format(bridge_channel->chan, slin_format);
 
        /* set up new DSP.  This is on the read side only right before the read frame enters the smoother.  */
-       sc->dsp = ast_dsp_new_with_rate(channel_read_rate);
+       sc->dsp = ast_dsp_new_with_rate(rate);
        /* we want to aggressively detect silence to avoid feedback */
        if (bridge_channel->tech_args.talking_threshold) {
                ast_dsp_set_threshold(sc->dsp, bridge_channel->tech_args.talking_threshold);
@@ -591,6 +594,28 @@ static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bri
 
        /* Write the frame into the conference */
        ast_mutex_lock(&sc->lock);
+
+       if (ast_format_cmp(frame->subclass.format, sc->read_slin_format) != AST_FORMAT_CMP_EQUAL) {
+               /*
+                * The incoming frame is not the expected format.  Update
+                * the channel's translation path to get us slinear from
+                * the new format for the next frame.
+                *
+                * There is the possibility that this frame is an old slinear
+                * rate frame that was in flight when the softmix bridge
+                * changed rates.  If so it will self correct on subsequent
+                * frames.
+                */
+               ast_channel_lock(bridge_channel->chan);
+               ast_debug(1, "Channel %s wrote unexpected format into bridge.  Got %s, expected %s.\n",
+                       ast_channel_name(bridge_channel->chan),
+                       ast_format_get_name(frame->subclass.format),
+                       ast_format_get_name(sc->read_slin_format));
+               ast_set_read_format_path(bridge_channel->chan, frame->subclass.format,
+                       sc->read_slin_format);
+               ast_channel_unlock(bridge_channel->chan);
+       }
+
        ast_dsp_silence_with_energy(sc->dsp, frame, &totalsilence, &cur_energy);
 
        if (bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC) {
@@ -729,15 +754,19 @@ static void gather_softmix_stats(struct softmix_stats *stats,
        struct ast_bridge_channel *bridge_channel)
 {
        int channel_native_rate;
-       int i;
+
        /* Gather stats about channel sample rates. */
-       channel_native_rate = MAX(ast_format_get_sample_rate(ast_channel_rawwriteformat(bridge_channel->chan)),
+       ast_channel_lock(bridge_channel->chan);
+       channel_native_rate = MAX(SOFTMIX_MIN_SAMPLE_RATE,
                ast_format_get_sample_rate(ast_channel_rawreadformat(bridge_channel->chan)));
+       ast_channel_unlock(bridge_channel->chan);
 
-       if (channel_native_rate > stats->highest_supported_rate) {
+       if (stats->highest_supported_rate < channel_native_rate) {
                stats->highest_supported_rate = channel_native_rate;
        }
-       if (channel_native_rate > softmix_data->internal_rate) {
+       if (softmix_data->internal_rate < channel_native_rate) {
+               int i;
+
                for (i = 0; i < ARRAY_LEN(stats->sample_rates); i++) {
                        if (stats->sample_rates[i] == channel_native_rate) {
                                stats->num_channels[i]++;
@@ -749,7 +778,7 @@ static void gather_softmix_stats(struct softmix_stats *stats,
                        }
                }
                stats->num_above_internal_rate++;
-       } else if (channel_native_rate == softmix_data->internal_rate) {
+       } else if (softmix_data->internal_rate == channel_native_rate) {
                stats->num_at_internal_rate++;
        }
 }
@@ -1119,8 +1148,8 @@ static int softmix_bridge_create(struct ast_bridge *bridge)
                softmix_bridge_data_destroy(softmix_data);
                return -1;
        }
-       /* start at 8khz, let it grow from there */
-       softmix_data->internal_rate = 8000;
+       /* start at minimum rate, let it grow from there */
+       softmix_data->internal_rate = SOFTMIX_MIN_SAMPLE_RATE;
        softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
 
        bridge->tech_pvt = softmix_data;
index 745347c..ca0e5cc 100644 (file)
@@ -629,22 +629,6 @@ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
                return f;
        }
 
-       if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
-               struct ast_format_cap *caps;
-
-               ast_debug(1, "Oooh, format changed to %s\n", ast_format_get_name(f->subclass.format));
-
-               caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-               if (caps) {
-                       ast_format_cap_append(caps, f->subclass.format, 0);
-                       ast_channel_nativeformats_set(ast, caps);
-                       ao2_ref(caps, -1);
-               }
-
-               ast_set_read_format(ast, ast_channel_readformat(ast));
-               ast_set_write_format(ast, ast_channel_writeformat(ast));
-       }
-
        if (channel->session->dsp) {
                f = ast_dsp_process(ast, channel->session->dsp, f);
 
index 6dd3ac4..82abe99 100644 (file)
@@ -1953,6 +1953,21 @@ int ast_write_text(struct ast_channel *chan, struct ast_frame *frame);
 int ast_prod(struct ast_channel *chan);
 
 /*!
+ * \brief Set specific read path on channel.
+ * \since 13.4.0
+ *
+ * \param chan Channel to setup read path.
+ * \param raw_format Format to expect from the channel driver.
+ * \param core_format What the core wants to read.
+ *
+ * \pre chan is locked
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_set_read_format_path(struct ast_channel *chan, struct ast_format *raw_format, struct ast_format *core_format);
+
+/*!
  * \brief Sets read format on channel chan from capabilities
  * Set read format for channel to whichever component of "format" is best.
  * \param chan channel to change
index 6aca21a..af93956 100644 (file)
@@ -4114,78 +4114,133 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
                                        ast_frfree(f);
                                        f = &ast_null_frame;
                                }
-                       } else if (f->frametype == AST_FRAME_VOICE && ast_format_cap_iscompatible_format(ast_channel_nativeformats(chan), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
-                               /* This frame is not one of the current native formats -- drop it on the floor */
-                               struct ast_str *codec_buf = ast_str_alloca(64);
-                               ast_log(LOG_NOTICE, "Dropping incompatible voice frame on %s of format %s since our native format has changed to %s\n",
-                                       ast_channel_name(chan), ast_format_get_name(f->subclass.format), ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf));
-                               ast_frfree(f);
-                               f = &ast_null_frame;
-                       } else if (f->frametype == AST_FRAME_VOICE) {
-                               /* Send frame to audiohooks if present */
-                               if (ast_channel_audiohooks(chan)) {
-                                       struct ast_frame *old_frame = f;
-                                       f = ast_audiohook_write_list(chan, ast_channel_audiohooks(chan), AST_AUDIOHOOK_DIRECTION_READ, f);
-                                       if (old_frame != f)
-                                               ast_frfree(old_frame);
+                               break;
+                       }
+                       if (f->frametype != AST_FRAME_VOICE) {
+                               break;
+                       }
+                       if (ast_format_cmp(f->subclass.format, ast_channel_rawreadformat(chan)) != AST_FORMAT_CMP_EQUAL
+                               && ast_format_cmp(f->subclass.format, ast_channel_readformat(chan)) != AST_FORMAT_CMP_EQUAL) {
+                               struct ast_format *core_format;
+
+                               /*
+                                * Note: This frame may not be one of the current native
+                                * formats.  We may have gotten it out of the read queue from
+                                * a previous multi-frame translation, from a framehook
+                                * injected frame, or the device we're talking to isn't
+                                * respecting negotiated formats.  Regardless we will accept
+                                * all frames.
+                                *
+                                * Update the read translation path to handle the new format
+                                * that just came in.  If the core wants slinear we need to
+                                * setup a new translation path because the core is usually
+                                * doing something with the audio itself and may not handle
+                                * any other format.  e.g., Softmix bridge, holding bridge
+                                * announcer channel, recording, AMD...  Otherwise, we'll
+                                * setup to pass the frame as is to the core.  In this case
+                                * the core doesn't care.  The channel is likely in
+                                * autoservice, safesleep, or the channel is in a bridge.
+                                * Let the bridge technology deal with format compatibility
+                                * between the channels in the bridge.
+                                *
+                                * Beware of the transcode_via_slin and genericplc options as
+                                * they force any transcoding to go through slin on a bridge.
+                                * Unfortunately transcode_via_slin is enabled by default and
+                                * genericplc is enabled in the codecs.conf.sample file.
+                                *
+                                * XXX Only updating translation to slinear frames has some
+                                * corner cases if slinear is one of the native formats and
+                                * there are different sample rates involved.  We might wind
+                                * up with conflicting translation paths between channels
+                                * where the read translation path on this channel reduces
+                                * the sample rate followed by a write translation path on
+                                * the peer channel that increases the sample rate.
+                                */
+                               core_format = ast_channel_readformat(chan);
+                               if (!ast_format_cache_is_slinear(core_format)) {
+                                       core_format = f->subclass.format;
+                               }
+                               if (ast_set_read_format_path(chan, f->subclass.format, core_format)) {
+                                       /* Drop frame.  We couldn't make it compatible with the core. */
+                                       ast_frfree(f);
+                                       f = &ast_null_frame;
+                                       break;
                                }
-                               if (ast_channel_monitor(chan) && ast_channel_monitor(chan)->read_stream ) {
-                                       /* XXX what does this do ? */
+                       }
+                       /* Send frame to audiohooks if present */
+                       if (ast_channel_audiohooks(chan)) {
+                               struct ast_frame *old_frame = f;
+
+                               f = ast_audiohook_write_list(chan, ast_channel_audiohooks(chan), AST_AUDIOHOOK_DIRECTION_READ, f);
+                               if (old_frame != f) {
+                                       ast_frfree(old_frame);
+                               }
+                       }
+                       if (ast_channel_monitor(chan) && ast_channel_monitor(chan)->read_stream) {
+                               /* XXX what does this do ? */
 #ifndef MONITOR_CONSTANT_DELAY
-                                       int jump = ast_channel_outsmpl(chan) - ast_channel_insmpl(chan) - 4 * f->samples;
-                                       if (jump >= 0) {
-                                               jump = calc_monitor_jump((ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)),
-                                                                        ast_format_get_sample_rate(f->subclass.format),
-                                                                        ast_format_get_sample_rate(ast_channel_monitor(chan)->read_stream->fmt->format));
-                                               if (ast_seekstream(ast_channel_monitor(chan)->read_stream, jump, SEEK_FORCECUR) == -1) {
-                                                       ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n");
-                                               }
-                                               ast_channel_insmpl_set(chan, ast_channel_insmpl(chan) + (ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)) + f->samples);
-                                       } else {
-                                               ast_channel_insmpl_set(chan, ast_channel_insmpl(chan) + f->samples);
+                               int jump = ast_channel_outsmpl(chan) - ast_channel_insmpl(chan) - 4 * f->samples;
+                               if (jump >= 0) {
+                                       jump = calc_monitor_jump((ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)),
+                                               ast_format_get_sample_rate(f->subclass.format),
+                                               ast_format_get_sample_rate(ast_channel_monitor(chan)->read_stream->fmt->format));
+                                       if (ast_seekstream(ast_channel_monitor(chan)->read_stream, jump, SEEK_FORCECUR) == -1) {
+                                               ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n");
                                        }
+                                       ast_channel_insmpl_set(chan, ast_channel_insmpl(chan) + (ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)) + f->samples);
+                               } else {
+                                       ast_channel_insmpl_set(chan, ast_channel_insmpl(chan) + f->samples);
+                               }
 #else
-                                       int jump = calc_monitor_jump((ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)),
-                                                                    ast_format_get_sample_rate(f->subclass.codec),
-                                                                    ast_format_get_sample_rate(ast_channel_monitor(chan)->read_stream->fmt->format));
-                                       if (jump - MONITOR_DELAY >= 0) {
-                                               if (ast_seekstream(ast_channel_monitor(chan)->read_stream, jump - f->samples, SEEK_FORCECUR) == -1)
-                                                       ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n");
-                                               ast_channel_insmpl(chan) += ast_channel_outsmpl(chan) - ast_channel_insmpl(chan);
-                                       } else
-                                               ast_channel_insmpl(chan) += f->samples;
-#endif
-                                       if (ast_channel_monitor(chan)->state == AST_MONITOR_RUNNING) {
-                                               if (ast_writestream(ast_channel_monitor(chan)->read_stream, f) < 0)
-                                                       ast_log(LOG_WARNING, "Failed to write data to channel monitor read stream\n");
+                               int jump = calc_monitor_jump((ast_channel_outsmpl(chan) - ast_channel_insmpl(chan)),
+                                       ast_format_get_sample_rate(f->subclass.codec),
+                                       ast_format_get_sample_rate(ast_channel_monitor(chan)->read_stream->fmt->format));
+                               if (jump - MONITOR_DELAY >= 0) {
+                                       if (ast_seekstream(ast_channel_monitor(chan)->read_stream, jump - f->samples, SEEK_FORCECUR) == -1) {
+                                               ast_log(LOG_WARNING, "Failed to perform seek in monitoring read stream, synchronization between the files may be broken\n");
                                        }
+                                       ast_channel_insmpl(chan) += ast_channel_outsmpl(chan) - ast_channel_insmpl(chan);
+                               } else {
+                                       ast_channel_insmpl(chan) += f->samples;
                                }
+#endif
+                               if (ast_channel_monitor(chan)->state == AST_MONITOR_RUNNING) {
+                                       if (ast_writestream(ast_channel_monitor(chan)->read_stream, f) < 0)
+                                               ast_log(LOG_WARNING, "Failed to write data to channel monitor read stream\n");
+                               }
+                       }
 
-                               if (ast_channel_readtrans(chan) && (f = ast_translate(ast_channel_readtrans(chan), f, 1)) == NULL) {
+                       if (ast_channel_readtrans(chan)
+                               && ast_format_cmp(f->subclass.format, ast_channel_rawreadformat(chan)) == AST_FORMAT_CMP_EQUAL) {
+                               f = ast_translate(ast_channel_readtrans(chan), f, 1);
+                               if (!f) {
                                        f = &ast_null_frame;
                                }
+                       }
 
-                               /* it is possible for the translation process on chan->readtrans to have
-                                  produced multiple frames from the single input frame we passed it; if
-                                  this happens, queue the additional frames *before* the frames we may
-                                  have queued earlier. if the readq was empty, put them at the head of
-                                  the queue, and if it was not, put them just after the frame that was
-                                  at the end of the queue.
-                               */
-                               if (AST_LIST_NEXT(f, frame_list)) {
-                                       if (!readq_tail) {
-                                               ast_queue_frame_head(chan, AST_LIST_NEXT(f, frame_list));
-                                       } else {
-                                               __ast_queue_frame(chan, AST_LIST_NEXT(f, frame_list), 0, readq_tail);
-                                       }
-                                       ast_frfree(AST_LIST_NEXT(f, frame_list));
-                                       AST_LIST_NEXT(f, frame_list) = NULL;
+                       /*
+                        * It is possible for the translation process on the channel to have
+                        * produced multiple frames from the single input frame we passed it; if
+                        * this happens, queue the additional frames *before* the frames we may
+                        * have queued earlier. if the readq was empty, put them at the head of
+                        * the queue, and if it was not, put them just after the frame that was
+                        * at the end of the queue.
+                        */
+                       if (AST_LIST_NEXT(f, frame_list)) {
+                               if (!readq_tail) {
+                                       ast_queue_frame_head(chan, AST_LIST_NEXT(f, frame_list));
+                               } else {
+                                       __ast_queue_frame(chan, AST_LIST_NEXT(f, frame_list), 0, readq_tail);
                                }
-
-                               /* Run generator sitting on the line if timing device not available
-                               * and synchronous generation of outgoing frames is necessary       */
-                               ast_read_generator_actions(chan, f);
+                               ast_frfree(AST_LIST_NEXT(f, frame_list));
+                               AST_LIST_NEXT(f, frame_list) = NULL;
                        }
+
+                       /*
+                        * Run generator sitting on the line if timing device not available
+                        * and synchronous generation of outgoing frames is necessary
+                        */
+                       ast_read_generator_actions(chan, f);
                        break;
                default:
                        /* Just pass it on! */
@@ -5034,29 +5089,35 @@ int ast_write(struct ast_channel *chan, struct ast_frame *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 (ast_format_cmp(fr->subclass.format, ast_channel_rawwriteformat(chan)) != AST_FORMAT_CMP_NOT_EQUAL) {
+               if (ast_format_cmp(fr->subclass.format, ast_channel_rawwriteformat(chan)) == AST_FORMAT_CMP_EQUAL) {
                        f = fr;
                } else {
-                       if ((ast_format_cap_iscompatible_format(ast_channel_nativeformats(chan), fr->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) &&
-                           (ast_format_cmp(ast_channel_writeformat(chan), fr->subclass.format) != AST_FORMAT_CMP_EQUAL)) {
-                               struct ast_str *codec_buf = ast_str_alloca(64);
+                       if (ast_format_cmp(ast_channel_writeformat(chan), fr->subclass.format) != AST_FORMAT_CMP_EQUAL) {
+                               struct ast_str *codec_buf = ast_str_alloca(256);
 
                                /*
-                                * XXX Something is not right.  We are not compatible with this
-                                * frame.  Bad things can happen.  Problems range from no audio,
-                                * one-way audio, to unexplained line hangups.  As a last resort
-                                * try to adjust the format.  Ideally, we do not want to do this
-                                * because it indicates a deeper problem.  For now, we log these
-                                * events to reduce user impact and help identify the problem
-                                * areas.
+                                * We are not setup to write this frame.  Things may have changed
+                                * on the peer side of the world and we try to adjust the format to
+                                * make it compatible again.  However, bad things can happen if we
+                                * cannot setup a new translation path.  Problems range from no
+                                * audio, one-way audio, to garbled audio.  The best we can do is
+                                * request the call to hangup since we could not make it compatible.
+                                *
+                                * Being continuously spammed by this message likely indicates a
+                                * problem with the peer because it cannot make up its mind about
+                                * which format to use.
                                 */
-                               ast_log(LOG_WARNING, "Codec mismatch on channel %s setting write format to %s from %s native formats %s\n",
-                                       ast_channel_name(chan), ast_format_get_name(fr->subclass.format), ast_format_get_name(ast_channel_writeformat(chan)),
+                               ast_debug(1, "Channel %s changing write format from %s to %s, native formats %s\n",
+                                       ast_channel_name(chan),
+                                       ast_format_get_name(ast_channel_writeformat(chan)),
+                                       ast_format_get_name(fr->subclass.format),
                                        ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf));
-                               ast_set_write_format(chan, fr->subclass.format);
+                               if (ast_set_write_format(chan, fr->subclass.format)) {
+                                       /* Could not handle the new write format.  Induce a hangup. */
+                                       break;
+                               }
                        }
-
-                       f = (ast_channel_writetrans(chan)) ? ast_translate(ast_channel_writetrans(chan), fr, 0) : fr;
+                       f = ast_channel_writetrans(chan) ? ast_translate(ast_channel_writetrans(chan), fr, 0) : fr;
                }
 
                if (!f) {
@@ -5216,6 +5277,42 @@ done:
        return res;
 }
 
+int ast_set_read_format_path(struct ast_channel *chan, struct ast_format *raw_format, struct ast_format *core_format)
+{
+       struct ast_trans_pvt *trans_old;
+       struct ast_trans_pvt *trans_new;
+
+       if (ast_format_cmp(ast_channel_rawreadformat(chan), raw_format) == AST_FORMAT_CMP_EQUAL
+               && ast_format_cmp(ast_channel_readformat(chan), core_format) == AST_FORMAT_CMP_EQUAL) {
+               /* Nothing to setup */
+               return 0;
+       }
+
+       ast_debug(1, "Channel %s setting read format path: %s -> %s\n",
+               ast_channel_name(chan),
+               ast_format_get_name(raw_format),
+               ast_format_get_name(core_format));
+
+       /* Setup new translation path. */
+       if (ast_format_cmp(raw_format, core_format) != AST_FORMAT_CMP_EQUAL) {
+               trans_new = ast_translator_build_path(core_format, raw_format);
+               if (!trans_new) {
+                       return -1;
+               }
+       } else {
+               /* No translation needed. */
+               trans_new = NULL;
+       }
+       trans_old = ast_channel_readtrans(chan);
+       if (trans_old) {
+               ast_translator_free_path(trans_old);
+       }
+       ast_channel_readtrans_set(chan, trans_new);
+       ast_channel_set_rawreadformat(chan, raw_format);
+       ast_channel_set_readformat(chan, core_format);
+       return 0;
+}
+
 struct set_format_access {
        const char *direction;
        struct ast_trans_pvt *(*get_trans)(const struct ast_channel *chan);
@@ -6209,15 +6306,17 @@ static int ast_channel_make_compatible_helper(struct ast_channel *from, struct a
        RAII_VAR(struct ast_format *, best_dst_fmt, NULL, ao2_cleanup);
        int no_path;
 
-       ast_channel_lock_both(from, to);
+       /*
+        * We cannot short circuit this code because it is possible to ask
+        * to make compatible two channels that are "compatible" because
+        * they already have translation paths setup but together make for
+        * a sub-optimal path.  e.g., The From channel has g722 -> ulaw
+        * and the To channel has ulaw -> g722.  They are "compatible" but
+        * together the translations are unnecessary and the audio loses
+        * fidelity in the process.
+        */
 
-       if ((ast_format_cmp(ast_channel_readformat(from), ast_channel_writeformat(to)) != AST_FORMAT_CMP_NOT_EQUAL) &&
-               (ast_format_cmp(ast_channel_readformat(to), ast_channel_writeformat(from)) != AST_FORMAT_CMP_NOT_EQUAL)) {
-               /* Already compatible!  Moving on ... */
-               ast_channel_unlock(to);
-               ast_channel_unlock(from);
-               return 0;
-       }
+       ast_channel_lock_both(from, to);
 
        src_cap = ast_channel_nativeformats(from); /* shallow copy, do not destroy */
        dst_cap = ast_channel_nativeformats(to);   /* shallow copy, do not destroy */
index 2ecd11a..7756179 100644 (file)
@@ -252,8 +252,8 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi
        /* get the joint capabilities between peer and endpoint */
        ast_format_cap_get_compatible(caps, peer, joint);
        if (!ast_format_cap_count(joint)) {
-               struct ast_str *usbuf = ast_str_alloca(64);
-               struct ast_str *thembuf = ast_str_alloca(64);
+               struct ast_str *usbuf = ast_str_alloca(256);
+               struct ast_str *thembuf = ast_str_alloca(256);
 
                ast_rtp_codecs_payloads_destroy(&codecs);
                ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
@@ -269,33 +269,22 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi
        ast_format_cap_append_from_cap(session->req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);
 
        if (session->channel) {
-               struct ast_format *fmt;
-
                ast_channel_lock(session->channel);
                ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);
-               ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel), AST_MEDIA_TYPE_UNKNOWN);
+               ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),
+                       AST_MEDIA_TYPE_UNKNOWN);
                ast_format_cap_remove_by_type(caps, media_type);
-
-               /*
-                * XXX Historically we picked the "best" joint format to use
-                * and stuck with it.  It would be nice to just append the
-                * determined joint media capabilities to give translation
-                * more formats to choose from when necessary.  Unfortunately,
-                * there are some areas of the system where this doesn't work
-                * very well. (The softmix bridge in particular is reluctant
-                * to pick higher fidelity formats and has a problem with
-                * asymmetric sample rates.)
-                */
-               fmt = ast_format_cap_get_format(joint, 0);
-               ast_format_cap_append(caps, fmt, 0);
+               ast_format_cap_append_from_cap(caps, joint, media_type);
 
                /*
                 * Apply the new formats to the channel, potentially changing
                 * raw read/write formats and translation path while doing so.
                 */
                ast_channel_nativeformats_set(session->channel, caps);
-               ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
-               ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
+               if (media_type == AST_MEDIA_TYPE_AUDIO) {
+                       ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
+                       ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
+               }
                if ((session->endpoint->dtmf == AST_SIP_DTMF_AUTO)
                    && (ast_rtp_instance_dtmf_mode_get(session_media->rtp) == AST_RTP_DTMF_MODE_RFC2833)
                    && (session->dsp)) {
@@ -309,8 +298,6 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi
                        }
                }
                ast_channel_unlock(session->channel);
-
-               ao2_ref(fmt, -1);
        }
 
        ast_rtp_codecs_payloads_destroy(&codecs);