1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #if defined AVI_CAPTURING
4 #include "AVIGenerator.h"
5
6 #include "Rendering/GlobalRendering.h"
7 #include "Rendering/GL/myGL.h"
8 #include "Game/GameVersion.h"
9 #include "System/Log/ILog.h"
10 #include "System/SpringApp.h"
11 #include "System/Platform/Threading.h"
12
13 #include <windows.h>
14
15 #include <boost/bind.hpp>
16 #include <cassert>
17
18 #if defined(_WIN32) && !defined(__MINGW32__)
19 #pragma message("Adding library: vfw32.lib")
20 #pragma comment(lib, "vfw32.lib")
21 #endif
22
23
24
25
initVFW()26 bool CAVIGenerator::initVFW() {
27 #if defined(_WIN32) && defined(__MINGW32__)
28
29 msvfw32 = LoadLibrary("msvfw32.dll");
30 avifil32 = LoadLibrary("avifil32.dll");
31
32 VideoForWindowsVersion_ptr=(VideoForWindowsVersion_type)GetProcAddress(msvfw32, "VideoForWindowsVersion");
33 ICCompressorChoose_ptr=(ICCompressorChoose_type)GetProcAddress(msvfw32, "ICCompressorChoose");
34 ICCompressorFree_ptr=(ICCompressorFree_type)GetProcAddress(msvfw32, "ICCompressorFree");
35 ICOpen_ptr=(ICOpen_type)GetProcAddress(msvfw32, "ICOpen");
36
37 AVIFileInit_ptr=(AVIFileInit_type)GetProcAddress(avifil32, "AVIFileInit");
38 AVIFileOpenA_ptr=(AVIFileOpenA_type)GetProcAddress(avifil32, "AVIFileOpenA");
39 AVIFileCreateStreamA_ptr=(AVIFileCreateStreamA_type)GetProcAddress(avifil32, "AVIFileCreateStreamA");
40 AVIMakeCompressedStream_ptr=(AVIMakeCompressedStream_type)GetProcAddress(avifil32, "AVIMakeCompressedStream");
41 AVIStreamSetFormat_ptr=(AVIStreamSetFormat_type)GetProcAddress(avifil32, "AVIStreamSetFormat");
42 AVIStreamRelease_ptr=(AVIStreamRelease_type)GetProcAddress(avifil32, "AVIStreamRelease");
43 AVIFileRelease_ptr=(AVIFileRelease_type)GetProcAddress(avifil32, "AVIFileRelease");
44 AVIFileExit_ptr=(AVIFileExit_type)GetProcAddress(avifil32, "AVIFileExit");
45 AVIStreamWrite_ptr=(AVIStreamWrite_type)GetProcAddress(avifil32, "AVIStreamWrite");
46
47
48 if (NULL == msvfw32 || NULL == avifil32) {
49 errorMsg="LoadLibrary failed.";
50 return false;
51 }
52
53 if (!VideoForWindowsVersion_ptr || !ICCompressorChoose_ptr || !ICCompressorFree_ptr || !ICOpen_ptr) {
54 errorMsg="initVFW Error.";
55 return false;
56 }
57
58 if (!AVIFileInit_ptr || !AVIFileOpenA_ptr || !AVIFileCreateStreamA_ptr ||
59 !AVIMakeCompressedStream_ptr || !AVIStreamSetFormat_ptr || !AVIStreamRelease_ptr ||
60 !AVIFileRelease_ptr || !AVIFileExit_ptr || !AVIStreamWrite_ptr) {
61 errorMsg="initVFW Error.";
62 return false;
63 }
64 #else
65
66 VideoForWindowsVersion_ptr =&VideoForWindowsVersion;
67 AVIFileInit_ptr = &AVIFileInit;
68 AVIFileOpenA_ptr = &AVIFileOpenA;
69 AVIFileCreateStreamA_ptr = &AVIFileCreateStreamA;
70 AVIMakeCompressedStream_ptr = &AVIMakeCompressedStream;
71 AVIStreamSetFormat_ptr = &AVIStreamSetFormat;
72 AVIStreamRelease_ptr = &AVIStreamRelease;
73 AVIFileRelease_ptr = &AVIFileRelease;
74 AVIFileExit_ptr = &AVIFileExit;
75 AVIStreamWrite_ptr = &AVIStreamWrite;
76 ICCompressorChoose_ptr = &ICCompressorChoose;
77 ICCompressorFree_ptr = &ICCompressorFree;
78 ICOpen_ptr = &ICOpen;
79 #endif
80 return true;
81 }
82
83
CAVIGenerator(const std::string & fileName,int videoSizeX,int videoSizeY,DWORD videoFPS)84 CAVIGenerator::CAVIGenerator(const std::string& fileName, int videoSizeX, int videoSizeY, DWORD videoFPS)
85 :
86 fileName(fileName),
87 videoFPS(videoFPS),
88 errorMsg("Ok"),
89 quitAVIgen(false),
90 AVIThread(0),
91 readBuf(NULL),
92 m_lFrame(0),
93 m_pAVIFile(NULL),
94 m_pStream(NULL),
95 m_pStreamCompressed(NULL) {
96
97 assert(videoSizeX % 4 == 0);
98 assert(videoSizeY % 4 == 0);
99
100 memset(&bitmapInfo, 0, sizeof(BITMAPINFOHEADER));
101 bitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
102 bitmapInfo.biWidth = videoSizeX;
103 bitmapInfo.biHeight = videoSizeY;
104 bitmapInfo.biPlanes = 1;
105 bitmapInfo.biBitCount = 24;
106 bitmapInfo.biSizeImage = videoSizeX * videoSizeY * 3;
107 bitmapInfo.biCompression = BI_RGB;
108
109 if (!initVFW()) {
110 quitAVIgen = true;
111 }
112 }
113
114
~CAVIGenerator()115 CAVIGenerator::~CAVIGenerator() {
116
117 if (AVIThread) {
118 {
119 boost::mutex::scoped_lock lock(AVIMutex);
120 quitAVIgen = true;
121 AVICondition.notify_all();
122 }
123 AVIThread->join();
124
125 delete AVIThread;
126 AVIThread = 0;
127 }
128
129 while (!freeImageBuffers.empty()) {
130 unsigned char* tmp = freeImageBuffers.front();
131 freeImageBuffers.pop_front();
132 delete [] tmp;
133 }
134 while (!imageBuffers.empty()) {
135 unsigned char* tmp = imageBuffers.front();
136 imageBuffers.pop_front();
137 delete [] tmp;
138 }
139
140 delete [] readBuf;
141 readBuf = 0;
142
143
144 ReleaseAVICompressionEngine();
145 LOG("Finished writing avi file %s", fileName.c_str());
146
147 // Just checking that all allocated ressources have been released.
148 assert(AVIThread == NULL);
149 assert(m_pAVIFile == NULL);
150 assert(m_pStream == NULL);
151 assert(m_pStreamCompressed == NULL);
152 assert(freeImageBuffers.empty());
153 assert(imageBuffers.empty());
154 assert(readBuf == NULL);
155 }
156
157
ReleaseAVICompressionEngine()158 void CAVIGenerator::ReleaseAVICompressionEngine() {
159
160 if (m_pStream && AVIStreamRelease_ptr) {
161 AVIStreamRelease_ptr(m_pStream);
162 m_pStream=NULL;
163 }
164
165 if(m_pStreamCompressed && AVIStreamRelease_ptr){
166 AVIStreamRelease_ptr(m_pStreamCompressed);
167 m_pStreamCompressed=NULL;
168 }
169
170 if (m_pAVIFile && AVIFileRelease_ptr) {
171 AVIFileRelease_ptr(m_pAVIFile);
172 m_pAVIFile=NULL;
173 }
174
175 if (ICCompressorFree_ptr) {
176 ICCompressorFree_ptr(&cv);
177 }
178 if (AVIFileExit_ptr) {
179 AVIFileExit_ptr();
180 }
181
182 if (msvfw32) {
183 FreeLibrary(msvfw32);
184 }
185 if (avifil32) {
186 FreeLibrary(avifil32);
187 }
188 }
189
190
InitAVICompressionEngine()191 HRESULT CAVIGenerator::InitAVICompressionEngine() {
192
193 AVISTREAMINFO strHdr; // Information for a single stream
194 AVICOMPRESSOPTIONS opts;
195 HRESULT hr;
196
197
198 // Let us make sure we are running on 1.1
199 DWORD wVer = HIWORD(VideoForWindowsVersion_ptr());
200 if (wVer < 0x010a) {
201 // oops, we are too old, blow out of here
202 errorMsg = "Version of Video for Windows too old. Come on, join the 21th century!";
203 return S_FALSE;
204 }
205
206 // Initialize AVI engine
207 AVIFileInit_ptr();
208
209
210 memset(&cv, 0, sizeof(COMPVARS));
211 cv.cbSize = sizeof(COMPVARS);
212 cv.dwFlags = ICMF_COMPVARS_VALID;
213 cv.fccHandler = mmioFOURCC('x', 'v', 'i', 'd'); // default video codec
214 cv.lQ = ICQUALITY_DEFAULT;
215
216
217 //Set the compression, prompting dialog if necessary
218 if (!ICCompressorChoose_ptr(NULL, ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME, &bitmapInfo, NULL, &cv, NULL)) {
219 return S_FALSE;
220 }
221
222 // Fill in the header for the video stream....
223 memset(&strHdr, 0, sizeof(AVISTREAMINFO));
224 strHdr.fccType = streamtypeVIDEO; // video stream type
225 strHdr.fccHandler = cv.fccHandler;
226 strHdr.dwScale = 1; // should be one for video
227 strHdr.dwRate = videoFPS; // fps
228 strHdr.dwSuggestedBufferSize = bitmapInfo.biSizeImage; // Recommended buffer size, in bytes, for the stream.
229 SetRect(&strHdr.rcFrame, 0, 0, bitmapInfo.biWidth, bitmapInfo.biHeight);
230 strcpy(strHdr.szName, "Spring video.");
231
232
233 memset(&opts, 0, sizeof(AVICOMPRESSOPTIONS));
234 opts.fccType = streamtypeVIDEO;
235 opts.fccHandler = cv.fccHandler;
236 opts.dwKeyFrameEvery = cv.lKey;
237 opts.dwQuality = cv.lQ;
238 opts.dwBytesPerSecond = cv.lDataRate;
239 opts.dwFlags = ((cv.lDataRate > 0) ? AVICOMPRESSF_DATARATE : 0) | ((cv.lKey > 0) ? AVICOMPRESSF_KEYFRAMES : 0);
240 opts.lpFormat = NULL;
241 opts.cbFormat = 0;
242 opts.lpParms = cv.lpState;
243 opts.cbParms = cv.cbState;
244 opts.dwInterleaveEvery = 0;
245
246
247
248 //Open the movie file for writing
249 hr = AVIFileOpenA_ptr(&m_pAVIFile, // Address to contain the new file interface pointer
250 fileName.c_str(), // Null-terminated string containing the name of the file to open
251 OF_WRITE | OF_CREATE | OF_SHARE_EXCLUSIVE, // Access mode to use when opening the file.
252 NULL); // use handler determined from file extension.
253
254 if (hr != AVIERR_OK) {
255 errorMsg = "AVI Engine failed to initialize. Check filename ";
256 errorMsg += fileName;
257 // Translate error code
258 switch (hr) {
259 case AVIERR_BADFORMAT: {
260 errorMsg += "The file couldn't be read, indicating a corrupt file or an unrecognized format.";
261 } break;
262 case AVIERR_MEMORY: {
263 errorMsg += "The file could not be opened because of insufficient memory.";
264 } break;
265 case AVIERR_FILEREAD: {
266 errorMsg += "A disk error occurred while reading the file.";
267 } break;
268 case AVIERR_FILEOPEN: {
269 errorMsg += "A disk error occurred while opening the file.";
270 } break;
271 case REGDB_E_CLASSNOTREG: {
272 errorMsg += "According to the registry, the type of file specified in AVIFileOpen does not have a handler to process it";
273 } break;
274 default: {
275 errorMsg += "Unknown error.";
276 }
277 }
278 return hr;
279 }
280
281
282 // Create the stream
283 hr = AVIFileCreateStreamA_ptr(m_pAVIFile, // file pointer
284 &m_pStream, // returned stream pointer
285 &strHdr); // stream header
286
287 if (hr != AVIERR_OK) {
288 errorMsg = "AVI Stream creation failed. Check Bitmap info.";
289 if (hr == AVIERR_READONLY) {
290 errorMsg += " Read only file.";
291 }
292 return hr;
293 }
294
295
296 // Create a compressed stream using codec options.
297 hr = AVIMakeCompressedStream_ptr(&m_pStreamCompressed, m_pStream, &opts, NULL);
298 if (hr != AVIERR_OK) {
299 errorMsg = "AVI Compressed Stream creation failed.";
300
301 switch (hr) {
302 case AVIERR_NOCOMPRESSOR: {
303 errorMsg += " A suitable compressor cannot be found.";
304 } break;
305 case AVIERR_MEMORY: {
306 errorMsg += " There is not enough memory to complete the operation.";
307 } break;
308 case AVIERR_UNSUPPORTED: {
309 errorMsg += "Compression is not supported for this type of data. This error might be returned if you try to compress data that is not audio or video.";
310 } break;
311 default: {
312 errorMsg += "Unknown error.";
313 }
314 }
315 return hr;
316 }
317
318
319 //Sets the format of a stream at the specified position
320 hr = AVIStreamSetFormat_ptr(m_pStreamCompressed,
321 0, // position
322 &bitmapInfo, // stream format
323 bitmapInfo.biSize + // format size
324 bitmapInfo.biClrUsed * sizeof(RGBQUAD));
325
326 if (hr != AVIERR_OK) {
327 errorMsg = "AVI Compressed Stream format setting failed.";
328 return hr;
329 }
330
331 return hr;
332 }
333
334
InitEngine()335 bool CAVIGenerator::InitEngine() {
336
337 if (quitAVIgen) {
338 // error in initVFW
339 return false;
340 }
341
342 for (int i=0; i < 10; i++) {
343 unsigned char* tmpBuf = new unsigned char[bitmapInfo.biSizeImage];
344 freeImageBuffers.push_back(tmpBuf);
345 }
346
347 HWND mainWindow = FindWindow(NULL, ("Spring " + SpringVersion::GetFull()).c_str());
348 if (globalRendering->fullScreen) {
349 ShowWindow(mainWindow, SW_SHOWMINNOACTIVE);
350 }
351 boost::mutex::scoped_lock lock(AVIMutex);
352 AVIThread = new boost::thread(boost::bind(&CAVIGenerator::AVIGeneratorThreadProc, this));
353 AVICondition.wait(lock); // Wait until InitAVICompressionEngine() completes.
354 if (globalRendering->fullScreen) {
355 ShowWindow(mainWindow, SW_RESTORE);
356 }
357 return !quitAVIgen;
358 }
359
360
AddFrame(unsigned char * pixelData)361 HRESULT CAVIGenerator::AddFrame(unsigned char* pixelData){
362
363 // compress bitmap
364 HRESULT hr = AVIStreamWrite_ptr(m_pStreamCompressed, // stream pointer
365 m_lFrame, // time of this frame
366 1, // number to write
367 pixelData, // image buffer
368 bitmapInfo.biSizeImage, // size of this frame
369 AVIIF_KEYFRAME, // flags....
370 NULL,
371 NULL);
372
373 // updating frame counter
374 m_lFrame++;
375
376 return hr;
377 }
378
379
readOpenglPixelDataThreaded()380 bool CAVIGenerator::readOpenglPixelDataThreaded() {
381
382 while (true) {
383 boost::mutex::scoped_lock lock(AVIMutex);
384 if (quitAVIgen) {
385 return false;
386 } else if (readBuf != NULL) {
387 imageBuffers.push_back(readBuf);
388 readBuf = NULL;
389 AVICondition.notify_all();
390 }
391 if (freeImageBuffers.empty()) {
392 AVICondition.wait(lock);
393 } else {
394 readBuf = freeImageBuffers.front();
395 freeImageBuffers.pop_front();
396 break;
397 }
398 }
399
400 glReadPixels(0, 0, bitmapInfo.biWidth, bitmapInfo.biHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, readBuf);
401 return true;
402 }
403
404
405 __FORCE_ALIGN_STACK__
AVIGeneratorThreadProc()406 void CAVIGenerator::AVIGeneratorThreadProc() {
407
408 Threading::SetThreadName("avi-recorder");
409
410 //Run init from the encoding thread because custom controls displayed by codecs
411 //sends window messages to the thread it started from, thus deadlocking if
412 //sending to the main thread whilst it is blocking on readOpenglPixelDataThreaded().
413 quitAVIgen = InitAVICompressionEngine() != AVIERR_OK;
414 AVICondition.notify_all();
415 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
416 unsigned char* localWriteBuf = 0;
417 bool encoderError = false;
418
419 while (true) {
420 {
421 boost::mutex::scoped_lock lock(AVIMutex);
422 if (encoderError) {
423 LOG_L(L_ERROR, "The avi generator terminated unexpectedly!");
424 quitAVIgen = true;
425 // Do not let the main thread wait, as the encoder will not
426 // process and free the remaining content in imageBuffers.
427 AVICondition.notify_all();
428 }
429 if (quitAVIgen) {
430 break;
431 }
432 if (localWriteBuf != 0) {
433 freeImageBuffers.push_back(localWriteBuf);
434 localWriteBuf = 0;
435 AVICondition.notify_all();
436 }
437 if (imageBuffers.empty()) {
438 AVICondition.wait(lock);
439 continue;
440 }
441 localWriteBuf = imageBuffers.front();
442 imageBuffers.pop_front();
443 }
444 if (AddFrame(localWriteBuf)) {
445 encoderError = true;
446 }
447 MSG msg;
448 // Handle all messages the codec sends.
449 while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
450 if (GetMessage(&msg, NULL, 0, 0) < 0) {
451 encoderError = true;
452 break;
453 }
454 TranslateMessage(&msg);
455 DispatchMessage(&msg);
456 }
457 }
458 delete [] localWriteBuf;
459 }
460
461 #endif // defined AVI_CAPTURING
462
463