Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

839 lines
22 KiB

/*
* stream.c
*
* 32-bit Video Capture driver
* hardware-specific streaming routines for Video Spigot board.
*
*
*
* Geraint Davies, April 93
*/
#include <vckernel.h>
#include <spigot.h>
#include "hardware.h"
#include "hwstruct.h"
#include "profile.h"
#if DBG
// profiling support for usec timing of loops
profiling core, lineprof;
int overruns;
int skips;
int nones;
int none_over_thresh;
#endif
/*
* outline of data streaming interface:
*
*
* StreamInit will be called to initialise for streaming.
* This will be followed by a call to StreamStart to start
* streaming (immediately). Thus any time-consuming setup or cueing should
* be done in the StreamInit call.
*
* After StreamStart has enabled interrupts, our InterruptAck routine
* will be called on each interrupt. This is responsible for:
* -- clearing and re-enabling interrupts
* -- determining if it is time yet to capture a frame (based on the
* microsecs/frame argument to StreamInit).
*
* If InterruptAck returns TRUE to request capture service, then the
* CaptureService function will be called at some later point - IF there is
* a user-supplied buffer to copy into. At this point, the buffer will be
* locked down and the data can be copied in, subject to any conversion.
*
*
* Mutual exclusion outside of this module ensures that only one
* of the init/fini/start/stop functions is called at one time. However,
* there is nothing to prevent the InterruptAcknowledge or the
* CaptureService being called at any time. The interrupt ack routine
* needs to package up all code that needs to be synchronised
* in calls to VC_SynchronizeExecution.
*
* If there are any contention between the DPC and other routines
* we have to package the code into calls to
* VC_SynchronizeDPC (for sync between the CaptureService and the
* passive-level functions) or VC_SynchronizeExecution (for sync with
* the interrupt routine as well as everything else).
*/
/*
* outline of VideoSpigot capture strategy.
*
* on StreamStart, we enable VSYNC interrupts. At the first VSYNC, we detect
* that there is no data ready (pHw->DataReady == DR_NONE) and so we Arm
* the board (requesting capture to start at the next vsync). At the next vsync,
* capture starts, and we do nothing. At the following vsync, data is ready and
* we perform capture-service to copy and translate it. We only ever capture one
* field. Even for 640x480 we capture 640x240 and double the scanlines.
*
* There are two complexities:
*
* We have a 256Kb FIFO. 640x480 images are larger than this: if we waited until
* the VSYNC when capture is complete, the first part of the field will have been
* overwritten. So when arming the board, we switch interrupts in this case to
* occur on first write to fifo. When we get this interrupt, we know there is
* data to read in the fifo, and we capture it as fast as we can. We have one
* field-time to capture enough data to avoid overflow (as this is only
* 40Kb this should not be an issue).
*
* In order to capture a frame, we need to arm the board beforehand using the
* CaptureNextVSync line. We can arm the board to start capturing before we
* have emptied the fifo. In fact, when <= 20% of the scans are left in the
* fifo, it is safe to start the next capture. Also, we can only arm the
* board the field before the field we want to capture. For a normal-size
* capture, we arm the board and (re-)enable VSYNC interrupts. We flag the fact
* that data will be ready the vsync after next by setting pHw->bDataReady to
* DR_NEXT. At the next VSYNC, this is shifted to DR_TRUE. On getting a VSYNC
* with DR_TRUE, it is time to capture.
*
* When arming for a larger-than-fifo frame, we enable interrupts on first
* write to fifo, instead of VSYNC, and we set DR_TRUE. We then do not
* get the next vsync interrupt, but we do get the first-write interrupt
* not long after - and we are ready to start capturing. We re-enable vsync
* interrupts at this point.
*
* Every time we empty one scanline out of the fifo, we check to see if it is
* less than the threshold. If so, we re-arm for the next capture.
*
* We honour the requested frame-rate in the interrupt routine: we count
* the number of interrupts and multiply them by the expected vsync rate
* based on PAL or NTSC standards. We count first-write interrupts as well,
* since we have always just missed a vsync interrupt in this case. If we
* enter the ISR with DR_TRUE, we check this value to see if it is time
* for the next frame. If not, we throw away the captured data (change
* the count of scans in the fifo), and re-arm.
*/
/* forward declaration of private functions in this module */
VOID HW_ArmDuringInterrupt(PDEVICE_INFO pDevInfo);
BOOLEAN HW_DiscardAndArm_Sync(PVOID pGeneric);
VOID HW_DiscardAndArm(PDEVICE_INFO pDevInfo);
/* -- functions called from vckernel via callback table --------------------*/
/*
* enable capture on request from app - actually does nothing.
*/
BOOLEAN
HW_Capture(PDEVICE_INFO pDevInfo, BOOL bCapture)
{
return(TRUE);
}
/*
* initialise and cue ready for streaming from the framebuffer into
* memory. The argument gives the desired frame rate.
*
* We have very little to do at this point except store the msperframe arg.
*
*/
BOOLEAN
HW_StreamInit(PDEVICE_INFO pDevInfo, ULONG microsecperframe)
{
PHWINFO pHw;
pHw = VC_GetHWInfo(pDevInfo);
/* remember the frame rate. Keep it in microsec per frame
* to reduce the rounding errors that would be introduced if we
* did everything in millisecs
*/
// no -convert to 100-usec units to avoid overflow.
pHw->dwTimePerFrame = microsecperframe / 100;
/* check that a format has been set */
if (pHw->Format == FmtInvalid) {
dprintf1(("trying to capture without setting format"));
return(FALSE);
}
return(TRUE);
}
/* clean up after streaming. nothing to do here */
BOOLEAN
HW_StreamFini(PDEVICE_INFO pDevInfo)
{
return (TRUE);
}
/*
* start streaming data into the users buffers.
*
* Reset the counters and flag that capture is in progress.
* Then enable interrupts (per frame) and wait for the right number
* of interrupts.
*/
BOOLEAN
HW_StreamStart(PDEVICE_INFO pDevInfo)
{
/* we set the microsecs per vsync interrupt based on the
* PAl/NTSC frame rate, and we use this to update the video
* clock every interrupt. When this reaches the next frame time,
* we capture the frame.
*
*/
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
pHw->dwNextFrameNr = 0;
pHw->dwTimePerInterrupt =
(pHw->VideoStd == PAL) ? PAL_MICROSPERFIELD : NTSC_MICROSPERFIELD;
// convert to 100-usec units to avoid overflow
pHw->dwTimePerInterrupt /= 100;
pHw->dwNextFrameTime = 0;
pHw->dwVideoTime = 0;
pHw->dwInterruptCount = 0;
pHw->bVideoIn = TRUE;
INIT_PROFILING(&core);
INIT_PROFILING(&lineprof);
#if DBG
overruns = 0;
skips = 0;
nones = 0;
none_over_thresh = 0;
#endif
/*
* prepare for acquisition into the buffer. Selects the active connector
* (if auto-detect of signal enabled), and decides which
* field we should be capturing. Enables VSYNC interrupts.
*
* Set up nScansInFifo and DataReady.
*
* Arming for capture takes place at the first vsync interrupt.
*/
/* select the correct source */
if (pHw->dwFlags & SPG_SOURCEAUTO) {
// note: HW_GetActiveSource calls HW_HaveHlck before changing anything
if (!HW_GetActiveSource(pDevInfo, pHw)) {
dprintf3(("HW_GetActiveSource failed - returning FALSE"));
pHw->bVideoIn = FALSE;
return(FALSE);
}
}
/* no data yet - acquisition started during interrupt processing */
pHw->DataReady = DR_None;
pHw->nScansInFifo = -pHw->nThreshold;
HW_EnableVSyncInts(pDevInfo);
return(TRUE);
}
/*
* stop streaming - simply flag that acquisition should stop.
*
*/
BOOLEAN
HW_StreamStop(PDEVICE_INFO pDevInfo)
{
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
pHw->bVideoIn = FALSE;
#if DBG
if (PROFILING_COUNT(&core) > 1) {
dprintf1(("core loop %d usec/frame", PROFILING_TIME(&core)));
dprintf1(("line loop (%d) %d usec/line", PROFILING_COUNT(&lineprof),
PROFILING_TIME(&lineprof)));
dprintf1(("%d skips, %d overruns", skips, overruns));
dprintf1(("%d ints, %d non-enabled, %d non-overthresh",
pHw->dwInterruptCount, nones, none_over_thresh));
}
#endif
return(TRUE);
}
/*
* get the position - for this we return the number of millisecs
* since capture began, according to the video vsync clock
*/
ULONG
HW_StreamGetPosition(PDEVICE_INFO pDevInfo)
{
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
// remember, dwVideoTime is in 100usecs.
return( (ULONG) pHw->dwVideoTime / 10);
}
/*
* interrupt acknowledge routine. This is called to ack the interrupt
* and re-enable it for next time. It should return TRUE if it is time
* to capture a frame.
*
* If we are not currently capturing (bVideoIn is false), then disable
* interrupts and stop. Inc the interrupt count in any case, since the
* interrupt may have been generated by HW_Init to test the hardware.
*
* See comment at top of file about the complex design needed to enable
* acquisition at the right time. pHw->DataReady describes this state:
* if this is DR_None, then no acquisition has taken place yet and maybe
* we should start one (HW_ArmDuringInterrupt). DR_Next means there is no data
* yet but capture has started and data will be ready next vsync: (we should
* change to DR_True). If we enter here with DR_True, then data is ready
* to capture now: if it is not time for the next frame, then discard
* data and re-arm.
*
* We disable interrupts here to clear them. We then re-enable them for
* VSync interrupts. HW_ArmDuringInterrupt may re-enable
* interrupts for either VSYNC or first write.
*
*/
BOOLEAN
HW_InterruptAcknowledge(PDEVICE_INFO pDevInfo)
{
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
pHw->dwInterruptCount++;
/* clear and disable interrupts */
HW_DisableInts(pDevInfo, TRUE);
/* are we actually streaming ? */
if (!pHw->bVideoIn) {
return(FALSE);
}
/* re-enable VSYNC interrupts */
VC_Out(pDevInfo, PORT_INTFIFO, (BYTE) (pHw->bIntFIFO | INT_ENABLE));
// advance video position by one VSYNC
pHw->dwVideoTime += pHw->dwTimePerInterrupt;
/*
* do we have data to capture ?
*/
switch(pHw->DataReady) {
case DR_None:
/*
* no data ready or expected - let's try to start capture
*/
#if DBG
nones++;
#endif
HW_ArmDuringInterrupt(pDevInfo);
return (FALSE);
case DR_Next:
/*
* data ready at next vsync (not this one) - so change state and wait
*/
pHw->DataReady = DR_True;
return(FALSE);
case DR_True:
/*
* data is ready - do we want it ?
*/
pHw->DataReady = DR_None; // no more now we have this lot
break;
default:
dprintf(("bad data-ready flag state"));
return(FALSE);
}
/*
* we now have a frame ready to capture if we want it. Is it
* time to capture one, or should we throw it away and start again ?
*/
if (pHw->dwVideoTime < pHw->dwNextFrameTime) {
/* note that we don't need this frame */
HW_DiscardAndArm_Sync(pDevInfo);
return(FALSE);
} else {
/*
* check that the fifo has just one frame in it!
*/
if ((pHw->nScansInFifo + pHw->nThreshold) > pHw->nScansInField) {
#if DBG
overruns++;
#endif
dprintf(("overrun: %d excess lines at data-ready",
(pHw->nScansInFifo + pHw->nThreshold) - pHw->nScansInField));
HW_DiscardAndArm_Sync(pDevInfo);
return(FALSE);
}
/* advance next frame time and counter */
pHw->dwNextFrameNr++;
pHw->dwNextFrameTime += pHw->dwTimePerFrame;
/* reset the fifo read pointers so that we start reading at
* the same place as writing started. perhaps this should be in
* CaptureService, but as it needs to be synchronized with the
* ISR, it's easier to do it here.
*
* Note: we enable VSYNC interrupts by doing this. VSync interrupts
* will always be enabled when we reach this state anyway, so this is
* safe.
*/
VC_Out(pDevInfo, PORT_INTFIFO, (BYTE) (pHw->bIntFIFO | INT_ENABLE | FIFO_READ_RESET));
VC_Stall(1);
VC_Out(pDevInfo, PORT_INTFIFO, (BYTE) (pHw->bIntFIFO | INT_ENABLE) );
return(TRUE);
}
}
/* latch prefetch - to avoid optimisation! */
volatile WORD wLatchPrefetch;
/*
* Capture a frame - copy the frame buffer into the buffer passed
* as argument. The user buffer is at this point locked down and
* mapped into system memory.
*
* returns bytes written to buffer PUCHAR (whose length is ULONG).
*
* If there is no buffer available, PUCHAR will be NULL. In this case, we
* need to discard the current data, and re-arm for more captures.
*
*
* returns the time stamp in pTimeStamp (the millisec count
* since capture began - based on the number of video syncs and the
* video standard (PAL, NTSC...).
*/
ULONG
HW_CaptureService(
PDEVICE_INFO pDevInfo,
PUCHAR pBuffer,
PULONG pTimeStamp,
ULONG BufferLength)
{
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
PUCHAR pFrame = VC_GetFrameBuffer(pDevInfo);
ULONG BitmapSize;
if (pBuffer == NULL) {
#if DBG
skips++;
#endif
HW_DiscardAndArm(pDevInfo);
return(0);
}
*pTimeStamp = (ULONG) pHw->dwVideoTime / 10;
/*
* enable the memory-mapped fifo region
*/
VC_Out(pDevInfo, PORT_MEMORY_BASE, pHw->bMemoryBase);
/*
* after resetting the fifo read ptr, we need to read one word to load the one-deep
* latch - we assign this to a global to avoid optimisation problems.
*/
wLatchPrefetch = VC_ReadIOMemoryUSHORT((WORD *) pFrame);
START_PROFILING(&core);
switch(pHw->Format) {
case FmtPal8:
/*
* convert to palettised data (using the translation table) while
* copying out of the frame buffer fifo.
*/
CopyYUVToPal8(pDevInfo,
pBuffer,
pFrame,
pHw->pXlate,
pHw->dwWidth,
pHw->dwHeight,
pHw->dwFifoWidth
);
BitmapSize = pHw->dwHeight * pHw->dwWidth;
break;
case FmtRGB555:
/*
* convert to 16-bit RGB while copying from the buffer (using
* the translation table)
*/
CopyYUVToRGB555(pDevInfo,
pBuffer,
pFrame,
(PWORD) pHw->pXlate,
pHw->dwWidth,
pHw->dwHeight,
pHw->dwFifoWidth
);
BitmapSize = pHw->dwHeight * pHw->dwWidth * 2; // two bytes per dest pix
break;
case FmtRGB24:
/*
* convert to 24-bit RGB while copying from the buffer*/
CopyYUVToRGB24(pDevInfo,
pBuffer,
pFrame,
(PDWORD) pHw->pXlate,
pHw->dwWidth,
pHw->dwHeight,
pHw->dwFifoWidth
);
BitmapSize = pHw->dwHeight * pHw->dwWidth * 3; // 3 bytes per dest pix
break;
case FmtYUV422:
/* straight copy of the rectangle. call the generic
* rectangle copy routine.
*/
CopyFifoRect(pDevInfo,
pBuffer,
pFrame,
pHw->dwWidth,
pHw->dwHeight,
pHw->dwFifoWidth
);
/*
* we may have only captured one field, expecting scan doubling
* on playback/later conversion. if more than one field
* expected, adjust the actual data size
*/
if (pHw->dwHeight >= TWO_FIELD_SIZE) {
/* half the height, two bytes per pixel */
BitmapSize = pHw->dwHeight * pHw->dwWidth;
} else {
BitmapSize = pHw->dwHeight * pHw->dwWidth * 2;
}
break;
}
STOP_PROFILING(&core);
/*
* disable the fifo memory window again until we need it next time
*/
VC_Out(pDevInfo, PORT_MEMORY_BASE, 0);
return(BitmapSize);
}
/* --- internal functions ---------------------------------------*/
/*
* HW_ArmDuringInterrupt
*
* If everything is ok to start capture, trigger capture to start at the next
* vsync, including enabling interrupts as appropriate.
*
* Assumes we are running at interrupt level and already hold any necessary
* spinlocks for accessing the device.
*
* We can only arm if the field is the one before the one we want, and
* if the fifo has less than the threshold number of lines in it. If these
* are true then we enable capture by setting and then resetting the trigger
* bit. At the same time we enable interrupts for VSync, unless this is a
* large capture (one field is larger than the fifo) in which case we enable
* interrupts for first write to fifo.
*/
VOID
HW_ArmDuringInterrupt(PDEVICE_INFO pDevInfo)
{
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
int field;
/*
* find out what field we're on
*/
field = VC_In(pDevInfo, PORT_STATUS) & ODD_FIELD;
/* check the fifo against the restart threshold. remember that this
* value is offset so that 0 means at threshold.
*/
if (pHw->nScansInFifo > 0) {
pHw->LastArmCallInterrupt = pHw->dwInterruptCount;
#if DBG
none_over_thresh++;
#endif
pHw->LastField = field;
return;
}
/*
* check that the field is the one before the one we want
*/
if ((pHw->CaptureField != CAPTURE_ANY) &&
(field == pHw->CaptureField)) {
/*
* not the right field. Let's try to detect single-field devices and
* switch to CAPTURE_ANY for them.
*/
if (pHw->LastArmCallInterrupt == pHw->dwInterruptCount + 1) {
if (field == pHw->LastField) {
dprintf1(("switching to ANY FIELD"));
pHw->CaptureField = CAPTURE_ANY;
}
}
pHw->LastArmCallInterrupt = pHw->dwInterruptCount;
pHw->LastField = field;
return;
}
pHw->LastArmCallInterrupt = pHw->dwInterruptCount;
pHw->LastField = field;
/*
* set the flag indicating when data will be ready. If this is a small
* stream capture, no data until the vsync interrupt following capture start.
* For large stream, we enable interrupt on first-write to fifo, and
* can start capture straightaway.
*/
pHw->DataReady = (pHw->dwFlags & SPG_BLARGE ? DR_True : DR_Next);
/* toggle fifo-write reset line so that all captures start at
* beginning of fifo. Also toggle CaptureAtVSync to initiate acquisition.
*/
VC_Out(pDevInfo, PORT_INTFIFO,
(BYTE) (pHw->bIntFIFO | FIFO_WRITE_RESET | CAPTURE_AT_NEXT_VSYNC));
VC_Stall(1);
/* lower the write-reset and capture lines again, and also enable
* interrupts. Enable for VSYNC unless it is a large-stream capture.
*/
VC_Out(pDevInfo, PORT_INTFIFO,
(BYTE) (pHw->bIntFIFO | INT_ENABLE | (pHw->dwFlags & SPG_BLARGE ? INT_ON_FIRST_WRITE : 0)));
VC_Stall(1);
/* since we are now capturing a field, record that in the fifo scan count. */
pHw->nScansInFifo += pHw->nScansInField;
}
/*
* disable interrupts - called only from interlocked code.
*/
BOOLEAN
HW_DisableInts_Sync(PVOID pGeneric)
{
PDEVICE_INFO pDevInfo = (PDEVICE_INFO) pGeneric;
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
VC_Out(pDevInfo, PORT_INTFIFO, pHw->bIntFIFO);
return TRUE;
}
/*
* enable VSYNC interrupts - called only from interlocked code
*/
BOOLEAN
HW_EnableInts_Sync(PVOID pGeneric)
{
PDEVICE_INFO pDevInfo = (PDEVICE_INFO) pGeneric;
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
VC_Out(pDevInfo, PORT_INTFIFO, (BYTE) (pHw->bIntFIFO | INT_ENABLE));
return TRUE;
}
/*
* disable interrupts on the card.
*
* can be called either from the interrupt routine (ie with the spinlock
* already held) or not (bInISR FALSE) in which case we need to
* synchronize with the interrupt routine.
*/
VOID
HW_DisableInts(PDEVICE_INFO pDevInfo, BOOL bInISR)
{
if (bInISR) {
HW_DisableInts_Sync(pDevInfo);
} else {
VC_SynchronizeExecution(pDevInfo, HW_DisableInts_Sync, pDevInfo);
}
}
/*
* enable vsync interrupts.
*
* always called from non-interlocked code, so we need to interlock with
* the interrupt routine.
*/
VOID
HW_EnableVSyncInts(PDEVICE_INFO pDevInfo)
{
VC_SynchronizeExecution(pDevInfo, HW_EnableInts_Sync, pDevInfo);
}
/*
* struct for passing a count and a pDevInfo to a callback function
* that gets only one parameter.
*/
typedef struct _DEV_AND_COUNT {
PDEVICE_INFO pDevInfo;
int count;
} DEV_AND_COUNT, * PDEV_AND_COUNT;
/*
* decrement the count of scans remaining in the fifo, and
* arm for capture if this falls below the threshold at which it is safe to
* restart.
*
* called only from interlocked code (from HW_DecScansAndArm). Assumes we already
* hold any spinlocks necessary to interlock with the isr.
*/
BOOLEAN
HW_DecScansAndArm_Sync(PVOID pGeneric)
{
PDEV_AND_COUNT pDev = (PDEV_AND_COUNT) pGeneric;
PHWINFO pHw = VC_GetHWInfo(pDev->pDevInfo);
pHw->nScansInFifo -= pDev->count;
if (pHw->nScansInFifo <= 0) {
HW_ArmDuringInterrupt(pDev->pDevInfo);
}
return(TRUE);
}
/*
* decrement the count of scans remaining in the fifo, and
* arm for capture if this falls below the threshold at which it is safe to
* restart.
*
* Called from non-interlocked code (ie not from the ISR itself): it uses
* VC_SynchronizeExecution to sync up with the isr.
*/
VOID HW_DecScansAndArm(PDEVICE_INFO pDevInfo, int count)
{
DEV_AND_COUNT devcount;
devcount.pDevInfo = pDevInfo;
devcount.count = count;
VC_SynchronizeExecution(pDevInfo, HW_DecScansAndArm_Sync, &devcount);
}
/*
* discard all lines in the buffer and re-arm for capture.
*
* Assumes that we hold any necessary spinlocks
*/
BOOLEAN
HW_DiscardAndArm_Sync(PVOID pGeneric)
{
PDEVICE_INFO pDevInfo = (PDEVICE_INFO) pGeneric;
PHWINFO pHw = VC_GetHWInfo(pDevInfo);
pHw->nScansInFifo -= pHw->nScansInField;
if (pHw->nScansInFifo <= 0) {
HW_ArmDuringInterrupt(pDevInfo);
}
return(TRUE);
}
/*
* acquires necessary spinlocks to interlock with interrupt routine, and
* then discards one field from the fifo and re-arms.
*/
VOID
HW_DiscardAndArm(PDEVICE_INFO pDevInfo)
{
VC_SynchronizeExecution(pDevInfo, HW_DiscardAndArm_Sync, pDevInfo);
}