diff options
-rw-r--r-- | include/asterisk/channel.h | 28 | ||||
-rw-r--r-- | main/channel.c | 54 | ||||
-rw-r--r-- | main/channel_internal_api.c | 9 | ||||
-rw-r--r-- | tests/test_stream.c | 419 |
4 files changed, 498 insertions, 12 deletions
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 4170a8af4..80476a4e0 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -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 */ diff --git a/main/channel.c b/main/channel.c index 1e7bc563e..183f8936f 100644 --- a/main/channel.c +++ b/main/channel.c @@ -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: diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 1934eb9a4..362bd1a3d 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -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) { diff --git a/tests/test_stream.c b/tests/test_stream.c index 5134cfb50..d602d52fe 100644 --- a/tests/test_stream.c +++ b/tests/test_stream.c @@ -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; } |