DataStream API

DATASTREAM API

Varjo native datastream API is defined in varjo_datastream.h header. API can be used to access different video streams from a user application. Currently streaming is supported for mixed reality color cameras, HDR cubemap texture and infrared eye cameras. SDK’s Common/DataStreamer class contains an example how this API can be interfaced.

AVAILABLE DATASTREAMS

Stream Format Samplerate Channels Resolution Supported devices
Video see through
(varjo_StreamType_DistortedColor)
CPU NV12 90Hz 2 1152x1152 XR-3
Video see through
(varjo_StreamType_DistortedColor)
CPU YUV422
semiplanar
90Hz 2 1008x1008 XR-1
HDR environment cubemap
(varjo_StreamType_EnvironmentCubemap)
CPU RGBA16 FLOAT 90Hz 1 6x(256x256) XR-3, XR-1
Eye tracking
(varjo_StreamType_EyeCamera)
CPU Y8 200Hz 2 640x400 XR-3, VR-3, Aero

USING THE DATASTREAM API

Obtaining available streams

std::vector<varjo_StreamConfig> configs;
configs.resize(varjo_GetDataStreamConfigCount(m_session));
varjo_GetDataStreamConfigs(m_session, configs.data(), static_cast<int32_t>(configs.size()));

Starting a stream

varjo_StartDataStream(m_session, conf.streamId, varjo_ChannelFlag_Left | varjo_ChannelFlag_Right, dataStreamFrameCallback, this);

Stopping a stream

varjo_StopDataStream(m_session, conf.streamId);

Process the frames in the callback functions. Callback contains buffers for both left and right channels as a batch. Note that callback is executed in data stream worker thread.

// "C" trampoline callback function to get back to "C++"
void DataStreamer::dataStreamFrameCallback(const varjo_StreamFrame* frame, varjo_Session* session, void* userData)
{
    DataStreamer* streamer = reinterpret_cast<DataStreamer*>(userData);
    streamer->onDataStreamFrame(frame, session);
}

void DataStreamer::onDataStreamFrame(const varjo_StreamFrame* frame, varjo_Session* session)
{
    // Get frame world pose
    const varjo_Matrix& pose = frame->hmdPose;
    ...

    // Get extra metadata specific for each stream type
    switch (frame->type) {
        case varjo_StreamType_DistortedColor: {
            const varjo_DistortedColorFrameMetadata& frameMetadata = frame->metadata.distortedColor;
            ...
        } break;
        case varjo_StreamType_EnvironmentCubemap: {
            const varjo_EnvironmentCubemapFrameMetadata& frameMetadata = frame->metadata.environmentCubemap;
            ...
        } break;
        case varjo_StreamType_EyeCamera: {
            const varjo_EyeCameraFrameMetadata& frameMetadata = frame->metadata.eyeCamera;
            ...
        } break;
    }

    for (varjo_ChannelIndex channelIndex : {varjo_ChannelIndex_Left, varjo_ChannelIndex_Right}) {
        if (!(frame->channels & (1ull << channelIndex))) {
            continue;
        }

        // Get camera extrinsics if available
        if (frame->dataFlags & varjo_DataFlag_Extrinsics) {
            varjo_Matrix extrinsics = varjo_GetCameraExtrinsics(session, frame->id, frame->frameNumber, channelIndex);
            ...
        }

        // Get camera intrinsics if available
        if (frame->dataFlags & varjo_DataFlag_Intrinsics) {
            varjo_CameraIntrinsics intrinsics = varjo_GetCameraIntrinsics(session, frame->id, frame->frameNumber, channelIndex);
            ...
        }

        // Get buffer ID for the channel, if available
        if (frame->dataFlags & varjo_DataFlag_Buffer) {
            varjo_BufferId bufferId = varjo_GetBufferId(session, frame->id, frame->frameNumber, channelIndex);
            ...
        }
    }
}

Once you have buffer ID, you can access its metadata and buffer contents

// Lock buffer
varjo_LockDataStreamBuffer(session, bufferId);

// Get buffer metadata
varjo_BufferMetadata meta = varjo_GetBufferMetadata(session, bufferId);

// Get buffer contents
if (meta.type == varjo_BufferType_CPU) {
    void* cpuData = varjo_GetBufferCPUData(session, bufferId);
    ...
} else if (meta.type == varjo_BufferType_GPU) {
    varjo_Texture textureId = varjo_GetBufferGPUData(session, bufferId);
    ...
}

// Unlock buffer
varjo_UnlockDataStreamBuffer(session, bufferId);

It is also possible to use delayed buffer handling where buffer is locked in the callback thread, put in queue and then released in render thread.

void DataStreamer::onDataStreamFrame(const varjo_StreamFrame* frame, varjo_Session* session)
{
    ...
    {
        // Lock buffer
        varjo_LockDataStreamBuffer(session, bufferId);

        // Put buffer to queue
        std::lock_guard<std::mutex> lock(m_mutex);
        m_buffers.push_back(bufferId);
    }
    ...
}

void Application::onRenderFrame()
{
    // Pop frames from the queue
    std::vector<varjo_BufferId> buffers;
    {
         std::lock_guard<std::mutex> lock(m_mutex);
         std::swap(buffers, m_buffers);
    }
    
    for (auto bufferId : buffers) {
        // Do something with the buffer
        ...

        // Unlock buffer
        varjo_UnlockDataStreamBuffer(m_session, bufferId);
    }
}