hedgewars/uVideoRec.pas
changeset 7376 48b79b3ca592
parent 7369 46921fbe76d3
child 7379 aa29a2f16cc7
equal deleted inserted replaced
7373:d5ec4e4eb2d5 7376:48b79b3ca592
    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
   145     begin
   146     begin
   146         AddFileLog('Error: Could not allocate memory for video recording (RGB buffer).');
   147         AddFileLog('Error: Could not allocate memory for video recording (RGB buffer).');
   147         exit(false);
   148         exit(false);
   148     end;
   149     end;
   149 
   150 
       
   151     curTime:= 0;
       
   152     numFrames:= 0;
       
   153     progress:= 0;
   150     BeginVideoRecording:= true;
   154     BeginVideoRecording:= true;
   151 end;
   155 end;
   152 
   156 
   153 procedure StopVideoRecording;
   157 procedure StopVideoRecording;
   154 begin
   158 begin
   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