SS7 marked the start of an open season for trunk again but here's something minor...
[asterisk/asterisk.git] / main / channel.c
index 61df3d2..3df9191 100644 (file)
@@ -102,6 +102,9 @@ unsigned long global_fin = 0, global_fout = 0;
 AST_THREADSTORAGE(state2str_threadbuf, state2str_threadbuf_init);
 #define STATE2STR_BUFSIZE   32
 
+/* XXX 100ms ... this won't work with wideband support */
+#define AST_DEFAULT_EMULATE_DTMF_SAMPLES 800
+
 struct chanlist {
        const struct ast_channel_tech *tech;
        AST_LIST_ENTRY(chanlist) list;
@@ -240,7 +243,8 @@ static int show_channeltype(int fd, int argc, char *argv[])
                "    Indication: %s\n"
                "     Transfer : %s\n"
                "  Capabilities: %d\n"
-               "    Send Digit: %s\n"
+               "   Digit Begin: %s\n"
+               "     Digit End: %s\n"
                "    Send HTML : %s\n"
                " Image Support: %s\n"
                "  Text Support: %s\n",
@@ -249,7 +253,8 @@ static int show_channeltype(int fd, int argc, char *argv[])
                (cl->tech->indicate) ? "yes" : "no",
                (cl->tech->transfer) ? "yes" : "no",
                (cl->tech->capabilities) ? cl->tech->capabilities : -1,
-               (cl->tech->send_digit) ? "yes" : "no",
+               (cl->tech->send_digit_begin) ? "yes" : "no",
+               (cl->tech->send_digit_end) ? "yes" : "no",
                (cl->tech->send_html) ? "yes" : "no",
                (cl->tech->send_image) ? "yes" : "no",
                (cl->tech->send_text) ? "yes" : "no"
@@ -283,18 +288,32 @@ static char *complete_channeltypes(const char *line, const char *word, int pos,
 }
 
 static char show_channeltypes_usage[] =
-"Usage: show channeltypes\n"
-"       Shows available channel types registered in your Asterisk server.\n";
+"Usage: channeltype list\n"
+"       Lists available channel types registered in your Asterisk server.\n";
 
 static char show_channeltype_usage[] =
-"Usage: show channeltype <name>\n"
+"Usage: channeltype show <name>\n"
 "      Show details about the specified channel type, <name>.\n";
 
-static struct ast_cli_entry cli_show_channeltypes =
-       { { "show", "channeltypes", NULL }, show_channeltypes, "Show available channel types", show_channeltypes_usage };
+static struct ast_cli_entry cli_show_channeltypes_deprecated = {
+       { "show", "channeltypes", NULL },
+       show_channeltypes, NULL,
+       NULL };
+
+static struct ast_cli_entry cli_show_channeltype_deprecated = {
+       { "show", "channeltype", NULL },
+       show_channeltype, NULL,
+       NULL, complete_channeltypes };
 
-static struct ast_cli_entry cli_show_channeltype =
-       { { "show", "channeltype", NULL }, show_channeltype, "Give more details on that channel type", show_channeltype_usage, complete_channeltypes };
+static struct ast_cli_entry cli_channel[] = {
+       { { "channeltype", "list", NULL },
+       show_channeltypes, "List available channel types",
+       show_channeltypes_usage, NULL, &cli_show_channeltypes_deprecated },
+
+       { { "channeltype", "show", NULL },
+       show_channeltype, "Give more details on that channel type",
+       show_channeltype_usage, complete_channeltypes, &cli_show_channeltype_deprecated },
+};
 
 /*! \brief Checks to see if a channel is needing hang up */
 int ast_check_hangup(struct ast_channel *chan)
@@ -1198,22 +1217,61 @@ int ast_channel_spy_add(struct ast_channel *chan, struct ast_channel_spy *spy)
        return 0;
 }
 
+/* Clean up a channel's spy information */
+static void spy_cleanup(struct ast_channel *chan)
+{
+       if (!AST_LIST_EMPTY(&chan->spies->list))
+               return;
+       if (chan->spies->read_translator.path)
+               ast_translator_free_path(chan->spies->read_translator.path);
+       if (chan->spies->write_translator.path)
+               ast_translator_free_path(chan->spies->write_translator.path);
+       free(chan->spies);
+       chan->spies = NULL;
+       return;
+}
+
+/* Detach a spy from it's channel */
+static void spy_detach(struct ast_channel_spy *spy, struct ast_channel *chan)
+{
+       ast_mutex_lock(&spy->lock);
+
+       /* We only need to poke them if they aren't already done */
+       if (spy->status != CHANSPY_DONE) {
+               /* Indicate to the spy to stop */
+               spy->status = CHANSPY_STOP;
+               spy->chan = NULL;
+               /* Poke the spy if needed */
+               if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE)
+                       ast_cond_signal(&spy->trigger);
+       }
+
+       /* Print it out while we still have a lock so the structure can't go away (if signalled above) */
+       ast_log(LOG_DEBUG, "Spy %s removed from channel %s\n", spy->type, chan->name);
+
+       ast_mutex_unlock(&spy->lock);
+
+       return;
+}
+
 void ast_channel_spy_stop_by_type(struct ast_channel *chan, const char *type)
 {
-       struct ast_channel_spy *spy;
+       struct ast_channel_spy *spy = NULL;
        
        if (!chan->spies)
                return;
 
-       AST_LIST_TRAVERSE(&chan->spies->list, spy, list) {
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->spies->list, spy, list) {
                ast_mutex_lock(&spy->lock);
                if ((spy->type == type) && (spy->status == CHANSPY_RUNNING)) {
-                       spy->status = CHANSPY_STOP;
-                       if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE)
-                               ast_cond_signal(&spy->trigger);
-               }
-               ast_mutex_unlock(&spy->lock);
+                       ast_mutex_unlock(&spy->lock);
+                       AST_LIST_REMOVE_CURRENT(&chan->spies->list, list);
+                       spy_detach(spy, chan);
+               } else
+                       ast_mutex_unlock(&spy->lock);
        }
+       AST_LIST_TRAVERSE_SAFE_END
+       spy_cleanup(chan);
 }
 
 void ast_channel_spy_trigger_wait(struct ast_channel_spy *spy)
@@ -1230,62 +1288,54 @@ void ast_channel_spy_trigger_wait(struct ast_channel_spy *spy)
 
 void ast_channel_spy_remove(struct ast_channel *chan, struct ast_channel_spy *spy)
 {
-       struct ast_frame *f;
-
        if (!chan->spies)
                return;
 
        AST_LIST_REMOVE(&chan->spies->list, spy, list);
+       spy_detach(spy, chan);
+       spy_cleanup(chan);
+}
 
-       ast_mutex_lock(&spy->lock);
+void ast_channel_spy_free(struct ast_channel_spy *spy)
+{
+       struct ast_frame *f = NULL;
 
-       spy->chan = NULL;
+       if (spy->status == CHANSPY_DONE)
+               return;
 
-       while ((f = AST_LIST_REMOVE_HEAD(&spy->read_queue.list, frame_list)))
-               ast_frfree(f);
-       
+       /* Switch status to done in case we get called twice */
+       spy->status = CHANSPY_DONE;
+
+       /* Drop any frames in the queue */
        while ((f = AST_LIST_REMOVE_HEAD(&spy->write_queue.list, frame_list)))
                ast_frfree(f);
+       while ((f = AST_LIST_REMOVE_HEAD(&spy->read_queue.list, frame_list)))
+               ast_frfree(f);
 
+       /* Destroy the condition if in use */
        if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE)
                ast_cond_destroy(&spy->trigger);
 
-       ast_mutex_unlock(&spy->lock);
-
-       ast_log(LOG_DEBUG, "Spy %s removed from channel %s\n",
-               spy->type, chan->name);
+       /* Destroy our mutex since it is no longer in use */
+       ast_mutex_destroy(&spy->lock);
 
-       if (AST_LIST_EMPTY(&chan->spies->list)) {
-               if (chan->spies->read_translator.path)
-                       ast_translator_free_path(chan->spies->read_translator.path);
-               if (chan->spies->write_translator.path)
-                       ast_translator_free_path(chan->spies->write_translator.path);
-               free(chan->spies);
-               chan->spies = NULL;
-       }
+       return;
 }
 
 static void detach_spies(struct ast_channel *chan)
 {
-       struct ast_channel_spy *spy;
+       struct ast_channel_spy *spy = NULL;
 
        if (!chan->spies)
                return;
 
-       /* Marking the spies as done is sufficient.  Chanspy or spy users will get the picture. */
-       AST_LIST_TRAVERSE(&chan->spies->list, spy, list) {
-               ast_mutex_lock(&spy->lock);
-               spy->chan = NULL;
-               if (spy->status == CHANSPY_RUNNING)
-                       spy->status = CHANSPY_DONE;
-               if (ast_test_flag(spy, CHANSPY_TRIGGER_MODE) != CHANSPY_TRIGGER_NONE)
-                       ast_cond_signal(&spy->trigger);
-               ast_mutex_unlock(&spy->lock);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->spies->list, spy, list) {
+               AST_LIST_REMOVE_CURRENT(&chan->spies->list, list);
+               spy_detach(spy, chan);
        }
+       AST_LIST_TRAVERSE_SAFE_END
 
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&chan->spies->list, spy, list)
-               ast_channel_spy_remove(chan, spy);
-       AST_LIST_TRAVERSE_SAFE_END;
+       spy_cleanup(chan);
 }
 
 /*! \brief Softly hangup a channel, don't lock */
@@ -1329,6 +1379,7 @@ static void queue_frame_to_spies(struct ast_channel *chan, struct ast_frame *f,
 
        AST_LIST_TRAVERSE(&chan->spies->list, spy, list) {
                struct ast_channel_spy_queue *queue;
+               struct ast_frame *duped_fr;
 
                ast_mutex_lock(&spy->lock);
 
@@ -1359,17 +1410,17 @@ static void queue_frame_to_spies(struct ast_channel *chan, struct ast_frame *f,
                                        break;
                                }
                        }
-                       AST_LIST_INSERT_TAIL(&queue->list, ast_frdup(translated_frame), frame_list);
-               } else {
-                       if (f->subclass != queue->format) {
-                               ast_log(LOG_WARNING, "Spy '%s' on channel '%s' wants format '%s', but frame is '%s', dropping\n",
-                                       spy->type, chan->name,
-                                       ast_getformatname(queue->format), ast_getformatname(f->subclass));
-                               ast_mutex_unlock(&spy->lock);
-                               continue;
-                       }
-                       AST_LIST_INSERT_TAIL(&queue->list, ast_frdup(f), frame_list);
-               }
+                       duped_fr = ast_frdup(translated_frame);
+               } else if (f->subclass != queue->format) {
+                       ast_log(LOG_WARNING, "Spy '%s' on channel '%s' wants format '%s', but frame is '%s', dropping\n",
+                               spy->type, chan->name,
+                               ast_getformatname(queue->format), ast_getformatname(f->subclass));
+                       ast_mutex_unlock(&spy->lock);
+                       continue;
+               } else
+                       duped_fr = ast_frdup(f);
+
+               AST_LIST_INSERT_TAIL(&queue->list, duped_fr, frame_list);
 
                queue->samples += f->samples;
 
@@ -1683,7 +1734,7 @@ struct ast_channel *ast_waitfor_nandfds(struct ast_channel **c, int n, int *fds,
        /* Wait full interval */
        rms = *ms;
        if (whentohangup) {
-               rms = (whentohangup - now) * 1000;      /* timeout in milliseconds */
+               rms = whentohangup * 1000;              /* timeout in milliseconds */
                if (*ms >= 0 && *ms < rms)              /* original *ms still smaller */
                        rms =  *ms;
        }
@@ -1857,13 +1908,17 @@ int ast_waitfordigit_full(struct ast_channel *c, int ms, int audiofd, int cmdfd)
                                        break;
                                default:
                                        ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", f->subclass);
+                                       break;
                                }
+                               break;
                        case AST_FRAME_VOICE:
                                /* Write audio if appropriate */
                                if (audiofd > -1)
                                        write(audiofd, f->data, f->datalen);
+                       default:
+                               /* Ignore */
+                               break;
                        }
-                       /* Ignore */
                        ast_frfree(f);
                }
        }
@@ -1881,11 +1936,10 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
         */
        ast_channel_lock(chan);
        if (chan->masq) {
-               if (ast_do_masquerade(chan)) {
+               if (ast_do_masquerade(chan))
                        ast_log(LOG_WARNING, "Failed to perform masquerade\n");
-               } else {
+               else
                        f =  &ast_null_frame;
-               }
                goto done;
        }
 
@@ -1897,13 +1951,17 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
        }
        prestate = chan->_state;
 
-       if (!ast_test_flag(chan, AST_FLAG_DEFER_DTMF) && !ast_strlen_zero(chan->dtmfq)) {
+       if (!ast_test_flag(chan, AST_FLAG_DEFER_DTMF | AST_FLAG_EMULATE_DTMF | AST_FLAG_IN_DTMF) && 
+           !ast_strlen_zero(chan->dtmfq)) {
                /* We have DTMF that has been deferred.  Return it now */
-               chan->dtmff.frametype = AST_FRAME_DTMF;
+               chan->dtmff.frametype = AST_FRAME_DTMF_BEGIN;
                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;
+               ast_set_flag(chan, AST_FLAG_EMULATE_DTMF);
+               chan->emulate_dtmf_digit = f->subclass;
+               chan->emulate_dtmf_samples = AST_DEFAULT_EMULATE_DTMF_SAMPLES;
                goto done;
        }
        
@@ -2017,27 +2075,57 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
                                }
                        }
                        break;
-               case AST_FRAME_DTMF:
-                       ast_log(LOG_DTMF, "DTMF '%c' received on %s\n", f->subclass, chan->name);
-                       if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF)) {
+               case AST_FRAME_DTMF_END:
+                       ast_log(LOG_DTMF, "DTMF end '%c' received on %s\n", f->subclass, chan->name);
+                       if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF | AST_FLAG_EMULATE_DTMF)) {
                                if (strlen(chan->dtmfq) < sizeof(chan->dtmfq) - 2)
                                        chan->dtmfq[strlen(chan->dtmfq)] = f->subclass;
                                else
                                        ast_log(LOG_WARNING, "Dropping deferred DTMF digits on %s\n", chan->name);
                                ast_frfree(f);
                                f = &ast_null_frame;
-                       }
+                       } else if (!ast_test_flag(chan, AST_FLAG_IN_DTMF)) {
+                               f->frametype = AST_FRAME_DTMF_BEGIN;
+                               ast_set_flag(chan, AST_FLAG_EMULATE_DTMF);
+                               chan->emulate_dtmf_digit = f->subclass;
+                               if (f->samples)
+                                       chan->emulate_dtmf_samples = f->samples;
+                               else
+                                       chan->emulate_dtmf_samples = AST_DEFAULT_EMULATE_DTMF_SAMPLES;
+                       } else 
+                               ast_clear_flag(chan, AST_FLAG_IN_DTMF);
                        break;
                case AST_FRAME_DTMF_BEGIN:
                        ast_log(LOG_DTMF, "DTMF begin '%c' received on %s\n", f->subclass, chan->name);
-                       break;
-               case AST_FRAME_DTMF_END:
-                       ast_log(LOG_DTMF, "DTMF end '%c' received on %s\n", f->subclass, chan->name);
+                       if (ast_test_flag(chan, AST_FLAG_DEFER_DTMF)) {
+                               ast_frfree(f);
+                               f = &ast_null_frame;
+                       } else 
+                               ast_set_flag(chan, AST_FLAG_IN_DTMF);
                        break;
                case AST_FRAME_VOICE:
-                       if (dropaudio) {
+                       /* The EMULATE_DTMF flag must be cleared here as opposed to when the samples
+                        * first get to zero, because we want to make sure we pass at least one
+                        * voice frame through before starting the next digit, to ensure a gap
+                        * between DTMF digits. */
+                       if (ast_test_flag(chan, AST_FLAG_EMULATE_DTMF) && !chan->emulate_dtmf_samples) {
+                               ast_clear_flag(chan, AST_FLAG_EMULATE_DTMF);
+                               chan->emulate_dtmf_digit = 0;
+                       }
+
+                       if (dropaudio || ast_test_flag(chan, AST_FLAG_IN_DTMF)) {
                                ast_frfree(f);
                                f = &ast_null_frame;
+                       } else if (ast_test_flag(chan, AST_FLAG_EMULATE_DTMF)) {
+                               if (f->samples >= chan->emulate_dtmf_samples) {
+                                       chan->emulate_dtmf_samples = 0;
+                                       f->frametype = AST_FRAME_DTMF_END;
+                                       f->subclass = chan->emulate_dtmf_digit;
+                               } else {
+                                       chan->emulate_dtmf_samples -= f->samples;
+                                       ast_frfree(f);
+                                       f = &ast_null_frame;
+                               }
                        } else if (!(f->subclass & chan->nativeformats)) {
                                /* This frame can't be from the current native formats -- drop it on the
                                   floor */
@@ -2106,6 +2194,9 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
                                        }
                                }
                        }
+               default:
+                       /* Just pass it on! */
+                       break;
                }
        } else {
                /* Make sure we always return NULL in the future */
@@ -2258,12 +2349,13 @@ int ast_sendtext(struct ast_channel *chan, const char *text)
        return res;
 }
 
-static int do_senddigit(struct ast_channel *chan, char digit)
+int ast_senddigit_begin(struct ast_channel *chan, char digit)
 {
        int res = -1;
 
-       if (chan->tech->send_digit)
-               res = chan->tech->send_digit(chan, digit);
+       if (chan->tech->send_digit_begin)
+               res = chan->tech->send_digit_begin(chan, digit);
+
        if (res) {
                /*
                 * Device does not support DTMF tones, lets fake
@@ -2299,12 +2391,30 @@ static int do_senddigit(struct ast_channel *chan, char digit)
                        ast_log(LOG_DEBUG, "Unable to generate DTMF tone '%c' for '%s'\n", digit, chan->name);
                }
        }
+
+       return 0;
+}
+
+int ast_senddigit_end(struct ast_channel *chan, char digit)
+{
+       int res = -1;
+
+       if (chan->tech->send_digit_end)
+               res = chan->tech->send_digit_end(chan, digit);
+
+       if (res && chan->generator)
+               ast_playtones_stop(chan);
+       
        return 0;
 }
 
 int ast_senddigit(struct ast_channel *chan, char digit)
 {
-       return do_senddigit(chan, digit);
+       ast_senddigit_begin(chan, digit);
+       
+       ast_safe_sleep(chan, 100); /* XXX 100ms ... probably should be configurable */
+       
+       return ast_senddigit_end(chan, digit);
 }
 
 int ast_prod(struct ast_channel *chan)
@@ -2372,17 +2482,16 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                        chan->tech->indicate(chan, fr->subclass, fr->data, fr->datalen);
                break;
        case AST_FRAME_DTMF_BEGIN:
-               res = (chan->tech->send_digit_begin == NULL) ? 0 :
-                       chan->tech->send_digit_begin(chan, fr->subclass);
+               ast_clear_flag(chan, AST_FLAG_BLOCKING);
+               ast_channel_unlock(chan);
+               res = ast_senddigit_begin(chan, fr->subclass);
+               ast_channel_lock(chan);
+               CHECK_BLOCKING(chan);
                break;
        case AST_FRAME_DTMF_END:
-               res = (chan->tech->send_digit_end == NULL) ? 0 :
-                       chan->tech->send_digit_end(chan);
-               break;
-       case AST_FRAME_DTMF:
                ast_clear_flag(chan, AST_FLAG_BLOCKING);
                ast_channel_unlock(chan);
-               res = do_senddigit(chan,fr->subclass);
+               res = ast_senddigit_end(chan, fr->subclass);
                ast_channel_lock(chan);
                CHECK_BLOCKING(chan);
                break;
@@ -2467,7 +2576,15 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
 
                        res = chan->tech->write(chan, f);
                }
-               break;  
+               break;
+       case AST_FRAME_NULL:
+       case AST_FRAME_IAX:
+               /* Ignore these */
+               res = 0;
+               break;
+       default:
+               res = chan->tech->write(chan, f);
+               break;
        }
 
        if (f && f != fr)
@@ -2511,7 +2628,7 @@ static int set_format(struct ast_channel *chan, int fmt, int *rawformat, int *fo
        /* Now we have a good choice for both. */
        ast_channel_lock(chan);
 
-       if ((*rawformat == native) && (*format == fmt)) {
+       if ((*rawformat == native) && (*format == fmt) && ((*rawformat == *format) || (*trans))) {
                /* the channel is already in these formats, so nothing to do */
                ast_channel_unlock(chan);
                return 0;
@@ -3051,6 +3168,8 @@ int ast_do_masquerade(struct ast_channel *original)
        void *t_pvt;
        struct ast_callerid tmpcid;
        struct ast_channel *clone = original->masq;
+       struct ast_channel_spy_list *spy_list = NULL;
+       struct ast_channel_spy *spy = NULL;
        int rformat = original->readformat;
        int wformat = original->writeformat;
        char newn[100];
@@ -3129,6 +3248,27 @@ int ast_do_masquerade(struct ast_channel *original)
        original->rawwriteformat = clone->rawwriteformat;
        clone->rawwriteformat = x;
 
+       /* Swap the spies */
+       spy_list = original->spies;
+       original->spies = clone->spies;
+       clone->spies = spy_list;
+
+       /* Update channel on respective spy lists if present */
+       if (original->spies) {
+               AST_LIST_TRAVERSE(&original->spies->list, spy, list) {
+                       ast_mutex_lock(&spy->lock);
+                       spy->chan = original;
+                       ast_mutex_unlock(&spy->lock);
+               }
+       }
+       if (clone->spies) {
+               AST_LIST_TRAVERSE(&clone->spies->list, spy, list) {
+                       ast_mutex_lock(&spy->lock);
+                       spy->chan = clone;
+                       ast_mutex_unlock(&spy->lock);
+               }
+       }
+
        /* Save any pending frames on both sides.  Start by counting
         * how many we're going to need... */
        x = 0;
@@ -3496,6 +3636,7 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
                                break;
                }
                if ((f->frametype == AST_FRAME_VOICE) ||
+                   (f->frametype == AST_FRAME_DTMF_BEGIN) ||
                    (f->frametype == AST_FRAME_DTMF) ||
                    (f->frametype == AST_FRAME_VIDEO) ||
                    (f->frametype == AST_FRAME_IMAGE) ||
@@ -3505,10 +3646,14 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
                        /* monitored dtmf causes exit from bridge */
                        int monitored_source = (who == c0) ? watch_c0_dtmf : watch_c1_dtmf;
 
-                       if (f->frametype == AST_FRAME_DTMF && monitored_source) {
+                       if (monitored_source && 
+                               (f->frametype == AST_FRAME_DTMF_END || 
+                               f->frametype == AST_FRAME_DTMF_BEGIN)) {
                                *fo = f;
                                *rc = who;
-                               ast_log(LOG_DEBUG, "Got DTMF on channel (%s)\n", who->name);
+                               ast_log(LOG_DEBUG, "Got DTMF %s on channel (%s)\n", 
+                                       f->frametype == AST_FRAME_DTMF_END ? "end" : "begin",
+                                       who->name);
                                break;
                        }
                        /* Write immediately frames, not passed through jb */
@@ -3530,6 +3675,16 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
        return res;
 }
 
+/*! \brief Bridge two channels together (early) */
+int ast_channel_early_bridge(struct ast_channel *c0, struct ast_channel *c1)
+{
+       /* Make sure we can early bridge, if not error out */
+       if (!c0->tech->early_bridge || (c1 && (!c1->tech->early_bridge || c0->tech->early_bridge != c1->tech->early_bridge)))
+               return -1;
+
+       return c0->tech->early_bridge(c0, c1);
+}
+
 /*! \brief Bridge two channels together */
 enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1,
                                          struct ast_bridge_config *config, struct ast_frame **fo, struct ast_channel **rc)
@@ -4018,8 +4173,7 @@ void ast_moh_cleanup(struct ast_channel *chan)
 
 void ast_channels_init(void)
 {
-       ast_cli_register(&cli_show_channeltypes);
-       ast_cli_register(&cli_show_channeltype);
+       ast_cli_register_multiple(cli_channel, sizeof(cli_channel) / sizeof(struct ast_cli_entry));
 }
 
 /*! \brief Print call group and pickup group ---*/