# Multi-streaming (/en/realtime-media/iot/build/stream-and-optimize-media/multi-channel-streaming/linux-c)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

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 [#understand-the-tech-1]

    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:

    ![Live streaming over multiple channels](https://assets-docs.agora.io/images/iot/iot-multi-channel.svg)

    ## Prerequisites [#prerequisites-1]

    To follow this procedure you must have implemented the [SDK quickstart](/en/realtime-media/iot/quickstart) for IoT SDK.

    ## Project setup [#project-setup-1]

    In order to create the environment necessary to implement Agora multi-streaming feature into your app, open the IoT SDK [quickstart](/en/realtime-media/iot/quickstart) project you created previously.

    ## Implement multi-streaming [#implement-multi-streaming-1]

    This section shows you how to implement the following multi-streaming methods:

    * [Push multiple streams to a single channel](#push-multiple-streams-to-a-single-channel)

    * [Stream to multiple channels](#stream-to-multiple-channels)

    ### Add `pthread` library to your project [#add-pthread-library-to-your-project]

    The [pthread](https://en.wikipedia.org/wiki/Pthreads) library enables you to control multiple different flows of
    work that overlap in time. To integrate pthread into your app:

    1. **Add the library into your project**

       In the project you implemented for the [SDK quickstart](/en/realtime-media/iot/quickstart) for
       IoT SDK, open `<project-root>/agora_rtsa_sdk/example/hello_rtsa/CMakeLists.txt` and add the following line after `target_link_libraries(hello_rtsa_multi agora-rtc-sdk file_parser ${LIBS})`:

       ```bash
       target_link_libraries(hello_rtsa_multi -lpthread)
       ```

    2. **Include the header in your code**

       Open `<project-root>/agora_rtsa_sdk/example/hello_rtsa/hello_rtsa_multi.c` and add the following to the list of `#include` statements:

       ```c
       #include <pthread.h>
       ```

    ### Push multiple streams to a single channel [#push-multiple-streams-to-a-single-channel-1]

    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.

    1. **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:

       ```c
       // 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;
       ```
    2. **Create multiple connections**

       When the app starts, you create the multiple connections that you use to send audio or video data to
       a channel:

       ```c
       // 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;
           }
       }
       ```
    3. **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.

       ```c
       // 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;
           }
       }
       ```
    4. **Join the same channel multiple times**

       You configure `rtc_channel_options_t` and join the same channel multiple times with different user Ids and connection Ids.

       ```c
       // 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;
           }
       }
       ```
    5. **Stream audio and video data**

       You create tasks, that is, asynchronous threads, to send video and audio frames.

       ```c
       // 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);
       ```
    6. **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:

       ```c
       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;
       }
       ```
    7. **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 call `agora_rtc_send_video_data`. The following code sends a video frame each time it is called:

       ```c
       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;
       }
       ```
    8. **Leave a channel and clean up all connections**

       To leave a channel and destroy all connections you call `agora_rtc_leave_channel` and `agora_rtc_destroy_connection` with their connection Ids.

       ```c
       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 [#stream-to-multiple-channels-1]

    To send audio and video streams to multiple channels, open the same sample project you implemented for [Push multiple streams to a single channel](#push-multiple-streams-to-a-single-channel) and make the following changes:

    1. **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`:

       ```c
       char channel_name[10][512];
       char token[10][512];
       ```
    2. **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`:

       ```c
       // 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>);
       ```
    3. **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_channel` in the join channel loop with:

       ```c
       rval = agora_rtc_join_channel(g_app.conn_ids[i], channel_name[i], config->uid + i + 1, token[i],
                                     &channel_options);
       ```

    ## Test your implementation [#test-your-implementation-1]

    To ensure that you have implemented multi-streaming into your app, follow the relevant testing procedure:

    ### Test pushing multiple streams to a single channel [#test-pushing-multiple-streams-to-a-single-channel-1]

    1. [Generate a temporary token](../manage-agora-account#generate-a-temporary-token) in Agora Console.

    2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and update *App ID*, *Channel*, and *Token* with the values for your temporary token, then click **Join**.

    3. Compile the sample project

       In the terminal, execute the following commands:

       ```bash
       cd <project-root>/agora_rtsa_sdk/example

       ./build-x86_64.sh
       ```

    4. Run the sample app:

       1. In the terminal, navigate to the output folder:

          ```bash
          cd out/x86_64
          ```

       2. Launch the compiled app with your app ID, channel ID, and token from Agora Console

          ```bash
           ./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:

    ```bash
    [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 640
    ```

    ### Test streaming to multiple channels [#test-streaming-to-multiple-channels-1]

    1. Generate the following [temporary tokens](../manage-agora-account#generate-a-temporary-token) in Agora Console.

       * `token1` - using the `appId` and `channelName1`.

       * `token2` - using the `appId` and `channelName2`.

       * `token3` - using the `appId` and `channelName3`.

    2. In your browser, navigate to the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) and join a channel using
       `appId`, `channelName1` and `token1`.

    3. In another browser tab, join the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) using `appId`, `channelName2` and `token2`.

    4. In another browser tab, join the [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) using `appId`, `channelName3` and `token3`.

    5. Compile the sample project

       In the terminal, execute the following commands:

       ```bash
       cd <project-root>/agora_rtsa_sdk/example

       ./build-x86_64.sh
       ```

    6. Run the sample app:

       1. In the terminal, navigate to the output folder:

          ```bash
          cd out/x86_64
          ```

       2. Launch the compiled app using your app ID:

          ```bash
           ./hello_rtsa_multi --app-id <your-app-id>
          ```

    You see that your app joins all 3 [Agora web demo](https://webdemo.agora.io/basicVideoCall/index.html) channels. The video and audio streams are playing individually. The terminal
    output shows the behavior:

    ```bash
    [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 640
    ```

    ## Reference [#reference-1]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-1]

    * [agora\_rtc\_create\_connection](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#adef869ae47e2f1c194af611b57d06394)

    * [agora\_rtc\_destroy\_connection](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#ac58d42a331c45233d81085fc13defcaf)

    * [agora\_rtc\_send\_audio\_data](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#a0a00045d34a87e7a3798c6c906f9e197)

    * [agora\_rtc\_send\_video\_data](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#a05973c4f32a5eb480e52b4f566601bf5)

    * [agora\_rtc\_join\_channel](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#a6c29ff27f04623526a164cf6e5dcd738)

    * [agora\_rtc\_leave\_channel](https://api-ref.agora.io/en/iot-sdk/linux/1.x/agora__rtc__api_8h.html#a939878648ce563ef4041da892d478f8b)

    
  
