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