Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
716 | lvd | 1 | #include "std.h" |
2 | #include "emul.h" |
||
3 | #include "vars.h" |
||
4 | #include "util.h" |
||
5 | |||
6 | #include "savevid.h" |
||
7 | |||
8 | |||
9 | //#define DEBUG 1 |
||
10 | //handles |
||
11 | HANDLE hPipe=INVALID_HANDLE_VALUE; |
||
12 | STARTUPINFO si; |
||
13 | PROCESS_INFORMATION pi; |
||
14 | |||
15 | TSVSet SVSet; //settings |
||
16 | int videosaver_state; //0-not saving, 1-saving |
||
17 | |||
18 | |||
19 | //AVI global hdr + video hdrs |
||
20 | char AVIRIFF[]= |
||
21 | "\x52\x49\x46\x46\x00\x00\x00\x00\x41\x56\x49\x20\x4C\x49\x53\x54" //RIFF size=0 (inf) |
||
22 | "\x3c\x01\x00\x00\x68\x64\x72\x6C\x61\x76\x69\x68\x38\x00\x00\x00" |
||
23 | "\x20\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00" |
||
24 | "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00" |
||
25 | "\x80\x02\x00\x00\xE0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
||
26 | "\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x49\x53\x54\x80\x00\x00\x00" |
||
27 | "\x73\x74\x72\x6C\x73\x74\x72\x68\x38\x00\x00\x00\x76\x69\x64\x73" |
||
28 | "\x44\x49\x42\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
||
29 | "\x01\x00\x00\x00\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
||
30 | "\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00" |
||
31 | "\x80\x02\xE0\x01\x73\x74\x72\x66\x28\x00\x00\x00\x28\x00\x00\x00" |
||
32 | "\x80\x02\x00\x00\xE0\x01\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00" |
||
33 | "\x00\x10\x0E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
||
34 | "\x00\x00\x00\x00\x4A\x55\x4E\x4B\x04\x00\x00\x00\x00\x00\x00\x00" |
||
35 | ; |
||
36 | |||
37 | //AVI audio hdrs |
||
38 | char AVIRIFF2[]= |
||
39 | "\x4C\x49\x53\x54\x68\x00\x00\x00\x73\x74\x72\x6C\x73\x74\x72\x68" |
||
40 | "\x38\x00\x00\x00\x61\x75\x64\x73\x00\x00\x00\x00\x00\x00\x00\x00" |
||
41 | "\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x10\xB1\x02\x00" |
||
42 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF" |
||
43 | "\x04\x00\x00\x00\x6D\x00\x70\x00\x67\x00\x3B\x00\x73\x74\x72\x66" |
||
44 | "\x10\x00\x00\x00\x01\x00\x02\x00\x44\xAC\x00\x00\x10\xB1\x02\x00" |
||
45 | "\x04\x00\x10\x00\x4A\x55\x4E\x4B\x04\x00\x00\x00\x00\x00\x00\x00" |
||
46 | ; |
||
47 | |||
48 | //AVI start streams |
||
49 | char AVIRIFF3[]= |
||
50 | "\x4C\x49\x53\x54\x00\x00\x00\x00\x6D\x6F\x76\x69"; //LIST movi size=0 |
||
51 | |||
52 | //AVI stream data headers |
||
53 | char avi_frameh_vid[] = "00db "; //DIB |
||
54 | char avi_frameh_aud[] = "01wb "; //wave |
||
55 | |||
56 | |||
57 | //proto |
||
58 | static int pipewrite(HANDLE hPipe, char *buf, int len); |
||
59 | static void savevideo_finish(); |
||
60 | |||
61 | |||
62 | //init: |
||
63 | //ffmpeg_exec - path/name of ffmpeg executable (ex: "ffmpeg.exe") |
||
64 | //ffmpeg_param - params for ffmpeg output video (ex: "-c:a libmp3lame -b:a 320k") |
||
65 | //out_fname - output video name (ex: "video0.avi") |
||
66 | //newcons - create new console for ffmpeg (0/1) |
||
67 | //w,h - width, height (ex: 460, 480) |
||
68 | //fps - frames per second (ex: 50) |
||
69 | //sndfq - sound sample rate (ex: 44100) |
||
70 | 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) |
||
71 | { |
||
72 | //create named pipe for stream video to ffmpeg |
||
73 | hPipe=CreateNamedPipe( |
||
74 | PIPENAME, // pipe name |
||
75 | PIPE_ACCESS_OUTBOUND, // write access |
||
76 | PIPE_TYPE_BYTE | // byte type pipe |
||
77 | PIPE_READMODE_BYTE | // byte-read mode |
||
78 | PIPE_NOWAIT, // non blocking mode (for async connect) |
||
79 | 1, // max. instances |
||
80 | PIPESIZE, // output buffer size |
||
81 | 1024, // input buffer size |
||
82 | 0, // client time-out |
||
83 | NULL); // default security attribute |
||
84 | |||
85 | if(hPipe==INVALID_HANDLE_VALUE) |
||
86 | { |
||
87 | color(CONSCLR_ERROR); printf("error: CreateNamedPipe failed.\n"); |
||
88 | return -1; |
||
89 | } |
||
90 | #ifdef DEBUG |
||
91 | color(CONSCLR_INFO); printf("debug: named pipe '%s' created.\n",PIPENAME); |
||
92 | #endif |
||
93 | |||
94 | //start ffmpeg process |
||
95 | char args[VS_MAX_FFPATH+VS_MAX_FFPARM+VS_MAX_FFVOUT+100]; |
||
96 | _snprintf(args, sizeof(args), "\"%s\" -i %s %s -y %s", ffmpeg_exec, PIPENAME, ffmpeg_param, out_fname); |
||
97 | #ifdef DEBUG |
||
98 | color(CONSCLR_INFO); printf("debug: %s\n", args); |
||
99 | #endif |
||
100 | |||
101 | memset(&si, 0, sizeof(si)); |
||
102 | si.cb=sizeof(si); |
||
103 | //si.wShowWindow=SW_SHOW; |
||
104 | si.wShowWindow=SW_MINIMIZE; |
||
105 | //si.wShowWindow=SW_HIDE; |
||
106 | si.dwFlags=STARTF_USESHOWWINDOW; |
||
107 | memset(&pi, 0, sizeof(pi)); |
||
108 | |||
109 | if(!CreateProcess(NULL, // no app name |
||
110 | args, // cmd line |
||
111 | NULL, // proc attr |
||
112 | NULL, // thread attr |
||
113 | FALSE, // Inherit Handles |
||
114 | (newcons)?CREATE_NEW_CONSOLE:0, // Creation Flags |
||
115 | NULL, // Environment |
||
116 | NULL, // Current Directory |
||
117 | &si, // Startup Info |
||
118 | &pi)) // Process Information |
||
119 | { |
||
120 | color(CONSCLR_ERROR); printf("error: can not start ffmpeg.\n"); |
||
121 | CloseHandle(hPipe); |
||
122 | return -1; |
||
123 | } |
||
124 | #ifdef DEBUG |
||
125 | color(CONSCLR_INFO); printf("debug: ffmpeg started.\n"); |
||
126 | #endif |
||
127 | |||
128 | //wait for connection from ffmpeg, 5 sec timeout |
||
129 | unsigned long t=GetTickCount(); |
||
130 | int conn=0; |
||
131 | while(t+5000ul>GetTickCount()) |
||
132 | { |
||
133 | conn=ConnectNamedPipe(hPipe, NULL) ? 1 : (GetLastError()==ERROR_PIPE_CONNECTED); |
||
134 | if(conn) break; |
||
135 | Sleep(10); |
||
136 | } |
||
137 | |||
138 | //connected? |
||
139 | if (!conn) |
||
140 | { |
||
141 | color(CONSCLR_ERROR); printf("error: no connection from ffmpeg.\n"); |
||
142 | CloseHandle(hPipe); |
||
143 | return -1; |
||
144 | } |
||
145 | DWORD dwMode=PIPE_READMODE_BYTE | PIPE_WAIT; |
||
146 | SetNamedPipeHandleState(hPipe, &dwMode, NULL,NULL); //set blocking mode |
||
147 | #ifdef DEBUG |
||
148 | color(CONSCLR_INFO); printf("debug: got connection from pipe.\n"); |
||
149 | #endif |
||
150 | |||
151 | //patch and send video header |
||
152 | //with negative height we can use linear bitmap data! :) |
||
153 | //this also fix ffmpeg's bug-o-feature with BottomUp property when '-c:v copy' is used |
||
154 | *(unsigned int*)(AVIRIFF+0x20)=1000000/fps; |
||
155 | *(unsigned int*)(AVIRIFF+0x40)=w; |
||
156 | *(unsigned int*)(AVIRIFF+0x44)=-h; |
||
157 | |||
158 | *(unsigned int*)(AVIRIFF+0x84)=fps; |
||
159 | *(unsigned short*)(AVIRIFF+0xa0)=w; |
||
160 | *(unsigned short*)(AVIRIFF+0xa2)=-h; |
||
161 | *(unsigned int*)(AVIRIFF+0xb0)=w; |
||
162 | *(unsigned int*)(AVIRIFF+0xb4)=-h; |
||
163 | |||
164 | *(unsigned int*)(AVIRIFF2+0x2c)=sndfq*4; |
||
165 | *(unsigned int*)(AVIRIFF2+0x58)=sndfq; |
||
166 | |||
167 | int res=pipewrite(hPipe,AVIRIFF,sizeof(AVIRIFF)-1); |
||
168 | res+=pipewrite(hPipe,AVIRIFF2,sizeof(AVIRIFF2)-1); |
||
169 | res+=pipewrite(hPipe,AVIRIFF3,sizeof(AVIRIFF3)-1); |
||
170 | if(res<0) |
||
171 | { |
||
172 | //pipe error, finish |
||
173 | color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection at the beginning.\n"); |
||
174 | savevideo_finish(); |
||
175 | return -1; |
||
176 | } |
||
177 | #ifdef DEBUG |
||
178 | color(CONSCLR_INFO); printf("debug: video header sent.\n"); |
||
179 | #endif |
||
180 | |||
181 | return 0; |
||
182 | } |
||
183 | |||
184 | |||
185 | //send video frame to stream: |
||
186 | //buf - picture buffer (raw bmp RGB24 format) |
||
187 | //size - size of picture (W*H*3), bytes |
||
188 | static int savevideo_put_vframe(char *buf, unsigned size) |
||
189 | { |
||
190 | //send frame header |
||
191 | *(unsigned int*)(avi_frameh_vid+4)=size; |
||
192 | int res=pipewrite(hPipe,avi_frameh_vid,8); |
||
193 | if(res<0) return -1; |
||
194 | |||
195 | //send frame data |
||
196 | res=pipewrite(hPipe,buf,size); |
||
197 | if(res<0) return -1; |
||
198 | return 0; |
||
199 | } |
||
200 | |||
201 | //send audio frame to stream: |
||
202 | //buf - sound buffer (raw 16 bit le stereo) |
||
203 | //size - length of sound data, bytes |
||
204 | static int savevideo_put_aframe(char *buf, unsigned int size) |
||
205 | { |
||
206 | //send frame header |
||
207 | *(unsigned int*)(avi_frameh_aud+4)=size; |
||
208 | int res=pipewrite(hPipe,avi_frameh_aud,8); |
||
209 | if(res<0) return -1; |
||
210 | |||
211 | //send frame data |
||
212 | res=pipewrite(hPipe,buf,size); |
||
213 | if(res<0) return -1; |
||
214 | return 0; |
||
215 | } |
||
216 | |||
217 | //finish saving: close handles |
||
218 | static void savevideo_finish() |
||
219 | { |
||
220 | //send video trailer (none) |
||
221 | //close pipe |
||
222 | CloseHandle(hPipe); |
||
223 | |||
224 | //wait for ffmpeg done |
||
225 | #ifdef DEBUG |
||
226 | color(CONSCLR_INFO); printf("debug: waiting for ffmpeg finish.\n"); |
||
227 | #endif |
||
228 | WaitForSingleObject(pi.hProcess, INFINITE); |
||
229 | #ifdef DEBUG |
||
230 | color(CONSCLR_INFO); printf("debug: saving video done.\n"); |
||
231 | #endif |
||
232 | |||
233 | //close handles |
||
234 | CloseHandle(pi.hProcess); |
||
235 | CloseHandle(pi.hThread); |
||
236 | } |
||
237 | |||
238 | |||
239 | //send data block to pipe |
||
240 | static int pipewrite(HANDLE hPipe, char *buf, int len) |
||
241 | { |
||
242 | DWORD cbWritten = 0; |
||
243 | int res = 0; |
||
244 | |||
245 | int p=0; |
||
246 | while(p<len) //is all data sent? |
||
247 | { |
||
248 | res = WriteFile( |
||
249 | hPipe, // handle to pipe |
||
250 | &buf[p], // buffer to write from |
||
251 | len-p, // number of bytes to write |
||
252 | &cbWritten, // number of bytes written |
||
253 | NULL); // not overlapped I/O |
||
254 | if(res) |
||
255 | { |
||
256 | p+=cbWritten; |
||
257 | } |
||
258 | else |
||
259 | { |
||
260 | #ifdef DEBUG |
||
261 | color(CONSCLR_INFO); printf("debug: pipe disconnected.\n"); |
||
262 | #endif |
||
263 | return -1; |
||
264 | } |
||
265 | } |
||
266 | return 0; |
||
267 | } |
||
268 | |||
269 | |||
270 | |||
271 | /***************************************************************************** |
||
272 | * Public functions * |
||
273 | *****************************************************************************/ |
||
274 | |||
275 | //main function of video saver |
||
276 | void main_savevideo() |
||
277 | { |
||
278 | static int vidn=0; |
||
279 | char video_name[VS_MAX_FFVOUT],tmp[VS_MAX_FFVOUT]={0}; |
||
280 | int res; |
||
281 | |||
282 | if(videosaver_state==0) //start saver |
||
283 | { |
||
284 | //construct name of output file |
||
285 | strncpy(tmp,conf.ffmpeg.vout,sizeof(tmp)-1); |
||
286 | char *p=strchr(tmp,'#'); |
||
287 | if(p) |
||
288 | { //'#' found, split name into two parts, insert a number |
||
289 | *p=0; |
||
290 | _snprintf(video_name,sizeof(video_name),"%s%u%s",tmp,vidn,p+1); |
||
291 | } |
||
292 | else |
||
293 | _snprintf(video_name,sizeof(video_name),"%s",tmp); |
||
294 | |||
295 | //init ffmpeg |
||
296 | res=savevideo_init(conf.ffmpeg.exec, conf.ffmpeg.parm, video_name, |
||
297 | conf.ffmpeg.newcons, temp.ox, temp.oy, conf.intfq, conf.sound.fq); |
||
298 | if(res) //init ok? |
||
299 | { |
||
300 | color(CONSCLR_ERROR); printf("error: init video saver failed.\n"); |
||
301 | return; |
||
302 | } |
||
303 | sprintf(statusline, "start saving video"); |
||
304 | |||
305 | |||
306 | //store screen and audio settings |
||
307 | SVSet.xsz=temp.ox; |
||
308 | SVSet.ysz=temp.oy; |
||
309 | SVSet.fps=conf.intfq; |
||
310 | SVSet.sndfq=conf.sound.fq; |
||
311 | SVSet.snden=conf.sound.enabled; |
||
312 | |||
313 | //allocate buffers for pictures |
||
314 | SVSet.dx = temp.ox * temp.obpp / 8; |
||
315 | SVSet.scrbuf_unaligned = (unsigned char*)malloc(SVSet.dx * temp.oy + CACHE_LINE); |
||
316 | SVSet.scrbuf = (unsigned char*)align_by(SVSet.scrbuf_unaligned, CACHE_LINE); |
||
317 | SVSet.dsll = ((temp.ox * 3 + 3) & ~3); |
||
318 | SVSet.ds = (u8*)malloc(SVSet.dsll * temp.oy); |
||
319 | |||
320 | vidn++; |
||
321 | videosaver_state=1; |
||
322 | } |
||
323 | else //saving done |
||
324 | { |
||
325 | //stop ffmpeg |
||
326 | savevideo_finish(); |
||
327 | sprintf(statusline, "stop saving video"); |
||
328 | |||
329 | //free buffers |
||
330 | free(SVSet.ds); |
||
331 | free(SVSet.scrbuf_unaligned); |
||
332 | |||
333 | videosaver_state=0; |
||
334 | } |
||
335 | |||
336 | statcnt = 25; //show status during 25 frames |
||
337 | } |
||
338 | |||
339 | |||
340 | //save graphics handler |
||
341 | void savevideo_gfx() |
||
342 | { |
||
343 | //is format changed? |
||
344 | if(temp.ox!=SVSet.xsz || temp.oy!=SVSet.ysz || |
||
345 | conf.intfq!=SVSet.fps || conf.sound.fq!=SVSet.sndfq || |
||
346 | conf.sound.enabled!=SVSet.snden) |
||
347 | { |
||
348 | main_savevideo(); //stop saving! |
||
349 | return; |
||
350 | } |
||
351 | |||
352 | //render screen to scrbuf buffer |
||
353 | renders[conf.render].func(SVSet.scrbuf, SVSet.dx); // render to memory buffer (PAL8, YUY2, RGB15, RGB16, RGB32) |
||
354 | //convert colors to RGB24 |
||
355 | ConvBgr24(SVSet.ds, SVSet.scrbuf, SVSet.dx); |
||
356 | //send frame to encoder |
||
357 | if(savevideo_put_vframe((char*)SVSet.ds, SVSet.dsll*SVSet.ysz)) |
||
358 | { |
||
359 | //stop saving if error occured |
||
360 | color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection.\n"); |
||
361 | main_savevideo(); |
||
362 | return; |
||
363 | } |
||
364 | } |
||
365 | |||
366 | //save sound handler |
||
367 | void savevideo_snd() |
||
368 | { |
||
369 | //is format changed? |
||
370 | if(temp.ox!=SVSet.xsz || temp.oy!=SVSet.ysz || |
||
371 | conf.intfq!=SVSet.fps || conf.sound.fq!=SVSet.sndfq || |
||
372 | conf.sound.enabled!=SVSet.snden) |
||
373 | { |
||
374 | main_savevideo(); //stop saving! |
||
375 | return; |
||
376 | } |
||
377 | |||
378 | //send frame to encoder |
||
379 | if(savevideo_put_aframe((char*)sndplaybuf,spbsize)) |
||
380 | { |
||
381 | //stop saving if error occured |
||
382 | color(CONSCLR_ERROR); printf("error: ffmpeg aborted connection.\n"); |
||
383 | main_savevideo(); |
||
384 | return; |
||
385 | } |
||
386 | } |