'*************************************************************************************
'
' File name: MseqProcess.vb
' Location: See Matrox Example Launcher in the MIL Control Center
' 
'
' Synopsis:  This program shows the use of the MseqProcess() and MseqFeed() functions
'            to perform real-time encoding of a sequence of captured images.
'
'            The user's preprocessing and compression code is written in a hook
'            function that will be called by MdigProcess() for each frame grabbed
'            (see ProcessingFunction()). The queueing for encoding of the next
'            frame is also made in that hook function to allow fully parallel
'            execution of the capture and the encoding.
'
'      Note: The average encoding time must be shorter than the grab time or
'            some frames will be missed. Missed frames are very frequent when
'            the encoding is done by software. Also, if the captured images
'            are not displayed and the frame count is not drawn or printed
'            in the hook function, the CPU usage is reduced significantly.
'
'            When encoding a 1080p source it is recommended to have your
'            MIL Non-Paged Memory set to at least 64MB.
'
' Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
' All Rights Reserved

Imports Microsoft.VisualBasic
Imports System
Imports System.Runtime.InteropServices
Imports System.Text
Imports Matrox.MatroxImagingLibrary

Namespace MseqProcess
    ' User's processing function hook data object.
    Friend Class ProcessingHookDataStruct
        Public MilDigitizer As MIL_ID
        Public MilImageDisp As MIL_ID
        Public MilSeqContext As MIL_ID
        Public ProcessedImageCount As MIL_INT
        Public ProcessingOperation As ProcessingHookOperation
    End Class

    ' Optional encoding end function hook data object.
    Friend Class EncodingFrameEndHookDataStruct
        Public EncodedImageCount As MIL_INT
    End Class

    ' Optional decoding end function hook data object.
    Friend Class DecodingFrameEndHookDataStruct
        Public DecodedImageCount As MIL_INT
        Public MilImageDisp As MIL_ID
    End Class

    Friend Enum ProcessingHookOperation
        DISPLAY
        ENCODE
    End Enum

    Friend Class Program
        ' Number of images in the buffering grab queue.
        ' Generally, increasing this number gives better real-time grab.
        Private Const BUFFERING_SIZE_MAX As Integer = 20

        ' Target sequence file name and location.
        ' The temporary directory location can be reached with %temp% under Windows. 
        Private Shared ReadOnly SEQUENCE_FILE As String = MIL.M_TEMP_DIR & "SeqProcess.mp4"

        ' Remote target sequence file name and location if Distributed MIL is used.
        Private Shared ReadOnly REMOTE_SEQUENCE_FILE As String = "remote:///" + SEQUENCE_FILE


        Shared Function Main(ByVal args() As String) As Integer
            Dim MilApplication As MIL_ID = MIL.M_NULL
            Dim MilRemoteApplication As MIL_ID = MIL.M_NULL
            Dim MilSystem As MIL_ID = MIL.M_NULL
            Dim MilDigitizer As MIL_ID = MIL.M_NULL
            Dim MilDisplay As MIL_ID = MIL.M_NULL
            Dim MilImageDisp As MIL_ID = MIL.M_NULL
            Dim MilGrabBufferList(BUFFERING_SIZE_MAX - 1) As MIL_ID
            Dim MilCompressContext As MIL_ID = MIL.M_NULL
            Dim MilDecompressContext As MIL_ID = MIL.M_NULL
            Dim LicenseModules As MIL_INT = 0
            Dim MilSystemLocation As MIL_INT = MIL.M_NULL
            Dim MilGrabBufferListSize As MIL_INT
            Dim ProcessFrameCount As MIL_INT = 0
            Dim NbFrames As MIL_INT = 0
            Dim n As MIL_INT = 0
            Dim EncodingDesiredFrameRate As Double = 0.0
            Dim ProcessFrameRate As Double = 0.0
            Dim SeqProcessFilePathSize As MIL_INT = 0
            Dim SeqProcessFilePath As StringBuilder = Nothing
            Dim ProcessingUserHookData As New ProcessingHookDataStruct()
            Dim EncodingFrameEndUserHookData As New EncodingFrameEndHookDataStruct()
            Dim DecodingFrameEndUserHookData As New DecodingFrameEndHookDataStruct()
            Dim SeqSystemType As MIL_INT = MIL.M_NULL

            ' Allocate defaults.
            MIL.MappAllocDefault(MIL.M_DEFAULT, MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)

            MIL.MsysInquire(MilSystem, MIL.M_OWNER_APPLICATION, MilRemoteApplication)
            MilSystemLocation = MIL.MsysInquire(MilSystem, MIL.M_LOCATION, MIL.M_NULL)

            ' Check if the system is running under Windows.
            If (MIL.MappInquire(MilRemoteApplication, MIL.M_PLATFORM_OS_TYPE, MIL.M_NULL) <> MIL.M_OS_WINDOWS) Then
                If (MilSystemLocation = MIL.M_REMOTE) Then
                    Console.WriteLine("The Distributed MIL server must run on a Windows system.")
                Else
                    Console.WriteLine("This example only works with a Windows system.")
                End If
                Console.WriteLine("Press <Enter> to end.")
                Console.ReadKey()

                MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)
                Return 0
            End If

            ' Inquire MIL licenses.
            MIL.MappInquire(MilRemoteApplication, MIL.M_LICENSE_MODULES, LicenseModules)
            If ((LicenseModules And MIL.M_LICENSE_JPEGSTD) <> MIL.M_LICENSE_JPEGSTD) Then
                Console.WriteLine("Need a Compression/Decompression license to run this example.")
                Console.WriteLine("Press <Enter> to end.")
                Console.ReadKey()

                MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)
                Return 0
            End If

            ' Allocate the grab buffers and clear them.
            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_DISABLE)
            For MilGrabBufferListSize = 0 To BUFFERING_SIZE_MAX - 1
                MIL.MbufAllocColor(MilSystem, _
                                   MIL.MdigInquire(MilDigitizer, MIL.M_SIZE_BAND, MIL.M_NULL), _
                                   MIL.MdigInquire(MilDigitizer, MIL.M_SIZE_X, MIL.M_NULL), _
                                   MIL.MdigInquire(MilDigitizer, MIL.M_SIZE_Y, MIL.M_NULL), _
                                   8 + MIL.M_UNSIGNED, MIL.M_IMAGE + MIL.M_GRAB + MIL.M_PROC, _
                                   MilGrabBufferList(CType(MilGrabBufferListSize, Integer)))

                If MilGrabBufferList(CType(MilGrabBufferListSize, Integer)) <> MIL.M_NULL Then
                    MIL.MbufClear(MilGrabBufferList(CType(MilGrabBufferListSize, Integer)), &HFF)
                Else
                    Exit For
                End If
            Next MilGrabBufferListSize
            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_ENABLE)

            ' Free buffers to leave space for possible temporary buffers.
            n = 0
            Do While n < 2 AndAlso MilGrabBufferListSize > 0
                MilGrabBufferListSize -= 1
                MIL.MbufFree(MilGrabBufferList(CType(MilGrabBufferListSize, Integer)))
                n += 1
            Loop

            If MilGrabBufferListSize = 0 Then
                Console.WriteLine("!!! No grab buffers have been allocated. Need to set more Non-Paged Memory. !!!")

                MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)
                Console.WriteLine("Press <Enter> to end.")
                Console.ReadKey()
                Return 1
            End If

            ' Initialize the User's processing function data structure only for Display.
            ProcessingUserHookData.MilDigitizer = MilDigitizer
            ProcessingUserHookData.MilSeqContext = MIL.M_NULL
            ProcessingUserHookData.MilImageDisp = MilImageDisp
            ProcessingUserHookData.ProcessedImageCount = 0
            ProcessingUserHookData.ProcessingOperation = ProcessingHookOperation.DISPLAY

            ' get a handle to the HookDataStruct object in the managed heap, we will use this 
            ' handle to get the object back in the callback function
            Dim hUserData As GCHandle = GCHandle.Alloc(ProcessingUserHookData)

            ' Start the sequence acquisition. The preprocessing and encoding hook function 
            ' is called for every frame grabbed.
            Dim ProcessingFunctionDelegate As MIL_DIG_HOOK_FUNCTION_PTR = AddressOf ProcessingFunction

            ' Start MdigProcess() to show the live camera output.
            MIL.MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize, MIL.M_START, MIL.M_DEFAULT, ProcessingFunctionDelegate, GCHandle.ToIntPtr(hUserData))

            ' Print a message.
            Console.Write(vbNewLine + "H.264 IMAGE SEQUENCE COMPRESSION." + vbNewLine)
            Console.Write("---------------------------------" + vbNewLine + vbNewLine)
            Console.Write("Press <Enter> to start compression." + vbNewLine)
            Console.ReadKey()

            ' Stop MdigProcess()
            MIL.MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize, MIL.M_STOP, MIL.M_DEFAULT, ProcessingFunctionDelegate, GCHandle.ToIntPtr(hUserData))

            ' Inquire the rate at which the frames were grabbed using MdigProcess()
            MIL.MdigInquire(MilDigitizer, MIL.M_PROCESS_FRAME_RATE, EncodingDesiredFrameRate)
            Console.Write("Grabbing frames at  {0:0.00} frames/sec." + vbNewLine, EncodingDesiredFrameRate)

            ' Creates a context for the H.264 compression engine. Compression will be done
            ' using hardware or software depending on the system hardware configuration. 
            MIL.MseqAlloc(MilSystem, MIL.M_DEFAULT, MIL.M_SEQ_COMPRESS, MIL.M_DEFAULT, MIL.M_DEFAULT, MilCompressContext)

            ' Specify the destination of the compressed file and the target container type.
            ' The last argument specifies to generate an MP4 file.
            MIL.MseqDefine(MilCompressContext, MIL.M_SEQ_OUTPUT(0) + MIL.M_SEQ_DEST(0), MIL.M_FILE, (If(MilSystemLocation <> MIL.M_REMOTE, SEQUENCE_FILE, REMOTE_SEQUENCE_FILE)), MIL.M_FILE_FORMAT_MP4)

            ' Set the compression context's settings.
            ' Sets the compression context's settings to compress frames at any resolution under
            ' 1920 x 1080. Any resolution higher than that will generate a warning that can be disabled
            ' using MseqControl with M_SETTING_AUTO_ADJUSTMENT. See documentation for more details.
            '
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_BIT_RATE_MODE, MIL.M_VARIABLE)      ' MIL.M_VARIABLE or MIL.M_CONSTANT
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_BIT_RATE, 5000)                     ' 5 Mbps bit rate
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_BIT_RATE_MAX, 5000)                 ' 5 Mbps bit rate
            If EncodingDesiredFrameRate <> 0 Then
                MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_FRAME_RATE, EncodingDesiredFrameRate)      ' 60Hz frame rate.
            End If

            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_FRAME_RATE_MODE, MIL.M_VARIABLE)    ' Attempts to update the file header with the encoding frame rate if lower than the specified frame rate.
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_PROFILE, MIL.M_PROFILE_HIGH)        ' MIL.M_PROFILE_BASELINE, MIL.M_PROFILE_MAIN, MIL.M_PROFILE_HIGH
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_LEVEL, MIL.M_LEVEL_4_2)             ' MIL.M_LEVEL_1, MIL.M_LEVEL_1B, MIL.M_LEVEL_1_1, MIL.M_LEVEL_1_2, MIL.M_LEVEL_1_3,
            ' MIL.M_LEVEL_2, MIL.M_LEVEL_2_1, MIL.M_LEVEL_2_2,
            ' MIL.M_LEVEL_3, MIL.M_LEVEL_3_1, MIL.M_LEVEL_3_2,
            ' MIL.M_LEVEL_4, MIL.M_LEVEL_4_1, MIL.M_LEVEL_4_2,
            ' MIL.M_LEVEL_5, MIL.M_LEVEL_5_1
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_STREAM_GROUP_OF_PICTURE_SIZE, 30) ' Interval between I-Frame

            ' Initialize the optional encoding end function data structure.
            EncodingFrameEndUserHookData.EncodedImageCount = 0

            ' get a handle to the HookDataStruct object in the managed heap, we will use this 
            ' handle to get the object back in the callback function
            Dim EncodingFrameEndUserHookDataHandle As GCHandle = GCHandle.Alloc(EncodingFrameEndUserHookData)

            ' Register the encoding end function to the sequence context.
            Dim FrameEncodingEndFunctionDelegate As MIL_SEQ_HOOK_FUNCTION_PTR = AddressOf FrameEncodingEndFunction
            MIL.MseqHookFunction(MilCompressContext, MIL.M_FRAME_END, FrameEncodingEndFunctionDelegate, GCHandle.ToIntPtr(EncodingFrameEndUserHookDataHandle))

            ' Provide a sample image to initialize the encoding engine accordingly.
            MIL.MseqControl(MilCompressContext, MIL.M_CONTEXT, MIL.M_BUFFER_SAMPLE, MilGrabBufferList(0))

            ' Disable error prints because MseqProcess() might not support the current input source.
            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_DISABLE)

            ' Start the encoding process, waits for buffer to be fed for encoding.
            MIL.MseqProcess(MilCompressContext, MIL.M_START, MIL.M_ASYNCHRONOUS)

            ' Checks if an error has been logged by MseqProcess(). If so, stop the example.
            If (CheckMseqProcessError(MilApplication, MilCompressContext)) Then

                ' An error happened during MseqProcess() and we need to free the allocated resources.
                MIL.MseqProcess(MilCompressContext, MIL.M_STOP, MIL.M_NULL)

                Dim SourceSizeX As MIL_INT = 0
                Dim SourceSizeY As MIL_INT = 0
                Dim SourceFPS As Double = 0.0

                MIL.MdigInquire(MilDigitizer, MIL.M_SIZE_X, SourceSizeX)
                MIL.MdigInquire(MilDigitizer, MIL.M_SIZE_Y, SourceSizeY)
                MIL.MdigInquire(MilDigitizer, MIL.M_PROCESS_FRAME_RATE, SourceFPS)

                Console.WriteLine("Unable to perform H.264 encoding with the current input source of")
                Console.WriteLine("{0} X {1} @ {2:0.00} fps.", SourceSizeX, SourceSizeY, SourceFPS)
                Console.WriteLine(vbNewLine + "Example parameters are optimized for sources of")
                Console.WriteLine("1920 x 1080 @ 60 fps.")
                Console.WriteLine(vbNewLine + "You can try changing encoding parameters to better match your source." + vbNewLine)

                Console.WriteLine("Press <Enter> to end.")
                Console.ReadKey()

                Do While MilGrabBufferListSize > 0
                    MilGrabBufferListSize -= 1
                    MIL.MbufFree(MilGrabBufferList(CType(MilGrabBufferListSize, Integer)))
                    MilGrabBufferList(CType(MilGrabBufferListSize, Integer)) = MIL.M_NULL
                Loop


                MIL.MseqFree(MilCompressContext)
                MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)
                Return 0
            End If

            ' MseqProcess() is running without error, so re-enable the error print.
            MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_ENABLE)

            ' Display the type of compression used.
            Console.Write("Live image capture and compression to file using ")
            MIL.MseqInquire(MilCompressContext, MIL.M_CONTEXT, MIL.M_CODEC_TYPE, SeqSystemType)
            If SeqSystemType = MIL.M_HARDWARE + MIL.M_QSV Then
                Console.WriteLine("Hardware acceleration.")
            Else ' MIL.M_SOFTWARE + MIL.M_QSV
                Console.WriteLine("Software implementation.")
            End If

            ' Set the sequence context id in the user hook data structure to start
            ' feeding buffers for encoding in ProcessingFunction.
            '
            ProcessingUserHookData.MilSeqContext = MilCompressContext
            ProcessingUserHookData.ProcessedImageCount = 0
            ProcessingUserHookData.ProcessingOperation = ProcessingHookOperation.ENCODE

            MIL.MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize, MIL.M_START, MIL.M_DEFAULT, ProcessingFunctionDelegate, GCHandle.ToIntPtr(hUserData))

            ' NOTE: Now the main() is free to perform other tasks while the compression is executing.
            ' ---------------------------------------------------------------------------------------

            ' Print a message and wait for a key press after a minimum number of frames.
            Console.WriteLine("Press <Enter> to stop." + vbNewLine)
            Console.ReadKey()

            ' Stop the processing.
            MIL.MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize, MIL.M_STOP + MIL.M_WAIT, MIL.M_DEFAULT, ProcessingFunctionDelegate, GCHandle.ToIntPtr(hUserData))

            ' Free the processing user data handle.
            hUserData.Free()

            ' Stop the encoding process
            MIL.MseqProcess(MilCompressContext, MIL.M_STOP, MIL.M_WAIT)

            ' Make sure the hook handler function delegate is not prematurely garbage collected since
            ' only MIL has a reference to it.
            GC.KeepAlive(FrameEncodingEndFunctionDelegate)

            ' Free the encoding user data handle.
            EncodingFrameEndUserHookDataHandle.Free()

            ' Print statistics.
            MIL.MdigInquire(MilDigitizer, MIL.M_PROCESS_FRAME_COUNT, ProcessFrameCount)
            MIL.MdigInquire(MilDigitizer, MIL.M_PROCESS_FRAME_RATE, ProcessFrameRate)
            Console.WriteLine("{0} frames encoded at {1:0.00} frames/sec ({2:0.0} ms/frame).", ProcessFrameCount, ProcessFrameRate, 1000.0 / ProcessFrameRate)
            Console.WriteLine()

            MIL.MseqInquire(MilCompressContext, MIL.M_SEQ_OUTPUT(0) + MIL.M_SEQ_DEST(0), MIL.M_STREAM_FILE_NAME + MIL.M_STRING_SIZE, SeqProcessFilePathSize)
            SeqProcessFilePath = New StringBuilder(CInt(SeqProcessFilePathSize))
            MIL.MseqInquire(MilCompressContext, MIL.M_SEQ_OUTPUT(0) + MIL.M_SEQ_DEST(0), MIL.M_STREAM_FILE_NAME, SeqProcessFilePath)

            ' Free the grab buffers and sequence context.
            Do While MilGrabBufferListSize > 0
                MilGrabBufferListSize -= 1
                MIL.MbufFree(MilGrabBufferList(CType(MilGrabBufferListSize, Integer)))
                MilGrabBufferList(CType(MilGrabBufferListSize, Integer)) = MIL.M_NULL
            Loop

            MIL.MseqFree(MilCompressContext)

            If ProcessFrameCount > 1 Then

                Console.WriteLine("The video sequence file was written to:")
                Console.WriteLine("{0}.", SeqProcessFilePath.ToString())
                Console.WriteLine()
                Console.WriteLine("It can be played back using any compatible video player.")

                ' Wait for a key to start the replay.
                Console.WriteLine("Press <Enter> to replay encoded sequence.")
                Console.ReadKey()

                MIL.MseqAlloc(MilSystem, MIL.M_DEFAULT, MIL.M_SEQ_DECOMPRESS, MIL.M_DEFAULT, MIL.M_DEFAULT, MilDecompressContext)

                MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_DISABLE)

                ' Specify the destination of the compressed file and the target container type.
                ' The last argument specifies to generate an MP4 file.
                MIL.MseqDefine(MilDecompressContext, MIL.M_SEQ_INPUT(0), MIL.M_FILE, (If(MilSystemLocation <> MIL.M_REMOTE, SEQUENCE_FILE, REMOTE_SEQUENCE_FILE)), MIL.M_FILE_FORMAT_MP4)

                If PrintMilErrorMessage(MilApplication) <> 0 Then

                    Console.Write(vbNewLine + "Press <Enter> to end." + vbNewLine)
                    Console.ReadKey()
                    MIL.MseqFree(MilDecompressContext)
                    MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)
                    Return 0
                End If
                MIL.MappControl(MIL.M_DEFAULT, MIL.M_ERROR, MIL.M_PRINT_ENABLE)

                Dim outputFrameRate As Double = 0.0
                MIL.MseqInquire(MilDecompressContext, MIL.M_SEQ_INPUT(0), MIL.M_STREAM_FRAME_RATE, outputFrameRate)

                Console.WriteLine()
                Console.WriteLine("Replaying file at {0:0.00} frames/second.", outputFrameRate)

                ' Initialize the optional decoding end function data structure.
                DecodingFrameEndUserHookData.DecodedImageCount = 0
                DecodingFrameEndUserHookData.MilImageDisp = MilImageDisp

                ' get a handle to the HookDataStruct object in the managed heap, we will use this 
                ' handle to get the object back in the callback function
                Dim DecodingFrameEndUserHookDataHandle As GCHandle = GCHandle.Alloc(DecodingFrameEndUserHookData)

                ' Register the decoding end function to the sequence context.
                Dim FrameDecodingEndFunctionDelegate As MIL_SEQ_HOOK_FUNCTION_PTR = AddressOf FrameDecodingEndFunction
                MIL.MseqHookFunction(MilDecompressContext, MIL.M_FRAME_END, FrameDecodingEndFunctionDelegate, GCHandle.ToIntPtr(DecodingFrameEndUserHookDataHandle))

                ' Start the decoding process, waits for buffer to be fed for encoding.
                MIL.MseqProcess(MilDecompressContext, MIL.M_START, MIL.M_ASYNCHRONOUS)

                ' Print a message and wait for a key press after a minimum number of frames.
                Console.WriteLine("Press <Enter> to stop." + vbNewLine)
                Console.ReadKey()

                ' Stop the play back.
                MIL.MseqProcess(MilDecompressContext, MIL.M_STOP, MIL.M_NULL)

                ' Make sure the hook handler function delegate is not prematurely garbage collected since
                ' only MIL has a reference to it.
                GC.KeepAlive(FrameDecodingEndFunctionDelegate)

                ' Free the decoding user data handle.
                DecodingFrameEndUserHookDataHandle.Free()

                MIL.MseqFree(MilDecompressContext)
            Else
                Console.WriteLine("Did not record enough frames to be able to replay.")
            End If

            ' Wait for a key to end.
            Console.WriteLine("Press <Enter> to end.")
            Console.ReadKey()

            ' Release defaults.
            MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MilDigitizer, MilImageDisp)

            Return 0
        End Function

        ' Local defines.
        Private Const STRING_LENGTH_MAX As Integer = 20
        Private Const STRING_POS_X As Integer = 20
        Private Const STRING_POS_Y As Integer = 20

        ' User's processing function called every time a grab buffer is modified. 
        ' ------------------------------------------------------------------------
        Private Shared Function ProcessingFunction(ByVal HookType As MIL_INT, ByVal HookId As MIL_ID, ByVal HookDataPtr As IntPtr) As MIL_INT
            ' this is how to check if the user data is null, the IntPtr class
            ' contains a member, Zero, which exists solely for this purpose
            If (Not IntPtr.Zero.Equals(HookDataPtr)) Then
                ' get the handle to the ProcessingHookDataStruct object back from the IntPtr
                Dim hUserData As GCHandle = GCHandle.FromIntPtr(HookDataPtr)

                ' get a reference to the ProcessingHookDataStruct object
                Dim UserHookDataPtr As ProcessingHookDataStruct = TryCast(hUserData.Target, ProcessingHookDataStruct)
                Dim ModifiedBufferId As MIL_ID = MIL.M_NULL
                Dim Text As String

                ' Retrieve the MIL_ID of the grabbed buffer.
                MIL.MdigGetHookInfo(HookId, MIL.M_MODIFIED_BUFFER + MIL.M_BUFFER_ID, ModifiedBufferId)

                Select Case UserHookDataPtr.ProcessingOperation
                    Case ProcessingHookOperation.DISPLAY
                        ' Update the display with the last captured image.
                        MIL.MbufCopy(ModifiedBufferId, UserHookDataPtr.MilImageDisp)
                    Case ProcessingHookOperation.ENCODE
                        ' Increase the compressed images count.
                        UserHookDataPtr.ProcessedImageCount += 1

                        ' Print and draw the frame count (comment this block to reduce CPU usage).
                        Console.Write("Processing frame #{0}." & Constants.vbCr, UserHookDataPtr.ProcessedImageCount)
                        Text = String.Format("{0}", UserHookDataPtr.ProcessedImageCount)
                        MIL.MgraText(MIL.M_DEFAULT, ModifiedBufferId, STRING_POS_X, STRING_POS_Y, Text)

                        ' Enqueue the grabbed buffer for parallel encoding.
                        MIL.MseqFeed(UserHookDataPtr.MilSeqContext, ModifiedBufferId, MIL.M_DEFAULT)

                        ' Update the display with the last captured image.
                        MIL.MbufCopy(ModifiedBufferId, UserHookDataPtr.MilImageDisp)
                End Select
            End If

            Return 0
        End Function

        ' Optional encoding end function called every time a buffer is finished being compressed. 
        ' ----------------------------------------------------------------------------------------
        Private Shared Function FrameEncodingEndFunction(ByVal HookType As MIL_INT, ByVal HookId As MIL_ID, ByVal HookDataPtr As IntPtr) As MIL_INT
            ' this is how to check if the user data is null, the IntPtr class
            ' contains a member, Zero, which exists solely for this purpose
            If (Not IntPtr.Zero.Equals(HookDataPtr)) Then
                ' get the handle to the FrameEndHookDataStruct object back from the IntPtr
                Dim hUserData As GCHandle = GCHandle.FromIntPtr(HookDataPtr)

                ' get a reference to the EncodingFrameEndHookDataStruct object
                Dim UserHookDataPtr As EncodingFrameEndHookDataStruct = TryCast(hUserData.Target, EncodingFrameEndHookDataStruct)

                ' Frame end hook post processing.
                If HookType = MIL.M_FRAME_END Then
                    Dim CompressedBufferId As MIL_ID = MIL.M_NULL
                    Dim CompressedDataPtr As MIL_INT = MIL.M_NULL
                    Dim CompressedDataSize As MIL_INT = 0

                    ' Increment a encoded frame counter
                    UserHookDataPtr.EncodedImageCount += 1

                    ' Retrieve the MIL_ID of the encoded buffer.
                    MIL.MseqGetHookInfo(HookId, MIL.M_MODIFIED_BUFFER + MIL.M_BUFFER_ID, CompressedBufferId)

                    ' Retrieves the address of the encoded data.
                    MIL.MbufInquire(CompressedBufferId, MIL.M_HOST_ADDRESS, CompressedDataPtr)

               ' Retrieves the size in bytes of the encoded data.
               MIL.MbufInquire(CompressedBufferId, MIL.M_COMPRESSED_DATA_SIZE_BYTE, CompressedDataSize)

               ' -----------------------------------------------------------------------------------------------
               ' Here you can do any action with the encoded data, such as send the buffer through a network
               ' stream. If the processing done on the compressed data is long, it is recommended to copy the
               ' buffer and to process it in a separate thread to avoid blocking the compression's flow.
               ' -----------------------------------------------------------------------------------------------
            End If
            End If

            Return 0
        End Function

        ' Optional encoding end function called every time a buffer is finished being compressed. 
        ' ----------------------------------------------------------------------------------------
        Private Shared Function FrameDecodingEndFunction(ByVal HookType As MIL_INT, ByVal HookId As MIL_ID, ByVal HookDataPtr As IntPtr) As MIL_INT
            ' this is how to check if the user data is null, the IntPtr class
            ' contains a member, Zero, which exists solely for this purpose
            If (Not IntPtr.Zero.Equals(HookDataPtr)) Then
                ' get the handle to the FrameEndHookDataStruct object back from the IntPtr
                Dim hUserData As GCHandle = GCHandle.FromIntPtr(HookDataPtr)

                ' get a reference to the DecodingFrameEndHookDataStruct object
                Dim UserHookDataPtr As DecodingFrameEndHookDataStruct = TryCast(hUserData.Target, DecodingFrameEndHookDataStruct)

                ' Frame end hook post processing.
                If HookType = MIL.M_FRAME_END Then
                    Dim DecompressedBufferId As MIL_ID = MIL.M_NULL

                    ' Increment a encoded frame counter
                    UserHookDataPtr.DecodedImageCount += 1

                    ' Retrieve the MIL_ID of the encoded buffer.
                    MIL.MseqGetHookInfo(HookId, MIL.M_MODIFIED_BUFFER + MIL.M_BUFFER_ID, DecompressedBufferId)

                    ' -----------------------------------------------------------------------------------------------
                    ' Here you can do any action with the decoded buffer.
                    ' -----------------------------------------------------------------------------------------------

                    MIL.MbufCopy(DecompressedBufferId, UserHookDataPtr.MilImageDisp)

                End If
            End If

            Return 0
        End Function

        Private Structure MSEQ_PARAM
            Public Sub New(ByVal controlName As String, ByVal controlType As MIL_INT, ByVal originalValue As MIL_INT, ByVal effectiveValue As MIL_INT)
                Me.ControlName = controlName
                Me.ControlType = controlType
                Me.OriginalValue = originalValue
                Me.EffectiveValue = effectiveValue
            End Sub

            Public ControlName As String
            Public ControlType As MIL_INT
            Public OriginalValue As MIL_INT
            Public EffectiveValue As MIL_INT
        End Structure

        ' Checks if MseqProcess generated an error or a warning.
        ' This function prints out the MIL error message, if any.
        ' If a sequence context parameter has been modified, it means that only a warning has been generated.
        ' If it is a warning, it displays the control that has been modified.
        ' If it is an error, it return M_YES to indicate that the example needs to be stopped.
        ' ----------------------------------------------------------------------------------------------------
        Private Shared Function CheckMseqProcessError(ByVal MilApplication As MIL_ID, ByVal MilCompressContext As MIL_ID) As Boolean
            Dim IsError As Boolean = False
            Dim IsWarning As Boolean = False
            Dim MilErrorCode As MIL_INT = PrintMilErrorMessage(MilApplication)
            ' MseqProcess generated an error, check in details if it is a warning or an error.
            If MilErrorCode <> MIL.M_NULL_ERROR Then
                Dim MseqParamList() As MSEQ_PARAM = {New MSEQ_PARAM("M_STREAM_BIT_RATE_MODE", MIL.M_STREAM_BIT_RATE_MODE, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_BIT_RATE", MIL.M_STREAM_BIT_RATE, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_BIT_RATE_MAX", MIL.M_STREAM_BIT_RATE_MAX, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_FRAME_RATE_MODE", MIL.M_STREAM_FRAME_RATE_MODE, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_QUALITY", MIL.M_STREAM_QUALITY, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_PROFILE", MIL.M_STREAM_PROFILE, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_LEVEL", MIL.M_STREAM_LEVEL, 0, 0), _
                                                     New MSEQ_PARAM("M_STREAM_GROUP_OF_PICTURE_SIZE", MIL.M_STREAM_GROUP_OF_PICTURE_SIZE, 0, 0)}

                Dim NumberOfModifiedParams As MIL_INT = 0

                ' Loop though the param list to find which one has been internally modified.
                For ParamIndex As Integer = 0 To MseqParamList.Length - 1
                    ' Query the original control values.
                    MIL.MseqInquire(MilCompressContext, MIL.M_CONTEXT, MseqParamList(ParamIndex).ControlType, MseqParamList(ParamIndex).OriginalValue)

                    ' Query the effective control values.
                    MIL.MseqInquire(MilCompressContext, MIL.M_CONTEXT, MseqParamList(ParamIndex).ControlType Or MIL.M_EFFECTIVE_VALUE, MseqParamList(ParamIndex).EffectiveValue)

                    ' If the original value is different than the effective value,
                    ' the error received is only a warning and processing can continue.
                    ' 
                    If MseqParamList(ParamIndex).OriginalValue <> MseqParamList(ParamIndex).EffectiveValue Then
                        If NumberOfModifiedParams = 0 Then
                            Console.Write(Constants.vbLf & "Parameter(s) that have been internally modified:" & Constants.vbLf)
                        End If

                        ' Prints the Control type internally modified.
                        Console.WriteLine("- {0}", MseqParamList(ParamIndex).ControlName)
                        NumberOfModifiedParams += 1
                        IsWarning = True
                    End If
                Next ParamIndex
                Console.WriteLine()

                ' If the error logged is not a warning, you cannot encode the current input source.
                ' The example needs to be stopped.
                '
                If (Not IsWarning) Then
                    IsError = True
                End If
            End If

            Return IsError
        End Function


        ' Print the current error code in the console
        Private Shared Function PrintMilErrorMessage(ByVal MilApplication As MIL_ID) As MIL_INT
            Dim MilErrorCode As MIL_INT
            Dim MilErrorMsg As New StringBuilder(MIL.M_ERROR_MESSAGE_SIZE)
            Dim MilErrorSubCode(2) As MIL_INT
            Dim MilErrorSubMsg(2) As StringBuilder

            ' Initialize MilErrorSubMsg array.
            For i As Integer = 0 To 2
                MilErrorSubMsg(i) = New StringBuilder(MIL.M_ERROR_MESSAGE_SIZE)
            Next i

            MilErrorCode = MIL.MappGetError(MilApplication, MIL.M_CURRENT + MIL.M_MESSAGE, MilErrorMsg)
            If MilErrorCode <> MIL.M_NULL_ERROR Then
                ' Collects Mil error messages and sub-messages 
                Dim subCount As MIL_INT = 3
                MIL.MappGetError(MilApplication, MIL.M_CURRENT_SUB_NB, subCount)
                MilErrorSubCode(0) = MIL.MappGetError(MilApplication, MIL.M_CURRENT_SUB_1 + MIL.M_MESSAGE, MilErrorSubMsg(0))
                MilErrorSubCode(1) = MIL.MappGetError(MilApplication, MIL.M_CURRENT_SUB_2 + MIL.M_MESSAGE, MilErrorSubMsg(1))
                MilErrorSubCode(2) = MIL.MappGetError(MilApplication, MIL.M_CURRENT_SUB_3 + MIL.M_MESSAGE, MilErrorSubMsg(2))

                Console.WriteLine(Constants.vbLf & "MseqProcess generated a warning or an error:")
                Console.WriteLine("  {0}", MilErrorMsg.ToString())
                For i As Integer = 0 To CType(subCount - 1, Integer)
                    If MilErrorSubCode(i) <> 0 Then
                        Console.WriteLine("  {0}", MilErrorSubMsg(i))
                    End If
                Next i
            End If

            Return MilErrorCode
        End Function

    End Class
End Namespace