rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: rlm@1: /* Note: This module assumes everyone uses RGB15 as display depth */ rlm@1: rlm@1: static std::string VIDEO_CMD = rlm@1: "mencoder - -o test0.avi" rlm@1: " -noskip -mc 0" rlm@1: " -ovc lavc" rlm@1: " -oac mp3lame" rlm@1: " -lameopts preset=256:aq=2:mode=3" rlm@1: " -lavcopts vcodec=ffv1:context=0:format=BGR32:coder=0:vstrict=-1" rlm@1: " >& mencoder.log"; rlm@1: rlm@1: static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length); rlm@1: rlm@1: #define BGR24 (0x42475218) // BGR24 fourcc rlm@1: #define BGR16 (0x42475210) // BGR16 fourcc rlm@1: #define BGR15 (0x4247520F) // BGR15 fourcc rlm@1: rlm@1: static FILE* (*openFunc) (const char*, const char*) = NULL; rlm@1: static int (*closeFunc) (FILE*) = NULL; rlm@1: rlm@1: #if (defined(WIN32) || defined(win32)) // capital is standard, but check for either rlm@1: #include rlm@1: #define popen _popen; rlm@1: #define pclose _pclose; rlm@1: #endif rlm@1: rlm@1: #define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&255 rlm@1: #define u16(n) (n)&255,((n)>>8)&255 rlm@1: #define s4(s) s[0],s[1],s[2],s[3] rlm@1: rlm@1: static const unsigned FPS_SCALE = (0x1000000); rlm@1: rlm@1: // general-purpose A/V sync debugging, ignored unless explicitly enabled with NESVideoEnableDebugging rlm@1: static void (*debugVideoMessageFunc)(const char *msg) = NULL; rlm@1: static void (*debugAudioMessageFunc)(const char *msg) = NULL; rlm@1: // 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) rlm@1: static unsigned audioFramesWritten=0, videoFramesWritten=1; rlm@1: static double audioSecondsWritten=0, videoSecondsWritten=0; rlm@1: rlm@1: rlm@1: static class AVI rlm@1: { rlm@1: FILE* avifp; rlm@1: rlm@1: bool KnowVideo; rlm@1: unsigned width; rlm@1: unsigned height; rlm@1: unsigned fps_scaled; rlm@1: std::vector VideoBuffer; rlm@1: rlm@1: bool KnowAudio; rlm@1: unsigned rate; rlm@1: unsigned chans; rlm@1: unsigned bits; rlm@1: std::vector AudioBuffer; rlm@1: rlm@1: public: rlm@1: AVI() : rlm@1: avifp(NULL), rlm@1: KnowVideo(false), rlm@1: KnowAudio(false) rlm@1: { rlm@1: } rlm@1: ~AVI() rlm@1: { rlm@1: if(avifp) closeFunc(avifp); rlm@1: } rlm@1: rlm@1: void Audio(unsigned r,unsigned b,unsigned c, rlm@1: const unsigned char*d, unsigned nsamples) rlm@1: { rlm@1: if(!KnowAudio) rlm@1: { rlm@1: rate = r; rlm@1: chans = c; rlm@1: bits = b; rlm@1: KnowAudio = true; rlm@1: CheckFlushing(); rlm@1: } rlm@1: unsigned bytes = nsamples*chans*(bits/8); rlm@1: rlm@1: if(debugAudioMessageFunc) rlm@1: { rlm@1: audioFramesWritten++; rlm@1: audioSecondsWritten += (double)nsamples / (double)rate; // += bytes times seconds per byte rlm@1: char temp [64]; rlm@1: sprintf(temp, "A: %.2lf s, %d f", audioSecondsWritten, audioFramesWritten); rlm@1: debugAudioMessageFunc(temp); rlm@1: } rlm@1: rlm@1: if(KnowVideo) rlm@1: SendAudioFrame(d, bytes); rlm@1: else rlm@1: { rlm@1: AudioBuffer.insert(AudioBuffer.end(), d, d+bytes); rlm@1: fprintf(stderr, "Buffering %u bytes of audio\n", bytes); rlm@1: } rlm@1: } rlm@1: void Video(unsigned w,unsigned h,unsigned f, const unsigned char*d) rlm@1: { rlm@1: if(!KnowVideo) rlm@1: { rlm@1: width=w; rlm@1: height=h; rlm@1: fps_scaled=f; rlm@1: KnowVideo = true; rlm@1: CheckFlushing(); rlm@1: } rlm@1: rlm@1: unsigned bytes = width*height*2; rlm@1: rlm@1: //std::vector tmp(bytes, 'k'); rlm@1: //d = &tmp[0]; rlm@1: rlm@1: if(debugVideoMessageFunc) rlm@1: { rlm@1: videoFramesWritten++; rlm@1: videoSecondsWritten += (double)FPS_SCALE / (double)fps_scaled; // += seconds per frame rlm@1: char temp [64]; rlm@1: sprintf(temp, "V: %.2lf s, %d f", videoSecondsWritten, videoFramesWritten); rlm@1: debugVideoMessageFunc(temp); rlm@1: } rlm@1: rlm@1: if(KnowAudio) rlm@1: SendVideoFrame(d, bytes); rlm@1: else rlm@1: { rlm@1: VideoBuffer.insert(VideoBuffer.end(), d, d+bytes); rlm@1: fprintf(stderr, "Buffering %u bytes of video\n", bytes); rlm@1: } rlm@1: } rlm@1: rlm@1: private: rlm@1: void CheckFlushing() rlm@1: { rlm@1: //AudioBuffer.clear(); rlm@1: //VideoBuffer.clear(); rlm@1: rlm@1: if(KnowAudio && KnowVideo) rlm@1: { rlm@1: unsigned last_offs; rlm@1: rlm@1: // Flush Audio rlm@1: rlm@1: last_offs = 0; rlm@1: while(last_offs < AudioBuffer.size()) rlm@1: { rlm@1: unsigned bytes = rate / (fps_scaled / FPS_SCALE); rlm@1: bytes *= chans*(bits/8); rlm@1: rlm@1: unsigned remain = AudioBuffer.size() - last_offs; rlm@1: if(bytes > remain) bytes = remain; rlm@1: if(!bytes) break; rlm@1: rlm@1: unsigned begin = last_offs; rlm@1: last_offs += bytes; rlm@1: SendAudioFrame(&AudioBuffer[begin], bytes); rlm@1: } rlm@1: AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin()+last_offs); rlm@1: rlm@1: // Flush Video rlm@1: rlm@1: last_offs = 0; rlm@1: while(last_offs < VideoBuffer.size()) rlm@1: { rlm@1: unsigned bytes = width*height*2; rlm@1: unsigned remain = VideoBuffer.size() - last_offs; rlm@1: if(bytes > remain) bytes = remain; rlm@1: if(!bytes)break; rlm@1: rlm@1: unsigned begin = last_offs; rlm@1: last_offs += bytes; rlm@1: SendVideoFrame(&VideoBuffer[begin], bytes); rlm@1: } rlm@1: VideoBuffer.erase(VideoBuffer.begin(), VideoBuffer.begin()+last_offs); rlm@1: } rlm@1: } rlm@1: rlm@1: void SendVideoFrame(const unsigned char* vidbuf, unsigned framesize) rlm@1: { rlm@1: CheckBegin(); rlm@1: rlm@1: //fprintf(stderr, "Writing 00dc of %u bytes\n", framesize); rlm@1: rlm@1: const unsigned char header[] = { s4("00dc"), u32(framesize) }; rlm@1: FlushWrite(avifp, header, sizeof(header)); rlm@1: FlushWrite(avifp, vidbuf, framesize); rlm@1: } rlm@1: rlm@1: void SendAudioFrame(const unsigned char* audbuf, unsigned framesize) rlm@1: { rlm@1: CheckBegin(); rlm@1: rlm@1: //fprintf(stderr, "Writing 01wb of %u bytes\n", framesize); rlm@1: rlm@1: const unsigned char header[] = { s4("01wb"), u32(framesize) }; rlm@1: FlushWrite(avifp, header, sizeof(header)); rlm@1: FlushWrite(avifp, audbuf, framesize); rlm@1: } rlm@1: rlm@1: void CheckBegin() rlm@1: { rlm@1: if(avifp) return; rlm@1: rlm@1: if(!openFunc) openFunc = popen; // default rlm@1: if(!closeFunc) closeFunc = pclose; // default rlm@1: rlm@1: avifp = openFunc(VIDEO_CMD.c_str(), "wb"); rlm@1: if(!avifp) return; rlm@1: rlm@1: const unsigned fourcc = BGR16; rlm@1: const unsigned framesize = width*height*2; rlm@1: rlm@1: const unsigned aud_rate = rate; rlm@1: const unsigned aud_chans = chans; rlm@1: const unsigned aud_bits = bits; rlm@1: rlm@1: const unsigned nframes = 0; //unknown rlm@1: const unsigned scale = FPS_SCALE; rlm@1: const unsigned scaled_fps = fps_scaled; rlm@1: rlm@1: const unsigned SIZE_strh_vids = 4 + 4*2 + 2*2 + 8*4 + 2*4; rlm@1: const unsigned SIZE_strf_vids = 4*3 + 2*2 + 4*6; rlm@1: const unsigned SIZE_strl_vids = 4+ 4+(4+SIZE_strh_vids) + 4+(4+SIZE_strf_vids); rlm@1: rlm@1: const unsigned SIZE_strh_auds = 4 + 4*3 + 2*2 + 4*8 + 2*4; rlm@1: const unsigned SIZE_strf_auds = 2*2 + 4*2 + 2*3; rlm@1: const unsigned SIZE_strl_auds = 4+ 4+(4+SIZE_strh_auds) + 4+(4+SIZE_strf_auds); rlm@1: rlm@1: const unsigned SIZE_avih = 4*12; rlm@1: const unsigned SIZE_hdrl = 4+4+ (4+SIZE_avih) + 4 + (4+SIZE_strl_vids) + 4 + (4+SIZE_strl_auds); rlm@1: const unsigned SIZE_movi = 4 + nframes*(4+4+framesize); rlm@1: const unsigned SIZE_avi = 4+4+ (4+SIZE_hdrl) + 4 + (4+SIZE_movi); rlm@1: rlm@1: const unsigned char AVIheader[] = rlm@1: { rlm@1: s4("RIFF"), rlm@1: u32(SIZE_avi), rlm@1: s4("AVI "), rlm@1: rlm@1: // HEADER rlm@1: rlm@1: s4("LIST"), rlm@1: u32(SIZE_hdrl), rlm@1: s4("hdrl"), rlm@1: rlm@1: s4("avih"), rlm@1: u32(SIZE_avih), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(nframes), rlm@1: u32(0), rlm@1: u32(2), // two streams rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: rlm@1: // VIDEO HEADER rlm@1: rlm@1: s4("LIST"), rlm@1: u32(SIZE_strl_vids), rlm@1: s4("strl"), rlm@1: rlm@1: s4("strh"), rlm@1: u32(SIZE_strh_vids), rlm@1: s4("vids"), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u16(0), rlm@1: u16(0), rlm@1: u32(0), rlm@1: u32(scale), rlm@1: u32(scaled_fps), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u16(0), rlm@1: u16(0), rlm@1: u16(0), rlm@1: u16(0), rlm@1: rlm@1: s4("strf"), rlm@1: u32(SIZE_strf_vids), rlm@1: u32(0), rlm@1: u32(width), rlm@1: u32(height), rlm@1: u16(0), rlm@1: u16(0), rlm@1: u32(fourcc), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: u32(0), rlm@1: rlm@1: // AUDIO HEADER rlm@1: rlm@1: s4("LIST"), rlm@1: u32(SIZE_strl_auds), rlm@1: s4("strl"), rlm@1: rlm@1: s4("strh"), rlm@1: u32(SIZE_strh_auds), rlm@1: s4("auds"), rlm@1: u32(0), //fourcc rlm@1: u32(0), //handler rlm@1: u32(0), //flags rlm@1: u16(0), //prio rlm@1: u16(0), //lang rlm@1: u32(0), //init frames rlm@1: u32(1), //scale rlm@1: u32(aud_rate), rlm@1: u32(0), //start rlm@1: u32(0), //rate*length rlm@1: u32(1048576), //suggested bufsize rlm@1: u32(0), //quality rlm@1: u32(aud_chans * (aud_bits / 8)), //sample size rlm@1: u16(0), //frame size rlm@1: u16(0), rlm@1: u16(0), rlm@1: u16(0), rlm@1: rlm@1: s4("strf"), rlm@1: u32(SIZE_strf_auds), rlm@1: u16(1), // pcm format rlm@1: u16(aud_chans), rlm@1: u32(aud_rate), rlm@1: u32(aud_rate * aud_chans * (aud_bits/8)), // samples per second rlm@1: u16(aud_chans * (aud_bits/8)), //block align rlm@1: u16(aud_bits), //bits rlm@1: u16(0), //cbSize rlm@1: rlm@1: // MOVIE rlm@1: rlm@1: s4("LIST"), rlm@1: u32(SIZE_movi), rlm@1: s4("movi") rlm@1: }; rlm@1: rlm@1: FlushWrite(avifp, AVIheader, sizeof(AVIheader)); rlm@1: } rlm@1: } AVI; rlm@1: rlm@1: extern "C" rlm@1: { rlm@1: int LoggingEnabled = 0; /* 0=no, 1=yes, 2=recording! */ rlm@1: rlm@1: const char* NESVideoGetVideoCmd() rlm@1: { rlm@1: return VIDEO_CMD.c_str(); rlm@1: } rlm@1: void NESVideoSetVideoCmd(const char *cmd) rlm@1: { rlm@1: VIDEO_CMD = cmd; rlm@1: } rlm@1: void NESVideoEnableDebugging( void videoMessageFunc(const char *msg), void audioMessageFunc(const char *msg) ) rlm@1: { rlm@1: debugVideoMessageFunc = videoMessageFunc; rlm@1: debugAudioMessageFunc = audioMessageFunc; rlm@1: } rlm@1: void NESVideoSetFileFuncs( FILE* open(const char *,const char *), int close(FILE*) ) rlm@1: { rlm@1: openFunc = open; rlm@1: closeFunc = close; rlm@1: } rlm@1: rlm@1: void NESVideoLoggingVideo rlm@1: (const void*data, unsigned width,unsigned height, rlm@1: unsigned fps_scaled rlm@1: ) rlm@1: { rlm@1: if(LoggingEnabled < 2) return; rlm@1: rlm@1: unsigned LogoFrames = fps_scaled >> 24; rlm@1: rlm@1: static bool First = true; rlm@1: if(First) rlm@1: { rlm@1: First=false; rlm@1: /* Bisqwit's logo addition routine. */ rlm@1: /* If you don't have his files, this function does nothing rlm@1: * and it does not matter at all. rlm@1: */ rlm@1: rlm@1: const char *background = rlm@1: width==320 ? "logo320_240" rlm@1: : width==160 ? "logo160_144" rlm@1: : width==240 ? "logo240_160" rlm@1: : height>224 ? "logo256_240" rlm@1: : "logo256_224"; rlm@1: rlm@1: /* Note: This should be 1 second long. */ rlm@1: for(unsigned frame = 0; frame < LogoFrames; ++frame) rlm@1: { rlm@1: char Buf[4096]; rlm@1: sprintf(Buf, "/shares/home/bisqwit/povray/nesvlogo/%s_f%u.tga", rlm@1: background, frame); rlm@1: rlm@1: FILE*fp = fopen(Buf, "rb"); rlm@1: if(!fp) // write blackness when missing frames to keep the intro 1 second long: rlm@1: { rlm@1: unsigned bytes = width*height*2; rlm@1: unsigned char* buf = (unsigned char*)malloc(bytes); rlm@1: if(buf) rlm@1: { rlm@1: memset(buf,0,bytes); rlm@1: AVI.Video(width,height,fps_scaled, buf); rlm@1: if(debugVideoMessageFunc) videoFramesWritten--; rlm@1: free(buf); rlm@1: } rlm@1: } rlm@1: else // write 1 frame of the logo: rlm@1: { rlm@1: int idlen = fgetc(fp); rlm@1: /* Silently ignore all other header data. rlm@1: * These files are assumed to be uncompressed BGR24 tga files with Y swapped. rlm@1: * Even their geometry is assumed to match perfectly. rlm@1: */ rlm@1: fseek(fp, 1+1+2+2+1+ /*org*/2+2+ /*geo*/2+2+ 1+1+idlen, SEEK_CUR); rlm@1: rlm@1: bool yflip=true; rlm@1: std::vector data(width*height*3); rlm@1: for(unsigned y=height; y-->0; ) rlm@1: fread(&data[y*width*3], 1, width*3, fp); rlm@1: fclose(fp); rlm@1: rlm@1: std::vector result(width*height); rlm@1: for(unsigned pos=0, max=result.size(); pos 0) rlm@1: { rlm@1: unsigned bytes = n*chans*(bits/8); rlm@1: unsigned char* buf = (unsigned char*)malloc(bytes); rlm@1: if(buf) rlm@1: { rlm@1: memset(buf,0,bytes); rlm@1: AVI.Audio(rate,bits,chans, buf, n); rlm@1: free(buf); rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: AVI.Audio(rate,bits,chans, (const unsigned char*) data, nsamples); rlm@1: } rlm@1: } /* extern "C" */ rlm@1: rlm@1: rlm@1: rlm@1: static void FlushWrite(FILE* fp, const unsigned char*buf, unsigned length) rlm@1: { rlm@1: /// unsigned failures = 0; rlm@1: /// 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 rlm@1: while(length > 0 /*&& failures < FAILURE_THRESH*/) rlm@1: { rlm@1: unsigned written = fwrite(buf, 1, length, fp); rlm@1: /// if(written == 0) rlm@1: /// failures++; rlm@1: /// else rlm@1: /// { rlm@1: length -= written; rlm@1: buf += written; rlm@1: /// failures = 0; rlm@1: /// } rlm@1: } rlm@1: /// if(failures >= FAILURE_THRESH) rlm@1: /// { rlm@1: /// fprintf(stderr, "FlushWrite() failed to write %d bytes %d times - giving up.", length, failures); rlm@1: /// LoggingEnabled = 0; rlm@1: /// } rlm@1: } rlm@1: rlm@1: // for the UB tech rlm@1: #undef BGR24 rlm@1: #undef BGR16 rlm@1: #undef BGR15 rlm@1: rlm@1: #undef u32 rlm@1: #undef u16 rlm@1: #undef s4