view src/common/nesvideos-piece.cpp @ 43:629602cb5e8c

GPL v2, same as vba-rerecording.
author Robert McIntyre <rlm@mit.edu>
date Tue, 26 Feb 2013 10:08:20 +0000
parents f9f4f1b99eed
children
line wrap: on
line source
1 #include <cmath>
2 #include <cstdio>
3 #include <string>
4 #include <vector>
6 /* Note: This module assumes everyone uses RGB15 as display depth */
8 static std::string VIDEO_CMD =
9 "mencoder - -o test0.avi"
10 " -noskip -mc 0"
11 " -ovc lavc"
12 " -oac mp3lame"
13 " -lameopts preset=256:aq=2:mode=3"
14 " -lavcopts vcodec=ffv1:context=0:format=BGR32:coder=0:vstrict=-1"
15 " >& mencoder.log";
17 static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length);
19 #define BGR24 (0x42475218) // BGR24 fourcc
20 #define BGR16 (0x42475210) // BGR16 fourcc
21 #define BGR15 (0x4247520F) // BGR15 fourcc
23 static FILE* (*openFunc) (const char*, const char*) = NULL;
24 static int (*closeFunc) (FILE*) = NULL;
26 #if (defined(WIN32) || defined(win32)) // capital is standard, but check for either
27 #include <cstdlib>
28 #define popen _popen;
29 #define pclose _pclose;
30 #endif
32 #define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&255
33 #define u16(n) (n)&255,((n)>>8)&255
34 #define s4(s) s[0],s[1],s[2],s[3]
36 static const unsigned FPS_SCALE = (0x1000000);
38 // general-purpose A/V sync debugging, ignored unless explicitly enabled with NESVideoEnableDebugging
39 static void (*debugVideoMessageFunc)(const char *msg) = NULL;
40 static void (*debugAudioMessageFunc)(const char *msg) = NULL;
41 // logo adds 1 "frame" to audio, so offset that (A/V frames shouldn't necessarily match up depending on the rates, but should at least make them start out matching in case they do)
42 static unsigned audioFramesWritten=0, videoFramesWritten=1;
43 static double audioSecondsWritten=0, videoSecondsWritten=0;
46 static class AVI
47 {
48 FILE* avifp;
50 bool KnowVideo;
51 unsigned width;
52 unsigned height;
53 unsigned fps_scaled;
54 std::vector<unsigned char> VideoBuffer;
56 bool KnowAudio;
57 unsigned rate;
58 unsigned chans;
59 unsigned bits;
60 std::vector<unsigned char> AudioBuffer;
62 public:
63 AVI() :
64 avifp(NULL),
65 KnowVideo(false),
66 KnowAudio(false)
67 {
68 }
69 ~AVI()
70 {
71 if(avifp) closeFunc(avifp);
72 }
74 void Audio(unsigned r,unsigned b,unsigned c,
75 const unsigned char*d, unsigned nsamples)
76 {
77 if(!KnowAudio)
78 {
79 rate = r;
80 chans = c;
81 bits = b;
82 KnowAudio = true;
83 CheckFlushing();
84 }
85 unsigned bytes = nsamples*chans*(bits/8);
87 if(debugAudioMessageFunc)
88 {
89 audioFramesWritten++;
90 audioSecondsWritten += (double)nsamples / (double)rate; // += bytes times seconds per byte
91 char temp [64];
92 sprintf(temp, "A: %.2lf s, %d f", audioSecondsWritten, audioFramesWritten);
93 debugAudioMessageFunc(temp);
94 }
96 if(KnowVideo)
97 SendAudioFrame(d, bytes);
98 else
99 {
100 AudioBuffer.insert(AudioBuffer.end(), d, d+bytes);
101 fprintf(stderr, "Buffering %u bytes of audio\n", bytes);
102 }
103 }
104 void Video(unsigned w,unsigned h,unsigned f, const unsigned char*d)
105 {
106 if(!KnowVideo)
107 {
108 width=w;
109 height=h;
110 fps_scaled=f;
111 KnowVideo = true;
112 CheckFlushing();
113 }
115 unsigned bytes = width*height*2;
117 //std::vector<unsigned char> tmp(bytes, 'k');
118 //d = &tmp[0];
120 if(debugVideoMessageFunc)
121 {
122 videoFramesWritten++;
123 videoSecondsWritten += (double)FPS_SCALE / (double)fps_scaled; // += seconds per frame
124 char temp [64];
125 sprintf(temp, "V: %.2lf s, %d f", videoSecondsWritten, videoFramesWritten);
126 debugVideoMessageFunc(temp);
127 }
129 if(KnowAudio)
130 SendVideoFrame(d, bytes);
131 else
132 {
133 VideoBuffer.insert(VideoBuffer.end(), d, d+bytes);
134 fprintf(stderr, "Buffering %u bytes of video\n", bytes);
135 }
136 }
138 private:
139 void CheckFlushing()
140 {
141 //AudioBuffer.clear();
142 //VideoBuffer.clear();
144 if(KnowAudio && KnowVideo)
145 {
146 unsigned last_offs;
148 // Flush Audio
150 last_offs = 0;
151 while(last_offs < AudioBuffer.size())
152 {
153 unsigned bytes = rate / (fps_scaled / FPS_SCALE);
154 bytes *= chans*(bits/8);
156 unsigned remain = AudioBuffer.size() - last_offs;
157 if(bytes > remain) bytes = remain;
158 if(!bytes) break;
160 unsigned begin = last_offs;
161 last_offs += bytes;
162 SendAudioFrame(&AudioBuffer[begin], bytes);
163 }
164 AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin()+last_offs);
166 // Flush Video
168 last_offs = 0;
169 while(last_offs < VideoBuffer.size())
170 {
171 unsigned bytes = width*height*2;
172 unsigned remain = VideoBuffer.size() - last_offs;
173 if(bytes > remain) bytes = remain;
174 if(!bytes)break;
176 unsigned begin = last_offs;
177 last_offs += bytes;
178 SendVideoFrame(&VideoBuffer[begin], bytes);
179 }
180 VideoBuffer.erase(VideoBuffer.begin(), VideoBuffer.begin()+last_offs);
181 }
182 }
184 void SendVideoFrame(const unsigned char* vidbuf, unsigned framesize)
185 {
186 CheckBegin();
188 //fprintf(stderr, "Writing 00dc of %u bytes\n", framesize);
190 const unsigned char header[] = { s4("00dc"), u32(framesize) };
191 FlushWrite(avifp, header, sizeof(header));
192 FlushWrite(avifp, vidbuf, framesize);
193 }
195 void SendAudioFrame(const unsigned char* audbuf, unsigned framesize)
196 {
197 CheckBegin();
199 //fprintf(stderr, "Writing 01wb of %u bytes\n", framesize);
201 const unsigned char header[] = { s4("01wb"), u32(framesize) };
202 FlushWrite(avifp, header, sizeof(header));
203 FlushWrite(avifp, audbuf, framesize);
204 }
206 void CheckBegin()
207 {
208 if(avifp) return;
210 if(!openFunc) openFunc = popen; // default
211 if(!closeFunc) closeFunc = pclose; // default
213 avifp = openFunc(VIDEO_CMD.c_str(), "wb");
214 if(!avifp) return;
216 const unsigned fourcc = BGR16;
217 const unsigned framesize = width*height*2;
219 const unsigned aud_rate = rate;
220 const unsigned aud_chans = chans;
221 const unsigned aud_bits = bits;
223 const unsigned nframes = 0; //unknown
224 const unsigned scale = FPS_SCALE;
225 const unsigned scaled_fps = fps_scaled;
227 const unsigned SIZE_strh_vids = 4 + 4*2 + 2*2 + 8*4 + 2*4;
228 const unsigned SIZE_strf_vids = 4*3 + 2*2 + 4*6;
229 const unsigned SIZE_strl_vids = 4+ 4+(4+SIZE_strh_vids) + 4+(4+SIZE_strf_vids);
231 const unsigned SIZE_strh_auds = 4 + 4*3 + 2*2 + 4*8 + 2*4;
232 const unsigned SIZE_strf_auds = 2*2 + 4*2 + 2*3;
233 const unsigned SIZE_strl_auds = 4+ 4+(4+SIZE_strh_auds) + 4+(4+SIZE_strf_auds);
235 const unsigned SIZE_avih = 4*12;
236 const unsigned SIZE_hdrl = 4+4+ (4+SIZE_avih) + 4 + (4+SIZE_strl_vids) + 4 + (4+SIZE_strl_auds);
237 const unsigned SIZE_movi = 4 + nframes*(4+4+framesize);
238 const unsigned SIZE_avi = 4+4+ (4+SIZE_hdrl) + 4 + (4+SIZE_movi);
240 const unsigned char AVIheader[] =
241 {
242 s4("RIFF"),
243 u32(SIZE_avi),
244 s4("AVI "),
246 // HEADER
248 s4("LIST"),
249 u32(SIZE_hdrl),
250 s4("hdrl"),
252 s4("avih"),
253 u32(SIZE_avih),
254 u32(0),
255 u32(0),
256 u32(0),
257 u32(0),
258 u32(nframes),
259 u32(0),
260 u32(2), // two streams
261 u32(0),
262 u32(0),
263 u32(0),
264 u32(0),
265 u32(0),
267 // VIDEO HEADER
269 s4("LIST"),
270 u32(SIZE_strl_vids),
271 s4("strl"),
273 s4("strh"),
274 u32(SIZE_strh_vids),
275 s4("vids"),
276 u32(0),
277 u32(0),
278 u16(0),
279 u16(0),
280 u32(0),
281 u32(scale),
282 u32(scaled_fps),
283 u32(0),
284 u32(0),
285 u32(0),
286 u32(0),
287 u32(0),
288 u16(0),
289 u16(0),
290 u16(0),
291 u16(0),
293 s4("strf"),
294 u32(SIZE_strf_vids),
295 u32(0),
296 u32(width),
297 u32(height),
298 u16(0),
299 u16(0),
300 u32(fourcc),
301 u32(0),
302 u32(0),
303 u32(0),
304 u32(0),
305 u32(0),
307 // AUDIO HEADER
309 s4("LIST"),
310 u32(SIZE_strl_auds),
311 s4("strl"),
313 s4("strh"),
314 u32(SIZE_strh_auds),
315 s4("auds"),
316 u32(0), //fourcc
317 u32(0), //handler
318 u32(0), //flags
319 u16(0), //prio
320 u16(0), //lang
321 u32(0), //init frames
322 u32(1), //scale
323 u32(aud_rate),
324 u32(0), //start
325 u32(0), //rate*length
326 u32(1048576), //suggested bufsize
327 u32(0), //quality
328 u32(aud_chans * (aud_bits / 8)), //sample size
329 u16(0), //frame size
330 u16(0),
331 u16(0),
332 u16(0),
334 s4("strf"),
335 u32(SIZE_strf_auds),
336 u16(1), // pcm format
337 u16(aud_chans),
338 u32(aud_rate),
339 u32(aud_rate * aud_chans * (aud_bits/8)), // samples per second
340 u16(aud_chans * (aud_bits/8)), //block align
341 u16(aud_bits), //bits
342 u16(0), //cbSize
344 // MOVIE
346 s4("LIST"),
347 u32(SIZE_movi),
348 s4("movi")
349 };
351 FlushWrite(avifp, AVIheader, sizeof(AVIheader));
352 }
353 } AVI;
355 extern "C"
356 {
357 int LoggingEnabled = 0; /* 0=no, 1=yes, 2=recording! */
359 const char* NESVideoGetVideoCmd()
360 {
361 return VIDEO_CMD.c_str();
362 }
363 void NESVideoSetVideoCmd(const char *cmd)
364 {
365 VIDEO_CMD = cmd;
366 }
367 void NESVideoEnableDebugging( void videoMessageFunc(const char *msg), void audioMessageFunc(const char *msg) )
368 {
369 debugVideoMessageFunc = videoMessageFunc;
370 debugAudioMessageFunc = audioMessageFunc;
371 }
372 void NESVideoSetFileFuncs( FILE* open(const char *,const char *), int close(FILE*) )
373 {
374 openFunc = open;
375 closeFunc = close;
376 }
378 void NESVideoLoggingVideo
379 (const void*data, unsigned width,unsigned height,
380 unsigned fps_scaled
381 )
382 {
383 if(LoggingEnabled < 2) return;
385 unsigned LogoFrames = fps_scaled >> 24;
387 static bool First = true;
388 if(First)
389 {
390 First=false;
391 /* Bisqwit's logo addition routine. */
392 /* If you don't have his files, this function does nothing
393 * and it does not matter at all.
394 */
396 const char *background =
397 width==320 ? "logo320_240"
398 : width==160 ? "logo160_144"
399 : width==240 ? "logo240_160"
400 : height>224 ? "logo256_240"
401 : "logo256_224";
403 /* Note: This should be 1 second long. */
404 for(unsigned frame = 0; frame < LogoFrames; ++frame)
405 {
406 char Buf[4096];
407 sprintf(Buf, "/shares/home/bisqwit/povray/nesvlogo/%s_f%u.tga",
408 background, frame);
410 FILE*fp = fopen(Buf, "rb");
411 if(!fp) // write blackness when missing frames to keep the intro 1 second long:
412 {
413 unsigned bytes = width*height*2;
414 unsigned char* buf = (unsigned char*)malloc(bytes);
415 if(buf)
416 {
417 memset(buf,0,bytes);
418 AVI.Video(width,height,fps_scaled, buf);
419 if(debugVideoMessageFunc) videoFramesWritten--;
420 free(buf);
421 }
422 }
423 else // write 1 frame of the logo:
424 {
425 int idlen = fgetc(fp);
426 /* Silently ignore all other header data.
427 * These files are assumed to be uncompressed BGR24 tga files with Y swapped.
428 * Even their geometry is assumed to match perfectly.
429 */
430 fseek(fp, 1+1+2+2+1+ /*org*/2+2+ /*geo*/2+2+ 1+1+idlen, SEEK_CUR);
432 bool yflip=true;
433 std::vector<unsigned char> data(width*height*3);
434 for(unsigned y=height; y-->0; )
435 fread(&data[y*width*3], 1, width*3, fp);
436 fclose(fp);
438 std::vector<unsigned short> result(width*height);
439 for(unsigned pos=0, max=result.size(); pos<max; ++pos)
440 {
441 unsigned usepos = pos;
442 if(yflip)
443 {
444 unsigned y = pos/width;
445 usepos = (usepos%width) + (height-y-1)*width;
446 }
448 unsigned B = data[usepos*3+0];
449 unsigned G = data[usepos*3+1];
450 unsigned R = data[usepos*3+2];
451 result[pos] = ((B*31/255)<<0)
452 | ((G*63/255)<<5)
453 | ((R*31/255)<<11);
454 }
455 AVI.Video(width,height,fps_scaled, (const unsigned char*)&result[0]);
456 if(debugVideoMessageFunc) videoFramesWritten--;
457 }
458 }
459 }
460 AVI.Video(width,height,fps_scaled, (const unsigned char*) data);
461 }
463 void NESVideoLoggingAudio
464 (const void*data,
465 unsigned rate, unsigned bits, unsigned chans,
466 unsigned nsamples)
467 {
468 if(LoggingEnabled < 2) return;
470 static bool First = true;
471 if(First)
472 {
473 First=false;
475 const unsigned n = rate; // assumes 1 second of logo to write silence for
476 if(n > 0)
477 {
478 unsigned bytes = n*chans*(bits/8);
479 unsigned char* buf = (unsigned char*)malloc(bytes);
480 if(buf)
481 {
482 memset(buf,0,bytes);
483 AVI.Audio(rate,bits,chans, buf, n);
484 free(buf);
485 }
486 }
487 }
489 AVI.Audio(rate,bits,chans, (const unsigned char*) data, nsamples);
490 }
491 } /* extern "C" */
495 static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length)
496 {
497 /// unsigned failures = 0;
498 /// const static int FAILURE_THRESH = 8092; // don't want to loop infinitely if we keep failing to make progress - actually maybe you would want this, so the checking is disabled
499 while(length > 0 /*&& failures < FAILURE_THRESH*/)
500 {
501 unsigned written = fwrite(buf, 1, length, fp);
502 /// if(written == 0)
503 /// failures++;
504 /// else
505 /// {
506 length -= written;
507 buf += written;
508 /// failures = 0;
509 /// }
510 }
511 /// if(failures >= FAILURE_THRESH)
512 /// {
513 /// fprintf(stderr, "FlushWrite() failed to write %d bytes %d times - giving up.", length, failures);
514 /// LoggingEnabled = 0;
515 /// }
516 }
518 // for the UB tech
519 #undef BGR24
520 #undef BGR16
521 #undef BGR15
523 #undef u32
524 #undef u16
525 #undef s4