{******************************************************************************}
{ FileName............: StreamReaderSaa7146a                                   }
{ Project.............:                                                        }
{ Author(s)...........: MM                                                     }
{ Version.............: 1.00                                                   }
{------------------------------------------------------------------------------}
{  Front end budget card interface (based on StreamReader API).                }
{                                                                              }
{  Copyright (C) 2003-2005  M.Majoor                                           }
{                                                                              }
{  This program is free software; you can redistribute it and/or               }
{  modify it under the terms of the GNU General Public License                 }
{  as published by the Free Software Foundation; either version 2              }
{  of the License, or (at your option) any later version.                      }
{                                                                              }
{  This program is distributed in the hope that it will be useful,             }
{  but WITHOUT ANY WARRANTY; without even the implied warranty of              }
{  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               }
{  GNU General Public License for more details.                                }
{                                                                              }
{  You should have received a copy of the GNU General Public License           }
{  along with this program; if not, write to the Free Software                 }
{  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. }
{                                                                              }
{------------------------------------------------------------------------------}
{                                                                              }
{  StreamReader function calls                                                 }
{    CheckForDvb       Check for available hardware                            }
{    DelFilter         Remove filter from list                                 }
{    SendDiSEqC        Send DiSEqC command                                     }
{    SetBitFilter      Set PID filter with additional requirements             }
{    SetChannel        Set the tuner                                           }
{    SetFilter         Set PID filter                                          }
{    SetRemoteControl  Set remote control                                      }
{    StartDVB          Initialize hardware                                     }
{    StopDVB           Deinitialize hardware                                   }
{    SetFilterEx       Differently called SetFilter                            }
{                                                                              }
{------------------------------------------------------------------------------}
{                                                                              }
{ Version   Date   Comment                                                     }
{  1.00   20050426 - Initial release                                           }
{******************************************************************************}
unit StreamReaderSaa7146a;
interface

uses
  Windows;

type
  PByte = ^Byte;
  ByteArray = array[0..$FFFF] of Byte;
  PByteArray = ^ByteArray;

  TFastDvbCallbackEx = procedure(Data: Pointer; Length: Integer; Pid: Integer); register;
  TFastDvbCallback   = procedure(Data: Pointer; Length: Integer); register;
  TStdDvbCallback    = procedure(Data: Pointer; Length: Integer); stdcall;
  TStdDvbCallbackEx  = procedure(Data: Pointer; Length: Integer; Pid: Integer); stdcall;
  TCdeclDvbCallback  = procedure(Data: Pointer; Length: Integer); cdecl;

  // Note: Ordinals 1/2/4 should stay at same indexes because it is also used
  //       by calling functions
  TCallbackTypes = (cbFastEx, cbFast, cbStd, cbStdEx, cbCdecl);

  function CheckForDvb     : Boolean; cdecl;
  function DelFilter       (Filter_num  : Dword): Boolean; cdecl;
  function SendDiSEqC      (DiSEqCType  : Dword;           Data        : Byte): Boolean; cdecl;
  function SetBitFilter    (Pid         : Word;            FilterData  : PByteArray;
                            FilterMask  : PByteArray;      FilterLength: Byte;
                            LpFunc      : Pointer;         LpFilter_num: PDword): Boolean; cdecl;
  function SetChannel      (Freq        : Dword;           Symb        : Dword;
                            Pol         : Dword;           Fec         : Dword;
                            Lof1        : Dword;           Lof2        : Dword;
                            LofSw       : Dword): Boolean; cdecl;
  function SetFilter       (Pid         : Word;            LpFunc      : Pointer;
                            CallBackType: Dword;           Size        : Dword;
                            LpFilter_Num: PDword): Boolean; cdecl;
  function SetRemoteControl(IR_Type     : Dword;           DevAddr     : Word;
                            LpFunc      : Pointer;         LpFilter_num: PDword):Boolean; cdecl;
  function StartDVB        : Boolean; cdecl;
  function StopDVB         : Boolean; cdecl;
  function SetFilterEx     (Pid         : Word;            LpFunc      : Pointer;
                            CallBackType: Dword;           Size        : Dword;
                            LpFilter_Num: PDword): Boolean; cdecl;

implementation

uses
  Dvb,
  DvbStreamBuffering,
  Saa7146aI2c, Saa7146aInterface,
  Saa7146aIoControl,
  Saa7146aRegisters, Saa7146aGpio,
  Stv0299bRegisters, Tsa5059Registers,
{$IFDEF USELOG}
  Classes,
{$ENDIF}
  IniFiles, SyncObjs, SysUtils;


type
  // PID filter definition
  TFilter = record
    Active          : Boolean;                   // Filter is (to be) set active
    Pid             : Word;                      // Only used for cross reference
    CountDetected   : Integer;                      // Debug counter
    CountCheckedOut : Integer;                   // Debug counter
    CountCalled     : Integer;                   // Debug counter
    CallBackFunction: Pointer;                   // Function to call
    CallBackType    : TCallbackTypes;            // Type of function to use for callback
    FilterData      : array of Byte;             // Additional bytes to check (from 4th byte in packet)
    FilterMask      : array of Byte;             // Mask data for FilterData
    PacketBuffer    : PByteArray;                // Packet buffer if not every single packet is
                                                 // directly passed on
    PacketBufferSize: Integer;                   // Size of buffer (packets)
    PacketIndex     : Integer;                   // Packets in buffer
  end;
  PTFilters = ^TFilters;
  TFilters = record
    Filters         : array[0..255] of TFilter; // The filter definitions
                                                // Index 0 use for passing on ALL data
    CrossReference  : array[0..$1FFF] of Byte;  // Pid index references to <Filters>
    Active          : Word;                     // Number of active filters
  end;

  // The data thread receives the DVB-S data
  TDataThread = class(TThread)
  private
    { Private declarations }
    FHandle               : THandle;             // Handle to driver in thread
    FBufferId             : Dword;               // Buffer identifier for DMA data
    FPacketBuffers        : Word;                // Number of buffers used
    FHasStopped           : Boolean;             // Flag indicating thread not running
    FProgrammedStop       : Boolean;             // Flag indicating thread should stop
    FLoops                : Word;                // Debug counter
    procedure ProcessTransportStreamData(StreamData: PDvbTransportPackets);
  protected
    procedure Execute; override;
  end;



var
  StreamDataBuffer     : TDvbTransportPackets;   // DVB-S data
  PacketThread         : TDataThread;            // Thread handling packets

const
  StreamDataBufferSize = SizeOf(StreamDataBuffer);

var
  TunerType            : Byte;                   // Tuner type
  SynthesizerAddress   : Byte;                   // Synthesizer address tuner
  TunerReset           : Byte;                   // GPIO line for reset ($FF if not to be used)
  LNBPolarity          : Byte;                   // OPx line to use for polarity ($FF if not to be used)
  LNBOnOff             : Byte;                   // OPx line to use for on/off ($FF if not to be used)

  IniFile              : TMemIniFile;            // Ini file
  Cardhandle           : THandle;
  FilterLock           : TCriticalSection;
  Filters              : TFilters;
  PacketSize           : Byte;                   // Size of a packet to pass on
  BufferSize           : Word;                   // Buffer size
  Rps0Program          : Integer;
  BufferId             : Dword;                  // DMA buffer identifier
  CardToUse            : Integer;                // Card to use (0=1st)
  PassAll              : Boolean;                // True if all data to be passed on
                                                 // Forces use of filter index 0

{$IFDEF USELOG}
  LogStream            : TFileStream;            // Filestream for log
  LogLevel             : Byte;                   // LogLevel (00 = no log)
                                                 //  $x0 = No log
                                                 //  $x1 = Lowest level
                                                 //  $x2 = Command errors
                                                 //  $x3 = Procedure/Function entries
                                                 //  $x4 = Procedure/Function specifics
                                                 //  $x5 = Generic procedure/Function entries


{------------------------------------------------------------------------------
  Params  : <LogString>  Data to log
            <Level>      Log level: if this number if <= <LogLevel> then
                         it is logged
                         $8x indicates that the string is intended for
                             displaying on form too.
  Returns : -

  Descript: Write data to log. A timestamp is added.
  Notes   :
 ------------------------------------------------------------------------------}
procedure ToLog(LogString: string; Level: Byte);
var
  NewLog: string;
begin
  try
    // Only log if the level is high enough
    if (Level and $0F) > LogLevel then
      Exit;
    // The check on <LogStream> is necessary because the application might
    // call this prior to completing initialization
    if not Assigned(LogStream) then
      Exit;
    NewLog := FormatDateTime('YYYYMMDD"T"HHMMSS"  "', Now) + LogString + #13#10;
    LogStream.Write(NewLog[1], Length(NewLog));
  except
  end;  
end;
{$ENDIF}


{------------------------------------------------------------------------------
  Params  : <Section>    Section to access in INI file
            <Paremeter>  Paremeter in INI file
            <Default>    Default value (eg. if not present in INI file)
  Returns : <Result>     String value for parameter

  Descript: Get parameter from INI file.
  Notes   :
 ------------------------------------------------------------------------------}
function GetParameter(Section: string; Parameter: string; Default: string): string;
var
  FromIni : string;
  Position: Integer;
  TempStr : string;
begin
  try
{$IFDEF USELOG}
    ToLog('GetParameter (Section: [' + Section + '], Parameter: [' + Parameter + '].', $03);
{$ENDIF}
    if not Assigned(IniFile) then
      Exit;
    FromIni := IniFile.ReadString(Section, Parameter, Default);
    // Strip out leading/trailing spaces
    FromIni := Trim(FromIni);
    // Check for starting string parameter
    Position := Pos('''', FromIni);
    if Position = 1 then
    begin
      TempStr  := Copy(FromIni, Position+1, 255);
      // Find ending of string
      Position := Pos('''', TempStr);
      if Position <> 0 then
      begin
        // End of string found, which will be the result
        Result := Copy(TempStr, 1, Position-1);
{$IFDEF USELOG}
        ToLog('Result: [' + Result + '].', $05);
{$ENDIF}
        Exit;
      end;
    end
    else
    begin
      Position := Pos('"', FromIni);
      if Position = 1 then
      begin
        TempStr := Copy(FromIni, Position+1, 255);
        // Find ending of string
        Position := Pos('"', TempStr);
        if Position <> 0 then
        begin
          // End of string found, which will be the result
          Result := Copy(TempStr, 1, Position-1);
{$IFDEF USELOG}
          ToLog('Result: [' + Result + '].', $05);
{$ENDIF}
          Exit;
        end;
      end;
    end;
    // We know we don't have a string type so handle the whole string as
    // normal text. We could have a comment in it so check this too
    Position := Pos(';', FromIni);
    if Position <> 0 then
    begin
      TempStr := Copy(FromIni, 1, Position-1);
      // Strip out leading/trailing spaces
      Result := Trim(TempStr);
    end
    else
      Result := FromIni;
{$IFDEF USELOG}
    ToLog('Result: [' + Result + '].', $05);
{$ENDIF}
  except
  end;
end;



{******************************************************************************}
{                           START FILTER FUNCTIONS                             }
{******************************************************************************}
{------------------------------------------------------------------------------
  Params  : <Filter>  Pointer to filter data
  Returns : -

  Descript: Update cross reference table for filters.
  Notes   :
------------------------------------------------------------------------------}
procedure FilterUpdateCrossReference(Filter: PTFilters);
var
  FilterIndex: Integer;
begin
  // Clear cross references
  Filter.Active := 0;
  for FilterIndex := 1 to High(Filter.CrossReference)
    do
    Filter.CrossReference[FilterIndex] := 0;
  // Renew cross reference
  for FilterIndex := 1 to High(Filter.Filters) do
    if Filter.Filters[FilterIndex].Active then
    begin
      Filter.CrossReference[Filter.Filters[FilterIndex].Pid] := FilterIndex;
      Inc(Filter.Active);
    end;
end;


{------------------------------------------------------------------------------
  Params  : <Filter>      Filter to use
            <FilterIndex> Filter index to clear
                          0 will stop all filters
  Returns : <Result>      0 if no error

  Descript: Clear filter data
  Notes   :
------------------------------------------------------------------------------}

function DvbStopFilter(Filter: PTFilters; FilterIndex: Dword): Dword; stdcall;
var
  ClearAll: Boolean;
begin
  FilterLock.Acquire;
  try
    Result := 0;
    if FilterIndex > High(Filter.Filters) then
      Exit;
    // Check for 'all' data passing to stop
    if FilterIndex = 0 then
    begin
      ClearAll := True;
      FilterIndex := 1;
      PassAll := False;
    end
    else
      ClearAll := False;

    repeat
      Filter.Filters[FilterIndex].Active := False;
      // Remove mask data
      Filter.Filters[FilterIndex].FilterData := nil;
      Filter.Filters[FilterIndex].FilterMask := nil;
      if Assigned(Filter.Filters[FilterIndex].PacketBuffer) then
        FreeMem(Filter.Filters[FilterIndex].PacketBuffer);
      Filter.Filters[FilterIndex].PacketBuffer := nil;
      Filter.Filters[FilterIndex].PacketBufferSize := 0;
      FilterUpdateCrossReference(Filter);
      Inc(FilterIndex);
      if not ClearAll then
        FilterIndex := (High(Filter.Filters)+1);
    until FilterIndex > High(Filter.Filters);
  finally
    FilterLock.Release;
  end;
end;

{------------------------------------------------------------------------------
  Params  : <Filter>        Filter to use
            <Pid>           Pid for filter to set
                            >$1FFF will initiate passing on all data until
                            DelFilter with filter id 0 is used
            <FilterProc>    Callback function for Pid
            <CallBackType>  Type of filter callback (see TFilter)
            <FilterData>    Optional filter data
            <FilterMask>    Optional filter mask for FilterData (size must be the same)
            <BufferSize>    Size of buffer for packets
  Returns : <Result>        Filter index/identifier
                            $FFFF if error

  Descript: Set filter data
  Notes   :
------------------------------------------------------------------------------}

function DvbSetFilter(Filter: PTFilters; Pid: Word; FilterProc: Pointer;
  CallBackType: TCallbackTypes; FilterData: array of Byte; FilterMask: array of Byte;
  BufferSize: Integer): Dword; stdcall;
var
  FilterIndex: Integer;
  Valid      : Boolean;
  Loop       : Integer;
begin
  Result := $FFFF;
  FilterLock.Acquire;
  try
    if Pid > High(Filter.CrossReference) then
    begin
      // Remove all present filters
      DvbStopFilter(@Filters, 0);
      FilterIndex := 0;
      Valid := True;
    end
    else
    begin
      Valid := False;
      // First try to find existing active filter
      if Filter.CrossReference[Pid] <> 0 then
      begin
        FilterIndex := Filter.CrossReference[Pid];
        Valid := True;
      end
      else
      begin
        // No existing filter found for Pid, find first available one
        FilterIndex := 1;
        repeat
          if Filter.Filters[FilterIndex].Active then
            Inc(FilterIndex)
          else
            Valid := True;
        until Valid or (FilterIndex > High(Filter.Filters));
      end;
    end;
    if Valid then
    begin
      // Now set filter data and
      Filter.Filters[FilterIndex].Pid := Pid;
      Filter.Filters[FilterIndex].CallBackFunction := FilterProc;

      Filter.Filters[FilterIndex].CallBackType := CallBackType;
      Filter.Filters[FilterIndex].PacketBuffer := nil;
      Filter.Filters[FilterIndex].PacketBufferSize := 0;
      Filter.Filters[FilterIndex].PacketIndex := 0;
      Filter.Filters[FilterIndex].FilterMask := nil;
      Filter.Filters[FilterIndex].FilterData := nil;
      if Length(FilterData) <> Length(FilterMask) then
        Exit;
      if Length(FilterData) > 0 then
      begin
        try
          SetLength(Filter.Filters[FilterIndex].FilterData, Length(FilterData));
          SetLength(Filter.Filters[FilterIndex].FilterMask, Length(FilterMask));
          for Loop := 0 to Length(FilterData)-1 do
            Filter.Filters[FilterIndex].FilterData[Loop] := FilterData[Loop];
          for Loop := 0 to Length(FilterMask)-1 do
            Filter.Filters[FilterIndex].FilterMask[Loop] := FilterMask[Loop];
        except
          Filter.Filters[FilterIndex].FilterData := nil;
          Filter.Filters[FilterIndex].FilterMask := nil;
        end;
      end;
      Filter.Filters[FilterIndex].CountDetected   := 0;
      Filter.Filters[FilterIndex].CountCheckedOut := 0;
      Filter.Filters[FilterIndex].CountCalled     := 0;
      Filter.Filters[FilterIndex].Active := True;
      FilterUpdateCrossReference(Filter);
      if BufferSize > 0 then
      begin
        // Number of whole packets in buffer
        Filter.Filters[FilterIndex].PacketBufferSize := BufferSize div PacketSize;
        try
{$IFDEF USELOG}
          ToLog(format('DvbSetFilter acquiring %d bytes (%d packets).',
            [Filter.Filters[FilterIndex].PacketBufferSize * PacketSize,
             Filter.Filters[FilterIndex].PacketBufferSize]), $04);
{$ENDIF}
          GetMem(Filter.Filters[FilterIndex].PacketBuffer,
            Filter.Filters[FilterIndex].PacketBufferSize * PacketSize);
        except
{$IFDEF USELOG}
          ToLog('DvbSetFilter error acquiring memory.', $02);
{$ENDIF}
          Filter.Filters[FilterIndex].PacketBufferSize := 0;
        end;
      end;
      Result := FilterIndex;
      if FilterIndex = 0 then
        PassAll := True;
    end;
  finally
    FilterLock.Release;
  end;
end;
{******************************************************************************}
{                            END FILTER FUNCTIONS                              }
{******************************************************************************}


{******************************************************************************}
{                                 PACKET THREAD                                }
{******************************************************************************}
{------------------------------------------------------------------------------
  Params  : <StreamPacketData>  Packet data buffer
  Returns : <Result>            True if valid Pid (no other errors detected)
            <Pid>               Pid value

  Descript: Get PID from transport stream.
  Notes   :
 ------------------------------------------------------------------------------}

function DvbFilterGetPid(StreamPacketData: PDvbTransportPacket; var Pid: Word):
  Boolean;
begin
  Result := False;
  // One of the few checks at this point is the synchronization byte which must be correct
  if StreamPacketData[0] = CDvbPacketSyncWord then
    Result := True;
  Pid := ((StreamPacketData[1] and $1F) shl 8) or StreamPacketData[2];
end;

{------------------------------------------------------------------------------
  Params  : <StreamPacketData>  Packet data buffer
  Returns : <Result>            True if valid offset
                                False if error or no payload
            <Offset>            Offset into first data byte of payload

  Descript: Get offset where payload starts.
  Notes   :
 ------------------------------------------------------------------------------}

function DvbGetPayloadOffset(StreamPacketData: PDvbTransportPacket; var Offset:
  Word): Boolean;
var
  AdaptationFieldControl: Byte;
begin
  Result := False;
  Offset := 0;
  // The start of the payload depends on the adaptation field. If there is
  // an adaptation field then the payload starts after this. If there is no
  // adaptation field then the payload start immediately.
  // Also there is the posibility that there is no payload at all.
  AdaptationFieldControl := StreamPacketData[3] and $30;
  case AdaptationFieldControl of
    $00: Exit; // Reserved
    $10: Offset := 4; // Payload only
    $20: Exit; // Adaptation field only, no payload
    $30: begin
           Offset := StreamPacketData[4] + 5; // Adaptation field with payload
           // Check for excessive value
           if Offset > CDvbPacketSize then
           begin
             Offset := 0;
             Exit;
           end;
         end;    
  end;
  Result := True;
end;


{------------------------------------------------------------------------------
  Params  : <StreamData>  Stream packets (multiple packets of 188 bytes)
  Returns : -

  Descript: Process transport stream.
  Notes   :
 ------------------------------------------------------------------------------}
procedure TDataThread.ProcessTransportStreamData(StreamData: PDvbTransportPackets);
var
  StreamPacket: Word;
  Pid         : Word;
  Ref         : Byte;
  Process     : Boolean;
  Loop        : Integer;
  Data        : Byte;
  InitialStart: Word;
begin
  try
    for StreamPacket := 0 to CDvbPacketVSync-1 do
      // The Pid is the identifier of what type of information we are dealing with
      if DvbFilterGetPid(@StreamData[StreamPacket], Pid) then
      begin
        if PassAll then
          Ref := 0
        else  
          Ref := Filters.Crossreference[Pid];
        if PassAll or (Ref > 0) then
        begin
          if Filters.Filters[Ref].Active then
          begin
            if Filters.Filters[Ref].CountDetected = MaxInt then
              Filters.Filters[Ref].CountDetected := 0
            else
              Inc(Filters.Filters[Ref].CountDetected);

            Process := False;
            // First check if there is mask data to check for
            if Length(Filters.Filters[Ref].FilterData) > 0 then
            begin
              // If we check the data we must make sure that our packet is at
              // the start of a payload
              if (StreamData[StreamPacket][1] and $40) <> 0 then
              begin
                // Get offset to payload
                if DvbGetPayloadOffset(@StreamData[StreamPacket], InitialStart) then
                begin
                  // Check mask data
                  Process := True;
                  for Loop := 0 to Length(Filters.Filters[Ref].FilterData)-1 do
                  begin
                    Data := StreamData[StreamPacket][InitialStart + Loop];
                    Data := Data and Filters.Filters[Ref].FilterMask[Loop];
                    if (Data <> Filters.Filters[Ref].FilterData[Loop]) then
                    begin
                      // Exit check if match fails
                      Process := False;
                      Break;
                    end;
                  end;
                end;
              end
            end
            else
              Process := True;

            if Process then
            begin
              if Filters.Filters[Ref].CountCheckedOut = MaxInt then
                Filters.Filters[Ref].CountCheckedOut := 0
              else
                Inc(Filters.Filters[Ref].CountCheckedOut);
              // Valid packet data found (either just the PID or also the masked data)
              // Now buffer it (if indicated)
              if Filters.Filters[Ref].PacketBufferSize > 0 then
              begin
                // Buffering active, always copy first
                CopyMemory(@Filters.Filters[Ref].PacketBuffer[Filters.Filters[Ref].PacketIndex*PacketSize],
                  @StreamData[StreamPacket][CDvbPacketSize-PacketSize], PacketSize);
                // Adjust packets in buffer
                Inc(Filters.Filters[Ref].PacketIndex);
                if Filters.Filters[Ref].PacketIndex = Filters.Filters[Ref].PacketBufferSize then
                begin
                  if Filters.Filters[Ref].CountCalled = MaxInt then
                    Filters.Filters[Ref].CountCalled := 0
                  else
                    Inc(Filters.Filters[Ref].CountCalled);
                  // If buffer full, pass it on to callback
                  case Filters.Filters[Ref].CallBackType of
                    cbFastEx: TFastDvbCallBackEx(Filters.Filters[Ref].CallBackFunction)(Filters.Filters[Ref].PacketBuffer,
                                Filters.Filters[Ref].PacketIndex * PacketSize, Pid);
                    cbFast  : TFastDvbCallBack  (Filters.Filters[Ref].CallBackFunction)(Filters.Filters[Ref].PacketBuffer,
                                Filters.Filters[Ref].PacketIndex * PacketSize);
                    cbStd   : TStdDvbCallBack   (Filters.Filters[Ref].CallBackFunction)(Filters.Filters[Ref].PacketBuffer,
                                Filters.Filters[Ref].PacketIndex * PacketSize);
                    cbStdEx : TStdDvbCallBackEx (Filters.Filters[Ref].CallBackFunction)(Filters.Filters[Ref].PacketBuffer,
                                Filters.Filters[Ref].PacketIndex * PacketSize, Pid);
                    cbCdecl : TCdeclDvbCallBack (Filters.Filters[Ref].CallBackFunction)(Filters.Filters[Ref].PacketBuffer,
                                Filters.Filters[Ref].PacketIndex * PacketSize);
                  end;
                  Filters.Filters[Ref].PacketIndex := 0;
                end;
              end
              else
              begin
                if Filters.Filters[Ref].CountCalled = MaxInt then
                  Filters.Filters[Ref].CountCalled := 0
                else
                  Inc(Filters.Filters[Ref].CountCalled);
                // No buffering, always pass on single packets
                case Filters.Filters[Ref].CallBackType of
                  cbFastEx: TFastDvbCallBackEx(Filters.Filters[Ref].CallBackFunction)(@StreamData[StreamPacket][CDvbPacketSize-PacketSize],
                              PacketSize, Pid);
                  cbFast  : TFastDvbCallBack  (Filters.Filters[Ref].CallBackFunction)(@StreamData[StreamPacket][CDvbPacketSize-PacketSize],
                              PacketSize);
                  cbStd   : TStdDvbCallBack   (Filters.Filters[Ref].CallBackFunction)(@StreamData[StreamPacket][CDvbPacketSize-PacketSize],
                              PacketSize);
                  cbStdEx : TStdDvbCallBackEx (Filters.Filters[Ref].CallBackFunction)(@StreamData[StreamPacket][CDvbPacketSize-PacketSize],
                              PacketSize, Pid);
                  cbCdecl : TCdeclDvbCallBack (Filters.Filters[Ref].CallBackFunction)(@StreamData[StreamPacket][CDvbPacketSize-PacketSize],
                              PacketSize);
                end;
              end;
            end;
          end;
        end;  
      end;
  except
  end;
end;


{------------------------------------------------------------------------------
  Params  : -
  Returns : -

  Descript: Execution of thread. This thread handles reception of data.
  Notes   : We get an interrupt at both edges of the field indicator,
            meaning that we get notified when a switch is made from
            'odd' -> 'even' and from 'even' -> 'odd'
            The rate is about 25 interrupts a second
 ------------------------------------------------------------------------------}
procedure TDataThread.Execute;
var
  Data      : Dword;
  LastBuffer: Dword;
  Buffer    : TSaa7146aTransferBuffer;
begin
  try
    FHasStopped     := False;
    FProgrammedStop := False;
    FLoops          := 0;
    // The driver must be running and must be in use
    // We MUST use a new file handle. If we don't then only one call per handle is
    // allowed and when this happens to be the 'wait for notification'
    // we can not send any other calls eg. 'generate manual notification'
    // which might be needed in a deadlock situation.
    FHandle := Saa7146aCreateFile(CardToUse);
    Buffer.Identifier      := FBufferId;
    Buffer.TransferAddress := @StreamDataBuffer;
    Buffer.TargetIndex     := 0;
    Buffer.TransferLength  := StreamDataBufferSize;

    // Wait for application to have started and such
    LastBuffer := 0;
    Data       := $FFFF;
    try
      repeat
        // First check if there are already buffers filled
        Saa7146aReadFromSaa7146aRegister(FHandle, CSaa7146aEcT1R, Data);
        // In case no data is being received the returned buffer can
        // be outside of range so correct for this
        if Data > (FPacketBuffers*2) then
          Data := 0;
        // If no new data, wait (or if first time)
        if Data = LastBuffer then
        begin
          Saa7146aWaitForNotification(FHandle);
          if FProgrammedStop then
            Exit;
        end;
        Buffer.SourceIndex := CDvbPacketBufferSize * LastBuffer;
        // Next buffer to wait for
        Inc(LastBuffer);
        // Circular buffer
        if LastBuffer = (FPacketBuffers*2) then
          LastBuffer := 0;
        // Get data in local buffer
        Saa7146aReadFromDma(FHandle, Buffer);
        // Process packet data
        ProcessTransportStreamData(@StreamDataBuffer);
        if FLoops = $FFFF then
          FLoops := 0
        else
          Inc(FLoops);
      until FProgrammedStop or Terminated;
    finally
      Saa7146aCloseHandle(FHandle);
      FHasStopped := True;
{$IFDEF USELOG}
      ToLog(format('Data thread stopped, loop counter at %d.', [FLoops]), $81);
{$ENDIF}
    end;
  except
    FHasStopped := True;
{$IFDEF USELOG}
    ToLog('Data thread exception occured.', $81);
{$ENDIF}
  end;
end;
{******************************************************************************}
{                             END PACKET THREAD                                }
{******************************************************************************}


{******************************************************************************}
{                               API FUNCTIONS                                  }
{                                                                              }
{******************************************************************************}
{------------------------------------------------------------------------------
  Params  : -
  Returns : <Result>          True if supported driver found
                              False if no driver found

  Descript: Checks if the driver is installed.
  Notes   :
 ------------------------------------------------------------------------------}
function CheckForDvb: Boolean; cdecl;
begin
{$IFDEF USELOG}
   ToLog('CheckForDvb.', $04);
{$ENDIF}
  try
    if Saa7146aGetNumberOfCards = 0 then
      Result := False
    else
      Result := True;
{$IFDEF USELOG}
    if Result then
      ToLog('CheckForDvb found a driver.', $02)
    else
      ToLog('CheckForDvb did not find a driver.', $02)
{$ENDIF}
  except
    Result := False;
  end;
end;


{------------------------------------------------------------------------------
  Params  : <Filter_num>      The filter number/identifier to remove
                              $FFFF for remote control
  Returns : <Result>          Always true

  Descript: Stops the processing of a filter
  Notes   :
 ------------------------------------------------------------------------------}
function DelFilter(Filter_num: Dword): Boolean; cdecl;
begin
{$IFDEF USELOG}
  ToLog(format('DelFilter removing filter number %d.', [Filter_num]), $04);
{$ENDIF}
  // Filter number 0 is the remote control ....
  if Filter_num = $FFFF then
  begin
    // Stop remote control
  end
  else
    DvbStopFilter(@Filters, Filter_Num);
  Result := True;
end;


{------------------------------------------------------------------------------
  Params  : <DiSEqCType>  Desired action
                          0: None, disable DiSEqC
                          1: Simple, send tone burst only
                          2: Version 1.0, Multi, Send DiSEqC sequence
            <Data>        DiSEqCType
                          0  ->  Not used
                          1  ->  0 = Burst A
                                 1 = Burst B
                          2  ->  Data is send 'as is' using
                                 the committed switch command

  Returns : <Result>      True  if success
                          False if error

  Descript: Send a DiSEqC message to the LNB.
  Notes   :
 ------------------------------------------------------------------------------}
function SendDiSEqC(DiSEqCType: Dword; Data: Byte): Boolean; cdecl;
var
  DiSEqcData: TDiSEqCData;
begin
{$IFDEF USELOG}
  ToLog(format('SendDiSEqC type %d, data $%2.2x.', [DiSEqCType, Data]), $04);
{$ENDIF}
  Result := False;
  case DiSEqCType of
    0: Result := True;
    1: begin
         case Data of
           0: Result := Saa7146aDiSEqCCommand(CardHandle, 0, CDiSEqCBurstA, 0, DiSEqCData);
           1: Result := Saa7146aDiSEqCCommand(CardHandle, 0, CDiSEqCBurstB, 0, DiSEqCData);
           else
             Exit;
         end;
       end;
    2: begin
         DiSEqCData[0] := 0;                     // Will be written over
         DiSEqCData[1] := $10;                   // Any LNB/switcher
         DiSEqCData[2] := $38;                   // Committed sitches (DiSEqc level 1.0)
         DiSEqCData[4] := Data;                  // Send data 'as is'
         Result := Saa7146aDiSEqCCommand(CardHandle, 0, CDiSEqCCommand , 4, DiSEqCData);
       end;
    else
      Exit;
  end;
{$IFDEF USELOG}
  if not Result then
    ToLog('SendDiSEqC failed.', $02);
{$ENDIF}
end;


{------------------------------------------------------------------------------
  Params  : <Pid>           Pid number
            <FilterData>    The additional bytes to inspect
            <FilterMask>    The mask to check the bytes with. Only the '1' masked
                            bits should match to be
            <FilterLength>  Length of FilterData/FilterMaks array
            <LpFunc>        Pointer to callback
                            This is a STDCALL function
  Returns : <Result>        True if filter set
                            False if any error
            <LpFilter_Num>  Filter identifier

  Descript: Creates a filter with additional selection criteria.
            Besides the PID the data at specific bytes must match the mask to
            be passed on.
  Notes   :
 ------------------------------------------------------------------------------}
function SetBitFilter(Pid: Word; FilterData: PByteArray; FilterMask: PByteArray;
                      FilterLength: Byte; LpFunc: Pointer; LpFilter_num: PDword): Boolean; cdecl;
var
  Data    : array of Byte;
  Mask    : array of Byte;
  Loop    : Integer;
  Id      : Dword;
{$IFDEF USELOG}
  TempStr1: ShortString;
  TempStr2: ShortString;
{$ENDIF}
begin
{$IFDEF USELOG}
  ToLog(format('SetBitFilter, Pid %d, %d filter data bytes.', [Pid, Filterlength]), $04);
{$ENDIF}
  Result := False;
  if FilterLength = 0 then
  begin
    Data := nil;
    Mask := nil;
  end
  else
  begin
    try
      TempStr1 := '$';
      TempStr2 := '$';
      SetLength(Data, Filterlength);
      SetLength(Mask, Filterlength);
      for Loop := 0 to Filterlength-1 do
      begin
        Data[Loop] := FilterData[Loop];
        Mask[Loop] := FilterMask[Loop];
{$IFDEF USELOG}
        TempStr1 := TempStr1 + format('%2.2x ', [Data[Loop]]);
        TempStr2 := TempStr2 + format('%2.2x ', [Mask[Loop]]);
{$ENDIF}
      end;
{$IFDEF USELOG}
      ToLog(format('SetBitFilter, Data %s, Mask %s.', [TempStr1, TempStr2]), $04);
{$ENDIF}
    except
      Data := nil;
      Mask := nil;
{$IFDEF USELOG}
      ToLog('SetBitFilter error in data/mask.', $02);
{$ENDIF}
      Exit;
    end;
  end;
  Id := DvbSetFilter(@Filters, Pid, LpFunc, cbStd, Data, Mask, 0);
  if Id <> $FFFF then
  begin
    try
      LpFilter_Num^ := Id;
      Result := True;
{$IFDEF USELOG}
      if Id = 0 then
        ToLog('SetBitFilter success, passing on all data.', $04)
      else
        ToLog(format('SetBitFilter success, Pid %d has filter identifier %d.', [Pid, Id]), $04);
{$ENDIF}
    except
{$IFDEF USELOG}
      ToLog('SetBitFilter error passing back identifier.', $02);
{$ENDIF}
      Result := False;
    end;
  end
  else
  begin
{$IFDEF USELOG}
    ToLog(format('SetBitFilter setting filter for Pid %d failed.', [Pid]), $02);
{$ENDIF}
  end;
end;


{------------------------------------------------------------------------------
  Params  : <Freq>    Frequency (kHz)
            <Symb>    Symbol rate (symbols/sec)
            <Pol>     Polarity (0 = Horizontal, 1= Vertical)
            <Fec>     Forward Error correction
                      Not used. Always automatic FEC used.
            <Lof1>    Local Oscillator Frequency low band (kHz)
            <Lof2>    Local Oscillator Frequency high band (kHz)
            <Lofsw>   Switching frequency for using <Lof2> instead of <Lof1> (kHz)
  Returns : <Result>  True if no error (and tuned)
                      False if error or not tuned

  Descript: Sets the card's tuner to desired values.
  Notes   :
 ------------------------------------------------------------------------------}
function SetChannel(Freq: Dword; Symb: Dword; Pol: Dword; Fec: Dword;
                    Lof1: Dword; Lof2: Dword; LofSw: Dword): Boolean; cdecl;
var
  MaxWait  : Word;
  Wait     : Word;
  LockState: Byte;
begin
  Result := False;
{$IFDEF USELOG}
    ToLog(format('SetChannel %d kHz, %d S/s, LOF1: %d kHz, LOF2: %d kHz.', [Freq, Symb, Lof1, Lof2]), $04);
{$ENDIF}

  // Set frequency (force it if already set)
  if not Saa7146aFrequencyLNB(CardHandle, Freq, Lof1, Lof2, TunerType,
    SynthesizerAddress) then
  begin
{$IFDEF USELOG}
    ToLog('SetChannel setting frequency failed.', $02);
{$ENDIF}
    Exit;
  end;
  if Pol = 0 then
  begin
    if not Saa7146aHorizontalPolarityLNB(CardHandle, (LNBPolarity = 0)) then
    begin
{$IFDEF USELOG}
      ToLog('SetChannel setting horizontal polarity failed.', $02);
{$ENDIF}
      Exit;
    end;
  end;
  if Pol = 1 then
  begin
    if not Saa7146averticalPolarityLNB(CardHandle, (LNBPolarity = 0)) then
    begin
{$IFDEF USELOG}
      ToLog('SetChannel setting vertical polarity failed.', $02);
{$ENDIF}
      Exit;
    end;
  end;

  if not Saa7146aQpskSetSymbolRate(CardHandle, Symb div 1000) then
  begin
{$IFDEF USELOG}
    ToLog('SetChannel setting symbolrate failed.', $02);
{$ENDIF}
    Exit;
  end;
  MaxWait := 100;
  Wait := 0;
  repeat
    Sleep(10);
    Inc(Wait);
    if not Saa7146aReadFromQpsk(CardHandle, CStv0299bVStatus, LockState) then
    begin
{$IFDEF USELOG}
      ToLog('SetChannel reading lock state failed.', $02);
{$ENDIF}
      Exit;
    end;
  until (Wait > MaxWait) or ((LockState and $98) = $98);

  if ((LockState and $98) = $98) then
    Result := True
  else
  begin
{$IFDEF USELOG}
    ToLog('SetChannel could not lock to channel in time.', $02);
{$ENDIF}
  end;
end;


{------------------------------------------------------------------------------
  Params  : <Pid>           Pid number
            <LpFunc>        Pointer to callback
            <CallBackType>  Type of callback. Only types 1/2/4 are defined
            <Size>          Size of data to be returned in callback
                            Size of a packet is defined in INI file
                            1: Single packet
                            2: 4096 byte blocks
                               Note that only complete packets are returned,
                               thus for a 188 packet size data is passed on
                               when 21 packets are received
  Returns : <Result>        True if filter set
                            False if any error
            <LpFilter_Num>  Filter identifier

  Descript: Creates a filter for the indicated Pid.
  Notes   :
 ------------------------------------------------------------------------------}
function SetFilter(Pid: Word; LpFunc: Pointer; CallBackType: Dword; Size: Dword;
                   LpFilter_Num: PDword): Boolean; cdecl;
var
  Id   : Dword;
  CType: TCallbackTypes;
  Data : array of Byte;
  Mask : array of Byte;
begin
{$IFDEF USELOG}
  ToLog('SetFilter.', $04);
{$ENDIF}
  Result := False;
  case CallBackType of
    Ord(cbFast) : CType := cbFast;
    Ord(cbStd)  : CType := cbStd;
    Ord(cbCdecl): CType := cbCdecl;
    else begin
{$IFDEF USELOG}
          ToLog('SetFilter invalid callback type detected.', $02);
{$ENDIF}
          Exit;
         end;
  end;
  Data := nil;
  Mask := nil;
  if Size = 2 then
    Size := 4096
  else
    Size := 0;  
  Id := DvbSetFilter(@Filters, Pid, LpFunc, CType, Data, Mask, Size);
  if Id <> $FFFF then
  begin
    try
      LpFilter_Num^ := Id;
      Result := True;
{$IFDEF USELOG}
      if Id = 0 then
      begin
        if Size = 0 then
          ToLog(format('SetFilter success, callback type %d, passing on all data with filter identifier %d, single packets.', [CallBackType, Id]), $02)
        else
          ToLog(format('SetFilter success, callback type %d, passing on all data with filter identifier %d, 4K buffer.', [CallBackType, Id]), $02);
      end
      else
      begin
        if Size = 0 then
          ToLog(format('SetFilter success, callback type %d, Pid %d has filter identifier %d, single packets.', [CallBackType, Pid, Id]), $02)
        else
          ToLog(format('SetFilter success, callback type %d, Pid %d has filter identifier %d, 4K buffer.', [CallBackType, Pid, Id]), $02)
      end;
{$ENDIF}
    except
{$IFDEF USELOG}
      ToLog('SetFilter error passing back identifier.', $02);
{$ENDIF}
      Result := False;
    end;
  end
  else
  begin
{$IFDEF USELOG}
    ToLog(format('SetFilter setting filter for Pid %d failed.', [Pid]), $02);
{$ENDIF}
  end;
end;


{------------------------------------------------------------------------------
  Params  : <Pid>           Pid number
            <LpFunc>        Pointer to callback
            <CallBackType>  Type of callback. NOT USED. Special type is used.
            <Size>          Size of data to be returned in callback
                            Size of a packet is defined in INI file
                            1: Single packet
                            2: 4096 byte blocks
                               Note that only complete packets are returned,
                               thus for a 188 packet size data is passed on
                               when 21 packets are received
  Returns : <Result>        True if filter set
                            False if any error
            <LpFilter_Num>  Filter identifier

  Descript: Creates a filter for the indicated Pid (special type).
  Notes   :
 ------------------------------------------------------------------------------}
function SetFilterEx(Pid: Word; LpFunc: Pointer; CallBackType: Dword; Size: Dword;
                     LpFilter_Num: PDword): Boolean; cdecl;
begin
{$IFDEF USELOG}
  ToLog('SetFilterEx -> we call SetFilter internally.', $04);
{$ENDIF}
  Result := SetFilter(Pid, LpFunc, Ord(cbFastEx), Size, LpFilter_Num);
end;


{------------------------------------------------------------------------------
  Params  : <IR_Type>       $01 = RC5
            <DevAddr>       Device address to respond to
                            $FFFF if all devices to pass through
            <LpFunc>        Callback
  Returns : <Result>        True if remote control callback installed
                            False if any error
            <LpFilter_Num>  Filter identifier (always 0)

  Descript: Creates a filter for the remote control.
  Notes   : Not implemented
 ------------------------------------------------------------------------------}
function SetRemoteControl(IR_Type: Dword; DevAddr: Word; LpFunc: Pointer;
                          LpFilter_num: PDword): Boolean; cdecl;
begin
{$IFDEF USELOG}
  ToLog('SetRemoteControl.', $04);
{$ENDIF}
  Result := True;
end;


{------------------------------------------------------------------------------
  Params  : -
  Returns : <Result>  True if success
                      False if any error

  Descript: Initialize the driver and setup reception.
  Notes   :
 ------------------------------------------------------------------------------}
function StartDVB: Boolean; cdecl;
var
  IsSlave : Boolean;
  Loop    : Integer;
begin
{$IFDEF USELOG}
  ToLog('StartDVB.', $04);
{$ENDIF}
  Result := False;
  if CardHandle <> INVALID_HANDLE_VALUE then
    Exit;
  CardHandle := Saa7146aCreateFile(CardToUse);
  if CardHandle = INVALID_HANDLE_VALUE then
    Exit;

  for Loop := Low(Filters.Filters) to High(Filters.Filters) do
  begin
    Filters.Filters[Loop].Active := False;
    Filters.Filters[Loop].CountDetected := 0;
    Filters.Filters[Loop].CountCheckedOut := 0;
    Filters.Filters[Loop].CountCalled := 0;
  end;

  // Reset tuner if it has this option
  // First a software reset
  Saa7146aWriteToSaa7146aRegister(CardHandle, CSaa7146aMc1, CSaa7146aMc1SoftReset);
  // The RESET of the tuner is connected to a GPIO line of the SAA7146A, so we have to configure this
  // Note: Not all tuner have this reset line (eg. Cinergy 1200 DVB-S has no reset facility)
  if TunerReset <> 255 then
    Saa7146aIssueReset(CardHandle, TunerReset);
  Saa7146aInitializeI2c(CardHandle);

  // We also have to initialize the tuner to the default state
  if not Saa7146aWriteDefaultsToQpsk(CardHandle, TunerType) then
  begin
    Saa7146aCloseHandle(CardHandle);
{$IFDEF USELOG}
    ToLog('StartDVB error: Unable to access QPSK.', $02);
{$ENDIF}
    Exit;
  end
  else
  begin
    // Power on (if supported)
    if not Saa7146aEnableLNB(CardHandle, (LNBOnOff = 1)) then
    begin
{$IFDEF USELOG}
      ToLog('StartDVB error: Unable to enable LNB.', $02);
{$ENDIF}
      Exit;
    end;
  end;

  // Setup data transfer from tuner to memory and initialize tuner
  IsSlave := False;
  if not DvbSetupBuffering(CardHandle, True, TunerType, TunerReset, @IsSlave, @BufferSize, @BufferId, Rps0Program) then
  begin
{$IFDEF USELOG}
    ToLog('StartDVB error: Could not setup buffering mechanism.', $02);
{$ENDIF}
    Exit;
  end;
  // Start the thread which receives notifications
  PacketThread := TDataThread.Create(True);
  PacketThread.FBufferId      := BufferId;
  PacketThread.FPacketBuffers := BufferSize;
  PacketThread.Resume;

  Result := True;
end;


{------------------------------------------------------------------------------
  Params  : -
  Returns : <Result>  True if success
                      False if any error

  Descript: De-initialize driver and reception.
  Notes   :
 ------------------------------------------------------------------------------}
function StopDVB: Boolean; cdecl;
var
  Loop: Integer;
  Counts1: Integer;
  Counts2: Integer;
  Counts3: Integer;
begin
{$IFDEF USELOG}
  ToLog('StopDVB.', $04);
{$ENDIF}
  Result := False;
  Counts1 := 0;
  Counts2 := 0;
  Counts3 := 0;
  for Loop := Low(Filters.Filters) to High(Filters.Filters) do
  begin
    Counts1 := Counts1 + Filters.Filters[Loop].CountDetected;
    Counts2 := Counts2 + Filters.Filters[Loop].CountCheckedOut;
    Counts3 := Counts3 + Filters.Filters[Loop].CountCalled;
    DvbStopFilter(@Filters, Loop);
  end;
{$IFDEF USELOG}
  ToLog(format('StopDVB total filter counts at %d of which %d had a correct mask, resulting in %d callbacks.', [Counts1, Counts2, Counts3]), $04);
{$ENDIF}

  if CardHandle = INVALID_HANDLE_VALUE then
    Exit;

  if Assigned(PacketThread) and
    (not PacketThread.FHasStopped) then
  begin
    // Note: The behaviour of the thread (during finalization) is different
    //       if called from the DLL finalizepart. It seems the thread is
    //       then already 'stalled'. Waiting with 'sleeps' is also slow
    //       because the finalize here seems to get very low priority.
    //       Therefore no waiting in a loop or simular.
    PacketThread.FProgrammedStop := True;
    PacketThread.Terminate;                                // Mark it for termination
    if PacketThread.Suspended then
      PacketThread.Resume;
    // If we are waiting for a notification then generate (end) it manually.
    // Note the use of <DriverHandle> and NOT the handle from the thread. This is because
    // the other handle might be 'busy' with a <Saa7146aWaitForNotification>!
    // Because the thread might not react directly or requires multiple
    // notification we allow for that.
    Saa7146aGenerateManualNotification(CardHandle);
    Sleep(20);
    Saa7146aGenerateManualNotification(CardHandle);
    Sleep(100);
//        PacketThread.WaitFor;  For some reason never expires when DLL is dynamically used
    PacketThread.Free;
  end;

  Saa7146aCloseHandle(CardHandle);
  CardHandle := INVALID_HANDLE_VALUE;

  Result := True;
end;


{******************************************************************************}
{                               API FUNCTIONS END                              }
{******************************************************************************}


{******************************************************************************}
{                              INITIALIZE AND FINALIZE                         }
{******************************************************************************}
{------------------------------------------------------------------------------
  Params  : -
  Returns : -

  Descript: Initialization of unit
  Notes   :
 ------------------------------------------------------------------------------}
procedure Initialize;
var
  Parameter    : string;
  Error        : Integer;
{$IFDEF USELOG}
  LogClear     : Boolean;
{$ENDIF}
  CheckString  : string;
  DvbCard      : string;
begin
  // Set initial values
  IniFile      := nil;
  CardHandle   := INVALID_HANDLE_VALUE;
  FilterLock   := TCriticalSection.Create;
  PassAll      := False;
{$IFDEF USELOG}
  LogStream    := nil;
  LogLevel     := 0;
  LogClear     := True;
{$ENDIF}

  if FileExists('StreamReader.ini') then
  begin
    IniFile := TMemIniFile.Create('StreamReader.ini');
{$IFDEF USELOG}
    // Only with a local INI file we allow logging
    Parameter := GetParameter('Interface', 'LogLevel', '0');
    Val(Parameter, LogLevel, Error);
    if Error <> 0 then
      LogLevel := 0;
    Parameter := GetParameter('Interface', 'LogClear', 'Yes');
    if LowerCase(Parameter) = 'no' then
      LogClear := False
    else
      LogClear := True;
{$ENDIF}
  end;
{$IFDEF USELOG}
  if LogLevel in [1..5] then
  begin
    try
      if (not LogClear) and
         FileExists('StreamReader.log') then
      begin
        LogStream := TFileStream.Create('StreamReader.log', fmOpenReadWrite);
        // Go to end of file (we will be appending)
        LogStream.Seek(0, soFromEnd);
      end
      else
        LogStream := TFileStream.Create('StreamReader.log', fmCreate);
      ToLog('-------------------------------------------------------------', $00);
      ToLog(format('Log level: %d', [LogLevel]), $00);
    except
      FreeAndNil(LogStream);
    end;
  end;
{$ENDIF}

  // Get tuner type
  CheckString := LowerCase(GetParameter('Interface', 'Tuner', 'BSRU6'));
  if LowerCase(CheckString) = 'su1278' then
    TunerType := CTunerSU1278
  else
    if LowerCase(CheckString) = 'bsbe1' then
      TunerType := CTunerBSBE1
    else
      TunerType := CTunerBSRU6;

  // Get synthesizer address
  Parameter := LowerCase(GetParameter('Interface', 'SynthesizerAddress', '1'));
  Val(Parameter, SynthesizerAddress, Error);
  if Error <> 0 then
    SynthesizerAddress := 1;
  if SynthesizerAddress > 3 then
    SynthesizerAddress := 255;

  // Get tuner reset line
  Parameter := LowerCase(GetParameter('Interface', 'TunerReset', '2'));
  Val(Parameter, TunerReset, Error);
  if Error <> 0 then
    TunerReset := 2;
  if TunerReset > 3 then
    TunerReset := 255;

  // Get LNB polarity line
  Parameter := LowerCase(GetParameter('Interface', 'LNBPolarity', '0'));
  Val(Parameter, LNBPolarity, Error);
  if Error <> 0 then
    LNBPolarity := 0;
  if LNBPolarity > 1 then
    LNBPolarity := 255;

  // Get LNB on/off line
  Parameter := LowerCase(GetParameter('Interface', 'LNBOnOff', '1'));
  Val(Parameter, LNBOnOff, Error);
  if Error <> 0 then
    LNBOnOff := 0;
  if LNBOnOff > 1 then
    LNBOnOff := 255;
  if LNBOnOff = LNBPolarity then
    LNBOnOff := 255;

  // Get card type (overrules other settings)
  DvbCard := GetParameter('Interface', 'Card', '');
  if (UpperCase(DvbCard) = 'TTPCLINEBUDGET') or
     (UpperCase(DvbCard) = 'NOVA') then
  begin
    TunerType          := CTunerBSRU6;
    SynthesizerAddress := 1;
    TunerReset         := 2;
    LNBPolarity        := 0;
    LNBOnOff           := 1;
  end;
  if (UpperCase(DvbCard) = 'TTPCLINEBUDGET2') or
     (UpperCase(DvbCard) = 'NOVA2') then
  begin
    TunerType          := CTunerSU1278;
    SynthesizerAddress := 0;
    TunerReset         := 2;
    LNBPolarity        := 0;
    LNBOnOff           := 1;
  end;
  if (UpperCase(DvbCard) = 'TERRATEC') or
     (UpperCase(DvbCard) = 'KNC') or
     (UpperCase(DvbCard) = 'TYPHOON') or
     (UpperCase(DvbCard) = 'ANUBIS') then
  begin
    TunerType          := CTunerSU1278;
    SynthesizerAddress := 1;
    TunerReset         := 255;
    LNBPolarity        := 1;
    LNBOnOff           := 255;
  end;

  // Get packet size
  Parameter := LowerCase(GetParameter('Interface', 'PacketSize', '184'));
  Val(Parameter, PacketSize, Error);
  if Error <> 0 then
    PacketSize := 184;
  if PacketSize = 0 then
    PacketSize := 188;
  if PacketSize > 188 then
    PacketSize := 188;
  // Get buffer time
  Parameter := LowerCase(GetParameter('Interface', 'TSBufferSize', '400'));
  Val(Parameter, BufferSize, Error);
  if Error <> 0 then
    BufferSize := 400;
  // RPS0Program setting
  Parameter := GetParameter('Interface', 'RPS0Program', '-1');
  Val(Parameter, Rps0Program, Error);
  if Error <> 0 then
    Rps0Program := -1;
  // Card settings
  Parameter := LowerCase(GetParameter('Interface', 'Device', '0'));
  Val(Parameter, CardToUse, Error);
  if Error <> 0 then
    CardToUse := 0;
end;


{------------------------------------------------------------------------------
  Params  : -
  Returns : -

  Descript: Finalization of unit
  Notes   :
 ------------------------------------------------------------------------------}
procedure Finalize;
begin
  if CardHandle <> INVALID_HANDLE_VALUE then
  begin
{$IFDEF USELOG}
    ToLog('Calling StopDVB internally.', $04);
{$ENDIF}
    StopDvb;
  end;
{$IFDEF USELOG}
  ToLog('StreamReader Finalizing.', $03);
  if Assigned(LogStream) then
    FreeAndNil(LogStream);
{$ENDIF}
  if Assigned(IniFile) then
    FreeAndNil(IniFile);
  FilterLock.Free;
end;


initialization
  Initialize;

  finalization
    Finalize;
end.

