hedgewars/uVideoRec.pas
author Stepan777 <stepik-777@mail.ru>
Mon, 11 Jun 2012 18:15:30 +0400
changeset 7235 baa69bd025d9
parent 7198 5debd5fe526e
child 7280 fd707afbc3a2
permissions -rw-r--r--
1. Implement new page in frontend with options for video recording. 2. Store temoprary files in different directory

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * 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;

procedure BeginPreRecording;
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, finalFilename, soundFile, format, vcodec, acodec, preset: PChar;
              width, height, framerateNum, framerateDen, frequency, channels, vquality, aquality: 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, finalFilename, soundFile, format, vcodec, acodec, preset: PChar;
              width, height, framerateNum, framerateDen, frequency, channels, vquality, aquality: 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;

    firstTick, nframes: Int64;

    cameraFilePath, soundFilePath: shortstring;

function BeginVideoRecording: Boolean;
var filename, finalFilename: shortstring;
begin
    AddFileLog('BeginVideoRecording');

    numPixels:= cScreenWidth*cScreenHeight;

{$IOCHECKS OFF}
    // open file with prerecorded camera positions
    cameraFilePath:= UserPathPrefix + '/VideoTemp/' + 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 + '/VideoTemp/' + cRecPrefix + #0;
    finalFilename:= UserPathPrefix + '/Videos/' + cRecPrefix + #0;
    soundFilePath:= UserPathPrefix + '/VideoTemp/' + cRecPrefix + '.sw' + #0;
    cAVFormat+= #0;
    cAudioCodec+= #0;
    cVideoCodec+= #0;
    cVideoPreset+= #0;
    AVWrapper_Init(@AddFileLogRaw, @filename[1], @finalFilename[1], @soundFilePath[1], @cAVFormat[1], @cVideoCodec[1], @cAudioCodec[1], @cVideoPreset[1],
                   cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, frequency, channels, cAudioQuality, cVideoQuality);

    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;

// returns new game ticks
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}
    WorldDx:= NextWorldDx;
    WorldDy:= NextWorldDy + cScreenHeight div 2;
    zoom:= NextZoom/10000*cScreenWidth;
    ZoomValue:= zoom;
    LoadNextCameraPosition:= NextTime;
end;

// Callback which records sound.
// This procedure may be called from different thread.
procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl;
begin
    udata:= udata; // avoid warning
{$IOCHECKS OFF}
    BlockWrite(audioFile, stream^, len);
{$IOCHECKS ON}
end;

procedure BeginPreRecording;
var format: word;
    filePrefix, filename: shortstring;
begin
    AddFileLog('BeginPreRecording');

    nframes:= 0;
    firstTick:= SDL_GetTicks();

    filePrefix:= FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now());

    Mix_QuerySpec(@frequency, @format, @channels);
    if format <> $8010 then
    begin
        // TODO: support any audio format
        AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
        exit;
    end;

{$IOCHECKS OFF}
    // create sound file
    filename:= UserPathPrefix + '/VideoTemp/' + filePrefix + '.sw';
    Assign(audioFile, filename);
    Rewrite(audioFile, 1);
    if IOResult <> 0 then
    begin
        AddFileLog('Error: Could not write to ' + filename);
        exit;
    end;

    // create file with camera positions
    filename:= UserPathPrefix + '/VideoTemp/' + filePrefix + '.txtout';
    Assign(cameraFile, filename);
    Rewrite(cameraFile);
    if IOResult <> 0 then
    begin
        AddFileLog('Error: Could not write to ' + filename);
        exit;
    end;
{$IOCHECKS ON}
    WriteLn(cameraFile, inttostr(frequency) + ' ' + inttostr(channels));

    // register callback for actual audio recording
    Mix_SetPostMix(@RecordPostMix, nil);

    flagPrerecording:= 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)*cVideoFramerateNum > nframes*cVideoFramerateDen*1000 do
    begin
        WriteLn(cameraFile, inttostr(GameTicks) + ' ' + inttostr(WorldDx) + ' ' + inttostr(WorldDy - cScreenHeight div 2) + ' ' + inttostr(Round(zoom*10000/cScreenWidth)));
        inc(nframes);
    end;
end;

procedure freeModule;
begin
    if flagPrerecording then
        StopPreRecording();
end;

end.