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 allocation — MediaGlobal.LockBuffer allocates a VersatileBuffer from the buffer pool. Append copies one frame of pre-encoded audio data into the buffer.
  2. Timestamps — Pts and Dts are set in 100-nanosecond units (the .NET Ticks time base). All audio frames are key frames.
  3. Pushing — PushMedia 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