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

1. 移植目标
将H.264解码器移植到OPhone操作系统之上(NDK+C),并写一个测试程序(OPhoneSDK+Java)测试解码库是否正常运行,下面是解码时的截图:


OPhone的模拟器和Mobile的模拟器一样是模拟ARM指令的,不像Symbian模拟器一样执行的是本地代码,所以在模拟器上模拟出来的效率会比 真实手机上的效率要低,之前这款解码器已经优化到在nokia 6600(相当低端的一款手机,CPU主频才120Hz)上做到在线播放。

2. 面向人群
本文面向有一定的手机应用开发经验(例如:S60/Mobile/MTK)和有一定的跨手机平台移植经验的人员,帮助她们了解一个企业的核心库(C/C++)是怎么移植到OPhone之上的。

3. 假定前提
1)熟悉Java/C/C++语言;
2)熟悉Java的JNI技术;
3)有一定的跨手机平台移植经验;
4)有一套可供移植的源代码库,这里以H.264解码库为例,为了保护我们的知识版权,这里只能够公开头文件:

  1. #ifndef__H264DECODE_H__
  2. #define__H264DECODE_H__
  3. #ifdefined(__SYMBIAN32__)//S602rd/3rd/UIQ
  4. #include<e32base.h>
  5. #include<libc"stdio.h>
  6. #include<libc"stdlib.h>
  7. #include<libc"string.h>
  8. #else//Windows/Mobile/MTK/OPhone
  9. #include<stdio.h>
  10. #include<stdlib.h>
  11. #include<string.h>
  12. #endif
  13. classH264Decode
  14. {
  15. public:
  16. /***************************************************************************/
  17. /*构造解码器*/
  18. /*@returnH264Decode解码器实例*/
  19. /***************************************************************************/
  20. staticH264Decode*H264DecodeConstruct();
  21. /***************************************************************************/
  22. /*解码一帧*/
  23. /*@pInBuffer指向H264的视频流*/
  24. /*@iInSizeH264视频流的大小*/
  25. /*@pOutBuffer解码后的视频视频*/
  26. /*@iOutSize解码后的视频大小*/
  27. /*@return已解码的H264视频流的尺寸*/
  28. /***************************************************************************/
  29. intDecodeOneFrame(unsignedchar*pInBuffer,unsignedintiInSize,unsignedchar*pOutBuffer,unsignedint&iOutSize);
  30. ~H264Decode();
  31. };
  32. #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__

你不用熟悉OPhone平台,一切从零开始,因为在此之前,我也不熟悉。

4. 开发环境(请参考: http://www.ophonesdn.com/documentation/)

5. 移植过程

5.1移植流程

5.2 封装Java接口

在“假定前提”中提到了要移植的函数,接下来会编写这些 函数的Java Native Interface。

  1. packageophone.streaming.video.h264;
  2. importjava.nio.ByteBuffer;
  3. publicclassH264decode{
  4. //H264解码库指针,因为Java没有指针一说,所以这里用一个32位的数来存放指针的值
  5. privatelongH264decode=0;
  6. static{
  7. System.loadLibrary("H264Decode");
  8. }
  9. publicH264decode(){
  10. this.H264decode=Initialize();
  11. }
  12. publicvoidCleanup(){
  13. Destroy(H264decode);
  14. }
  15. publicintDecodeOneFrame(ByteBufferpInBuffer,ByteBufferpOutBuffer){
  16. returnDecodeOneFrame(H264decode,pInBuffer,pOutBuffer);
  17. }
  18. privatenativestaticintDecodeOneFrame(longH264decode,ByteBufferpInBuffer,ByteBufferpOutBuffer);
  19. privatenativestaticlongInitialize();
  20. privatenativestaticvoidDestroy(longH264decode);
  21. }
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); }


这块没什么好说的,就是按照H264解码库的函数,封装的一层接口,如果你熟悉Java JNI,会发现原来是这么类似。这里插入一句:我一直认为技术都是相通的,底层的技术就那么几种,学懂了,其它技术都是一通百通。

5.3 使用C实现本地方法

5.3.1生成头文件

使用javah命令生成JNI头文件,这里需要注意是class路径不是源代码的路径,并且要加上包名:

这里生成了一个ophone_streaming_video_h264_H264decode.h,我们打开来看看:

  1. #include<jni.h>
  2. #ifndef_Included_ophone_streaming_video_h264_H264decode
  3. #define_Included_ophone_streaming_video_h264_H264decode
  4. #ifdef__cplusplus
  5. extern"C"{
  6. #endif
  7. JNIEXPORTjintJNICALLJava_ophone_streaming_video_h264_H264decode_DecodeOneFrame
  8. (JNIEnv*,jclass,jlong,jobject,jobject);
  9. JNIEXPORTjlongJNICALLJava_ophone_streaming_video_h264_H264decode_Initialize
  10. (JNIEnv*,jclass);
  11. JNIEXPORTvoidJNICALLJava_ophone_streaming_video_h264_H264decode_Destroy
  12. (JNIEnv*,jclass,jlong);
  13. #ifdef__cplusplus
  14. }
  15. #endif
  16. #endif
#include <jni.h> #ifndef _Included_ophone_streaming_video_h264_H264decode #define _Included_ophone_streaming_video_h264_H264decode #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_ophone_streaming_video_h264_H264decode_DecodeOneFrame (JNIEnv *, jclass, jlong, jobject, jobject); JNIEXPORT jlong JNICALL Java_ophone_streaming_video_h264_H264decode_Initialize (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_ophone_streaming_video_h264_H264decode_Destroy (JNIEnv *, jclass, jlong); #ifdef __cplusplus } #endif #endif

5.3.2实现本地方法

之前已经生成了JNI头文件,接下来只需要实现这个头文件的几个导出函数,这里以H264解码器的实现为例:

  1. #include"ophone_streaming_video_h264_H264decode.h"
  2. #include"H264Decode.h"
  3. JNIEXPORTjintJNICALLJava_ophone_streaming_video_h264_H264decode_DecodeOneFrame
  4. (JNIEnv*env,jclassobj,jlongdecode,jobjectpInBuffer,jobjectpOutBuffer){
  5. H264Decode*pDecode=(H264Decode*)decode;
  6. unsignedchar*In=NULL;unsignedchar*Out=NULL;
  7. unsignedintInPosition=0;unsignedintInRemaining=0;unsignedintInSize=0;
  8. unsignedintOutSize=0;
  9. jintDecodeSize=-1;
  10. jbyte*InJbyte=0;
  11. jbyte*OutJbyte=0;
  12. jbyteArrayInByteArrary=0;
  13. jbyteArrayOutByteArrary=0;
  14. //获取Input/OutByteBuffer相关属性
  15. {
  16. //Input
  17. {
  18. jclassByteBufferClass=env->GetObjectClass(pInBuffer);
  19. jmethodIDPositionMethodId=env->GetMethodID(ByteBufferClass,"position","()I");
  20. jmethodIDRemainingMethodId=env->GetMethodID(ByteBufferClass,"remaining","()I");
  21. jmethodIDArraryMethodId=env->GetMethodID(ByteBufferClass,"array","()[B");
  22. InPosition=env->CallIntMethod(pInBuffer,PositionMethodId);
  23. InRemaining=env->CallIntMethod(pInBuffer,RemainingMethodId);
  24. InSize=InPosition+InRemaining;
  25. InByteArrary=(jbyteArray)env->CallObjectMethod(pInBuffer,ArraryMethodId);
  26. InJbyte=env->GetByteArrayElements(InByteArrary,0);
  27. In=(unsignedchar*)InJbyte+InPosition;
  28. }
  29. //Output
  30. {
  31. jclassByteBufferClass=env->GetObjectClass(pOutBuffer);
  32. jmethodIDArraryMethodId=env->GetMethodID(ByteBufferClass,"array","()[B");
  33. jmethodIDClearMethodId=env->GetMethodID(ByteBufferClass,"clear","()Ljava/nio/Buffer;");
  34. //清理输出缓存区
  35. env->CallObjectMethod(pOutBuffer,ClearMethodId);
  36. OutByteArrary=(jbyteArray)env->CallObjectMethod(pOutBuffer,ArraryMethodId);
  37. OutJbyte=env->GetByteArrayElements(OutByteArrary,0);
  38. Out=(unsignedchar*)OutJbyte;
  39. }
  40. }
  41. //解码
  42. DecodeSize=pDecode->DecodeOneFrame(In,InRemaining,Out,OutSize);
  43. //设置Input/OutputByteBuffer相关属性
  44. {
  45. //Input
  46. {
  47. jclassByteBufferClass=env->GetObjectClass(pInBuffer);
  48. jmethodIDSetPositionMethodId=env->GetMethodID(ByteBufferClass,"position","(I)Ljava/nio/Buffer;");
  49. //设置输入缓冲区偏移
  50. env->CallObjectMethod(pInBuffer,SetPositionMethodId,InPosition+DecodeSize);
  51. }
  52. //Output
  53. {
  54. jclassByteBufferClass=env->GetObjectClass(pOutBuffer);
  55. jmethodIDSetPositionMethodId=env->GetMethodID(ByteBufferClass,"position","(I)Ljava/nio/Buffer;");
  56. //设置输出缓冲区偏移
  57. env->CallObjectMethod(pOutBuffer,SetPositionMethodId,OutSize);
  58. }
  59. }
  60. //清理
  61. env->ReleaseByteArrayElements(InByteArrary,InJbyte,0);
  62. env->ReleaseByteArrayElements(OutByteArrary,OutJbyte,0);
  63. returnDecodeSize;
  64. }
  65. JNIEXPORTjlongJNICALLJava_ophone_streaming_video_h264_H264decode_Initialize
  66. (JNIEnv*env,jclassobj){
  67. H264Decode*pDecode=H264Decode::H264DecodeConstruct();
  68. return(jlong)pDecode;
  69. }
  70. JNIEXPORTvoidJNICALLJava_ophone_streaming_video_h264_H264decode_Destroy
  71. (JNIEnv*env,jclassobj,jlongdecode){
  72. H264Decode*pDecode=(H264Decode*)decode;
  73. if(pDecode)
  74. {
  75. deletepDecode;
  76. pDecode=NULL;
  77. }
  78. }
#include "ophone_streaming_video_h264_H264decode.h" #include "H264Decode.h" JNIEXPORT jint JNICALL Java_ophone_streaming_video_h264_H264decode_DecodeOneFrame (JNIEnv * env, jclass obj, jlong decode, jobject pInBuffer, jobject pOutBuffer) { H264Decode *pDecode = (H264Decode *)decode; unsigned char *In = NULL;unsigned char *Out = NULL; unsigned int InPosition = 0;unsigned int InRemaining = 0;unsigned int InSize = 0; unsigned int OutSize = 0; jint DecodeSize = -1; jbyte *InJbyte = 0; jbyte *OutJbyte = 0; jbyteArray InByteArrary = 0; jbyteArray OutByteArrary = 0; //获取Input/Out ByteBuffer相关属性 { //Input { jclass ByteBufferClass = env->GetObjectClass(pInBuffer); jmethodID PositionMethodId = env->GetMethodID(ByteBufferClass,"position","()I"); jmethodID RemainingMethodId = env->GetMethodID(ByteBufferClass,"remaining","()I"); jmethodID ArraryMethodId = env->GetMethodID(ByteBufferClass,"array","()[B"); InPosition = env->CallIntMethod(pInBuffer,PositionMethodId); InRemaining = env->CallIntMethod(pInBuffer,RemainingMethodId); InSize = InPosition + InRemaining; InByteArrary = (jbyteArray)env->CallObjectMethod(pInBuffer,ArraryMethodId); InJbyte = env->GetByteArrayElements(InByteArrary,0); In = (unsigned char*)InJbyte + InPosition; } //Output { jclass ByteBufferClass = env->GetObjectClass(pOutBuffer); jmethodID ArraryMethodId = env->GetMethodID(ByteBufferClass,"array","()[B"); jmethodID ClearMethodId = env->GetMethodID(ByteBufferClass,"clear","()Ljava/nio/Buffer;"); //清理输出缓存区 env->CallObjectMethod(pOutBuffer,ClearMethodId); OutByteArrary = (jbyteArray)env->CallObjectMethod(pOutBuffer,ArraryMethodId); OutJbyte = env->GetByteArrayElements(OutByteArrary,0); Out = (unsigned char*)OutJbyte; } } //解码 DecodeSize = pDecode->DecodeOneFrame(In,InRemaining,Out,OutSize); //设置Input/Output ByteBuffer相关属性 { //Input { jclass ByteBufferClass = env->GetObjectClass(pInBuffer); jmethodID SetPositionMethodId = env->GetMethodID(ByteBufferClass,"position","(I)Ljava/nio/Buffer;"); //设置输入缓冲区偏移 env->CallObjectMethod(pInBuffer,SetPositionMethodId,InPosition + DecodeSize); } //Output { jclass ByteBufferClass = env->GetObjectClass(pOutBuffer); jmethodID SetPositionMethodId = env->GetMethodID(ByteBufferClass,"position","(I)Ljava/nio/Buffer;"); //设置输出缓冲区偏移 env->CallObjectMethod(pOutBuffer,SetPositionMethodId,OutSize); } } //清理 env->ReleaseByteArrayElements(InByteArrary,InJbyte,0); env->ReleaseByteArrayElements(OutByteArrary,OutJbyte,0); return DecodeSize; } JNIEXPORT jlong JNICALL Java_ophone_streaming_video_h264_H264decode_Initialize (JNIEnv * env, jclass obj) { H264Decode *pDecode = H264Decode::H264DecodeConstruct(); return (jlong)pDecode; } JNIEXPORT void JNICALL Java_ophone_streaming_video_h264_H264decode_Destroy (JNIEnv * env, jclass obj, jlong decode) { H264Decode *pDecode = (H264Decode *)decode; if (pDecode) { delete pDecode; pDecode = NULL; } }

5.3.3编译本地方法

接下来,只需要把用C实现的本地方法编译为动态链接库,如果之前你用于移植的那个库曾经移植到Symbian上过,那么编译会相当简单,因为NDK的编译器和Symbian的编译器一样,都是采用GCC做交叉编译器。

首先,需要在$NDK"apps目录下,创建一个项目目录,这里创建了一个H264Decode目录,在H264Decode目录中,创建一个Android.mk文件:

  1. APP_PROJECT_PATH:=$(callmy-dir)
  2. APP_MODULES:=H264Decode
APP_PROJECT_PATH := $(call my-dir) APP_MODULES := H264Decode


接下来,需要在$NDK"source目录下,创建源代码目录(这里的目录名要和上面创建的项目目录文件名相同),这里创建一个H264Decode目录,然后把之前生成的JNI头文件和你实现的本地方法相关头文件和源代码,都拷贝到 这个目录下面。

然后,我们编辑Android.mk文件:

  1. LOCAL_PATH:=$(callmy-dir)
  2. include$(CLEAR_VARS)
  3. LOCAL_MODULE:=H264Decode
  4. LOCAL_SRC_FILES:=common.ccabac.cutils.cgolomb.cmpegvideo.cmem.cimgconvert.ch264decode.cpph264.cdsputil.cophone_streaming_video_h264_H264decode.cpp
  5. include$(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := H264Decode LOCAL_SRC_FILES := common.c cabac.c utils.c golomb.c mpegvideo.c mem.c imgconvert.c h264decode.cpp h264.c dsputil.c ophone_streaming_video_h264_H264decode.cpp include $(BUILD_SHARED_LIBRARY)


关于Android.mk文件中,各个字段的解释,可以参考$NDK"doc下的《OPHONE-MK.TXT》和《OVERVIEW.TXT》,里面有详细的介绍。

最后,我们启动Cygwin,开始编译:

如果你看到了Install:**,这说明你的库已经编译好了。

FAQ 2:
如果编译遇到下面错误,怎么办?

  1. error:redefinitionoftypedef'int8_t'

需要注释掉你的代码中“typedef signed char int8_t;”,如果你的代码之前是已经移植到了Mobile/Symbian上的话,很有可能遇到这个问题。

5.4编写库测试程序

用Eclipse创建一个OPhone工程,在入口类中输入如下代码:

  1. /**
  2. *@authorophone
  3. *@email3751624@qq.com
  4. */
  5. packageophone.streaming.video.h264;
  6. importjava.io.File;
  7. importjava.io.FileInputStream;
  8. importjava.io.InputStream;
  9. importjava.nio.ByteBuffer;
  10. importOPhone.app.Activity;
  11. importOPhone.graphics.BitmapFactory;
  12. importOPhone.os.Bundle;
  13. importOPhone.os.Handler;
  14. importOPhone.os.Message;
  15. importOPhone.widget.ImageView;
  16. importOPhone.widget.TextView;
  17. publicclassH264ExampleextendsActivity{
  18. privatestaticfinalintVideoWidth=352;
  19. privatestaticfinalintVideoHeight=288;
  20. privateImageViewImageLayout=null;
  21. privateTextViewFPSLayout=null;
  22. privateH264decodeDecode=null;
  23. privateHandlerH=null;
  24. privatebyte[]Buffer=null;
  25. privateintDecodeCount=0;
  26. privatelongStartTime=0;
  27. publicvoidonCreate(BundlesavedInstanceState){
  28. super.onCreate(savedInstanceState);
  29. setContentView(R.layout.main);
  30. ImageLayout=(ImageView)findViewById(R.id.ImageView);
  31. FPSLayout=(TextView)findViewById(R.id.TextView);
  32. Decode=newH264decode();
  33. StartTime=System.currentTimeMillis();
  34. newThread(newRunnable(){
  35. publicvoidrun(){
  36. StartDecode();
  37. }
  38. }).start();
  39. H=newHandler(){
  40. publicvoidhandleMessage(Messagemsg){
  41. ImageLayout.invalidate();
  42. ImageLayout.setImageBitmap(BitmapFactory.decodeByteArray(Buffer,0,Buffer.length));
  43. longTime=(System.currentTimeMillis()-StartTime)/1000;
  44. if(Time>0){
  45. FPSLayout.setText("花费时间:"+Time+"秒解码帧数:"+DecodeCount+"FPS:"+(DecodeCount/Time));
  46. }
  47. }
  48. };
  49. }
  50. privatevoidStartDecode(){
  51. Fileh264file=newFile("/tmp/Demo.264");
  52. InputStreamh264stream=null;
  53. try{
  54. h264stream=newFileInputStream(h264file);
  55. ByteBufferpInBuffer=ByteBuffer.allocate(51200);//分配50k的缓存
  56. ByteBufferpRGBBuffer=ByteBuffer.allocate(VideoWidth*VideoHeight*3);
  57. while(h264stream.read(pInBuffer.array(),pInBuffer.position(),pInBuffer.remaining())>=0){
  58. pInBuffer.position(0);
  59. do{
  60. intDecodeLength=Decode.DecodeOneFrame(pInBuffer,pRGBBuffer);
  61. //如果解码成功,把解码出来的图片显示出来
  62. if(DecodeLength>0&&pRGBBuffer.position()>0){
  63. //转换RGB字节为BMP
  64. BMPImagebmp=newBMPImage(pRGBBuffer.array(),VideoWidth,VideoHeight);
  65. Buffer=bmp.getByte();
  66. H.sendMessage(H.obtainMessage());
  67. Thread.sleep(1);
  68. DecodeCount++;
  69. }
  70. }while(pInBuffer.remaining()>10240);//确保缓存区里面的数据始终大于10k
  71. //清理已解码缓冲区
  72. intRemaining=pInBuffer.remaining();
  73. System.arraycopy(pInBuffer.array(),pInBuffer.position(),pInBuffer.array(),0,Remaining);
  74. pInBuffer.position(Remaining);
  75. }
  76. }catch(Exceptione1){
  77. e1.printStackTrace();
  78. }finally{
  79. try{h264stream.close();}catch(Exceptione){}
  80. }
  81. }
  82. protectedvoidonDestroy(){
  83. super.onDestroy();
  84. Decode.Cleanup();
  85. }
  86. }
/** * @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图象用于显示:

  1. @authorophone
  2. *@email3751624@qq.com
  3. */
  4. packageophone.streaming.video.h264;
  5. importjava.nio.ByteBuffer;
  6. publicclassBMPImage{
  7. //---私有常量
  8. privatefinalstaticintBITMAPFILEHEADER_SIZE=14;
  9. privatefinalstaticintBITMAPINFOHEADER_SIZE=40;
  10. //---位图文件标头
  11. privatebytebfType[]={'B','M'};
  12. privateintbfSize=0;
  13. privateintbfReserved1=0;
  14. privateintbfReserved2=0;
  15. privateintbfOffBits=BITMAPFILEHEADER_SIZE+BITMAPINFOHEADER_SIZE;
  16. //---位图信息标头
  17. privateintbiSize=BITMAPINFOHEADER_SIZE;
  18. privateintbiWidth=176;
  19. privateintbiHeight=144;
  20. privateintbiPlanes=1;
  21. privateintbiBitCount=24;
  22. privateintbiCompression=0;
  23. privateintbiSizeImage=biWidth*biHeight*3;
  24. privateintbiXPelsPerMeter=0x0;
  25. privateintbiYPelsPerMeter=0x0;
  26. privateintbiClrUsed=0;
  27. privateintbiClrImportant=0;
  28. ByteBufferbmpBuffer=null;
  29. publicBMPImage(byte[]Data,intWidth,intHeight){
  30. biWidth=Width;
  31. biHeight=Height;
  32. biSizeImage=biWidth*biHeight*3;
  33. bfSize=BITMAPFILEHEADER_SIZE+BITMAPINFOHEADER_SIZE+biWidth*biHeight*3;
  34. bmpBuffer=ByteBuffer.allocate(BITMAPFILEHEADER_SIZE+BITMAPINFOHEADER_SIZE+biWidth*biHeight*3);
  35. writeBitmapFileHeader();
  36. writeBitmapInfoHeader();
  37. bmpBuffer.put(Data);
  38. }
  39. publicbyte[]getByte(){
  40. returnbmpBuffer.array();
  41. }
  42. privatebyte[]intToWord(intparValue){
  43. byteretValue[]=newbyte[2];
  44. retValue[0]=(byte)(parValue&0x00FF);
  45. retValue[1]=(byte)((parValue>>8)&0x00FF);
  46. return(retValue);
  47. }
  48. privatebyte[]intToDWord(intparValue){
  49. byteretValue[]=newbyte[4];
  50. retValue[0]=(byte)(parValue&0x00FF);
  51. retValue[1]=(byte)((parValue>>8)&0x000000FF);
  52. retValue[2]=(byte)((parValue>>16)&0x000000FF);
  53. retValue[3]=(byte)((parValue>>24)&0x000000FF);
  54. return(retValue);
  55. }
  56. privatevoidwriteBitmapFileHeader(){
  57. bmpBuffer.put(bfType);
  58. bmpBuffer.put(intToDWord(bfSize));
  59. bmpBuffer.put(intToWord(bfReserved1));
  60. bmpBuffer.put(intToWord(bfReserved2));
  61. bmpBuffer.put(intToDWord(bfOffBits));
  62. }
  63. privatevoidwriteBitmapInfoHeader(){
  64. bmpBuffer.put(intToDWord(biSize));
  65. bmpBuffer.put(intToDWord(biWidth));
  66. bmpBuffer.put(intToDWord(biHeight));
  67. bmpBuffer.put(intToWord(biPlanes));
  68. bmpBuffer.put(intToWord(biBitCount));
  69. bmpBuffer.put(intToDWord(biCompression));
  70. bmpBuffer.put(intToDWord(biSizeImage));
  71. bmpBuffer.put(intToDWord(biXPelsPerMeter));
  72. bmpBuffer.put(intToDWord(biYPelsPerMeter));
  73. bmpBuffer.put(intToDWord(biClrUsed));
  74. bmpBuffer.put(intToDWord(biClrImportant));
  75. }
  76. }
@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)); } }


测试程序完整工程在此暂不提供。

5.5集成测试

集成测试有两点需要注意,在运行程序前,需要把动态库复制到模拟器的/system/lib目录下面,还需要把需要解码的视频传到模拟器的/tmp目录下。
这里要明确的是,OPhone和Symbian的模拟器都做的太不人性化了,Symbian复制一个文件到模拟器中,要进一堆很深的目录,OPhone的 更恼火,需要敲命令把文件传递到模拟器里,说实话,仅在这点上,Mobile的模拟器做的还是非常人性化的。
命令:

  1. PATH=D:"OPhone"OPhoneSDK"tools"
  2. adb.exeremount
  3. adb.exepushD:"Eclipse"workspace"H264Example"libs"armeabi"libH264Decode.so/system/lib
  4. adb.exepushD:"Eclipse"workspace"H264Example"Demo.264/tmp
  5. pause
PATH=D:"OPhone"OPhone SDK"tools" adb.exe remount adb.exe push D:"Eclipse"workspace"H264Example"libs"armeabi"libH264Decode.so /system/lib adb.exe push D:"Eclipse"workspace"H264Example"Demo.264 /tmp pause


这里解释一下abd push命令:
adb push <本地文件路径> <远程文件路径> - 复制文件或者目录到模拟器
在Eclipse中,启动库测试程序,得到画面如下:

FAQ 3:

模拟器黑屏怎么办?
这可能是由于模拟器启动速度比较慢所引起的,所以需要多等一会。希望下个版本能够改进。

原文地址:http://www.ophonesdn.com/article/show/45;jsessionid=306BD3BE92F43DC693BEB09B0234B036



国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值