# Receive raw data There are two ways to consume incoming media as raw data: - **Video and share frames**: subscribe a `ZoomVideoSDKRawDataPipeDelegate` to a user's video or share pipe. - **Audio frames**: install a `ZoomVideoSDKVirtualAudioSpeaker` on the session context at join time, or listen for the audio raw-data callbacks on your `ZoomVideoSDKDelegate`. ## Receive raw video frames Raw video arrives as YUV (I420) data: three planes (Y, U, V) plus dimensions and rotation. Implement `ZoomVideoSDKRawDataPipeDelegate` and pass it to a user's video pipe. ```kotlin val dataDelegate = object : ZoomVideoSDKRawDataPipeDelegate { override fun onRawDataStatusChanged(status: ZoomVideoSDKRawDataPipeDelegate.RawDataStatus?) { // The pipe is now active or inactive. } override fun onRawDataFrameReceived(rawData: ZoomVideoSDKVideoRawData?) { rawData ?: return // Read YUV planes and dimensions — see "Read a video frame" below. } } val pipe = user.videoPipe val result = pipe.subscribe( ZoomVideoSDKVideoResolution.VideoResolution_360P, dataDelegate ) if (result != ZoomVideoSDKErrors.Errors_Success) { // Subscription was rejected. } ``` ```java ZoomVideoSDKRawDataPipeDelegate dataDelegate = new ZoomVideoSDKRawDataPipeDelegate() { @Override public void onRawDataStatusChanged(ZoomVideoSDKRawDataPipeDelegate.RawDataStatus status) { // The pipe is now active or inactive. } @Override public void onRawDataFrameReceived(ZoomVideoSDKVideoRawData rawData) { if (rawData == null) return; // Read YUV planes and dimensions — see "Read a video frame" below. } }; ZoomVideoSDKRawDataPipe pipe = user.getVideoPipe(); int result = pipe.subscribe( ZoomVideoSDKVideoResolution.VideoResolution_360P, dataDelegate ); if (result != ZoomVideoSDKErrors.Errors_Success) { // Subscription was rejected. } ``` `subscribe` returns an `int`: `ZoomVideoSDKErrors.Errors_Success` (value `0`) means frames will start arriving on `onRawDataFrameReceived`; any other value is an error code from [`ZoomVideoSDKErrors`](https://marketplacefront.zoom.us/sdk/custom/android/us/zoom/sdk/ZoomVideoSDKErrors.html). ### Read a video frame Inside `onRawDataFrameReceived`, read planes and metadata from the `ZoomVideoSDKVideoRawData`. ```kotlin val width = rawData.streamWidth val height = rawData.streamHeight val yBuffer = rawData.yBuffer // ByteBuffer for the Y plane val uBuffer = rawData.uBuffer // ByteBuffer for the U plane val vBuffer = rawData.vBuffer // ByteBuffer for the V plane val rotation = rawData.rotation // 0, 90, 180, or 270 (clockwise) val format = rawData.format // currently always FORMAT_TYPE_I420 val timestamp = rawData.timeStamp ``` ```java int width = rawData.getStreamWidth(); int height = rawData.getStreamHeight(); ByteBuffer yBuffer = rawData.getyBuffer(); // Y plane ByteBuffer uBuffer = rawData.getuBuffer(); // U plane ByteBuffer vBuffer = rawData.getvBuffer(); // V plane int rotation = rawData.getRotation(); // 0, 90, 180, or 270 (clockwise) int format = rawData.getFormat(); // currently always FORMAT_TYPE_I420 long timestamp = rawData.getTimeStamp(); ``` The getter casing (`getyBuffer`, `getuBuffer`, `getvBuffer`) reflects the SDK method names. Don't "correct" the lowercase letter when grepping or wiring up reflection. ### Retain a frame past the callback Raw data buffers are valid only inside `onRawDataFrameReceived` by default. Once the callback returns, the buffer may be recycled. To hold onto a frame (for example, to hand it off to a background thread for processing or encoding), use the SDK's reference counting. ```kotlin override fun onRawDataFrameReceived(rawData: ZoomVideoSDKVideoRawData?) { rawData ?: return if (rawData.canAddRef()) { rawData.addRef() backgroundExecutor.execute { try { processFrame(rawData) } finally { rawData.releaseRef() } } } } ``` ```java @Override public void onRawDataFrameReceived(ZoomVideoSDKVideoRawData rawData) { if (rawData == null) return; if (rawData.canAddRef()) { rawData.addRef(); backgroundExecutor.execute(() -> { try { processFrame(rawData); } finally { rawData.releaseRef(); } }); } } ``` Always pair `addRef()` with `releaseRef()`. Orphaned refs leak frame buffers. ### Stop receiving frames Call `unSubscribe(delegate)` on the same pipe when you no longer want frames. Always unsubscribe before tearing down the Activity, otherwise the SDK keeps invoking your delegate against a destroyed context. ```kotlin user.videoPipe.unSubscribe(dataDelegate) ``` ```java user.getVideoPipe().unSubscribe(dataDelegate); ``` ## Receive raw audio frames Through your `ZoomVideoSDKDelegate`, you can listen for mixed (combined audio from one or more users), per-user, and share audio raw data callbacks. The callbacks fire continuously while any audio is flowing in the session. ```kotlin override fun onMixedAudioRawDataReceived(rawData: ZoomVideoSDKAudioRawData?) { // Combined audio for the whole session. } override fun onOneWayAudioRawDataReceived( rawData: ZoomVideoSDKAudioRawData?, user: ZoomVideoSDKUser ) { // Per-user audio. } override fun onShareAudioRawDataReceived(rawData: ZoomVideoSDKAudioRawData?) { // Audio from an active screen share. } ``` ```java @Override public void onMixedAudioRawDataReceived(ZoomVideoSDKAudioRawData rawData) { // Combined audio for the whole session. } @Override public void onOneWayAudioRawDataReceived( ZoomVideoSDKAudioRawData rawData, ZoomVideoSDKUser user ) { // Per-user audio. } @Override public void onShareAudioRawDataReceived(ZoomVideoSDKAudioRawData rawData) { // Audio from an active screen share. } ``` Inside each callback, the `ZoomVideoSDKAudioRawData` exposes the PCM buffer through `rawData.buffer` (Kotlin) / `rawData.getBuffer()` (Java). ### Receive audio through a virtual speaker You can also install a `ZoomVideoSDKVirtualAudioSpeaker` on the session context when joining. This routes received audio to your speaker implementation instead of (or in addition to) the device's audio output. Use this when you want to process incoming audio without playing it through the speaker. ```kotlin val virtualSpeaker = object : ZoomVideoSDKVirtualAudioSpeaker { override fun onVirtualSpeakerMixedAudioReceived(rawData: ZoomVideoSDKAudioRawData?) { // Combined session audio. } override fun onVirtualSpeakerOneWayAudioReceived( rawData: ZoomVideoSDKAudioRawData?, user: ZoomVideoSDKUser? ) { // Per-user audio. } override fun onVirtualSpeakerShareAudioReceived(rawData: ZoomVideoSDKAudioRawData?) { // Share audio. } } val context = ZoomVideoSDKSessionContext().apply { virtualAudioSpeaker = virtualSpeaker } ``` ```java ZoomVideoSDKVirtualAudioSpeaker virtualSpeaker = new ZoomVideoSDKVirtualAudioSpeaker() { @Override public void onVirtualSpeakerMixedAudioReceived(ZoomVideoSDKAudioRawData rawData) { // Combined session audio. } @Override public void onVirtualSpeakerOneWayAudioReceived( ZoomVideoSDKAudioRawData rawData, ZoomVideoSDKUser user ) { // Per-user audio. } @Override public void onVirtualSpeakerShareAudioReceived(ZoomVideoSDKAudioRawData rawData) { // Share audio. } }; ZoomVideoSDKSessionContext context = new ZoomVideoSDKSessionContext(); context.virtualAudioSpeaker = virtualSpeaker; ``` ## Receive raw share data A screen share streams as both video frames (the screen capture) and optionally audio (the device audio the sharer included with `enableShareDeviceAudio`). ### Share video Get the share pipe from the sharing user, then subscribe exactly like you would for video. ```kotlin val pipe = user.sharePipe pipe.subscribe( ZoomVideoSDKVideoResolution.VideoResolution_360P, dataDelegate ) ``` ```java ZoomVideoSDKRawDataPipe pipe = user.getSharePipe(); pipe.subscribe( ZoomVideoSDKVideoResolution.VideoResolution_360P, dataDelegate ); ``` The same `onRawDataFrameReceived` callback fires for share frames; the same accessors and lifecycle rules apply. ### Share audio Listen for `onShareAudioRawDataReceived` on your `ZoomVideoSDKDelegate` (see [Receive raw audio frames](#receive-raw-audio-frames) above) or `onVirtualSpeakerShareAudioReceived` on a `ZoomVideoSDKVirtualAudioSpeaker`. The buffer format and access pattern is the same as session audio.