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.
542 lines
13 KiB
542 lines
13 KiB
/*************************************************************************
|
|
* Copyright (C) Microsoft Corporation 1992. All rights reserved.
|
|
*
|
|
*************************************************************************/
|
|
|
|
/*
|
|
* aviread.c: read blocks from the avi file (using worker thread).
|
|
* Only built in WIN32 case.
|
|
*/
|
|
|
|
//#define AVIREAD
|
|
#ifdef AVIREAD
|
|
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#include <ntavi.h> // This must be included, for both versions
|
|
|
|
#include <string.h> // needed for memmove in nt
|
|
#include <mmddk.h>
|
|
#include <memory.h>
|
|
#include "common.h"
|
|
#include "ntaviprt.h"
|
|
#include "aviread.h"
|
|
#include "aviffmt.h"
|
|
#include "graphic.h"
|
|
|
|
|
|
/*
|
|
* overview of operation:
|
|
*
|
|
* creation of a avird object (via avird_startread) creates an avird_header
|
|
* data structure and a worker thread. The data structure is protected by
|
|
* a critical section, and contains two semaphores. the semEmpty semaphore is
|
|
* initialised to the number of buffers allocated (normally 2), and the
|
|
* semFull semaphore is initialised to 0 (there are initially no full buffers).
|
|
*
|
|
* The worker thread then loops waiting on semEmpty to get empty buffers,
|
|
* and once it finds them, filling them and signalling via semFull that they
|
|
* are ready. It fills them by callbacks to a AVIRD_FUNC function that
|
|
* we were given a pointer to on object creation.
|
|
*
|
|
* Getting a buffer via avird_getnextbuffer waits on semFull until there
|
|
* are full buffers, and returns the first on the list, after moving
|
|
* it to the 'in use' list. The caller
|
|
* will use/play the data in the buffer, and then call avird_emptybuffer:
|
|
* - this finds the buffer on the 'in use' list, moves it to the
|
|
* 'empty' list and then signals the worker thread via
|
|
* semEmpty.
|
|
*
|
|
* The worker thread checks the object state each time it is woken up. If this
|
|
* state is 'closing' (bOK == FALSE), the thread frees all memory and exits.
|
|
* avird_endread changes the object state and signals semEmpty to wake up the
|
|
* worker thread.
|
|
*/
|
|
|
|
|
|
/*
|
|
* each buffer is represented by one of these headers
|
|
*/
|
|
typedef struct avird_buffer {
|
|
|
|
/*
|
|
* size of the buffer in bytes
|
|
*/
|
|
long lSize;
|
|
|
|
/* size of read data in bytes */
|
|
long lDataSize;
|
|
|
|
|
|
/*
|
|
* pointer to the next buffer in this state
|
|
*/
|
|
struct avird_buffer * pNextBuffer;
|
|
|
|
/* FALSE if buffer read failed */
|
|
BOOL bOK;
|
|
|
|
/* request sequence */
|
|
int nSeq;
|
|
|
|
/*
|
|
* pointer to the actual block of buffer data
|
|
*/
|
|
PBYTE pData;
|
|
} AVIRD_BUFFER, * PAVIRD_BUFFER;
|
|
|
|
|
|
|
|
/* handles to HAVIRD are pointers to this data structure, but the
|
|
* contents of the struct are known only within this module
|
|
*/
|
|
typedef struct avird_header {
|
|
/*
|
|
* always hold the critical section before checking/changing the
|
|
* object state or any buffer state
|
|
*/
|
|
CRITICAL_SECTION critsec;
|
|
|
|
/*
|
|
* the count of this semaphore is the count of empty buffers
|
|
* waiting to be picked up by the worker thread
|
|
*/
|
|
HANDLE semEmpty;
|
|
|
|
/*
|
|
* the count of this semaphore is the count of full buffers waiting
|
|
* to be picked up by the caller.
|
|
*/
|
|
HANDLE semFull;
|
|
|
|
/* object state - FALSE indicates close-down request. */
|
|
BOOL bOK;
|
|
|
|
|
|
/* pointer to list of buffer headers ready to be filled */
|
|
PAVIRD_BUFFER pEmpty;
|
|
|
|
/* pointer to a list of buffer headers in use by the client */
|
|
PAVIRD_BUFFER pInUse;
|
|
|
|
/*
|
|
* pointer to an ordered list of buffer headers ready to be
|
|
* picked up by the client.
|
|
*/
|
|
PAVIRD_BUFFER pFull;
|
|
|
|
/*
|
|
* function to call to fill a buffer
|
|
*/
|
|
AVIRD_FUNC pFunc;
|
|
|
|
/* instance arg to pass to pFunc() */
|
|
DWORD dwInstanceData;
|
|
|
|
/* size of next buffer to be read */
|
|
long lNextSize;
|
|
|
|
/* request sequence */
|
|
int nNext;
|
|
/* total in sequence */
|
|
int nBlocks;
|
|
|
|
|
|
} AVIRD_HEADER, * PAVIRD_HEADER;
|
|
|
|
|
|
/* number of buffers to queue up */
|
|
#define MAX_Q_BUFS 4
|
|
|
|
|
|
/*
|
|
* worker thread function
|
|
*/
|
|
DWORD avird_worker(LPVOID lpvThreadData);
|
|
|
|
|
|
/*
|
|
* function to delete whole AVIRD_HEADER data structure.
|
|
*/
|
|
void avird_freeall(PAVIRD_HEADER phdr);
|
|
|
|
/*
|
|
* start an avird operation and return a handle to use in subsequent
|
|
* calls. This will cause an asynchronous read (achieved using a separate
|
|
* thread) to start reading the next few buffers
|
|
*/
|
|
HAVIRD
|
|
avird_startread(AVIRD_FUNC func, DWORD dwInstanceData, long lFirstSize,
|
|
int nFirst, int nBlocks)
|
|
{
|
|
PAVIRD_HEADER phdr;
|
|
PAVIRD_BUFFER pbuf;
|
|
int i;
|
|
HANDLE hThread;
|
|
DWORD dwThreadId;
|
|
int nBufferSize;
|
|
|
|
/*
|
|
* allocate and init the header
|
|
*/
|
|
phdr = (PAVIRD_HEADER) LocalLock(LocalAlloc(LHND, sizeof(AVIRD_HEADER)));
|
|
|
|
if (phdr == NULL) {
|
|
return(NULL);
|
|
}
|
|
|
|
InitializeCriticalSection(&phdr->critsec);
|
|
phdr->semEmpty = CreateSemaphore(NULL, MAX_Q_BUFS, MAX_Q_BUFS, NULL);
|
|
phdr->semFull = CreateSemaphore(NULL, 0, MAX_Q_BUFS, NULL);
|
|
phdr->bOK = TRUE;
|
|
|
|
phdr->pInUse = NULL;
|
|
phdr->pFull = NULL;
|
|
phdr->pEmpty = NULL;
|
|
|
|
phdr->pFunc = func;
|
|
phdr->dwInstanceData = dwInstanceData;
|
|
phdr->lNextSize = lFirstSize;
|
|
phdr->nNext = nFirst;
|
|
phdr->nBlocks = nBlocks;
|
|
|
|
/*
|
|
* round sizes up to 2k to reduce cost of small increases
|
|
*/
|
|
nBufferSize = (lFirstSize + 2047) & ~2047;
|
|
/*
|
|
* allocate and init the buffers
|
|
*/
|
|
for (i = 0; i < MAX_Q_BUFS; i++) {
|
|
pbuf = (PAVIRD_BUFFER) LocalLock(LocalAlloc(LHND, sizeof(AVIRD_BUFFER)));
|
|
|
|
pbuf->lSize = nBufferSize;
|
|
pbuf->pData = (PBYTE) LocalLock(LocalAlloc(LHND, pbuf->lSize));
|
|
|
|
pbuf->pNextBuffer = phdr->pEmpty;
|
|
phdr->pEmpty = pbuf;
|
|
}
|
|
|
|
/*
|
|
* create the worker thread
|
|
*/
|
|
hThread = CreateThread(NULL, 0, avird_worker, (LPVOID)phdr, 0, &dwThreadId);
|
|
if (hThread) {
|
|
/* thread was created ok */
|
|
CloseHandle(hThread);
|
|
return( phdr);
|
|
} else {
|
|
avird_freeall(phdr);
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* return the next buffer from an HAVIRD object.
|
|
*/
|
|
PBYTE
|
|
avird_getnextbuffer(HAVIRD havird, long * plSize)
|
|
{
|
|
PAVIRD_HEADER phdr = havird;
|
|
PAVIRD_BUFFER pbuf;
|
|
|
|
|
|
|
|
/* wait for a full buffer -report if actual wait needed*/
|
|
if (WaitForSingleObject(phdr->semFull, 0) == WAIT_TIMEOUT) {
|
|
DPF(("..waiting.."));
|
|
WaitForSingleObject(phdr->semFull, INFINITE);
|
|
}
|
|
|
|
|
|
/* always hold critsec before messing with queues */
|
|
EnterCriticalSection(&phdr->critsec);
|
|
|
|
/* de-queue first full buffer and place on InUse queue */
|
|
pbuf = phdr->pFull;
|
|
phdr->pFull = pbuf->pNextBuffer;
|
|
pbuf->pNextBuffer = phdr->pInUse;
|
|
phdr->pInUse = pbuf;
|
|
|
|
/* finished with critical section */
|
|
LeaveCriticalSection(&phdr->critsec);
|
|
|
|
if (!pbuf->bOK) {
|
|
/* buffer read failed */
|
|
DPF(("reporting read failure on %d\n", pbuf->nSeq));
|
|
if (plSize) {
|
|
*plSize = 0;
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
/* return size of buffer if requested */
|
|
if (plSize) {
|
|
*plSize = pbuf->lDataSize;
|
|
}
|
|
|
|
|
|
return(pbuf->pData);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* return to the queue a buffer that has been finished with (is now empty)
|
|
*
|
|
* causes the worker thread to be woken up and to start filling the buffer
|
|
* again.
|
|
*/
|
|
void
|
|
avird_emptybuffer(HAVIRD havird, PBYTE pBuffer)
|
|
{
|
|
PAVIRD_HEADER phdr = havird;
|
|
PAVIRD_BUFFER pbuf, pprev;
|
|
|
|
|
|
/* always get the critsec before messing with queues */
|
|
EnterCriticalSection(&phdr->critsec);
|
|
|
|
pprev = NULL;
|
|
for (pbuf = phdr->pInUse; pbuf != NULL; pbuf = pbuf->pNextBuffer) {
|
|
|
|
if (pbuf->pData == pBuffer) {
|
|
|
|
/* this is the buffer */
|
|
break;
|
|
}
|
|
pprev = pbuf;
|
|
}
|
|
|
|
if (pbuf != NULL) {
|
|
/* de-queue from InUse and place on empty q */
|
|
if (pprev) {
|
|
pprev->pNextBuffer = pbuf->pNextBuffer;
|
|
} else {
|
|
phdr->pInUse = pbuf->pNextBuffer;
|
|
}
|
|
pbuf->pNextBuffer = phdr->pEmpty;
|
|
phdr->pEmpty = pbuf;
|
|
|
|
/* mark as not validly read */
|
|
pbuf->bOK = FALSE;
|
|
|
|
/* signal that there is another buffer to fill */
|
|
ReleaseSemaphore(phdr->semEmpty, 1, NULL);
|
|
} else {
|
|
DPF(("buffer 0x%x not found on InUse list\n", pBuffer));
|
|
}
|
|
|
|
LeaveCriticalSection(&phdr->critsec);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* delete an avird object. the worker thread will be stopped and all
|
|
* data allocated will be freed. The HAVIRD handle is no longer valid after
|
|
* this call.
|
|
*/
|
|
void
|
|
avird_endread(HAVIRD havird)
|
|
{
|
|
PAVIRD_HEADER phdr = havird;
|
|
|
|
DPF(("killing an avird object\n"));
|
|
|
|
/* get the critsec before messing with states */
|
|
EnterCriticalSection(&phdr->critsec);
|
|
|
|
/* tell the worker thread to do all the work */
|
|
phdr->bOK = FALSE;
|
|
|
|
/* wake up the worker thread */
|
|
ReleaseSemaphore(phdr->semEmpty, 1, NULL);
|
|
|
|
/*
|
|
* we must hold the critsec past the semaphore signal: if we
|
|
* release the critsec first, the worker thread might see the
|
|
* state change before we have signalled the semaphore. He would
|
|
* then potentially have destroyed the semaphore AND freed the
|
|
* AVIRD_HEADER structure by the time we tried to signal the
|
|
* semaphore. This way, we are sure that until we release the
|
|
* critsec, everything is still valid
|
|
*/
|
|
|
|
LeaveCriticalSection(&phdr->critsec);
|
|
|
|
/* all done - phdr now may not exist */
|
|
}
|
|
|
|
/*
|
|
* worker thread function.
|
|
*
|
|
* loop waiting for semEmpty to tell us there are empty buffers. When
|
|
* we see one, fill it with phdr->pFunc and move it to the
|
|
* full queue. Each time we are woken up, check the state. If it
|
|
* changes to false, delete the whole thing and exit.
|
|
*
|
|
* the argument we are passed is the PAVIRD_HEADER.
|
|
*/
|
|
|
|
DWORD
|
|
avird_worker(LPVOID lpvThreadData)
|
|
{
|
|
PAVIRD_HEADER phdr = (PAVIRD_HEADER) lpvThreadData;
|
|
PAVIRD_BUFFER pbuf, pprev;
|
|
long lNextSize;
|
|
HANDLE hmem;
|
|
|
|
DPF(("Worker %d started\n", GetCurrentThreadId()));
|
|
|
|
for (; ;) {
|
|
|
|
/* wait for an empty buffer (or state change) */
|
|
WaitForSingleObject(phdr->semEmpty, INFINITE);
|
|
|
|
|
|
/* get the critical section before touching the state, queues */
|
|
EnterCriticalSection(&phdr->critsec);
|
|
|
|
if (phdr->bOK == FALSE) {
|
|
/* all over bar the shouting */
|
|
DPF(("%d exiting\n", GetCurrentThreadId()));
|
|
avird_freeall(phdr);
|
|
ExitThread(0);
|
|
}
|
|
|
|
|
|
/* dequeue the first empty buffer */
|
|
pbuf = phdr->pEmpty;
|
|
|
|
Assert(pbuf != NULL);
|
|
|
|
phdr->pEmpty = pbuf->pNextBuffer;
|
|
|
|
lNextSize = phdr->lNextSize;
|
|
|
|
pbuf->nSeq = phdr->nNext++;
|
|
|
|
if (pbuf->nSeq < phdr->nBlocks) {
|
|
/* we can now release the critsec until we need to re-Q the filled buf*/
|
|
LeaveCriticalSection(&phdr->critsec);
|
|
|
|
/* resize the buffer if not big enough */
|
|
if (pbuf->lSize < lNextSize) {
|
|
|
|
hmem = LocalHandle(pbuf->pData);
|
|
LocalUnlock(hmem);
|
|
LocalFree(hmem);
|
|
|
|
pbuf->lSize = ((lNextSize + 2047) & ~2047);
|
|
pbuf->pData = LocalLock(LocalAlloc(LHND, pbuf->lSize));
|
|
}
|
|
|
|
/* record the data content of the buffer */
|
|
pbuf->lDataSize = lNextSize;
|
|
|
|
/* call the filler function */
|
|
if ((*phdr->pFunc)(pbuf->pData, phdr->dwInstanceData, lNextSize,
|
|
&lNextSize)) {
|
|
pbuf->bOK = TRUE;
|
|
} else {
|
|
DPF(("filler reported failure on %d\n", pbuf->nSeq));
|
|
}
|
|
|
|
/* get the critsec before messing with q's or states */
|
|
EnterCriticalSection(&phdr->critsec);
|
|
|
|
/* size for next read */
|
|
phdr->lNextSize = lNextSize;
|
|
}
|
|
|
|
/* place buffer at end of Full queue */
|
|
if (phdr->pFull == NULL) {
|
|
phdr->pFull = pbuf;
|
|
} else {
|
|
for (pprev = phdr->pFull; pprev->pNextBuffer != NULL; ) {
|
|
pprev = pprev->pNextBuffer;
|
|
}
|
|
pprev->pNextBuffer = pbuf;
|
|
}
|
|
pbuf->pNextBuffer = NULL;
|
|
|
|
LeaveCriticalSection(&phdr->critsec);
|
|
|
|
/* signal calling thread that there's another buffer for him */
|
|
ReleaseSemaphore(phdr->semFull, 1, NULL);
|
|
}
|
|
/* silence compiler */
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* free one buffer and buffer header
|
|
*/
|
|
void
|
|
avird_freebuffer(PAVIRD_BUFFER pbuf)
|
|
{
|
|
HANDLE hmem;
|
|
|
|
hmem = LocalHandle( (PSTR)pbuf->pData);
|
|
LocalUnlock(hmem);
|
|
LocalFree(hmem);
|
|
|
|
hmem = LocalHandle( (PSTR)pbuf);
|
|
LocalUnlock(hmem);
|
|
LocalFree(hmem);
|
|
}
|
|
|
|
|
|
/*
|
|
* function to delete whole AVIRD_HEADER data structure.
|
|
*
|
|
* called on calling thread if start-up fails, or on worker thread if
|
|
* asked to shutdown.
|
|
*/
|
|
void
|
|
avird_freeall(PAVIRD_HEADER phdr)
|
|
{
|
|
PAVIRD_BUFFER pbuf, pnext;
|
|
HANDLE hmem;
|
|
|
|
if (phdr->semEmpty) {
|
|
CloseHandle(phdr->semEmpty);
|
|
}
|
|
|
|
if (phdr->semEmpty) {
|
|
CloseHandle(phdr->semFull);
|
|
}
|
|
|
|
DeleteCriticalSection(&phdr->critsec);
|
|
|
|
|
|
for (pbuf = phdr->pInUse; pbuf != NULL; pbuf = pnext) {
|
|
DPF(("In Use buffers at EndRead\n"));
|
|
|
|
pnext = pbuf->pNextBuffer;
|
|
avird_freebuffer(pbuf);
|
|
}
|
|
|
|
for (pbuf = phdr->pEmpty; pbuf != NULL; pbuf = pnext) {
|
|
pnext = pbuf->pNextBuffer;
|
|
avird_freebuffer(pbuf);
|
|
}
|
|
|
|
for (pbuf = phdr->pFull; pbuf != NULL; pbuf = pnext) {
|
|
pnext = pbuf->pNextBuffer;
|
|
avird_freebuffer(pbuf);
|
|
}
|
|
|
|
|
|
hmem = LocalHandle((PSTR) phdr);
|
|
LocalUnlock(hmem);
|
|
LocalFree(hmem);
|
|
}
|
|
|
|
#endif //AVIREAD
|
|
|
|
|
|
|
|
|