Mercurial > vba-linux
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 fourcc20 #define BGR16 (0x42475210) // BGR16 fourcc21 #define BGR15 (0x4247520F) // BGR15 fourcc23 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 either27 #include <cstdlib>28 #define popen _popen;29 #define pclose _pclose;30 #endif32 #define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&25533 #define u16(n) (n)&255,((n)>>8)&25534 #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 NESVideoEnableDebugging39 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 AVI47 {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 byte91 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 else99 {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 frame124 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 else132 {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 Audio150 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 Video168 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; // default211 if(!closeFunc) closeFunc = pclose; // default213 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; //unknown224 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 // HEADER248 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 streams261 u32(0),262 u32(0),263 u32(0),264 u32(0),265 u32(0),267 // VIDEO HEADER269 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 HEADER309 s4("LIST"),310 u32(SIZE_strl_auds),311 s4("strl"),313 s4("strh"),314 u32(SIZE_strh_auds),315 s4("auds"),316 u32(0), //fourcc317 u32(0), //handler318 u32(0), //flags319 u16(0), //prio320 u16(0), //lang321 u32(0), //init frames322 u32(1), //scale323 u32(aud_rate),324 u32(0), //start325 u32(0), //rate*length326 u32(1048576), //suggested bufsize327 u32(0), //quality328 u32(aud_chans * (aud_bits / 8)), //sample size329 u16(0), //frame size330 u16(0),331 u16(0),332 u16(0),334 s4("strf"),335 u32(SIZE_strf_auds),336 u16(1), // pcm format337 u16(aud_chans),338 u32(aud_rate),339 u32(aud_rate * aud_chans * (aud_bits/8)), // samples per second340 u16(aud_chans * (aud_bits/8)), //block align341 u16(aud_bits), //bits342 u16(0), //cbSize344 // MOVIE346 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 NESVideoLoggingVideo379 (const void*data, unsigned width,unsigned height,380 unsigned fps_scaled381 )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 nothing393 * 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 NESVideoLoggingAudio464 (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 for476 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 disabled499 while(length > 0 /*&& failures < FAILURE_THRESH*/)500 {501 unsigned written = fwrite(buf, 1, length, fp);502 /// if(written == 0)503 /// failures++;504 /// else505 /// {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 tech519 #undef BGR24520 #undef BGR16521 #undef BGR15523 #undef u32524 #undef u16525 #undef s4