Merged revisions 163448 via svnmerge from
authorRussell Bryant <russell@russellbryant.com>
Fri, 12 Dec 2008 13:55:30 +0000 (13:55 +0000)
committerRussell Bryant <russell@russellbryant.com>
Fri, 12 Dec 2008 13:55:30 +0000 (13:55 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r163448 | russell | 2008-12-12 07:44:08 -0600 (Fri, 12 Dec 2008) | 26 lines

Resolve issues that could cause DTMF to be processed out of order.

These changes come from team/russell/issue_12658

1) Change autoservice to put digits on the head of the channel's frame readq
   instead of the tail.  If there were frames on the readq that autoservice
   had not yet read, the previous code would have resulted in out of order
   processing.  This required a new API call to queue a frame to the head
   of the queue instead of the tail.

2) Change up the processing of DTMF in ast_read().  Some of the problems
   were the result of having two sources of pending DTMF frames.  There
   was the dtmfq and the more generic readq.  Both were used for pending
   DTMF in various scenarios.  Simplifying things to only use the frame
   readq avoids some of the problems.

3) Fix a bug where a DTMF END frame could get passed through when it
   shouldn't have.  If code set END_DTMF_ONLY in the middle of digit emulation,
   and a digit arrived before emulation was complete, digits would get
   processed out of order.

(closes issue #12658)
Reported by: dimas
Tested by: russell, file
Review: http://reviewboard.digium.com/r/85/

........

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

include/asterisk/channel.h
main/autoservice.c
main/channel.c

index ae785a2..93ef1f1 100644 (file)
@@ -496,7 +496,7 @@ struct ast_channel {
 
        unsigned short transfercapability;              /*!< ISDN Transfer Capbility - AST_FLAG_DIGITAL is not enough */
 
 
        unsigned short transfercapability;              /*!< ISDN Transfer Capbility - AST_FLAG_DIGITAL is not enough */
 
-       char dtmfq[AST_MAX_EXTENSION];                  /*!< Any/all queued DTMF characters */
+       char unused_old_dtmfq[AST_MAX_EXTENSION];                       /*!< (deprecated, use readq instead) Any/all queued DTMF characters */
        char context[AST_MAX_CONTEXT];                  /*!< Dialplan: Current extension context */
        char exten[AST_MAX_EXTENSION];                  /*!< Dialplan: Current extension number */
        char macrocontext[AST_MAX_CONTEXT];             /*!< Macro: Current non-macro context. See app_macro.c */
        char context[AST_MAX_CONTEXT];                  /*!< Dialplan: Current extension context */
        char exten[AST_MAX_EXTENSION];                  /*!< Dialplan: Current extension number */
        char macrocontext[AST_MAX_CONTEXT];             /*!< Macro: Current non-macro context. See app_macro.c */
@@ -711,6 +711,20 @@ struct ast_channel *ast_channel_alloc(int needqueue, int state, const char *cid_
  */
 int ast_queue_frame(struct ast_channel *chan, struct ast_frame *f);
 
  */
 int ast_queue_frame(struct ast_channel *chan, struct ast_frame *f);
 
+/*!
+ * \brief Queue an outgoing frame to the head of the frame queue
+ *
+ * \param chan the channel to queue the frame on
+ * \param f the frame to queue.  Note that this frame will be duplicated by
+ *        this function.  It is the responsibility of the caller to handle
+ *        freeing the memory associated with the frame being passed if
+ *        necessary.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_queue_frame_head(struct ast_channel *chan, struct ast_frame *f);
+
 /*! 
  * \brief Queue a hangup frame 
  *
 /*! 
  * \brief Queue a hangup frame 
  *
index d3d0333..26cb085 100644 (file)
@@ -56,6 +56,9 @@ struct asent {
         *  it gets stopped for the last time. */
        unsigned int use_count;
        unsigned int orig_end_dtmf_flag:1;
         *  it gets stopped for the last time. */
        unsigned int use_count;
        unsigned int orig_end_dtmf_flag:1;
+       /*! Frames go on at the head of deferred_frames, so we have the frames
+        *  from newest to oldest.  As we put them at the head of the readq, we'll
+        *  end up with them in the right order for the channel's readq. */
        AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
        AST_LIST_ENTRY(asent) list;
 };
        AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
        AST_LIST_ENTRY(asent) list;
 };
@@ -161,7 +164,7 @@ static void *autoservice_run(void *ign)
                                }
                                
                                if ((dup_f = ast_frdup(defer_frame))) {
                                }
                                
                                if ((dup_f = ast_frdup(defer_frame))) {
-                                       AST_LIST_INSERT_TAIL(&ents[i]->deferred_frames, dup_f, frame_list);
+                                       AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list);
                                }
                                
                                break;
                                }
                                
                                break;
@@ -292,10 +295,12 @@ int ast_autoservice_stop(struct ast_channel *chan)
                ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
        }
 
                ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
        }
 
+       ast_channel_lock(chan);
        while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) {
        while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) {
-               ast_queue_frame(chan, f);
+               ast_queue_frame_head(chan, f);
                ast_frfree(f);
        }
                ast_frfree(f);
        }
+       ast_channel_unlock(chan);
 
        free(as);
 
 
        free(as);
 
index d4d24b6..3debac5 100644 (file)
@@ -953,7 +953,7 @@ alertpipe_failed:
 }
 
 /*! \brief Queue an outgoing media frame */
 }
 
 /*! \brief Queue an outgoing media frame */
-int ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin)
+static int __ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin, int head)
 {
        struct ast_frame *f;
        struct ast_frame *cur;
 {
        struct ast_frame *f;
        struct ast_frame *cur;
@@ -962,9 +962,9 @@ int ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin)
 
        /* Build us a copy and free the original one */
        if (!(f = ast_frdup(fin))) {
 
        /* Build us a copy and free the original one */
        if (!(f = ast_frdup(fin))) {
-               ast_log(LOG_WARNING, "Unable to duplicate frame\n");
                return -1;
        }
                return -1;
        }
+
        ast_channel_lock(chan);
 
        /* See if the last frame on the queue is a hangup, if so don't queue anything */
        ast_channel_lock(chan);
 
        /* See if the last frame on the queue is a hangup, if so don't queue anything */
@@ -991,20 +991,39 @@ int ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin)
                        return 0;
                }
        }
                        return 0;
                }
        }
-       AST_LIST_INSERT_TAIL(&chan->readq, f, frame_list);
+
+       if (head) {
+               AST_LIST_INSERT_HEAD(&chan->readq, f, frame_list);
+       } else {
+               AST_LIST_INSERT_TAIL(&chan->readq, f, frame_list);
+       }
+
        if (chan->alertpipe[1] > -1) {
        if (chan->alertpipe[1] > -1) {
-               if (write(chan->alertpipe[1], &blah, sizeof(blah)) != sizeof(blah))
+               if (write(chan->alertpipe[1], &blah, sizeof(blah)) != sizeof(blah)) {
                        ast_log(LOG_WARNING, "Unable to write to alert pipe on %s, frametype/subclass %d/%d (qlen = %d): %s!\n",
                                chan->name, f->frametype, f->subclass, qlen, strerror(errno));
                        ast_log(LOG_WARNING, "Unable to write to alert pipe on %s, frametype/subclass %d/%d (qlen = %d): %s!\n",
                                chan->name, f->frametype, f->subclass, qlen, strerror(errno));
+               }
        } else if (chan->timingfd > -1) {
                ast_timer_enable_continuous(chan->timingfd);
        } else if (ast_test_flag(chan, AST_FLAG_BLOCKING)) {
                pthread_kill(chan->blocker, SIGURG);
        }
        } else if (chan->timingfd > -1) {
                ast_timer_enable_continuous(chan->timingfd);
        } else if (ast_test_flag(chan, AST_FLAG_BLOCKING)) {
                pthread_kill(chan->blocker, SIGURG);
        }
+
        ast_channel_unlock(chan);
        ast_channel_unlock(chan);
+
        return 0;
 }
 
        return 0;
 }
 
+int ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin)
+{
+       return __ast_queue_frame(chan, fin, 0);
+}
+
+int ast_queue_frame_head(struct ast_channel *chan, struct ast_frame *fin)
+{
+       return __ast_queue_frame(chan, fin, 1);
+}
+
 /*! \brief Queue a hangup frame for channel */
 int ast_queue_hangup(struct ast_channel *chan)
 {
 /*! \brief Queue a hangup frame for channel */
 int ast_queue_hangup(struct ast_channel *chan)
 {
@@ -2370,6 +2389,42 @@ static void ast_read_generator_actions(struct ast_channel *chan, struct ast_fram
        }
 }
 
        }
 }
 
+static inline void queue_dtmf_readq(struct ast_channel *chan, struct ast_frame *f)
+{
+       struct ast_frame *fr = &chan->dtmff;
+
+       fr->frametype = AST_FRAME_DTMF_END;
+       fr->subclass = f->subclass;
+       fr->len = f->len;
+
+       /* The only time this function will be called is for a frame that just came
+        * out of the channel driver.  So, we want to stick it on the tail of the
+        * readq. */
+
+       ast_queue_frame(chan, fr);
+}
+
+/*!
+ * \brief Determine whether or not we should ignore DTMF in the readq
+ */
+static inline int should_skip_dtmf(struct ast_channel *chan)
+{
+       if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF | AST_FLAG_EMULATE_DTMF)) {
+               /* We're in the middle of emulating a digit, or DTMF has been
+                * explicitly deferred.  Skip this digit, then. */
+               return 1;
+       }
+                       
+       if (!ast_tvzero(chan->dtmf_tv) && 
+                       ast_tvdiff_ms(ast_tvnow(), chan->dtmf_tv) < AST_MIN_DTMF_GAP) {
+               /* We're not in the middle of a digit, but it hasn't been long enough
+                * since the last digit, so we'll have to skip DTMF for now. */
+               return 1;
+       }
+
+       return 0;
+}
+
 static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
 {
        struct ast_frame *f = NULL;     /* the return value */
 static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
 {
        struct ast_frame *f = NULL;     /* the return value */
@@ -2403,28 +2458,6 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
        }
        prestate = chan->_state;
 
        }
        prestate = chan->_state;
 
-       if (!ast_test_flag(chan, AST_FLAG_DEFER_DTMF | AST_FLAG_EMULATE_DTMF | AST_FLAG_IN_DTMF) && 
-           !ast_strlen_zero(chan->dtmfq) && 
-               (ast_tvzero(chan->dtmf_tv) || ast_tvdiff_ms(ast_tvnow(), chan->dtmf_tv) > AST_MIN_DTMF_GAP) ) {
-               /* We have DTMF that has been deferred.  Return it now */
-               chan->dtmff.subclass = chan->dtmfq[0];
-               /* Drop first digit from the buffer */
-               memmove(chan->dtmfq, chan->dtmfq + 1, sizeof(chan->dtmfq) - 1);
-               f = &chan->dtmff;
-               if (ast_test_flag(chan, AST_FLAG_END_DTMF_ONLY)) {
-                       ast_log(LOG_DTMF, "DTMF end emulation of '%c' queued on %s\n", f->subclass, chan->name);
-                       chan->dtmff.frametype = AST_FRAME_DTMF_END;
-               } else {
-                       ast_log(LOG_DTMF, "DTMF begin emulation of '%c' with duration %d queued on %s\n", f->subclass, AST_DEFAULT_EMULATE_DTMF_DURATION, chan->name);
-                       chan->dtmff.frametype = AST_FRAME_DTMF_BEGIN;
-                       ast_set_flag(chan, AST_FLAG_EMULATE_DTMF);
-                       chan->emulate_dtmf_digit = f->subclass;
-                       chan->emulate_dtmf_duration = AST_DEFAULT_EMULATE_DTMF_DURATION;
-               }
-               chan->dtmf_tv = ast_tvnow();
-               goto done;
-       }
-       
        /* Read and ignore anything on the alertpipe, but read only
           one sizeof(blah) per frame that we send from it */
        if (chan->alertpipe[0] > -1) {
        /* Read and ignore anything on the alertpipe, but read only
           one sizeof(blah) per frame that we send from it */
        if (chan->alertpipe[0] > -1) {
@@ -2492,7 +2525,35 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
 
        /* Check for pending read queue */
        if (!AST_LIST_EMPTY(&chan->readq)) {
 
        /* Check for pending read queue */
        if (!AST_LIST_EMPTY(&chan->readq)) {
-               f = AST_LIST_REMOVE_HEAD(&chan->readq, frame_list);
+               int skip_dtmf = should_skip_dtmf(chan);
+
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->readq, f, frame_list) {
+                       /* We have to be picky about which frame we pull off of the readq because
+                        * there are cases where we want to leave DTMF frames on the queue until
+                        * some later time. */
+
+                       if ( (f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END) && skip_dtmf) {
+                               continue;
+                       }
+
+                       AST_LIST_REMOVE_CURRENT(frame_list);
+                       break;
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+               
+               if (!f) {
+                       /* There were no acceptable frames on the readq. */
+                       f = &ast_null_frame;
+                       if (chan->alertpipe[0] > -1) {
+                               int poke = 0;
+                               /* Restore the state of the alertpipe since we aren't ready for any
+                                * of the frames in the readq. */
+                               if (write(chan->alertpipe[1], &poke, sizeof(poke)) != sizeof(poke)) {
+                                       ast_log(LOG_ERROR, "Failed to write to alertpipe: %s\n", strerror(errno));
+                               }
+                       }
+               }
+
                /* Interpret hangup and return NULL */
                /* XXX why not the same for frames from the channel ? */
                if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) {
                /* Interpret hangup and return NULL */
                /* XXX why not the same for frames from the channel ? */
                if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) {
@@ -2549,26 +2610,16 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
                case AST_FRAME_DTMF_END:
                        send_dtmf_event(chan, "Received", f->subclass, "No", "Yes");
                        ast_log(LOG_DTMF, "DTMF end '%c' received on %s, duration %ld ms\n", f->subclass, chan->name, f->len);
                case AST_FRAME_DTMF_END:
                        send_dtmf_event(chan, "Received", f->subclass, "No", "Yes");
                        ast_log(LOG_DTMF, "DTMF end '%c' received on %s, duration %ld ms\n", f->subclass, chan->name, f->len);
-                       /* Queue it up if DTMF is deffered, or if DTMF emulation is forced.
-                        * However, only let emulation be forced if the other end cares about BEGIN frames */
-                       if ( ast_test_flag(chan, AST_FLAG_DEFER_DTMF) ||
-                               (ast_test_flag(chan, AST_FLAG_EMULATE_DTMF) && !ast_test_flag(chan, AST_FLAG_END_DTMF_ONLY)) ) {
-                               if (strlen(chan->dtmfq) < sizeof(chan->dtmfq) - 2) {
-                                       ast_log(LOG_DTMF, "DTMF end '%c' put into dtmf queue on %s\n", f->subclass, chan->name);
-                                       chan->dtmfq[strlen(chan->dtmfq)] = f->subclass;
-                               } else
-                                       ast_log(LOG_WARNING, "Dropping deferred DTMF digits on %s\n", chan->name);
+                       /* Queue it up if DTMF is deferred, or if DTMF emulation is forced. */
+                       if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF) || ast_test_flag(chan, AST_FLAG_EMULATE_DTMF)) {
+                               queue_dtmf_readq(chan, f);
                                ast_frfree(f);
                                f = &ast_null_frame;
                        } else if (!ast_test_flag(chan, AST_FLAG_IN_DTMF | AST_FLAG_END_DTMF_ONLY)) {
                                if (!ast_tvzero(chan->dtmf_tv) && 
                                    ast_tvdiff_ms(ast_tvnow(), chan->dtmf_tv) < AST_MIN_DTMF_GAP) {
                                        /* If it hasn't been long enough, defer this digit */
                                ast_frfree(f);
                                f = &ast_null_frame;
                        } else if (!ast_test_flag(chan, AST_FLAG_IN_DTMF | AST_FLAG_END_DTMF_ONLY)) {
                                if (!ast_tvzero(chan->dtmf_tv) && 
                                    ast_tvdiff_ms(ast_tvnow(), chan->dtmf_tv) < AST_MIN_DTMF_GAP) {
                                        /* If it hasn't been long enough, defer this digit */
-                                       if (strlen(chan->dtmfq) < sizeof(chan->dtmfq) - 2) {
-                                               ast_log(LOG_DTMF, "DTMF end '%c' put into dtmf queue on %s\n", f->subclass, chan->name);
-                                               chan->dtmfq[strlen(chan->dtmfq)] = f->subclass;
-                                       } else
-                                               ast_log(LOG_WARNING, "Dropping deferred DTMF digits on %s\n", chan->name);
+                                       queue_dtmf_readq(chan, f);
                                        ast_frfree(f);
                                        f = &ast_null_frame;
                                } else {
                                        ast_frfree(f);
                                        f = &ast_null_frame;
                                } else {