流媒体程序开发之: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