现在的位置: 首页 > 综合 > 正文

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

2013年10月29日 ⁄ 综合 ⁄ 共 14613字 ⁄ 字号 评论关闭

模拟器一样是模拟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__
      

 

      你不用熟悉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. }  

 

 

  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解码器的实现为例:

  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.     }
      

 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 3751624@qq.com
 */

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 3751624@qq.com
*/

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

 

 

 

抱歉!评论已关闭.