diff src/common/nesvideos-piece.cpp @ 1:f9f4f1b99eed

importing src directory
author Robert McIntyre <rlm@mit.edu>
date Sat, 03 Mar 2012 10:31:27 -0600
parents
children
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/common/nesvideos-piece.cpp	Sat Mar 03 10:31:27 2012 -0600
     1.3 @@ -0,0 +1,525 @@
     1.4 +#include <cmath>
     1.5 +#include <cstdio>
     1.6 +#include <string>
     1.7 +#include <vector>
     1.8 +
     1.9 +/* Note: This module assumes everyone uses RGB15 as display depth */
    1.10 +
    1.11 +static std::string VIDEO_CMD =
    1.12 +    "mencoder - -o test0.avi"
    1.13 +    " -noskip -mc 0"
    1.14 +    " -ovc lavc"
    1.15 +    " -oac mp3lame"
    1.16 +    " -lameopts preset=256:aq=2:mode=3"
    1.17 +    " -lavcopts vcodec=ffv1:context=0:format=BGR32:coder=0:vstrict=-1"
    1.18 +    " >& mencoder.log";
    1.19 +
    1.20 +static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length);
    1.21 +
    1.22 +#define BGR24 (0x42475218)  // BGR24 fourcc
    1.23 +#define BGR16 (0x42475210)  // BGR16 fourcc
    1.24 +#define BGR15 (0x4247520F)  // BGR15 fourcc
    1.25 +
    1.26 +static FILE* (*openFunc)  (const char*, const char*) = NULL;
    1.27 +static int (*closeFunc) (FILE*) = NULL;
    1.28 +
    1.29 +#if (defined(WIN32) || defined(win32)) // capital is standard, but check for either
    1.30 + #include <cstdlib>
    1.31 + #define popen _popen;
    1.32 + #define pclose _pclose;
    1.33 +#endif
    1.34 +
    1.35 +#define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&255
    1.36 +#define u16(n) (n)&255,((n)>>8)&255
    1.37 +#define s4(s) s[0],s[1],s[2],s[3]
    1.38 +
    1.39 +static const unsigned FPS_SCALE = (0x1000000);
    1.40 +
    1.41 +// general-purpose A/V sync debugging, ignored unless explicitly enabled with NESVideoEnableDebugging
    1.42 +static void (*debugVideoMessageFunc)(const char *msg) = NULL;
    1.43 +static void (*debugAudioMessageFunc)(const char *msg) = NULL;
    1.44 +// 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)
    1.45 +static unsigned audioFramesWritten=0, videoFramesWritten=1;
    1.46 +static double audioSecondsWritten=0, videoSecondsWritten=0;
    1.47 +
    1.48 +
    1.49 +static class AVI
    1.50 +{
    1.51 +    FILE* avifp;
    1.52 +    
    1.53 +    bool KnowVideo;
    1.54 +    unsigned width;
    1.55 +    unsigned height;
    1.56 +    unsigned fps_scaled;
    1.57 +    std::vector<unsigned char> VideoBuffer;
    1.58 +    
    1.59 +    bool KnowAudio;
    1.60 +    unsigned rate;
    1.61 +    unsigned chans;
    1.62 +    unsigned bits;
    1.63 +    std::vector<unsigned char> AudioBuffer;
    1.64 +    
    1.65 +public:
    1.66 +    AVI() :
    1.67 +        avifp(NULL),
    1.68 +        KnowVideo(false),
    1.69 +        KnowAudio(false)
    1.70 +    {
    1.71 +    }
    1.72 +    ~AVI()
    1.73 +    {
    1.74 +        if(avifp) closeFunc(avifp);
    1.75 +    }
    1.76 +    
    1.77 +    void Audio(unsigned r,unsigned b,unsigned c,
    1.78 +               const unsigned char*d, unsigned nsamples)
    1.79 +    {
    1.80 +        if(!KnowAudio)
    1.81 +        {
    1.82 +            rate = r;
    1.83 +            chans = c;
    1.84 +            bits = b;
    1.85 +            KnowAudio = true;
    1.86 +            CheckFlushing();
    1.87 +        }
    1.88 +        unsigned bytes = nsamples*chans*(bits/8);
    1.89 +
    1.90 +		if(debugAudioMessageFunc)
    1.91 +		{
    1.92 +			audioFramesWritten++;
    1.93 +			audioSecondsWritten += (double)nsamples / (double)rate; // += bytes times seconds per byte
    1.94 +			char temp [64];
    1.95 +			sprintf(temp, "A: %.2lf s, %d f", audioSecondsWritten, audioFramesWritten);
    1.96 +			debugAudioMessageFunc(temp);
    1.97 +		}
    1.98 +
    1.99 +        if(KnowVideo)
   1.100 +            SendAudioFrame(d, bytes);
   1.101 +        else
   1.102 +        {
   1.103 +            AudioBuffer.insert(AudioBuffer.end(), d, d+bytes);
   1.104 +            fprintf(stderr, "Buffering %u bytes of audio\n", bytes);
   1.105 +        }
   1.106 +    }
   1.107 +    void Video(unsigned w,unsigned h,unsigned f, const unsigned char*d)
   1.108 +    {
   1.109 +        if(!KnowVideo)
   1.110 +        {
   1.111 +            width=w;
   1.112 +            height=h;
   1.113 +            fps_scaled=f;
   1.114 +            KnowVideo = true;
   1.115 +            CheckFlushing();
   1.116 +        }
   1.117 +        
   1.118 +        unsigned bytes = width*height*2;
   1.119 +        
   1.120 +        //std::vector<unsigned char> tmp(bytes, 'k');
   1.121 +        //d = &tmp[0];
   1.122 +
   1.123 +		if(debugVideoMessageFunc)
   1.124 +		{
   1.125 +			videoFramesWritten++;
   1.126 +			videoSecondsWritten += (double)FPS_SCALE / (double)fps_scaled; // += seconds per frame
   1.127 +			char temp [64];
   1.128 +			sprintf(temp, "V: %.2lf s, %d f", videoSecondsWritten, videoFramesWritten);
   1.129 +			debugVideoMessageFunc(temp);
   1.130 +		}
   1.131 +
   1.132 +        if(KnowAudio)
   1.133 +            SendVideoFrame(d, bytes);
   1.134 +        else
   1.135 +        {
   1.136 +            VideoBuffer.insert(VideoBuffer.end(), d, d+bytes);
   1.137 +            fprintf(stderr, "Buffering %u bytes of video\n", bytes);
   1.138 +        }
   1.139 +    }
   1.140 +
   1.141 +private:
   1.142 +    void CheckFlushing()
   1.143 +    {
   1.144 +        //AudioBuffer.clear();
   1.145 +        //VideoBuffer.clear();
   1.146 +        
   1.147 +        if(KnowAudio && KnowVideo)
   1.148 +        {
   1.149 +            unsigned last_offs;
   1.150 +            
   1.151 +            // Flush Audio
   1.152 +            
   1.153 +            last_offs = 0;
   1.154 +            while(last_offs < AudioBuffer.size())
   1.155 +            {
   1.156 +                unsigned bytes = rate / (fps_scaled / FPS_SCALE);
   1.157 +                bytes *= chans*(bits/8);
   1.158 +                
   1.159 +                unsigned remain = AudioBuffer.size() - last_offs;
   1.160 +                if(bytes > remain) bytes = remain;
   1.161 +                if(!bytes) break;
   1.162 +                
   1.163 +                unsigned begin = last_offs;
   1.164 +                last_offs += bytes;
   1.165 +                SendAudioFrame(&AudioBuffer[begin], bytes);
   1.166 +            }
   1.167 +            AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin()+last_offs);
   1.168 +            
   1.169 +            // Flush Video
   1.170 +            
   1.171 +            last_offs = 0;
   1.172 +            while(last_offs < VideoBuffer.size())
   1.173 +            {
   1.174 +                unsigned bytes  = width*height*2;
   1.175 +                unsigned remain = VideoBuffer.size() - last_offs;
   1.176 +                if(bytes > remain) bytes = remain;
   1.177 +                if(!bytes)break;
   1.178 +                
   1.179 +                unsigned begin = last_offs;
   1.180 +                last_offs += bytes;
   1.181 +                SendVideoFrame(&VideoBuffer[begin], bytes);
   1.182 +            }
   1.183 +            VideoBuffer.erase(VideoBuffer.begin(), VideoBuffer.begin()+last_offs);
   1.184 +        }
   1.185 +    }
   1.186 +    
   1.187 +    void SendVideoFrame(const unsigned char* vidbuf, unsigned framesize)
   1.188 +    {
   1.189 +        CheckBegin();
   1.190 +        
   1.191 +        //fprintf(stderr, "Writing 00dc of %u bytes\n", framesize);
   1.192 +        
   1.193 +        const unsigned char header[] = { s4("00dc"), u32(framesize) };
   1.194 +        FlushWrite(avifp, header, sizeof(header));
   1.195 +        FlushWrite(avifp, vidbuf, framesize);
   1.196 +    }
   1.197 +
   1.198 +    void SendAudioFrame(const unsigned char* audbuf, unsigned framesize)
   1.199 +    {
   1.200 +        CheckBegin();
   1.201 +        
   1.202 +        //fprintf(stderr, "Writing 01wb of %u bytes\n", framesize);
   1.203 +        
   1.204 +        const unsigned char header[] = { s4("01wb"), u32(framesize) };
   1.205 +        FlushWrite(avifp, header, sizeof(header));
   1.206 +        FlushWrite(avifp, audbuf, framesize);
   1.207 +    }
   1.208 +
   1.209 +    void CheckBegin()
   1.210 +    {
   1.211 +        if(avifp) return;
   1.212 +        
   1.213 +		if(!openFunc) openFunc = popen; // default
   1.214 +		if(!closeFunc) closeFunc = pclose; // default
   1.215 +
   1.216 +        avifp = openFunc(VIDEO_CMD.c_str(), "wb");
   1.217 +        if(!avifp) return;
   1.218 +
   1.219 +        const unsigned fourcc = BGR16;
   1.220 +        const unsigned framesize = width*height*2;
   1.221 +        
   1.222 +        const unsigned aud_rate  = rate;
   1.223 +        const unsigned aud_chans = chans;
   1.224 +        const unsigned aud_bits  = bits;
   1.225 +
   1.226 +        const unsigned nframes    = 0; //unknown
   1.227 +        const unsigned scale      = FPS_SCALE;
   1.228 +        const unsigned scaled_fps = fps_scaled;
   1.229 +        
   1.230 +        const unsigned SIZE_strh_vids = 4 + 4*2 + 2*2 + 8*4 + 2*4;
   1.231 +        const unsigned SIZE_strf_vids = 4*3 + 2*2 + 4*6;
   1.232 +        const unsigned SIZE_strl_vids = 4+ 4+(4+SIZE_strh_vids) + 4+(4+SIZE_strf_vids);
   1.233 +
   1.234 +        const unsigned SIZE_strh_auds = 4 + 4*3 + 2*2 + 4*8 + 2*4;
   1.235 +        const unsigned SIZE_strf_auds = 2*2 + 4*2 + 2*3;
   1.236 +        const unsigned SIZE_strl_auds = 4+ 4+(4+SIZE_strh_auds) + 4+(4+SIZE_strf_auds);
   1.237 +        
   1.238 +        const unsigned SIZE_avih = 4*12;
   1.239 +        const unsigned SIZE_hdrl = 4+4+ (4+SIZE_avih) + 4 + (4+SIZE_strl_vids) + 4 + (4+SIZE_strl_auds);
   1.240 +        const unsigned SIZE_movi = 4 + nframes*(4+4+framesize);
   1.241 +        const unsigned SIZE_avi = 4+4+ (4+SIZE_hdrl) + 4 + (4+SIZE_movi);
   1.242 +        
   1.243 +        const unsigned char AVIheader[] =
   1.244 +        {
   1.245 +            s4("RIFF"),
   1.246 +            u32(SIZE_avi),
   1.247 +            s4("AVI "),   
   1.248 +            
   1.249 +            // HEADER
   1.250 +
   1.251 +            s4("LIST"),   
   1.252 +            u32(SIZE_hdrl),
   1.253 +             s4("hdrl"),   
   1.254 +             
   1.255 +             s4("avih"),
   1.256 +             u32(SIZE_avih),
   1.257 +              u32(0),
   1.258 +              u32(0),
   1.259 +              u32(0),
   1.260 +              u32(0),
   1.261 +              u32(nframes),
   1.262 +              u32(0),
   1.263 +              u32(2), // two streams
   1.264 +              u32(0),
   1.265 +              u32(0),
   1.266 +              u32(0),
   1.267 +              u32(0),
   1.268 +              u32(0),
   1.269 +             
   1.270 +             // VIDEO HEADER
   1.271 +             
   1.272 +             s4("LIST"),
   1.273 +             u32(SIZE_strl_vids),
   1.274 +              s4("strl"),   
   1.275 +              
   1.276 +               s4("strh"),
   1.277 +               u32(SIZE_strh_vids),
   1.278 +                s4("vids"),
   1.279 +                u32(0),
   1.280 +                u32(0),
   1.281 +                u16(0),
   1.282 +                u16(0),
   1.283 +                u32(0),
   1.284 +                u32(scale),
   1.285 +                u32(scaled_fps),
   1.286 +                u32(0),
   1.287 +                u32(0),
   1.288 +                u32(0),
   1.289 +                u32(0),
   1.290 +                u32(0),
   1.291 +                u16(0),
   1.292 +                u16(0),
   1.293 +                u16(0),
   1.294 +                u16(0),
   1.295 +               
   1.296 +               s4("strf"),
   1.297 +               u32(SIZE_strf_vids),
   1.298 +                u32(0),
   1.299 +                u32(width),
   1.300 +                u32(height),
   1.301 +                u16(0),
   1.302 +                u16(0),
   1.303 +                u32(fourcc),
   1.304 +                u32(0),
   1.305 +                u32(0),
   1.306 +                u32(0),
   1.307 +                u32(0),
   1.308 +                u32(0),
   1.309 +             
   1.310 +             // AUDIO HEADER
   1.311 +             
   1.312 +             s4("LIST"),
   1.313 +             u32(SIZE_strl_auds),
   1.314 +              s4("strl"),   
   1.315 +              
   1.316 +               s4("strh"),
   1.317 +               u32(SIZE_strh_auds),
   1.318 +                s4("auds"),
   1.319 +                u32(0), //fourcc
   1.320 +                u32(0), //handler
   1.321 +                u32(0), //flags
   1.322 +                u16(0), //prio
   1.323 +                u16(0), //lang
   1.324 +                u32(0), //init frames
   1.325 +                u32(1), //scale
   1.326 +                u32(aud_rate),
   1.327 +                u32(0), //start
   1.328 +                u32(0), //rate*length
   1.329 +                u32(1048576), //suggested bufsize
   1.330 +                u32(0), //quality
   1.331 +                u32(aud_chans * (aud_bits / 8)), //sample size
   1.332 +                u16(0), //frame size
   1.333 +                u16(0),
   1.334 +                u16(0),
   1.335 +                u16(0),
   1.336 +               
   1.337 +               s4("strf"),
   1.338 +               u32(SIZE_strf_auds),
   1.339 +                u16(1), // pcm format
   1.340 +                u16(aud_chans),
   1.341 +                u32(aud_rate),
   1.342 +                u32(aud_rate * aud_chans * (aud_bits/8)), // samples per second
   1.343 +                u16(aud_chans * (aud_bits/8)), //block align
   1.344 +                u16(aud_bits), //bits
   1.345 +                u16(0), //cbSize
   1.346 +
   1.347 +            // MOVIE
   1.348 +
   1.349 +            s4("LIST"),
   1.350 +            u32(SIZE_movi),
   1.351 +             s4("movi")
   1.352 +        };
   1.353 +          
   1.354 +        FlushWrite(avifp, AVIheader, sizeof(AVIheader));
   1.355 +    }
   1.356 +} AVI;
   1.357 +
   1.358 +extern "C"
   1.359 +{
   1.360 +    int LoggingEnabled = 0; /* 0=no, 1=yes, 2=recording! */
   1.361 +
   1.362 +    const char* NESVideoGetVideoCmd()
   1.363 +    {
   1.364 +        return VIDEO_CMD.c_str();
   1.365 +    }
   1.366 +    void NESVideoSetVideoCmd(const char *cmd)
   1.367 +    {
   1.368 +        VIDEO_CMD = cmd;
   1.369 +    }
   1.370 +	void NESVideoEnableDebugging( void videoMessageFunc(const char *msg), void audioMessageFunc(const char *msg) )
   1.371 +	{
   1.372 +		debugVideoMessageFunc = videoMessageFunc;
   1.373 +		debugAudioMessageFunc = audioMessageFunc;
   1.374 +	}
   1.375 +	void NESVideoSetFileFuncs( FILE* open(const char *,const char *), int close(FILE*) )
   1.376 +	{
   1.377 +		openFunc = open;
   1.378 +		closeFunc = close;
   1.379 +	}
   1.380 +
   1.381 +    void NESVideoLoggingVideo
   1.382 +        (const void*data, unsigned width,unsigned height,
   1.383 +         unsigned fps_scaled
   1.384 +        )
   1.385 +    {
   1.386 +        if(LoggingEnabled < 2) return;
   1.387 +        
   1.388 +        unsigned LogoFrames = fps_scaled >> 24;
   1.389 +
   1.390 +        static bool First = true;
   1.391 +        if(First)
   1.392 +        {
   1.393 +            First=false;
   1.394 +            /* Bisqwit's logo addition routine. */
   1.395 +            /* If you don't have his files, this function does nothing
   1.396 +             * and it does not matter at all.
   1.397 +             */
   1.398 +            
   1.399 +            const char *background =
   1.400 +                width==320 ? "logo320_240"
   1.401 +              : width==160 ? "logo160_144"
   1.402 +              : width==240 ? "logo240_160"
   1.403 +              : height>224 ? "logo256_240"
   1.404 +              :              "logo256_224";
   1.405 +            
   1.406 +            /* Note: This should be 1 second long. */
   1.407 +            for(unsigned frame = 0; frame < LogoFrames; ++frame)
   1.408 +            {
   1.409 +                char Buf[4096];
   1.410 +                sprintf(Buf, "/shares/home/bisqwit/povray/nesvlogo/%s_f%u.tga",
   1.411 +                    background, frame);
   1.412 +                
   1.413 +                FILE*fp = fopen(Buf, "rb");
   1.414 +                if(!fp) // write blackness when missing frames to keep the intro 1 second long:
   1.415 +				{
   1.416 +			        unsigned bytes = width*height*2;
   1.417 +					unsigned char* buf = (unsigned char*)malloc(bytes);
   1.418 +					if(buf)
   1.419 +					{
   1.420 +						memset(buf,0,bytes);
   1.421 +						AVI.Video(width,height,fps_scaled, buf);
   1.422 +						if(debugVideoMessageFunc) videoFramesWritten--;
   1.423 +						free(buf);
   1.424 +					}
   1.425 +				}
   1.426 +				else // write 1 frame of the logo:
   1.427 +				{
   1.428 +					int idlen = fgetc(fp);
   1.429 +					/* Silently ignore all other header data.
   1.430 +					 * These files are assumed to be uncompressed BGR24 tga files with Y swapped.
   1.431 +					 * Even their geometry is assumed to match perfectly.
   1.432 +					 */
   1.433 +					fseek(fp, 1+1+2+2+1+ /*org*/2+2+ /*geo*/2+2+ 1+1+idlen, SEEK_CUR);
   1.434 +
   1.435 +					bool yflip=true;
   1.436 +					std::vector<unsigned char> data(width*height*3);
   1.437 +					for(unsigned y=height; y-->0; )
   1.438 +						fread(&data[y*width*3], 1, width*3, fp);
   1.439 +					fclose(fp);
   1.440 +	                
   1.441 +					std::vector<unsigned short> result(width*height);
   1.442 +					for(unsigned pos=0, max=result.size(); pos<max; ++pos)
   1.443 +					{
   1.444 +						unsigned usepos = pos;
   1.445 +						if(yflip)
   1.446 +						{
   1.447 +							unsigned y = pos/width;
   1.448 +							usepos = (usepos%width) + (height-y-1)*width;
   1.449 +						}
   1.450 +	                    
   1.451 +						unsigned B = data[usepos*3+0];
   1.452 +						unsigned G = data[usepos*3+1];
   1.453 +						unsigned R = data[usepos*3+2];
   1.454 +						result[pos] = ((B*31/255)<<0)
   1.455 +									| ((G*63/255)<<5)
   1.456 +									| ((R*31/255)<<11);
   1.457 +					}
   1.458 +					AVI.Video(width,height,fps_scaled, (const unsigned char*)&result[0]);
   1.459 +					if(debugVideoMessageFunc) videoFramesWritten--;
   1.460 +				}
   1.461 +            }
   1.462 +        }
   1.463 +        AVI.Video(width,height,fps_scaled,  (const unsigned char*) data);
   1.464 +    }
   1.465 +
   1.466 +    void NESVideoLoggingAudio
   1.467 +        (const void*data,
   1.468 +         unsigned rate, unsigned bits, unsigned chans,
   1.469 +         unsigned nsamples)
   1.470 +    {
   1.471 +        if(LoggingEnabled < 2) return;
   1.472 +
   1.473 +        static bool First = true;
   1.474 +        if(First)
   1.475 +        {
   1.476 +            First=false;
   1.477 +            
   1.478 +			const unsigned n = rate; // assumes 1 second of logo to write silence for
   1.479 +            if(n > 0)
   1.480 +			{
   1.481 +				unsigned bytes = n*chans*(bits/8);
   1.482 +				unsigned char* buf = (unsigned char*)malloc(bytes);
   1.483 +				if(buf)
   1.484 +				{
   1.485 +					memset(buf,0,bytes);
   1.486 +					AVI.Audio(rate,bits,chans, buf, n);
   1.487 +					free(buf);
   1.488 +				}
   1.489 +			}
   1.490 +        }
   1.491 +        
   1.492 +        AVI.Audio(rate,bits,chans, (const unsigned char*) data, nsamples);
   1.493 +    }
   1.494 +} /* extern "C" */
   1.495 +
   1.496 +
   1.497 +
   1.498 +static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length)
   1.499 +{
   1.500 +///	unsigned failures = 0;
   1.501 +///	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
   1.502 +    while(length > 0 /*&& failures < FAILURE_THRESH*/)
   1.503 +    {
   1.504 +        unsigned written = fwrite(buf, 1, length, fp);
   1.505 +///		if(written == 0)
   1.506 +///			failures++;
   1.507 +///		else
   1.508 +///		{
   1.509 +			length -= written;
   1.510 +			buf += written;
   1.511 +///			failures = 0;
   1.512 +///		}
   1.513 +    }
   1.514 +///	if(failures >= FAILURE_THRESH)
   1.515 +///	{
   1.516 +///		fprintf(stderr, "FlushWrite() failed to write %d bytes %d times - giving up.", length, failures);
   1.517 +///		LoggingEnabled = 0;
   1.518 +///	}
   1.519 +}
   1.520 +
   1.521 +// for the UB tech
   1.522 +#undef BGR24
   1.523 +#undef BGR16
   1.524 +#undef BGR15
   1.525 +
   1.526 +#undef u32
   1.527 +#undef u16
   1.528 +#undef s4