class ZMBV_MKV: public AVI { static const unsigned KeyInt = 30 * 10; //3600;//60; int pid, pip[2]; mk_Writer* mkw; mk_Track *mkt_v; mk_TrackConfig mkc_v; bool cfg_v; char mkc_v_buf[40]; mk_Track *mkt_a; mk_TrackConfig mkc_a; bool cfg_a; ZMBV_Codec zmbv; zmbv_format_t format; std::vector vid_buf, pal_frame; std::list< std::pair > > pending_video; std::list< std::vector > pending_audio; unsigned char previous_palette[256*4]; unsigned palette_age[256]; int auto_palette_active; typedef std::map, FSBAllocator > palette_search_t; palette_search_t palette_search; KDTree palette_tree; double palette_gamma[256][3]; unsigned luma_table[256]; enum { CandCount = 16 }; struct ncand { unsigned char index[CandCount]; }; std::map, FSBAllocator > cache; unsigned long long AudioPos; unsigned long long VideoPos; timeval started_time; public: #define e(call) do { \ int r = call; \ if(r) fprintf(stderr, "%s failed: %d\n", #call, r); } while(0) ZMBV_MKV() : mkw(0), mkt_v(0), mkc_v(), cfg_v(false), mkt_a(0), mkc_a(), cfg_a(false) { // hudmaker -> pip2 -> bufferprog -> pipe -> mkv&zmbv int pip2[2]; pipe(pip2); pid = fork(); if(pid) { close(pip2[0]); pip[1] = pip2[1]; return; } close(pip2[1]); pipe(pip); if(!fork()) { // Rename pip2[0] into 0, pip[1] into 1. close(pip[0]); if(pip[1] == 0) pip[1] = dup(pip[1]); // Ensure stdout is not 0 if(pip2[0] != 0) { dup2(pip2[0], 0); close(pip2[0]); } if(pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } // Close everything except 0,1,2. for(int q=getdtablesize(),p=3; p& v = pending_audio.front(); PutAudioFrame(&v[0], v.size(), v.size() / (bytes/nsamples), aud_rate); pending_audio.pop_front(); } PutAudioFrame(d, bytes, nsamples, aud_rate); } else { pending_audio.push_back( std::vector (d, d+bytes) ); } #else if(!cfg_a && cfg_v) WriteHeader(); cfg_a = true; #endif } virtual void VideoPtr (unsigned w,unsigned h,unsigned f, const unsigned char*d) { if(pid) { unsigned bytes = w * h * ((INPUT_BPP+7)/8); char v = 'V'; FullyWrite(pip[1], &v, 1); FullyWrite(pip[1], &w, sizeof(w)); FullyWrite(pip[1], &h, sizeof(h)); FullyWrite(pip[1], &f, sizeof(f)); FullyWrite(pip[1], &INPUT_BPP, sizeof(INPUT_BPP)); FullyWrite(pip[1], d, bytes); std::free( (void*) d); return; } if(!cfg_v) { mkc_v.extra.video.pixelWidth = w; mkc_v.extra.video.pixelHeight = h; mkc_v.extra.video.displayWidth = w; mkc_v.extra.video.displayHeight = h; mkc_v.extra.video.aspectRatioType = MK_ASPECTRATIO_FIXED; cfg_v = true; mkt_v = mk_createTrack(mkw, &mkc_v); format = zmbv.BPPFormat(INPUT_BPP); vid_buf.resize( zmbv.NeededSize(w,h, ZMBV_FORMAT_32BPP) ); e(!zmbv.SetupCompress(w,h)); fprintf(stderr, "Begins %u x %u video, bpp %u, zmbv format %u\n", w,h, (unsigned)INPUT_BPP,(unsigned)format); if(cfg_a) WriteHeader(); } std::pair frameinfo = CompressZMBV(d, w,h, f); bool make_keyframe = frameinfo.first; size_t gotsize = frameinfo.second; typedef std::pair > pending_frame_t; if(cfg_a && cfg_v) { while(!pending_video.empty()) { pending_frame_t& v = pending_video.front(); PutVideoFrame( &v.second[0], v.second.size(), f,v.first); pending_video.pop_front(); } PutVideoFrame(&vid_buf[0], gotsize, f, make_keyframe); } else { pending_video.push_back( pending_frame_t( make_keyframe, std::vector (&vid_buf[0], &vid_buf[0]+gotsize) ) ); } } private: void WaitEvents() { memset(&previous_palette, 0, sizeof(previous_palette)); memset(&palette_age, 0, sizeof(palette_age)); /* */ memset(&mkc_v, 0, sizeof(mkc_v)); mkc_v.trackType = MK_TRACK_VIDEO; mkc_v.flagLacing = MK_LACING_NONE; mkc_v.flagEnabled = 1; mkc_v.flagDefault = 1; mkc_v.defaultDuration = (6551711.0 / 39375000.0) * 1e9; mkc_v.name = 0; mkc_v.codecID = "V_MS/VFW/FOURCC"; mkc_v.codecName = "ZMBV"; memcpy(mkc_v_buf+16, CODEC_4CC, 4); // MPlayer looks for fourcc in this offset mkc_v.codecPrivate = mkc_v_buf; mkc_v.codecPrivateSize = 40; // MPlayer requires at least this much /* */ memset(&mkc_a, 0, sizeof(mkc_a)); mkc_a.trackType = MK_TRACK_AUDIO; mkc_a.flagLacing = MK_LACING_NONE; mkc_a.flagEnabled = 1; mkc_a.flagDefault = 1; mkc_a.defaultDuration = 0;//(1/60.0) * 1e9; mkc_a.name = 0; mkc_a.codecID = MK_ACODEC_PCMINTLE; AudioPos = 0; VideoPos = 0; gettimeofday(&started_time, NULL); auto_palette_active = 2; const char* const v = VIDEO_CMD.c_str() + 4; mkw = mk_createWriter(v, 1000000, 1); fprintf(stderr, "pid %d, mkv writer initialized: %p [%s]\n", (int) getpid(), mkw, v); while(!Terminate) { char c = 'Q'; FullyRead(pip[0], &c, 1); switch(c) { case 'A': { unsigned aud_rate, aud_bits, nsamples, aud_chans; FullyRead(pip[0], &aud_rate, sizeof(aud_rate)); FullyRead(pip[0], &aud_bits, sizeof(aud_bits)); FullyRead(pip[0], &nsamples, sizeof(nsamples)); FullyRead(pip[0], &aud_chans, sizeof(aud_chans)); unsigned bytes = nsamples * aud_chans * (aud_bits / 8); std::vector d( bytes ); FullyRead(pip[0], &d[0], bytes); AudioPtr(aud_rate,aud_bits,aud_chans, &d[0], nsamples); break; } case 'V': { unsigned w,h,f; FullyRead(pip[0], &w, sizeof(w)); FullyRead(pip[0], &h, sizeof(h)); FullyRead(pip[0], &f, sizeof(f)); FullyRead(pip[0], &INPUT_BPP, sizeof(INPUT_BPP)); unsigned bytes = w * h * ((INPUT_BPP+7)/8); std::vector d( bytes ); FullyRead(pip[0], &d[0], bytes); VideoPtr(w,h,f, &d[0]); break; } case 'Q': { Terminate = true; break; } default: fprintf(stderr, "Unknown data in pipe: %c (%02X)\n", c, (unsigned char) c); } } fprintf(stderr, "Closing MKV file\n"); mk_close(mkw); close(pip[0]); _exit(0); } std::pair CompressZMBV(const unsigned char* d, unsigned w,unsigned h, unsigned fps_scaled) { #define dith(input, inmax, outmax) \ ( (input) * qmax * outmax + (inmax+1)*q ) / (inmax*qmax) enum { qmax = 32*32 }; static int q32x32[ 32*32 ], q4x4[4*4]; static bool first = true; if(first) { first = false; for(unsigned y=0; y<32; ++y) for(unsigned x=0; x<32; ++x) { unsigned M=5, L=5, v=0, offset=0, xmask=M, ymask=L; unsigned xc = x, yc = y ^ (x<>M); for(unsigned bit=0; bit < M+L; ) { v |= ((xc >> --xmask)&1) << bit++; for(offset += L; offset >= M; offset -= M) v |= ((yc >> --ymask)&1) << bit++; } fprintf(stderr, "%5d", v); q32x32[y*32+x] = v; if(x==31) fprintf(stderr,"\n"); } for(unsigned y=0; y<4; ++y) for(unsigned x=0; x<4; ++x) { unsigned M=2, L=2, v=0, offset=0, xmask=M, ymask=L; unsigned xc = x, yc = y ^ (x<>M); for(unsigned bit=0; bit < M+L; ) { v |= ((xc >> --xmask)&1) << bit++; for(offset += L; offset >= M; offset -= M) v |= ((yc >> --ymask)&1) << bit++; } fprintf(stderr, "%5d", v); q4x4[y*4+x] = v; if(x==3) fprintf(stderr,"\n"); } } double time = VideoPos * 16777216. / fps_scaled; bool make_keyframe = (VideoPos % KeyInt) == 0 || VideoPos == 60 || VideoPos == 71; unsigned actual_bpp = INPUT_BPP; unsigned num_pixels = w * h; if(INPUT_BPP == 16 || INPUT_BPP == 32 || INPUT_BPP == 24) { bool paletted_okay = true; // Figure out which colors are repsent in the palette. typedef std::set, FSBAllocator > palette_contents_t; palette_contents_t this_frame_used_colors; size_t num_colors = 0; #define PIX16at(a) (((const unsigned short*)d)[a]) #define PIX32at(a) (((const unsigned *)d)[a]) #define PIX24at(a) (0xFFFFFF & *(const unsigned *)&d[a*3]) if(INPUT_BPP == 16) { for(unsigned a=0; a 256) // Too many distinct colors? { paletted_okay = false; break; } } } else if(INPUT_BPP == 32) { for(unsigned a=0; a 256) // Too many distinct colors? { paletted_okay = false; break; } } } else if(INPUT_BPP == 24) { for(unsigned a=0; a 256) // Too many distinct colors? { paletted_okay = false; break; } } } //goto Now24bpp; if(!paletted_okay) { // Convert to 24bpp format, and go to generic-palettizer static std::vector frame; frame.resize(w*h*3); if(INPUT_BPP == 16) { Convert16To24Frame(d, &frame[0], w*h, false); d = &frame[0]; } else if(INPUT_BPP == 32) { Convert32To24Frame(d, &frame[0], w*h); d = &frame[0]; } // if(auto_palette_active == 2) { auto_palette_active = 0; format = ZMBV_FORMAT_1BPP; // ^Dummy value, force palette reset fprintf(stderr, "ZMBV: Going for dithering...\n"); } goto Now24bpp; } if(auto_palette_active == 0) { fprintf(stderr, "ZMBV: No longer dithering...\n"); auto_palette_active = 1; } if(format == ZMBV_FORMAT_8BPP && !paletted_okay) { make_keyframe = true; // Must change format, palette is no longer usable fprintf(stderr, "ZMBV: Reverting to 16bpp format (from format %u)\n", (unsigned)format); format = ZMBV_FORMAT_16BPP; } if(paletted_okay) { if(format != ZMBV_FORMAT_8BPP) { make_keyframe = true; // Must change format fprintf(stderr, "ZMBV:Changing to 8bpp format (from format %u)\n", (unsigned)format); format = ZMBV_FORMAT_8BPP; } // Figure out what kind of palette we can use // First, mark those palette slots that we can reuse; don't change them. bool dontchange[256] = { false }; unsigned n_kept = 0, n_missing = 0; for(palette_contents_t::iterator i = this_frame_used_colors.begin(); i != this_frame_used_colors.end(); ) { palette_search_t::iterator j = palette_search.find( *i ); if(j != palette_search.end()) { dontchange[j->second] = true; palette_contents_t::iterator i2 = i++; this_frame_used_colors.erase(i2); ++n_kept; } else { ++n_missing; ++i; } } if(n_missing) { unsigned n_added = 0, n_replaced = 0; bool unused[256]; for(unsigned n=0; n<256; ++n) unused[n] = true; // Figure out which palette entries are unused. for(palette_search_t::iterator i = palette_search.begin(); i != palette_search.end(); ++i) { unused[i->second] = false; } // Now add missing colors for(palette_contents_t::iterator i = this_frame_used_colors.begin(); i != this_frame_used_colors.end(); ++i) { // Find an unused slot in the palette. unsigned color = 0; while(!unused[color] && color < 256) ++color; if(color < 256) { ++n_added; } else { // None available? Replace one of don't-need colors. color = 0; while(dontchange[color] && color < 256) ++color; if(color < 256) { // Find the slot that was used longest time ago unsigned age = palette_age[color]; for(unsigned c=color+1; c<256; ++c) if(!dontchange[c] && palette_age[c] > age) { age = palette_age[c]; color = c; } ++n_replaced; } } if(color < 256) { // Remove the palette entrycorresponding to that slot for(palette_search_t::iterator j = palette_search.begin(); j != palette_search.end(); ++j) { if(j->second == color) { palette_search.erase(j); break; } } unsigned rgb565 = *i; palette_search.insert( std::make_pair(rgb565, color) ); if(INPUT_BPP == 16) Unbuild16(&previous_palette[color*4], rgb565); else { previous_palette[color*4+0] = (rgb565 >> 16) & 0xFF; previous_palette[color*4+1] = (rgb565 >> 8) & 0xFF; previous_palette[color*4+2] = (rgb565 >> 0) & 0xFF; } fprintf(stderr, "ZMBV palette slot %u set to: %02X,%02X,%02X\n", color, previous_palette[color*4+0], previous_palette[color*4+1], previous_palette[color*4+2]); unused[color] = false; dontchange[color] = true; } } fprintf(stderr, "ZMBV palette replaced: %u kept, %u was missing, %u added, %u replaced, %zu colors in palette\n", n_kept, n_missing, n_added, n_replaced, palette_search.size()); auto_palette_active = 2; } pal_frame.resize(num_pixels); if(INPUT_BPP == 16) for(unsigned a=0; asecond; pal_frame[a] = c; palette_age[c] = 0; } else if(INPUT_BPP == 32) for(unsigned a=0; asecond; pal_frame[a] = c; palette_age[c] = 0; } else if(INPUT_BPP == 24) for(unsigned a=0; asecond; pal_frame[a] = c; palette_age[c] = 0; } for(unsigned a=0; a<256; ++a) palette_age[a] += 1; d = &pal_frame[0]; actual_bpp = 8; } } else if(INPUT_BPP == 24/* || INPUT_BPP == 32*/) { if(time < 0.5 || (time >= 2.0 && time < 3.0) /* || (time >= 497+0.1 && time <= 497+2.5-0.1) || (time >= 506+0.1 && time <= 506+2.5-0.1) || (time >= 531+0.1 && time <= 531+2.5-0.1) || (time >= 588+0.1 && time <= 588+2.5-0.1) || (time >= 644+0.1 && time <= 644+2.5-0.1) || (time >= 691+0.1 && time <= 691+2.5-0.1) || (time >= 742+0.1 && time <= 742+2.5-0.1) || (time >= 804+0.1 && time <= 804+2.5-0.1) */ ) { if(INPUT_BPP != 32) { // Change into 32bpp pal_frame.clear(); pal_frame.resize(num_pixels * 4); for(unsigned a=0, y=0; y= 1.8 && time <= 7.4) { // Change into 16bpp pal_frame.resize(num_pixels * 2); unsigned short* p = (unsigned short*) &pal_frame[0]; for(unsigned a=0, y=0; y> 16) | (pal & 0x00FF00) | ((pal << 16) & 0xFF0000); if(palette_search.find(pal) != palette_search.end()) { fprintf(stderr, "Duplicate %06X ignored\n", constpal[n]); continue; } palette_search[pal] = c; ((unsigned *)previous_palette)[c] = pal; ++c; } /* static const int sectiontable[10] = {0200,0210,0220,0020,0022,0012,0002,0102,0202,0201}; for(int sat=0; sat<5; ++sat) for(int section=0; section<10; ++section) for(int bright=0; bright<5; ++bright) { if(c >= 256) break; double gray = (sat<2 ? (2+bright) / 6.0 : (1+bright) / 5.0); double cr = gray * ((sectiontable[section]>>6)&3)/2; double cg = gray * ((sectiontable[section]>>3)&3)/2; double cb = gray * ((sectiontable[section]>>0)&3)/2; double luma = sat==3 ? gray : (cr*0.299 + cg*0.587 + cb*0.114); double satfactor = sat==3?0.2:pow((sat+1) / 5.0, 1.5); double r = luma + (cr-luma) * satfactor; double g = luma + (cg-luma) * satfactor; double b = luma + (cb-luma) * satfactor; int rr = r*255.9, gg = g*255.9, bb = b*255.9; previous_palette[c*4+0] = rr; previous_palette[c*4+1] = gg; previous_palette[c*4+2] = bb; fprintf(stderr, "ZMBV color %u: %02X,%02X,%02X\n", c,rr,gg,bb); ++c; }*/ #else /* Use regular palette */ for(unsigned r=0; r<5; ++r) for(unsigned g=0; g<7; ++g) for(unsigned b=0; b<7; ++b, ++c) { unsigned rr = r==4 ? 0x7F : r*0x55; unsigned gg = g*0x55/2; unsigned bb = b*0x55/2; previous_palette[c*4+0] = rr; // 00 55 AA FF 7F previous_palette[c*4+1] = gg; // 00 2A 55 7F AA D4 FF previous_palette[c*4+2] = bb; // 00 2A 55 7F AA D4 FF fprintf(stderr, "ZMBV color %u: %02X,%02X,%02X\n", c,rr,gg,bb); } for(unsigned y=1; y<=10; ++y, ++c) // 245..254 { unsigned yy = y*0xFF/11.0 + 0.5; previous_palette[c*4+0] = yy; previous_palette[c*4+1] = yy; previous_palette[c*4+2] = yy; fprintf(stderr, "ZMBV color %u: %02X,%02X,%02X\n", c, yy,yy,yy); // 17 2E 46 5D 74 8B A2 B9 D1 E8 // also have 00, 55, 7F, AA, FF } while(c < 256) { previous_palette[c*4+0] = 0xFF; previous_palette[c*4+1] = 0x00; previous_palette[c*4+2] = 0xFF; ++c; } // debug // Total: 5*7*7 = 245 #endif palette_tree = KDTree (); for(unsigned i=0; i::iterator i = cache.lower_bound(key); if(i != cache.end() && i->first == key) { unsigned k = i->second.index[ q * CandCount / (32u*32u) ]; pal_frame[a] = k; continue; } ncand result; unsigned char* candlist = result.index; double in[3] = { rr/255.0, gg/255.0, bb/255.0 }; for(unsigned p=0; p<3; ++p) in[p] = pow(in[p], Gamma); /*fprintf(stderr, "Trying for %06X (%6.3f,%6.3f,%6.3f); ", key, in[0],in[1],in[2]);*/ KDTree::KDPoint trying(in); for(unsigned c=0; c= CandCount) break; // Compensate for error for(unsigned p=0; p<3; ++p) { trying.coord[p] += (in[p] - palette_gamma[k][p]) * 1.0; if(trying.coord[p] < 0.0) trying.coord[p] = 0.0; if(trying.coord[p] > 1.0) trying.coord[p] = 1.0; } } // Order candidates by luminosity, using insertion sort. for(unsigned j=1; j=1 && luma_table[candlist[i-1]] > luma_table[k]; --i) candlist[i] = candlist[i-1]; candlist[i] = k; } /*fprintf(stderr, "Gets: "); for(unsigned c=0; c=0x54 && yy<=0x56) pal_frame[a] = 2 + 2*7 + 7*7*1; // 55 else if(yy>=0x7E && yy<=0x80) pal_frame[a] = 3 + 3*7 + 7*7*4; // 7F else if(yy>=0xA9 && yy<=0xAC) pal_frame[a] = 4 + 4*7 + 7*7*2; // AA else if(yv == 11) pal_frame[a] = 6 + 6*7 + 7*7*3; // FF else pal_frame[a] = 245 + (yv-1); } else { unsigned r = dith(rr,255, 6); if(r == 3) r = 4; else r = dith(rr,255, 3); unsigned g = dith(gg,255, 6); unsigned b = dith(bb,255, 6); pal_frame[a] = b + g*7 + r*7*7; } #endif } actual_bpp = 8; d = &pal_frame[0]; } #undef dith } int bw_base = 8, bh_base = 8;/* int bw_base = 20; int bh_base = 15; // 15 % (2400/480) = 0 if(time >= 11.5) { bw_base = 40; bh_base = 24; }*/ e(!zmbv.PrepareCompressFrame( make_keyframe, format, (char*) previous_palette, &vid_buf[0], vid_buf.size(), bw_base, bh_base )); unsigned stride = w * ( (actual_bpp+7) / 8 ); for(unsigned y=0; y (make_keyframe, gotsize); } void WriteHeader() { e(mk_writeHeader(mkw, "hudmaker")); } void PutAudioFrame(const unsigned char* d, unsigned bytes, unsigned samples, unsigned rate) { unsigned long long time = AudioPos * (1e9l / rate); e(mk_startFrame(mkw, mkt_a)); e(mk_addFrameData(mkw, mkt_a, d, bytes)); e(mk_setFrameFlags(mkw, mkt_a, time, true, 0)); e(mk_flushFrame(mkw, mkt_a)); AudioPos += samples; fprintf(stderr, "AudioPos %g\n", time/1e9); } void PutVideoFrame(const unsigned char* d, unsigned bytes, unsigned fps_scaled, bool is_keyframe) { timeval now; gettimeofday(&now, NULL); unsigned long long time = VideoPos * ((16777216e9l) / fps_scaled); e(mk_startFrame(mkw, mkt_v)); e(mk_addFrameData(mkw, mkt_v, d,bytes)); e(mk_setFrameFlags(mkw, mkt_v, time, is_keyframe, 0)); e(mk_flushFrame(mkw, mkt_v)); double start_time = started_time.tv_sec*1e9 + started_time.tv_usec*1e3; double now_time = now.tv_sec*1e9 + now.tv_usec*1e3; double elapsed = now_time - start_time; VideoPos += 1; fprintf(stderr, "VideoPos %g; Average speed: %g s realtime per 1s of video\n", time/1e9, elapsed / time); } #undef e };