Trillium SDK Examples

Trillium is a high performance video capture, playback and compositing framework that is suitable for a wide variety of .NET applications. It allows rapid video filter authoring in your preferred .NET Runtime language.
Below is a summary of each of Trillium's namespaces, as well as real-world C# code examples on how they're used.
Trillium.Presenters

Our Video Engine includes a managed Graph component for simplified DirectShow filter graph composition using standard .NET runtime object enumeration constructs. A low-latency TSDemultiplexer and filter is also included with advanced hooks for extracting PES and PSI metadata, elegant handling of heavily discontinuous feeds, and more. A powerful PresenterFactory for rendering to Direct3D 9 surfaces, WPF and HWND targets, lets you quickly output DXVA2 HD video from arbitrary sources and RF feeds. It implements custom VMR9 and EVR presenter APIs as well as HWND windowless modes.

Example

Brush d3dVideoBrush;
var player = new PresenterFactory(d3dDevice);
player.Direct3DBackBufferCount = 5;
player.Mode = PresenterFactoryMode.EVR;
player.PropertyChanged += (sender, e) => {
    if (e.PropertyName == "CurrentFrameSurface") {
        d3dVideoBrush = new Brush(player.CurrentFrameSurface);
        d3dVideoBrush.TextureStetch = TextureStretch.UniformFillWithBars;
    }
};
player.SourceUri = new Uri(yourSource);
player.Play();
Trillium.Filters

The TSDemuxerFilter is a high performance Transport Stream demultiplexer DirectShow filter. It has advanced meta data processing, error handling, and low-latency features specifically tailored for aerospace applications.

Example

var demux = new TSDemuxerFilter();
// Byte seeking support
demux.Settings.PreferredTimeFormat = TIME_FORMAT.TIME_FORMAT_BYTE;
// Begin even prior to PMT arrival
demux.Settings.FastStartupEnable = true; 
// Graph clock recovery during link loss
demux.Settings.ClockAutoCorrectionEnable = true;
// Reduce stream latency
demux.Settings.AvailableStreams.CollectionChanged
    += (object sender, NotifyCollectionChangedEventArgs e) => {
        foreach (DemultiplexSettings.StreamInfo stream in e.NewItems)
            stream.DesiredNumberOfSamples = 1; 
    };
demux.Settings.MetaDataReceive += (object sender, MetaDataEventArgs e) => {
    Debug.WriteLine("Meta data {0}: {1} ", e.PID, e.Payload.Length);
};
// Decoder selection
demux.Settings.Decoders.Add(
    new DecoderSetting(
        new DXVA2VideoDecoderFilter(),
        StreamType.MPEG2Video,
        StreamType.H264Video));
demux.Settings.Decoders.Add(
new DecoderSetting(
    Filter.GetInstalledFilters(Filter.Category.DirectShow_Filters)
    .Find(x => x.Name == "Microsoft DTV-DVD Audio Decoder"),
    StreamType.AC3Audio,
    StreamType.AACAudio,
    StreamType.MPEG2Audio));
var graph = new Graph();
graph.AddFilter(demux, "demux");
demux.Load(new FileStream(file, FileMode.Open));
graph.Run();
Trillium.UI

Our UI product is a hardware-accelerated Direct3D 9 GUI framework, specifically tailored towards rich and exciting video viewing applications. It provides a separated “rendering” and “arrange” architecture designed to eliminate non-deterministic video frame rate intervals* sometimes caused by application tasks potentially blocking the rendering pipeline.

It features easily animated layout properties, true 3D transformations, high performance pixel-shader based brushes, font tessellation, and basic user input handling (including touch.)

*Note: When WPF is used as a video rendering platform it results in non-deterministic frame rates. Videos always have jittery/tearing issues even for file based playback.

Example

var ui = new Border();
// Gradient brush
ui.Background.Value = new Brush(
    new VectorF(0, 0), 
    new VectorF(1, 1), 
    "#ff333333", 
    "#ffcccccc") { 
    GradientNoise = .25f 
};
// Add some animated content
var l = new Label();
l.Foreground.Value = new Brush("#ee222222");
l.Text.Value = "trillium demo";
l.FontStyle.Value = FontStyle.Bold;
l.FontSize.Value = 50;
l.HorizontalAlignment.Value = HorizontalAlignment.Center;
l.VerticalAlignment.Value = VerticalAlignment.Center;
l.RelativeTransform[CommonState.Default].Apply(
    new RelativeTransform(1, 1, 0, 0, 0, 0, 0),
    new RelativeTransform(2, 2, 0, 360, 0, 0, 0),
    TimeSpan.FromSeconds(4),
    Easing.CubicOut,
    Repeat.PingPong);
ui.Children.Add(l);
// Initialize the UI direct3d renderer
var renderer = new WinFormsRenderer();
renderer.TargetFrameRate = 30;
renderer.Root = ui;
this.Controls.Add(renderer);
Trillium.Mpeg

The Trillium SDK utilizes a declarative, object oriented bit parsing framework to support serialization and deserialization of transport streams as well as most DVB and ATSC sections and descriptors. Some MPEG2 and H.264/AVC bit stream support is also available. Our high-performance direct-from-the-ISO/IEC-spec object library is the foundation of the Trillium SDK.

Example

var demux = new TSDemultiplexer();
demux.DVBMode = true;
// Dump channel names and TV guide data
demux.ProcessPSI += (object sender, PSIEventsArgs e) => {
    if (e.Table is TableEIT) {
        var eit = e.Table as TableEIT;
        Debug.WriteLine(String.Format("EIT for channel {0}:", 
            eit.service_id));
        foreach (var item in eit.Items) {
            Debug.WriteLine(String.Format("{0}: {1} at {2}", 
                        item.event_id, item.Name, item.Start));
        }
    }
    if (e.Table is TableSDT) {
        var sdt = e.Table as TableSDT;
        foreach (var item in sdt.Items) {
            var multiLingual = item.Descriptors.FirstOrDefault(x => 
                x.descriptor_tag == DescriptorID.MultilingualServiceName) 
                as DescriptorMultilingualServiceName;
            var name = item.Descriptors.FirstOrDefault(x => 
                x.descriptor_tag == DescriptorID.Service) 
                as DescriptorService;
            if (multiLingual != null) {
                foreach (var local in multiLingual.Names)
                    Debug.WriteLine(
                        String.Format("Channel {0}: {1} {2}", 
                        item.service_id, local.ISO_639_language_code,
                        local.service));
            } else if (name != null) {
                Debug.WriteLine(
                    String.Format("Channel {0}: {1}", 
                    item.service_id, name.service));
            }
        }
    }
};
demux.Load(yourSource); // or demux.Write(..) directly
demux.BeginStreaming();
Trillium.Capture

A unified Device class supports the Microsoft Broadcast Driver Architecture (BDA) as well as analog inputs.

BDA television devices can be easily integrated using the Device class, which takes all of the leg-work out of building capture graphs for DVB-S, DVB-T, DVB-C and ATSC tuner devices on the market. Included is a custom hook for vendor-specific (pre-PBDA) IKsPropertySet interfaces for controlling DiSEqC.

Example

var devices = Device.GetInstalledDevices(DeviceType.DVBS);
var tuner = devices[0];
var demux = new TSDemultiplexer();
tuner.ProcessTS += (Device sender, byte* data, int length) => {
    // process the raw transport
    demux.Write(data, 0, length);
};
if (tuner.Open()) {
    tuner.Tune(new DeviceTuneRequestDVBS() {
        Frequency = 12224,
        LocalOscilatorFrequencyHighBand = 11250,
        LocalOscilatorFrequencyLowBand = 11250,
        HighLowSwitchFrequency = 14000,
        Polarity = Polarisation.BDA_POLARISATION_LINEAR_V,
        SymbolRate = 20000,
        ModulationType = ModulationType.BDA_MOD_8PSK,
        RollOff = RollOff.BDA_ROLL_OFF_25,
        Pilot = Pilot.BDA_PILOT_ON,
    });
}
Trillium.Encoding

Trillium's TSTranscoderFactory provides a simplified video encoding framework for use with live and pre-recorded sources. It supports most standard Video Encoder filters on the market today.

A HTTP Live Streaming, TS multiplexer, Fragmented-MP4 (fMP4) multiplexer and light-weight HTTP web server are also included for building web-connected applications.

Example

// Encode to a live HLS endpoint
var encoder = new TSTranscoderFactory(
    new TranscodeEndPoint(TranscodeEndPointType.HttpLiveStreaming,
        new System.Net.IPEndPoint(
            System.Net.IPAddress.Any,
            8080)));
// Settings for iPod
encoder.Settings.DefaultVideoProfile = new Profile() {
    AVEncCommonBufferSize = 8000000,
    AVEncCommonMaxBitRate = 2400000,
    AVEncCommonMeanBitRate = 900000,
    AVEncMPVProfile = eAVEncMPVProfile.eAVEncMPVProfile_Simple,
    AVEncMPVLevel = eAVEncMPVLevel.eAVEncMPVLevel_Main,
    AVEncVideoOutputChromaResolution = eAVEncVideoChromaResolution.eAVEncVideoChromaResolution_420
};
FileStream fs = new FileStream(file, FileMode.Open);
// Configure ServiceChannels automatically based on source file
byte[] b = new byte[188 * 50000];
int read = fs.Read(b, 0, b.Length);
encoder.PrePopulateSettings(b, 0, read);
// Begin encoding to HLS
fs.Position = 0;
encoder.Start(fs);
// A web server is now available and HLS, TS and 
// fragmented MP4 muxes available at http://localhost:8080/
Trillium.Compute

Trillium's ComputeOverlayFilter is a DirectShow filter that is capable of running HLSL version 5 shader code against video frames. It contains a number of features which allow you to easily upload and download C# structures to/from the GPU, with just a few lines of code, and then overlay or animate vector UIElement graphics ontop of frames.

The ComputeOverlayFilter supports end-to-end DXVA2 surface handling, meaning video frames never touch the CPU.

Example

unsafe void AddComputeShader(DecoderSetting videoDecoderSetting) {
    var compute = new ComputeOverlayFilter(
        directXDebugMode: false, // Set to true to enable DirectX 11 VS2013 shader debugging (requires sdk.)
        hwnd: null, // custom DirectX target HWND
        displayAdapter: 0); // GPU# to select
    // The Trillium demultiplexers can connect
    // the ComputeOverlayFilter to the decoder
    // automatically.
    videoDecoderSetting.NativeTransforms.Add(compute);
    // This basic overlay example draws over the changed pixels
    var overlay = new MyBakedVideoOverlay();
    compute.Overlay = overlay;
    // You can in-line HLSL code like this.
    var myShader = new Trillium.Compute.ComputeShader(
        compute, // Our ComputeOverlayFilter
    @"
    SamplerState LinearClamped
    {
        Filter = MIN_MAG_MIP_LINEAR;
        AddressU = Clamp;
        AddressV = Clamp;
    };
    cbuffer GlobalArgs
    {
        float threshold;
        float2 imageSize;
    };
    // Output
    struct Result
    {
        int x;
        int y;
        float change; // change in intensity
    };
    // Constants
    AppendStructuredBuffer<Result> Results : register(u0);
    Texture2D CurrentFrame;
    Texture2D PreviousFrame;
    Texture2D ThirdFrame;
    [numthreads(16, 16, 1)]
    void MyFunction(uint3 dispatchThreadID : SV_DispatchThreadID) {
        int x = dispatchThreadID.x;
        int y = dispatchThreadID.y;
        float2 textureCoord = float2(x / imageSize.x, y / imageSize.y);
        float4 current = CurrentFrame.SampleLevel(LinearClamped, textureCoord, 0);
        float4 last = PreviousFrame.SampleLevel(LinearClamped, textureCoord, 0);
        float4 third = ThirdFrame.SampleLevel(LinearClamped, textureCoord, 0);
        float intensityCurrent = (current.r + current.g + current.b) / 3;
        float intensityLast = (last.r + last.g + last.b) / 3;
        float intensityThird = (third.r + third.g + third.b) / 3;
        float change = ((intensityLast+intensityThird)/2) - intensityCurrent;
        // Compare last two frames with current for change in intensity
        if(abs(change) > threshold) {
            Result res;
            res.x = x;
            res.y = y;
            res.change = change;
            Results.Append(res); // append output struct
        }
    }
                    ",
            "MyFunction", // Entry point
            false); // Debug compile flag
    // When the ComputeOverlayFilter has video frame
    // dimensions, update the compute shader.
    compute.SizeChanged += (newSize) => {
        var globals = new GlobalArgs();
        globals.imageSize = new VectorF(newSize.Width, newSize.Height);
        globals.threshold = .25f;
        myShader.InitConstant<GlobalArgs>(&globals);
    };
    // Initialize the HLSL "results" buffer for CPU read-back
    myShader.InitUAVStructuredBuffer<Result>("Results", 256);
    // Configure our frame samplers. The ComputeOverlayFilter
    // can back-reference a number of frames, or keep a permanent
    // copy of a reference frame.
    myShader.InitTexture("CurrentFrame", TextureAssignment.Frame_0);
    myShader.InitTexture("PreviousFrame", TextureAssignment.Frame_1);
    myShader.InitTexture("ThirdFrame", TextureAssignment.Frame_2);
    myShader.OnResult = () => {
        // Copy results to CPU. We're exposing the locked buffer
        // in a nice C# IList
        using (var list = myShader
            .ReadUAVStructuredBuffer<result>("Results")) {
            overlay.Update(list);
        }
        // when our LockedGpuArray<t> is disposed, it becomes
        // freed (unmapped)
    };
    // Add the shader to the ComputeOverlayFilter's collection
    compute.ComputeShaders.Add(myShader);
}
unsafe class MyBakedVideoOverlay : UIElement {
    Result[] Results = new Result[256];
    int Count;
    public void Update(LockedGpuArray<Result> ar) {
        // We'll only take the first 256
        int count = Math.Min(ar.Count, Results.Length);
        fixed (Result* pDest = Results)
            NativeMethods.CopyMemory((byte*)pDest, (byte*)ar.Map.pData, sizeof(Result) * count);
        Count = count;
        InvalidateRender();
    }
    protected override void OnRender(DrawingContext dc, SizeF size) {
        for (int i = 0; i < Count; i++) {
            var r = Results[i];
            dc.DrawRectangle(new RectangleF(r.x, r.y, 4, 4), Brush.Fuschia, null, 0);
        }
    }
}
/// <summary>
/// Define a C# equivalent of the HLSL
/// structured buffer.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
struct Result {
    public int x;
    public int y;
    public float change; // change in rgb
};
/// <summary>
/// Define a C# equivalent of the HLSL
/// cbuffer GlobalArgs.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
unsafe struct GlobalArgs {
    public float threshold;
    public VectorF imageSize;
    fixed byte padding[4];
}
Ready to evaluate?

Each and every new customer evaluation demands a significant investment on our side in the form of e-mail consultations and 1-on-1 guidance. To ensure that your evaluation enquiry is considered legitimate, please provide your full company name and use an official company e-mail address. Enquiries from generic email services may not receive a response. We promise not to disclose your information to any 3rd parties.

 

© Copyright 2017 Media Foundry Inc.