Mojn,
Ostatnimi czasy interesuję się tworzeniem aplikacji typu Remote Desktop. Dzisiaj traf padł na kompresję strumienia video za pomocą kodeka xvid, no i zatrzymał mnie dziwny problem polegający na segfaulcie w kodeku (w jego części napisanej w SSE) - przy szerokości bitmapy wejściowej większej od 1020 pixeli kodek usiłuje odczytać conajmiej jeden pixel za dużo - dokładnie z adresuicc.lpInput + biIn->biSizeImage
Dzieje się to w WriteFrames->DriverProc(ICM_COMPRESS).
Nazwałem to dziwnym, ponieważ kilka dni temu (dzisiaj też) używając tego samego kodeka i interfejsu IAVIFile + IAVIStream byłem w stanie bez problemu kompresować bitmapę 1280*768 do pliku avi.
Poniższy kod jest tworem doświadczalnym, używa pliku jako transportu strumienia video jest synchroniczny - najpierw record około 5 sekund 10 FPS, później play. Jak pewnie widać - rozmawiam z kodekiem bezpośrednio.
Rozmiar bitmapy jest ustawiany w funkcji Compress -> GetSystemMetrics. Przyznam że nie wiem jakie są ograniczenia i przypuszczam że wielkie bitmapy należy dzielić, co by było absurdalne.
#include <windows.h>
#include <vfw.h>
#pragma warning(disable:4311)
#pragma warning(disable:4312)
typedef struct CODEC CODEC;
typedef LONG (__stdcall *DP)(CODEC* codec, HDRVR hdrvr, UINT msg, LONG lParam1, LONG lParam2);
DP DriverProc;
#define hdrvr (HDRVR)0
struct FILECHUNK
{
DWORD framelen;
DWORD dwFlags;
DWORD ckid;
};
// file: [BITMAPINFOHEADER video]
// [BITMAPINFOHEADER compressed]
// [DWORD maxframelen]
// repeat([FILECHUNK][char frame])
void Decompress();
BOOL Compress();
int main()
{
HMODULE hMod = LoadLibrary(TEXT("xvidvfw.dll"));
if (!hMod)
return MessageBox(0, TEXT("Unable to load xvidvfw.dll"),0,0);
DriverProc = (DP)GetProcAddress(hMod, "DriverProc");
if (!DriverProc)
return MessageBox(0, TEXT("Unable to find DriverProc function in xvidvfw.dll"),0,0);
if (Compress())
{
if (MessageBox(0, TEXT("kliknij OK aby odtworzyć video"), 0, MB_TOPMOST|MB_YESNO) == IDYES)
Decompress();
}
return 0;
}
// czyta dane z nośnika (pliku) i rysuje na desktopie; 10 FPS
void Decompress()
{
ICINFO ici;
if (!DriverProc(0, hdrvr, ICM_GETINFO, (LONG)&ici, sizeof(ici)))
MessageBox(0, TEXT("ICM_GETINFO failed"),0,0);
ICOPEN icop;
icop.fccType = ici.fccType; // wg żródeł vxid - to wystarczy
CODEC *codec = (CODEC*)DriverProc(0, hdrvr, DRV_OPEN, 0, (LONG)&icop);
if (!codec)
{
MessageBox(0, TEXT("DRV_OPEN failed"),0,0);
}
else
{
DWORD cch;
BITMAPINFO biIn; // compressed
BITMAPINFO biOut; // uncompressed
HANDLE hFile = CreateFile(TEXT("c:\\xvidcap.bin"), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
// read in reversed order
ReadFile(hFile, &biOut, sizeof(BITMAPINFOHEADER), &cch, 0);
ReadFile(hFile, &biIn, sizeof(BITMAPINFOHEADER), &cch, 0);
if (DriverProc(codec, hdrvr, ICM_DECOMPRESS_BEGIN, (LONG)&biIn,
(LONG)&biOut) != ICERR_OK)
{
MessageBox(0, TEXT("ICM_DECOMPRESS_BEGIN failed"),0,0);
}
else
{
DWORD dwCompressBufferSize;
ReadFile(hFile, &dwCompressBufferSize, 4, &cch, 0);
ICDECOMPRESS icd;
ZeroMemory(&icd, sizeof(icd));
//icd.dwFlags = ;
icd.lpbiInput = &biIn.bmiHeader;
icd.lpInput = VirtualAlloc(0, dwCompressBufferSize,
MEM_COMMIT, PAGE_READWRITE);
icd.lpbiOutput = &biOut.bmiHeader;
//icd.lpOutput = <- CreateDIBSection
//icd.ckid = ;
HDC dcDesktop = GetDC(0);
HDC cdc = CreateCompatibleDC(dcDesktop);
HANDLE dib = SelectObject(cdc, CreateDIBSection(dcDesktop, &biOut,
DIB_RGB_COLORS, &icd.lpOutput, 0, 0));
while (1)
{
int time = (int)GetTickCount();
FILECHUNK fc;
if (!ReadFile(hFile, &fc, sizeof(fc), &cch, 0) ||
(cch != sizeof(fc))) break;
if (!ReadFile(hFile, icd.lpInput, fc.framelen, &cch, 0) ||
(cch != fc.framelen)) break;
icd.dwFlags = fc.dwFlags;
icd.ckid = fc.ckid;
if (DriverProc(codec, hdrvr, ICM_DECOMPRESS, (LONG)&icd, sizeof(icd)) != ICERR_OK)
{
MessageBox(0, TEXT("ICM_DECOMPRESS failed"),0,0);
break;
}
time = 100 - (GetTickCount() - time);
if (time > 0) Sleep((DWORD)time);
BitBlt(dcDesktop, 0, 0, biIn.bmiHeader.biWidth, biIn.bmiHeader.biWidth,
cdc, 0, 0, SRCCOPY);
}
VirtualFree(icd.lpInput, 0, MEM_RELEASE);
DeleteObject(SelectObject(cdc, dib));
DeleteDC(cdc);
ReleaseDC(0, dcDesktop);
DriverProc(codec, hdrvr, ICM_DECOMPRESS_END, 0, 0);
}
DriverProc(codec, hdrvr, DRV_CLOSE, 0, (LONG)&icop);
}
}
BOOL WriteFrames(CODEC* codec,BITMAPINFOHEADER* biIn,BITMAPINFOHEADER* biOut);
BOOL Compress()
{
BOOL ok = false;
ICINFO ici;
if (!DriverProc(0, hdrvr, ICM_GETINFO, (LONG)&ici, sizeof(ici)))
MessageBox(0, TEXT("ICM_GETINFO failed"),0,0);
ICOPEN icop;
icop.fccType = ici.fccType; // wg żródeł vxid - to wystarczy
CODEC *codec = (CODEC*)DriverProc(0, hdrvr, DRV_OPEN, 0, (LONG)&icop);
if (!codec)
{
MessageBox(0, TEXT("DRV_OPEN failed"),0,0);
}
else
{
if (DriverProc(codec, hdrvr, ICM_CONFIGURE, 0, 0) == ICERR_OK)
{
BITMAPINFO biIn;
BITMAPINFO biOut;
ZeroMemory(&biIn, sizeof(biIn));
biIn.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
biIn.bmiHeader.biPlanes = 1;
biIn.bmiHeader.biWidth = GetSystemMetrics(SM_CXSCREEN); // 1020 max
biIn.bmiHeader.biHeight = GetSystemMetrics(SM_CYSCREEN);
biIn.bmiHeader.biBitCount = 24;
biIn.bmiHeader.biSizeImage = ((biIn.bmiHeader.biWidth+3)&0xfffffffc) *
biIn.bmiHeader.biHeight * 3;
if (DriverProc(codec, hdrvr, ICM_COMPRESS_GET_FORMAT, (LONG)&biIn,
(LONG)&biOut) != ICERR_OK)
{
MessageBox(0, TEXT("ICM_COMPRESS_GET_FORMAT failed"),0,0);
}
else
{
if (DriverProc(codec, hdrvr, ICM_COMPRESS_QUERY, (LONG)&biIn,
(LONG)&biOut) != ICERR_OK)
MessageBox(0, TEXT("ICM_COMPRESS_QUERY failed"),0,0);
if (DriverProc(codec, hdrvr, ICM_COMPRESS_BEGIN,
(LONG)&biIn.bmiHeader, (LONG)&biOut.bmiHeader) != ICERR_OK)
{
MessageBox(0, TEXT("ICM_COMPRESS_BEGIN failed"),0,0);
}
else
{
ok = WriteFrames(codec, &biIn.bmiHeader, &biOut.bmiHeader);
DriverProc(codec, hdrvr, ICM_COMPRESS_END, (LONG)&biIn,
(LONG)&biOut);
}
}
}
DriverProc(codec, hdrvr, DRV_CLOSE, 0, (LONG)&icop);
}
return ok;
}
BOOL WriteFrames(CODEC* codec,BITMAPINFOHEADER* biIn,BITMAPINFOHEADER* biOut)
{
BOOL ok = true;
FILECHUNK fc;
DWORD dwCompressBufferSize = DriverProc(codec, hdrvr, ICM_COMPRESS_GET_SIZE,
(LONG)biIn, (LONG)biOut);
ICCOMPRESS icc;
DWORD cch;
ZeroMemory(&icc, sizeof(icc));
icc.dwFlags = ICCOMPRESS_KEYFRAME;
icc.lpckid = &fc.ckid;
icc.lpdwFlags = &fc.dwFlags;
icc.lFrameNum = 1;
icc.dwFrameSize= biIn->biSizeImage;
//icc.lFrameNum = <- for {}
icc.lpbiOutput = biOut;
icc.lpOutput = VirtualAlloc(0, dwCompressBufferSize, MEM_COMMIT, PAGE_READWRITE);
icc.lpbiInput = biIn;
//icc.lpInput = ; <- CreateDIBSection
icc.lpbiPrev = biIn;
// niech plik będzie nośnikiem
HANDLE hFile = CreateFile(TEXT("c:\\xvidcap.bin"), GENERIC_WRITE, FILE_SHARE_READ,
0, CREATE_ALWAYS, 0, 0);
WriteFile(hFile, biIn, sizeof(BITMAPINFOHEADER), &cch, 0);
WriteFile(hFile, biOut, sizeof(BITMAPINFOHEADER), &cch, 0);
WriteFile(hFile, &dwCompressBufferSize, 4, &cch, 0);
HDC dcDesktop = GetDC(0);
HDC cdc = CreateCompatibleDC(dcDesktop);
//FIX biIn->biHeight++;
HANDLE dib = SelectObject(cdc, CreateDIBSection(dcDesktop, (BITMAPINFO*)biIn,
DIB_RGB_COLORS, (void**)&icc.lpInput, 0, 0));
//FIX biIn->biHeight--;
for (icc.lFrameNum=0; icc.lFrameNum<50; icc.lFrameNum++)
{
int time = (int)GetTickCount();
BitBlt(cdc, 0, 0, biIn->biWidth, biIn->biHeight,
dcDesktop, 0, 0, SRCCOPY | CAPTUREBLT);
icc.lpbiOutput->biSizeImage = dwCompressBufferSize;
__try
{
if (DriverProc(codec, hdrvr, ICM_COMPRESS, (LONG)&icc, sizeof(icc)) != ICERR_OK)
{
ok = false;
MessageBox(0, TEXT("ICM_COMPRESS failed"),0,0);
break;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ok = false;
MessageBox(0, TEXT("exception in ICM_COMPRESS"),0,0);
break;
}
// compressed: icc.lpOutput
// size: icc.lpbiOutput->biSizeImage
fc.framelen = icc.lpbiOutput->biSizeImage;
WriteFile(hFile, &fc, sizeof(fc), &cch, 0);
WriteFile(hFile, icc.lpOutput, icc.lpbiOutput->biSizeImage, &cch, 0);
time = 100 - (GetTickCount() - time);
if (time > 0) Sleep((DWORD)time);
}
CloseHandle(hFile);
VirtualFree(icc.lpOutput, 0, MEM_RELEASE);
DeleteObject(SelectObject(cdc, dib));
DeleteDC(cdc);
ReleaseDC(0, dcDesktop);
return ok;
}
EDIT: CreateDIBSection z jedną zapasową linią załatwia sprawę. Identyczny bug jest w 3ivxVfWCodec.dll.
Poprawki dodałem jako //FIX.