#include "std.h"
#include "emul.h"
#include "vars.h"
#include "util.h"
#include "savevid.h"
//#define DEBUG 1
//handles
static HANDLE hPipe=INVALID_HANDLE_VALUE;
static STARTUPINFO si;
static PROCESS_INFORMATION pi;
TSVSet SVSet; //settings
int videosaver_state; //0-not saving, 1-saving
//AVI global hdr + video hdrs
static char AVIRIFF[]=
"\x52\x49\x46\x46\x00\x00\x00\x00\x41\x56\x49\x20\x4C\x49\x53\x54" //RIFF size=0 (inf)
"\x3c\x01\x00\x00\x68\x64\x72\x6C\x61\x76\x69\x68\x38\x00\x00\x00"
"\x20\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00"
"\x80\x02\x00\x00\xE0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x49\x53\x54\x80\x00\x00\x00"
"\x73\x74\x72\x6C\x73\x74\x72\x68\x38\x00\x00\x00\x76\x69\x64\x73"
"\x44\x49\x42\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x01\x00\x00\x00\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00"
"\x80\x02\xE0\x01\x73\x74\x72\x66\x28\x00\x00\x00\x28\x00\x00\x00"
"\x80\x02\x00\x00\xE0\x01\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00"
"\x00\x10\x0E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x4A\x55\x4E\x4B\x04\x00\x00\x00\x00\x00\x00\x00"
;
//AVI audio hdrs
static char AVIRIFF2[]=
"\x4C\x49\x53\x54\x68\x00\x00\x00\x73\x74\x72\x6C\x73\x74\x72\x68"
"\x38\x00\x00\x00\x61\x75\x64\x73\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x10\xB1\x02\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF"
"\x04\x00\x00\x00\x6D\x00\x70\x00\x67\x00\x3B\x00\x73\x74\x72\x66"
"\x10\x00\x00\x00\x01\x00\x02\x00\x44\xAC\x00\x00\x10\xB1\x02\x00"
"\x04\x00\x10\x00\x4A\x55\x4E\x4B\x04\x00\x00\x00\x00\x00\x00\x00"
;
//AVI start streams
static char AVIRIFF3[]=
"\x4C\x49\x53\x54\x00\x00\x00\x00\x6D\x6F\x76\x69"; //LIST movi size=0
//AVI stream data headers
static char avi_frameh_vid[] = "00db "; //DIB
static char avi_frameh_aud[] = "01wb "; //wave
//proto
static int pipewrite(HANDLE hPipe, char *buf, unsigned len);
static void savevideo_finish();
//init:
//ffmpeg_exec - path/name of ffmpeg executable (ex: "ffmpeg.exe")
//ffmpeg_param - params for ffmpeg output video (ex: "-c:a libmp3lame -b:a 320k")
//out_fname - output video name (ex: "video0.avi")
//newcons - create new console for ffmpeg (0/1)
//w,h - width, height (ex: 460, 480)
//fps - frames per second (ex: 50)
//sndfq - sound sample rate (ex: 44100)
static int savevideo_init(const char *ffmpeg_exec, const char *ffmpeg_param, const char *out_fname, char newcons, int w, int h, int fps, int sndfq)
{
//create named pipe for stream video to ffmpeg
hPipe=CreateNamedPipe(
PIPENAME, // pipe name
PIPE_ACCESS_OUTBOUND, // write access
PIPE_TYPE_BYTE | // byte type pipe
PIPE_READMODE_BYTE | // byte-read mode
PIPE_NOWAIT, // non blocking mode (for async connect)
1, // max. instances
PIPESIZE, // output buffer size
1024, // input buffer size
0, // client time-out
nullptr); // default security attribute
if(hPipe==INVALID_HANDLE_VALUE)
{
color(CONSCLR_ERROR); printf("error: CreateNamedPipe failed.\n");
return -1;
}
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: named pipe '%s' created.\n",PIPENAME);
#endif
//start ffmpeg process
char args[VS_MAX_FFPATH+VS_MAX_FFPARM+VS_MAX_FFVOUT+100];
_snprintf(args, sizeof(args), "\"%s\" -i %s %s -y %s", ffmpeg_exec, PIPENAME, ffmpeg_param, out_fname);
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: %s\n", args);
#endif
memset(&si, 0, sizeof(si));
si.cb=sizeof(si);
//si.wShowWindow=SW_SHOW;
si.wShowWindow=SW_MINIMIZE;
//si.wShowWindow=SW_HIDE;
si.dwFlags=STARTF_USESHOWWINDOW;
memset(&pi, 0, sizeof(pi));
if(!CreateProcess(nullptr, // no app name
args, // cmd line
nullptr, // proc attr
nullptr, // thread attr
FALSE, // Inherit Handles
(newcons)?CREATE_NEW_CONSOLE:0, // Creation Flags
nullptr, // Environment
nullptr, // Current Directory
&si, // Startup Info
&pi)) // Process Information
{
color(CONSCLR_ERROR); printf("error: can not start ffmpeg.\n");
CloseHandle(hPipe);
return -1;
}
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: ffmpeg started.\n");
#endif
//wait for connection from ffmpeg, 5 sec timeout
unsigned long t=GetTickCount();
int conn=0;
while(t+5000ul>GetTickCount())
{
conn=ConnectNamedPipe(hPipe, nullptr) ? 1 : (GetLastError()==ERROR_PIPE_CONNECTED);
if(conn) break;
Sleep(10);
}
//connected?
if (!conn)
{
color(CONSCLR_ERROR); printf("error: no connection from ffmpeg.\n");
CloseHandle(hPipe);
return -1;
}
DWORD dwMode=PIPE_READMODE_BYTE | PIPE_WAIT;
SetNamedPipeHandleState(hPipe, &dwMode, nullptr,nullptr); //set blocking mode
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: got connection from pipe.\n");
#endif
//patch and send video header
//with negative height we can use linear bitmap data! :)
//this also fix ffmpeg's bug-o-feature with BottomUp property when '-c:v copy' is used
*(unsigned int*)(AVIRIFF+0x20)=u32(1000000/fps);
*(unsigned int*)(AVIRIFF+0x40)=u32(w);
*(unsigned int*)(AVIRIFF+0x44)=u32(-h);
*(unsigned int*)(AVIRIFF+0x84)=unsigned(fps);
*(unsigned short*)(AVIRIFF+0xa0)=u16(w);
*(unsigned short*)(AVIRIFF+0xa2)=u16(-h);
*(unsigned int*)(AVIRIFF+0xb0)=u32(w);
*(unsigned int*)(AVIRIFF+0xb4)=u32(-h);
*(unsigned int*)(AVIRIFF2+0x2c)=u32(sndfq*4);
*(unsigned int*)(AVIRIFF2+0x58)=u32(sndfq);
int res=pipewrite(hPipe,AVIRIFF,sizeof(AVIRIFF)-1);
res+=pipewrite(hPipe,AVIRIFF2,sizeof(AVIRIFF2)-1);
res+=pipewrite(hPipe,AVIRIFF3,sizeof(AVIRIFF3)-1);
if(res<0)
{
//pipe error, finish
color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection at the beginning.\n");
savevideo_finish();
return -1;
}
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: video header sent.\n");
#endif
return 0;
}
//send video frame to stream:
//buf - picture buffer (raw bmp RGB24 format)
//size - size of picture (W*H*3), bytes
static int savevideo_put_vframe(char *buf, unsigned size)
{
//send frame header
*(unsigned int*)(avi_frameh_vid+4)=size;
int res=pipewrite(hPipe,avi_frameh_vid,8);
if(res<0) return -1;
//send frame data
res=pipewrite(hPipe,buf,size);
if(res<0) return -1;
return 0;
}
//send audio frame to stream:
//buf - sound buffer (raw 16 bit le stereo)
//size - length of sound data, bytes
static int savevideo_put_aframe(char *buf, unsigned int size)
{
//send frame header
*(unsigned int*)(avi_frameh_aud+4)=size;
int res=pipewrite(hPipe,avi_frameh_aud,8);
if(res<0) return -1;
//send frame data
res=pipewrite(hPipe,buf,size);
if(res<0) return -1;
return 0;
}
//finish saving: close handles
static void savevideo_finish()
{
//send video trailer (none)
//close pipe
CloseHandle(hPipe);
//wait for ffmpeg done
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: waiting for ffmpeg finish.\n");
#endif
WaitForSingleObject(pi.hProcess, INFINITE);
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: saving video done.\n");
#endif
//close handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
//send data block to pipe
static int pipewrite(HANDLE hPipe, char *buf, unsigned len)
{
DWORD cbWritten = 0;
int res = 0;
unsigned p=0;
while(p<len) //is all data sent?
{
res = WriteFile(
hPipe, // handle to pipe
&buf[p], // buffer to write from
len-p, // number of bytes to write
&cbWritten, // number of bytes written
nullptr); // not overlapped I/O
if(res)
{
p+=cbWritten;
}
else
{
#ifdef DEBUG
color(CONSCLR_INFO); printf("debug: pipe disconnected.\n");
#endif
return -1;
}
}
return 0;
}
/*****************************************************************************
* Public functions *
*****************************************************************************/
//main function of video saver
void main_savevideo()
{
static int vidn=0;
char video_name[VS_MAX_FFVOUT],tmp[VS_MAX_FFVOUT]={0};
int res;
if(videosaver_state==0) //start saver
{
//construct name of output file
strncpy(tmp,conf.ffmpeg.vout,sizeof(tmp)-1);
char *p=strchr(tmp,'#');
if(p)
{ //'#' found, split name into two parts, insert a number
*p=0;
_snprintf(video_name,sizeof(video_name),"%s%d%s",tmp,vidn,p+1);
}
else
_snprintf(video_name,sizeof(video_name),"%s",tmp);
//init ffmpeg
res=savevideo_init(conf.ffmpeg.exec, conf.ffmpeg.parm, video_name,
conf.ffmpeg.newcons, int(temp.ox), int(temp.oy), int(conf.intfq), int(conf.sound.fq));
if(res) //init ok?
{
color(CONSCLR_ERROR); printf("error: init video saver failed.\n");
return;
}
sprintf(statusline, "start saving video");
//store screen and audio settings
SVSet.xsz=temp.ox;
SVSet.ysz=temp.oy;
SVSet.fps=conf.intfq;
SVSet.sndfq=conf.sound.fq;
SVSet.snden=conf.sound.enabled;
//allocate buffers for pictures
SVSet.dx = temp.ox * temp.obpp / 8;
SVSet.scrbuf_unaligned = (unsigned char*)malloc(SVSet.dx * temp.oy + CACHE_LINE);
SVSet.scrbuf = (unsigned char*)align_by(SVSet.scrbuf_unaligned, CACHE_LINE);
SVSet.dsll = ((temp.ox * 3 + 3) & ~3U);
SVSet.ds = (u8*)malloc(SVSet.dsll * temp.oy);
vidn++;
videosaver_state=1;
}
else //saving done
{
//stop ffmpeg
savevideo_finish();
sprintf(statusline, "stop saving video");
//free buffers
free(SVSet.ds);
free(SVSet.scrbuf_unaligned);
videosaver_state=0;
}
statcnt = 25; //show status during 25 frames
}
//save graphics handler
void savevideo_gfx()
{
//is format changed?
if(temp.ox!=SVSet.xsz || temp.oy!=SVSet.ysz ||
conf.intfq!=SVSet.fps || conf.sound.fq!=SVSet.sndfq ||
conf.sound.enabled!=SVSet.snden)
{
main_savevideo(); //stop saving!
return;
}
//render screen to scrbuf buffer
renders[conf.render].func(SVSet.scrbuf, SVSet.dx); // render to memory buffer (PAL8, YUY2, RGB15, RGB16, RGB32)
//convert colors to RGB24
ConvBgr24(SVSet.ds, SVSet.scrbuf, int(SVSet.dx));
//send frame to encoder
if(savevideo_put_vframe((char*)SVSet.ds, SVSet.dsll*SVSet.ysz))
{
//stop saving if error occured
color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection.\n");
main_savevideo();
return;
}
}
//save sound handler
void savevideo_snd()
{
//is format changed?
if(temp.ox!=SVSet.xsz || temp.oy!=SVSet.ysz ||
conf.intfq!=SVSet.fps || conf.sound.fq!=SVSet.sndfq ||
conf.sound.enabled!=SVSet.snden)
{
main_savevideo(); //stop saving!
return;
}
//send frame to encoder
if(savevideo_put_aframe((char*)sndplaybuf,spbsize))
{
//stop saving if error occured
color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection.\n");
main_savevideo();
return;
}
}