// $Header: G:/SwDev/WDM/Video/bt848/rcs/Vidch.cpp 1.22 1998/05/12 20:39:19 tomz Exp $

#include "vidch.h"
#include "defaults.h"
#include "fourcc.h"
#include "capmain.h"

#ifdef	HAUPPAUGE
#include "HCWDebug.h"
#endif

void CheckSrbStatus( PHW_STREAM_REQUEST_BLOCK pSrb );

BOOL VideoChannel::bIsVBI()
{
   PSTREAMEX pStrmEx = (PSTREAMEX)GetStrmEx( );
   if ( pStrmEx->StreamNumber == STREAM_IDX_VBI )
   {
      return TRUE;
   }
   else
   {
      return FALSE;
   }
}

BOOL VideoChannel::bIsVideo()
{
   PSTREAMEX pStrmEx = (PSTREAMEX)GetStrmEx( );
   if (( pStrmEx->StreamNumber == STREAM_IDX_PREVIEW ) ||
       ( pStrmEx->StreamNumber == STREAM_IDX_CAPTURE ))
   {
      return TRUE;
   }
   else
   {
      return FALSE;
   }
}


/* Method: VideoChannel::SetDigitalWindow
 * Purpose: Sets the output image size
 * Input: r:   MRect &
 * Output:
 */
ErrorCode VideoChannel::SetDigitalWindow( MRect &r )
{
   Trace t("VideoChannel::SetDigitalWindow()");
   return Digitizer_->SetDigitalWindow( r, *OurField_ );
}

/* Method: VideoChannel::SetAnalogWindow
 * Purpose: Sets the analog dimention for this stream
 * Input: r: MRect &
 * Output:
 */
ErrorCode VideoChannel::SetAnalogWindow( MRect &r )
{
   Trace t("VideoChannel::SetAnalogWindow()");
   return Digitizer_->SetAnalogWindow( r, *OurField_ );
}

/* Method: VideoChannel::OpenChannel
 * Purpose: Allocates a stream from a capture chip
 * Input:
 * Output:
 * Note: It is possible that the current implementation does not require an
 *   elaborate stream allocation scheme. Nonetheless it is used as number of
 *   streams can increase in the future and their dynamics can change
 */
ErrorCode VideoChannel::OpenChannel()
{
   Trace t("VideoChannel::OpenChannel()");

   // can not open twice
   if ( IsOpen() == true )
      return Fail;
   if ( Digitizer_->AllocateStream( OurField_, Stream_ ) == Success ) {
      // store information for all subsequent calls

      SetPaired( false );

      OurField_->SetCallback( &Caller_ );
      SetInterrupt( true );

      // flag the state
      SetOpen();

      SetDefaultQue();
      return Success;
   }
   return Fail;
}

/* Method: VideoChannel::CloseChannel
 * Purpose: Closes the channel. Makes sure everything is freed
 * Input:
 * Output:
 */
ErrorCode VideoChannel::CloseChannel()
{
   Trace t("VideoChannel::CloseChannel()");

   if ( !IsOpen() )
      return Fail;
   
   Stop( );

   while( !BufQue_.IsEmpty( ) )
   {
      DataBuf buf = BufQue_.Get();
   }

   BufQue_.Flush();

   while( !Requests_.IsEmpty( ) )
   {
      PHW_STREAM_REQUEST_BLOCK pSrb = Requests_.Get();
      if ( RemoveSRB( pSrb ))
      {
         DebugOut((0, "   RemoveSRB failed\n"));
         DEBUG_BREAKPOINT();
      }
   }

   Requests_.Flush();

   SetClose();
   return Success;
}

/* Method: VideoChannel::SetFormat
 * Purpose:
 * Input:
 * Output:
 */
ErrorCode VideoChannel::SetFormat( ColFmt aFormat )
{
   Trace t("VideoChannel::SetFormat()");
   Digitizer_->SetPixelFormat( aFormat, *OurField_ );
   return Success;
}

/* Method: VideoChannel::GetFormat
 * Purpose:
 * Input:
 * Output:
 */
ColFmt VideoChannel::GetFormat()
{
   Trace t("VideoChannel::GetFormat()");
   return Digitizer_->GetPixelFormat( *OurField_ );
}

/* Method: VideoChannel::AddBuffer
 * Purpose: This function adds a buffer to a queue
 * Input: pNewBuffer: PVOID - pointer to a buffer to add
 * Output: None
 * Note: This function 'does not know' where the queue is located. It just uses
 *   a pointer to it.
 */
void VideoChannel::AddBuffer( PVOID pPacket )
{
   Trace t("VideoChannel::AddBuffer()");
   DataBuf buf( GetSRB(), pPacket );

   BufQue_.Put( buf );
   DebugOut((1, "AddBuf %x\n", pPacket ) );

   LONGLONG *pB1 = (LONGLONG *)pPacket;
   LONGLONG *pB2 = pB1 + 1;
#ifdef DEBUG
   for ( UINT i = 0; i < 640; i++ ) {
#endif
      *pB1 = 0xAAAAAAAA33333333;
      *pB2 = 0xBBBBBBBB22222222;
#ifdef DEBUG
      pB1 += 2;
      pB2 += 2;
   }
#endif
}

/* Method: VideoChannel::ResetCounters
 * Purpose: Reset the frame info counters
 * Input: None
 * Output: None
 */
VOID VideoChannel::ResetCounters( )
{
   ULONG StreamNumber = Stream_;
   if ( StreamNumber == STREAM_IDX_VBI )
   {
      PKS_VBI_FRAME_INFO pSavedFrameInfo = &((PSTREAMEX)GetStrmEx())->FrameInfo.VbiFrameInfo;
      pSavedFrameInfo->ExtendedHeaderSize = sizeof( KS_VBI_FRAME_INFO );
      pSavedFrameInfo->PictureNumber = 0;
      pSavedFrameInfo->DropCount = 0;
   }
   else
   {
      PKS_FRAME_INFO pSavedFrameInfo = &((PSTREAMEX)GetStrmEx())->FrameInfo.VideoFrameInfo;
      pSavedFrameInfo->ExtendedHeaderSize = sizeof( KS_FRAME_INFO );
      pSavedFrameInfo->PictureNumber = 0;
      pSavedFrameInfo->DropCount = 0;
   }
}

/* Method: VideoChannel::TimeStamp
 * Purpose: Performs the standard buffer massaging when it's done
 * Input: pSrb
 * Output: None
 */
void STREAMAPI VideoChannel::TimeStamp( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("VideoChannel::TimeStamp()");

   PKSSTREAM_HEADER  pDataPacket = pSrb->CommandData.DataBufferArray;
   VideoChannel *chan = (VideoChannel *)((PSTREAMEX)pSrb->StreamObject->HwStreamExtension)->videochannel;

   pDataPacket->PresentationTime.Numerator = 1;
   pDataPacket->PresentationTime.Denominator = 1;

	if( chan->IsVideoInfo2() )
	{
		pDataPacket->DataUsed = chan->GetVidHdr2()->bmiHeader.biSizeImage;
	}
	else
	{
		pDataPacket->DataUsed = chan->GetVidHdr()->bmiHeader.biSizeImage;
	}

   pDataPacket->Duration = chan->GetTimePerFrame();

   DebugOut((1, "DataUsed = %d\n", pDataPacket->DataUsed));

   // [TMZ] [!!!] - hack, timestamping seems broken
   if( 0 ) {
   //if( hMasterClock ) {
      pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_DURATIONVALID;
      pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_TIMEVALID;
      //pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_TIMEVALID;

      HW_TIME_CONTEXT   TimeContext;

      TimeContext.HwDeviceExtension = (struct _HW_DEVICE_EXTENSION *)pSrb->HwDeviceExtension;
      TimeContext.HwStreamObject    = pSrb->StreamObject;
      TimeContext.Function          = TIME_GET_STREAM_TIME;

      StreamClassQueryMasterClockSync (
         chan->hMasterClock,
         &TimeContext
      );

      /*
      LARGE_INTEGER     Delta;

      Delta.QuadPart = TimeContext.Time;
      
      if( TimeContext.Time > (ULONGLONG) Delta.QuadPart )
      {
         pDataPacket->PresentationTime.Time = TimeContext.Time;
      } else {
         pDataPacket->PresentationTime.Time = 0;
      }
      */
      pDataPacket->PresentationTime.Time = TimeContext.Time;

   } else {
      pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_DURATIONVALID;
      pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_TIMEVALID;
      pDataPacket->PresentationTime.Time = 0;
   }

   // now gather the statistics
   PKS_FRAME_INFO pSavedFrameInfo = &((PSTREAMEX)chan->GetStrmEx())->FrameInfo.VideoFrameInfo;
   pSavedFrameInfo->ExtendedHeaderSize = sizeof( KS_FRAME_INFO );
   pSavedFrameInfo->PictureNumber++;
   pSavedFrameInfo->DropCount = 0;

   PKS_FRAME_INFO pFrameInfo =
   (PKS_FRAME_INFO) ( pSrb->CommandData.DataBufferArray + 1 );

   // copy the information to the outbound buffer
   pFrameInfo->ExtendedHeaderSize = pSavedFrameInfo->ExtendedHeaderSize;
   pFrameInfo->PictureNumber =      pSavedFrameInfo->PictureNumber;
   pFrameInfo->DropCount =          pSavedFrameInfo->DropCount;

   if ( pFrameInfo->DropCount ) {
      pSrb->CommandData.DataBufferArray->OptionsFlags |=
         KSSTREAM_HEADER_OPTIONSF_DATADISCONTINUITY;
   }

   // Every frame we generate is a key frame (aka SplicePoint)
   // Delta frames (B or P) should not set this flag

   pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_SPLICEPOINT;

   // make the stream class driver happy
   pSrb->Status = STATUS_SUCCESS;

   DebugOut((1, "*** 2 *** completing SRB %x\n", pSrb));
   CheckSrbStatus( pSrb );
   StreamClassStreamNotification( StreamRequestComplete, pSrb->StreamObject, pSrb );
   
   DebugOut((1, "Signal SRB - %x\n", pSrb->CommandData.DataBufferArray->Data ) );
   DebugOut((1, "********** NeedNotification_ = %d\n", chan->NeedNotification_ ) );

   if ( chan->NeedNotification_ ) {
      // queue was full; now it has at least one entry 
      StreamClassStreamNotification( ReadyForNextStreamDataRequest, pSrb->StreamObject );
   }
}

/* Method: VideoChannel::Interrupt
 * Purpose: Called by the interface class on behalf of capture chip to let know
 *   an interrupt happened.
 * Input: pTag: PVOID, to be passed to the Digitizer_
 * Output: None
 */
void VideoChannel::Interrupt( PVOID pTag, bool skipped )
{
   Trace t("VideoChannel::Interrupt()");

   Digitizer_->ProcessBufferAtInterrupt( pTag );

   if ( skipped ) {
      DebugOut((1, "VidChan::Interrupt skipped\n" ) );
      return;
   }
   // let the class driver know we are done with this buffer
   if ( !Requests_.IsEmpty() ) {
      PHW_STREAM_REQUEST_BLOCK pSrb = Requests_.Get();
      TimeStamp( pSrb ); // [TMZ] [!!!] [HACK]
   }
}

/* Method: VideoChannel::Create
 * Purpose: Creates the stream
 * Input: None
 * Output: None
 */
ErrorCode VideoChannel::Create()
{
   Trace t("VideoChannel::Create()");

	KS_VIDEOINFOHEADER* pVideoInfoHdr = NULL;
	KS_VIDEOINFOHEADER2* pVideoInfoHdr2 = NULL;

	DWORD				biCompression;
	WORD				biBitCount;
	LONG				biWidth;
	LONG				biHeight;
   LONG				biWidthBytes;

	if( IsVideoInfo2() )
	{
		pVideoInfoHdr2 = GetVidHdr2();
		biCompression = pVideoInfoHdr2->bmiHeader.biCompression;
		biBitCount = pVideoInfoHdr2->bmiHeader.biBitCount;   
		biWidth = pVideoInfoHdr2->bmiHeader.biWidth;      
		biHeight = abs(pVideoInfoHdr2->bmiHeader.biHeight);     
	}
	else
	{
		pVideoInfoHdr = GetVidHdr();
		biCompression = pVideoInfoHdr->bmiHeader.biCompression;
		biBitCount = pVideoInfoHdr->bmiHeader.biBitCount;   
		biWidth = pVideoInfoHdr->bmiHeader.biWidth;      
		biHeight = abs(pVideoInfoHdr->bmiHeader.biHeight);     
	}

   MRect analog( 0, 0, biWidth, biHeight );
   MRect ImageRect( 0, 0, biWidth, biHeight );

   DebugOut((1, "**************************************************************************\n"));
   DebugOut((1, "biCompression = %d\n", biCompression));
   DebugOut((1, "biBitCount = %d\n", biBitCount));

   if ( pVideoInfoHdr->bmiHeader.biCompression == 3)
	{
		if( IsVideoInfo2() )
		{
			pVideoInfoHdr2->bmiHeader.biCompression = FCC_YUY2;
			biCompression = FCC_YUY2;
		}
		else
		{
			pVideoInfoHdr->bmiHeader.biCompression = FCC_YUY2;
			biCompression = FCC_YUY2;
		}
	}

   ColorSpace tmp( biCompression, biBitCount );

   DebugOut((1, "ColorFormat = %d\n", tmp.GetColorFormat()));
   DebugOut((1, "**************************************************************************\n"));

   OurField_->ResetCounters();
   ResetCounters();
      
   // verify that we are not asked to produce a smaller image

   #ifdef HACK_FUDGE_RECTANGLES
	if( IsVideoInfo2() )
	{
      if( pVideoInfoHdr2->rcTarget.bottom == 0 ) 
		{
            // [!!!] [TMZ] - hack
            pVideoInfoHdr2->rcTarget.left    = 0;
            pVideoInfoHdr2->rcTarget.top     = 0;
            pVideoInfoHdr2->rcTarget.right   = biWidth;
            pVideoInfoHdr2->rcTarget.bottom  = biHeight;
      }
	}
	else
	{
      if( pVideoInfoHdr->rcTarget.bottom == 0 ) 
		{
            // [!!!] [TMZ] - hack
            pVideoInfoHdr->rcTarget.left    = 0;
            pVideoInfoHdr->rcTarget.top     = 0;
            pVideoInfoHdr->rcTarget.right   = biWidth;
            pVideoInfoHdr->rcTarget.bottom  = biHeight;
      }
	}
   #endif


   MRect		dst;
   MRect		src;
	if( IsVideoInfo2() )
	{
		dst.Set( pVideoInfoHdr2->rcTarget.left, pVideoInfoHdr2->rcTarget.top, pVideoInfoHdr2->rcTarget.right, pVideoInfoHdr2->rcTarget.bottom );
		src.Set( pVideoInfoHdr2->rcSource.left, pVideoInfoHdr2->rcSource.top, pVideoInfoHdr2->rcSource.right, pVideoInfoHdr2->rcSource.bottom );
	}
	else
	{
		dst.Set( pVideoInfoHdr->rcTarget.left, pVideoInfoHdr->rcTarget.top, pVideoInfoHdr->rcTarget.right, pVideoInfoHdr->rcTarget.bottom );
		src.Set( pVideoInfoHdr->rcSource.left, pVideoInfoHdr->rcSource.top, pVideoInfoHdr->rcSource.right, pVideoInfoHdr->rcSource.bottom );
	}
   if ( !dst.IsEmpty() ) 
	{
      // use the new size                                  
      ImageRect = dst;
      if ( !src.IsEmpty() )
		{
         analog = src;
		}
      else
		{
         analog = dst;
		}
      // calculate the offset for the new beginning of the data
      dwBufferOffset_ = dst.top * biWidth + dst.left * tmp.GetPitchBpp();
      // when rcTarget is non-empty, biWidth is stride of the buffer
      biWidthBytes = biWidth;
   } 
	else
	{
      biWidthBytes = biWidth * tmp.GetPitchBpp() / 8;
	}


	if( IsVideoInfo2() )
	{
		DebugOut((1, "pVideoInfoHdr2->rcTarget(%d, %d, %d, %d)\n", 
						  pVideoInfoHdr2->rcTarget.left, 
						  pVideoInfoHdr2->rcTarget.top, 
						  pVideoInfoHdr2->rcTarget.right, 
						  pVideoInfoHdr2->rcTarget.bottom
						  ));
	}
	else
	{
		DebugOut((1, "pVideoInfoHdr->rcTarget(%d, %d, %d, %d)\n", 
						  pVideoInfoHdr->rcTarget.left, 
						  pVideoInfoHdr->rcTarget.top, 
						  pVideoInfoHdr->rcTarget.right, 
						  pVideoInfoHdr->rcTarget.bottom
						  ));
	}
   DebugOut((1, "dst(%d, %d, %d, %d)\n", 
                 dst.left, 
                 dst.top, 
                 dst.right, 
                 dst.bottom
                 ));
   DebugOut((1, "Pitch =%d, width = %d\n", biWidthBytes, dst.Width() ) );

   SetBufPitch( biWidthBytes );

   if ( SetAnalogWindow ( analog  ) == Success && //<-must be set first !
        SetDigitalWindow( ImageRect ) == Success &&
        SetFormat( tmp.GetColorFormat() ) == Success &&
        Digitizer_->Create( *OurField_ ) == Success ) 
	{
      State_ = Created;
      return Success;
   }
   return Fail;
}

/* Method: VideoChannel::Start
 * Purpose: Starts the stream
 * Input: None
 * Output: None
 */
void VideoChannel::Start()
{
   Trace t("VideoChannel::Start()");
   State_ = Started;
   Digitizer_->Start( *OurField_ );
}

/* Method: VideoChannel::Stop
 * Purpose: Stops the stream
 * Input: None
 * Output: None
 */
ErrorCode VideoChannel::Stop()
{
   Trace t("VideoChannel::Stop()");

   if ( !IsOpen() )
      return Fail;

   Digitizer_->Stop( *OurField_ );
   State_ = Open;

   while( !BufQue_.IsEmpty( ) )
   {
      DataBuf buf = BufQue_.Get();
   }

   BufQue_.Flush();
   return Success;
}

/* Method: VideoChannel::Pause
 * Purpose: Stops the stream
 * Input: None
 * Output: None
 */
ErrorCode VideoChannel::Pause()
{
   Trace t("VideoChannel::Pause()");

   Digitizer_->Pause( *OurField_ );
   State_ = Paused;
   OurField_->ResetCounters();  // jaybo
   ResetCounters();
   return Success;
}

/* Method: VideoChanIface::Notify
 * Purpose:  Notifies the VideoChannel that an interrupt happened
 * Input: None
 * Output: None
 */
void VideoChanIface::Notify( PVOID pTag, bool skipped  )
{
   Trace t("VideoChanIface::Notify()");
   ToBeNotified_->Interrupt( pTag, skipped  );
}

/* Method: VideoChannel::AddSRB
 * Purpose: Adds SRB and buffer to the queues
 * Input: pSrb
 * Output: None
 */
void VideoChannel::AddSRB( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("VideoChannel::AddSRB()");

   Requests_.Put( pSrb );
   SetSRB( pSrb );

   PUCHAR pBufAddr = (PUCHAR)pSrb->CommandData.DataBufferArray->Data;
   AddBuffer( pBufAddr + dwBufferOffset_ );

   // don't forget to report our field type !
   // this cast is valid for VBI FRAME as well ( see ksmedia.h )
   PKS_FRAME_INFO pFrameInfo =
   (PKS_FRAME_INFO) ( pSrb->CommandData.DataBufferArray + 1 );
   pFrameInfo->dwFrameFlags = FieldType_;

   // ask for more buffers
   CheckNotificationNeed();
}

/* Method: VideoChannel::RemoveSRB
 * Purpose: Removes SRB from the queue and signals it
 * Input: pSrb
 * Output: None
 */

bool VideoChannel::RemoveSRB( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("VideoChannel::RemoveSRB()");

/*
	//FGR - TODO: i guess we should see if there really is a record of this SRB
   if(Requests_.IsEmpty()){
	   pSrb->Status = STATUS_CANCELLED;

      DebugOut((1, "*** 3 *** completing SRB %x\n", pSrb));
      CheckSrbStatus( pSrb );
      StreamClassStreamNotification( StreamRequestComplete, pSrb->StreamObject, pSrb );
      //StreamClassStreamNotification( ReadyForNextStreamDataRequest, pSrb->StreamObject );

	   return( true );
   }
*/

   int n = 0;
   
   n = Requests_.GetNumOfItems();
   DebugOut((1, "VideoChannel::RemoveSRB - Found %d SRBs in queue\n", n));

   bool bFound = false;

   // cycle through the list
   // pull from the head, put to the tail
   // if we find our pSrb during one cycle, pull it out

   while ( n-- > 0 ) // yes it can go negative
   {
      PHW_STREAM_REQUEST_BLOCK pTempSrb = Requests_.Get();
      if ( pTempSrb == pSrb )
      {
         // Pull him out
         if  ( bFound )
         {
            DebugOut((0, "Found pSrb(%x) in the queue more than once\n", pSrb));
            DEBUG_BREAKPOINT();
         }
         else
         {
            bFound = true;
   	      pSrb->Status = STATUS_CANCELLED;

            DebugOut((1, "*** 4 *** completing SRB %x\n", pSrb));
            CheckSrbStatus( pSrb );
            StreamClassStreamNotification( StreamRequestComplete, pSrb->StreamObject, pSrb );
            //StreamClassStreamNotification( ReadyForNextStreamDataRequest, pSrb->StreamObject );
         }
         n--;  // warning: if this is the last, it will go negative
      }
      else
      {
         Requests_.Put( pTempSrb );
      }
   }

   n = Requests_.GetNumOfItems();
   DebugOut((1, "VideoChannel::RemoveSRB - Left %d SRBs in queue, returning %d\n", n, bFound));

/*   
   PHW_STREAM_REQUEST_BLOCK InQueSRB = Requests_.PeekLeft();
   if ( InQueSRB == pSrb ) {

      InQueSRB = Requests_.Get();
      InQueSRB->Status = STATUS_CANCELLED;

      DebugOut((1, "Cancel SRB -%x\n", pSrb ) );

      CheckSrbStatus( pSrb );
      StreamClassStreamNotification( StreamRequestComplete,
         InQueSRB->StreamObject, InQueSRB );

      if ( Requests_.IsEmpty() )
         DebugOut((1, " queue is empty\n" ) );
      else
         DebugOut((1, "queue is not empty\n" ) );

	   return( true );

   } else {
//      DebugOut((1, "Cancelling wrong SRB ! - %x, %x\n", pSrb, InQueSRB ) );
//#ifdef	HAUPPAUGE
//	  TRAP();
//#endif
//   }
	   InQueSRB = Requests_.PeekRight();
	   if ( InQueSRB == pSrb ) {
		   InQueSRB = Requests_.GetRight();
		   InQueSRB->Status = STATUS_CANCELLED;
		   DebugOut((1, "Cancel SRB from right - %x\n", pSrb ) );
         CheckSrbStatus( pSrb );
		   StreamClassStreamNotification( StreamRequestComplete,
			   pSrb->StreamObject, pSrb );
	      return( true );
	   } else {
         DebugOut((0, "Cancelling wrong SRB from right too! - %x, %x\n", pSrb, InQueSRB ) );
	      return( false );
	   }
   }
*/
   return( bFound );
}

VideoChannel::~VideoChannel()
{
   Trace t("VideoChannel::~VideoChannel()");
   CloseChannel();
}

/* Method: VideoChannel::CheckNotificationNeed
 * Purpose: Sees if there is room for more buffers
 * Input: None
 * Output: None
 */
void VideoChannel::CheckNotificationNeed()
{
   Trace t("VideoChannel::CheckNotificationNeed()");

   if ( !BufQue_.IsFull() ) {
      // always hungry for more
      StreamClassStreamNotification( ReadyForNextStreamDataRequest, pSRB_->StreamObject );
      NeedNotification_ = false;
   } else
      NeedNotification_ = true;
}

/* Method: InterVideoChannel::Interrupt
 * Purpose: Processes the interrupt for the interleaved video streams
 * Input: pTag: PVOID - index in reality
 *   skipped: bool - indicates if buffer was written to
 * Output: None
 */
void InterVideoChannel::Interrupt( PVOID pTag, bool skipped )
{
   Trace t("InterVideoChannel::Interrupt()");

   int idx = (int)pTag;
   slave.IntNotify( PVOID( idx - ProgsWithinField ), skipped );
   Parent::Interrupt( pTag, skipped );
}

/* Method: InterVideoChannel::AddSRB
 * Purpose: Adds SRB to itself and dispatches 2 buffer pointers, one to each
 *   channel
 * Input: pSRB
 * Output: None
 */
void InterVideoChannel::AddSRB( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("InterVideoChannel::AddSRB()");

   PUCHAR pBufAddr = (PUCHAR)pSrb->CommandData.DataBufferArray->Data;
   // biWidth was set in Create()
   UINT biWidthBytes;
	if( IsVideoInfo2() )
	{
		biWidthBytes = VidHeader2_.bmiHeader.biWidth / 2;
	}
	else
	{
		biWidthBytes = VidHeader_.bmiHeader.biWidth / 2;
	}

   // to be used when adding buffer
   SetSRB( pSrb );
   slave.SetSRB( pSrb );

   // need to swap addresses for even/odd fields for RGB formats due to up-side-down bitmaps
   ColorSpace tmp( GetFormat() );
   if ( !( tmp.GetColorFormat() > CF_RGB8 && tmp.GetColorFormat() < CF_VBI ) ) 
	{
      // put buffer in its place
      // and adjusted address into the other channel
      slave.AddBuffer( pBufAddr + biWidthBytes );
      AddBuffer( pBufAddr );
   } 
	else 
	{
      slave.AddBuffer( pBufAddr );
      AddBuffer( pBufAddr + biWidthBytes );
   }

   // don't forget to add the SRB !
   Requests_.Put( pSrb );

   // set field type to full frame.
   PKS_FRAME_INFO pFrameInfo = (PKS_FRAME_INFO)( pSrb->CommandData.DataBufferArray + 1 );
   pFrameInfo->dwFrameFlags = KS_VIDEO_FLAG_FRAME;

   CheckNotificationNeed();
}

/* Function: SplitFrame
 * Purpose: Halfs the size of the video image so 2 fields can be used to create
 *   the original size
 * Input: VidHdr: KS_VIDEOINFOHEADER &
 * Output: None
 */
inline void  SplitFrame( KS_VIDEOINFOHEADER &VidHdr )
{
   Trace t("SplitFrame()");

   VidHdr.bmiHeader.biHeight /= 2;
   VidHdr.rcSource.top /= 2;
   VidHdr.rcTarget.top /= 2;
   VidHdr.rcSource.bottom /= 2;
   VidHdr.rcTarget.bottom /= 2;
}

inline void  SplitFrame2( KS_VIDEOINFOHEADER2 &VidHdr2 )
{
   Trace t("SplitFrame()");

   VidHdr2.bmiHeader.biHeight /= 2;
   VidHdr2.rcSource.top /= 2;
   VidHdr2.rcTarget.top /= 2;
   VidHdr2.rcSource.bottom /= 2;
   VidHdr2.rcTarget.bottom /= 2;
}


/* Method: InterVideoChannel::Create
 * Purpose: Sets the video parameters for the slave channel and
 *   calls into parent to create both
 * Input: None
 * Output: None
 */
ErrorCode InterVideoChannel::Create()
{
   Trace t("InterVideoChannel::Create()");

//   slave.SetInterrupt( false );
   slave.SetCallback( 0 );
   // restore the original as SplitFrame mangles the parameters

	MRect		dst;
	DWORD				biCompression;
	WORD				biBitCount;
   LONG				biWidthBytes;

	if( IsVideoInfo2() )
	{
		VidHeader2_ = OrigVidHeader2_;
		// split a frame into two fields
		SplitFrame2( VidHeader2_ );
		// double up the pitch, so we can interleave the buffers
		dst.Set( VidHeader2_.rcTarget.left, VidHeader2_.rcTarget.top, VidHeader2_.rcTarget.right, VidHeader2_.rcTarget.bottom );
		biCompression = VidHeader2_.bmiHeader.biCompression;
		biBitCount = VidHeader2_.bmiHeader.biBitCount;
	}
	else
	{
		VidHeader_ = OrigVidHeader_;
		// split a frame into two fields
		SplitFrame( VidHeader_ );
		// double up the pitch, so we can interleave the buffers
		dst.Set( VidHeader_.rcTarget.left, VidHeader_.rcTarget.top, VidHeader_.rcTarget.right, VidHeader_.rcTarget.bottom );
		biCompression = VidHeader_.bmiHeader.biCompression;
		biBitCount = VidHeader_.bmiHeader.biBitCount;
	}


   ColorSpace tmp( biCompression, biBitCount );

   if ( !dst.IsEmpty() ) 
	{
      // biWidth is the stride in bytes
		if( IsVideoInfo2() )
		{
			VidHeader2_.bmiHeader.biWidth *= 2 * 2;
			biWidthBytes = VidHeader2_.bmiHeader.biWidth;
		}
		else
		{
			VidHeader_.bmiHeader.biWidth *= 2 * 2;
			biWidthBytes = VidHeader_.bmiHeader.biWidth;
		}
   } 
	else 
	{
		if( IsVideoInfo2() )
		{
			// calculate the number of bytes per scan line
			biWidthBytes = tmp.GetPitchBpp() * VidHeader2_.bmiHeader.biWidth / 8;
			// can it be non-aligned ??
			biWidthBytes += 3;
			biWidthBytes &= ~3;

			// must be increased two times to interleave the fields;
			biWidthBytes *= 2;

			// the rcTarget uses half the original height and full width
			VidHeader2_.rcTarget = MRect(
				0, 
				0, 
				VidHeader2_.bmiHeader.biWidth,
				abs(VidHeader2_.bmiHeader.biHeight) 
			);

			DebugOut((1, "VidHeader2_.rcTarget(%d, %d, %d, %d)\n", 
							  VidHeader2_.rcTarget.left, 
							  VidHeader2_.rcTarget.top, 
							  VidHeader2_.rcTarget.right, 
							  VidHeader2_.rcTarget.bottom
							  ));

			// have to trick the slave into using correct ( doubled ) pitch
			VidHeader2_.bmiHeader.biWidth = biWidthBytes; // this is the pitch slave uses
		}
		else
		{
			// calculate the number of bytes per scan line
			biWidthBytes = tmp.GetPitchBpp() * VidHeader_.bmiHeader.biWidth / 8;
			// can it be non-aligned ??
			biWidthBytes += 3;
			biWidthBytes &= ~3;

			// must be increased two times to interleave the fields;
			biWidthBytes *= 2;

			// the rcTarget uses half the original height and full width
			VidHeader_.rcTarget = MRect(
				0, 
				0, 
				VidHeader_.bmiHeader.biWidth,
				abs(VidHeader_.bmiHeader.biHeight) 
			);

			DebugOut((1, "VidHeader_.rcTarget(%d, %d, %d, %d)\n", 
							  VidHeader_.rcTarget.left, 
							  VidHeader_.rcTarget.top, 
							  VidHeader_.rcTarget.right, 
							  VidHeader_.rcTarget.bottom
							  ));

			// have to trick the slave into using correct ( doubled ) pitch
			VidHeader_.bmiHeader.biWidth = biWidthBytes; // this is the pitch slave uses
		}
   }
   SetBufPitch( biWidthBytes );

	// at this point slave will have all the members set up properly
	if( IsVideoInfo2() )
	{
		slave.SetVidHdr2( VidHeader2_ );
	}
	else
	{
		slave.SetVidHdr( VidHeader_ );
	}
   slave.SetPaired( true );

   // needed for full-size YUV9 and other planar modes
   Digitizer_->SetPlanarAdjust( biWidthBytes / 2 );

   return Parent::Create();
}

/* Method: VideoChannel::GetStreamType
 * Purpose: reports back type of the stream. Used when destroying channels
 */
StreamType VideoChannel::GetStreamType()
{
   Trace t("VideoChannel::GetStreamType()");
   return Single;
}

/* Method: VideoChannel::TimeStampVBI
 * Purpose: Performs the standard buffer massaging when it's done
 * Input: pSrb
 * Output: None
 */
void STREAMAPI VideoChannel::TimeStampVBI( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("VideoChannel::TimeStamp()");

   PKSSTREAM_HEADER  pDataPacket = pSrb->CommandData.DataBufferArray;
   VideoChannel *chan = (VideoChannel *)((PSTREAMEX)pSrb->StreamObject->HwStreamExtension)->videochannel;

   pDataPacket->PresentationTime.Numerator = 1;
   pDataPacket->PresentationTime.Denominator = 1;

	if( chan->IsVideoInfo2() )
	{
		pDataPacket->DataUsed = chan->GetVidHdr2()->bmiHeader.biSizeImage;
	}
	else
	{
		pDataPacket->DataUsed = chan->GetVidHdr()->bmiHeader.biSizeImage;
	}

   pDataPacket->Duration = chan->GetTimePerFrame();

   DebugOut((1, "DataUsed = %d\n", pDataPacket->DataUsed));

   // [TMZ] [!!!] - hack, timestamping seems broken
   if( 0 ) {
   //if( hMasterClock ) {
      pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_DURATIONVALID;
      pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_TIMEVALID;
      //pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_TIMEVALID;

      HW_TIME_CONTEXT   TimeContext;

      TimeContext.HwDeviceExtension = (struct _HW_DEVICE_EXTENSION *)pSrb->HwDeviceExtension;
      TimeContext.HwStreamObject    = pSrb->StreamObject;
      TimeContext.Function          = TIME_GET_STREAM_TIME;

      StreamClassQueryMasterClockSync (
         chan->hMasterClock,
         &TimeContext
      );

      /*
      LARGE_INTEGER     Delta;

      Delta.QuadPart = TimeContext.Time;
      
      if( TimeContext.Time > (ULONGLONG) Delta.QuadPart )
      {
         pDataPacket->PresentationTime.Time = TimeContext.Time;
      } else {
         pDataPacket->PresentationTime.Time = 0;
      }
      */
      pDataPacket->PresentationTime.Time = TimeContext.Time;

   } else {
      pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_DURATIONVALID;
      pDataPacket->OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_TIMEVALID;
      pDataPacket->PresentationTime.Time = 0;
   }

   PKS_VBI_FRAME_INFO pSavedFrameInfo = &((PSTREAMEX)chan->GetStrmEx())->FrameInfo.VbiFrameInfo;
   pSavedFrameInfo->ExtendedHeaderSize = sizeof( PKS_VBI_FRAME_INFO );
   pSavedFrameInfo->PictureNumber++;
   pSavedFrameInfo->DropCount = 0;

   // now gather the statistics
   PKS_VBI_FRAME_INFO pFrameInfo =
   (PKS_VBI_FRAME_INFO) ( pSrb->CommandData.DataBufferArray + 1 );

   // copy the information to the outbound buffer
   pFrameInfo->ExtendedHeaderSize = pSavedFrameInfo->ExtendedHeaderSize;
   pFrameInfo->PictureNumber =      pSavedFrameInfo->PictureNumber;
   pFrameInfo->DropCount =          pSavedFrameInfo->DropCount;

   pFrameInfo->dwSamplingFrequency = VBISampFreq; // Bug - changes with video format

   if ( ((VBIChannel*)(chan))->Dirty_ ) { // propagate the tv tuner change notification
      ((VBIChannel*)(chan))->Dirty_ = false;
      pFrameInfo->TvTunerChangeInfo = ((VBIChannel*)(chan))->TVTunerChangeInfo_;
      pFrameInfo->dwFrameFlags      |= KS_VBI_FLAG_TVTUNER_CHANGE;
      pFrameInfo->VBIInfoHeader     = ((VBIChannel*)(chan))->VBIInfoHeader_;
      pFrameInfo->dwFrameFlags      |= KS_VBI_FLAG_VBIINFOHEADER_CHANGE ;
   } else {
      pFrameInfo->dwFrameFlags &= ~KS_VBI_FLAG_TVTUNER_CHANGE;
      pFrameInfo->dwFrameFlags &= ~KS_VBI_FLAG_VBIINFOHEADER_CHANGE;
   }

   if ( pFrameInfo->DropCount ) {
      pSrb->CommandData.DataBufferArray->OptionsFlags |=
         KSSTREAM_HEADER_OPTIONSF_DATADISCONTINUITY;
   }

    // Every frame we generate is a key frame (aka SplicePoint)
    // Delta frames (B or P) should not set this flag

    pDataPacket->OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_SPLICEPOINT;

   // make the stream class driver happy
   pSrb->Status = STATUS_SUCCESS;

   DebugOut((1, "*** 5 *** completing SRB %x\n", pSrb));
   CheckSrbStatus( pSrb );
   StreamClassStreamNotification( StreamRequestComplete, pSrb->StreamObject, pSrb );

   DebugOut((1, "Signal SRB - %x\n", pSrb->CommandData.DataBufferArray->Data ) );

   DebugOut((1, "********** NeedNotification_ = %d\n", chan->NeedNotification_ ) );

   if ( chan->NeedNotification_ ) {
      // queue was full; now it has at least one entry 
      StreamClassStreamNotification( ReadyForNextStreamDataRequest,
         pSrb->StreamObject );
   }
}

/* Method: VBIAlterChannel::Interrupt
 * Purpose: Processes the interrupt for the VBI channel
 */
void VBIChannel::Interrupt( PVOID pTag, bool skipped )
{
   Trace t("VBIChannel::Interrupt()");

   if ( Requests_.IsEmpty( ) )
   {
      DebugOut((1, "VBI interrupt, but Requests_ is empty\n"));
      return;
   }

   // save the SRB for further processing ( it is gone from the qu in the Parent::Interrupt
   PHW_STREAM_REQUEST_BLOCK pSrb = Requests_.PeekLeft();

   // Parent::Interrupt( pTag, skipped );
   {
      Digitizer_->ProcessBufferAtInterrupt( pTag );

      if ( skipped ) {
         DebugOut((1, "VidChan::Interrupt skipped\n" ) );
         return;
      }
      // let the class driver know we are done with this buffer
      if ( !Requests_.IsEmpty() ) {
         PHW_STREAM_REQUEST_BLOCK pTimeSrb = Requests_.Get();
         TimeStampVBI( pTimeSrb ); // [TMZ] [!!!]
      }
   }
}

/* Method: VBIChannel::ChangeNotification
 * Purpose: Called to save off the tv tuner change notification
 * Input: pSrb
 */
void VBIChannel::ChangeNotification( PHW_STREAM_REQUEST_BLOCK pSrb )
{
   Trace t("VBIChannel::ChangeNotification()");

   const KSSTREAM_HEADER &DataPacket = *pSrb->CommandData.DataBufferArray;
   RtlCopyMemory( &TVTunerChangeInfo_, DataPacket.Data, sizeof( KS_TVTUNER_CHANGE_INFO ) );
   Dirty_ = true;
}

/* Method: VideoChannel::ChangeNotification
 * Purpose: Noop for the base class.
 */
void VideoChannel::ChangeNotification( PHW_STREAM_REQUEST_BLOCK )
{
   Trace t("VideoChannel::ChangeNotification()");
}

/* Method: VBIAlterChannel::SetVidHdr
 * Purpose: Transforms the VBI parameters ( size ) into regular video header
 * Input:
 */
void VBIAlterChannel::SetVidHdr( const KS_DATAFORMAT_VBIINFOHEADER &df )
{
   Trace t("VBIAlterChannel::SetVidHdr()");

   // save for the history ( for the interrupt, actually )
   SetVBIInfHdr( df.VBIInfoHeader );
   (*(VBIChannel*)&slave).SetVBIInfHdr( df.VBIInfoHeader );
   
   KS_VIDEOINFOHEADER VidInfHdr;
   RtlZeroMemory( &VidInfHdr, sizeof( VidInfHdr ) );

   // create a regular video info header
   VidInfHdr.bmiHeader.biWidth = VBISamples;
   VidInfHdr.bmiHeader.biHeight =
      df.VBIInfoHeader.EndLine - df.VBIInfoHeader.StartLine + 1; // inclusive
   // taken from the VBI GUID
   VidInfHdr.bmiHeader.biCompression = FCC_VBI;
   VidInfHdr.bmiHeader.biBitCount = 8;

   // this is very important too
   VidInfHdr.bmiHeader.biSizeImage =
      VidInfHdr.bmiHeader.biWidth * VidInfHdr.bmiHeader.biHeight;

   // now handle the case when stride is larger than width ( have to set the
   // target rectangle )
   if ( df.VBIInfoHeader.StrideInBytes > VBISamples ) {
      VidInfHdr.rcTarget.right  = df.VBIInfoHeader.StrideInBytes;
      VidInfHdr.rcTarget.bottom = VidInfHdr.bmiHeader.biHeight;
   }

   // the Parent::Create will take care of setting vid header for the slave
   Parent::SetVidHdr( VidInfHdr );
}

//??? TODO: -- is this needed?
void VBIAlterChannel::SetVidHdr2( const KS_DATAFORMAT_VBIINFOHEADER &df )
{
   Trace t("VBIAlterChannel::SetVidHdr2()");

   // save for the history ( for the interrupt, actually )
   SetVBIInfHdr( df.VBIInfoHeader );
   
   KS_VIDEOINFOHEADER2 VidInfHdr;
   RtlZeroMemory( &VidInfHdr, sizeof( VidInfHdr ) );

   // create a regular video info header
   VidInfHdr.bmiHeader.biWidth = VBISamples;
   VidInfHdr.bmiHeader.biHeight =
      df.VBIInfoHeader.EndLine - df.VBIInfoHeader.StartLine + 1; // inclusive
   // taken from the VBI GUID
   VidInfHdr.bmiHeader.biCompression = FCC_VBI;
   VidInfHdr.bmiHeader.biBitCount = 8;

   // this is very important too
   VidInfHdr.bmiHeader.biSizeImage =
      VidInfHdr.bmiHeader.biWidth * VidInfHdr.bmiHeader.biHeight;

   // now handle the case when stride is larger than width ( have to set the
   // target rectangle )
   if ( df.VBIInfoHeader.StrideInBytes > VBISamples ) {
      VidInfHdr.rcTarget.right  = df.VBIInfoHeader.StrideInBytes;
      VidInfHdr.rcTarget.bottom = VidInfHdr.bmiHeader.biHeight;
   }

   // the Parent::Create will take care of setting vid header for the slave
   Parent::SetVidHdr2( VidInfHdr );
}