流媒体程序开发之:H264解码器移植到OPhone

流媒体程序开发之:H264解码器移植到OPhone
模拟器一样是模拟ARM指令的,不像Symbian模拟器一样执行的是本地代码,所以在模拟器上模拟出来的效率会比真实手机上的效率要低,之前这款 解码器已经优化到在nokia 6600(相当低端的一款手机,CPU主频才120Hz)上做到在线播放。
假定前提
1)熟悉Java/C/C++语言;
2)熟悉Java的JNI技术;
3)有一定的跨手机平台移植经验;
4)有一套可供移植的源代码库,这里以H.264解码库为例,为了保护我们的知识版权,这里只能够公开头文件:
1.#ifndef __H264DECODE_H__   
2.#define __H264DECODE_H__   
3.  
4.# if  defined(__SYMBIAN32__)   //S602rd/3rd/UIQ   
5.    #include <e32base.h>   
6.    #include <libc/stdio.h>   
7.    #include <libc/stdlib.h>   
8.    #include <libc/string.h>   
9.# else                         //Windows/Mobile/MTK/OPhone   
10.    #include <stdio.h>   
11.    #include <stdlib.h>   
12.    #include <string.h>   
13.#endif   
14.  
15.class  H264Decode   
16.{   
17.public :   
18.     /***************************************************************************/   
19.     /* 构造解码 器                                                        */   
20.     /* @return H264Decode解码器实 例                                      */   
21.     /***************************************************************************/   
22.     static  H264Decode *H264DecodeConstruct();   
23.     /***************************************************************************/   
24.     /* 解码一 帧                                                     */   
25.     /* @pInBuffer   指向H264的视频 流                                      */   
26.     /* @iInSize H264视频流的大 小                                      */   
27.     /* @pOutBuffer  解码后的视频视 频                                        */   
28.     /* @iOutSize    解码后的视频大 小                                        */   
29.     /* @return      已解码的H264视频流的尺 寸                              */   
30.     /***************************************************************************/   
31.     int  DecodeOneFrame(unsigned  char  *pInBuffer,unsigned  int  iInSize,unsigned  char  *pOutBuffer,unsigned  int  &iOutSize);   
32.    ~H264Decode();   
33.};   
34.#endif   // __H264DECODE_H__   
#ifndef __H264DECODE_H__ #define __H264DECODE_H__ #if defined(__SYMBIAN32__) //S602rd/3rd/UIQ #include <e32base.h> #include <libc/stdio.h> #include <libc/stdlib.h> #include <libc/string.h> #else //Windows/Mobile/MTK/OPhone #include <stdio.h> #include <stdlib.h> #include <string.h> #endif class H264Decode { public: /***************************************************************************/ /* 构造解码器 */ /* @return H264Decode解码器实例 */ /***************************************************************************/ static H264Decode *H264DecodeConstruct(); /***************************************************************************/ /* 解码一帧 */ /* @pInBuffer 指向H264的视频流 */ /* @iInSize H264视频流的大小 */ /* @pOutBuffer 解码后的视频视频 */ /* @iOutSize 解码后的视频大小 */ /* @return 已解码的H264视频流的尺寸 */ /***************************************************************************/ int DecodeOneFrame(unsigned char *pInBuffer,unsigned int iInSize,unsigned char *pOutBuffer,unsigned int &iOutSize); ~H264Decode(); }; #endif // __H264DECODE_H__
      你不用熟悉Android平台,一切从零开始,因为在此之前,我也不熟悉
封装Java接口
        在“假定前提”中提到了要移植的函数,接下来会编写这些 函数的Java Native Interface。
 
 
1.package  ophone.streaming.video.h264;   
2.  
3.import  java.nio.ByteBuffer;   
4.  
5.public   class  H264decode {   
6.       
7.//H264解码库指针,因为Java没有指针一说,所以这里用一个32 位的数来存放指针的值   
8.     private   long  H264decode =  0 ;   
9.       
10.     static {      
11.        System.loadLibrary( "H264Decode" );   
12.    }   
13.  
14.     public  H264decode() {   
15.         this .H264decode = Initialize();   
16.    }   
17.       
18.     public   void  Cleanup() {   
19.        Destroy(H264decode);   
20.    }   
21.       
22.     public   int  DecodeOneFrame(ByteBuffer pInBuffer,ByteBuffer pOutBuffer) {   
23.         return  DecodeOneFrame(H264decode, pInBuffer, pOutBuffer);   
24.    }   
25.  
26.     private   native   static   int  DecodeOneFrame( long  H264decode,ByteBuffer pInBuffer,ByteBuffer pOutBuffer);   
27.     private   native   static   long  Initialize();   
28.     private   native   static   void  Destroy( long  H264decode);   
29.}  
package ophone.streaming.video.h264; import java.nio.ByteBuffer; public class H264decode { //H264解码库指针,因为Java没有指针一说,所以这里用一个32位的数来存放指针的值 private long H264decode = 0; static{ System.loadLibrary("H264Decode"); } public H264decode() { this.H264decode = Initialize(); } public void Cleanup() { Destroy(H264decode); } public int DecodeOneFrame(ByteBuffer pInBuffer,ByteBuffer pOutBuffer) { return DecodeOneFrame(H264decode, pInBuffer, pOutBuffer); } private native static int DecodeOneFrame(long H264decode,ByteBuffer pInBuffer,ByteBuffer pOutBuffer); private native static long Initialize(); private native static void Destroy(long H264decode); }
1.#include <jni.h>   
2.  
3.#ifndef _Included_ophone_streaming_video_h264_H264decode   
4.#define _Included_ophone_streaming_video_h264_H264decode   
5.#ifdef __cplusplus   
6.extern  "C"  {   
7.#endif   
8.  
9.JNIEXPORT jint JNICALL Java_ophone_streaming_video_h264_H264decode_DecodeOneFrame   
10.  (JNIEnv *, jclass, jlong, jobject, jobject);   
11.  
12.JNIEXPORT jlong JNICALL Java_ophone_streaming_video_h264_H264decode_Initialize   
13.  (JNIEnv *, jclass);   
14.  
15.JNIEXPORT  void  JNICALL Java_ophone_streaming_video_h264_H264decode_Destroy   
16.  (JNIEnv *, jclass, jlong);   
17.  
18.#ifdef __cplusplus   
19.}   
20.#endif   
21.#endif 
  之前已经生成了JNI头文件,接下来只需要实现这个头文件的几个导出函数,这里以H264解码器的实现为例:
view plain copy to clipboard print ?
1.#include  "ophone_streaming_video_h264_H264decode.h"   
2.#include  "H264Decode.h"   
3.  
4.JNIEXPORT jint JNICALL Java_ophone_streaming_video_h264_H264decode_DecodeOneFrame   
5.  (JNIEnv * env, jclass obj, jlong decode, jobject pInBuffer, jobject pOutBuffer) {   
6.  
7.    H264Decode *pDecode = (H264Decode *)decode;   
8.    unsigned  char  *In = NULL;unsigned  char  *Out = NULL;   
9.    unsigned  int  InPosition =  0 ;unsigned  int  InRemaining =  0 ;unsigned  int  InSize =  0 ;   
10.    unsigned  int  OutSize =  0 ;   
11.    jint DecodeSize = - 1 ;   
12.  
13.    jbyte *InJbyte =  0 ;   
14.    jbyte *OutJbyte =  0 ;   
15.  
16.    jbyteArray InByteArrary =  0 ;   
17.    jbyteArray OutByteArrary =  0 ;   
18.  
19.     //获取Input/Out ByteBuffer相关属性   
20.    {   
21.         //Input   
22.        {   
23.            jclass ByteBufferClass = env->GetObjectClass(pInBuffer);   
24.            jmethodID PositionMethodId = env->GetMethodID(ByteBufferClass, "position" , "()I" );   
25.            jmethodID RemainingMethodId = env->GetMethodID(ByteBufferClass, "remaining" , "()I" );   
26.            jmethodID ArraryMethodId = env->GetMethodID(ByteBufferClass, "array" , "()[B" );   
27.               
28.            InPosition = env->CallIntMethod(pInBuffer,PositionMethodId);   
29.            InRemaining = env->CallIntMethod(pInBuffer,RemainingMethodId);   
30.            InSize = InPosition + InRemaining;   
31.               
32.            InByteArrary = (jbyteArray)env->CallObjectMethod(pInBuffer,ArraryMethodId);   
33.           
34.            InJbyte = env->GetByteArrayElements(InByteArrary, 0 );   
35.               
36.            In = (unsigned  char *)InJbyte + InPosition;   
37.        }   
38.  
39.         //Output   
40.        {   
41.            jclass ByteBufferClass = env->GetObjectClass(pOutBuffer);   
42.            jmethodID ArraryMethodId = env->GetMethodID(ByteBufferClass, "array" , "()[B" );   
43.            jmethodID ClearMethodId = env->GetMethodID(ByteBufferClass, "clear" , "()Ljava/nio/Buffer;" );   
44.               
45.             //清理输出缓存区   
46.            env->CallObjectMethod(pOutBuffer,ClearMethodId);   
47.  
48.            OutByteArrary = (jbyteArray)env->CallObjectMethod(pOutBuffer,ArraryMethodId);   
49.            OutJbyte = env->GetByteArrayElements(OutByteArrary, 0 );   
50.  
51.            Out = (unsigned  char *)OutJbyte;   
52.        }   
53.    }   
54.  
55.     //解码   
56.    DecodeSize = pDecode->DecodeOneFrame(In,InRemaining,Out,OutSize);   
57.  
58.     //设置Input/Output ByteBuffer相关属性   
59.    {   
60.         //Input   
61.        {   
62.            jclass ByteBufferClass = env->GetObjectClass(pInBuffer);   
63.            jmethodID SetPositionMethodId = env->GetMethodID(ByteBufferClass, "position" , "(I)Ljava/nio/Buffer;" );   
64.               
65.             //设置输入缓冲区偏移   
66.            env->CallObjectMethod(pInBuffer,SetPositionMethodId,InPosition + DecodeSize);   
67.        }   
68.  
69.         //Output   
70.        {   
71.            jclass ByteBufferClass = env->GetObjectClass(pOutBuffer);   
72.            jmethodID SetPositionMethodId = env->GetMethodID(ByteBufferClass, "position" , "(I)Ljava/nio/Buffer;" );   
73.  
74.             //设置输出缓冲区偏移   
75.            env->CallObjectMethod(pOutBuffer,SetPositionMethodId,OutSize);   
76.        }   
77.    }   
78.  
79.     //清理   
80.    env->ReleaseByteArrayElements(InByteArrary,InJbyte, 0 );   
81.    env->ReleaseByteArrayElements(OutByteArrary,OutJbyte, 0 );   
82.  
83.     return  DecodeSize;   
84.}   
85.  
86.JNIEXPORT jlong JNICALL Java_ophone_streaming_video_h264_H264decode_Initialize   
87.  (JNIEnv * env, jclass obj) {   
88.  
89.    H264Decode *pDecode = H264Decode::H264DecodeConstruct();   
90.     return  (jlong)pDecode;   
91.}   
92.  
93.JNIEXPORT  void  JNICALL Java_ophone_streaming_video_h264_H264decode_Destroy   
94.  (JNIEnv * env, jclass obj, jlong decode) {   
95.  
96.    H264Decode *pDecode = (H264Decode *)decode;   
97.     if  (pDecode)   
98.    {   
99.        delete pDecode;   
100.        pDecode = NULL;   
101.    }   
102.} 
 5.3.3 编译本地方法
        接下来,只需要把用C实现的本地方法编译为动态链接库,如果之前你用于移植的那个库曾经移植到Symbian上过,那么编译会相当简单,因为NDK的编译 器和Symbian的编译器一样,都是采用GCC做交叉编译器。
        首先,需要在$NDK/apps目录下,创建一个项目目录,这里创建了一个H264Decode目录,在H264Decode目录中,创建一个 Android.mk文件:
 
 
1.APP_PROJECT_PATH := $(call my-dir)   
2.APP_MODULES      := H264Decode 
  接下来,需要在$NDK/source目录下,创建源代码目录(这里的目录名要和上面创建的项目目录文件名相同),这里创建一个H264Decode目 录,然后把之前生成的JNI头文件和你实现的本地方法相关头文件和源代码,都拷贝到   这个目录下面。
编写库测试程序
/**  
 * @author ophone
 * @email [email protected]
 */

package ophone.streaming.video.h264;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import OPhone.app.Activity;
import OPhone.graphics.BitmapFactory;
import OPhone.os.Bundle;
import OPhone.os.Handler;
import OPhone.os.Message;
import OPhone.widget.ImageView;
import OPhone.widget.TextView;

public class H264Example extends Activity {
   
    private static final int VideoWidth = 352;
    private static final int VideoHeight = 288;
   
    private ImageView ImageLayout = null;
    private TextView FPSLayout = null;
    private H264decode Decode = null;
    private Handler H = null;
    private byte[] Buffer = null;
   
    private int DecodeCount = 0;
    private long StartTime = 0;

    public void onCreate(Bundle savedInstanceState) {
       
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        ImageLayout = (ImageView) findViewById(R.id.ImageView);
        FPSLayout = (TextView) findViewById(R.id.TextView);
        Decode = new H264decode();
       
        StartTime = System.currentTimeMillis();
       
        new Thread(new Runnable(){
            public void run() {
                StartDecode();
            }
        }).start();
       
        H = new Handler(){
            public void handleMessage(Message msg) {
                ImageLayout.invalidate();
                ImageLayout.setImageBitmap(BitmapFactory.decodeByteArray(Buffer, 0, Buffer.length));
               
                long Time = (System.currentTimeMillis()-StartTime)/1000;
                if(Time > 0){
                    FPSLayout.setText("花费时间:" + Time + "秒  解码帧数:" + DecodeCount + "  FPS:" + (DecodeCount/Time) );
                }
            }
        };
    }
   
    private void StartDecode(){
       
        File h264file = new File("/tmp/Demo.264");
        InputStream h264stream = null;
        try {
           
            h264stream = new FileInputStream(h264file);
            ByteBuffer pInBuffer = ByteBuffer.allocate(51200);//分配50k的缓存
            ByteBuffer pRGBBuffer = ByteBuffer.allocate(VideoWidth*VideoHeight*3);
           
            while (h264stream.read(pInBuffer.array(), pInBuffer.position(), pInBuffer.remaining()) >= 0) {
               
                pInBuffer.position(0);
                do{
                    int DecodeLength = Decode.DecodeOneFrame(pInBuffer, pRGBBuffer);
                   
                    //如果解码成功,把解码出来的图片显示出来
                    if(DecodeLength > 0 && pRGBBuffer.position() > 0){
                       
                        //转换RGB字节为BMP
                        BMPImage bmp = new BMPImage(pRGBBuffer.array(),VideoWidth,VideoHeight);
                        Buffer = bmp.getByte();
           
                        H.sendMessage(H.obtainMessage());

                        Thread.sleep(1);
                        DecodeCount ++;
                    }
                   
                }while(pInBuffer.remaining() > 10240);//确保缓存区里面的数据始终大于10k
               
                //清理已解码缓冲区
                int Remaining = pInBuffer.remaining();
                System.arraycopy(pInBuffer.array(), pInBuffer.position(), pInBuffer.array(), 0, Remaining);
                pInBuffer.position(Remaining);
            }
           
        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            try{h264stream.close();} catch(Exception e){}
        }
       
    }

    protected void onDestroy() {
        super.onDestroy();
        Decode.Cleanup();
    }
}
BMPImage是一个工具类,主要用于把RGB序列,转换为BMP图象用于显示:
@author ophone
 * @email [email protected]
*/

package ophone.streaming.video.h264;

import java.nio.ByteBuffer;

public class BMPImage {

    // --- 私有常量
    private final static int BITMAPFILEHEADER_SIZE = 14;
    private final static int BITMAPINFOHEADER_SIZE = 40;

    // --- 位图文件标头
    private byte bfType[] = { 'B', 'M' };
    private int bfSize = 0;
    private int bfReserved1 = 0;
    private int bfReserved2 = 0;
    private int bfOffBits = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE;

    // --- 位图信息标头
    private int biSize = BITMAPINFOHEADER_SIZE;
    private int biWidth = 176;
    private int biHeight = 144;
    private int biPlanes = 1;
    private int biBitCount = 24;
    private int biCompression = 0;
    private int biSizeImage = biWidth*biHeight*3;
    private int biXPelsPerMeter = 0x0;
    private int biYPelsPerMeter = 0x0;
    private int biClrUsed = 0;
    private int biClrImportant = 0;
   
    ByteBuffer bmpBuffer = null;
   
    public BMPImage(byte[] Data,int Width,int Height){
        biWidth = Width;
        biHeight = Height;
       
        biSizeImage = biWidth*biHeight*3;
        bfSize = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE + biWidth*biHeight*3;
        bmpBuffer = ByteBuffer.allocate(BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE + biWidth*biHeight*3);
       
        writeBitmapFileHeader();
        writeBitmapInfoHeader();
        bmpBuffer.put(Data);
    }
   
    public byte[] getByte(){
        return bmpBuffer.array();
    }
   
    private byte[] intToWord(int parValue) {

        byte retValue[] = new byte[2];

        retValue[0] = (byte) (parValue & 0x00FF);
        retValue[1] = (byte) ((parValue >> 8) & 0x00FF);

        return (retValue);
    }

    private byte[] intToDWord(int parValue) {

        byte retValue[] = new byte[4];

        retValue[0] = (byte) (parValue & 0x00FF);
        retValue[1] = (byte) ((parValue >> 8) & 0x000000FF);
        retValue[2] = (byte) ((parValue >> 16) & 0x000000FF);
        retValue[3] = (byte) ((parValue >> 24) & 0x000000FF);

        return (retValue);

    }
   
    private void writeBitmapFileHeader () {
       
        bmpBuffer.put(bfType);
        bmpBuffer.put(intToDWord (bfSize));
        bmpBuffer.put(intToWord (bfReserved1));
        bmpBuffer.put(intToWord (bfReserved2));
        bmpBuffer.put(intToDWord (bfOffBits));
       
    }
   
    private void writeBitmapInfoHeader () {
       
        bmpBuffer.put(intToDWord (biSize));   
        bmpBuffer.put(intToDWord (biWidth));   
        bmpBuffer.put(intToDWord (biHeight));   
        bmpBuffer.put(intToWord (biPlanes));   
        bmpBuffer.put(intToWord (biBitCount));   
        bmpBuffer.put(intToDWord (biCompression));   
        bmpBuffer.put(intToDWord (biSizeImage));   
        bmpBuffer.put(intToDWord (biXPelsPerMeter));   
        bmpBuffer.put(intToDWord (biYPelsPerMeter));   
        bmpBuffer.put(intToDWord (biClrUsed));   
        bmpBuffer.put(intToDWord (biClrImportant));
       
    }
}
来自:http://www.ophonesdn.com/article/show/45

你可能感兴趣的:(video,null,byte,Symbian,OPhone,程序开发)