Skip to content
Snippets Groups Projects
test_stream.c 72.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	}
    	for (i = 0; i < pvt->frames_per_read && pvt->frame_count < pvt->frame_limit; i++) {
    		struct ast_frame *fr;
    
    		if (pvt->frame_count % 2 == 0) {
    			f.frametype = AST_FRAME_VOICE;
    			f.subclass.format = ast_format_ulaw;
    		} else {
    			f.frametype = AST_FRAME_VIDEO;
    			f.subclass.format = ast_format_h264;
    		}
    		f.seqno = pvt->frame_count;
    		f.stream_num = pvt->frame_count % pvt->streams;
    		pvt->frame_count++;
    		fr = ast_frdup(&f);
    		if (!head_frame) {
    			head_frame = fr;
    		} else  {
    			tail_frame->frame_list.next = fr;
    		}
    		tail_frame = fr;
    	}
    
    	return(head_frame);
    }
    
    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_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
    {
    	struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
    
    	if (condition == AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE) {
    		pvt->indicated_change_request = 1;
    	} else if (condition == AST_CONTROL_STREAM_TOPOLOGY_CHANGED) {
    		pvt->indicated_changed = 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 const struct ast_channel_tech mock_stream_channel_tech = {
    
    	.read_stream = mock_channel_read,
    	.write_stream = mock_channel_write_stream,
    
    };
    
    AST_TEST_DEFINE(stream_topology_channel_set)
    {
    
    	RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
    
    	struct ast_channel *mock_channel;
    	enum ast_test_result_state res = AST_TEST_PASS;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_topology_channel_set";
    		info->category = "/main/stream/";
    		info->summary = "stream topology setting on a channel unit test";
    		info->description =
    			"Test that setting a stream topology on a channel works";
    		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;
    	}
    
    	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_stream_channel_tech);
    	ast_channel_set_stream_topology(mock_channel, topology);
    
    	if (ast_channel_get_stream_topology(mock_channel) != topology) {
    		ast_test_status_update(test, "Set an explicit stream topology on a channel but the returned one did not match it\n");
    		res = AST_TEST_FAIL;
    	}
    
    	topology = NULL;
    	ast_channel_unlock(mock_channel);
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    
    static int mock_channel_hangup(struct ast_channel *chan)
    
    {
    	struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
    
    
    	if (pvt->mallocd) {
    		ast_free(pvt);
    	}
    
    
    	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 = { 0, };
    
    	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 = {
    	.write = mock_channel_write,
    	.write_video = mock_channel_write,
    	.write_stream = mock_channel_write_stream,
    
    	.read_stream = mock_channel_read,
    
    	.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 load_stream_readqueue(struct ast_channel *chan, int frames)
    {
    	struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
    	struct ast_frame f = { 0, };
    	struct ast_frame *frame = NULL;
    	int i;
    
    	while ((frame = AST_LIST_REMOVE_HEAD(ast_channel_readq(chan), frame_list)))
    			ast_frfree(frame);
    
    	for (i = 0; i < frames; i++) {
    		if (pvt->frame_count % 2 == 0) {
    			f.frametype = AST_FRAME_VOICE;
    			f.subclass.format = ast_format_ulaw;
    		} else {
    			f.frametype = AST_FRAME_VIDEO;
    			f.subclass.format = ast_format_h264;
    		}
    		f.stream_num = pvt->frame_count % pvt->streams;
    		f.seqno = pvt->frame_count;
    
    		ast_queue_frame(chan, &f);
    
    		pvt->frame_count++;
    	}
    
    	return 0;
    }
    
    static struct ast_channel *make_channel(struct ast_test *test, int streams,
    		struct ast_channel_tech *tech)
    {
    	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
    	struct ast_channel *mock_channel = NULL;
    	struct mock_channel_pvt *pvt = NULL;
    	struct ast_stream_topology *topology = NULL;
    	struct ast_stream *stream;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	int i;
    
    	mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    	ast_channel_tech_set(mock_channel, tech);
    
    	if (tech->read_stream) {
    		topology = ast_stream_topology_alloc();
    		ast_test_validate_cleanup(test, topology, res, done);
    
    		for (i = 0; i < streams; i++) {
    			stream = ast_stream_alloc((i % 2 ? "video": "audio"), (i % 2 ? AST_MEDIA_TYPE_VIDEO : AST_MEDIA_TYPE_AUDIO));
    			ast_test_validate_cleanup(test, stream, res, done);
    			ast_test_validate_cleanup(test, ast_stream_topology_append_stream(topology, stream) == i, res, done);
    		}
    		ast_test_validate_cleanup(test, ast_stream_topology_get_count(topology) == streams, res, done);
    		ast_channel_set_stream_topology(mock_channel, topology);
    		topology = NULL;
    	} else {
    		caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
    		ast_test_validate_cleanup(test, caps, res, done);
    
    		ast_test_validate_cleanup(test, ast_format_cap_append(caps, ast_format_ulaw, 0) == 0, res, done);
    		ast_test_validate_cleanup(test, ast_format_cap_append(caps, ast_format_h264, 0) == 0, res, done);
    		ast_channel_nativeformats_set(mock_channel, caps);
    	}
    
    	pvt = ast_calloc(1, sizeof(*pvt));
    	ast_test_validate_cleanup(test, pvt, res, done);
    	pvt->mallocd = 1;
    	ast_channel_tech_pvt_set(mock_channel, pvt);
    
    	ast_channel_unlock(mock_channel);
    
    done:
    	ast_stream_topology_free(topology);
    	if (res == AST_TEST_FAIL && mock_channel) {
    		ast_hangup(mock_channel);
    	}
    
    	return mock_channel;
    }
    
    enum CHANNEL_READ_TYPE {
    	CHANNEL_READ,
    	CHANNEL_READ_STREAM
    };
    
    static struct ast_frame *read_from_chan(enum CHANNEL_READ_TYPE rt, struct ast_channel *chan)
    {
    	if (rt == CHANNEL_READ_STREAM) {
    		return ast_read_stream(chan);
    	} else {
    		return ast_read(chan);
    	}
    }
    
    static enum ast_test_result_state read_test(struct ast_test *test, struct ast_channel_tech *tech,
    		enum CHANNEL_READ_TYPE rt, int streams, int frames, int frames_per_read, int expected_nulls)
    {
    	struct ast_channel *mock_channel;
    	struct mock_channel_pvt *pvt;
    	struct ast_frame *fr = NULL;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	int i = 0;
    	int null_frames = 0;
    
    	ast_test_status_update(test, "ChanType: %s ReadType: %s Streams: %d Frames: %d Frames per read: %d Expected Nulls: %d\n",
    			tech->read_stream ? "MULTI" : "NON-MULTI",
    			rt == CHANNEL_READ_STREAM ? "STREAM" : "NON-STREAM",
    			streams, frames, frames_per_read, expected_nulls);
    	mock_channel = make_channel(test, 4, tech);
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    
    	pvt = ast_channel_tech_pvt(mock_channel);
    	pvt->frame_count = 0;
    	pvt->frame_limit = frames;
    	pvt->streams = streams;
    	pvt->frames_per_read = frames_per_read;
    
    	load_stream_readqueue(mock_channel, frames / 2);
    	ast_channel_fdno_set(mock_channel, 0);
    
    	while ((fr = read_from_chan(rt, mock_channel))) {
    		ast_channel_fdno_set(mock_channel, 0);
    		if (fr->frametype != AST_FRAME_NULL) {
    			ast_test_validate_cleanup(test, i == fr->seqno, res, done);
    			ast_test_validate_cleanup(test, fr->frametype == ( i % 2 ? AST_FRAME_VIDEO : AST_FRAME_VOICE), res, done);
    			ast_test_validate_cleanup(test, fr->stream_num == ( i % streams ), res, done);
    			ast_frfree(fr);
    		} else {
    			null_frames++;
    		}
    		fr = NULL;
    		i++;
    	}
    	ast_test_validate_cleanup(test, i == frames, res, done);
    	ast_test_validate_cleanup(test, null_frames == expected_nulls, res, done);
    
    done:
    	ast_test_status_update(test, "    Frames read: %d NULL frames: %d\n", i, null_frames);
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    AST_TEST_DEFINE(stream_read_non_multistream)
    {
    	struct ast_channel_tech tech = {
    		.read = mock_channel_read,
    		.hangup = mock_channel_hangup,
    	};
    
    	enum ast_test_result_state res = AST_TEST_PASS;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_read_non_multistream";
    		info->category = "/main/stream/";
    		info->summary = "stream reading from non-multistream capable channel test";
    		info->description =
    			"Test that reading frames from a non-multistream channel works as expected";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	res = read_test(test, &tech, CHANNEL_READ, 2, 16, 1, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "non multi, non read stream, 2 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 1, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "non multi, read stream, 2 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ, 2, 16, 3, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "non multi, non read stream, 2 stream, 3 frames per read");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 3, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "non multi, read stream, 2 stream, 3 frames per read");
    
    	return res;
    }
    
    AST_TEST_DEFINE(stream_read_multistream)
    {
    	struct ast_channel_tech tech = {
    		.read_stream = mock_channel_read,
    		.write_stream = mock_channel_write_stream,
    		.hangup = mock_channel_hangup,
    	};
    	enum ast_test_result_state res = AST_TEST_PASS;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_read_multistream";
    		info->category = "/main/stream/";
    		info->summary = "stream reading from multistream capable channel test";
    		info->description =
    			"Test that reading frames from a multistream channel works as expected";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	res = read_test(test, &tech, CHANNEL_READ, 2, 16, 1, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 2 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 1, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 2 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ, 4, 16, 1, 8);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 4 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 4, 16, 1, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 4 stream");
    
    	res = read_test(test, &tech, CHANNEL_READ, 2, 16, 3, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 2 stream, 3 frames per read");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 3, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 2 stream, 3 frames per read");
    
    	res = read_test(test, &tech, CHANNEL_READ, 4, 16, 3, 8);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 4 stream, 3 frames per read");
    
    	res = read_test(test, &tech, CHANNEL_READ_STREAM, 4, 16, 3, 0);
    	ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 4 stream, 3 frames per read");
    
    	return res;
    }
    
    
    AST_TEST_DEFINE(stream_topology_change_request_from_application_non_multistream)
    {
    	struct ast_channel_tech tech = {
    		.read = mock_channel_read,
    		.indicate = mock_channel_indicate,
    		.hangup = mock_channel_hangup,
    	};
    	struct ast_channel *mock_channel;
    	struct mock_channel_pvt *pvt;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	int change_res;
    	RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_topology_change_request_from_application_non_multistream";
    		info->category = "/main/stream/";
    		info->summary = "stream topology changing on non-multistream channel test";
    		info->description =
    			"Test that an application trying to change the stream topology of a non-multistream channel gets a failure";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	mock_channel = make_channel(test, 1, &tech);
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    
    	pvt = ast_channel_tech_pvt(mock_channel);
    	pvt->indicated_change_request = 0;
    	pvt->indicated_changed = 0;
    
    	topology = ast_stream_topology_alloc();
    	ast_test_validate_cleanup(test, topology, res, done);
    
    
    	change_res = ast_channel_request_stream_topology_change(mock_channel, topology, NULL);
    
    
    	ast_test_validate_cleanup(test, change_res == -1, res, done);
    	ast_test_validate_cleanup(test, !pvt->indicated_change_request, res, done);
    
    
    	ast_channel_lock(mock_channel);
    
    	change_res = ast_channel_stream_topology_changed(mock_channel, topology);
    
    	ast_channel_unlock(mock_channel);
    
    
    	ast_test_validate_cleanup(test, change_res == -1, res, done);
    	ast_test_validate_cleanup(test, !pvt->indicated_changed, res, done);
    
    done:
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    AST_TEST_DEFINE(stream_topology_change_request_from_channel_non_multistream)
    {
    	struct ast_channel_tech tech = {
    		.read_stream = mock_channel_read,
    		.write_stream = mock_channel_write_stream,
    		.indicate = mock_channel_indicate,
    		.hangup = mock_channel_hangup,
    	};
    	struct ast_channel *mock_channel;
    	struct mock_channel_pvt *pvt;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
    	struct ast_frame request_change = {
    		.frametype = AST_FRAME_CONTROL,
    		.subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE,
    	};
    	struct ast_frame *fr = NULL;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_topology_change_request_from_channel_non_multistream";
    		info->category = "/main/stream/";
    		info->summary = "channel requesting stream topology change to non-multistream application test";
    		info->description =
    			"Test that a channel requesting a stream topology change from a non-multistream application does not work";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	mock_channel = make_channel(test, 1, &tech);
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    
    	pvt = ast_channel_tech_pvt(mock_channel);
    	pvt->indicated_changed = 0;
    
    	topology = ast_stream_topology_alloc();
    	ast_test_validate_cleanup(test, topology, res, done);
    
    	request_change.data.ptr = topology;
    	ast_queue_frame(mock_channel, &request_change);
    
    	fr = ast_read(mock_channel);
    	ast_test_validate_cleanup(test, fr, res, done);
    	ast_test_validate_cleanup(test, fr == &ast_null_frame, res, done);
    	ast_test_validate_cleanup(test, pvt->indicated_changed, res, done);
    
    done:
    	if (fr) {
    		ast_frfree(fr);
    	}
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    AST_TEST_DEFINE(stream_topology_change_request_from_application)
    {
    	struct ast_channel_tech tech = {
    		.read_stream = mock_channel_read,
    		.write_stream = mock_channel_write_stream,
    		.indicate = mock_channel_indicate,
    		.hangup = mock_channel_hangup,
    	};
    	struct ast_channel *mock_channel;
    	struct mock_channel_pvt *pvt;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	int change_res;
    	RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_topology_change_request_from_application";
    		info->category = "/main/stream/";
    		info->summary = "stream topology change request from application test";
    		info->description =
    			"Test that an application changing the stream topology of a multistream capable channel receives success";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	mock_channel = make_channel(test, 1, &tech);
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    
    	pvt = ast_channel_tech_pvt(mock_channel);
    	pvt->indicated_change_request = 0;
    	pvt->indicated_changed = 0;
    
    	topology = ast_stream_topology_alloc();
    	ast_test_validate_cleanup(test, topology, res, done);
    
    
    	change_res = ast_channel_request_stream_topology_change(mock_channel, topology, NULL);
    
    
    	ast_test_validate_cleanup(test, !change_res, res, done);
    	ast_test_validate_cleanup(test, pvt->indicated_change_request, res, done);
    
    
    	ast_channel_lock(mock_channel);
    
    	change_res = ast_channel_stream_topology_changed(mock_channel, topology);
    
    	ast_channel_unlock(mock_channel);
    
    
    	ast_test_validate_cleanup(test, !change_res, res, done);
    	ast_test_validate_cleanup(test, pvt->indicated_changed, res, done);
    
    done:
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    AST_TEST_DEFINE(stream_topology_change_request_from_channel)
    {
    	struct ast_channel_tech tech = {
    		.read_stream = mock_channel_read,
    		.write_stream = mock_channel_write_stream,
    		.indicate = mock_channel_indicate,
    		.hangup = mock_channel_hangup,
    	};
    	struct ast_channel *mock_channel;
    	struct mock_channel_pvt *pvt;
    	enum ast_test_result_state res = AST_TEST_PASS;
    	RAII_VAR(struct ast_stream_topology *, topology, NULL, ast_stream_topology_free);
    	struct ast_frame request_change = {
    		.frametype = AST_FRAME_CONTROL,
    		.subclass.integer = AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE,
    	};
    	struct ast_frame *fr = NULL;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "stream_topology_change_request_from_channel";
    		info->category = "/main/stream/";
    		info->summary = "channel requesting stream topology change to multistream application test";
    		info->description =
    			"Test that a channel requesting a stream topology change from a multistream application works";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	mock_channel = make_channel(test, 1, &tech);
    	ast_test_validate_cleanup(test, mock_channel, res, done);
    
    	pvt = ast_channel_tech_pvt(mock_channel);
    	pvt->indicated_changed = 0;
    
    	topology = ast_stream_topology_alloc();
    	ast_test_validate_cleanup(test, topology, res, done);
    
    	request_change.data.ptr = topology;
    	ast_queue_frame(mock_channel, &request_change);
    
    	fr = ast_read_stream(mock_channel);
    	ast_test_validate_cleanup(test, fr, res, done);
    	ast_test_validate_cleanup(test, fr->frametype == AST_FRAME_CONTROL, res, done);
    	ast_test_validate_cleanup(test, fr->subclass.integer == AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, res, done);
    	ast_test_validate_cleanup(test, !pvt->indicated_changed, res, done);
    
    done:
    	if (fr) {
    		ast_frfree(fr);
    	}
    	ast_hangup(mock_channel);
    
    	return res;
    }
    
    
    AST_TEST_DEFINE(format_cap_from_stream_topology)
    {
    	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
    	RAII_VAR(struct ast_format_cap *, stream_caps, NULL, ao2_cleanup);
    	struct ast_stream_topology *topology;
    
    	struct ast_stream *stream;
    	struct ast_format_cap *new_cap;
    
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "format_cap_from_stream_topology";
    		info->category = "/main/stream/";
    		info->summary = "stream topology to format capabilities conversion test";
    		info->description =
    			"Test that converting a stream topology to format capabilities results in expected formats";
    		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 ulaw format to capabilities\n");
    
    		return AST_TEST_FAIL;
    	}
    
    	if (ast_format_cap_append(caps, ast_format_h264, 0)) {
    
    		ast_test_status_update(test, "Failed to append h264 format to capabilities\n");
    
    		return AST_TEST_FAIL;
    	}
    
    	topology = ast_stream_topology_create_from_format_cap(caps);
    	if (!topology) {
    		ast_test_status_update(test, "Failed to create a stream topology from format capabilities of ulaw and h264\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	/*
    	 * Append declined stream with formats that should not be included
    	 * in combined topology caps.
    	 */
    	stream = ast_stream_alloc("audio", AST_MEDIA_TYPE_AUDIO);
    	if (!stream) {
    		ast_test_status_update(test, "Failed to create an audio stream for testing stream topology\n");
    		ast_stream_topology_free(topology);