Click here to Skip to main content
15,895,142 members
Articles / Desktop Programming / XAML

Play AVI files in Silverlight 5 using MediaElement and MediaStreamSource - P/Invoke

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
14 Sep 2011CPOL5 min read 40.4K   1.7K   5  
This code demostrates how to use Silverlight 5 with OOB+elevated trust to play a local video (.avi). Uses P/Invoke support for native code
Imports System.Collections.Generic
Imports System.Threading
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports Silverlight5_SampleVB1.cAVIDefs

Public Class MyDerivedMediaStreamSource : Inherits MediaStreamSource

    ' <summary>
    '     Describes the Media Stream
    ' </summary>
    Private _videoDesc As MediaStreamDescription

    ' <summary>
    '     Width of the Video Frame (set as constant)
    ' </summary>
    Private _frameWidth As Integer = 320 ' *** best to set this when you have acquired actual video width using obj.videoWidth()

    ' <summary>
    '     Height of the Video frame (set as constant)
    ' </summary>
    Private _frameHeight As Integer = 240 ' *** best to set this when you have acquired actual video height using obj.videoHeight()

    ' <summary>
    '     Rendering time in the media 
    ' </summary>
    Private _timeStamp As Long = 0

    ' <summary>
    '     Number of bytes of each pixel (4 bytes - RGBA)
    ' </summary>
    Private Const _framePixelSize As Integer = 4

    ' <summary>
    '     Size in bytes for each Sample of type RGBA (4 bytes per pixel)
    ' </summary>
    Private _count As Integer '= _frameHeight * _frameWidth * _framePixelSize

    ' <summary>
    '     Size in bytes of the stream (same as the frame size in bytes)
    ' </summary>
    Private _frameStreamSize As Integer '= _count

    ' <summary>
    '     Stream to contain a Sample
    ' </summary>
    Private _stream As MemoryStream = New MemoryStream(_frameStreamSize)

    ' <summary>
    '     The Offset into the stream where the actual sample data begins
    ' </summary>
    Private _offset As Integer = 0

    ' <summary>                
    '     Buffer to hold a collection of type Sample. 
    ' </summary>                
    Private sampleBufferList As Queue(Of Sample) = New Queue(Of Sample)

    ' <summary>
    '     variable holds the FPS of the video played
    ' </summary>
    Public _speed As Long = 30

    ' <summary>                
    '     Timeout period (fps from video is used).                
    ' </summary>                
    Private timeout As TimeSpan = TimeSpan.FromSeconds(_speed)

    ' <summary>                
    '     Empty Dictionary used in the returned empty sample.               
    ' </summary>                
    Private emptyDictionary As Dictionary(Of MediaSampleAttributeKeys, String) = New Dictionary(Of MediaSampleAttributeKeys, String)

    ' <summary>                
    '     Total number of Samples to buffer.
    ' </summary>                
    Private Const numberOfSamplesBuffer As Integer = 15 ' I set to 15 as an example, but you can increase or decrease



    Protected Overrides Sub OpenMediaAsync()

        Dim availableStreams As List(Of MediaStreamDescription) = New List(Of MediaStreamDescription)

        Dim streamAttributes As Dictionary(Of MediaStreamAttributeKeys, String) = New Dictionary(Of MediaStreamAttributeKeys, String)

        Dim sourceAttributes As Dictionary(Of MediaSourceAttributesKeys, String) = New Dictionary(Of MediaSourceAttributesKeys, String)

        ' We are going to convert our video frame/sample from RGB to RGBA
        streamAttributes(MediaStreamAttributeKeys.VideoFourCC) = "RGBA"

        streamAttributes(MediaStreamAttributeKeys.Height) = _frameHeight.ToString()

        streamAttributes(MediaStreamAttributeKeys.Width) = _frameWidth.ToString()

        _videoDesc = New MediaStreamDescription(MediaStreamType.Video, streamAttributes)


        availableStreams.Add(_videoDesc)


        sourceAttributes(MediaSourceAttributesKeys.Duration) = TimeSpan.FromSeconds(0).Ticks.ToString()

        sourceAttributes(MediaSourceAttributesKeys.CanSeek) = False.ToString()


        ReportOpenMediaCompleted(sourceAttributes, availableStreams)

        Return


    End Sub


    Protected Overrides Sub GetSampleAsync(mediaStreamType As System.Windows.Media.MediaStreamType)

        If mediaStreamType = mediaStreamType.Video Then

            ' start a thread to get the sample                        
            'Dim thread As Thread = New Thread(New ThreadStart(AddressOf retrieveSampleThread))
            'thread.Start()

            retrieveSample()

            Return

        End If
    End Sub

    Protected Overrides Sub SeekAsync(seekToTime As Long)
        _timeStamp = seekToTime
        ReportSeekCompleted(seekToTime)
    End Sub

    Protected Overrides Sub GetDiagnosticAsync(diagnosticKind As System.Windows.Media.MediaStreamSourceDiagnosticKind)

    End Sub

    Protected Overrides Sub SwitchMediaStreamAsync(mediaStreamDescription As System.Windows.Media.MediaStreamDescription)

    End Sub

    Protected Overrides Sub CloseMedia()

        ' Do your cleanup here

        _stream.Close()
        _stream = Nothing

    End Sub

    ' <summary>
    '     flag to kill a Sample processing thread
    ' </summary>
    Private _done As Boolean = True

    ' <summary>
    '     Background  Worker Thread to process Samples
    ' </summary>
    Private _worker As BackgroundWorker = New BackgroundWorker()

    ' <summary>
    '     Represents the total number of frames in the video
    ' </summary>
    Dim numFrames As Integer = 0

    ' <summary>
    '     Byte Array for a single Sample (RGB format, hence * 3 bytes for each pixel)
    ' </summary>
    Private RGB_Sample As Byte() '= New Byte(_frameHeight * _frameWidth * 3)

    ' <summary>
    '     Byte Array for a single frame (RGBA format)
    ' </summary>
    Private RGBA_Sample() As Byte '= New Byte(_count)

    ' <summary>
    '     Set to true to rotate Sample to normal view
    ' </summary>
    Private _flipped As Boolean = True

    Public Sub closeStream()

        ' set to true to stop the processing thread
        '_done = True

        CloseMedia()


        If pGetFrameObj <> 0 Then
            Call AVIStreamGetFrameClose(pGetFrameObj) ' 
        End If
        If pAVIStream <> 0 Then
            Call AVIStreamRelease(pAVIStream) ' Close video stream
        End If
        If pAVIFile <> 0 Then
            Call AVIFileRelease(pAVIFile) ' Close the AVI file
        End If

        AVIFileExit()

    End Sub

    Public Sub flipped(val As Boolean)

        _flipped = val
        'sampleBufferList.Clear()

    End Sub



    Private j As Int32 = 0
    Private Const RGBByteCount As Int16 = 3 ' represents RGB total bytes per pixel
    Private Const RGBAByteCount As Int16 = 4 ' represents RGBA total bytes per pixel
    Private pixelPos As Int32

    Private timeexpended As Int32, frametime As Int32, initialtime As Int32
    Private timelapse As Integer, initcapture As Integer

    Private m_memBits() As Byte
    Private m_bih As New BITMAPINFO

    Private Sub initializeFrametime()
        frametime = 0
        timelapse = 30
        initcapture = 1
    End Sub

    ' <summary>
    '     Method that checks availability of a Sample
    ' </summary>
    Private Sub retrieveSample()

        '---------------------------------------------------------------
        ' I CANNOT REMEMBER WHERE I GOT THIS CALCULATION FROM ONLINE
        ' it basically calculates the frame number based on time passed
        ' since start of play
        '-------------------------**********----------------------------
        'If Not videoPaused Then
        If initcapture = 0 Then
            timeexpended = timeGetTime() - initialtime
        Else
            frametime = 0
            timeexpended = 0

            initialtime = timeGetTime()
        End If

        If initcapture = 0 Then
            If timelapse > 1000 Then
                frametime = frametime + 1
            Else
                frametime = (timeexpended / 1000) * (1000 / _speed) 'timelapse)

                If frametime >= numFrames Then
                    initializeFrametime()
                End If
            End If
        Else
            initcapture = 0
        End If
        'End If

        '-------------------------**********----------------------------
        '
        '---------------------------------------------------------------

        Try
            ' Get the requested Frame from the Stream - frame number "frametime" provided.
            ' Return a Pointer to the DIB
            pDIB = AVIStreamGetFrame(pGetFrameObj, frametime)
        Catch ex As Exception
        End Try

        If pDIB <> 0 Then

            'Copy the Frame bits into RGB_Sample
            'Call CopyMemory(RGB_Sample(0), pDIB + Marshal.SizeOf(bih), bih.biSizeImage)
            Marshal.Copy(pDIB + Marshal.SizeOf(bih), RGB_Sample, 0, bih.biSizeImage)

            If Not RGB_Sample Is Nothing Then

                For verticalCount As Integer = RGB_Sample.Length - 1 To 0 Step _frameWidth * RGBByteCount * -1

                    For horizontalCount As Integer = 0 To _frameWidth - 1

                        ' Calculate the next pixel position from the original Sample
                        ' based on the outer loop, it is calculated from bottom-right
                        pixelPos = verticalCount - (_frameWidth * RGBByteCount) + (horizontalCount * RGBByteCount) + 1

                        RGBA_Sample(j) = RGB_Sample(pixelPos)
                        RGBA_Sample(j + 1) = RGB_Sample(pixelPos + 1)
                        RGBA_Sample(j + 2) = RGB_Sample(pixelPos + 2)

                        ' Assign 1 byte for the Alpha Channel
                        RGBA_Sample(j + 3) = 255 '&FF

                        'jump 4 bytes for the RGBA byte counter
                        j += RGBAByteCount

                    Next
                Next

            End If

            j = 0

        End If


        ' Instantiate a Sample
        ' The Sample has two members (Time & Buffer)           
        Dim _sample As Sample = New Sample()
        _sample.sampleBuffer = RGBA_Sample
        _sample.sampleTime = DateTime.Now


        ' We always start at the beginning of the stream
        ' this is because we always reset the stream with one sample at a time
        ' if you decide to add more than one sample into the stream then you
        ' can modify the logic to increment this offset by the size of the sample
        ' everytime there is a call to return a sample
        _offset = 0
        _stream.Seek(0, SeekOrigin.Begin)


        ' write the retrieved Sample into the stream
        ' remember our stream is just one Sample
        _stream.Write(_sample.sampleBuffer, 0, _count)

        Dim mediaSample As MediaStreamSample = New MediaStreamSample(
                Me._videoDesc,
                _stream,
                _offset,
                _count,
                _timeStamp,
                Me.emptyDictionary)

        _timeStamp += TimeSpan.FromSeconds(1 / _speed).Ticks

        ' report back a successful Sample
        Me.ReportGetSampleCompleted(mediaSample)

        Return

    End Sub

    ' <summary>
    '     Pointer to the AVI File in memory
    ' </summary>
    Private pAVIFile As Int32

    ' <summary>
    '     Pointer to the AVI Stream in memory
    ' </summary>
    Private pAVIStream As Int32

    ' <summary>
    '     Structure representing the Bitmap Information 
    ' </summary>
    Private bih As BITMAPINFOHEADER

    ' <summary>
    '     Pointer to Video Frames Object in Memory
    ' </summary>
    Private pGetFrameObj As Int32

    ' <summary>
    '     Pointer to a DIB
    ' </summary>
    Private pDIB As Int32

    ' <summary>
    '     Number of Scanlines in a Video Frame
    ' </summary>
    Private numScans As Int16

    Private Const streamtypeVIDEO As Int32 = 1935960438
    Private Const BI_RGB As Int32 = 0

    Private firstFrame As Int32

    Private streamInfo As AVISTREAM_INFO = New AVISTREAM_INFO()


    ' <summary>
    '       Opens an AVI File and extract the Video Stream
    '       IN: fname, video file name
    ' </summary>
    Public Function startVideo(fname As String) As Boolean

        ' Return a Pointer of the File in Memory assigned to pAVIFile
        Dim res As Int32 = AVIFileOpen(pAVIFile, fname, 32, 0)

        ' Return a Pointer of the Video Stream in Memory assigned to pAVIStream
        res = AVIFileGetStream(pAVIFile, pAVIStream, streamtypeVIDEO, 0)

        ' Return the First Video Frame Number
        firstFrame = AVIStreamStart(pAVIStream)

        ' Return the Total Number of Frames in the Stream
        numFrames = AVIStreamLength(pAVIStream)

        ' Return the Video Stream Information
        res = AVIStreamInfo(pAVIStream, streamInfo, Marshal.SizeOf(streamInfo))

        _speed = streamInfo.dwRate 'fps

        With bih
            .biBitCount = 24
            .biClrImportant = 0
            .biClrUsed = 0
            .biCompression = BI_RGB
            .biHeight = streamInfo.rcFrame.bottom - streamInfo.rcFrame.top
            .biPlanes = 1
            .biSize = 40
            .biWidth = streamInfo.rcFrame.right - streamInfo.rcFrame.left
            .biXPelsPerMeter = 0
            .biYPelsPerMeter = 0
            .biSizeImage = (((.biWidth * 3) + 3) And &HFFFC) * .biHeight
        End With



        _frameWidth = bih.biWidth
        _frameHeight = bih.biHeight

        _count = _frameHeight * _frameWidth * _framePixelSize
        _frameStreamSize = _count

        numScans = IIf(_frameHeight > _frameWidth, _frameHeight, _frameWidth)



        ' Retrieve the Frames Object from the stream byte by supplying the Stream and the format we expect
        pGetFrameObj = AVIStreamGetFrameOpen(pAVIStream, bih)

        If pGetFrameObj = 0 Then Return False 'ERROR

        ' Resize our actual Samples in Bytes
        ReDim RGB_Sample(bih.biSizeImage - 1)

        ' Resize our final modified sample in Bytes
        ReDim RGBA_Sample(_count - 1)

        initializeFrametime()


        Return True

    End Function

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Other Norconsult Telematics, KSA
Canada Canada
I have been a Developer for many years. I have worked as Senior Developer in Kenya, Canada and Saudi Arabia. I enjoy coding and I am looking forward to more challenges with new Technologies

I am currently IT Manager at Norconsult Telematics, Saudi Arabia.

Comments and Discussions