#if _MSC_VER >= 1400
#define _CRT_SECURE_NO_DEPRECATE 1
#endif
#define _WIN32_DCOM
#define _WIN32_WINNT 0x0500
#include "tsystem.h"
#include <windows.h>
#include <objbase.h>
#include "texception.h"
#include "tiio_avi.h"
#include "tsound.h"
#include "tconvert.h"
#include "timageinfo.h"
#include "trasterimage.h"
//------------------------------------------------------------------------------
namespace
{
#define DibPtr(lpbi) (LPBYTE)(DibColors(lpbi) + (UINT)(lpbi)->biClrUsed)
#define DibColors(lpbi) ((LPRGBQUAD)((LPBYTE)(lpbi) + (int)(lpbi)->biSize))
//-----------------------------------------------------------
void WideChar2Char(LPCWSTR wideCharStr, char *str, int strBuffSize)
{
WideCharToMultiByte(CP_ACP, 0, wideCharStr, -1, str, strBuffSize, 0, 0);
}
//------------------------------------------------------------------------------
std::string buildAVIExceptionString(int rc)
{
switch (rc) {
CASE AVIERR_BADFORMAT : return "The file couldn't be read, indicating a corrupt file or an unrecognized format.";
CASE AVIERR_MEMORY : return "The file could not be opened because of insufficient memory.";
CASE AVIERR_FILEREAD : return "A disk error occurred while reading the file.";
CASE AVIERR_FILEOPEN : return "A disk error occurred while opening the file.";
CASE REGDB_E_CLASSNOTREG : return "According to the registry, the type of file specified in m_aviFileOpen does not have a handler to process it.";
CASE AVIERR_UNSUPPORTED : return "Format unsupported";
CASE AVIERR_INTERNAL : return "Internal error";
CASE AVIERR_NODATA : return "No data";
DEFAULT:
return "Unable to create avi.";
}
}
//------------------------------------------------------------------------------
std::string SplitFourCC(DWORD fcc)
{
std::string s;
s += (char((fcc & 0x000000ff) >> 0));
s += (char((fcc & 0x0000ff00) >> 8));
s += (char((fcc & 0x00ff0000) >> 16));
s += (char((fcc & 0xff000000) >> 24));
return s;
}
//-----------------------------------------------------------
TRaster32P DIBToRaster(UCHAR *pDIBImage, int width, int height)
{
assert(pDIBImage);
if (!pDIBImage)
return TRaster32P();
//BITMAPINFOHEADER *bihdr = reinterpret_cast<BITMAPINFOHEADER *>(pDIBImage);
UCHAR *rawData = pDIBImage;
TRaster32P rasOut32(width, height);
//ULONG imgSize = bihdr->biSizeImage;
int ly = rasOut32->getLy();
int n = 0;
rasOut32->lock();
while (n < ly) {
TPixel32 *pix = rasOut32->pixels(n);
TPixel32 *endPix = pix + rasOut32->getLx();
while (pix < endPix) {
pix->b = *rawData;
rawData++;
pix->g = *rawData;
rawData++;
pix->r = *rawData;
rawData++;
pix->m = 255;
++pix;
//rawData += 3;
}
++n;
}
rasOut32->unlock();
return rasOut32;
}
//-----------------------------------------------------------
int getPrevKeyFrame(PAVISTREAM videoStream, int index)
{
return AVIStreamFindSample(videoStream, index, FIND_PREV | FIND_KEY);
}
//-----------------------------------------------------------
bool isAKeyFrame(PAVISTREAM videoStream, int index)
{
return index == getPrevKeyFrame(videoStream, index);
}
}; //end of namespace
//===========================================================
//
// TImageWriterAvi
//
//===========================================================
class TImageWriterAvi : public TImageWriter
{
public:
int m_frameIndex;
TImageWriterAvi(const TFilePath &path, int frameIndex, TLevelWriterAvi *lwa)
: TImageWriter(path), m_frameIndex(frameIndex), m_lwa(lwa)
{
m_lwa->addRef();
}
~TImageWriterAvi() { m_lwa->release(); }
bool is64bitOutputSupported() { return false; }
void save(const TImageP &img) { m_lwa->save(img, m_frameIndex); }
private:
TLevelWriterAvi *m_lwa;
//not implemented
TImageWriterAvi(const TImageWriterAvi &);
TImageWriterAvi &operator=(const TImageWriterAvi &src);
};
//===========================================================
//
// TLevelWriterAvi;
//
//===========================================================
#ifdef _WIN32
TLevelWriterAvi::TLevelWriterAvi(const TFilePath &path, TPropertyGroup *winfo)
: TLevelWriter(path, winfo), m_aviFile(0), m_videoStream(0), m_audioStream(0), m_bitmapinfo(0), m_outputFmt(0), m_hic(0), m_initDone(false), IOError(0), m_st(0), m_bpp(32), m_maxDataSize(0), m_buffer(0), m_firstframe(-1)
{
CoInitializeEx(0, COINIT_MULTITHREADED);
m_frameRate = 12.;
AVIFileInit();
if (TSystem::doesExistFileOrLevel(m_path))
TSystem::deleteFile(m_path);
int rc = AVIFileOpenW(&m_aviFile, m_path.getWideString().c_str(), OF_CREATE | OF_WRITE, 0);
if (rc != 0)
throw TImageException(getFilePath(), "Unable to create file");
m_delayedFrames.clear();
}
//-----------------------------------------------------------
TLevelWriterAvi::~TLevelWriterAvi()
{
//controllo che non ci siano ancora dei frame in coda nel codec (alcuni codec sono asincroni!)
while (!m_delayedFrames.empty()) {
LONG lSampWritten, lBytesWritten;
int frameIndex = m_delayedFrames.front();
DWORD flagsOut = 0;
DWORD flagsIn = !frameIndex ? AVIIF_KEYFRAME : 0;
BITMAPINFOHEADER outHeader;
void *bufferOut = 0;
int res = compressFrame(&outHeader, &bufferOut, frameIndex, flagsIn, flagsOut);
if (res != ICERR_OK) {
if (bufferOut)
_aligned_free(bufferOut);
break;
}
if (AVIStreamWrite(m_videoStream, frameIndex, 1, bufferOut,
outHeader.biSizeImage,
flagsOut,
&lSampWritten, &lBytesWritten)) {
if (bufferOut)
_aligned_free(bufferOut);
break;
}
m_delayedFrames.pop_front();
if (bufferOut)
_aligned_free(bufferOut);
}
if (m_st) {
doSaveSoundTrack();
m_st = 0;
}
if (m_hic) {
ICCompressEnd(m_hic);
ICClose(m_hic);
}
if (m_bitmapinfo)
free(m_bitmapinfo);
if (m_outputFmt)
free(m_outputFmt);
if (m_videoStream)
AVIStreamClose(m_videoStream);
if (m_audioStream)
AVIStreamClose(m_audioStream);
if (m_aviFile)
AVIFileClose(m_aviFile);
AVIFileExit();
CoUninitialize();
if (!m_delayedFrames.empty())
throw TImageException(getFilePath(), "error compressing frame " + toString(m_delayedFrames.front()));
}
//-----------------------------------------------------------
void TLevelWriterAvi::searchForCodec()
{
if (!m_properties)
m_properties = new Tiio::AviWriterProperties();
TEnumProperty *p = (TEnumProperty *)m_properties->getProperty("Codec");
assert(p);
std::wstring codecName = p->getValue();
//-------- // cerco compressorName fra i codec
HIC hic = 0;
ICINFO icinfo;
memset(&icinfo, 0, sizeof(ICINFO));
bool found = false;
if (codecName != L"Uncompressed") {
char descr[2048], name[2048];
DWORD fccType = 0;
BITMAPINFO inFmt;
memset(&inFmt, 0, sizeof(BITMAPINFO));
inFmt.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
inFmt.bmiHeader.biWidth = inFmt.bmiHeader.biHeight = 100;
inFmt.bmiHeader.biPlanes = 1;
inFmt.bmiHeader.biCompression = BI_RGB;
found = false;
for (int bpp = 32; (bpp >= 24) && !found; bpp -= 8) {
inFmt.bmiHeader.biBitCount = bpp;
for (int i = 0; ICInfo(fccType, i, &icinfo); i++) {
hic = ICOpen(icinfo.fccType, icinfo.fccHandler, ICMODE_COMPRESS);
ICGetInfo(hic, &icinfo, sizeof(ICINFO)); // Find out the compressor name
WideChar2Char(icinfo.szDescription, descr, sizeof(descr));
WideChar2Char(icinfo.szName, name, sizeof(name));
std::string compressorName;
compressorName = std::string(name) + " '" + toString(bpp) + "' " + std::string(descr);
if (hic) {
if (ICCompressQuery(hic, &inFmt, NULL) != ICERR_OK) {
ICClose(hic);
continue; // Skip this compressor if it can't handle the format.
}
if (toWideString(compressorName) == codecName) {
found = true;
m_bpp = bpp;
break;
}
ICClose(hic);
}
}
}
}
m_hic = hic;
}
//-----------------------------------------------------------
void TLevelWriterAvi::createBitmap(int lx, int ly)
{
const RGBQUAD NOMASK = {0x00, 0x00, 0x00, 0x00};
m_bitmapinfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFOHEADER) + 255 * sizeof(RGBQUAD));
m_bitmapinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_bitmapinfo->bmiHeader.biWidth = lx;
m_bitmapinfo->bmiHeader.biHeight = ly;
m_bitmapinfo->bmiHeader.biPlanes = 1;
m_bitmapinfo->bmiHeader.biBitCount = m_bpp;
m_bitmapinfo->bmiHeader.biCompression = BI_RGB;
m_bitmapinfo->bmiHeader.biSizeImage = lx * ly * m_bpp / 8;
m_bitmapinfo->bmiHeader.biXPelsPerMeter = 0;
m_bitmapinfo->bmiHeader.biYPelsPerMeter = 0;
m_bitmapinfo->bmiHeader.biClrUsed = 0;
m_bitmapinfo->bmiHeader.biClrImportant = 0;
m_bitmapinfo->bmiColors[0] = NOMASK;
m_bitmapinfo->bmiColors[1] = NOMASK;
m_bitmapinfo->bmiColors[2] = NOMASK;
m_buffer = _aligned_malloc(m_bitmapinfo->bmiHeader.biSizeImage, 128);
}
//-----------------------------------------------------------
TImageWriterP TLevelWriterAvi::getFrameWriter(TFrameId fid)
{
if (IOError != 0)
throw TImageException(m_path, buildAVIExceptionString(IOError));
if (fid.getLetter() != 0)
return TImageWriterP(0);
int index = fid.getNumber() - 1;
TImageWriterAvi *iwa = new TImageWriterAvi(m_path, index, this);
return TImageWriterP(iwa);
}
//-----------------------------------------------------------
void TLevelWriterAvi::save(const TImageP &img, int frameIndex)
{
CoInitializeEx(0, COINIT_MULTITHREADED);
if (m_firstframe < 0)
m_firstframe = frameIndex;
int index = frameIndex - m_firstframe;
TRasterImageP image(img);
int lx = image->getRaster()->getLx();
int ly = image->getRaster()->getLy();
int pixelSize = image->getRaster()->getPixelSize();
if (pixelSize != 4)
throw TImageException(getFilePath(), "Unsupported pixel type");
QMutexLocker sl(&m_mutex);
if (!m_initDone) {
searchForCodec();
createBitmap(lx, ly);
DWORD num = DWORD(tround(m_frameRate * 100.0));
DWORD den = 100;
AVISTREAMINFO psi;
memset(&psi, 0, sizeof(AVISTREAMINFO));
psi.fccType = streamtypeVIDEO;
if (m_hic) {
ICINFO icinfo;
ICGetInfo(m_hic, &icinfo, sizeof(ICINFO));
psi.fccHandler = icinfo.fccHandler;
}
psi.dwScale = den;
psi.dwRate = num;
psi.dwQuality = ICQUALITY_DEFAULT;
psi.rcFrame.right = lx;
psi.rcFrame.bottom = ly;
::strcpy(psi.szName, m_path.getName().c_str());
int rc;
if (AVIFileCreateStream(m_aviFile, &(m_videoStream), &(psi)))
throw TImageException(getFilePath(), "Unable to create video stream");
if (!m_hic) {
if (AVIStreamSetFormat(m_videoStream, 0, &m_bitmapinfo->bmiHeader, m_bitmapinfo->bmiHeader.biSize))
throw TImageException(getFilePath(), "unable to set format");
} else {
m_outputFmt = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFOHEADER) + 255 * sizeof(RGBQUAD));
m_outputFmt->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
rc = ICCompressGetFormat(m_hic, m_bitmapinfo, m_outputFmt);
if (rc != ICERR_OK)
throw TImageException(getFilePath(),
"Error codec (ec = " + toString(rc) + ")");
ICCOMPRESSFRAMES icf;
memset(&icf, 0, sizeof icf);
icf.dwFlags = (DWORD)&icf.lKeyRate;
icf.lStartFrame = index;
icf.lFrameCount = 0;
icf.lQuality = ICQUALITY_DEFAULT;
icf.lDataRate = 0;
icf.lKeyRate = (DWORD)(num / den);
icf.dwRate = num;
icf.dwScale = den;
ICSendMessage(m_hic, ICM_COMPRESS_FRAMES_INFO, (WPARAM)&icf, sizeof(ICCOMPRESSFRAMES));
m_maxDataSize = ICCompressGetSize(m_hic, m_bitmapinfo, m_outputFmt);
rc = ICCompressBegin(m_hic, m_bitmapinfo, m_outputFmt);
if (rc != ICERR_OK)
throw TImageException(getFilePath(),
"Error starting codec (ec = " + toString(rc) + ")");
if (AVIStreamSetFormat(m_videoStream, 0, &m_outputFmt->bmiHeader, m_outputFmt->bmiHeader.biSize))
throw TImageException(getFilePath(), "unable to set format");
}
m_initDone = true;
}
// copio l'immagine nella bitmap che ho creato in createBitmap
image->getRaster()->lock();
void *buffin = image->getRaster()->getRawData();
assert(buffin);
TRasterGR8P raux;
if (m_bpp == 24) {
int newlx = lx * 3;
raux = TRasterGR8P(newlx, ly);
raux->lock();
UCHAR *buffout24 = (UCHAR *)raux->getRawData(); //new UCHAR[newlx*ly];
TPixel *buffin32 = (TPixel *)buffin;
buffin = buffout24;
for (int i = 0; i < ly; i++) {
UCHAR *pix = buffout24 + newlx * i;
for (int j = 0; j < lx; j++, buffin32++) {
*pix++ = buffin32->b;
*pix++ = buffin32->g;
*pix++ = buffin32->r;
}
}
raux->unlock();
}
memcpy(m_buffer, buffin, lx * ly * m_bpp / 8);
image->getRaster()->unlock();
raux = TRasterGR8P();
LONG lSampWritten, lBytesWritten;
lSampWritten = 0;
lBytesWritten = 0;
if (!m_hic) {
if (AVIStreamWrite(m_videoStream, index, 1, m_buffer,
m_bitmapinfo->bmiHeader.biSizeImage,
AVIIF_KEYFRAME,
&lSampWritten, &lBytesWritten)) {
throw TImageException(getFilePath(), "error writing frame");
}
} else {
BITMAPINFOHEADER outHeader;
void *bufferOut = 0;
DWORD flagsOut = 0;
DWORD flagsIn = !index ? ICCOMPRESS_KEYFRAME : 0;
int res = compressFrame(&outHeader, &bufferOut, index, flagsIn, flagsOut);
if (res != ICERR_OK) {
if (bufferOut)
_aligned_free(bufferOut);
throw TImageException(getFilePath(), "error compressing frame " + toString(index));
}
if (outHeader.biCompression == '05xd' ||
outHeader.biCompression == '05XD' ||
outHeader.biCompression == 'divx' ||
outHeader.biCompression == 'DIVX')
if (outHeader.biSizeImage == 1 && *(char *)bufferOut == 0x7f) {
m_delayedFrames.push_back(index);
if (bufferOut)
_aligned_free(bufferOut);
return;
}
if (!m_delayedFrames.empty()) {
m_delayedFrames.push_back(index);
index = m_delayedFrames.front();
m_delayedFrames.pop_front();
}
if (AVIStreamWrite(m_videoStream, index, 1, bufferOut,
outHeader.biSizeImage,
!index ? flagsOut : 0,
&lSampWritten, &lBytesWritten)) {
if (bufferOut)
_aligned_free(bufferOut);
throw TImageException(getFilePath(), "error writing frame");
}
if (bufferOut)
_aligned_free(bufferOut);
}
}
//-----------------------------------------------------------
int TLevelWriterAvi::compressFrame(BITMAPINFOHEADER *outHeader, void **bufferOut,
int frameIndex, DWORD flagsIn, DWORD &flagsOut)
{
*bufferOut = _aligned_malloc(m_maxDataSize, 128);
*outHeader = m_outputFmt->bmiHeader;
DWORD chunkId = 0;
if (flagsIn)
flagsOut = AVIIF_KEYFRAME;
int res = ICCompress(m_hic, flagsIn,
outHeader, *bufferOut,
&m_bitmapinfo->bmiHeader, m_buffer,
&chunkId, &flagsOut,
frameIndex, frameIndex ? 0 : 0xFFFFFF,
0, NULL, NULL);
return res;
}
#else
TLevelWriterAvi::TLevelWriterAvi(const TFilePath &path)
: TLevelWriter(path)
{
}
TLevelWriterAvi::~TLevelWriterAvi() {}
TImageWriterP TLevelWriterAvi::getFrameWriter(TFrameId)
{
throw TImageException(getFilePath(), "not supported");
return TImageWriterP(iwa);
}
void TImageWriterAvi::save(const TImageP &) { throw TImageException(m_path, "AVI file format not supported"); }
#endif
//-----------------------------------------------------------
void TLevelWriterAvi::saveSoundTrack(TSoundTrack *st)
{
if (!m_aviFile)
throw TException("unable to save soundtrack");
if (!st)
throw TException("null reference to soundtrack");
m_st = st; // prima devo aver salvato tutto il video, salvo l'audio alla fine!
}
//-----------------------------------------------------------
void TLevelWriterAvi::doSaveSoundTrack()
{
WAVEFORMATEX waveinfo;
int ret;
LONG lSampWritten, lBytesWritten;
int rc;
rc = FALSE;
AVISTREAMINFO audioStreamInfo;
memset(&audioStreamInfo, 0, sizeof(AVISTREAMINFO));
audioStreamInfo.fccType = streamtypeAUDIO;
audioStreamInfo.fccHandler = 0;
audioStreamInfo.dwFlags = 0;
audioStreamInfo.dwCaps = 0;
audioStreamInfo.wPriority = 0;
audioStreamInfo.wLanguage = 0;
audioStreamInfo.dwScale = 1;
audioStreamInfo.dwRate = m_st->getSampleRate();
audioStreamInfo.dwStart = 0;
audioStreamInfo.dwLength = 0;
audioStreamInfo.dwInitialFrames = 0;
audioStreamInfo.dwSuggestedBufferSize = 0;
audioStreamInfo.dwQuality = (ULONG)-1;
audioStreamInfo.dwSampleSize = 0;
audioStreamInfo.rcFrame.left = 0;
audioStreamInfo.rcFrame.top = 0;
audioStreamInfo.rcFrame.right = 0;
audioStreamInfo.rcFrame.bottom = 0;
audioStreamInfo.dwEditCount = 0;
audioStreamInfo.dwFormatChangeCount = 0;
audioStreamInfo.szName[0] = 0;
waveinfo.wFormatTag = WAVE_FORMAT_PCM; //WAVE_FORMAT_DRM
waveinfo.nChannels = m_st->getChannelCount();
waveinfo.nSamplesPerSec = m_st->getSampleRate();
waveinfo.wBitsPerSample = m_st->getBitPerSample();
waveinfo.nBlockAlign = waveinfo.nChannels * waveinfo.wBitsPerSample >> 3;
waveinfo.nAvgBytesPerSec = waveinfo.nSamplesPerSec * waveinfo.nBlockAlign;
waveinfo.cbSize = sizeof(WAVEFORMATEX);
const UCHAR *buffer = m_st->getRawData();
if (AVIFileCreateStream(m_aviFile, &m_audioStream, &audioStreamInfo))
throw TException("error creating soundtrack stream");
if (AVIStreamSetFormat(m_audioStream, 0, &waveinfo, sizeof(WAVEFORMATEX)))
throw TException("error setting soundtrack format");
LONG count = m_st->getSampleCount();
LONG bufSize = m_st->getSampleCount() * m_st->getSampleSize();
if (ret = AVIStreamWrite(m_audioStream, 0, count, (char *)buffer, bufSize, AVIIF_KEYFRAME,
&lSampWritten, &lBytesWritten))
throw TException("error writing soundtrack samples");
}
//===========================================================
//
// TImageReaderAvi
//
//===========================================================
class TImageReaderAvi : public TImageReader
{
public:
int m_frameIndex;
TImageReaderAvi(const TFilePath &path, int index, TLevelReaderAvi *lra)
: TImageReader(path), m_lra(lra), m_frameIndex(index)
{
m_lra->addRef();
}
~TImageReaderAvi() { m_lra->release(); }
TImageP load() { return m_lra->load(m_frameIndex); }
TDimension getSize() const { return m_lra->getSize(); }
TRect getBBox() const { return TRect(); }
private:
TLevelReaderAvi *m_lra;
//not implemented
TImageReaderAvi(const TImageReaderAvi &);
TImageReaderAvi &operator=(const TImageReaderAvi &src);
};
//===========================================================
//
// TLevelReaderAvi
//
//===========================================================
#ifdef _WIN32
TLevelReaderAvi::TLevelReaderAvi(const TFilePath &path)
: TLevelReader(path)
#ifdef _WIN32
,
m_srcBitmapInfo(0), m_dstBitmapInfo(0), m_hic(0), IOError(0), m_prevFrame(-1), m_decompressedBuffer(0)
#endif
{
CoInitializeEx(0, COINIT_MULTITHREADED);
AVIFileInit();
int rc = AVIStreamOpenFromFileW(&m_videoStream, path.getWideString().c_str(), streamtypeVIDEO, 0, OF_READ, 0);
if (rc != 0) {
IOError = rc;
throw TImageException(m_path, buildAVIExceptionString(IOError));
}
LONG size = sizeof(BITMAPINFO);
m_srcBitmapInfo = (BITMAPINFO *)calloc(1, size);
m_dstBitmapInfo = (BITMAPINFO *)calloc(1, size);
rc = AVIStreamReadFormat(m_videoStream, 0, m_srcBitmapInfo, &size);
if (rc)
throw TImageException(getFilePath(), "error reading info.");
*m_dstBitmapInfo = *m_srcBitmapInfo;
m_dstBitmapInfo->bmiHeader.biCompression = 0;
AVISTREAMINFO si;
rc = AVIStreamInfo(m_videoStream, &si, sizeof(AVISTREAMINFO));
if (rc)
throw TImageException(getFilePath(), "error reading info.");
m_info = new TImageInfo();
m_info->m_frameRate = si.dwRate / double(si.dwScale);
m_info->m_lx = si.rcFrame.right - si.rcFrame.left;
m_info->m_ly = si.rcFrame.bottom - si.rcFrame.top;
int bpp = m_srcBitmapInfo->bmiHeader.biBitCount;
switch (bpp) {
case 32: {
m_info->m_bitsPerSample = 8;
m_info->m_samplePerPixel = 4;
m_dstBitmapInfo->bmiHeader.biSizeImage =
m_dstBitmapInfo->bmiHeader.biWidth * m_dstBitmapInfo->bmiHeader.biHeight * 4;
break;
}
case 24: {
m_info->m_bitsPerSample = 8;
m_info->m_samplePerPixel = 3;
m_dstBitmapInfo->bmiHeader.biSizeImage =
m_dstBitmapInfo->bmiHeader.biWidth * m_dstBitmapInfo->bmiHeader.biHeight * 3;
break;
}
case 16: {
m_info->m_bitsPerSample = 8;
m_info->m_samplePerPixel = 2;
//chiedo al decoder di decomprimerla in un'immagine a 24 bit (sperando che lo permetta)
m_dstBitmapInfo->bmiHeader.biBitCount = 24;
m_dstBitmapInfo->bmiHeader.biSizeImage =
m_dstBitmapInfo->bmiHeader.biWidth * m_dstBitmapInfo->bmiHeader.biHeight * 3;
break;
}
default: {
throw TImageException(getFilePath(), "unable to find a decompressor for this format");
}
}
Tiio::AviWriterProperties *prop = new Tiio::AviWriterProperties();
m_info->m_properties = prop;
if (m_srcBitmapInfo->bmiHeader.biCompression != 0) {
//ci sono dei codec (es. Xvid) che non decomprimono bene dentro immagini a 32 bit.
m_dstBitmapInfo->bmiHeader.biBitCount = 24;
m_dstBitmapInfo->bmiHeader.biSizeImage =
m_dstBitmapInfo->bmiHeader.biWidth * m_dstBitmapInfo->bmiHeader.biHeight * 3;
ICINFO icinfo;
memset(&icinfo, 0, sizeof(ICINFO));
ICInfo(ICTYPE_VIDEO, si.fccHandler, &icinfo);
m_hic = ICOpen(icinfo.fccType, icinfo.fccHandler, ICMODE_DECOMPRESS);
if (!m_hic) {
m_hic = findCandidateDecompressor();
if (!m_hic)
throw TImageException(getFilePath(), "unable to find a decompressor for this format");
}
DWORD result = ICDecompressQuery(m_hic, m_srcBitmapInfo, m_dstBitmapInfo);
if (result != ICERR_OK)
throw TImageException(getFilePath(), "unable to find a decompressor for this format");
result = ICDecompressBegin(m_hic, m_srcBitmapInfo, m_dstBitmapInfo);
if (result != ICERR_OK)
throw TImageException(getFilePath(), "unable to initializate the decompressor");
char descr[2048], name[2048];
ICGetInfo(m_hic, &icinfo, sizeof(ICINFO)); // Find out the compressor name
WideChar2Char(icinfo.szDescription, descr, sizeof(descr));
WideChar2Char(icinfo.szName, name, sizeof(name));
std::string compressorName;
compressorName = std::string(name) + " '" + toString(m_dstBitmapInfo->bmiHeader.biBitCount) + "' " + std::string(descr);
TEnumProperty *p = (TEnumProperty *)m_info->m_properties->getProperty("Codec");
p->setValue(toWideString(compressorName));
m_decompressedBuffer = _aligned_malloc(m_dstBitmapInfo->bmiHeader.biSizeImage, 128);
} else {
m_hic = 0;
TEnumProperty *p = (TEnumProperty *)m_info->m_properties->getProperty("Codec");
p->setValue(L"Uncompressed");
m_decompressedBuffer = _aligned_malloc(si.dwSuggestedBufferSize, 128);
}
}
//------------------------------------------------
TLevelReaderAvi::~TLevelReaderAvi()
{
if (m_hic) {
ICDecompressEnd(m_hic);
ICClose(m_hic);
}
if (m_srcBitmapInfo)
free(m_srcBitmapInfo);
if (m_dstBitmapInfo)
free(m_dstBitmapInfo);
if (m_videoStream)
AVIStreamClose(m_videoStream);
AVIFileExit();
_aligned_free(m_decompressedBuffer);
}
HIC TLevelReaderAvi::findCandidateDecompressor()
{
ICINFO info = {0};
BITMAPINFO srcInfo = *m_srcBitmapInfo;
BITMAPINFO dstInfo = *m_dstBitmapInfo;
for (DWORD id = 0; ICInfo(ICTYPE_VIDEO, id, &info); ++id) {
info.dwSize = sizeof(ICINFO); // I don't think this is necessary, but just in case....
HIC hic = ICOpen(info.fccType, info.fccHandler, ICMODE_DECOMPRESS);
if (!hic)
continue;
DWORD result = ICDecompressQuery(hic, &srcInfo, &dstInfo);
if (result == ICERR_OK) {
// Check for a codec that doesn't actually support what it says it does.
// We ask the codec whether it can do a specific conversion that it can't
// possibly support. If it does it, then we call BS and ignore the codec.
// The Grand Tech Camera Codec and Panasonic DV codecs are known to do this.
//
// (general idea from Raymond Chen's blog)
BITMAPINFOHEADER testSrc = {// note: can't be static const since IsBadWritePtr() will get called on it
sizeof(BITMAPINFOHEADER),
320,
240,
1,
24,
0x2E532E42,
320 * 240 * 3,
0,
0,
0,
0};
DWORD res = ICDecompressQuery(hic, &testSrc, NULL);
if (ICERR_OK == res) { // Don't need to wrap this, as it's OK if testSrc gets modified.
ICINFO info = {sizeof(ICINFO)};
ICGetInfo(hic, &info, sizeof info);
// Okay, let's give the codec a chance to redeem itself. Reformat the input format into
// a plain 24-bit RGB image, and ask it what the compressed format is. If it produces
// a FOURCC that matches, allow it to handle the format. This should allow at least
// the codec's primary format to work. Otherwise, drop it on the ground.
if (m_srcBitmapInfo) {
BITMAPINFOHEADER unpackedSrc = {
sizeof(BITMAPINFOHEADER),
m_srcBitmapInfo->bmiHeader.biWidth,
m_srcBitmapInfo->bmiHeader.biHeight,
1,
24,
BI_RGB,
0,
0,
0,
0,
0};
unpackedSrc.biSizeImage = ((unpackedSrc.biWidth * 3 + 3) & ~3) * abs(unpackedSrc.biHeight);
LONG size = ICCompressGetFormatSize(hic, &unpackedSrc);
if (size >= sizeof(BITMAPINFOHEADER)) {
BITMAPINFOHEADER tmp;
if (ICERR_OK == ICCompressGetFormat(hic, &unpackedSrc, &tmp) &&
tmp.biCompression == m_srcBitmapInfo->bmiHeader.biCompression)
return hic;
}
}
} else
return hic;
}
ICClose(hic);
}
return NULL;
}
//-----------------------------------------------------------
TLevelP TLevelReaderAvi::loadInfo()
{
if (IOError)
throw TImageException(m_path, buildAVIExceptionString(IOError));
int nFrames = AVIStreamLength(m_videoStream);
if (nFrames == -1)
return TLevelP();
TLevelP level;
for (int i = 1; i <= nFrames; i++)
level->setFrame(i, TImageP());
return level;
}
//-----------------------------------------------------------
TImageReaderP TLevelReaderAvi::getFrameReader(TFrameId fid)
{
if (IOError != 0)
throw TImageException(m_path, buildAVIExceptionString(IOError));
if (fid.getLetter() != 0)
return TImageReaderP(0);
int index = fid.getNumber() - 1;
TImageReaderAvi *ira = new TImageReaderAvi(m_path, index, this);
return TImageReaderP(ira);
}
//------------------------------------------------------------------------------
TDimension TLevelReaderAvi::getSize()
{
QMutexLocker sl(&m_mutex);
AVISTREAMINFO psi;
AVIStreamInfo(m_videoStream, &psi, sizeof(AVISTREAMINFO));
return TDimension(psi.rcFrame.right - psi.rcFrame.left, psi.rcFrame.bottom - psi.rcFrame.top);
}
//------------------------------------------------
TImageP TLevelReaderAvi::load(int frameIndex)
{
AVISTREAMINFO si;
int rc = AVIStreamInfo(m_videoStream, &si, sizeof(AVISTREAMINFO));
if (rc)
throw TImageException(getFilePath(), "error reading info.");
void *bufferOut = 0;
if (!m_hic) {
rc = readFrameFromStream(m_decompressedBuffer, si.dwSuggestedBufferSize, frameIndex);
if (rc) {
throw TImageException(m_path, "unable read frame " + toString(frameIndex) + "from video stream.");
}
} else {
int prevKeyFrame = m_prevFrame == frameIndex - 1 ? frameIndex : getPrevKeyFrame(m_videoStream, frameIndex);
while (prevKeyFrame <= frameIndex) {
bufferOut = _aligned_malloc(si.dwSuggestedBufferSize, 128);
DWORD bytesRead = si.dwSuggestedBufferSize;
rc = readFrameFromStream(bufferOut, bytesRead, prevKeyFrame);
if (rc) {
_aligned_free(bufferOut);
throw TImageException(m_path, "unable read frame " + toString(frameIndex) + "from video stream.");
}
DWORD res = decompressFrame(bufferOut, bytesRead, m_decompressedBuffer, prevKeyFrame, frameIndex);
_aligned_free(bufferOut);
if (res != ICERR_OK) {
throw TImageException(m_path, "error decompressing frame " + toString(frameIndex));
}
prevKeyFrame++;
}
}
int width = m_srcBitmapInfo->bmiHeader.biWidth;
int height = m_srcBitmapInfo->bmiHeader.biHeight;
int bpp = m_dstBitmapInfo->bmiHeader.biBitCount;
m_prevFrame = frameIndex;
switch (bpp) {
CASE 32:
{
TRasterPT<TPixelRGBM32> ret;
ret.create(width, height);
ret->lock();
memcpy(ret->getRawData(), m_decompressedBuffer, width * height * 4);
ret->unlock();
return TRasterImageP(ret);
}
CASE 24:
{
TRasterImageP i(DIBToRaster((UCHAR *)m_decompressedBuffer, width, height));
return i;
}
DEFAULT : {
throw TImageException(m_path, toString(bpp) + " to 32 bit not supported\n");
}
}
return TRasterImageP();
}
//------------------------------------------------
int TLevelReaderAvi::readFrameFromStream(void *bufferOut, DWORD &bufferSize, int frameIndex) const
{
assert(bufferOut && bufferSize > 0);
LONG bytesReaded = 0;
LONG samplesReaded = 0;
int rc = AVIStreamRead(m_videoStream,
frameIndex,
1,
bufferOut,
bufferSize,
&bytesReaded,
&samplesReaded);
if (!rc) {
assert(samplesReaded == 1); //deve aver letto un frame!!!
assert(bytesReaded <= (LONG)bufferSize); //deve aver letto un numero di byte
//minore o uguale di quello che ci aspettiamo
bufferSize = bytesReaded;
}
return rc;
}
//------------------------------------------------
DWORD TLevelReaderAvi::decompressFrame(void *srcBuffer, int srcSize, void *dstBuffer, int currentFrame, int desiredFrame)
{
BITMAPINFOHEADER srcHeader = m_srcBitmapInfo->bmiHeader;
BITMAPINFOHEADER dstHeader = m_dstBitmapInfo->bmiHeader;
srcHeader.biSizeImage = srcSize;
DWORD dwFlags = 0;
if (!isAKeyFrame(m_videoStream, currentFrame))
dwFlags |= ICDECOMPRESS_NOTKEYFRAME;
if (currentFrame < desiredFrame)
dwFlags |= ICDECOMPRESS_PREROLL;
DWORD res = ICDecompress(m_hic, dwFlags,
&srcHeader,
srcBuffer,
&dstHeader,
dstBuffer);
return res;
}
#else
TLevelReaderAvi::TLevelReaderAvi(const TFilePath &path)::TLevelReader(path)
{
}
TLevelReaderAvi::~TLevelReaderAvi() {}
TLevelP TLevelReaderAvi::loadInfo() { return TLevelP(); }
TImageReaderP TLevelReaderAvi::getFrameReader(TFrameId fid)
{
throw TImageException(m_path, "AVI not supported");
return TImageReaderP(0);
}
TDimension TLevelReaderAvi::getSize() { return TDimension(); }
TImageP TLevelReaderAvi::load(int frameIndex)
{
throw TImageException(m_path, "AVI not supported");
return TImageP(0);
}
#endif
//===========================================================
//
// Tiio::AviWriterProperties
//
//===========================================================
#ifdef _WIN32
Tiio::AviWriterProperties::AviWriterProperties()
: m_codec("Codec")
{
if (m_defaultCodec.getRange().empty()) {
char descr[2048], name[2048];
DWORD fccType = 0;
ICINFO icinfo;
BITMAPINFO inFmt;
m_defaultCodec.addValue(L"Uncompressed");
memset(&inFmt, 0, sizeof(BITMAPINFO));
inFmt.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
inFmt.bmiHeader.biWidth = 100;
inFmt.bmiHeader.biHeight = 100;
inFmt.bmiHeader.biPlanes = 1;
inFmt.bmiHeader.biCompression = BI_RGB;
for (int bpp = 32; bpp >= 24; bpp -= 8) {
inFmt.bmiHeader.biBitCount = bpp;
for (int i = 0;; i++) {
memset(&icinfo, 0, sizeof icinfo);
int rc = ICInfo(fccType, i, &icinfo);
if (!rc)
break;
HIC hic = 0;
#ifdef _MSC_VER
[&](){
__try {
hic = ICOpen(icinfo.fccType, icinfo.fccHandler, ICMODE_QUERY);
} __except (EXCEPTION_EXECUTE_HANDLER) {
}
}();
#else
try {
hic = ICOpen(icinfo.fccType, icinfo.fccHandler, ICMODE_QUERY);
} catch (...) {
}
#endif
if (hic) {
if (ICGetInfo(hic, &icinfo, sizeof(ICINFO)) == 0) // Find out the compressor name
{
ICClose(hic);
continue;
}
WideChar2Char(icinfo.szDescription, descr, sizeof(descr));
WideChar2Char(icinfo.szName, name, sizeof(name));
if (strstr(name, "IYUV") != 0 || (strstr(name, "IR32") != 0 && bpp == 24)) {
ICClose(hic);
continue;
}
std::string compressorName;
compressorName = std::string(name) + " '" + toString(bpp) + "' " + std::string(descr);
if (std::string(compressorName).find("Indeo") != -1) // per il momento togliamo i codec indeo
{
ICClose(hic);
continue;
}
if (ICCompressQuery(hic, &inFmt, NULL) != ICERR_OK) {
ICClose(hic);
continue; // Skip this compressor if it can't handle the format.
}
m_defaultCodec.addValue(toWideString(compressorName));
if (compressorName.find("inepak") != -1)
m_defaultCodec.setValue(toWideString(compressorName));
ICClose(hic);
}
}
}
}
m_codec = m_defaultCodec;
bind(m_codec);
}
TEnumProperty Tiio::AviWriterProperties::m_defaultCodec = TEnumProperty("Codec");
#endif