To create a timekeeper bot, I want to be able to play audio after connecting to a meeting

Meeting SDK Type and Version
Share the Meeting SDK type and version you’re working with to give relevant context.

Description
Hello @chunsiong.zoom

meetingsdk-headless-linux-sample
meetingsdk-linux-raw-recording-sample
I am trying to create a timekeeper bot that runs in the Ubuntu environment using the above two source codes as reference.

First of all, I would like to test that the audio is played after joining the zoom meeting,

I initialized it with reference to this thread and tested it until onMicInitialize was called back.

After the onMicInitialize callback
When I tried to copy the sample and play the audio, an error occurred at the send command.
zoomsdk-build-1 | :white_check_mark: configure
zoomsdk-build-1 | :white_check_mark: initialize
zoomsdk-build-1 | :white_check_mark: authorize
zoomsdk-build-1 | :white_check_mark: join a meeting
zoomsdk-build-1 | :hourglass_flowing_sand: connecting to the meeting
zoomsdk-build-1 | :white_check_mark: connected
zoomsdk-build-1 | :hourglass_flowing_sand: requesting local recording privilege
zoomsdk-build-1 | Success start send raw audio
zoomsdk-build-1 | OnMicInitialize
zoomsdk-build-1 | *********** BEFORE SEND IS CALLED! *************************** ************************************Error: Failed to send audio data to virtual camera. Error code: 13

Please tell me how to investigate what is wrong (microphone problem? audio file problem?) to resolve the error.

void ZoomSDKVirtualAudioMicEvent::onMicInitialize(IZoomSDKAudioRawDataSender* pSender) {
	//pSender->send();
	this->pSender_ = pSender; // Here this was missing
	// pSender_ = pSender;
	std::cout << "OnMicInitialize" << std::endl;
	ifstream file("test.wav", ios::binary | ios::ate);

	if (!file.is_open()) {
		std::cout << "Error: File not found. Tried to open " << "test.wav" << std::endl;
		return;
	}

	// Get the file size
	int file_size = file.tellg();

	// Read the file into a buffer
	vector<char> buffer(file_size);
	file.seekg(0, ios::beg);
	file.read(buffer.data(), file_size);

	// Send the audio data to the virtual camera
	std::cout << "************ BEFORE SEND IS CALLED!";
	SDKError err = pSender_->send(buffer.data(), buffer.size(), 44100);
	std::cout << "***************************************************************";

	if (err != SDKERR_SUCCESS) {
		std::cout << "Error: Failed to send audio data to virtual camera. Error code: " << err << std::endl;
		return;
	}
	file.close();
}

@kuisyuki8 here are some high level steps necessary to send raw audio in meeting sdk

I have these in my main class

//references for audio raw data
ZoomSDKVirtualAudioMicEvent* audio_source= new ZoomSDKVirtualAudioMicEvent();
IZoomSDKAudioRawDataHelper* audioHelper; 

.
.
.

void onInMeeting() {


    printf("onInMeeting Invoked\n");

    //double check if you are in a meeting
    if (meetingService->GetMeetingStatus() == ZOOM_SDK_NAMESPACE::MEETING_STATUS_INMEETING) {
        printf("In Meeting Now...\n");
        attemptToStartRawAudioSending();

    }

}

.
.
.
.


//audio only sends when unmuting
void attemptToStartRawAudioSending() {

 
    audioHelper = GetAudioRawdataHelper();
    if (audioHelper == NULL) {
        std::cout << "Error occurred";
        //handle error
    }
  
    SDKError err = audioHelper->setExternalAudioSource(audio_source);

    if (err != SDKERR_SUCCESS) {
        std::cout << "Error occurred";
        //handle error
    }
    
}


ZoomSDKVirtualAudioMicEvent is something like this

#pragma once
#include "windows.h"
#include <iostream>
#include <cstdint>
#include "rawdata/rawdata_audio_helper_interface.h"
#include "zoom_sdk.h"
#include "zoom_sdk_raw_data_def.h"


using namespace std;
using namespace ZOOMSDK;

class ZoomSDKVirtualAudioMicEvent :
	public IZoomSDKVirtualAudioMicEvent
{

private:
	IZoomSDKAudioRawDataSender* pSender_;

protected:

	/// \brief Callback for virtual audio mic to do some initialization.
/// \param pSender, You can send audio data based on this object, see \link IZoomSDKAudioRawDataSender \endlink.
	virtual void onMicInitialize(IZoomSDKAudioRawDataSender* pSender);

	/// \brief Callback for virtual audio mic can send raw data with 'pSender'.
	virtual void onMicStartSend();

	/// \brief Callback for virtual audio mic should stop send raw data.
	virtual void onMicStopSend();

	/// \brief Callback for virtual audio mic is uninitialized.
	virtual void onMicUninitialized();
};
#include "windows.h"
#include <iostream>
#include <cstdint>
#include <fstream>
#include <cstring>
#include <cstdio>
#include <vector>

#include "rawdata/rawdata_audio_helper_interface.h"
#include "ZoomSDKVirtualAudioMicEvent.h"
#include "zoom_sdk_def.h" 

#include <thread>

using namespace std;
using namespace ZOOM_SDK_NAMESPACE;

int audio_play_flag = -1;



void PlayAudioFileToVirtualMic(IZoomSDKAudioRawDataSender* audio_sender, string audio_source)
{


	// execute in a thread.
	while (audio_play_flag > 0 && audio_sender) {

		// Check if the file exists
		ifstream file(audio_source, ios::binary | ios::ate);
		if (!file.is_open()) {
			std::cout << "Error: File not found. Tried to open " << audio_source << std::endl;
			return;
		}

		// Get the file size
		int file_size = file.tellg();

		// Read the file into a buffer
		vector<char> buffer(file_size);
		file.seekg(0, ios::beg);
		file.read(buffer.data(), file_size);

		// Send the audio data to the virtual mic
		SDKError err = audio_sender->send(buffer.data(), buffer.size(), 44100);
		if (err != SDKERR_SUCCESS) {
			std::cout << "Error: Failed to send audio data to virtual mic. Error code: " << err << std::endl;
			return;
		}
		file.close();
		audio_play_flag = -1;
	}

}

/// \brief Callback for virtual audio mic to do some initialization.
/// \param pSender, You can send audio data based on this object, see \link IZoomSDKAudioRawDataSender \endlink.
void ZoomSDKVirtualAudioMicEvent::onMicInitialize(IZoomSDKAudioRawDataSender* pSender) {
	//pSender->send();
	pSender_ = pSender;
	printf("OnMicInitialize\n");
}

/// \brief Callback for virtual audio mic can send raw data with 'pSender'.
void ZoomSDKVirtualAudioMicEvent::onMicStartSend() {

	printf("onMicStartSend\n");


	std::cout << "onStartSend" << std::endl;
	if (pSender_ && audio_play_flag != 1) {
		while (audio_play_flag > -1) {}
		audio_play_flag = 1;
		std::string audio_source_ = "pcm1644m.wav";
		thread(PlayAudioFileToVirtualMic, pSender_, audio_source_).detach();

	}
}

/// \brief Callback for virtual audio mic should stop send raw data.
void ZoomSDKVirtualAudioMicEvent::onMicStopSend() {
	printf("onMicStopSend\n");


	audio_play_flag = 0;
}
/// \brief Callback for virtual audio mic is uninitialized.
void ZoomSDKVirtualAudioMicEvent::onMicUninitialized() {
	std::cout << "onUninitialized" << std::endl;
	pSender_ = nullptr;
}


Hey @kuisyuki8 ,

Chun Siong’s code sample should get you most of the way there. I just wanted to mention that one other thing that you might want to consider is if you’re checking if you’re actually unmuted, and if you aren’t, unmute your mic.

For example, using IsAudioMuted to determine if you’re muted, and then, if you are, unmute using UnMuteAudio.

If you didn’t want to deal with the nuances of creating your own meeting bots from scratch, another alternative is to use Recall.ai for your meeting bots instead. It’s a simple 3rd party API that lets you use meeting bots to get raw audio/video from meetings without you needing to spend months to build, scale and maintain these bots.

Let me know if you have any questions!