Table of Contents

Receive Sample

The ReceiveSample demonstrates how to use IMediaSource directly to connect to a remote stream, receive compressed media samples, and optionally decode them into raw pixel data (BGRA) and PCM audio (S16). This is useful when you need to process media frames in your application — for example, computer vision, audio analysis, or custom rendering.

Overview

The ReceiveSample class performs the following:

  1. Creates a media source from a URI using SourceFactory (or HlsClientSource for HTTP)
  2. Opens the source asynchronously and starts receiving media on the Opened state
  3. Detects streams via NewStream and creates decoders for video and audio
  4. Queues incoming samples and decodes them on a background task
  5. Delivers decoded raw data to processData() for application-specific processing

Creating the Source

#if VAST_FEATURE_HLS
if (uri.StartsWith("http"))
{
    this.source = new VAST.HLS.HlsClientSource();
}
else
#endif
{
    this.source = VAST.Media.SourceFactory.Create(uri);
}

this.source.Uri = uri;
this.source.NewStream += source_NewStream;
this.source.NewSample += source_NewSample;
this.source.Error += source_Error;
this.source.StateChanged += source_StateChanged;

this.source.Open();

SourceFactory creates the appropriate source for the URI protocol (rtsp://, rtmp://, srt://, etc.). HTTP URIs require the HLS client source explicitly. Four event handlers are registered:

Event Description
NewStream A new media stream has been detected
NewSample A compressed media sample has been received
StateChanged Source state transitions (Opened, Started, Closed)
Error An error occurred

Source State Management

private void source_StateChanged(object sender, Media.MediaState e)
{
    switch (e)
    {
        case VAST.Media.MediaState.Opened:
            this.source.Start();
            break;
        case VAST.Media.MediaState.Started:
            // samples will come soon
            break;
        case VAST.Media.MediaState.Closed:
            // source has been disconnected
            break;
    }
}

The source opens asynchronously. When the Opened state is reached, Start() is called to begin receiving media samples.

Stream Detection and Decoder Creation

When a new stream is detected, a decoder is created based on the content type:

Video Decoder

case VAST.Common.ContentType.Video:
    if (e.MediaType.CodecId == VAST.Common.Codec.Uncompressed
        && e.MediaType.PixelFormat == VAST.Common.PixelFormat.BGRA)
    {
        // already in BGRA format, no decoding needed
        streamContext.PerformDecoding = false;
    }
    else
    {
        VAST.Common.MediaType desiredVideoMediaType = new VAST.Common.MediaType
        {
            ContentType = VAST.Common.ContentType.Video,
            CodecId = VAST.Common.Codec.Uncompressed,
            PixelFormat = VAST.Common.PixelFormat.BGRA,
            Width = e.MediaType.Width,
            Height = e.MediaType.Height,
        };

        streamContext.Decoder = VAST.Media.DecoderFactory.Create(
            e.MediaType, desiredVideoMediaType, decoderParameters);
        streamContext.PerformDecoding = true;
    }
    break;

Video is decoded to uncompressed BGRA pixel data at the source resolution. If the source already provides uncompressed BGRA, decoding is skipped.

Audio Decoder

case VAST.Common.ContentType.Audio:
    if (e.MediaType.CodecId == VAST.Common.Codec.PCM
        && e.MediaType.SampleFormat == VAST.Common.SampleFormat.S16)
    {
        // already in PCM S16 format, no decoding needed
        streamContext.PerformDecoding = false;
    }
    else
    {
        VAST.Common.MediaType desiredAudioMediaType = new VAST.Common.MediaType
        {
            ContentType = VAST.Common.ContentType.Audio,
            CodecId = VAST.Common.Codec.PCM,
            SampleFormat = VAST.Common.SampleFormat.S16,
            SampleRate = e.MediaType.SampleRate,
            Channels = e.MediaType.Channels,
        };

        streamContext.Decoder = VAST.Media.DecoderFactory.Create(
            e.MediaType, desiredAudioMediaType, decoderParameters);
        streamContext.PerformDecoding = true;
    }
    break;

Audio is decoded to PCM signed 16-bit samples at the source sample rate and channel count.

Decoder Parameters

VAST.Media.DecoderParameters decoderParameters = new VAST.Media.DecoderParameters
{
    PreferredMediaFramework = useFFmpeg
        ? VAST.Common.MediaFramework.FFmpeg
        : VAST.Common.MediaFramework.Builtin,
    AllowHardwareAcceleration = allowHardwareAcceleration,
};
Field Default Description
useFFmpeg false Use FFmpeg for decoding instead of the built-in framework
allowHardwareAcceleration true Allow GPU-accelerated decoding

Sample Queue and Decoding

Incoming compressed samples are queued in source_NewSample and decoded on a background task:

Queuing Samples

private void source_NewSample(object sender, VAST.Media.NewSampleEventArgs e)
{
    this.inputSampleQueue.Enqueue(e.Sample);
    e.Sample.AddRef();
}

The sample's reference count is incremented via AddRef() to keep it alive until the decoding task processes it.

Decoding Loop

private async void decodingRoutine()
{
    while (this.running)
    {
        VAST.Common.VersatileBuffer inputSample = null;

        lock (this)
        {
            if (this.inputSampleQueue.Count > 0)
            {
                inputSample = this.inputSampleQueue.Dequeue();
            }
        }

        if (inputSample != null)
        {
            StreamContext streamContext = this.streams[inputSample.StreamIndex];

            if (streamContext.PerformDecoding)
            {
                streamContext.Decoder.Write(inputSample);

                while (true)
                {
                    VAST.Common.VersatileBuffer decodedSample = streamContext.Decoder.Read();
                    if (decodedSample == null) break;

                    this.processData(decodedSample);
                    decodedSample.Release();
                }
            }
            else
            {
                this.processData(inputSample);
            }

            inputSample.Release();
        }
        else
        {
            await Task.Delay(10);
        }
    }
}

The decoding loop follows the same write/read pattern as in Push Source 3: write a compressed sample, then read decoded frames in a loop. When decoding is not needed (source already provides the desired format), samples are passed directly to processData.

Processing Decoded Data

private void processData(VAST.Common.VersatileBuffer sample)
{
    // TODO: process decoded, uncompressed data
}

This is where application-specific processing goes:

  • Videosample contains a raw BGRA pixel array that can be used to create a bitmap, fed into a computer vision pipeline, or rendered to screen
  • Audiosample contains a raw PCM signed 16-bit buffer for audio analysis, playback, or further processing

See Also