Pipe Zlib example

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <zlib.h>

int main () {
	setmode(0, _O_BINARY);
	setmode(1, _O_BINARY);

	uLongf compressedBytes   = 0;
	uLongf unCompressedBytes = 0;

	// read data size
	fread(&compressedBytes  , sizeof(uLongf), 1, stdin);
	fread(&unCompressedBytes, sizeof(uLongf), 1, stdin);

	// allocate data container
	Bytef *compressedData   = (Bytef*) malloc(compressedBytes);
	Bytef *unCompressedData = (Bytef*) malloc(unCompressedBytes);

	// read compressed data
	fread(compressedData, compressedBytes, 1, stdin);

	// decompress
	uncompress(unCompressedData, &unCompressedBytes, compressedData, compressedBytes);

	// write un-compressed data
	fwrite(unCompressedData, unCompressedBytes, 1, stdout);
	fflush(stdout);

	free(compressedData);
	free(unCompressedData);
	return 0;
}

 

Fast deep clone in javascript

function recursiveDeepClone(o) {
    var newO,  i;

    if (typeof o !== 'object') { return o; }
    if (!o) {  return o;  }
    
    if ( Buffer.isBuffer(o) ) {
        let o2 = Buffer.alloc(o.length);
        o.copy(o2);
        return o2;
    }
    
    if ( o.constructor === Int8Array ) {
        newO = new Int8Array( o );
        return newO;
    }
    
    if ( o.constructor === Uint8Array ) {
        newO = new Uint8Array( o );
        return newO;
    }
    
    if ( o.constructor === Uint8ClampedArray ) {
        newO = new Uint8ClampedArray( o );
        return newO;
    }
    
    if ( o.constructor === Int16Array ) {
        newO = new Int16Array( o );
        return newO;
    }
    
    if ( o.constructor === Uint16Array ) {
        newO = new Uint16Array( o );
        return newO;
    }
    
    if ( o.constructor === Int32Array ) {
        newO = new Int32Array( o );
        return newO;
    }
    
    if ( o.constructor === Uint32Array ) {
        newO = new Uint32Array( o );
        return newO;
    }
    
    if ( o.constructor === Float32Array ) {
        newO = new Float32Array( o );
        return newO;
    }
    
    if ( o.constructor === Float64Array ) {
        newO = new Float64Array( o );
        return newO;
    }

    if ( o.constructor === Array ) {
        newO = [];
        for (i = 0; i < o.length; i += 1) {
            newO[i] = recursiveDeepClone(o[i]);
        }
        return newO;
    }
    
    newO = {};
    for (i in o) {
        newO[i] = recursiveDeepClone(o[i]);
    }
    return newO;
}

Reference

ffmpeg pipe example

這邊從 stdout stderr 流輸入給 ffmpeg 做壓縮,主要就是利用 pipe IPC

寫個小程式順便比較 javascript (nodejs) 和 C 語言的速度差異,最佳化的結果是 javascript 輸出到 stderr 幾乎等速於 C 語言,令人有點意外XD;反而直出到 stdout 的速度比 C 語言慢了 30%,這不太科學 。

題外話是 stderr 通常不會有 buffer 的問題,大多數時候都不用擔心沒有 flush 乾淨

Github code

 

閱讀全文〈ffmpeg pipe example〉

Difference between CRF and QP in x264

壓縮時該選哪個? 看目的是要做什麼,基本上一句話: 開發用 QP ,觀賞用CRF

  • CRF: 根據場景移動的速度動態選擇 QP (畫質) ,傾向在快速變化場景提高壓縮率 ( B frames ),目的是只要維持視覺上的 QP 就可以,快速變化的地方可以模糊一點沒關係。(實驗中在同樣的 QP 設定下,CRF在 I 和 P frame 的 QP 值反而比較高!@@ )
  • QP: 均化所有 frame 的QP,不論場景內容

CRF 或 QP=0 都是 loseless 這邊就不討論了,也沒有什麼實用價值。基本上在相同的設定值下,CRF 的檔案大小會比較小,但這是在於 CRF > 25 (平均畫質) 以上才比較有價值,否則差異不大。

就 MEMC (Frame Rate Conversion) 演算法開發我還是習慣用QP=18,因為同樣用 CRF=18,可能在快速移動物體邊緣出現些許 blocky ,但這是 CRF 壓縮造成的,不是演算法瑕疵,尤其我們在斤斤計較的就是這些快速移動的區域。

using libpng write file

#include <stdio.h>
#include <stdlib.h>
#include <png.h>

int imgSizeX = 1920;
int imgSizeY = 1080;
int imgNum   = 100;

void writePNG(FILE *file, const char *img) {
    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_set_compression_level(png_ptr, 9);
    png_infop  info_ptr = png_create_info_struct( png_ptr );
    png_init_io(png_ptr, file);
    
    setjmp( png_jmpbuf(png_ptr) );
    png_set_IHDR(png_ptr, info_ptr, imgSizeX, imgSizeY,
                 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
    png_write_info(png_ptr, info_ptr);
    
    setjmp( png_jmpbuf(png_ptr) );
    png_bytep *row_pointers = new png_bytep[imgSizeY];
    for (int y=0; y < imgSizeY; y++) {
        row_pointers[y] = (png_byte*) &img[y*imgSizeX*3];
    }
    png_write_image(png_ptr, row_pointers);
    
    setjmp( png_jmpbuf(png_ptr) );
    png_write_end(png_ptr, NULL);
    
    delete [] row_pointers;
}

libpng mingw-w64 build static library

C/C++ stdout without CRLF

#include <fcntl.h>
#include <io.h>

setmode(1, _O_BINARY);

 

example use in ffmpeg pipe encoding mode

a.exe | ffmpeg -f rawvideo -pixel_format rgb24 -video_size 1920x1080 -i - -pix_fmt yuv420p output.mp4
#include <stdio.h>

// includes for setmode
#include <fcntl.h>
#include <io.h>

int imgSizeX = 1920;
int imgSizeY = 1080;
int imgNum   = 100;

int main() {
    // change to binary stdout mode (avoid 0x0A be changed into 0x0D 0x0A)
    setmode(1, _O_BINARY);
    
    for (int i = 0; i < imgNum; ++i) {
        for (int y = 0; y < imgSizeY; ++y) {
            for (int x = 0; x < imgSizeX; ++x) {
                unsigned char color[3] = {0x00, (x+i*5) & 0xFF, 0x00};
                fwrite(color, 1, 3, stdout);
            }
        }
    }
    
    fflush(stdout);
    
    return 0;
}

 

GitHub Codes

x264 c++ encode example

有了 libx264 當然要試編一下影片,encode 出的檔案只是為 h264 的 bitstream ,並不是直接封裝成可直接撥放的檔案,還需要一些 wrapper 程序封裝成常見的影片格式如 ( mp4, mkv …)

不過也可以懶人用 ffmpeg 封裝 XD

ffmpeg -i in_file.h264 -vcodec copy out_file.mp4

 

這基本上就是一連串的繼承寄生關係,階層越高越笨重,也越複雜,這邊就第一步 codec 層來做個小實驗。

  1. codec library: e.g. x264
    • only encoder & decoder for data stream  (h264)
  2. video file container: e.g. libav
    • pack/unpack video file (mp4, avi, mkv)
  3. multi container & codec: e.g. ffmpeg
    • All file formats & all codecs (avi:h264, avi:h265, mkv:h264…)

 

Reference: h264/h265 bit stream 分析工具,可以幫助理解檔案格式

後面附上程式碼

閱讀全文〈x264 c++ encode example〉

Compile x264 library with mingw-w64

完全是參考,注意 YASM 可能要另外下載丟到 mingw/bin/

  1. Download the x264 source code
  2. run ./configure
  3. run make

但是如果這麼簡單就不用寫一篇文章了

MSYS 死活找不到 Mingw-w64,一直顯示找不到 gcc

直到我下載了 MSYS2 ,對,就是多個 2 而已。然後乖乖把mingw32和64放到 C:\msys64底下,執行對應的 msys 環境 (如 mingw64.exe ),就可以正常編譯了!

附上編譯好的懶人包 for mingw-w64

Javascript file path format extraction

輸入一組路徑,轉換成 sprintf  format

如把

'D:\\\\workspace\\path-parser\\img\\img_0005.txt'

轉換為

D:\\\\workspace\\path-parser\\img\\img_%04d.txt

基本上就是個掃目錄的蠢方法,反正成功了…演算法如下,後面附上 Javascript code

  1. 把輸入路徑轉換成 regex (這邊假定是索引最後一組數字字串)
  2. 在對應目錄下用 regex 找符合的檔名,紀錄最長和最短檔名長度
  3. 如果長度相同,就是有 zero padding ( e.g. %04d ),反之沒有
  4. 根據有沒有 zero padding 決定 format 要怎寫

閱讀全文〈Javascript file path format extraction〉

zlib node.js example

const zlib = require('zlib');
const fs   = require('fs');

// generate data
let buf = Buffer.alloc(4 * 240 * 135);
for (let y = 0, i = 0; y < 135; ++y) {
	for (let x = 0; x < 240; ++x, ++i) {
		buf.writeFloatBE(x*y, i*4);
	}
}

// compress
let bufCompress = zlib.deflateSync( buf );

console.log( bufCompress );

// write file
let file = fs.openSync('data.z', 'w+');
fs.writeSync( file, 'test\nabc\n' );                        // write some header
fs.writeSync( file, bufCompress, 0, bufCompress.length );   // write compressed data
fs.closeSync( file );

// read file
let readBuf = fs.readFileSync('data.z');
let readArr = readBuf.toString().split('\n');
console.log( readArr[0] );
console.log( readArr[1] );
let offset = readArr[0].length + readArr[1].length + 2;
bufCompress = readBuf.slice(offset);

console.log( bufCompress );

// decompress
let bufUncompress = zlib.inflateSync( bufCompress );

// check data
for (let i = 0; i < 240*135*4; ++i) {
	if ( buf[i] != bufUncompress[i] ) {
		console.log( 'error' );
	}
}

console.log( buf.length );
console.log( bufCompress.length );
console.log( bufUncompress.length );