1 /* Emacs style mode select -*- C++ -*-
2 *-----------------------------------------------------------------------------
3 *
4 *
5 * PrBoom: a Doom port merged with LxDoom and LSDLDoom
6 * based on BOOM, a modified and improved DOOM engine
7 *
8 * Copyright (C) 2011 by
9 * Nicholai Main
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
24 * 02111-1307, USA.
25 *
26 * DESCRIPTION:
27 *
28 *---------------------------------------------------------------------
29 */
30
31 #include "SDL.h"
32 #include "SDL_thread.h"
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include "i_sound.h"
37 #include "i_video.h"
38 #include "lprintf.h"
39 #include "i_capture.h"
40
41
42 int capturing_video = 0;
43 static const char *vid_fname;
44
45
46 typedef struct
47 { // information on a running pipe
48 char command[PATH_MAX];
49 FILE *f_stdin;
50 FILE *f_stdout;
51 FILE *f_stderr;
52 SDL_Thread *outthread;
53 const char *stdoutdumpname;
54 SDL_Thread *errthread;
55 const char *stderrdumpname;
56 void *user;
57 } pipeinfo_t;
58
59 static pipeinfo_t soundpipe;
60 static pipeinfo_t videopipe;
61 static pipeinfo_t muxpipe;
62
63
64 const char *cap_soundcommand;
65 const char *cap_videocommand;
66 const char *cap_muxcommand;
67 const char *cap_tempfile1;
68 const char *cap_tempfile2;
69 int cap_remove_tempfiles;
70
71 // parses a command with simple printf-style replacements.
72
73 // %w video width (px)
74 // %h video height (px)
75 // %s sound rate (hz)
76 // %f filename passed to -viddump
77 // %% single percent sign
78 // TODO: add aspect ratio information
parsecommand(char * out,const char * in,int len)79 static int parsecommand (char *out, const char *in, int len)
80 {
81 int i;
82
83 while (*in && len > 1)
84 {
85 if (*in == '%')
86 {
87 switch (in[1])
88 {
89 case 'w':
90 i = SNPRINTF (out, len, "%u", REAL_SCREENWIDTH);
91 break;
92 case 'h':
93 i = SNPRINTF (out, len, "%u", REAL_SCREENHEIGHT);
94 break;
95 case 's':
96 i = SNPRINTF (out, len, "%u", snd_samplerate);
97 break;
98 case 'f':
99 i = SNPRINTF (out, len, "%s", vid_fname);
100 break;
101 case '%':
102 i = SNPRINTF (out, len, "%%");
103 break;
104 default:
105 return 0;
106 }
107 out += i;
108 len -= i;
109 in += 2;
110 }
111 else
112 {
113 *out++ = *in++;
114 len--;
115 }
116 }
117 if (*in || len < 1)
118 { // out of space
119 return 0;
120 }
121 *out = 0;
122 return 1;
123 }
124
125
126
127
128 // popen3() implementation -
129 // starts a child process
130
131 // user is a pointer to implementation defined extra data
132 static int my_popen3 (pipeinfo_t *p); // 1 on success
133 // close waits on process
134 static void my_pclose3 (pipeinfo_t *p);
135
136
137 #ifdef _WIN32
138 // direct winapi implementation
139
140 #define WIN32_LEAN_AND_MEAN
141 #include <windows.h>
142 #include <io.h>
143
144 typedef struct
145 {
146 HANDLE proc;
147 HANDLE thread;
148 } puser_t;
149
150 // extra pointer is used to hold process id to wait on to close
151 // NB: stdin is opened as "wb", stdout, stderr as "r"
my_popen3(pipeinfo_t * p)152 static int my_popen3 (pipeinfo_t *p)
153 {
154 FILE *fin = NULL;
155 FILE *fout = NULL;
156 FILE *ferr = NULL;
157 HANDLE child_hin = INVALID_HANDLE_VALUE;
158 HANDLE child_hout = INVALID_HANDLE_VALUE;
159 HANDLE child_herr = INVALID_HANDLE_VALUE;
160 HANDLE parent_hin = INVALID_HANDLE_VALUE;
161 HANDLE parent_hout = INVALID_HANDLE_VALUE;
162 HANDLE parent_herr = INVALID_HANDLE_VALUE;
163
164
165 puser_t *puser = NULL;
166
167
168 PROCESS_INFORMATION piProcInfo;
169 STARTUPINFO siStartInfo;
170 SECURITY_ATTRIBUTES sa;
171
172 puser = malloc (sizeof (puser_t));
173 if (!puser)
174 return 0;
175
176 puser->proc = INVALID_HANDLE_VALUE;
177 puser->thread = INVALID_HANDLE_VALUE;
178
179
180 // make the pipes
181
182 sa.nLength = sizeof (sa);
183 sa.bInheritHandle = 1;
184 sa.lpSecurityDescriptor = NULL;
185 if (!CreatePipe (&child_hin, &parent_hin, &sa, 0))
186 goto fail;
187 if (!CreatePipe (&parent_hout, &child_hout, &sa, 0))
188 goto fail;
189 if (!CreatePipe (&parent_herr, &child_herr, &sa, 0))
190 goto fail;
191
192
193 // very important
194 if (!SetHandleInformation (parent_hin, HANDLE_FLAG_INHERIT, 0))
195 goto fail;
196 if (!SetHandleInformation (parent_hout, HANDLE_FLAG_INHERIT, 0))
197 goto fail;
198 if (!SetHandleInformation (parent_herr, HANDLE_FLAG_INHERIT, 0))
199 goto fail;
200
201
202
203
204 // start the child process
205
206 ZeroMemory (&siStartInfo, sizeof (STARTUPINFO));
207 siStartInfo.cb = sizeof (STARTUPINFO);
208 siStartInfo.hStdInput = child_hin;
209 siStartInfo.hStdOutput = child_hout;
210 siStartInfo.hStdError = child_herr;
211 siStartInfo.dwFlags = STARTF_USESTDHANDLES;
212
213 if (!CreateProcess(NULL,// application name
214 (LPTSTR)p->command,// command line
215 NULL, // process security attributes
216 NULL, // primary thread security attributes
217 TRUE, // handles are inherited
218 DETACHED_PROCESS, // creation flags
219 NULL, // use parent's environment
220 NULL, // use parent's current directory
221 &siStartInfo, // STARTUPINFO pointer
222 &piProcInfo)) // receives PROCESS_INFORMATION
223 {
224 goto fail;
225 }
226
227
228
229 puser->proc = piProcInfo.hProcess;
230 puser->thread = piProcInfo.hThread;
231
232
233 // what the hell is this cast for
234 if (NULL == (fin = _fdopen (_open_osfhandle ((int) parent_hin, 0), "wb")))
235 goto fail;
236 if (NULL == (fout = _fdopen (_open_osfhandle ((int) parent_hout, 0), "r")))
237 goto fail;
238 if (NULL == (ferr = _fdopen (_open_osfhandle ((int) parent_herr, 0), "r")))
239 goto fail;
240 // after fdopen(osf()), we don't need to keep track of parent handles anymore
241 // fclose on the FILE struct will automatically free them
242
243
244 p->user = puser;
245 p->f_stdin = fin;
246 p->f_stdout = fout;
247 p->f_stderr = ferr;
248
249 CloseHandle (child_hin);
250 CloseHandle (child_hout);
251 CloseHandle (child_herr);
252
253 return 1;
254
255 fail:
256 if (fin)
257 fclose (fin);
258 if (fout)
259 fclose (fout);
260 if (ferr)
261 fclose (ferr);
262
263 if (puser->proc)
264 CloseHandle (puser->proc);
265 if (puser->thread)
266 CloseHandle (puser->thread);
267
268 if (child_hin != INVALID_HANDLE_VALUE)
269 CloseHandle (child_hin);
270 if (child_hout != INVALID_HANDLE_VALUE)
271 CloseHandle (child_hout);
272 if (child_herr != INVALID_HANDLE_VALUE)
273 CloseHandle (child_herr);
274 if (parent_hin != INVALID_HANDLE_VALUE)
275 CloseHandle (parent_hin);
276 if (parent_hout != INVALID_HANDLE_VALUE)
277 CloseHandle (parent_hout);
278 if (parent_herr != INVALID_HANDLE_VALUE)
279 CloseHandle (parent_herr);
280
281 free (puser);
282
283 return 0;
284
285
286 }
287
my_pclose3(pipeinfo_t * p)288 static void my_pclose3 (pipeinfo_t *p)
289 {
290 puser_t *puser = (puser_t *) p->user;
291
292 if (!p->f_stdin || !p->f_stdout || !p->f_stderr || !puser)
293 return;
294
295 fclose (p->f_stdin);
296 //fclose (p->f_stdout); // these are closed elsewhere
297 //fclose (p->f_stderr);
298
299 WaitForSingleObject (puser->proc, INFINITE);
300
301 CloseHandle (puser->proc);
302 CloseHandle (puser->thread);
303 free (puser);
304 }
305
306 #else // _WIN32
307 // posix implementation
308 // not tested
309 #include <unistd.h>
310 #include <sys/types.h>
311 #include <sys/wait.h>
312
313
314 typedef struct
315 {
316 int pid;
317 } puser_t;
318
319
my_popen3(pipeinfo_t * p)320 static int my_popen3 (pipeinfo_t *p)
321 {
322 FILE *fin = NULL;
323 FILE *fout = NULL;
324 FILE *ferr = NULL;
325 int child_hin = -1;
326 int child_hout = -1;
327 int child_herr = -1;
328 int parent_hin = -1;
329 int parent_hout = -1;
330 int parent_herr = -1;
331
332 int scratch[2];
333
334 int pid;
335
336 puser_t *puser = NULL;
337
338 puser = malloc (sizeof (puser_t));
339 if (!puser)
340 return 0;
341
342
343
344 // make the pipes
345 if (pipe (scratch))
346 goto fail;
347 child_hin = scratch[0];
348 parent_hin = scratch[1];
349 if (pipe (scratch))
350 goto fail;
351 parent_hout = scratch[0];
352 child_hout = scratch[1];
353 if (pipe (scratch))
354 goto fail;
355 parent_herr = scratch[0];
356 child_herr = scratch[1];
357
358 pid = fork ();
359
360 if (pid == -1)
361 goto fail;
362 if (pid == 0)
363 {
364 dup2 (child_hin, STDIN_FILENO);
365 dup2 (child_hout, STDOUT_FILENO);
366 dup2 (child_herr, STDERR_FILENO);
367
368 close (parent_hin);
369 close (parent_hout);
370 close (parent_herr);
371
372 // does this work? otherwise we have to parse cmd into an **argv style array
373 execl ("/bin/sh", "sh", "-c", p->command, NULL);
374 // exit forked process if command failed
375 _exit (0);
376 }
377
378 if (NULL == (fin = fdopen (parent_hin, "wb")))
379 goto fail;
380 if (NULL == (fout = fdopen (parent_hout, "r")))
381 goto fail;
382 if (NULL == (ferr = fdopen (parent_herr, "r")))
383 goto fail;
384
385 close (child_hin);
386 close (child_hout);
387 close (child_herr);
388
389 puser->pid = pid;
390
391 p->user = puser;
392 p->f_stdin = fin;
393 p->f_stdout = fout;
394 p->f_stderr = ferr;
395 return 1;
396
397 fail:
398 if (fin)
399 fclose (fin);
400 if (fout)
401 fclose (fout);
402 if (ferr)
403 fclose (ferr);
404
405 close (parent_hin);
406 close (parent_hout);
407 close (parent_herr);
408 close (child_hin);
409 close (child_hout);
410 close (child_herr);
411
412 free (puser);
413 return 0;
414
415 }
416
417
my_pclose3(pipeinfo_t * p)418 static void my_pclose3 (pipeinfo_t *p)
419 {
420 puser_t *puser = (puser_t *) p->user;
421
422 int s;
423
424 if (!p->f_stdin || !p->f_stdout || !p->f_stderr || !puser)
425 return;
426
427 fclose (p->f_stdin);
428 //fclose (p->f_stdout); // these are closed elsewhere
429 //fclose (p->f_stderr);
430
431 waitpid (puser->pid, &s, 0);
432
433 free (puser);
434 }
435
436
437 #endif // _WIN32
438
439 typedef struct
440 {
441 FILE *fin;
442 const char *fn;
443 } threaddata_t;
444
445
threadstdoutproc(void * data)446 static int threadstdoutproc (void *data)
447 { // simple thread proc dumps stdout
448 // not terribly fast
449 int c;
450
451 pipeinfo_t *p = (pipeinfo_t *) data;
452
453 FILE *f = fopen (p->stdoutdumpname, "w");
454
455 if (!f || !p->f_stdout)
456 return 0;
457
458 while ((c = fgetc (p->f_stdout)) != EOF)
459 fputc (c, f);
460
461 fclose (f);
462 fclose (p->f_stdout);
463 return 1;
464 }
465
threadstderrproc(void * data)466 static int threadstderrproc (void *data)
467 { // simple thread proc dumps stderr
468 // not terribly fast
469 int c;
470
471 pipeinfo_t *p = (pipeinfo_t *) data;
472
473 FILE *f = fopen (p->stderrdumpname, "w");
474
475 if (!f || !p->f_stderr)
476 return 0;
477
478 while ((c = fgetc (p->f_stderr)) != EOF)
479 fputc (c, f);
480
481 fclose (f);
482 fclose (p->f_stderr);
483 return 1;
484 }
485
486
487 // init and open sound, video pipes
488 // fn is filename passed from command line, typically final output file
I_CapturePrep(const char * fn)489 void I_CapturePrep (const char *fn)
490 {
491 vid_fname = fn;
492
493 if (!parsecommand (soundpipe.command, cap_soundcommand, sizeof(soundpipe.command)))
494 {
495 lprintf (LO_ERROR, "I_CapturePrep: malformed command %s\n", cap_soundcommand);
496 capturing_video = 0;
497 return;
498 }
499 if (!parsecommand (videopipe.command, cap_videocommand, sizeof(videopipe.command)))
500 {
501 lprintf (LO_ERROR, "I_CapturePrep: malformed command %s\n", cap_videocommand);
502 capturing_video = 0;
503 return;
504 }
505 if (!parsecommand (muxpipe.command, cap_muxcommand, sizeof(muxpipe.command)))
506 {
507 lprintf (LO_ERROR, "I_CapturePrep: malformed command %s\n", cap_muxcommand);
508 capturing_video = 0;
509 return;
510 }
511
512 lprintf (LO_INFO, "I_CapturePrep: opening pipe \"%s\"\n", soundpipe.command);
513 if (!my_popen3 (&soundpipe))
514 {
515 lprintf (LO_ERROR, "I_CapturePrep: sound pipe failed\n");
516 capturing_video = 0;
517 return;
518 }
519 lprintf (LO_INFO, "I_CapturePrep: opening pipe \"%s\"\n", videopipe.command);
520 if (!my_popen3 (&videopipe))
521 {
522 lprintf (LO_ERROR, "I_CapturePrep: video pipe failed\n");
523 my_pclose3 (&soundpipe);
524 capturing_video = 0;
525 return;
526 }
527 I_SetSoundCap ();
528 lprintf (LO_INFO, "I_CapturePrep: video capture started\n");
529 capturing_video = 1;
530
531 // start reader threads
532 soundpipe.stdoutdumpname = "sound_stdout.txt";
533 soundpipe.stderrdumpname = "sound_stderr.txt";
534 soundpipe.outthread = SDL_CreateThread (threadstdoutproc, &soundpipe);
535 soundpipe.errthread = SDL_CreateThread (threadstderrproc, &soundpipe);
536 videopipe.stdoutdumpname = "video_stdout.txt";
537 videopipe.stderrdumpname = "video_stderr.txt";
538 videopipe.outthread = SDL_CreateThread (threadstdoutproc, &videopipe);
539 videopipe.errthread = SDL_CreateThread (threadstderrproc, &videopipe);
540
541 atexit (I_CaptureFinish);
542 }
543
544
545
546 // capture a single frame of video (and corresponding audio length)
547 // and send it to pipes
I_CaptureFrame(void)548 void I_CaptureFrame (void)
549 {
550 unsigned char *snd;
551 unsigned char *vid;
552 static int partsof35 = 0; // correct for sync when samplerate % 35 != 0
553 int nsampreq;
554
555 if (!capturing_video)
556 return;
557
558 nsampreq = snd_samplerate / 35;
559 partsof35 += snd_samplerate % 35;
560 if (partsof35 >= 35)
561 {
562 partsof35 -= 35;
563 nsampreq++;
564 }
565
566 snd = I_GrabSound (nsampreq);
567 if (snd)
568 {
569 if (fwrite (snd, nsampreq * 4, 1, soundpipe.f_stdin) != 1)
570 lprintf(LO_WARN, "I_CaptureFrame: error writing soundpipe.\n");
571 //free (snd); // static buffer
572 }
573 vid = I_GrabScreen ();
574 if (vid)
575 {
576 if (fwrite (vid, REAL_SCREENWIDTH * REAL_SCREENHEIGHT * 3, 1, videopipe.f_stdin) != 1)
577 lprintf(LO_WARN, "I_CaptureFrame: error writing videopipe.\n");
578 //free (vid); // static buffer
579 }
580
581 }
582
583
584 // close pipes, call muxcommand, finalize
I_CaptureFinish(void)585 void I_CaptureFinish (void)
586 {
587 int s;
588
589 if (!capturing_video)
590 return;
591 capturing_video = 0;
592
593 // on linux, we have to close videopipe first, because it has a copy of the write
594 // end of soundpipe_stdin (so that stream will never see EOF).
595 // is there a better way to do this?
596
597 // (on windows, it doesn't matter what order we do it in)
598 my_pclose3 (&videopipe);
599 SDL_WaitThread (videopipe.outthread, &s);
600 SDL_WaitThread (videopipe.errthread, &s);
601
602 my_pclose3 (&soundpipe);
603 SDL_WaitThread (soundpipe.outthread, &s);
604 SDL_WaitThread (soundpipe.errthread, &s);
605
606 // muxing and temp file cleanup
607
608 lprintf (LO_INFO, "I_CaptureFinish: opening pipe \"%s\"\n", muxpipe.command);
609
610 if (!my_popen3 (&muxpipe))
611 {
612 lprintf (LO_ERROR, "I_CaptureFinish: finalize pipe failed\n");
613 return;
614 }
615
616 muxpipe.stdoutdumpname = "mux_stdout.txt";
617 muxpipe.stderrdumpname = "mux_stderr.txt";
618 muxpipe.outthread = SDL_CreateThread (threadstdoutproc, &muxpipe);
619 muxpipe.errthread = SDL_CreateThread (threadstderrproc, &muxpipe);
620
621 my_pclose3 (&muxpipe);
622 SDL_WaitThread (muxpipe.outthread, &s);
623 SDL_WaitThread (muxpipe.errthread, &s);
624
625
626 // unlink any files user wants gone
627 if (cap_remove_tempfiles)
628 {
629 remove (cap_tempfile1);
630 remove (cap_tempfile2);
631 }
632 }
633