channel: Add support for writing to a specific stream.
authorJoshua Colp <jcolp@digium.com>
Wed, 22 Feb 2017 11:00:57 +0000 (11:00 +0000)
committerJoshua Colp <jcolp@digium.com>
Thu, 23 Feb 2017 18:31:15 +0000 (18:31 +0000)
This change adds an ast_write_stream function which allows
writing a frame to a specific media stream. It also moves
ast_write() to using this underneath by writing media
frames provided to it to the default streams of the channel.
Existing functionality (such as audiohooks, framehooks, etc)
are limited to being applied to the default stream only.

Unit tests have also been added which test the behavior of
both non-multistream and multistream channels to confirm that
the write() and write_stream() callbacks are invoked
appropriately.

ASTERISK-26793

Change-Id: I4df20d1b65bd4d787fce0b4b478e19d2dfea245c

include/asterisk/channel.h
main/channel.c
main/channel_internal_api.c
tests/test_stream.c

index 4170a8a..80476a4 100644 (file)
@@ -670,6 +670,9 @@ struct ast_channel_tech {
        /*! \brief Write a frame, in standard format (see frame.h) */
        int (* const write)(struct ast_channel *chan, struct ast_frame *frame);
 
+       /*! \brief Write a frame on a specific stream, in standard format (see frame.h) */
+       int (* const write_stream)(struct ast_channel *chan, int stream_num, struct ast_frame *frame);
+
        /*! \brief Display or transmit text */
        int (* const send_text)(struct ast_channel *chan, const char *text);
 
@@ -1968,6 +1971,18 @@ int ast_write_video(struct ast_channel *chan, struct ast_frame *frame);
  */
 int ast_write_text(struct ast_channel *chan, struct ast_frame *frame);
 
+/*!
+ * \brief Write a frame to a stream
+ * This function writes the given frame to the indicated stream on the channel.
+ * \param chan destination channel of the frame
+ * \param stream_num destination stream on the channel
+ * \param frame frame that will be written
+ * \return It returns 0 on success, -1 on failure.
+ * \note If -1 is provided as the stream number and a media frame is provided the
+ *       function will write to the default stream of the type of media.
+ */
+int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *frame);
+
 /*! \brief Send empty audio to prime a channel driver */
 int ast_prod(struct ast_channel *chan);
 
@@ -4768,4 +4783,17 @@ struct ast_stream_topology *ast_channel_get_stream_topology(
 struct ast_stream_topology *ast_channel_set_stream_topology(
        struct ast_channel *chan, struct ast_stream_topology *topology);
 
+/*!
+ * \brief Retrieve the default stream of a specific media type on a channel
+ *
+ * \param channel The channel to get the stream from
+ * \param type The media type of the default stream
+ *
+ * \pre chan is locked
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_stream *ast_channel_get_default_stream(struct ast_channel *chan, enum ast_media_type type);
+
 #endif /* _ASTERISK_CHANNEL_H */
index 1e7bc56..183f893 100644 (file)
@@ -5125,6 +5125,12 @@ static void apply_plc(struct ast_channel *chan, struct ast_frame *frame)
 
 int ast_write(struct ast_channel *chan, struct ast_frame *fr)
 {
+       return ast_write_stream(chan, -1, fr);
+}
+
+int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *fr)
+{
+       struct ast_stream *stream = NULL, *default_stream = NULL;
        int res = -1;
        struct ast_frame *f = NULL;
        int count = 0;
@@ -5139,13 +5145,28 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                }
                usleep(1);
        }
+
        /* Stop if we're a zombie or need a soft hangup */
-       if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) || ast_check_hangup(chan))
+       if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) || ast_check_hangup(chan)) {
                goto done;
+       }
+
+       /* If this frame is writing an audio or video frame get the stream information */
+       if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {
+               /* Initially use the default stream unless an explicit stream is provided */
+               stream = default_stream = ast_channel_get_default_stream(chan, ast_format_get_type(fr->subclass.format));
+
+               if (stream_num >= 0) {
+                       if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {
+                               goto done;
+                       }
+                       stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);
+               }
+       }
 
        /* Perform the framehook write event here. After the frame enters the framehook list
         * there is no telling what will happen, how awesome is that!!! */
-       if (!(fr = ast_framehook_list_write_event(ast_channel_framehooks(chan), fr))) {
+       if ((stream == default_stream) && !(fr = ast_framehook_list_write_event(ast_channel_framehooks(chan), fr))) {
                res = 0;
                goto done;
        }
@@ -5231,17 +5252,20 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                break;
        case AST_FRAME_VIDEO:
                /* XXX Handle translation of video codecs one day XXX */
-               res = (ast_channel_tech(chan)->write_video == NULL) ? 0 :
-                       ast_channel_tech(chan)->write_video(chan, fr);
+               if (ast_channel_tech(chan)->write_stream) {
+                       res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), fr);
+               } else if ((stream == default_stream) && ast_channel_tech(chan)->write_video) {
+                       res = ast_channel_tech(chan)->write_video(chan, fr);
+               } else {
+                       res = 0;
+
+               }
                break;
        case AST_FRAME_MODEM:
                res = (ast_channel_tech(chan)->write == NULL) ? 0 :
                        ast_channel_tech(chan)->write(chan, fr);
                break;
        case AST_FRAME_VOICE:
-               if (ast_channel_tech(chan)->write == NULL)
-                       break;  /*! \todo XXX should return 0 maybe ? */
-
                if (ast_opt_generic_plc && ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
                        apply_plc(chan, fr);
                }
@@ -5250,7 +5274,7 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                 * Send frame to audiohooks if present, if frametype is linear (else, later as per
                 * previous behavior)
                 */
-               if (ast_channel_audiohooks(chan)) {
+               if ((stream == default_stream) && ast_channel_audiohooks(chan)) {
                        if (ast_format_cache_is_slinear(fr->subclass.format)) {
                                struct ast_frame *old_frame;
                                hooked = 1;
@@ -5263,7 +5287,7 @@ 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_EQUAL) {
+               if ((stream != default_stream) || ast_format_cmp(fr->subclass.format, ast_channel_rawwriteformat(chan)) == AST_FORMAT_CMP_EQUAL) {
                        f = fr;
                } else {
                        if (ast_format_cmp(ast_channel_writeformat(chan), fr->subclass.format) != AST_FORMAT_CMP_EQUAL) {
@@ -5299,7 +5323,7 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                        break;
                }
 
-               if (ast_channel_audiohooks(chan) && !hooked) {
+               if ((stream == default_stream) && ast_channel_audiohooks(chan) && !hooked) {
                        struct ast_frame *prev = NULL, *new_frame, *cur, *dup;
                        int freeoldlist = 0;
 
@@ -5348,7 +5372,7 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                /* the translator on chan->writetrans may have returned multiple frames
                   from the single frame we passed in; if so, feed each one of them to the
                   monitor */
-               if (ast_channel_monitor(chan) && ast_channel_monitor(chan)->write_stream) {
+               if ((stream == default_stream) && ast_channel_monitor(chan) && ast_channel_monitor(chan)->write_stream) {
                        struct ast_frame *cur;
 
                        for (cur = f; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
@@ -5415,7 +5439,13 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
                        /* reset f so the code below doesn't attempt to free it */
                        f = NULL;
                } else {
-                       res = ast_channel_tech(chan)->write(chan, f);
+                       if (ast_channel_tech(chan)->write_stream) {
+                               res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), f);
+                       } else if ((stream == default_stream) && ast_channel_tech(chan)->write) {
+                               res = ast_channel_tech(chan)->write(chan, f);
+                       } else {
+                               res = 0;
+                       }
                }
                break;
        case AST_FRAME_NULL:
index 1934eb9..362bd1a 100644 (file)
@@ -1816,6 +1816,15 @@ struct ast_stream_topology *ast_channel_set_stream_topology(struct ast_channel *
        return new_topology;
 }
 
+struct ast_stream *ast_channel_get_default_stream(struct ast_channel *chan,
+       enum ast_media_type type)
+{
+       ast_assert(chan != NULL);
+       ast_assert(type < AST_MEDIA_TYPE_END);
+
+       return chan->default_streams[type];
+}
+
 void ast_channel_internal_swap_stream_topology(struct ast_channel *chan1,
        struct ast_channel *chan2)
 {
index 5134cfb..d602d52 100644 (file)
@@ -853,6 +853,421 @@ AST_TEST_DEFINE(stream_topology_channel_set)
        return res;
 }
 
+struct mock_channel_pvt {
+       unsigned int wrote;
+       unsigned int wrote_stream;
+       int stream_num;
+};
+
+static int mock_channel_write(struct ast_channel *chan, struct ast_frame *fr)
+{
+       struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
+
+       pvt->wrote = 1;
+
+       return 0;
+}
+
+static int mock_channel_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *fr)
+{
+       struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
+
+       pvt->wrote_stream = 1;
+       pvt->stream_num = stream_num;
+
+       return 0;
+}
+
+static int mock_channel_hangup(struct ast_channel *chan)
+{
+       ast_channel_tech_pvt_set(chan, NULL);
+       return 0;
+}
+
+static const struct ast_channel_tech mock_channel_old_write_tech = {
+       .write = mock_channel_write,
+       .write_video = mock_channel_write,
+       .hangup = mock_channel_hangup,
+};
+
+AST_TEST_DEFINE(stream_write_non_multistream)
+{
+       RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+       struct ast_channel *mock_channel;
+       struct mock_channel_pvt pvt;
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_frame frame = { 0, };
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "stream_write_non_multistream";
+               info->category = "/main/stream/";
+               info->summary = "stream writing to non-multistream capable channel test";
+               info->description =
+                       "Test that writing frames to a non-multistream channel works as expected";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+       if (!caps) {
+               ast_test_status_update(test, "Could not allocate an empty format capabilities structure\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_format_cap_append(caps, ast_format_ulaw, 0)) {
+               ast_test_status_update(test, "Failed to append a ulaw format to capabilities for channel nativeformats\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_format_cap_append(caps, ast_format_h264, 0)) {
+               ast_test_status_update(test, "Failed to append an h264 format to capabilities for channel nativeformats\n");
+               return AST_TEST_FAIL;
+       }
+
+       mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
+       if (!mock_channel) {
+               ast_test_status_update(test, "Failed to create a mock channel for testing\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_channel_tech_set(mock_channel, &mock_channel_old_write_tech);
+       ast_channel_nativeformats_set(mock_channel, caps);
+
+       pvt.wrote = 0;
+       ast_channel_tech_pvt_set(mock_channel, &pvt);
+       ast_channel_unlock(mock_channel);
+
+       frame.frametype = AST_FRAME_VOICE;
+       frame.subclass.format = ast_format_ulaw;
+
+       if (ast_write(mock_channel, &frame)) {
+               ast_test_status_update(test, "Failed to write a ulaw frame to the mock channel when it should be fine\n");
+               goto end;
+       }
+
+       if (!pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw but it never reached the channel driver\n");
+               goto end;
+       }
+
+       pvt.wrote = 0;
+
+       if (!ast_write_stream(mock_channel, 2, &frame) || pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to a non-existent stream\n");
+               goto end;
+       }
+
+       frame.frametype = AST_FRAME_VIDEO;
+       frame.subclass.format = ast_format_h264;
+
+       if (ast_write(mock_channel, &frame)) {
+               ast_test_status_update(test, "Failed to write an h264 frame to the mock channel when it should be fine\n");
+               goto end;
+       }
+
+       if (!pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 but it never reached the channel driver\n");
+               goto end;
+       }
+
+       res = AST_TEST_PASS;
+
+end:
+       ast_hangup(mock_channel);
+
+       return res;
+}
+
+static const struct ast_channel_tech mock_channel_write_stream_tech = {
+       .properties = AST_CHAN_TP_MULTISTREAM,
+       .write = mock_channel_write,
+       .write_video = mock_channel_write,
+       .write_stream = mock_channel_write_stream,
+       .hangup = mock_channel_hangup,
+};
+
+AST_TEST_DEFINE(stream_write_multistream)
+{
+       RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
+       struct ast_stream *stream;
+       struct ast_channel *mock_channel;
+       struct mock_channel_pvt pvt = { 0, };
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_frame frame = { 0, };
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "stream_write_multistream";
+               info->category = "/main/stream/";
+               info->summary = "stream writing to multistream capable channel test";
+               info->description =
+                       "Test that writing frames to a multistream channel works as expected";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       topology = ast_stream_topology_alloc();
+       if (!topology) {
+               ast_test_status_update(test, "Failed to create media stream topology\n");
+               return AST_TEST_FAIL;
+       }
+
+       stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
+       if (!stream) {
+               ast_test_status_update(test, "Failed to create an audio stream for testing multistream writing\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_stream_topology_append_stream(topology, stream) == -1) {
+               ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+               ast_stream_free(stream);
+               return AST_TEST_FAIL;
+       }
+
+       stream = ast_stream_alloc("audio2", AST_MEDIA_TYPE_AUDIO);
+       if (!stream) {
+               ast_test_status_update(test, "Failed to create an audio stream for testing multistream writing\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_stream_topology_append_stream(topology, stream) == -1) {
+               ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+               ast_stream_free(stream);
+               return AST_TEST_FAIL;
+       }
+
+       stream = ast_stream_alloc("video", AST_MEDIA_TYPE_VIDEO);
+       if (!stream) {
+               ast_test_status_update(test, "Failed to create a video stream for testing multistream writing\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_stream_topology_append_stream(topology, stream) == -1) {
+               ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+               ast_stream_free(stream);
+               return AST_TEST_FAIL;
+       }
+
+       stream = ast_stream_alloc("video2", AST_MEDIA_TYPE_VIDEO);
+       if (!stream) {
+               ast_test_status_update(test, "Failed to create a video stream for testing multistream writing\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_stream_topology_append_stream(topology, stream) == -1) {
+               ast_test_status_update(test, "Failed to append a perfectly good stream to a topology\n");
+               ast_stream_free(stream);
+               return AST_TEST_FAIL;
+       }
+
+       caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+       if (!caps) {
+               ast_test_status_update(test, "Could not allocate an empty format capabilities structure\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_format_cap_append(caps, ast_format_ulaw, 0)) {
+               ast_test_status_update(test, "Failed to append a ulaw format to capabilities for channel nativeformats\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_format_cap_append(caps, ast_format_h264, 0)) {
+               ast_test_status_update(test, "Failed to append an h264 format to capabilities for channel nativeformats\n");
+               return AST_TEST_FAIL;
+       }
+
+       mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
+       if (!mock_channel) {
+               ast_test_status_update(test, "Failed to create a mock channel for testing\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_channel_tech_set(mock_channel, &mock_channel_write_stream_tech);
+       ast_channel_set_stream_topology(mock_channel, topology);
+       ast_channel_nativeformats_set(mock_channel, caps);
+       topology = NULL;
+
+       ast_channel_tech_pvt_set(mock_channel, &pvt);
+       ast_channel_unlock(mock_channel);
+
+       frame.frametype = AST_FRAME_VOICE;
+       frame.subclass.format = ast_format_ulaw;
+       pvt.stream_num = -1;
+
+       if (ast_write(mock_channel, &frame)) {
+               ast_test_status_update(test, "Failed to write a ulaw frame to the mock channel when it should be fine\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 0) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the default stream but it ended up on stream %d and not 0\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       if (ast_write_stream(mock_channel, 0, &frame)) {
+               ast_test_status_update(test, "Failed to write a ulaw frame to the first audio stream\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the first audio stream but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the first audio stream but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 0) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the first audio stream but it ended up on stream %d and not 0\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       if (ast_write_stream(mock_channel, 1, &frame)) {
+               ast_test_status_update(test, "Failed to write a ulaw frame to the second audio stream\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the second audio stream but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the second audio stream but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 1) {
+               ast_test_status_update(test, "Successfully wrote a frame of ulaw to the second audio stream but it ended up on stream %d and not 1\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       frame.frametype = AST_FRAME_VIDEO;
+       frame.subclass.format = ast_format_h264;
+
+       if (ast_write(mock_channel, &frame)) {
+               ast_test_status_update(test, "Failed to write an h264 frame to the mock channel when it should be fine\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 2) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the default stream but it ended up on stream %d and not 2\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       if (ast_write_stream(mock_channel, 2, &frame)) {
+               ast_test_status_update(test, "Failed to write an h264 frame to the first video stream\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the first video stream but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the first video stream but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 2) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the first video stream but it ended up on stream %d and not 2\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       if (ast_write_stream(mock_channel, 3, &frame)) {
+               ast_test_status_update(test, "Failed to write an h264 frame to the second video stream\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the second video stream but it ended up on the old write callback instead of write_stream\n");
+               goto end;
+       }
+
+       if (!pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the second video stream but it never reached the channel driver\n");
+               goto end;
+       }
+
+       if (pvt.stream_num != 3) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to the second video stream but it ended up on stream %d and not 3\n",
+                       pvt.stream_num);
+               goto end;
+       }
+
+       pvt.wrote_stream = 0;
+       pvt.stream_num = -1;
+
+       if (!ast_write_stream(mock_channel, 9, &frame)) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to a non-existent stream\n");
+               goto end;
+       }
+
+       if (pvt.wrote) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to a non-existent stream and it ended up on the old write callback\n");
+               goto end;
+       }
+
+       if (pvt.wrote_stream) {
+               ast_test_status_update(test, "Successfully wrote a frame of h264 to a non-existent stream and it ended up on the write_stream callback\n");
+               goto end;
+       }
+
+       res = AST_TEST_PASS;
+
+end:
+       ast_hangup(mock_channel);
+
+       return res;
+}
+
 static int unload_module(void)
 {
        AST_TEST_UNREGISTER(stream_create);
@@ -869,6 +1284,8 @@ static int unload_module(void)
        AST_TEST_UNREGISTER(stream_topology_get_first_stream_by_type);
        AST_TEST_UNREGISTER(stream_topology_create_from_channel_nativeformats);
        AST_TEST_UNREGISTER(stream_topology_channel_set);
+       AST_TEST_UNREGISTER(stream_write_non_multistream);
+       AST_TEST_UNREGISTER(stream_write_multistream);
        return 0;
 }
 
@@ -887,6 +1304,8 @@ static int load_module(void)
        AST_TEST_REGISTER(stream_topology_get_first_stream_by_type);
        AST_TEST_REGISTER(stream_topology_create_from_channel_nativeformats);
        AST_TEST_REGISTER(stream_topology_channel_set);
+       AST_TEST_REGISTER(stream_write_non_multistream);
+       AST_TEST_REGISTER(stream_write_multistream);
        return AST_MODULE_LOAD_SUCCESS;
 }