#include #include #include #include #include #include #include // mknod, unlink, write #include // S_IFIFO #include // fcntl #include // poll #include /* Note: This module assumes everyone uses BGR16 as display depth */ #define LOGO_LENGTH_HEADER (-0.7) #define LOGO_LENGTH_OVERLAP (6.0-LOGO_LENGTH_HEADER) //#define LOGO_LENGTH_HEADER (0) //#define LOGO_LENGTH_OVERLAP (0) static std::string VIDEO_CMD = ""; /* -rawvideo on:fps=60:format=0x42475220:w=256:h=224:size=$[1024*224] -audiofile "+AUDIO_FN+" */ static std::string AUDIO_FN = "s.log"; static bool Terminate=false; static unsigned NonblockWrite(FILE* fp, const unsigned char*buf, unsigned length) { Retry: int result = write(fileno(fp), buf, length); if(result == -1 && errno==EAGAIN) { return 0; } if(result == -1 && errno==EINTR) goto Retry; if(result == -1) { perror("write"); Terminate=true; return 0; } return result; } static int WaitUntilOneIsWritable(FILE*f1, FILE*f2) { struct pollfd po[2] = { {fileno(f1),POLLOUT,0}, {fileno(f2),POLLOUT,0} }; poll(po, 2, -1); return ((po[0].revents & POLLOUT) ? 1 : 0) | ((po[1].revents & POLLOUT) ? 2 : 0); } #define BGR24 0x42475218 // BGR24 fourcc #define BGR16 0x42475210 // BGR16 fourcc #define BGR15 0x4247520F // BGR15 fourcc #define u32(n) (n)&255,((n)>>8)&255,((n)>>16)&255,((n)>>24)&255 #define u16(n) (n)&255,((n)>>8)&255 #define s4(s) s[0],s[1],s[2],s[3] static const unsigned FPS_SCALE = 0x1000000; static class AVI { FILE* vidfp; FILE* audfp; bool KnowVideo; unsigned vid_width; unsigned vid_height; unsigned vid_fps_scaled; std::list > VideoBuffer; unsigned VidBufSize; bool KnowAudio; unsigned aud_rate; unsigned aud_chans; unsigned aud_bits; std::list > AudioBuffer; unsigned AudBufSize; public: AVI() : vidfp(NULL), audfp(NULL), KnowVideo(false), VidBufSize(0), KnowAudio(false), AudBufSize(0) { char Buf[4096]; getcwd(Buf,sizeof(Buf)); Buf[sizeof(Buf)-1]=0; AUDIO_FN = Buf + std::string("/") + AUDIO_FN; } ~AVI() { while(VidBufSize && AudBufSize) { CheckFlushing(); } if(audfp) fclose(audfp); if(vidfp) pclose(vidfp); unlink(AUDIO_FN.c_str()); } void Audio(unsigned r,unsigned b,unsigned c, const unsigned char*d, unsigned nsamples) { if(Terminate) return; if(!KnowAudio) { aud_rate = r; aud_chans = c; aud_bits = b; KnowAudio = true; } CheckFlushing(); unsigned bytes = nsamples * aud_chans * (aud_bits / 8); unsigned wrote = 0; if(KnowVideo && AudioBuffer.empty()) { wrote = SendAudioFrame(d, bytes); } if(wrote < bytes) { unsigned remain = bytes-wrote; AudioBuffer.push_back(std::vector(d+wrote, d+bytes)); AudBufSize += remain; /* if(AudBufSize != remain) fprintf(stderr, "Buffering %u bytes of audio (%u in buffer)\n", remain, AudBufSize); */ } CheckFlushing(); } void Video(unsigned w,unsigned h,unsigned f, const unsigned char*d) { if(Terminate) return; if(!KnowVideo) { vid_width = w; vid_height = h; vid_fps_scaled = f; KnowVideo = true; } CheckFlushing(); unsigned bytes = vid_width * vid_height * 2; //std::vector tmp(bytes, 'k'); //d = &tmp[0]; unsigned wrote = 0; if(KnowAudio && VideoBuffer.empty()) { wrote = SendVideoFrame(d, bytes); } if(wrote < bytes) { unsigned remain = bytes-wrote; VideoBuffer.push_back(std::vector(d+wrote, d+bytes)); VidBufSize += remain; /* if(VidBufSize != remain) fprintf(stderr, "Buffering %u bytes of video (%u in buffer)\n", remain, VidBufSize); */ } CheckFlushing(); } private: unsigned SendVideoFrame(const unsigned char* vidbuf, unsigned framesize) { CheckBegin(); return NonblockWrite(vidfp, vidbuf, framesize); } unsigned SendAudioFrame(const unsigned char* audbuf, unsigned framesize) { CheckBegin(); return NonblockWrite(audfp, audbuf, framesize); } /* fp is passed as a reference because it may be NULL * prior to calling, and this function changes it. */ template void FlushBufferSome(BufType& List, unsigned& Size, FILE*& fp) { Retry: if(List.empty() || Terminate) return; typename BufType::iterator i = List.begin(); std::vector& buf = *i; if(buf.empty()) { List.erase(i); goto Retry; } unsigned bytes = buf.size(); CheckBegin(); //fprintf(stderr, "Writing %u from %p to %p\n", bytes, (void*)&buf[0], (void*)fp); unsigned ate = NonblockWrite(fp, &buf[0], bytes); //fprintf(stderr, "Wrote %u\n", ate); buf.erase(buf.begin(), buf.begin()+ate); Size -= ate; if(buf.empty()) { List.erase(i); } } void CheckFlushing() { //AudioBuffer.clear(); //VideoBuffer.clear(); if(KnowAudio && KnowVideo && !Terminate) { if(!AudioBuffer.empty() && !VideoBuffer.empty()) { do { /* vidfp = &1, audfp = &2 */ int attempt = WaitUntilOneIsWritable(vidfp, audfp); if(attempt <= 0) break; /* Some kind of error can cause this */ // Flush Video if(attempt&1) FlushBufferSome(VideoBuffer, VidBufSize, vidfp); // Flush Audio if(attempt&2) FlushBufferSome(AudioBuffer, AudBufSize, audfp); } while (!AudioBuffer.empty() && !VideoBuffer.empty()); } else { FlushBufferSome(VideoBuffer, VidBufSize, vidfp); FlushBufferSome(AudioBuffer, AudBufSize, audfp); } } } std::string GetMEncoderRawvideoParam() const { char Buf[512]; sprintf(Buf, "fps=%g:format=0x%04X:w=%u:h=%u:size=%u", vid_fps_scaled / (double)FPS_SCALE, BGR16, vid_width, vid_height, vid_width*vid_height * 2); return Buf; } std::string GetMEncoderRawaudioParam() const { char Buf[512]; sprintf(Buf, "channels=%u:rate=%u:samplesize=%u:bitrate=%u", aud_chans, aud_rate, aud_bits/8, aud_rate*aud_chans*(aud_bits/8) ); return Buf; } std::string GetMEncoderCommand() const { std::string mandatory = "-audiofile " + AUDIO_FN + " -audio-demuxer rawaudio" + " -demuxer rawvideo" + " -rawvideo " + GetMEncoderRawvideoParam() + " -rawaudio " + GetMEncoderRawaudioParam() ; std::string cmd = VIDEO_CMD; unsigned p = cmd.find("NESV""SETTINGS"); if(p != cmd.npos) cmd = cmd.replace(p, 4+8, mandatory); else fprintf(stderr, "Warning: NESVSETTINGS not found in videocmd\n"); fprintf(stderr, "Launch: %s\n", cmd.c_str()); fflush(stderr); return cmd; } void CheckBegin() { if(!vidfp) { /* Note: popen does not accept b/t in mode param */ vidfp = popen(GetMEncoderCommand().c_str(), "w"); if(!vidfp) { perror("Launch failed"); } else { fcntl(fileno(vidfp), F_SETFL, O_WRONLY | O_NONBLOCK); } } if(!audfp) { unlink(AUDIO_FN.c_str()); mknod(AUDIO_FN.c_str(), S_IFIFO|0666, 0); Retry: audfp = fopen(AUDIO_FN.c_str(), "wb"); if(!audfp) { perror(AUDIO_FN.c_str()); if(errno == ESTALE) goto Retry; } else { fcntl(fileno(audfp), F_SETFL, O_WRONLY | O_NONBLOCK); } } } } AVI; extern "C" { int LoggingEnabled = 0; /* 0=no, 1=yes, 2=recording! */ const char* NESVideoGetVideoCmd() { return VIDEO_CMD.c_str(); } void NESVideoSetVideoCmd(const char *cmd) { VIDEO_CMD = cmd; } namespace LogoInfo { unsigned width; unsigned height; } static unsigned Build16(const unsigned char* rgbdata) { unsigned B = rgbdata[0]; unsigned G = rgbdata[1]; unsigned R = rgbdata[2]; return ((B*32/256)<<0) | ((G*64/256)<<5) | ((R*32/256)<<11); } static void Unbuild16(unsigned char* target, unsigned rgb16) { unsigned B = (rgb16%32)*256/32; unsigned G = ((rgb16/32)%64)*256/64; unsigned R = ((rgb16/(32*64))%32)*256/32; target[0] = B; target[1] = G; target[2] = R; } static void Overlay32With32(unsigned char* target, const unsigned char* source, int alpha) { target[0] += ((int)(source[0] - target[0])) * alpha / 255; target[1] += ((int)(source[1] - target[1])) * alpha / 255; target[2] += ((int)(source[2] - target[2])) * alpha / 255; } static void OverlayLogoFrom(const char* fn, std::vector& data) { FILE*fp = fopen(fn, "rb"); if(!fp) return; /* Silently ignore missing frames */ int idlen = fgetc(fp); /* Silently ignore all other header data. * These files are assumed to be uncompressed BGR24 tga files with Y swapped. * Even their geometry is assumed to match perfectly. */ fseek(fp, 16, SEEK_SET); int bpp = fgetc(fp); if(bpp != 24 && bpp != 32) { fprintf(stderr, "'%s': ERROR, bpp=%d\n", fn, bpp); } fseek(fp, 1+1+1+2+2+1+ /*org*/2+2+ /*geo*/2+2+ 1+1+idlen, SEEK_SET); for(unsigned y=LogoInfo::height; --y > 0; ) { unsigned char pixbuf[4] = {0,0,0,0}; for(unsigned x = 0; x < LogoInfo::width; ++x) { int alpha = 255; fread(pixbuf,1,3,fp); if(bpp == 32) alpha = fgetc(fp); Overlay32With32(&data[(y*LogoInfo::width+x)*3], pixbuf, alpha); } } fclose(fp); } static const std::string GetLogoFileName(unsigned frameno) { const char *background = LogoInfo::width==320 ? "logo_320_240" : LogoInfo::width==160 ? "logo_160_144" : LogoInfo::width==240 ? "logo_240_160" : LogoInfo::height>224 ? "logo_256_240" : "logo_256_224"; char Buf[4096]; sprintf(Buf, "/home/bisqwit/povray/nesvlogov5/%s_f%03u.tga", background, frameno); return Buf; } static const std::vector Convert32To16Frame (const std::vector& logodata) { //bool yflip = true; std::vector result(LogoInfo::width * LogoInfo::height); for(unsigned pos=0, max=result.size(); pos 0) { /* Bisqwit's logo addition routine. */ /* Note: This should be 1 second long. */ for(unsigned frame = 0; frame < LogoFramesHeader; ++frame) { std::vector logodata(width*height*3); /* filled with black. */ std::string fn = GetLogoFileName(frame); OverlayLogoFrom(fn.c_str(), logodata); std::vector result = Convert32To16Frame(logodata); AVI.Video(width,height,fps_scaled, (const unsigned char*)&result[0]); } } } static unsigned LogoOverlapSent = 0; if(LogoOverlapSent < LogoFramesOverlap) { std::vector logodata(width*height*3); /* filled with black. */ for(unsigned y=0; y result = Convert32To16Frame(logodata); AVI.Video(width,height,fps_scaled, (const unsigned char*)&result[0]); ++LogoOverlapSent; return; } AVI.Video(width,height,fps_scaled, (const unsigned char*) data); } void NESVideoLoggingAudio (const void*data, unsigned rate, unsigned bits, unsigned chans, unsigned nsamples) { if(LoggingEnabled < 2) return; static bool LogoHeaderPartSent = false; if(!LogoHeaderPartSent && LOGO_LENGTH_HEADER > 0) { LogoHeaderPartSent=true; const unsigned n = (unsigned)(rate * LOGO_LENGTH_HEADER); unsigned bytes = n*chans*(bits/8); unsigned char* buf = (unsigned char*)malloc(bytes); if(buf) { memset(buf,0,bytes); AVI.Audio(rate,bits,chans, buf, n); free(buf); } } /* fprintf(stderr, "Writing %u samples (%u bits, %u chans, %u rate)\n", nsamples, bits, chans, rate);*/ /* static FILE*fp = fopen("audiodump.wav", "wb"); fwrite(data, 1, nsamples*(bits/8)*chans, fp); fflush(fp);*/ AVI.Audio(rate,bits,chans, (const unsigned char*) data, nsamples); } } /* extern "C" */