Multi-streaming
Updated
Stream to multiple channels or push multiple streams to a single channel.
Some special use-cases require live streaming over two or more separate channels. For example, consider the case of a real-time monitoring system, where camera feed is shared over two channels for two types of users. There are also certain applications where you want to send multiple streams over the same channel. Consider the case of a monitoring system where feeds from multiple cameras are shared on the same channel.
Agora IoT SDK multi-streaming allows you to join multiple channels at the same time or send multiple streams over a single channel. This page shows you how to implement two different multi-streaming methods into your app using IoT SDK. Choose the method that best fits your particular use-case.
Understand the tech
Agora IoT SDK provides the following approaches to implementing multi-streaming:
-
Push multiple streams to a single channel
To push multiple streams to a single channel, you create multiple connections using Agora engine. You call the join channel method multiple times with the same channel name but different connection Ids and distinct user Ids to set up multiple streams. To send audio or video data, you use the connection Id of the intended stream.
-
Stream to multiple channels
To stream over multiple channels, you create multiple connections using Agora engine. To join each channel, you use a distinct channel name and a dedicated connection Id. You use the connection Id to specify the intended channel when sending audio or video data, when leaving a particular channel, or when closing a connection.
The following figure shows the workflow you need to implement to add multi-streaming to your app:
Prerequisites
To follow this procedure you must have implemented the SDK quickstart for IoT SDK.
Project setup
In order to create the environment necessary to implement Agora multi-streaming feature into your app, open the IoT SDK quickstart project you created previously.
Implement multi-streaming
This section shows you how to implement the following multi-streaming methods:
Add pthread library to your project
The pthread library enables you to control multiple different flows of work that overlap in time. To integrate pthread into your app:
-
Add the library into your project
In the project you implemented for the SDK quickstart for IoT SDK, open
<project-root>/agora_rtsa_sdk/example/hello_rtsa/CMakeLists.txtand add the following line aftertarget_link_libraries(hello_rtsa_multi agora-rtc-sdk file_parser ${LIBS}):target_link_libraries(hello_rtsa_multi -lpthread) -
Include the header in your code
Open
<project-root>/agora_rtsa_sdk/example/hello_rtsa/hello_rtsa_multi.cand add the following to the list of#includestatements:#include <pthread.h>
Push multiple streams to a single channel
Pushing multiple audio and video streams to a single channel is implemented in
<project-root>/agora_rtsa_sdk/example/hello_rtsa/hello_rtsa_multi.c in the sample project. The following
section shows and explains code snippets that you can reuse in your own app to integrate this functionality.
-
Initialize the SDK, verify license, and set connection count
The following code initializes the Agora engine event handler, configures options, and sets the connection count. That is, the number of streams you push to a single channel:
// Initialize the event handler agora_rtc_event_handler_t event_handler = { 0 }; app_init_event_handler(&event_handler, config); // Set Agora Engine options rtc_service_option_t service_opt = { 0 }; service_opt.area_code = config->area; service_opt.log_cfg.log_level = RTC_LOG_INFO; service_opt.log_cfg.log_path = config->p_sdk_log_dir; snprintf(service_opt.license_value, sizeof(service_opt.license_value), "%s", config->p_license); // Initialize Agora rtc sdk rval = agora_rtc_init(p_appid, &event_handler, &service_opt); if (rval < 0) { LOGE("Failed to initialize Agora sdk, reason: %s", agora_rtc_err_2_str(rval)); return -1; } // Set bandwidth parameters agora_rtc_set_bwe_param(CONNECTION_ID_ALL, DEFAULT_BANDWIDTH_ESTIMATE_MIN_BITRATE, DEFAULT_BANDWIDTH_ESTIMATE_MAX_BITRATE, DEFAULT_BANDWIDTH_ESTIMATE_START_BITRATE); // Connection count g_app.conn_cnt = 3; -
Create multiple connections
When the app starts, you create the multiple connections that you use to send audio or video data to a channel:
// Create connections for (i = 0; i < g_app.conn_cnt; i++) { rval = agora_rtc_create_connection(&g_app.conn_ids[i]); if (rval < 0) { LOGE("Failed to create connection, reason: %s", agora_rtc_err_2_str(rval)); return -1; } } -
Handle Agora engine events
IoT SDK uses callbacks in the event handler to notify you of important events. For example, users joining or leaving a channel, and receipt of audio or video data. The following code adds some important methods to handle IoT SDK events and initializes the event handler.
// Important events static void __on_join_channel_success(connection_id_t conn_id, uint32_t uid, int elapsed) { int i = 0; for (i = 0; i < g_app.conn_cnt; i++) { if (conn_id == g_app.conn_ids[i]) { g_app.conn_flags[i] = true; } } g_app.b_connected_flag = true; LOGI("[conn-%u] Join the channel successfully, uid %u elapsed %d ms", conn_id, uid, elapsed); connection_info_t conn_info = { 0 }; if (agora_rtc_get_connection_info(conn_id, &conn_info) == 0) { LOGI("conn_id=%u uid=%u cname=%s", conn_info.conn_id, conn_info.uid, conn_info.channel_name); } } static void __on_connection_lost(connection_id_t conn_id) { int i = 0; for (i = 0; i < g_app.conn_cnt; i++) { if (conn_id == g_app.conn_ids[i]) { g_app.conn_flags[i] = true; } } g_app.b_connected_flag = false; LOGW("[conn-%u] Lost connection from the channel", conn_id); connection_info_t conn_info = { 0 }; if (agora_rtc_get_connection_info(conn_id, &conn_info) == 0) { LOGI("conn_id=%u uid=%u cname=%s", conn_info.conn_id, conn_info.uid, conn_info.channel_name); } } static void __on_user_joined(connection_id_t conn_id, uint32_t uid, int elapsed_ms) { LOGI("[conn-%u] Remote user \"%u\" has joined the channel, elapsed %d ms", conn_id, uid, elapsed_ms); if (uid == 1 || uid == 2) { LOGI("dddd"); } } static void __on_audio_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts, const void *data, size_t len, const audio_frame_info_t *info_ptr) { LOGI("[conn-%u] on_audio_data, uid %u sent_ts %u data_type %d, len %zu", conn_id, uid, sent_ts, info_ptr->data_type, len); //write_file(g_app.audio_file_writer, data_type, data, len); } static void __on_video_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts, const void *data, size_t len, const video_frame_info_t *info_ptr) { LOGI("[conn-%u] on_video_data: uid %u sent_ts %u data_type %d frame_type %d stream_type %d len %zu", conn_id, uid, sent_ts, info_ptr->data_type, info_ptr->frame_type, info_ptr->stream_type, len); //write_file(g_app.video_file_writer, info_ptr->data_type, data, len); } // Initialize the event_handler static void app_init_event_handler(agora_rtc_event_handler_t *event_handler, app_config_t *config) { event_handler->on_join_channel_success = __on_join_channel_success; event_handler->on_connection_lost = __on_connection_lost; event_handler->on_rejoin_channel_success = __on_rejoin_channel_success; event_handler->on_user_joined = __on_user_joined; event_handler->on_user_offline = __on_user_offline; event_handler->on_user_mute_audio = __on_user_mute_audio; event_handler->on_user_mute_video = __on_user_mute_video; event_handler->on_target_bitrate_changed = __on_target_bitrate_changed; event_handler->on_key_frame_gen_req = __on_key_frame_gen_req; event_handler->on_video_data = __on_video_data; event_handler->on_error = __on_error; if (config->enable_audio_mixer) { event_handler->on_mixed_audio_data = __on_mixed_audio_data; } else { event_handler->on_audio_data = __on_audio_data; } } -
Join the same channel multiple times
You configure
rtc_channel_options_tand join the same channel multiple times with different user Ids and connection Ids.// Set Channel options rtc_channel_options_t channel_options; memset(&channel_options, 0, sizeof(channel_options)); channel_options.auto_subscribe_audio = true; channel_options.auto_subscribe_video = true; channel_options.subscribe_local_user = false; // Join multiple channel with different user Ids and connection Ids. for (i = 0; i < g_app.conn_cnt; i++) { rval = agora_rtc_join_channel(g_app.conn_ids[i], config->p_channel, config->uid + i + 1, config->p_token, &channel_options); if (rval < 0) { LOGE("Failed to join channel \"%s\", reason: %s", config->p_channel, agora_rtc_err_2_str(rval)); return -1; } } -
Stream audio and video data
You create tasks, that is, asynchronous threads, to send video and audio frames.
// Create tasks sending video and audio frames pthread_t video_thread_id; pthread_t audio_thread_id; // Video stream task rval = pthread_create(&video_thread_id, NULL, video_send_thread, 0); if (rval < 0) { printf("Unable to create video push thread\n"); return -1; } // Audio stream task rval = pthread_create(&audio_thread_id, NULL, audio_send_thread, 0); if (rval < 0) { printf("Unable to create audio push thread\n"); return -1; } pthread_join(video_thread_id, NULL); pthread_join(audio_thread_id, NULL); -
Send an audio frame to through muliple connections
To send audio, you read data from an audio file and call
agora_rtc_send_audio_data. The following code sends an audio frame each time it is called:static int app_send_audio(void) { app_config_t *config = &g_app.config; int i = 0; // Get audio frame data frame_t frame; if (file_parser_obtain_frame(g_app.audio_file_parser, &frame) < 0) { LOGE("The file parser failed to obtain audio frame"); return -1; } // Send audio frames through all connections audio_frame_info_t info = { 0 }; info.data_type = config->audio_data_type; for (i = 0; i < g_app.conn_cnt; i++) { if (!g_app.conn_flags[i]) { continue; } int rval = agora_rtc_send_audio_data(g_app.conn_ids[i], frame.ptr, frame.len, &info); if (rval < 0) { LOGE("Failed to send audio data, reason: %s", agora_rtc_err_2_str(rval)); } } file_parser_release_frame(g_app.audio_file_parser, &frame); return 0; } -
Send a video frame to through muliple connections
To send video, you read frame data from a video file, create a
video_frame_info_t, and callagora_rtc_send_video_data. The following code sends a video frame each time it is called:static int app_send_video(void) { app_config_t *config = &g_app.config; int i = 0; // Get video frame data frame_t frame; if (file_parser_obtain_frame(g_app.video_file_parser, &frame) < 0) { LOGE("The file parser failed to obtain audio frame"); return -1; } // Send video frames through all connections video_frame_info_t info; info.frame_type = frame.u.video.is_key_frame ? VIDEO_FRAME_KEY : VIDEO_FRAME_DELTA; info.frame_rate = config->send_video_frame_rate; info.data_type = config->video_data_type; info.stream_type = VIDEO_STREAM_HIGH; for (i = 0; i < g_app.conn_cnt; i++) { if (!g_app.conn_flags[i]) { continue; } int rval = agora_rtc_send_video_data(g_app.conn_ids[i], frame.ptr, frame.len, &info); if (rval < 0) { LOGE("Failed to send video data, reason: %s", agora_rtc_err_2_str(rval)); } } file_parser_release_frame(g_app.video_file_parser, &frame); return 0; } -
Leave a channel and clean up all connections
To leave a channel and destroy all connections you call
agora_rtc_leave_channelandagora_rtc_destroy_connectionwith their connection Ids.for (i = 0; i < g_app.conn_cnt; i++) { // Leave channel agora_rtc_leave_channel(g_app.conn_ids[i]); // Destroy connection agora_rtc_destroy_connection(g_app.conn_ids[i]); }
Stream to multiple channels
To send audio and video streams to multiple channels, open the same sample project you implemented for Push multiple streams to a single channel and make the following changes:
-
Declare the variables you need
To manage other connections and join additional channels, add the following variable declarations to the
main()in<project-root>/agora_rtsa_sdk/example/hello_rtsa/hello_rtsa_multi.c:char channel_name[10][512]; char token[10][512]; -
Set channel name & token for each connection
Using the sample project, your user can join 10 additional channels. In this page you set channel names and tokens for three connections. To do this, add the following code before the join channel loop,
agora_rtc_join_channel:// Set channel name & token for each connection strcpy(channel_name[0], <channel_name1>); strcpy(token[0], <token1>); strcpy(channel_name[1], <channel_name2>); strcpy(token[1], <token2>); strcpy(channel_name[2], <channel_name3>); strcpy(token[2], <token3>); -
Join multiple channels
To send audio and video streams to multiple channels, join these different channels with different tokens, user and connection Ids. Replace
agora_rtc_join_channelin the join channel loop with:rval = agora_rtc_join_channel(g_app.conn_ids[i], channel_name[i], config->uid + i + 1, token[i], &channel_options);
Test your implementation
To ensure that you have implemented multi-streaming into your app, follow the relevant testing procedure:
Test pushing multiple streams to a single channel
-
Generate a temporary token in Agora Console.
-
In your browser, navigate to the Agora web demo and update App ID, Channel, and Token with the values for your temporary token, then click Join.
-
Compile the sample project
In the terminal, execute the following commands:
cd <project-root>/agora_rtsa_sdk/example ./build-x86_64.sh -
Run the sample app:
-
In the terminal, navigate to the output folder:
cd out/x86_64 -
Launch the compiled app with your app ID, channel ID, and token from Agora Console
./hello_rtsa_multi --app-id <your-app-id> --channel-id <your-channel-name> --token <authentication-token>
-
You see 3 video streams with audio are playing in the browser. The terminal output shows all the connections sending streams to same channel:
[INF] [conn-1] on_audio_data, uid 3416170155 sent_ts 39985 data_type 100, len 640
[INF] [conn-3] on_audio_data, uid 3416170155 sent_ts 39985 data_type 100, len 640
[INF] [conn-2] on_audio_data, uid 3416170155 sent_ts 39985 data_type 100, len 640
[INF] [conn-3] on_audio_data, uid 3416170155 sent_ts 40005 data_type 100, len 640
[INF] [conn-2] on_audio_data, uid 3416170155 sent_ts 40005 data_type 100, len 640
[INF] [conn-1] on_audio_data, uid 3416170155 sent_ts 40005 data_type 100, len 640Test streaming to multiple channels
-
Generate the following temporary tokens in Agora Console.
-
token1- using theappIdandchannelName1. -
token2- using theappIdandchannelName2. -
token3- using theappIdandchannelName3.
-
-
In your browser, navigate to the Agora web demo and join a channel using
appId,channelName1andtoken1. -
In another browser tab, join the Agora web demo using
appId,channelName2andtoken2. -
In another browser tab, join the Agora web demo using
appId,channelName3andtoken3. -
Compile the sample project
In the terminal, execute the following commands:
cd <project-root>/agora_rtsa_sdk/example ./build-x86_64.sh -
Run the sample app:
-
In the terminal, navigate to the output folder:
cd out/x86_64 -
Launch the compiled app using your app ID:
./hello_rtsa_multi --app-id <your-app-id>
-
You see that your app joins all 3 Agora web demo channels. The video and audio streams are playing individually. The terminal output shows the behavior:
[INF] [conn-3] Join the channel successfully, uid 3 elapsed 559 ms
[INF] conn_id=3 uid=3 cname=token55
[INF] joined-after: conn_id=1 uid=1 cname=token33
[INF] joined-after: conn_id=2 uid=2 cname=token44
[INF] joined-after: conn_id=3 uid=3 cname=token55
[INF] [conn-1] Join the channel successfully, uid 1 elapsed 2053 ms
[INF] conn_id=1 uid=1 cname=token33
[INF] [conn-2] Join the channel successfully, uid 2 elapsed 2164 ms
[INF] conn_id=2 uid=2 cname=token44
[INF] [conn-1] on_audio_data, uid 3416170155 sent_ts 37125 data_type 100, len 640
[INF] [conn-2] on_audio_data, uid 3416170155 sent_ts 37125 data_type 100, len 640
[INF] [conn-3] on_audio_data, uid 3416170155 sent_ts 37145 data_type 100, len 640
[INF] [conn-2] on_audio_data, uid 3416170155 sent_ts 37145 data_type 100, len 640
[INF] [conn-1] on_audio_data, uid 3416170155 sent_ts 37145 data_type 100, len 640Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
