60 type TAddFileLogRaw = procedure (s: pchar); cdecl; |
60 type TAddFileLogRaw = procedure (s: pchar); cdecl; |
61 |
61 |
62 {$IFDEF WIN32} |
62 {$IFDEF WIN32} |
63 procedure AVWrapper_Init( |
63 procedure AVWrapper_Init( |
64 AddLog: TAddFileLogRaw; |
64 AddLog: TAddFileLogRaw; |
65 filename, desc, soundFile, format, vcodec, acodec, preset: PChar; |
65 filename, desc, soundFile, format, vcodec, acodec: PChar; |
66 width, height, framerateNum, framerateDen, vquality, aquality: LongInt); cdecl; external AVWrapperLibName; |
66 width, height, framerateNum, framerateDen, vquality: LongInt); cdecl; external AVWrapperLibName; |
67 procedure AVWrapper_Close; cdecl; external AVWrapperLibName; |
67 procedure AVWrapper_Close; cdecl; external AVWrapperLibName; |
68 procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external AVWrapperLibName; |
68 procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external AVWrapperLibName; |
69 {$ELSE} |
69 {$ELSE} |
70 procedure AVWrapper_Init( |
70 procedure AVWrapper_Init( |
71 AddLog: TAddFileLogRaw; |
71 AddLog: TAddFileLogRaw; |
72 filename, desc, soundFile, format, vcodec, acodec, preset: PChar; |
72 filename, desc, soundFile, format, vcodec, acodec: PChar; |
73 width, height, framerateNum, framerateDen, vquality, aquality: LongInt); cdecl; external; |
73 width, height, framerateNum, framerateDen, vquality: LongInt); cdecl; external; |
74 procedure AVWrapper_Close; cdecl; external; |
74 procedure AVWrapper_Close; cdecl; external; |
75 procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external; |
75 procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external; |
76 {$ENDIF} |
76 {$ENDIF} |
77 |
77 |
78 type TFrame = record |
78 type TFrame = record |
79 ticks: LongWord; |
79 realTicks: LongWord; |
|
80 gameTicks: LongWord; |
80 CamX, CamY: LongInt; |
81 CamX, CamY: LongInt; |
81 zoom: single; |
82 zoom: single; |
82 end; |
83 end; |
83 |
84 |
84 var YCbCr_Planes: array[0..2] of PByte; |
85 var YCbCr_Planes: array[0..2] of PByte; |
85 RGB_Buffer: PByte; |
86 RGB_Buffer: PByte; |
86 cameraFile: File of TFrame; |
87 cameraFile: File of TFrame; |
87 audioFile: File; |
88 audioFile: File; |
88 numPixels: LongWord; |
89 numPixels: LongWord; |
89 startTime, numFrames: LongWord; |
90 startTime, numFrames, curTime, progress, maxProgress: LongWord; |
90 cameraFilePath, soundFilePath: shortstring; |
91 cameraFilePath, soundFilePath: shortstring; |
91 thumbnailSaved : Boolean; |
92 thumbnailSaved : Boolean; |
92 |
93 |
93 function BeginVideoRecording: Boolean; |
94 function BeginVideoRecording: Boolean; |
94 var filename, desc: shortstring; |
95 var filename, desc: shortstring; |
95 begin |
96 begin |
96 AddFileLog('BeginVideoRecording'); |
97 AddFileLog('BeginVideoRecording'); |
97 |
|
98 numPixels:= cScreenWidth*cScreenHeight; |
|
99 |
98 |
100 {$IOCHECKS OFF} |
99 {$IOCHECKS OFF} |
101 // open file with prerecorded camera positions |
100 // open file with prerecorded camera positions |
102 cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin'; |
101 cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin'; |
103 Assign(cameraFile, cameraFilePath); |
102 Assign(cameraFile, cameraFilePath); |
104 Reset(cameraFile); |
103 Reset(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 ' + cameraFilePath); |
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 desc:= ''; |
113 desc:= ''; |
113 if UserNick <> '' then |
114 if UserNick <> '' then |
114 desc+= 'Player: ' + UserNick + #10; |
115 desc+= 'Player: ' + UserNick + #10; |
115 if recordFileName <> '' then |
116 if recordFileName <> '' then |
116 desc+= 'Record: ' + recordFileName + #10; |
117 desc+= 'Record: ' + recordFileName + #10; |
124 filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + #0; |
125 filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + #0; |
125 soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw' + #0; |
126 soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw' + #0; |
126 cAVFormat+= #0; |
127 cAVFormat+= #0; |
127 cAudioCodec+= #0; |
128 cAudioCodec+= #0; |
128 cVideoCodec+= #0; |
129 cVideoCodec+= #0; |
129 cVideoPreset+= #0; |
130 AVWrapper_Init(@AddFileLogRaw, @filename[1], @desc[1], @soundFilePath[1], @cAVFormat[1], @cVideoCodec[1], @cAudioCodec[1], |
130 AVWrapper_Init(@AddFileLogRaw, @filename[1], @desc[1], @soundFilePath[1], @cAVFormat[1], @cVideoCodec[1], @cAudioCodec[1], @cVideoPreset[1], |
131 cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cVideoQuality); |
131 cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cAudioQuality, cVideoQuality); |
132 |
132 |
133 numPixels:= cScreenWidth*cScreenHeight; |
133 YCbCr_Planes[0]:= GetMem(numPixels); |
134 YCbCr_Planes[0]:= GetMem(numPixels); |
134 YCbCr_Planes[1]:= GetMem(numPixels div 4); |
135 YCbCr_Planes[1]:= GetMem(numPixels div 4); |
135 YCbCr_Planes[2]:= GetMem(numPixels div 4); |
136 YCbCr_Planes[2]:= GetMem(numPixels div 4); |
136 |
137 |
137 if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then |
138 if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then |
159 FreeMem(RGB_Buffer, 4*numPixels); |
163 FreeMem(RGB_Buffer, 4*numPixels); |
160 Close(cameraFile); |
164 Close(cameraFile); |
161 AVWrapper_Close(); |
165 AVWrapper_Close(); |
162 DeleteFile(cameraFilePath); |
166 DeleteFile(cameraFilePath); |
163 DeleteFile(soundFilePath); |
167 DeleteFile(soundFilePath); |
164 SendIPC(_S'v'); |
168 SendIPC(_S'v'); // inform frontend that we finished |
165 end; |
169 end; |
166 |
170 |
167 function pixel(x, y, color: LongInt): LongInt; |
171 function pixel(x, y, color: LongInt): LongInt; |
168 begin |
172 begin |
169 pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color]; |
173 pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color]; |
170 end; |
174 end; |
171 |
175 |
172 procedure EncodeFrame; |
176 procedure EncodeFrame; |
173 var x, y, r, g, b: LongInt; |
177 var x, y, r, g, b: LongInt; |
|
178 s: shortstring; |
174 begin |
179 begin |
175 // read pixels from OpenGL |
180 // read pixels from OpenGL |
176 glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer); |
181 glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer); |
177 |
182 |
178 // convert to YCbCr 4:2:0 format |
183 // convert to YCbCr 4:2:0 format |
192 YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16)); |
197 YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16)); |
193 end; |
198 end; |
194 |
199 |
195 AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]); |
200 AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]); |
196 |
201 |
197 // inform frontend that we have encoded new frame (p for progress) |
202 // inform frontend that we have encoded new frame |
198 SendIPC(_S'p'); |
203 s[0]:= #3; |
|
204 s[1]:= 'p'; // p for progress |
|
205 SDLNet_Write16(progress*10000 div maxProgress, @s[2]); |
|
206 SendIPC(s); |
|
207 inc(numFrames); |
199 end; |
208 end; |
200 |
209 |
201 // returns new game ticks |
210 // returns new game ticks |
202 function LoadNextCameraPosition: LongInt; |
211 function LoadNextCameraPosition: LongInt; |
203 var frame: TFrame; |
212 var frame: TFrame; |
204 begin |
213 begin |
205 {$IOCHECKS OFF} |
214 LoadNextCameraPosition:= GameTicks; |
206 if eof(cameraFile) then |
215 // we need to skip or duplicate frames to match target framerate |
207 exit(-1); |
216 while Int64(curTime)*cVideoFramerateNum < Int64(numFrames)*cVideoFramerateDen*1000 do |
208 BlockRead(cameraFile, frame, 1); |
217 begin |
209 {$IOCHECKS ON} |
218 {$IOCHECKS OFF} |
210 WorldDx:= frame.CamX; |
219 if eof(cameraFile) then |
211 WorldDy:= frame.CamY + cScreenHeight div 2; |
220 exit(-1); |
212 zoom:= frame.zoom*cScreenWidth; |
221 BlockRead(cameraFile, frame, 1); |
213 ZoomValue:= zoom; |
222 {$IOCHECKS ON} |
214 LoadNextCameraPosition:= frame.ticks; |
223 curTime:= frame.realTicks; |
|
224 WorldDx:= frame.CamX; |
|
225 WorldDy:= frame.CamY + cScreenHeight div 2; |
|
226 zoom:= frame.zoom*cScreenWidth; |
|
227 ZoomValue:= zoom; |
|
228 inc(progress); |
|
229 LoadNextCameraPosition:= frame.gameTicks; |
|
230 end; |
215 end; |
231 end; |
216 |
232 |
217 // Callback which records sound. |
233 // Callback which records sound. |
218 // This procedure may be called from different thread. |
234 // This procedure may be called from different thread. |
219 procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; |
235 procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; |
245 filename: shortstring; |
261 filename: shortstring; |
246 frequency, channels: LongInt; |
262 frequency, channels: LongInt; |
247 begin |
263 begin |
248 AddFileLog('BeginPreRecording'); |
264 AddFileLog('BeginPreRecording'); |
249 |
265 |
250 numFrames:= 0; |
|
251 thumbnailSaved:= false; |
266 thumbnailSaved:= false; |
252 RecPrefix:= FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()); |
267 RecPrefix:= FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()) + inttostr(GameTicks); |
253 |
268 |
254 Mix_QuerySpec(@frequency, @format, @channels); |
269 Mix_QuerySpec(@frequency, @format, @channels); |
255 AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels)); |
270 AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels)); |
256 if format <> $8010 then |
271 if format <> $8010 then |
257 begin |
272 begin |
308 if not thumbnailSaved then |
323 if not thumbnailSaved then |
309 SaveThumbnail(); |
324 SaveThumbnail(); |
310 end; |
325 end; |
311 |
326 |
312 procedure SaveCameraPosition; |
327 procedure SaveCameraPosition; |
313 var curTime: LongInt; |
328 var frame: TFrame; |
314 frame: TFrame; |
|
315 begin |
329 begin |
316 if (not thumbnailSaved) and (ScreenFade = sfNone) then |
330 if (not thumbnailSaved) and (ScreenFade = sfNone) then |
317 SaveThumbnail(); |
331 SaveThumbnail(); |
318 |
332 |
319 curTime:= SDL_GetTicks(); |
333 frame.realTicks:= SDL_GetTicks() - startTime; |
320 while Int64(curTime - startTime)*cVideoFramerateNum > Int64(numFrames)*cVideoFramerateDen*1000 do |
334 frame.gameTicks:= GameTicks; |
321 begin |
335 frame.CamX:= WorldDx; |
322 frame.ticks:= GameTicks; |
336 frame.CamY:= WorldDy - cScreenHeight div 2; |
323 frame.CamX:= WorldDx; |
337 frame.zoom:= zoom/cScreenWidth; |
324 frame.CamY:= WorldDy - cScreenHeight div 2; |
338 BlockWrite(cameraFile, frame, 1); |
325 frame.zoom:= zoom/cScreenWidth; |
|
326 BlockWrite(cameraFile, frame, 1); |
|
327 inc(numFrames); |
|
328 end; |
|
329 end; |
339 end; |
330 |
340 |
331 procedure freeModule; |
341 procedure freeModule; |
332 begin |
342 begin |
333 if flagPrerecording then |
343 if flagPrerecording then |