1. 程式人生 > >android編碼h264(一):使用x264編碼yuv為h264資料的例子(軟編碼)

android編碼h264(一):使用x264編碼yuv為h264資料的例子(軟編碼)

先說下簡單流程:

 1.camera回撥nv21 yuv;

 2.nv21轉yuv420;

 3.x264編碼h264,回調回java層

 4.寫檔案,生成.h264檔案;

 5.使用vlc等播放器播放。

android java層的程式碼比較簡單,簡單說下:

這個demo啟動會,surfaceView會顯示Camera拍攝到的資料,Activity需要繼承

SurfaceHolder.Callback,Camera.PreviewCallback介面

SurfaceHolder.Callback的方法有:

void surfaceCreated(SurfaceHolder var1);
void surfaceChanged(SurfaceHolder var1, int var2, int var3, int var4); void surfaceDestroyed(SurfaceHolder var1);

surfaceCreated方法一般可以做一些變數的初始化,在本地例子中,用來初始化x264編碼器,開啟照相機,程式碼如下:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    // TODO Auto-generated method stub
x264.initX264Encode(width
, height, fps, bitrate); camera = getBackCamera(); startcamera(camera); }
因為是做demo,所以width,height,fps,bitrate都是自己寫死的,如果做的好一些,應該先檢查camera支援的解析度,從而選擇width,height,然後獲取camera的幀率,再initx264Encoder物件

surfaceDestory方法可以將需要釋放的物件釋放,本例中,用來關閉照相機,關閉編碼器,關閉寫好的檔案

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // 
TODO Auto-generated method stub if (null != camera) { camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); camera = null; } x264.CloseX264Encode(); try { outputStream.flush(); outputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }

Camera.PreviewCallBack介面的方法是:

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    // TODO Auto-generated method stub
time += timespan;
    byte[] yuv420 = new byte[width*height*3/2];
YUV420SP2YUV420(data,yuv420,width,height);
x264.PushOriStream(yuv420, yuv420.length, time);
}
會回撥data ,為yuv資料,yuv資料格式的設定在startcamera中,程式碼如下:
parameters.setPreviewFormat(ImageFormat.NV21);
按說要檢查手機camera支援回撥的yuv資料格式,但是一般都支援nv21,所以先設定成nv21,

然後再通過方法將nv21轉換為yuv420,因為x264編碼器設定的支援的yuv格式為420的,程式碼如下:

private void YUV420SP2YUV420(byte[] yuv420sp, byte[] yuv420, int width, int height)
{
    if (yuv420sp == null ||yuv420 == null)return;
    int framesize = width*height;
    int i = 0, j = 0;
//copy y
for (i = 0; i < framesize; i++)
    {
        yuv420[i] = yuv420sp[i];
}
    i = 0;
    for (j = 0; j < framesize/2; j+=2)
    {
        yuv420[i + framesize*5/4] = yuv420sp[j+framesize];
i++;
}
    i = 0;
    for(j = 1; j < framesize/2;j+=2)
    {
        yuv420[i+framesize] = yuv420sp[j+framesize];
i++;
}
}

轉好以後,呼叫native層的介面來將yuv編碼為h264,編碼完成後,會通過回撥拋回來

x264.PushOriStream(yuv420, yuv420.length, time);
private x264sdk.listener l = new x264sdk.listener(){

    @Override
public void h264data(byte[] buffer, int length) {
        // TODO Auto-generated method stub
try {
            outputStream.write(buffer, 0, buffer.length);
} catch (IOException e) {
            // TODO Auto-generated catch block
e.printStackTrace();
}
    }
};
拋回來後,寫檔案,java的程式碼基本就這麼多。

下面簡單說下x264編碼器:

1.初始化x264編碼器

voidx264Encode::initX264Encode(int width, int height, int fps, int bite)

這個函式需要上層傳入視訊的解析度及幀率和位元率,具體程式碼請看例子吧,調重要的說下

x264支援多執行緒編碼,如果需要啟動多執行緒,則需要設定_x264_param->i_threads引數

2.編碼yuv為h264

voidx264Encode::startEncoder(uint8_t * dataptr, char *&bufdata,int &buflen, int &isKeyFrame)

第一個引數就是yuv資料,第二個引數為編碼成功後h264資料的地址,第三個引數返回h264的長度,第四個引數返回是否為I幀

3.回撥給java

使用h264callbackFunc回撥,傳到JNI層,JNI通過CallVoidMethod回撥到java層,JNI回撥java程式碼如下:


void CALLBACK H264DataCallBackFunc(void* pdata,int datalen)
{
h264datacallback.name = "H264DataCallBackFunc";
h264datacallback.signature = "([BI)V";
JavaEnv java;
if (java.istarch) {
JNIEnv* menv= NULL;
VM->AttachCurrentThread(&menv, NULL);
jbyteArray pcmdata = menv->NewByteArray(datalen);
menv->SetByteArrayRegion(pcmdata, 0, datalen,(jbyte*)pdata);
java.env->CallVoidMethod(ehobj,h264datacallback.getMID(java.env, jclz),pcmdata,datalen);
}
}

4.寫檔案->結束->關閉編碼器

完整的例子的下載地址如下:

https://github.com/sszhangpengfei/android_x264_encoder