Subscribing to multiple participants raw data?

Hi there. I’m running into a continual issue and I’m unsure how to resolve.

SDKError err_can_start = m_pRecordController->CanStartRawRecording();
    if (err_can_start == SDKERR_SUCCESS) {
        std::cout << "[REC_CTRL] Permission granted. Attempting to StartRawRecording()..." << std::endl;
        SDKError err_start = m_pRecordController->StartRawRecording();

        if (err_start == SDKERR_SUCCESS) {
            std::cout << "[REC_CTRL] StartRawRecording() call SUCCEEDED." << std::endl;
            g_rawRecordingGloballyActive = true; // <<< SET THE FLAG HERE

            if (isVideo) {
                std::cout << "[REC_CTRL] Initializing renderers for all *current* participants..." << std::endl;
                m_pParticipantsController = m_pMeetingService->GetMeetingParticipantsController();
                if (m_pParticipantsController) {
                    IList<unsigned int>* participants = m_pParticipantsController->GetParticipantsList();
                    if (participants) {
                        printf("[REC_CTRL] Found %d current participants. Creating renderers...\n", participants->GetCount());
                        for (int i = 0; i < participants->GetCount(); ++i) {
                            uint32_t participantId = participants->GetItem(i);
                            std::cout << "[REC_CTRL] Attempting to create/subscribe renderer for UserID: " << participantId << std::endl;
                            createAndSubscribeUserRenderer(participantId);

On startup I’m looping participants (shown above) and trying to subscribe to their raw data feed, using the function below:


void createAndSubscribeUserRenderer(uint32_t userIdToSubscribe) { // Renamed param for clarity
  std::lock_guard<std::mutex> lock(g_rendererMapMutex);

    if (g_userVideoRenderHelpers.count(userIdToSubscribe) || g_userVideoDelegates.count(userIdToSubscribe)) {
        std::cout << "[RENDER_MGR] Renderer already exists for UserID: " << userIdToSubscribe << ". Skipping creation." << std::endl;
        return;
    }

    if (g_zoom_unique_meeting_id.empty()) { return; }
    if (!m_pMeetingService) { return; }

    std::cout << "[RENDER_MGR] Creating renderer for UserID: " << userIdToSubscribe << std::endl;

    ZoomSDKRenderer* userDelegate = nullptr;
    IZoomSDKRenderer* sdkRendererHelper = nullptr; // SDK's helper object

    // 1. Create your custom delegate, informing it which UserID it's logically for.
    //    Pass nullptr for the helper initially, as it's not created yet.
    try {
        userDelegate = new ZoomSDKRenderer(
            m_pMeetingService,
            g_zoom_unique_meeting_id,
            userIdToSubscribe,       // The UserID this delegate is responsible for
            nullptr                  // Helper will be set later
        );
    } catch (const std::bad_alloc& e) {
        std::cerr << "[RENDER_MGR] FATAL: new ZoomSDKRenderer failed for UserID " << userIdToSubscribe << ": " << e.what() << std::endl;
        return;
    } catch (const std::exception& e) {
        std::cerr << "[RENDER_MGR] FATAL: new ZoomSDKRenderer exception for UserID " << userIdToSubscribe << ": " << e.what() << std::endl;
        return;
    }
    if (!userDelegate) { return; }

    // 2. Create the SDK's renderer helper object, associating your delegate with it.
    //    The SDK will call methods on 'userDelegate' via this helper.
    SDKError err_create_helper = createRenderer(&sdkRendererHelper, userDelegate);
    if (err_create_helper != SDKERR_SUCCESS || !sdkRendererHelper) {
        std::cerr << "[RENDER_MGR] Failed to create IZoomSDKRenderer helper for UserID: " << userIdToSubscribe << ". Error: " << err_create_helper << std::endl;
        delete userDelegate; // Clean up the delegate we allocated
        return;
    }

    // 3. Now that the SDK helper is created, provide it to your delegate for reference.
    userDelegate->SetRendererHelper(sdkRendererHelper);

    // 4. Configure the SDK helper (e.g., resolution)
    SDKError resolution_err = sdkRendererHelper->setRawDataResolution(ZoomSDKResolution_1080P); // Or your desired resolution
    if (resolution_err != SDKERR_SUCCESS) {
        std::cerr << "[RENDER_MGR] Warn: Failed to set resolution for UserID " << userIdToSubscribe << ", error: " << resolution_err << std::endl;
    }

    // 5. Subscribe the SDK helper to the specified user's video.
    SDKError subErr = sdkRendererHelper->subscribe(userIdToSubscribe, RAW_DATA_TYPE_VIDEO);
    if (subErr != SDKERR_SUCCESS) {
        std::cerr << "[RENDER_MGR] Video subscription FAILED for UserID " << userIdToSubscribe << " with error: " << subErr << std::endl;
        destroyRenderer(sdkRendererHelper); // Clean up the SDK-side helper
        delete userDelegate;                // Clean up our delegate
    } else {
        std::cout << "[RENDER_MGR] Video subscription SUCCEEDED for UserID " << userIdToSubscribe << "." << std::endl;
        g_userVideoRenderHelpers[userIdToSubscribe] = sdkRendererHelper;
        g_userVideoDelegates[userIdToSubscribe] = userDelegate;
    }
}

It’ll subscribe to the first user, but always fails on the next user. This is what it logs:

[ZoomSDKRenderer] Renderer helper set for logical UserID: xxxxxxxx
[RENDER_MGR] Video subscription FAILED for UserID xxxxxxxx with error: 2
[ZoomSDKRenderer] onRendererBeDestroyed for UserID xxxxxxxx.
[ZoomSDKRenderer_DTOR] Destructor called for UserID: xxxxxxxx
[REC_CTRL] Subscription for xxxxxxxx likely failed based on map check.

Which gives an error of:

SDKERR_WRONG_USAGE (2)

Can anyone tell me what I might be doing wrong? Also, just for a sanity check, I am allowed to subscribe to multiple users raw data simultaneously via my headless zoom bot, correct?

Hi there,

This issue has already been resolved and discussed in detail in the following thread:

:backhand_index_pointing_right: How to determine whether the frame is screenshare or video rawdata in onRawDataFrameReceived

The SDKERR_WRONG_USAGE error you’re encountering when subscribing to multiple users’ raw video streams is typically caused by incorrect handling of renderer instances or overlapping subscriptions. The linked thread explains how to correctly distinguish between screen share and video frames and how to manage concurrent subscriptions using separate renderer helpers for each participant.

Please take a look at the solution shared there — it should address your current issue.

If you still have any questions or run into related issues, feel free to ask!

Best regards,
Naeem Ahmed

@freelancer.nak

Thank you for your quick response and for pointing me to that thread! I’ve reviewed it, and the core principle of using separate renderer helpers and delegates for each stream (user video, screen share) is understood.

I believe my current implementation on the Linux Meeting SDK already follows this pattern. Here’s a summary of my createAndSubscribeUserRenderer function which is called for each participant I wish to get raw video from:

ZoomSDKRenderer* userDelegate = new ZoomSDKRenderer(..., userIdToSubscribe, nullptr);
SDKError err_create_helper = createRenderer(&sdkRendererHelper, userDelegate); 
if (err_create_helper == SDKERR_SUCCESS && sdkRendererHelper) {
    userDelegate->SetRendererHelper(sdkRendererHelper); // My delegate stores this helper
    sdkRendererHelper->setRawDataResolution(...);
    SDKError subErr = sdkRendererHelper->subscribe(userIdToSubscribe, RAW_DATA_TYPE_VIDEO);
    if (subErr == SDKERR_SUCCESS) {
        // Store distinct userDelegate and sdkRendererHelper in maps keyed by userIdToSubscribe
        g_userVideoRenderHelpers[userIdToSubscribe] = sdkRendererHelper;
        g_userVideoDelegates[userIdToSubscribe] = userDelegate;
    } else {
        // Cleanup: destroyRenderer(sdkRendererHelper); delete userDelegate;
        // Log: "[RENDER_MGR] Video subscription FAILED for UserID X with error: " << subErr
    }
} else { // createRenderer failed
    // Cleanup: delete userDelegate;
}

When I run this:

  1. The first participant’s video subscription (e.g., User A) succeeds .

  2. When I attempt to subscribe to a second participant’s video (User B) using a new delegate and what I believe is a new helper instance obtained from a subsequent call to createRenderer() , the subscribe() call for User B consistently fails with SDKERR_WRONG_USAGE (2) .

  3. I’ve confirmed I can subscribe to User A only successfully, or User B only successfully. The failure only occurs on the second concurrent video subscription attempt. I’ve also added delays between subscription attempts, which did not resolve the issue.

  4. Crucially, I’ve added logging to check the pointer address of the IZoomSDKRenderer helper returned by createRenderer(). The logs confirm that createRenderer() IS providing a distinct memory address for the helper instance for User A and a different, distinct memory address for the helper instance for User B. This suggests I am indeed working with separate renderer helper instances.*

Given this, and that the linked thread primarily discusses a Windows SDK implementation, could there be:

  • A subtle difference in how createRenderer or IZoomSDKRenderer::subscribe behaves on the Linux Meeting SDK that I’m missing regarding concurrent video streams?

  • An underlying limit on the number of concurrent active video subscriptions (even with distinct renderers) for our SDK App Key/Client ID on the Linux platform, which isn’t explicitly documented?

My understanding from your reply and the linked thread is that if I’m creating distinct IZoomSDKRenderer helpers (which my address logging confirms) and distinct delegates for each userID and RAW_DATA_TYPE_VIDEO, concurrent subscriptions should be possible, subject to overall system resources and SDK plan limitations. The SDKERR_WRONG_USAGE in this specific “second video stream” context on Linux is what’s puzzling me.

Could you offer any further insights specific to achieving multiple concurrent raw video subscriptions on the Linux Meeting SDK ? Is there a known limit we might be hitting, or a different pattern required for Linux compared to the Windows example shared?

Thank you again for your help!

@freelancer.nak

Quick update and a key finding regarding SDKERR_WRONG_USAGE (2) on the Linux Meeting SDK when trying to subscribe to multiple participant videos.

Following our discussion about ensuring separate renderer instances:

  1. I confirmed I am getting distinct IZoomSDKRenderer* helper instance addresses from each call to the global createRenderer().

  2. Crucially, if I subscribe to User A’s video, then explicitly call IZoomSDKRenderer::unSubscribe() and destroyRenderer() for User A, and then attempt to subscribe to User B’s video (using a new delegate and new helper from createRenderer()), the subscription to User B SUCCEEDS .

This was tested by:

  • Subscribing to “Jeff Phone” (UserID 16780288) → Success.

  • Unsubscribing and destroying renderer for “Jeff Phone”.

  • Waiting ~10 seconds.

  • Subscribing to “Jeff Desktop” (UserID 16778240) → Success.

This strongly suggests that the Linux Meeting SDK (for our app/plan) allows only one active IZoomSDKRenderer::subscribe(…RAW_DATA_TYPE_VIDEO) at a time for external participants. Attempting a second concurrent subscription results in SDKERR_WRONG_USAGE (2).