From 987a48600deeeec7733a7f01e418064c49dd6eb9 Mon Sep 17 00:00:00 2001 From: Peter Veenstra Date: Thu, 6 Feb 2020 10:36:10 +0000 Subject: [PATCH] Rewrite video capturing and fix some endian issues with all captures as well. Thanks jmarsh Imported-from: https://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk@4314 --- configure.ac | 27 +- include/hardware.h | 4 +- src/hardware/hardware.cpp | 964 ++++++++++++++++++++++------------ src/libs/zmbv/zmbv.cpp | 32 +- src/libs/zmbv/zmbv.h | 3 +- src/platform/visualc/config.h | 2 + 6 files changed, 662 insertions(+), 370 deletions(-) diff --git a/configure.ac b/configure.ac index e993b759..a01d7107 100644 --- a/configure.ac +++ b/configure.ac @@ -439,12 +439,12 @@ else fi AH_TEMPLATE(C_SSHOT,[Define to 1 to enable screenshots, requires libpng]) -AC_ARG_ENABLE(screenshots,AC_HELP_STRING([--disable-screenshots],[Disable screenshots and movie recording]),,enable_screenshots=yes) -AC_CHECK_HEADER(png.h,have_png_h=yes,) -AC_CHECK_LIB(png, png_get_io_ptr, have_png_lib=yes, ,-lz) +AC_ARG_ENABLE(screenshots,AC_HELP_STRING([--disable-screenshots],[Disable screenshots]),enable_screenshots=no,enable_screenshots=yes) +AC_CHECK_HEADER(png.h,have_png_h=yes,have_png_h=no) +AC_CHECK_LIB(png, png_get_io_ptr, have_png_lib=yes, have_png_lib=no,-lz) AC_MSG_CHECKING([whether screenshots will be enabled]) if test x$enable_screenshots = xyes; then - if test x$have_png_lib = xyes -a x$have_png_h = xyes ; then + if test x$have_png_lib = xyes -a x$have_png_h = xyes ; then LIBS="$LIBS -lpng -lz" AC_DEFINE(C_SSHOT,1) AC_MSG_RESULT([yes]) @@ -455,6 +455,25 @@ else AC_MSG_RESULT([no]) fi +AH_TEMPLATE(C_SRECORD,[Define to 1 to enable movie recording, requires zlib built without Z_SOLO]) +AC_ARG_ENABLE(recording,AC_HELP_STRING([--disable-recording],[Disable movie recording]),,enable_recording=yes) +AC_CHECK_HEADER(zlib.h,have_zlib_h=yes) +AC_CHECK_LIB(z,compress,have_z_lib=yes,,) +AC_MSG_CHECKING([whether recording will be enabled]) +if test x$enable_recording = xyes; then + if test x$have_z_lib = xyes -a x$have_zlib_h = xyes ; then + if test x$enable_screenshots = xno -o x$have_png_h = xno -o x$have_png_lib = xno ; then + LIBS="$LIBS -lz" + fi + AC_DEFINE(C_SRECORD,1) + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no, can't find zlib.]) + fi +else + AC_MSG_RESULT([no]) +fi + AH_TEMPLATE(C_MODEM,[Define to 1 to enable internal modem support, requires SDL_net]) AH_TEMPLATE(C_IPX,[Define to 1 to enable IPX over Internet networking, requires SDL_net]) AC_CHECK_HEADER(SDL_net.h,have_sdl_net_h=yes,) diff --git a/include/hardware.h b/include/hardware.h index 3a960868..a2e86ba0 100644 --- a/include/hardware.h +++ b/include/hardware.h @@ -45,10 +45,10 @@ bool TS_Get_Address(Bitu& tsaddr, Bitu& tsirq, Bitu& tsdma); extern Bit8u adlib_commandreg; FILE * OpenCaptureFile(const char * type,const char * ext); -void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data); +void CAPTURE_AddWave(Bit32u freq, Bitu len, Bit16s * data); #define CAPTURE_FLAG_DBLW 0x1 #define CAPTURE_FLAG_DBLH 0x2 -void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, Bit8u * data, Bit8u * pal); +void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, const Bit8u * data, const Bit8u * pal); void CAPTURE_AddMidi(bool sysex, Bitu len, Bit8u * data); #endif diff --git a/src/hardware/hardware.cpp b/src/hardware/hardware.cpp index 2f800b75..c8281001 100644 --- a/src/hardware/hardware.cpp +++ b/src/hardware/hardware.cpp @@ -17,6 +17,7 @@ */ +#include #include #include #include @@ -32,6 +33,8 @@ #if (C_SSHOT) #include +#endif +#if (C_SRECORD) #include "../libs/zmbv/zmbv.cpp" #endif @@ -39,14 +42,386 @@ static std::string capturedir; extern const char* RunningProgram; Bitu CaptureState; +static const char version_text[] = "DOSBox " VERSION; + #define WAVE_BUF 16*1024 #define MIDI_BUF 4*1024 -#define AVI_HEADER_SIZE 500 + +#if (C_SRECORD) + +#define AVII_KEYFRAME (1<<4) +#define AVIF_HASINDEX (1<<4) +#define AVIF_ISINTERLEAVED (1<<8) + +#define AVI_MAX_SIZE 0x7FFFFFFF + +struct AVITag { + char sTag[4]; + AVITag(const char *n) { + memcpy(sTag, n, sizeof(sTag)); + } +}; + +struct AVIChunk : AVITag { + Bit32u dwSize; + AVIChunk(const char *n, size_t s=AVI_MAX_SIZE) + : AVITag(n) { + var_write(&dwSize, (Bit32u)s); + } +}; + +// utility class to set dwSize automatically +template +struct AVIChunkT : AVIChunk { + AVIChunkT(const char *n) + : AVIChunk(n,sizeof(T)-sizeof(AVIChunk)) {} +}; + +struct AVIStreamFormatAudio { + AVIChunkT ck; + Bit16u wFormatTag; + Bit16u nChannels; + Bit32u nSamplesPerSec; + Bit32u nAvgBytesPerSec; + Bit16u nBlockAlign; + Bit16u wBitsPerSample; + AVIStreamFormatAudio() : ck("strf") { + var_write(&wFormatTag, 1); + var_write(&nChannels, 2); + var_write(&nBlockAlign, 4); + var_write(&wBitsPerSample, 16); + } +}; + +struct AVIStreamFormatVideo { + AVIChunkT ck; + Bit32u biSize; + Bit32u biWidth; + Bit32u biHeight; + Bit16u biPlanes; + Bit16u biBitCount; + Bit32u biCompression; + Bit32u biSizeImage; + Bit32u biXPelsPerMeter; + Bit32u biYPelsPerMeter; + Bit32u biClrUsed; + Bit32u biClrImportant; + AVIStreamFormatVideo() : ck("strf") { + biSize = ck.dwSize; + biPlanes = 0; + biBitCount = 0; + memcpy(&biCompression, CODEC_4CC, 4); + biXPelsPerMeter = 0; + biYPelsPerMeter = 0; + biClrUsed = 0; + biClrImportant = 0; + } +}; + +struct AVIStreamHeader { + AVIChunkT ck; + AVITag sFCC; + Bit32u sFCCHandler; + Bit32u dwFlags; + Bit16u wPriority; + Bit16u wLanguage; + Bit32u dwInitialFrames; + Bit32u dwScale; + Bit32u dwRate; + Bit32u dwStart; + Bit32u dwLength; + Bit32u dwSuggestedBufferSize; + Bit32u dwQuality; + Bit32u dwSampleSize; + Bit16u wLeft,wTop,wRight,wBottom; + AVIStreamHeader(const char *n) : ck("strh"), sFCC(n) { + dwFlags = 0; + wPriority = 0; + wLanguage = 0; + dwInitialFrames = 0; + dwStart = 0; + dwLength = 0xFFFFFFFF; + dwSuggestedBufferSize = 0; + dwQuality = 0xFFFFFFFF; + wLeft = wTop = 0; + } +}; + +struct AVIStreamHeaderVideo : AVIStreamHeader { + AVIStreamHeaderVideo() : AVIStreamHeader("vids") { + memcpy(&sFCCHandler, CODEC_4CC, 4); + var_write(&dwScale, 1<<24); + dwSampleSize = 0; + } +}; + +struct AVIStreamHeaderAudio : AVIStreamHeader { + AVIStreamHeaderAudio() : AVIStreamHeader("auds") { + sFCCHandler = 0; + var_write(&dwScale, 1); + var_write(&dwSampleSize, 4); + wRight = wBottom = 0; + } +}; + +struct AVIMainHeader { + AVIChunkT ck; + Bit32u dwMicroSecPerFrame; + Bit32u dwMaxBytesPerSec; + Bit32u dwPaddingGranularity; + Bit32u dwFlags; + Bit32u dwTotalFrames; + Bit32u dwInitialFrames; + Bit32u dwStreams; + Bit32u dwSuggestedBufferSize; + Bit32u dwWidth; + Bit32u dwHeight; + Bit32u dwReserved[4]; + AVIMainHeader() : ck("avih") { + dwMaxBytesPerSec = 0; + dwPaddingGranularity = 0; + var_write(&dwFlags, AVIF_ISINTERLEAVED); + dwTotalFrames = 0xFFFFFFFF; + dwInitialFrames = 0; + var_write(&dwStreams, 2); + dwSuggestedBufferSize = 0; + dwReserved[0]=dwReserved[1]=dwReserved[2]=dwReserved[3]=0; + } +}; + +template +struct AVIList { + AVIChunkT ck; + AVITag sName; + AVIList(const char *n) : ck("LIST"), sName(n) {} +}; + +/* movi list - special case, initial size is MAX */ +struct AVIListMovi : AVIChunk { + AVITag sName; + AVIListMovi() : AVIChunk("LIST"),sName("movi") {} +}; + +template +struct AVIListStream { + AVIList lst; + htype hdr; + ftype fmt; + AVIListStream() : lst("strl") {} +}; + +struct AVIListHeader { + AVIList lst; + AVIMainHeader hdr; + AVIListStream vid; + AVIListStream aud; + AVIListHeader() : lst("hdrl") {} +}; + +/* Interesting metadata tags: + * "ICMT" Comment/Description + * "ICRD" Date + * "INAM" Title + * "ISFT" Sofware/Encoder (not read by many programs, so using ICMT here instead) + * "IPRD" Product + * "ISBJ" Subject + * "ISRC" Source + */ +template +struct AVIMeta { + AVIChunkT ck; + char aTag[len]; + AVIMeta(const char *n, const char *s) : ck(n) { + Bitu i; + for (i=0; i < sizeof(aTag)-1; i++) { + aTag[i] = *s; + if (*s) ++s; + } + aTag[i] = '\0'; + } +}; + +struct AVIListInfo { + AVIList lst; + // multiple of 4 to keep dword alignment + AVIMeta<(sizeof(version_text)+3)&~3> comment; + AVIListInfo() : lst("INFO"), + comment("ICMT",version_text) {} +}; + +struct AVIRIFF : AVIChunk { + AVITag sName; + AVIListHeader hdr; + AVIListInfo info; + AVIListMovi movi; + AVIRIFF() : AVIChunk("RIFF"), sName("AVI ") {} +}; + +struct AVIIndexEntry { + AVITag dwChunkId; + Bit32u dwFlags; + Bit32u dwOffset; + Bit32u dwSize; + AVIIndexEntry(const AVIChunk &ck, Bit32u flags, size_t offset) + : dwChunkId(ck) { + var_write(&dwFlags, flags); + var_write(&dwOffset, (Bit32u)offset); + dwSize = ck.dwSize; + } +}; + +class AVIFILE { +private: + AVIRIFF riff; + std::vector idx; + size_t data_length; + FILE *handle; + Bitu samples; + Bit32u freq; + size_t buffer_size[2]; + /* set new sampling rate, returns true if changed */ + bool SetFreq(Bit32u newfreq) { + if (freq == newfreq) return false; + freq = newfreq; + var_write(&riff.hdr.aud.hdr.dwRate, freq); + riff.hdr.aud.fmt.nSamplesPerSec = riff.hdr.aud.hdr.dwRate; + var_write(&riff.hdr.aud.fmt.nAvgBytesPerSec, 4*freq); + return true; + } + bool AddChunk(const AVIChunk &ck, const AVIIndexEntry &entry, const void *data, size_t length) { + long pos = ftell(handle); // in case writing fails + if (fwrite(&ck,sizeof(ck),1,handle)==1 && \ + fwrite(data,length,1,handle)==1) + { + if (length&1) { // chunks must be aligned to 2-bytes + fseek(handle,1,SEEK_CUR); + length++; + } else fflush(handle); + idx.push_back(entry); + data_length += length+sizeof(ck); + return true; + } + + fseek(handle, pos, SEEK_SET); + return false; + } +public: + Bit32u frames; + + AVIFILE(FILE* _handle, Bitu width, Bitu height, float fps) : handle(_handle) { + data_length = sizeof(AVITag); // "movi" tag + frames = 0; + samples = 0; + freq = 0; + buffer_size[0]=buffer_size[1] = 0; + idx.reserve(4096); + + var_write(&riff.hdr.hdr.dwMicroSecPerFrame, (Bit32u)(1000000/fps)); + var_write(&riff.hdr.hdr.dwWidth, (Bit32u)width); + var_write(&riff.hdr.hdr.dwHeight, (Bit32u)height); + var_write(&riff.hdr.vid.hdr.dwRate, (Bit32u)((1<<24)*fps)); + var_write(&riff.hdr.vid.hdr.wRight, (Bit16u)width); + var_write(&riff.hdr.vid.hdr.wBottom, (Bit16u)height); + riff.hdr.vid.fmt.biWidth = riff.hdr.hdr.dwWidth; + riff.hdr.vid.fmt.biHeight = riff.hdr.hdr.dwHeight; + var_write(&riff.hdr.vid.fmt.biSizeImage, (Bit32u)(width*height*4)); + + SetFreq(44100); // guess, don't know the frequency until first audio block is written + fwrite(&riff, sizeof(riff), 1, handle); + } + ~AVIFILE() { + AVIChunk idx1("idx1",idx.size()*sizeof(AVIIndexEntry)); + + var_write(&riff.movi.dwSize, (Bit32u)data_length); + var_write(&riff.hdr.hdr.dwTotalFrames, frames); + riff.hdr.vid.hdr.dwLength = riff.hdr.hdr.dwTotalFrames; + var_write(&riff.hdr.vid.hdr.dwSuggestedBufferSize, (Bit32u)buffer_size[0]); + var_write(&riff.hdr.aud.hdr.dwLength, (Bit32u)samples); + var_write(&riff.hdr.aud.hdr.dwSuggestedBufferSize, (Bit32u)buffer_size[1]); + + // attempt to write index + if (fwrite(&idx1,sizeof(idx1),1,handle)==1 && \ + fwrite(&idx[0],sizeof(AVIIndexEntry),idx.size(),handle)==idx.size()) { + // index was added, set HASINDEX flag + var_write(&riff.hdr.hdr.dwFlags, AVIF_ISINTERLEAVED|AVIF_HASINDEX); + // accumulate index size + data_length += sizeof(AVIChunk)+sizeof(AVIIndexEntry)*idx.size(); + } + + var_write(&riff.dwSize, (Bit32u)(sizeof(riff)+data_length-sizeof(AVIChunk)-sizeof(AVITag))); + + fseek(handle, 0, SEEK_SET); + fwrite(&riff, sizeof(riff), 1, handle); + fclose(handle); + } + + /* want to add s bytes of data, fails if new size exceeds 2GB */ + bool CanAdd(size_t s) { + /* calculate maximum possible data size (all constants here)*/ + size_t max_size = AVI_MAX_SIZE; + /* minus headers (excluding "movi" tag) */ + max_size -= (sizeof(riff)-sizeof(AVITag)); + /* minus index chunk header + data chunk header */ + max_size -= 2*sizeof(AVIChunk); + /* minus index entry */ + max_size -= sizeof(AVIIndexEntry); + + /* round up to multiple of 2 */ + s += s&1; + /* add all data already written */ + s += data_length; + /* add existing index entries */ + s += idx.size()*sizeof(AVIIndexEntry); + + return s < max_size; + } + + /* add video data */ + bool AddVideo(const void *data, size_t length, Bit32u flags) { + AVIChunk ck("00dc",length); + AVIIndexEntry entry(ck, flags, data_length); + + if (AddChunk(ck, entry, data, length)) { + if (length > buffer_size[0]) + buffer_size[0] = length; + frames++; + return true; + } + return false; + } + + /* add audio data */ + bool AddAudio(const void *data, Bitu _samples, Bit32u new_freq) { + size_t length = _samples*4; + AVIChunk ck("01wb",length); + /* Every audio block marked as keyframe, just in case */ + AVIIndexEntry entry(ck, AVII_KEYFRAME, data_length); + + if (!CanAdd(length)) return false; + + if (SetFreq(new_freq)) { + /* rewrite audio headers */ + long pos = ftell(handle); + fseek(handle, (long)(riff.hdr.aud.lst.ck.sTag-riff.sTag), SEEK_SET); + fwrite(&riff.hdr.aud, sizeof(riff.hdr.aud), 1, handle); + fseek(handle, pos, SEEK_SET); + } + + if (AddChunk(ck, entry, data, length)) { + if (length > buffer_size[1]) + buffer_size[1] = length; + samples += _samples; + return true; + } + return false; + } +}; +#endif // C_SRECORD static struct { struct { FILE * handle; - Bit16s buf[WAVE_BUF][2]; + Bit16u buf[WAVE_BUF][2]; Bitu used; Bit32u length; Bit32u freq; @@ -60,22 +435,17 @@ static struct { struct { Bitu rowlen; } image; -#if (C_SSHOT) +#if (C_SRECORD) struct { - FILE *handle; - Bitu frames; + AVIFILE *avi_out; Bit16s audiobuf[WAVE_BUF][2]; Bitu audioused; - Bitu audiorate; - Bitu audiowritten; + Bit32u audiorate; VideoCodec *codec; Bitu width, height, bpp; - Bitu written; float fps; int bufSize; void *buf; - Bit8u *index; - Bitu indexsize, indexused; } video; #endif } capture; @@ -118,7 +488,7 @@ FILE * OpenCaptureFile(const char * type,const char * ext) { } close_directory( dir ); char file_name[CROSS_LEN]; - sprintf(file_name,"%s%c%s%03d%s",capturedir.c_str(),CROSS_FILESPLIT,file_start,last,ext); + sprintf(file_name,"%s%c%s%03" sBitfs(u) "%s",capturedir.c_str(),CROSS_FILESPLIT,file_start,last,ext); /* Open the actual file */ FILE * handle=fopen(file_name,"wb"); if (handle) { @@ -129,177 +499,39 @@ FILE * OpenCaptureFile(const char * type,const char * ext) { return handle; } -#if (C_SSHOT) -static void CAPTURE_AddAviChunk(const char * tag, Bit32u size, void * data, Bit32u flags) { - Bit8u chunk[8];Bit8u *index;Bit32u pos, writesize; - - chunk[0] = tag[0];chunk[1] = tag[1];chunk[2] = tag[2];chunk[3] = tag[3]; - host_writed(&chunk[4], size); - /* Write the actual data */ - fwrite(chunk,1,8,capture.video.handle); - writesize = (size+1)&~1; - fwrite(data,1,writesize,capture.video.handle); - pos = capture.video.written + 4; - capture.video.written += writesize + 8; - if (capture.video.indexused + 16 >= capture.video.indexsize ) { - capture.video.index = (Bit8u*)realloc( capture.video.index, capture.video.indexsize + 16 * 4096); - if (!capture.video.index) - E_Exit("Ran out of memory during AVI capturing"); - capture.video.indexsize += 16*4096; - } - index = capture.video.index+capture.video.indexused; - capture.video.indexused += 16; - index[0] = tag[0]; - index[1] = tag[1]; - index[2] = tag[2]; - index[3] = tag[3]; - host_writed(index+4, flags); - host_writed(index+8, pos); - host_writed(index+12, size); -} -#endif - -#if (C_SSHOT) +#if (C_SRECORD) static void CAPTURE_VideoEvent(bool pressed) { if (!pressed) return; if (CaptureState & CAPTURE_VIDEO) { + /* Flush remaining audio */ + if ( capture.video.audioused ) { + if (capture.video.avi_out) { + /* if it can't be added, leave it to be written to the next segment */ + if (capture.video.avi_out->AddAudio(capture.video.audiobuf, capture.video.audioused, capture.video.audiorate)) { + capture.video.audioused = 0; + } + } + } /* Close the video */ CaptureState &= ~CAPTURE_VIDEO; - LOG_MSG("Stopped capturing video."); + LOG_MSG("Stopped capturing video."); - Bit8u avi_header[AVI_HEADER_SIZE]; - Bitu main_list; - Bitu header_pos=0; -#define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4; -#define AVIOUTw(_S_) host_writew(&avi_header[header_pos], _S_);header_pos+=2; -#define AVIOUTd(_S_) host_writed(&avi_header[header_pos], _S_);header_pos+=4; - /* Try and write an avi header */ - AVIOUT4("RIFF"); // Riff header - AVIOUTd(AVI_HEADER_SIZE + capture.video.written - 8 + capture.video.indexused); - AVIOUT4("AVI "); - AVIOUT4("LIST"); // List header - main_list = header_pos; - AVIOUTd(0); // TODO size of list - AVIOUT4("hdrl"); + delete capture.video.avi_out; + capture.video.avi_out = NULL; - AVIOUT4("avih"); - AVIOUTd(56); /* # of bytes to follow */ - AVIOUTd((Bit32u)(1000000 / capture.video.fps)); /* Microseconds per frame */ - AVIOUTd(0); - AVIOUTd(0); /* PaddingGranularity (whatever that might be) */ - AVIOUTd(0x110); /* Flags,0x10 has index, 0x100 interleaved */ - AVIOUTd(capture.video.frames); /* TotalFrames */ - AVIOUTd(0); /* InitialFrames */ - AVIOUTd(2); /* Stream count */ - AVIOUTd(0); /* SuggestedBufferSize */ - AVIOUTd(capture.video.width); /* Width */ - AVIOUTd(capture.video.height); /* Height */ - AVIOUTd(0); /* TimeScale: Unit used to measure time */ - AVIOUTd(0); /* DataRate: Data rate of playback */ - AVIOUTd(0); /* StartTime: Starting time of AVI data */ - AVIOUTd(0); /* DataLength: Size of AVI data chunk */ - - /* Video stream list */ - AVIOUT4("LIST"); - AVIOUTd(4 + 8 + 56 + 8 + 40); /* Size of the list */ - AVIOUT4("strl"); - /* video stream header */ - AVIOUT4("strh"); - AVIOUTd(56); /* # of bytes to follow */ - AVIOUT4("vids"); /* Type */ - AVIOUT4(CODEC_4CC); /* Handler */ - AVIOUTd(0); /* Flags */ - AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */ - AVIOUTd(0); /* InitialFrames */ - AVIOUTd(1000000); /* Scale */ - AVIOUTd((Bit32u)(1000000 * capture.video.fps)); /* Rate: Rate/Scale == samples/second */ - AVIOUTd(0); /* Start */ - AVIOUTd(capture.video.frames); /* Length */ - AVIOUTd(0); /* SuggestedBufferSize */ - AVIOUTd(~0); /* Quality */ - AVIOUTd(0); /* SampleSize */ - AVIOUTd(0); /* Frame */ - AVIOUTd(0); /* Frame */ - /* The video stream format */ - AVIOUT4("strf"); - AVIOUTd(40); /* # of bytes to follow */ - AVIOUTd(40); /* Size */ - AVIOUTd(capture.video.width); /* Width */ - AVIOUTd(capture.video.height); /* Height */ -// OUTSHRT(1); OUTSHRT(24); /* Planes, Count */ - AVIOUTd(0); - AVIOUT4(CODEC_4CC); /* Compression */ - AVIOUTd(capture.video.width * capture.video.height*4); /* SizeImage (in bytes?) */ - AVIOUTd(0); /* XPelsPerMeter */ - AVIOUTd(0); /* YPelsPerMeter */ - AVIOUTd(0); /* ClrUsed: Number of colors used */ - AVIOUTd(0); /* ClrImportant: Number of colors important */ - - /* Audio stream list */ - AVIOUT4("LIST"); - AVIOUTd(4 + 8 + 56 + 8 + 16); /* Length of list in bytes */ - AVIOUT4("strl"); - /* The audio stream header */ - AVIOUT4("strh"); - AVIOUTd(56); /* # of bytes to follow */ - AVIOUT4("auds"); - AVIOUTd(0); /* Format (Optionally) */ - AVIOUTd(0); /* Flags */ - AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */ - AVIOUTd(0); /* InitialFrames */ - AVIOUTd(4); /* Scale */ - AVIOUTd(capture.video.audiorate*4); /* Rate, actual rate is scale/rate */ - AVIOUTd(0); /* Start */ - if (!capture.video.audiorate) - capture.video.audiorate = 1; - AVIOUTd(capture.video.audiowritten/4); /* Length */ - AVIOUTd(0); /* SuggestedBufferSize */ - AVIOUTd(~0); /* Quality */ - AVIOUTd(4); /* SampleSize */ - AVIOUTd(0); /* Frame */ - AVIOUTd(0); /* Frame */ - /* The audio stream format */ - AVIOUT4("strf"); - AVIOUTd(16); /* # of bytes to follow */ - AVIOUTw(1); /* Format, WAVE_ZMBV_FORMAT_PCM */ - AVIOUTw(2); /* Number of channels */ - AVIOUTd(capture.video.audiorate); /* SamplesPerSec */ - AVIOUTd(capture.video.audiorate*4); /* AvgBytesPerSec*/ - AVIOUTw(4); /* BlockAlign */ - AVIOUTw(16); /* BitsPerSample */ - int nmain = header_pos - main_list - 4; - /* Finish stream list, i.e. put number of bytes in the list to proper pos */ - - int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos; - AVIOUT4("JUNK"); - AVIOUTd(njunk); - /* Fix the size of the main list */ - header_pos = main_list; - AVIOUTd(nmain); - header_pos = AVI_HEADER_SIZE - 12; - AVIOUT4("LIST"); - AVIOUTd(capture.video.written+4); /* Length of list in bytes */ - AVIOUT4("movi"); - /* First add the index table to the end */ - memcpy(capture.video.index, "idx1", 4); - host_writed( capture.video.index+4, capture.video.indexused - 8 ); - fwrite( capture.video.index, 1, capture.video.indexused, capture.video.handle); - fseek(capture.video.handle, 0, SEEK_SET); - fwrite(&avi_header, 1, AVI_HEADER_SIZE, capture.video.handle); - fclose( capture.video.handle ); - free( capture.video.index ); free( capture.video.buf ); + capture.video.buf = NULL; delete capture.video.codec; - capture.video.handle = 0; + capture.video.codec = NULL; } else { CaptureState |= CAPTURE_VIDEO; + capture.video.audioused=0; } } #endif -void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, Bit8u * data, Bit8u * pal) { -#if (C_SSHOT) +void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, float fps, const Bit8u * data, const Bit8u * pal) { Bitu i; Bit8u doubleRow[SCALER_MAXWIDTH*4]; Bitu countWidth = width; @@ -313,161 +545,183 @@ void CAPTURE_AddImage(Bitu width, Bitu height, Bitu bpp, Bitu pitch, Bitu flags, return; if (width > SCALER_MAXWIDTH) return; - +#if (C_SSHOT) if (CaptureState & CAPTURE_IMAGE) { - png_structp png_ptr; - png_infop info_ptr; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; png_color palette[256]; CaptureState &= ~CAPTURE_IMAGE; /* Open the actual file */ FILE * fp=OpenCaptureFile("Screenshot",".png"); - if (!fp) goto skip_shot; - /* First try to allocate the png structures */ - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL, NULL); - if (!png_ptr) goto skip_shot; - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_write_struct(&png_ptr,(png_infopp)NULL); - goto skip_shot; + if (fp) { + /* First try to allocate the png structures */ + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL, NULL); + if (png_ptr) info_ptr = png_create_info_struct(png_ptr); } - - /* Finalize the initing of png library */ - png_init_io(png_ptr, fp); - png_set_compression_level(png_ptr,Z_BEST_COMPRESSION); + if (info_ptr) { + /* Finalize the initing of png library */ + png_init_io(png_ptr, fp); + png_set_compression_level(png_ptr,Z_BEST_COMPRESSION); + + /* set other zlib parameters */ + png_set_compression_mem_level(png_ptr, 8); + png_set_compression_strategy(png_ptr,Z_DEFAULT_STRATEGY); + png_set_compression_window_bits(png_ptr, 15); + png_set_compression_method(png_ptr, 8); + png_set_compression_buffer_size(png_ptr, 8192); - /* set other zlib parameters */ - png_set_compression_mem_level(png_ptr, 8); - png_set_compression_strategy(png_ptr,Z_DEFAULT_STRATEGY); - png_set_compression_window_bits(png_ptr, 15); - png_set_compression_method(png_ptr, 8); - png_set_compression_buffer_size(png_ptr, 8192); - - if (bpp==8) { - png_set_IHDR(png_ptr, info_ptr, width, height, - 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - for (i=0;i<256;i++) { - palette[i].red=pal[i*4+0]; - palette[i].green=pal[i*4+1]; - palette[i].blue=pal[i*4+2]; + if (bpp==8) { + png_set_IHDR(png_ptr, info_ptr, width, height, + 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + for (i=0;i<256;i++) { + palette[i].red=pal[i*4+0]; + palette[i].green=pal[i*4+1]; + palette[i].blue=pal[i*4+2]; + } + png_set_PLTE(png_ptr, info_ptr, palette,256); + } else { + png_set_bgr( png_ptr ); + png_set_IHDR(png_ptr, info_ptr, width, height, + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); } - png_set_PLTE(png_ptr, info_ptr, palette,256); - } else { - png_set_bgr( png_ptr ); - png_set_IHDR(png_ptr, info_ptr, width, height, - 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - } + #ifdef PNG_TEXT_SUPPORTED - int fields = 1; - png_text text[1] = {}; - const char* text_s = "DOSBox " VERSION; - size_t strl = strlen(text_s); - char* ptext_s = new char[strl + 1]; - strcpy(ptext_s, text_s); - char software[9] = { 'S','o','f','t','w','a','r','e',0}; - text[0].compression = PNG_TEXT_COMPRESSION_NONE; - text[0].key = software; - text[0].text = ptext_s; - png_set_text(png_ptr, info_ptr, text, fields); + char ptext[] = "DOSBox" VERSION; + char software[] = "Software"; + png_text text; + text.compression = PNG_TEXT_COMPRESSION_NONE; + text.key = software; + text.text = ptext; + png_set_text(png_ptr, info_ptr, &text, 1); #endif - png_write_info(png_ptr, info_ptr); -#ifdef PNG_TEXT_SUPPORTED - delete [] ptext_s; + png_write_info(png_ptr, info_ptr); + + for (i=0;i> 1)*pitch); + else + srcLine=(data+(i >> 0)*pitch); + rowPointer=srcLine; + switch (bpp) { + case 8: + if (flags & CAPTURE_FLAG_DBLW) { + for (Bitu x=0;x> 10; + doubleRow[x*6+1] = doubleRow[x*6+4] = (((pixel&0xe000)|((pixel&0x0003)<<16)) * 0x21) >> 15; + doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x007c) * 0x21) >> 4; +#else + doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2; + doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x03e0) * 0x21) >> 7; + doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x7c00) * 0x21) >> 12; #endif - for (i=0;i> 1)*pitch); - else - srcLine=(data+(i >> 0)*pitch); - rowPointer=srcLine; - switch (bpp) { - case 8: - if (flags & CAPTURE_FLAG_DBLW) { - for (Bitu x=0;x> 10; + doubleRow[x*3+1] = (((pixel&0xe000)|((pixel&0x0003)<<16)) * 0x21) >> 15; + doubleRow[x*3+2] = ((pixel& 0x007c) * 0x21) >> 4; +#else + doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2; + doubleRow[x*3+1] = ((pixel& 0x03e0) * 0x21) >> 7; + doubleRow[x*3+2] = ((pixel& 0x7c00) * 0x21) >> 12; +#endif + } + } rowPointer = doubleRow; + break; + case 16: + if (flags & CAPTURE_FLAG_DBLW) { + for (Bitu x=0;x> 10; + doubleRow[x*6+1] = doubleRow[x*6+4] = (((pixel&0xe000)|((pixel&0x0007)<<16)) * 0x41) >> 17; + doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x00f8) * 0x21) >> 5; +#else + doubleRow[x*6+0] = doubleRow[x*6+3] = ((pixel& 0x001f) * 0x21) >> 2; + doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x07e0) * 0x41) >> 9; + doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0xf800) * 0x21) >> 13; +#endif + } + } else { + for (Bitu x=0;x> 10; + doubleRow[x*3+1] = (((pixel&0xe000)|((pixel&0x0007)<<16)) * 0x41) >> 17; + doubleRow[x*3+2] = ((pixel& 0x00f8) * 0x21) >> 5; +#else + doubleRow[x*3+0] = ((pixel& 0x001f) * 0x21) >> 2; + doubleRow[x*3+1] = ((pixel& 0x07e0) * 0x41) >> 9; + doubleRow[x*3+2] = ((pixel& 0xf800) * 0x21) >> 13; +#endif + } + } + rowPointer = doubleRow; + break; + case 32: + if (flags & CAPTURE_FLAG_DBLW) { + for (Bitu x=0;x> 2; - doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x03e0) * 0x21) >> 7; - doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0x7c00) * 0x21) >> 12; - } - } else { - for (Bitu x=0;x> 2; - doubleRow[x*3+1] = ((pixel& 0x03e0) * 0x21) >> 7; - doubleRow[x*3+2] = ((pixel& 0x7c00) * 0x21) >> 12; - } + png_write_row(png_ptr, (png_bytep)rowPointer); + if (flags & CAPTURE_FLAG_DBLH) { + png_write_row(png_ptr, (png_bytep)rowPointer); + i++; } - rowPointer = doubleRow; - break; - case 16: - if (flags & CAPTURE_FLAG_DBLW) { - for (Bitu x=0;x> 2; - doubleRow[x*6+1] = doubleRow[x*6+4] = ((pixel& 0x07e0) * 0x41) >> 9; - doubleRow[x*6+2] = doubleRow[x*6+5] = ((pixel& 0xf800) * 0x21) >> 13; - } - } else { - for (Bitu x=0;x> 2; - doubleRow[x*3+1] = ((pixel& 0x07e0) * 0x41) >> 9; - doubleRow[x*3+2] = ((pixel& 0xf800) * 0x21) >> 13; - } - } - rowPointer = doubleRow; - break; - case 32: - if (flags & CAPTURE_FLAG_DBLW) { - for (Bitu x=0;xCanAdd(4*capture.video.audioused+capture.video.bufSize)) { + // need to switch files + CAPTURE_VideoEvent(true); + LOG_MSG("CAPTURE: Beginning new video segment"); + CaptureState |= CAPTURE_VIDEO; + } } - CaptureState &= ~CAPTURE_VIDEO; switch (bpp) { case 8:format = ZMBV_FORMAT_8BPP;break; case 15:format = ZMBV_FORMAT_15BPP;break; @@ -476,53 +730,42 @@ skip_shot: default: goto skip_video; } - if (!capture.video.handle) { - capture.video.handle = OpenCaptureFile("Video",".avi"); - if (!capture.video.handle) + if (!capture.video.avi_out) { + FILE *f = OpenCaptureFile("Video",".avi"); + if (f==NULL) goto skip_video; + capture.video.avi_out = new AVIFILE(f, width, height, fps); capture.video.codec = new VideoCodec(); if (!capture.video.codec) goto skip_video; - if (!capture.video.codec->SetupCompress( width, height)) + if (!capture.video.codec->SetupCompress( (int)width, (int)height)) goto skip_video; - capture.video.bufSize = capture.video.codec->NeededSize(width, height, format); + capture.video.bufSize = capture.video.codec->NeededSize((int)width, (int)height, format); capture.video.buf = malloc( capture.video.bufSize ); if (!capture.video.buf) goto skip_video; - capture.video.index = (Bit8u*)malloc( 16*4096 ); - if (!capture.video.index) - goto skip_video; - capture.video.indexsize = 16*4096; - capture.video.indexused = 8; capture.video.width = width; capture.video.height = height; capture.video.bpp = bpp; capture.video.fps = fps; - for (i=0;iframes % 300 == 0) codecFlags = 1; else codecFlags = 0; if (!capture.video.codec->PrepareCompressFrame( codecFlags, format, (char *)pal, capture.video.buf, capture.video.bufSize)) goto skip_video; for (i=0;i> 1)*pitch); + else + srcLine=(data+(i >> 0)*pitch); if (flags & CAPTURE_FLAG_DBLW) { - void *srcLine; Bitu x; Bitu countWidth = width >> 1; - if (flags & CAPTURE_FLAG_DBLH) - srcLine=(data+(i >> 1)*pitch); - else - srcLine=(data+(i >> 0)*pitch); switch ( bpp) { case 8: for (x=0;x> 1)*pitch); - else - rowPointer=(data+(i >> 0)*pitch); + srcLine=doubleRow; } - capture.video.codec->CompressLines( 1, &rowPointer ); + if (flags & CAPTURE_FLAG_DBLH) { + const void *rowPointer[2]; + rowPointer[0]=rowPointer[1]=srcLine; + capture.video.codec->CompressLines(2, rowPointer); + i++; + } else + capture.video.codec->CompressLines(1, &srcLine); } int written = capture.video.codec->FinishCompressFrame(); if (written < 0) goto skip_video; - CAPTURE_AddAviChunk( "00dc", written, capture.video.buf, codecFlags & 1 ? 0x10 : 0x0); - capture.video.frames++; -// LOG_MSG("Frame %d video %d audio %d",capture.video.frames, written, capture.video.audioused *4 ); - if ( capture.video.audioused ) { - CAPTURE_AddAviChunk( "01wb", capture.video.audioused * 4, capture.video.audiobuf, 0); - capture.video.audiowritten = capture.video.audioused*4; + if (capture.video.avi_out->AddVideo(capture.video.buf, written, codecFlags & 1 ? AVII_KEYFRAME : 0)) { +// LOG_MSG("Frame %d video %d audio %d",capture.video.avi_out->frames, written, capture.video.audioused *4 ); + if (!capture.video.audioused) return; + Bitu samples = capture.video.audioused; capture.video.audioused = 0; - } - - /* Everything went okay, set flag again for next frame */ - CaptureState |= CAPTURE_VIDEO; - } + if (capture.video.avi_out->AddAudio(capture.video.audiobuf, samples, capture.video.audiorate)) + return; + LOG_MSG("Failed to write audio data"); + } else LOG_MSG("Failed to write video data"); + } else return; skip_video: + /* something went wrong, shut it down */ + CAPTURE_VideoEvent(true); #endif return; } @@ -590,14 +834,33 @@ static Bit8u wavheader[]={ 0x0,0x0,0x0,0x0, /* Bit32u data size */ }; -void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data) { -#if (C_SSHOT) +void CAPTURE_AddWave(Bit32u freq, Bitu len, Bit16s * data) { +#if (C_SRECORD) if (CaptureState & CAPTURE_VIDEO) { Bitu left = WAVE_BUF - capture.video.audioused; + if (len > WAVE_BUF) + LOG_MSG("CAPTURE: WAVE_BUF too small"); + /* if framerate is very low (and audiorate is high) the audiobuffer may overflow */ + if (left < len && capture.video.avi_out) { + if (capture.video.avi_out->AddAudio(capture.video.audiobuf, capture.video.audioused, capture.video.audiorate)) { + capture.video.audioused = 0; + left = WAVE_BUF; + } + } if (left > len) left = len; - memcpy( &capture.video.audiobuf[capture.video.audioused], data, left*4); + // samples are native format, need to be little endian + Bit16u *buf = (Bit16u*)capture.video.audiobuf[capture.video.audioused]; + Bit16u *read = (Bit16u*)data; capture.video.audioused += left; +#ifdef WORDS_BIGENDIAN + do { + var_write(buf++, *read++); + var_write(buf++, *read++); + } while (--left); +#else + memcpy(buf, read, left*4); +#endif capture.video.audiorate = freq; } #endif @@ -613,7 +876,7 @@ void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data) { capture.wave.freq = freq; fwrite(wavheader,1,sizeof(wavheader),capture.wave.handle); } - Bit16s * read = data; + Bit16u * read = (Bit16u*)data; while (len > 0 ) { Bitu left = WAVE_BUF - capture.wave.used; if (!left) { @@ -624,10 +887,17 @@ void CAPTURE_AddWave(Bit32u freq, Bit32u len, Bit16s * data) { } if (left > len) left = len; - memcpy( &capture.wave.buf[capture.wave.used], read, left*4); + Bit16u *buf = capture.wave.buf[capture.wave.used]; capture.wave.used += left; - read += left*2; len -= left; +#ifdef WORDS_BIGENDIAN + do { + var_write(buf++, *read++); + var_write(buf++, *read++); + } while (--left); +#else + memcpy(buf, read, left*4); +#endif } } } @@ -754,12 +1024,14 @@ public: MAPPER_AddHandler(CAPTURE_MidiEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI"); #if (C_SSHOT) MAPPER_AddHandler(CAPTURE_ScreenShotEvent,MK_f5,MMOD1,"scrshot","Screenshot"); +#endif +#if (C_SRECORD) MAPPER_AddHandler(CAPTURE_VideoEvent,MK_f5,MMOD1|MMOD2,"video","Video"); #endif } ~HARDWARE(){ -#if (C_SSHOT) - if (capture.video.handle) CAPTURE_VideoEvent(true); +#if (C_SRECORD) + if (capture.video.avi_out) CAPTURE_VideoEvent(true); #endif if (capture.wave.handle) CAPTURE_WaveEvent(true); if (capture.midi.handle) CAPTURE_MidiEvent(true); diff --git a/src/libs/zmbv/zmbv.cpp b/src/libs/zmbv/zmbv.cpp index dc2c0478..5cbe77a6 100644 --- a/src/libs/zmbv/zmbv.cpp +++ b/src/libs/zmbv/zmbv.cpp @@ -153,7 +153,8 @@ INLINE int VideoCodec::PossibleBlock(int vx,int vy,FrameBlock * block) { P * pnew=((P*)newframe)+block->start;; for (int y=0;ydy;y+=4) { for (int x=0;xdx;x+=4) { - int test=0-((pold[x]-pnew[x])&0x00ffffff); + int test=pold[x]-pnew[x]; + test |= -test; ret-=(test>>31); } pold+=pitch*4; @@ -169,7 +170,8 @@ INLINE int VideoCodec::CompareBlock(int vx,int vy,FrameBlock * block) { P * pnew=((P*)newframe)+block->start;; for (int y=0;ydy;y++) { for (int x=0;xdx;x++) { - int test=0-((pold[x]-pnew[x])&0x00ffffff); + int test=pold[x]-pnew[x]; + test |= -test; ret-=(test>>31); } pold+=pitch; @@ -314,7 +316,7 @@ bool VideoCodec::PrepareCompressFrame(int flags, zmbv_format_t _format, char * return true; } -void VideoCodec::CompressLines(int lineCount, void *lineData[]) { +void VideoCodec::CompressLines(int lineCount, const void *lineData[]) { int linePitch = pitch * pixelsize; int lineWidth = width * pixelsize; int i = 0; @@ -348,7 +350,7 @@ int VideoCodec::FinishCompressFrame( void ) { AddXorFrame(); break; case ZMBV_FORMAT_32BPP: - AddXorFrame(); + AddXorFrame(); break; } } @@ -469,7 +471,7 @@ bool VideoCodec::DecompressFrame(void * framedata, int size) { UnXorFrame(); break; case ZMBV_FORMAT_32BPP: - UnXorFrame(); + UnXorFrame(); break; } } @@ -524,18 +526,10 @@ void VideoCodec::Output_UpsideDown_24(void *output) { } void VideoCodec::FreeBuffers(void) { - if (blocks) { - delete[] blocks;blocks=0; - } - if (buf1) { - delete[] buf1;buf1=0; - } - if (buf2) { - delete[] buf2;buf2=0; - } - if (work) { - delete[] work;work=0; - } + delete[] blocks;blocks=NULL; + delete[] buf1;buf1=NULL; + delete[] buf2;buf2=NULL; + delete[] work;work=NULL; } @@ -547,3 +541,7 @@ VideoCodec::VideoCodec() { work = 0; memset( &zstream, 0, sizeof(zstream)); } + +VideoCodec::~VideoCodec() { + FreeBuffers(); +} diff --git a/src/libs/zmbv/zmbv.h b/src/libs/zmbv/zmbv.h index 7a6705c0..6a46702b 100644 --- a/src/libs/zmbv/zmbv.h +++ b/src/libs/zmbv/zmbv.h @@ -105,12 +105,13 @@ private: INLINE void CopyBlock(int vx, int vy,FrameBlock * block); public: VideoCodec(); + ~VideoCodec(); bool SetupCompress( int _width, int _height); bool SetupDecompress( int _width, int _height); zmbv_format_t BPPFormat( int bpp ); int NeededSize( int _width, int _height, zmbv_format_t _format); - void CompressLines(int lineCount, void *lineData[]); + void CompressLines(int lineCount, const void *lineData[]); bool PrepareCompressFrame(int flags, zmbv_format_t _format, char * pal, void *writeBuf, int writeSize); int FinishCompressFrame( void ); bool DecompressFrame(void * framedata, int size); diff --git a/src/platform/visualc/config.h b/src/platform/visualc/config.h index cd95e71f..fbdd1b14 100644 --- a/src/platform/visualc/config.h +++ b/src/platform/visualc/config.h @@ -8,6 +8,8 @@ /* Define to 1 to enable screenshots, requires libpng */ #define C_SSHOT 1 +/* Define to 1 to enable movie recording, requires zlib built without Z_SOLO */ +#define C_SRECORD 1 /* Define to 1 to use opengl display output support */ #define C_OPENGL 1