diff -r fb4b0c6dfdbd -r 53ffc8853008 hedgewars/uVideoRec.pas --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hedgewars/uVideoRec.pas Mon Jun 04 21:32:30 2012 +0400 @@ -0,0 +1,272 @@ +(* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + *) + +{$INCLUDE "options.inc"} + +unit uVideoRec; + +{$IFDEF UNIX} + {$LINKLIB avwrapper} + {$LINKLIB avutil} + {$LINKLIB avcodec} + {$LINKLIB avformat} +{$ENDIF} + +interface + +var flagPrerecording: boolean = false; + +function BeginVideoRecording: Boolean; +function LoadNextCameraPosition: LongInt; +procedure EncodeFrame; +procedure StopVideoRecording; + +function BeginPreRecording(filePrefix: shortstring): Boolean; +procedure StopPreRecording; +procedure SaveCameraPosition; + +procedure freeModule; + +implementation + +uses uVariables, uUtils, GLunit, SDLh, SysUtils; + +{$IFDEF WIN32} +const AVWrapperLibName = 'libavwrapper.dll'; +{$ENDIF} + +type TAddFileLogRaw = procedure (s: pchar); cdecl; + +{$IFDEF WIN32} +procedure AVWrapper_Init(AddLog: TAddFileLogRaw; filename, soundFile: PChar; width, height, framerate, frequency, channels: LongInt); cdecl; external AVWrapperLibName; +procedure AVWrapper_Close; cdecl; external AVWrapperLibName; +procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external AVWrapperLibName; +{$ELSE} +procedure AVWrapper_Init(AddLog: TAddFileLogRaw; filename, soundFile: PChar; width, height, framerate, frequency, channels: LongInt); cdecl; external; +procedure AVWrapper_Close; cdecl; external; +procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external; +{$ENDIF} + +var YCbCr_Planes: array[0..2] of PByte; + RGB_Buffer: PByte; + + frequency, channels: LongInt; + + cameraFile: TextFile; + audioFile: File; + + numPixels: LongInt; + + framerate: Int64 = 30; + firstTick, nframes: Int64; + + cameraFilePath, soundFilePath: shortstring; + +function BeginVideoRecording: Boolean; +var filename: shortstring; +begin + AddFileLog('BeginVideoRecording'); + + numPixels:= cScreenWidth*cScreenHeight; + +{$IOCHECKS OFF} + // open file with prerecorded camera positions + cameraFilePath:= UserPathPrefix + '/Videos/' + cRecPrefix + '.txtin'; + Assign(cameraFile, cameraFilePath); + Reset(cameraFile); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not read from ' + cameraFilePath); + exit(false); + end; + + ReadLn(cameraFile, frequency, channels); +{$IOCHECKS ON} + + filename:= UserPathPrefix + '/Videos/' + cRecPrefix + '.mp4' + #0; + soundFilePath:= UserPathPrefix + '/Videos/' + cRecPrefix + '.hwsound' + #0; + AVWrapper_Init(@AddFileLogRaw, @filename[1], @soundFilePath[1], cScreenWidth, cScreenHeight, framerate, frequency, channels); + + YCbCr_Planes[0]:= GetMem(numPixels); + YCbCr_Planes[1]:= GetMem(numPixels div 4); + YCbCr_Planes[2]:= GetMem(numPixels div 4); + + if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then + begin + AddFileLog('Error: Could not allocate memory for video recording (YCbCr buffer).'); + exit(false); + end; + + RGB_Buffer:= GetMem(4*numPixels); + if RGB_Buffer = nil then + begin + AddFileLog('Error: Could not allocate memory for video recording (RGB buffer).'); + exit(false); + end; + + BeginVideoRecording:= true; +end; + +procedure StopVideoRecording; +begin + AddFileLog('StopVideoRecording'); + FreeMem(YCbCr_Planes[0], numPixels); + FreeMem(YCbCr_Planes[1], numPixels div 4); + FreeMem(YCbCr_Planes[2], numPixels div 4); + FreeMem(RGB_Buffer, 4*numPixels); + Close(cameraFile); + AVWrapper_Close(); + DeleteFile(cameraFilePath); + DeleteFile(soundFilePath); +end; + +function pixel(x, y, color: LongInt): LongInt; +begin + pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color]; +end; + +procedure EncodeFrame; +var x, y, r, g, b: LongInt; +begin + // read pixels from OpenGL + glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer); + + // convert to YCbCr 4:2:0 format + // Y + for y := 0 to cScreenHeight-1 do + for x := 0 to cScreenWidth-1 do + YCbCr_Planes[0][y*cScreenWidth + x]:= Byte(16 + ((16828*pixel(x,y,0) + 33038*pixel(x,y,1) + 6416*pixel(x,y,2)) shr 16)); + + // Cb and Cr + for y := 0 to cScreenHeight div 2 - 1 do + for x := 0 to cScreenWidth div 2 - 1 do + begin + r:= pixel(2*x,2*y,0) + pixel(2*x+1,2*y,0) + pixel(2*x,2*y+1,0) + pixel(2*x+1,2*y+1,0); + g:= pixel(2*x,2*y,1) + pixel(2*x+1,2*y,1) + pixel(2*x,2*y+1,1) + pixel(2*x+1,2*y+1,1); + b:= pixel(2*x,2*y,2) + pixel(2*x+1,2*y,2) + pixel(2*x,2*y+1,2) + pixel(2*x+1,2*y+1,2); + YCbCr_Planes[1][y*(cScreenWidth div 2) + x]:= Byte(128 + ((-2428*r - 4768*g + 7196*b) shr 16)); + YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16)); + end; + + AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]); +end; + +function LoadNextCameraPosition: LongInt; +var NextTime: LongInt; + NextZoom: LongInt; + NextWorldDx, NextWorldDy: LongInt; +begin +{$IOCHECKS OFF} + if eof(cameraFile) then + exit(-1); + ReadLn(cameraFile, NextTime, NextWorldDx, NextWorldDy, NextZoom); +{$IOCHECKS ON} + if NextTime = 0 then + exit(-1); + WorldDx:= NextWorldDx; + WorldDy:= NextWorldDy; + zoom:= NextZoom/10000; + ZoomValue:= NextZoom/10000; + LoadNextCameraPosition:= NextTime; +end; + +// this procedure may be called from different thread +procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl; +begin + udata:= udata; +{$IOCHECKS OFF} + BlockWrite(audioFile, stream^, len); +{$IOCHECKS ON} +end; + +function BeginPreRecording(filePrefix: shortstring): Boolean; +var format: word; + filename: shortstring; +begin + AddFileLog('BeginPreRecording'); + + nframes:= 0; + firstTick:= SDL_GetTicks(); + + Mix_QuerySpec(@frequency, @format, @channels); + if format <> $8010 then + begin + // TODO: support any audio format + AddFileLog('Error: Unexpected audio format ' + IntToStr(format)); + exit(false); + end; + +{$IOCHECKS OFF} + filename:= UserPathPrefix + '/Videos/' + filePrefix + '.hwsound'; + Assign(audioFile, filename); + Rewrite(audioFile, 1); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not write to ' + filename); + exit(false); + end; + + filename:= UserPathPrefix + '/Videos/' + filePrefix + '.txtout'; + Assign(cameraFile, filename); + Rewrite(cameraFile); + if IOResult <> 0 then + begin + AddFileLog('Error: Could not write to ' + filename); + exit(false); + end; +{$IOCHECKS ON} + WriteLn(cameraFile, inttostr(frequency) + ' ' + inttostr(channels)); + + // register callback for actual audio recording + Mix_SetPostMix(@RecordPostMix, nil); + + flagPrerecording:= true; + BeginPreRecording:= true; +end; + +procedure StopPreRecording; +begin + AddFileLog('StopPreRecording'); + flagPrerecording:= false; + + // call SDL_LockAudio because RecordPostMix may be executing right now + SDL_LockAudio(); + Close(audioFile); + Close(cameraFile); + Mix_SetPostMix(nil, nil); + SDL_UnlockAudio(); +end; + +procedure SaveCameraPosition; +var Ticks: LongInt; +begin + Ticks:= SDL_GetTicks(); + while (Ticks - firstTick)*framerate > nframes*1000 do + begin + WriteLn(cameraFile, inttostr(GameTicks) + ' ' + inttostr(WorldDx) + ' ' + inttostr(WorldDy) + ' ' + inttostr(Round(zoom*10000))); + inc(nframes); + end; +end; + +procedure freeModule; +begin + if flagPrerecording then + StopPreRecording(); +end; + +end.