/* -*- tab-width: 8; c-basic-offset: 4 -*- */

/*
 * Digital video MCI Wine Driver
 *
 * Copyright 1999, 2000 Eric POUECH
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "private_mciavi.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(mciavi);

static BOOL MCIAVI_GetInfoAudio(WINE_MCIAVI* wma, const MMCKINFO* mmckList)
{
    MMCKINFO	mmckInfo;

    mmckInfo.ckid = ckidSTREAMHEADER;
    if (mmioDescend(wma->hFile, &mmckInfo, mmckList, MMIO_FINDCHUNK) != 0) {
	WARN("Can't find 'strh' chunk\n");
	return FALSE;
    }

    mmioRead(wma->hFile, (LPSTR)&wma->ash_audio, sizeof(wma->ash_audio));

    TRACE("ash.fccType='%c%c%c%c'\n", 		LOBYTE(LOWORD(wma->ash_audio.fccType)),
	                                        HIBYTE(LOWORD(wma->ash_audio.fccType)),
	                                        LOBYTE(HIWORD(wma->ash_audio.fccType)),
	                                        HIBYTE(HIWORD(wma->ash_audio.fccType)));
    TRACE("ash.fccHandler='%c%c%c%c'\n",	LOBYTE(LOWORD(wma->ash_audio.fccHandler)),
	                                        HIBYTE(LOWORD(wma->ash_audio.fccHandler)),
	                                        LOBYTE(HIWORD(wma->ash_audio.fccHandler)),
	                                        HIBYTE(HIWORD(wma->ash_audio.fccHandler)));
    TRACE("ash.dwFlags=%ld\n", 			wma->ash_audio.dwFlags);
    TRACE("ash.wPriority=%d\n", 		wma->ash_audio.wPriority);
    TRACE("ash.wLanguage=%d\n", 		wma->ash_audio.wLanguage);
    TRACE("ash.dwInitialFrames=%ld\n", 		wma->ash_audio.dwInitialFrames);
    TRACE("ash.dwScale=%ld\n", 			wma->ash_audio.dwScale);
    TRACE("ash.dwRate=%ld\n", 			wma->ash_audio.dwRate);
    TRACE("ash.dwStart=%ld\n", 			wma->ash_audio.dwStart);
    TRACE("ash.dwLength=%ld\n", 		wma->ash_audio.dwLength);
    TRACE("ash.dwSuggestedBufferSize=%ld\n", 	wma->ash_audio.dwSuggestedBufferSize);
    TRACE("ash.dwQuality=%ld\n", 		wma->ash_audio.dwQuality);
    TRACE("ash.dwSampleSize=%ld\n", 		wma->ash_audio.dwSampleSize);
    TRACE("ash.rcFrame=(%d,%d,%d,%d)\n", 	wma->ash_audio.rcFrame.top, wma->ash_audio.rcFrame.left,
	  wma->ash_audio.rcFrame.bottom, wma->ash_audio.rcFrame.right);

    mmioAscend(wma->hFile, &mmckInfo, 0);

    mmckInfo.ckid = ckidSTREAMFORMAT;
    if (mmioDescend(wma->hFile, &mmckInfo, mmckList, MMIO_FINDCHUNK) != 0) {
	WARN("Can't find 'strh' chunk\n");
	return FALSE;
    }
    if (mmckInfo.cksize < sizeof(WAVEFORMAT)) {
	WARN("Size of strf chunk (%ld) < audio format struct\n", mmckInfo.cksize);
	return FALSE;
    }
    wma->lpWaveFormat = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
    if (!wma->lpWaveFormat) {
	WARN("Can't alloc WaveFormat\n");
	return FALSE;
    }

    mmioRead(wma->hFile, (LPSTR)wma->lpWaveFormat, mmckInfo.cksize);

    TRACE("waveFormat.wFormatTag=%d\n",		wma->lpWaveFormat->wFormatTag);
    TRACE("waveFormat.nChannels=%d\n", 		wma->lpWaveFormat->nChannels);
    TRACE("waveFormat.nSamplesPerSec=%ld\n",	wma->lpWaveFormat->nSamplesPerSec);
    TRACE("waveFormat.nAvgBytesPerSec=%ld\n",	wma->lpWaveFormat->nAvgBytesPerSec);
    TRACE("waveFormat.nBlockAlign=%d\n",	wma->lpWaveFormat->nBlockAlign);
    TRACE("waveFormat.wBitsPerSample=%d\n",	wma->lpWaveFormat->wBitsPerSample);
    if (mmckInfo.cksize >= sizeof(WAVEFORMATEX))
	TRACE("waveFormat.cbSize=%d\n", 		wma->lpWaveFormat->cbSize);

    mmioAscend(wma->hFile, &mmckInfo, 0);

    return TRUE;
}

static BOOL MCIAVI_GetInfoVideo(WINE_MCIAVI* wma, const MMCKINFO* mmckList)
{
    MMCKINFO	mmckInfo;

    mmckInfo.ckid = ckidSTREAMHEADER;
    if (mmioDescend(wma->hFile, &mmckInfo, mmckList, MMIO_FINDCHUNK) != 0) {
	WARN("Can't find 'strh' chunk\n");
	return FALSE;
    }

    mmioRead(wma->hFile, (LPSTR)&wma->ash_video, sizeof(wma->ash_video));

    TRACE("ash.fccType='%c%c%c%c'\n", 		LOBYTE(LOWORD(wma->ash_video.fccType)),
	                                        HIBYTE(LOWORD(wma->ash_video.fccType)),
	                                        LOBYTE(HIWORD(wma->ash_video.fccType)),
	                                        HIBYTE(HIWORD(wma->ash_video.fccType)));
    TRACE("ash.fccHandler='%c%c%c%c'\n",	LOBYTE(LOWORD(wma->ash_video.fccHandler)),
	                                        HIBYTE(LOWORD(wma->ash_video.fccHandler)),
	                                        LOBYTE(HIWORD(wma->ash_video.fccHandler)),
	                                        HIBYTE(HIWORD(wma->ash_video.fccHandler)));
    TRACE("ash.dwFlags=%ld\n", 			wma->ash_video.dwFlags);
    TRACE("ash.wPriority=%d\n", 		wma->ash_video.wPriority);
    TRACE("ash.wLanguage=%d\n", 		wma->ash_video.wLanguage);
    TRACE("ash.dwInitialFrames=%ld\n", 		wma->ash_video.dwInitialFrames);
    TRACE("ash.dwScale=%ld\n", 			wma->ash_video.dwScale);
    TRACE("ash.dwRate=%ld\n", 			wma->ash_video.dwRate);
    TRACE("ash.dwStart=%ld\n", 			wma->ash_video.dwStart);
    TRACE("ash.dwLength=%ld\n", 		wma->ash_video.dwLength);
    TRACE("ash.dwSuggestedBufferSize=%ld\n", 	wma->ash_video.dwSuggestedBufferSize);
    TRACE("ash.dwQuality=%ld\n", 		wma->ash_video.dwQuality);
    TRACE("ash.dwSampleSize=%ld\n", 		wma->ash_video.dwSampleSize);
    TRACE("ash.rcFrame=(%d,%d,%d,%d)\n", 	wma->ash_video.rcFrame.top, wma->ash_video.rcFrame.left,
	  wma->ash_video.rcFrame.bottom, wma->ash_video.rcFrame.right);

    mmioAscend(wma->hFile, &mmckInfo, 0);

    mmckInfo.ckid = ckidSTREAMFORMAT;
    if (mmioDescend(wma->hFile, &mmckInfo, mmckList, MMIO_FINDCHUNK) != 0) {
	WARN("Can't find 'strh' chunk\n");
	return FALSE;
    }

    wma->inbih = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
    if (!wma->inbih) {
	WARN("Can't alloc input BIH\n");
	return FALSE;
    }

    mmioRead(wma->hFile, (LPSTR)wma->inbih, mmckInfo.cksize);

    TRACE("bih.biSize=%ld\n", 		wma->inbih->biSize);
    TRACE("bih.biWidth=%ld\n", 		wma->inbih->biWidth);
    TRACE("bih.biHeight=%ld\n", 	wma->inbih->biHeight);
    TRACE("bih.biPlanes=%d\n", 		wma->inbih->biPlanes);
    TRACE("bih.biBitCount=%d\n", 	wma->inbih->biBitCount);
    TRACE("bih.biCompression=%lx\n", 	wma->inbih->biCompression);
    TRACE("bih.biSizeImage=%ld\n", 	wma->inbih->biSizeImage);
    TRACE("bih.biXPelsPerMeter=%ld\n", 	wma->inbih->biXPelsPerMeter);
    TRACE("bih.biYPelsPerMeter=%ld\n", 	wma->inbih->biYPelsPerMeter);
    TRACE("bih.biClrUsed=%ld\n", 	wma->inbih->biClrUsed);
    TRACE("bih.biClrImportant=%ld\n", 	wma->inbih->biClrImportant);

    mmioAscend(wma->hFile, &mmckInfo, 0);

    return TRUE;
}

struct AviListBuild {
    DWORD	numVideoFrames;
    DWORD	numAudioAllocated;
    DWORD	numAudioBlocks;
    DWORD	inVideoSize;
    DWORD	inAudioSize;
};

static BOOL	MCIAVI_AddFrame(WINE_MCIAVI* wma, LPMMCKINFO mmck,
				struct AviListBuild* alb)
{
    if (mmck->ckid == ckidAVIPADDING) return TRUE;

    switch (TWOCCFromFOURCC(mmck->ckid)) {
    case cktypeDIBbits:
    case cktypeDIBcompressed:
    case cktypePALchange:
	TRACE("Adding video frame[%ld]: %ld bytes\n",
	      alb->numVideoFrames, mmck->cksize);
	if (alb->numVideoFrames < wma->dwPlayableVideoFrames) {
	    wma->lpVideoIndex[alb->numVideoFrames].dwOffset = mmck->dwDataOffset;
	    wma->lpVideoIndex[alb->numVideoFrames].dwSize = mmck->cksize;
	    if (alb->inVideoSize < mmck->cksize)
		alb->inVideoSize = mmck->cksize;
	    alb->numVideoFrames++;
	} else {
	    WARN("Too many video frames\n");
	}
	break;
    case cktypeWAVEbytes:
	TRACE("Adding audio frame[%ld]: %ld bytes\n",
	      alb->numAudioBlocks, mmck->cksize);
	if (wma->lpWaveFormat) {
	    if (alb->numAudioBlocks >= alb->numAudioAllocated) {
		alb->numAudioAllocated += 32;
		wma->lpAudioIndex = HeapReAlloc(GetProcessHeap(), 0,
						wma->lpAudioIndex,
						alb->numAudioAllocated * sizeof(struct MMIOPos));
		if (!wma->lpAudioIndex) return FALSE;
	    }
	    wma->lpAudioIndex[alb->numAudioBlocks].dwOffset = mmck->dwDataOffset;
	    wma->lpAudioIndex[alb->numAudioBlocks].dwSize = mmck->cksize;
	    if (alb->inAudioSize < mmck->cksize)
		alb->inAudioSize = mmck->cksize;
	    alb->numAudioBlocks++;
	} else {
	    WARN("Wave chunk without wave format... discarding\n");
	}
	break;
    default:
	WARN("Unknown frame type %04x\n", TWOCCFromFOURCC(mmck->ckid));
	break;
    }
    return TRUE;
}

BOOL MCIAVI_GetInfo(WINE_MCIAVI* wma)
{
    MMCKINFO		ckMainRIFF;
    MMCKINFO		mmckHead;
    MMCKINFO		mmckList;
    MMCKINFO		mmckInfo;
    struct AviListBuild alb;

    if (mmioDescend(wma->hFile, &ckMainRIFF, NULL, 0) != 0) {
	WARN("Can't find 'RIFF' chunk\n");
	return FALSE;
    }

    if ((ckMainRIFF.ckid != FOURCC_RIFF) || (ckMainRIFF.fccType != formtypeAVI)) {
	WARN("Can't find 'AVI ' chunk\n");
	return FALSE;
    }

    mmckHead.fccType = listtypeAVIHEADER;
    if (mmioDescend(wma->hFile, &mmckHead, &ckMainRIFF, MMIO_FINDLIST) != 0) {
	WARN("Can't find 'hdrl' list\n");
	return FALSE;
    }

    mmckInfo.ckid = ckidAVIMAINHDR;
    if (mmioDescend(wma->hFile, &mmckInfo, &mmckHead, MMIO_FINDCHUNK) != 0) {
	WARN("Can't find 'avih' chunk\n");
	return FALSE;
    }

    mmioRead(wma->hFile, (LPSTR)&wma->mah, sizeof(wma->mah));

    TRACE("mah.dwMicroSecPerFrame=%ld\n", 	wma->mah.dwMicroSecPerFrame);
    TRACE("mah.dwMaxBytesPerSec=%ld\n", 	wma->mah.dwMaxBytesPerSec);
    TRACE("mah.dwPaddingGranularity=%ld\n", 	wma->mah.dwPaddingGranularity);
    TRACE("mah.dwFlags=%ld\n", 			wma->mah.dwFlags);
    TRACE("mah.dwTotalFrames=%ld\n", 		wma->mah.dwTotalFrames);
    TRACE("mah.dwInitialFrames=%ld\n", 		wma->mah.dwInitialFrames);
    TRACE("mah.dwStreams=%ld\n", 		wma->mah.dwStreams);
    TRACE("mah.dwSuggestedBufferSize=%ld\n",	wma->mah.dwSuggestedBufferSize);
    TRACE("mah.dwWidth=%ld\n", 			wma->mah.dwWidth);
    TRACE("mah.dwHeight=%ld\n", 		wma->mah.dwHeight);

    mmioAscend(wma->hFile, &mmckInfo, 0);

    mmckList.fccType = listtypeSTREAMHEADER;
    if (mmioDescend(wma->hFile, &mmckList, &mmckHead, MMIO_FINDLIST) != 0) {
	WARN("Can't find 'strl' list\n");
	return FALSE;
    }

    if (!MCIAVI_GetInfoVideo(wma, &mmckList)) {
	return FALSE;
    }

    mmioAscend(wma->hFile, &mmckList, 0);

    mmckList.fccType = listtypeSTREAMHEADER;
    if (mmioDescend(wma->hFile, &mmckList, &mmckHead, MMIO_FINDLIST) == 0) {
	if (!MCIAVI_GetInfoAudio(wma, &mmckList)) {
	    return FALSE;
	}
	mmioAscend(wma->hFile, &mmckList, 0);
    }

    mmioAscend(wma->hFile, &mmckHead, 0);

    /* no need to read optional JUNK chunk */

    mmckList.fccType = listtypeAVIMOVIE;
    if (mmioDescend(wma->hFile, &mmckList, &ckMainRIFF, MMIO_FINDLIST) != 0) {
	WARN("Can't find 'movi' list\n");
	return FALSE;
    }

    wma->dwPlayableVideoFrames = wma->mah.dwTotalFrames;
    wma->lpVideoIndex = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				  wma->dwPlayableVideoFrames * sizeof(struct MMIOPos));
    if (!wma->lpVideoIndex) {
	WARN("Can't alloc video index array\n");
	return FALSE;
    }
    wma->dwPlayableAudioBlocks = 0;
    wma->lpAudioIndex = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
				  wma->dwPlayableVideoFrames * sizeof(struct MMIOPos));
    if (!wma->lpAudioIndex) {
	WARN("Can't alloc audio index array\n");
	return FALSE;
    }

    alb.numAudioBlocks = alb.numVideoFrames = 0;
    alb.inVideoSize = alb.inAudioSize = 0;
    alb.numAudioAllocated = 0;

    while (mmioDescend(wma->hFile, &mmckInfo, &mmckList, 0) == 0) {
	if (mmckInfo.fccType == listtypeAVIRECORD) {
	    MMCKINFO	tmp;

	    while (mmioDescend(wma->hFile, &tmp, &mmckInfo, 0) == 0) {
		MCIAVI_AddFrame(wma, &tmp, &alb);
		mmioAscend(wma->hFile, &tmp, 0);
	    }
	} else {
	    MCIAVI_AddFrame(wma, &mmckInfo, &alb);
	}

	mmioAscend(wma->hFile, &mmckInfo, 0);
    }
    if (alb.numVideoFrames != wma->dwPlayableVideoFrames) {
	WARN("Found %ld video frames (/%ld), reducing playable frames\n",
	     alb.numVideoFrames, wma->dwPlayableVideoFrames);
	wma->dwPlayableVideoFrames = alb.numVideoFrames;
    }
    wma->dwPlayableAudioBlocks = alb.numAudioBlocks;

    if (alb.inVideoSize > wma->ash_video.dwSuggestedBufferSize) {
	WARN("inVideoSize=%ld suggestedSize=%ld\n", alb.inVideoSize, wma->ash_video.dwSuggestedBufferSize);
	wma->ash_video.dwSuggestedBufferSize = alb.inVideoSize;
    }
    if (alb.inAudioSize > wma->ash_audio.dwSuggestedBufferSize) {
	WARN("inAudioSize=%ld suggestedSize=%ld\n", alb.inAudioSize, wma->ash_audio.dwSuggestedBufferSize);
	wma->ash_audio.dwSuggestedBufferSize = alb.inAudioSize;
    }

    wma->indata = HeapAlloc(GetProcessHeap(), 0, wma->ash_video.dwSuggestedBufferSize);
    if (!wma->indata) {
	WARN("Can't alloc input buffer\n");
	return FALSE;
    }

    return TRUE;
}

BOOL    MCIAVI_OpenVideo(WINE_MCIAVI* wma)
{
    DWORD	outSize;
    FOURCC	fcc = wma->ash_video.fccHandler;

    /* check uncompressed AVI */
    if ((fcc == mmioFOURCC('D','I','B',' ')) ||
	(fcc == mmioFOURCC('R','L','E',' '))) {
	wma->hic = 0;
	MCIAVI_DrawFrame(wma);
	return TRUE;
    }

    /* get the right handle */
    if (fcc == 0) fcc = wma->inbih->biCompression;
    if (fcc == mmioFOURCC('C','R','A','M')) fcc = mmioFOURCC('M','S','V','C');

    /* try to get a decompressor for that type */
    wma->hic = ICLocate(ICTYPE_VIDEO, fcc, wma->inbih, NULL, ICMODE_DECOMPRESS);
    if (!wma->hic) {
	WARN("Can't locate codec for the file\n");
	return FALSE;
    }

    outSize = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);

    wma->outbih = HeapAlloc(GetProcessHeap(), 0, outSize);
    if (!wma->outbih) {
	WARN("Can't alloc output BIH\n");
	return FALSE;
    }
    if (!ICGetDisplayFormat(wma->hic, wma->inbih, wma->outbih, 0, 0, 0)) {
	WARN("Can't open decompressor\n");
	return FALSE;
    }

    TRACE("bih.biSize=%ld\n", 		wma->outbih->biSize);
    TRACE("bih.biWidth=%ld\n", 		wma->outbih->biWidth);
    TRACE("bih.biHeight=%ld\n", 	wma->outbih->biHeight);
    TRACE("bih.biPlanes=%d\n", 		wma->outbih->biPlanes);
    TRACE("bih.biBitCount=%d\n", 	wma->outbih->biBitCount);
    TRACE("bih.biCompression=%lx\n", 	wma->outbih->biCompression);
    TRACE("bih.biSizeImage=%ld\n", 	wma->outbih->biSizeImage);
    TRACE("bih.biXPelsPerMeter=%ld\n", 	wma->outbih->biXPelsPerMeter);
    TRACE("bih.biYPelsPerMeter=%ld\n", 	wma->outbih->biYPelsPerMeter);
    TRACE("bih.biClrUsed=%ld\n", 	wma->outbih->biClrUsed);
    TRACE("bih.biClrImportant=%ld\n", 	wma->outbih->biClrImportant);

    wma->outdata = HeapAlloc(GetProcessHeap(), 0, wma->outbih->biSizeImage);
    if (!wma->outdata) {
	WARN("Can't alloc output buffer\n");
	return FALSE;
    }

    if (ICSendMessage(wma->hic, ICM_DECOMPRESS_BEGIN,
		      (DWORD)wma->inbih, (DWORD)wma->outbih) != ICERR_OK) {
	WARN("Can't begin decompression\n");
	return FALSE;
    }

    MCIAVI_DrawFrame(wma);

    return TRUE;
}

static void CALLBACK MCIAVI_waveCallback(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance,
					 DWORD dwParam1, DWORD dwParam2)
{
    WINE_MCIAVI*	wma = (WINE_MCIAVI*)dwInstance;

    switch (uMsg) {
    case WOM_OPEN:
    case WOM_CLOSE:
	break;
    case WOM_DONE:
	InterlockedIncrement(&wma->dwEventCount);
	TRACE("Returning waveHdr=%lx\n", dwParam1);
	SetEvent(wma->hEvent);
	break;
    default:
	ERR("Unknown uMsg=%d\n", uMsg);
    }
}

DWORD MCIAVI_OpenAudio(WINE_MCIAVI* wma, unsigned* nHdr, LPWAVEHDR* pWaveHdr)
{
    DWORD	dwRet;
    LPWAVEHDR	waveHdr;
    unsigned	i;

    dwRet = waveOutOpen(&wma->hWave, WAVE_MAPPER, wma->lpWaveFormat,
			(DWORD)MCIAVI_waveCallback, (DWORD)wma, CALLBACK_FUNCTION);
    if (dwRet != 0) {
	TRACE("Can't open low level audio device %ld\n", dwRet);
	dwRet = MCIERR_DEVICE_OPEN;
	wma->hWave = 0;
	goto cleanUp;
    }

    /* FIXME: should set up a heuristic to compute the number of wave headers
     * to be used...
     */
    *nHdr = 7;
    waveHdr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
			*nHdr * (sizeof(WAVEHDR) + wma->ash_audio.dwSuggestedBufferSize));
    if (!waveHdr) {
	TRACE("Can't alloc wave headers\n");
	dwRet = MCIERR_DEVICE_OPEN;
	goto cleanUp;
    }

    for (i = 0; i < *nHdr; i++) {
	/* other fields are zero:ed on allocation */
	waveHdr[i].lpData = (char*)waveHdr +
	    *nHdr * sizeof(WAVEHDR) + i * wma->ash_audio.dwSuggestedBufferSize;
	waveHdr[i].dwBufferLength = wma->ash_audio.dwSuggestedBufferSize;
	if (waveOutPrepareHeader(wma->hWave, &waveHdr[i], sizeof(WAVEHDR))) {
	    dwRet = MCIERR_INTERNAL;
	    goto cleanUp;
	}
    }

    if (wma->dwCurrVideoFrame != 0 && wma->lpWaveFormat) {
	FIXME("Should recompute dwCurrAudioBlock, except unsynchronized sound & video\n");
    }
    wma->dwCurrAudioBlock = 0;

    wma->hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
    wma->dwEventCount = *nHdr - 1;
    *pWaveHdr = waveHdr;
 cleanUp:
    return dwRet;
}

void MCIAVI_PlayAudioBlocks(WINE_MCIAVI* wma, unsigned nHdr, LPWAVEHDR waveHdr)
{
    TRACE("%ld (ec=%lu)\n", wma->lpAudioIndex[wma->dwCurrAudioBlock].dwOffset, wma->dwEventCount);

    /* push as many blocks as possible => audio gets priority */
    while (wma->dwStatus != MCI_MODE_STOP && wma->dwStatus != MCI_MODE_NOT_READY &&
	   wma->dwCurrAudioBlock < wma->dwPlayableAudioBlocks) {
	unsigned	whidx = wma->dwCurrAudioBlock % nHdr;

	ResetEvent(wma->hEvent);
	if (InterlockedDecrement(&wma->dwEventCount) < 0 ||
	    !wma->lpAudioIndex[wma->dwCurrAudioBlock].dwOffset)
	    break;

	mmioSeek(wma->hFile, wma->lpAudioIndex[wma->dwCurrAudioBlock].dwOffset, SEEK_SET);
	mmioRead(wma->hFile, waveHdr[whidx].lpData, wma->lpAudioIndex[wma->dwCurrAudioBlock].dwSize);

	waveHdr[whidx].dwFlags &= ~WHDR_DONE;
	waveHdr[whidx].dwBufferLength = wma->lpAudioIndex[wma->dwCurrAudioBlock].dwSize;
	waveOutWrite(wma->hWave, &waveHdr[whidx], sizeof(WAVEHDR));
	wma->dwCurrAudioBlock++;
    }
    InterlockedIncrement(&wma->dwEventCount);
}

LRESULT MCIAVI_PaintFrame(WINE_MCIAVI* wma, HDC hDC)
{
    void* 		pBitmapData = NULL;
    LPBITMAPINFO	pBitmapInfo = NULL;
    HDC 		hdcMem;
    HBITMAP		hbmOld;
    int 		nWidth;
    int 		nHeight;

    if (!hDC || !wma->inbih)
	return TRUE;

    TRACE("Painting frame %lu\n", wma->dwCurrVideoFrame);

    if (wma->hic) {
        pBitmapData = wma->outdata;
        pBitmapInfo = (LPBITMAPINFO)wma->outbih;

        nWidth = wma->outbih->biWidth;
        nHeight = wma->outbih->biHeight;
    } else {
        pBitmapData = wma->indata;
        pBitmapInfo = (LPBITMAPINFO)wma->inbih;

        nWidth = wma->inbih->biWidth;
        nHeight = wma->inbih->biHeight;
    }

    if (!wma->hbmFrame)
        wma->hbmFrame = CreateCompatibleBitmap(hDC, nWidth, nHeight);

    SetDIBits(hDC, wma->hbmFrame, 0, nHeight, pBitmapData, pBitmapInfo, DIB_RGB_COLORS);

    hdcMem = CreateCompatibleDC(hDC);
    hbmOld = SelectObject(hdcMem, wma->hbmFrame);

    BitBlt(hDC, 0, 0, nWidth, nHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, hbmOld);
    DeleteDC(hdcMem);
    return TRUE;
}

LRESULT MCIAVI_DrawFrame(WINE_MCIAVI* wma)
{
    HDC		hDC;

    TRACE("Drawing frame %lu\n", wma->dwCurrVideoFrame);

    if (!wma->lpVideoIndex[wma->dwCurrVideoFrame].dwOffset)
	return FALSE;

    EnterCriticalSection(&wma->cs);

    mmioSeek(wma->hFile, wma->lpVideoIndex[wma->dwCurrVideoFrame].dwOffset, SEEK_SET);
    mmioRead(wma->hFile, wma->indata, wma->lpVideoIndex[wma->dwCurrVideoFrame].dwSize);

    /* FIXME ? */
    wma->inbih->biSizeImage = wma->lpVideoIndex[wma->dwCurrVideoFrame].dwSize;

    if (wma->hic &&
	ICDecompress(wma->hic, 0, wma->inbih, wma->indata,
		     wma->outbih, wma->outdata) != ICERR_OK) {
	LeaveCriticalSection(&wma->cs);
	WARN("Decompression error\n");
	return FALSE;
    }

    if (IsWindowVisible(wma->hWnd) && (hDC = GetDC(wma->hWnd)) != 0) {
	MCIAVI_PaintFrame(wma, hDC);
	ReleaseDC(wma->hWnd, hDC);
    }

    LeaveCriticalSection(&wma->cs);

    return TRUE;
}

