Wednesday, May 14, 2014

JNI for x264 encoder

I decided to implement JNI for x264 encoder as one of most popular H264 codec implementations. My short guideline covers x264, JNI related compilation steps and sample Java class.

So let start.

The first step that you need to perform is to compile proper library from x264 C code. For this activity I used following Alex's guide. I used several steps from this manual. One difference with my way was 64bit mingw installed on Ubuntu 14 64bit LTS, installed newest JDK 1.8. The JNI code compilation requires C headers copied from the Windows JDK located under C:\Program Files\Java\jdk1.8.0\include\win32. Copy this directory into your Ubuntu JDK folder: /usr/lib/jvm/java-8-oracle/include. Finally I configured x264:

./configure --cross-prefix=x86_64-w64-mingw32- --host=x86_64-pc-mingw32 --disable-avs --enable-shared --disable-cli --enable-win32thread

make

After a time you have to see the libx264-142.dll.

Next step I compile the JNI related code:

x86_64-w64-mingw32-gcc -Wl,--add-stdcall-alias -shared -m64 -O3 -ffast-math -I/home/vadym/win32-cross/src/x264 -I/home/vadym/win32-cross/include -I $JAVA_HOME/include -I $JAVA_HOME/include/win32 -mfpmath=sse -msse -std=gnu99 -fomit-frame-pointer -fno-tree-vectorize -fno-zero-initialized-in-bss -o h264-jni.dll h264-jni.c -L ~/win32-cross/src/x264 -l x264.dll

This command gets h264-jni.dll

These two dll's will be required for your Java encoder:

H264Encoder.java


package x264;

public class H264Encoder {

    static {
            System.loadLibrary("libx264-142");
            System.loadLibrary("h264-jni");
        }

    private long encoder_pointer;

    public H264Encoder(int width, int height) {
        this.encoder_pointer = openEncoder(width, height);
    }
        
    public long openEncoder(int width, int height){
        encoder_pointer = this.open(width, height);
        return encoder_pointer;
    }

    public int encode(int type, byte[] in, int insize, byte[] out, boolean[] keyframe){
        return this.encode(encoder_pointer, type, in, insize, out, keyframe);
    }
    
    public int finish(){
        return this.finish(encoder_pointer);
    }
    
    private final native long open(int width, int height);
    
    private final native int encode(long encoder, int type, byte[] in, int insize, byte[] out, boolean[] keyframe);
    
    private final native int finish(long encoder);

    public static void main(String[] args){
        H264Encoder encoder = new H264Encoder(300, 200);
        encoder.finish();    
    }
}


h264-jni.c


#include <stdint.h>
#include <string.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <x264.h>

#define DATA_MAX 3000000
#define H264_MTU 1024

typedef struct

{
    x264_param_t * param;
    x264_t *handle;
    x264_picture_t * picture;
    x264_nal_t  *nal;
} Encoder;

JNIEXPORT jlong JNICALL Java_x264_H264Encoder_open(JNIEnv* env, jobject thiz, jint width, jint height){

    Encoder * en = (Encoder *) malloc(sizeof(Encoder));
    en->param = (x264_param_t *) malloc(sizeof(x264_param_t));
    en->picture = (x264_picture_t *) malloc(sizeof(x264_picture_t));
    x264_param_default(en->param); //set default param

 //ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow,placebo
 //film,animation,grain,stillimage,psnr,ssim,fastdecode,zerolatency,touhou
    x264_param_default_preset(en->param, "fast", "animation");
    //en->param->rc.i_rc_method = X264_RC_CQP;
    //en->param->i_log_level = X264_LOG_NONE;
    en->param->i_width = width; //set frame width
    en->param->i_height = height; //set frame height
    //en->param->rc.i_lookahead = 0;
    en->param->i_bframe = 1;
    //en->param->i_fps_num = 5;
    //en->param->i_fps_den = 1;
    //baseline,main,high,high10,high422,high444
    x264_param_apply_profile(en->param, "main");
    if ((en->handle = x264_encoder_open(en->param)) == 0) {
        return 0;
    }
    /* Create a new pic */
    x264_picture_alloc(en->picture, X264_CSP_I420, en->param->i_width, en->param->i_height);
    return (jlong) en;
}

JNIEXPORT jint JNICALL Java_x264_H264Encoder_finish(JNIEnv* env, jobject thiz, jlong handle){
    Encoder * en = (Encoder *) handle;
    if(en->picture)
    {
        x264_picture_clean(en->picture);
        free(en->picture);
        en->picture = 0;
    }
    if(en->param)
    {
        free(en->param);
        en->param = 0;
    }

    if(en->handle)
    {
        x264_encoder_close(en->handle);
    }
    free(en);
    return 0;
}

JNIEXPORT jint JNICALL Java_x264_H264Encoder_encode(JNIEnv* env, jobject thiz, jlong handle, jint type, jbyteArray in, jint insize, jbyteArray out, jbooleanArray keyframe)

{
    Encoder * en = (Encoder *) handle;
    jboolean key_frame[1];

    x264_picture_t pic_out;
    int i_data=0;
    int nNal =-1;
    int result=0;
    int i=0;
    unsigned char in_buf[insize];
    unsigned char * buf = in_buf;
    (*env)->GetByteArrayRegion (env, in, 0, insize, buf);
    jbyte h264Buf[insize];
    jbyte * pTmpOut = h264Buf;
    int nPicSize=en->param->i_width*en->param->i_height;
    int size = nPicSize*sizeof(unsigned char)/4;
    memcpy(en->picture->img.plane[0], in_buf, 4*size);
    memcpy(en->picture->img.plane[1],&buf[nPicSize],  size);
    memcpy(en->picture->img.plane[2],&buf[nPicSize+size],size);
  /*#define X264_TYPE_AUTO          0x0000
    #define X264_TYPE_IDR           0x0001
    #define X264_TYPE_I             0x0002
    #define X264_TYPE_P             0x0003
    #define X264_TYPE_BREF          0x0004
    #define X264_TYPE_B             0x0005
    #define X264_TYPE_KEYFRAME      0x0006*/
    en->picture->i_type = type;

    if(x264_encoder_encode(en->handle, &(en->nal), &nNal, en->picture, &pic_out) < 0)
    {
        return -1;
    }
    for (i = 0; i < nNal; i++){
        memcpy(pTmpOut, en->nal[i].p_payload, en->nal[i].i_payload);
        pTmpOut += en->nal[i].i_payload;
        result += en->nal[i].i_payload;
    }
    
    key_frame[0] = (jboolean) pic_out.b_keyframe;
    (*env)->SetBooleanArrayRegion(env, keyframe, 0, 1, key_frame);
    (*env)->SetByteArrayRegion(env, out, 0, result, h264Buf);
    return result;

}