Setting the file. One moment.
Subchapter 150.13
examples/send-raw-audio.mdMarkdown9 KBView on GitHub
Complete working code for sending custom audio as a virtual microphone.
Official Sample: VSDK_sendRawAudio in videosdk-windows-rawdata-sample (opens in a new tab)
Inject custom audio into your session. Use cases:
┌─────────────────────────────────────────────────────────────────┐
│ AUDIO INJECTION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ Your Audio Source (file, TTS, generated) │
│ ↓ │
│ PCM format (16-bit, 32000 Hz) │
│ ↓ │
│ IZoomVideoSDKVirtualAudioMic::onMicInitialize() │
│ ↓ │
│ IZoomVideoSDKAudioSender::send() │
│ ↓ │
│ Participants hear your custom audio │
└─────────────────────────────────────────────────────────────────┘| Property | Value |
|---|---|
| Format | PCM (uncompressed) |
| Sample Rate | 32000 Hz (required) |
| Bit Depth | 16-bit signed |
| Channels | Mono (1) |
| Byte Order | Little-endian |
Important: The SDK requires exactly 32000 Hz sample rate.
#pragma once
#include <windows.h>
#include <thread>
#include <atomic>
#include <fstream>
#include "zoom_video_sdk_interface.h"
USING_ZOOM_VIDEO_SDK_NAMESPACE
class VirtualMic : public IZoomVideoSDKVirtualAudioMic {
public:
VirtualMic();
~VirtualMic();
// Set audio source file
void SetAudioFile(const std::string& pcmFile);
// IZoomVideoSDKVirtualAudioMic interface
void onMicInitialize(IZoomVideoSDKAudioSender* sender) override;
void onMicStartSend() override;
void onMicStopSend() override;
void onMicUninitialized() override;
private:
void SendLoop();
void GenerateSineWave(char* buffer, int samples, int frequency);
IZoomVideoSDKAudioSender* m_sender;
std::thread m_sendThread;
std::atomic<bool> m_sending;
std::string m_audioFile;
std::ifstream m_fileStream;
static const int SAMPLE_RATE = 32000;
static const int CHANNELS = 1;
static const int BITS_PER_SAMPLE = 16;
};#include "VirtualMic.h"
#include <iostream>
#include <chrono>
#include <cmath>
VirtualMic::VirtualMic()
: m_sender(nullptr)
, m_sending(false) {
}
VirtualMic::~VirtualMic() {
m_sending = false;
if (m_sendThread.joinable()) {
void StartVirtualMic() {
IZoomVideoSDKAudioHelper* audioHelper = sdk->getAudioHelper();
if (!audioHelper) return;
// Create virtual mic
VirtualMic* virtualMic = new VirtualMic();
// Optional: set audio file
// virtualMic->SetAudioFile("audio.pcm");
// Set as audio source
ZoomVideoSDKErrors err = audioHelper->setExternalAudioSource(virtualMic);
if (err != ZoomVideoSDKErrors_Success) {
std::cout << "setExternalAudioSource failed: " << err << std::endl;
return;
}
// Start audio (this triggers onMicStartSend)
audioHelper->startAudio();
}ffmpeg -i input.wav -f s16le -ar 32000 -ac 1 output.pcmffmpeg -i input.mp3 -f s16le -ar 32000 -ac 1 output.pcm| Flag | Description |
|---|---|
-f s16le | 16-bit signed little-endian PCM |
-ar 32000 | Sample rate 32000 Hz (required!) |
-ac 1 | Mono channel |
ZoomVideoSDKErrors send(
char* data, // PCM audio buffer
unsigned int length, // Buffer size in bytes
int sampleRate // Must be 32000
);Timing: Send 20ms chunks (640 samples = 1280 bytes at 32000 Hz mono)
Cause: Not calling startAudio() after setting source
Fix:
audioHelper->setExternalAudioSource(virtualMic);
audioHelper->startAudio(); // Required!Cause: Inconsistent send timing
Fix: Use precise 20ms intervals:
auto chunkInterval = std::chrono::milliseconds(20);
// ... send chunk ...
auto elapsed = std::chrono::steady_clock::now() - startTime;
std::this_thread::sleep_for(chunkInterval - elapsed);Cause: Using 44100 Hz instead of 32000 Hz
Fix: Always use 32000 Hz:
ffmpeg -ar 32000 ... # Not 44100!Cause: PCM amplitude out of range
Fix: Normalize to ±32767 range:
const double amplitude = 16000; // 50% volume