37 interface |
37 interface |
38 |
38 |
39 var flagPrerecording: boolean = false; |
39 var flagPrerecording: boolean = false; |
40 |
40 |
41 function BeginVideoRecording: Boolean; |
41 function BeginVideoRecording: Boolean; |
42 function LoadNextCameraPosition: LongInt; |
42 function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean; |
43 procedure EncodeFrame; |
43 procedure EncodeFrame; |
44 procedure StopVideoRecording; |
44 procedure StopVideoRecording; |
45 |
45 |
46 procedure BeginPreRecording; |
46 procedure BeginPreRecording; |
47 procedure StopPreRecording; |
47 procedure StopPreRecording; |
86 RGB_Buffer: PByte; |
86 RGB_Buffer: PByte; |
87 cameraFile: File of TFrame; |
87 cameraFile: File of TFrame; |
88 audioFile: File; |
88 audioFile: File; |
89 numPixels: LongWord; |
89 numPixels: LongWord; |
90 startTime, numFrames, curTime, progress, maxProgress: LongWord; |
90 startTime, numFrames, curTime, progress, maxProgress: LongWord; |
91 cameraFilePath, soundFilePath: shortstring; |
91 soundFilePath: shortstring; |
92 thumbnailSaved : Boolean; |
92 thumbnailSaved : Boolean; |
93 |
93 |
94 function BeginVideoRecording: Boolean; |
94 function BeginVideoRecording: Boolean; |
95 var filename, desc: shortstring; |
95 var filename, desc: shortstring; |
96 begin |
96 begin |
97 AddFileLog('BeginVideoRecording'); |
97 AddFileLog('BeginVideoRecording'); |
98 |
98 |
99 {$IOCHECKS OFF} |
99 {$IOCHECKS OFF} |
100 // open file with prerecorded camera positions |
100 // open file with prerecorded camera positions |
101 cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin'; |
101 filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin'; |
102 Assign(cameraFile, cameraFilePath); |
102 Assign(cameraFile, filename); |
103 Reset(cameraFile); |
103 Reset(cameraFile); |
104 maxProgress:= FileSize(cameraFile); |
104 maxProgress:= FileSize(cameraFile); |
105 if IOResult <> 0 then |
105 if IOResult <> 0 then |
106 begin |
106 begin |
107 AddFileLog('Error: Could not read from ' + cameraFilePath); |
107 AddFileLog('Error: Could not read from ' + filename); |
108 exit(false); |
108 exit(false); |
109 end; |
109 end; |
110 {$IOCHECKS ON} |
110 {$IOCHECKS ON} |
111 |
111 |
112 // store some description in output file |
112 // store some description in output file |
161 FreeMem(YCbCr_Planes[1], numPixels div 4); |
161 FreeMem(YCbCr_Planes[1], numPixels div 4); |
162 FreeMem(YCbCr_Planes[2], numPixels div 4); |
162 FreeMem(YCbCr_Planes[2], numPixels div 4); |
163 FreeMem(RGB_Buffer, 4*numPixels); |
163 FreeMem(RGB_Buffer, 4*numPixels); |
164 Close(cameraFile); |
164 Close(cameraFile); |
165 AVWrapper_Close(); |
165 AVWrapper_Close(); |
166 DeleteFile(cameraFilePath); |
166 Erase(cameraFile); |
167 DeleteFile(soundFilePath); |
167 DeleteFile(soundFilePath); |
168 SendIPC(_S'v'); // inform frontend that we finished |
168 SendIPC(_S'v'); // inform frontend that we finished |
169 end; |
169 end; |
170 |
170 |
171 function pixel(x, y, color: LongInt): LongInt; |
171 function pixel(x, y, color: LongInt): LongInt; |
205 SDLNet_Write16(progress*10000 div maxProgress, @s[2]); |
205 SDLNet_Write16(progress*10000 div maxProgress, @s[2]); |
206 SendIPC(s); |
206 SendIPC(s); |
207 inc(numFrames); |
207 inc(numFrames); |
208 end; |
208 end; |
209 |
209 |
210 // returns new game ticks |
210 function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean; |
211 function LoadNextCameraPosition: LongInt; |
|
212 var frame: TFrame; |
211 var frame: TFrame; |
213 begin |
212 begin |
214 LoadNextCameraPosition:= GameTicks; |
|
215 // we need to skip or duplicate frames to match target framerate |
213 // we need to skip or duplicate frames to match target framerate |
216 while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do |
214 while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do |
217 begin |
215 begin |
218 {$IOCHECKS OFF} |
216 {$IOCHECKS OFF} |
219 if eof(cameraFile) then |
217 if eof(cameraFile) then |
220 exit(-1); |
218 exit(false); |
221 BlockRead(cameraFile, frame, 1); |
219 BlockRead(cameraFile, frame, 1); |
222 {$IOCHECKS ON} |
220 {$IOCHECKS ON} |
223 curTime:= frame.realTicks; |
221 curTime:= frame.realTicks; |
224 WorldDx:= frame.CamX; |
222 WorldDx:= frame.CamX; |
225 WorldDy:= frame.CamY + cScreenHeight div 2; |
223 WorldDy:= frame.CamY + cScreenHeight div 2; |
226 zoom:= frame.zoom*cScreenWidth; |
224 zoom:= frame.zoom*cScreenWidth; |
227 ZoomValue:= zoom; |
225 ZoomValue:= zoom; |
228 inc(progress); |
226 inc(progress); |
229 LoadNextCameraPosition:= frame.gameTicks; |
227 newRealTicks:= frame.realTicks; |
230 end; |
228 newGameTicks:= frame.gameTicks; |
|
229 end; |
|
230 LoadNextCameraPosition:= true; |
231 end; |
231 end; |
232 |
232 |
233 // Callback which records sound. |
233 // Callback which records sound. |
234 // This procedure may be called from different thread. |
234 // This procedure may be called from different thread. |
235 procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; |
235 procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; |
249 k:= max(max(cScreenWidth, cScreenHeight) div 400, 1); // here 400 is minimum size of thumbnail |
249 k:= max(max(cScreenWidth, cScreenHeight) div 400, 1); // here 400 is minimum size of thumbnail |
250 MakeScreenshot(thumbpath, k); |
250 MakeScreenshot(thumbpath, k); |
251 thumbnailSaved:= true; |
251 thumbnailSaved:= true; |
252 end; |
252 end; |
253 |
253 |
|
254 // copy file (free pascal doesn't have copy file function) |
|
255 procedure CopyFile(src, dest: shortstring); |
|
256 var inF, outF: file; |
|
257 buffer: array[0..1023] of byte; |
|
258 result: LongInt; |
|
259 begin |
|
260 {$IOCHECKS OFF} |
|
261 result:= 0; // avoid compiler hint |
|
262 |
|
263 Assign(inF, src); |
|
264 Reset(inF, 1); |
|
265 if IOResult <> 0 then |
|
266 begin |
|
267 AddFileLog('Error: Could not read from ' + src); |
|
268 exit; |
|
269 end; |
|
270 |
|
271 Assign(outF, dest); |
|
272 Rewrite(outF, 1); |
|
273 if IOResult <> 0 then |
|
274 begin |
|
275 AddFileLog('Error: Could not write to ' + dest); |
|
276 exit; |
|
277 end; |
|
278 |
|
279 repeat |
|
280 BlockRead(inF, buffer, 1024, result); |
|
281 BlockWrite(outF, buffer, result); |
|
282 until result < 1024; |
|
283 {$IOCHECKS ON} |
|
284 end; |
|
285 |
254 procedure BeginPreRecording; |
286 procedure BeginPreRecording; |
255 var format: word; |
287 var format: word; |
256 filename: shortstring; |
288 filename: shortstring; |
257 frequency, channels: LongInt; |
289 frequency, channels: LongInt; |
258 begin |
290 begin |
259 AddFileLog('BeginPreRecording'); |
291 AddFileLog('BeginPreRecording'); |
260 |
292 |
261 thumbnailSaved:= false; |
293 thumbnailSaved:= false; |
262 RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now()); |
294 RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now()); |
|
295 |
|
296 // If this video is recorded from demo executed directly (without frontend) |
|
297 // then we need to copy demo so that frontend will be able to find it later. |
|
298 if recordFileName <> '' then |
|
299 begin |
|
300 if GameType <> gmtDemo then // this is save and game demo is not recording, abort |
|
301 exit; |
|
302 CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd'); |
|
303 end; |
263 |
304 |
264 Mix_QuerySpec(@frequency, @format, @channels); |
305 Mix_QuerySpec(@frequency, @format, @channels); |
265 AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels)); |
306 AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels)); |
266 if format <> $8010 then |
307 if format <> $8010 then |
267 begin |
308 begin |