Table of Contents

Send Sample 1

The SendSample1 demonstrates how to use IMediaSink directly to send pre-encoded audio data to a remote server. This is useful when you have pre-encoded media (from a file, capture device, or external encoder) and need to push it to a remote endpoint over RTP/RTSP.

Overview

The SendSample1 class performs the following:

  1. Creates an RtspPublisherSink targeting a remote RTP endpoint
  2. Configures a G.711 µ-law audio stream and registers it with the sink
  3. Opens the sink asynchronously and starts pushing on the Opened state
  4. Paces audio frames against real-time on a background task, looping the source data continuously

Creating the Sink

this.sink = new VAST.RTSP.RtspPublisherSink();
this.sink.Uri = "rtp://127.0.0.1:10000";
this.sink.Error += sink_Error;
this.sink.StateChanged += sink_StateChanged;

RtspPublisherSink implements IMediaSink and pushes media data to a remote RTSP or RTP server. The URI determines the transport protocol — rtp:// sends raw RTP packets, rtsp:// uses RTSP ANNOUNCE/RECORD signaling.

Configuring the Audio Stream

audioMediaType = new VAST.Common.MediaType
{
    ContentType = Common.ContentType.Audio,
    CodecId = Common.Codec.G711U,
    SampleRate = 8000,
    Channels = 1,
    Bitrate = 64000,
    FrameSize = 1280
};

this.sink.AddStream(0, audioMediaType);
this.sink.Open();

The audio stream is configured as G.711 µ-law (a standard telephony codec) with the following parameters:

Property Value Description
CodecId G711U G.711 µ-law codec
SampleRate 8000 8 kHz sample rate
Channels 1 Mono audio
Bitrate 64000 64 kbps
FrameSize 1280 Bytes per audio frame

AddStream registers the media stream with the sink before opening. The sink opens asynchronously — media pushing begins after the Opened state is reached.

Sink State Management

private void sink_StateChanged(object sender, Media.MediaState e)
{
    lock (this)
    {
        switch (e)
        {
            case VAST.Media.MediaState.Opened:
                this.sink.Start();
                this.pushingTask = Task.Run(() => pushingRoutine());
                break;
            case VAST.Media.MediaState.Started:
                // sink has been started
                break;
            case VAST.Media.MediaState.Closed:
                // sink has been disconnected
                break;
        }
    }
}

When the sink reaches the Opened state, Start() is called and the pushing routine is launched on a background task.

Pushing Audio Data

private void pushingRoutine()
{
    DateTime streamingStarted = DateTime.Now;
    long audioFrameCount = 0;
    int audioBitstreamPosition = 0;

    while (this.running)
    {
        long playbackTime = (long)(DateTime.Now - streamingStarted).Ticks;
        long audioFileTime = audioFrameCount * 10000000L * 1024 / audioMediaType.SampleRate;

        do
        {
            if (audioBitstreamPosition >= audioBuffer.Length)
            {
                audioBitstreamPosition = 0; // loop
            }

            if (audioFileTime <= playbackTime)
            {
                VAST.Common.VersatileBuffer packet =
                    VAST.Media.MediaGlobal.LockBuffer(audioMediaType.FrameSize);
                packet.Append(audioBuffer, audioBitstreamPosition, audioMediaType.FrameSize);
                packet.Pts = packet.Dts = audioFileTime;
                packet.KeyFrame = packet.CleanPoint = true;
                packet.StreamIndex = 0;

                this.sink.PushMedia(0, packet);
                packet.Release();

                audioBitstreamPosition += audioMediaType.FrameSize;
                audioFrameCount++;
                audioFileTime = audioFrameCount * 10000000L
                    * audioMediaType.FrameSize * 8 / audioMediaType.Bitrate;
            }
        }
        while (audioFileTime <= playbackTime);

        Task.Delay(10).Wait();
    }
}

The pushing routine maintains real-time pacing by comparing elapsed wall-clock time against the audio timeline:

  1. Buffer allocationMediaGlobal.LockBuffer allocates a VersatileBuffer from the buffer pool. Append copies one frame of pre-encoded audio data into the buffer.
  2. TimestampsPts and Dts are set in 100-nanosecond units (the .NET Ticks time base). All audio frames are key frames.
  3. PushingPushMedia sends the buffer to the remote endpoint. Release returns the buffer to the pool.
  4. Looping — when the bitstream position reaches the end of the source data, it resets to zero for continuous playback.
  5. Pacing — the inner do/while loop catches up if the thread falls behind, then the outer loop sleeps for 10 ms before checking again.

See Also