论文.docx
《论文.docx》由会员分享,可在线阅读,更多相关《论文.docx(31页珍藏版)》请在冰豆网上搜索。
论文
廊坊师范学院
J2ME课程论文
题目:
基于Android平台的流媒体播放器的设计
姓名:
杨秀喜
专业:
08信本2班
学号:
08040342035
基于Android平台的流媒体播放器的设计
摘要:
随着移动通信技术和多媒体技术的迅速发展,移动流媒体服务有这很大的市场潜力,结合FFmpeg源码的解码流程设计出基于分层结构的流媒体播放器的系统架构,并讨论了FFmpeg的修剪优化方法,使其效率等特性适用于移动终端,再将优化后的代码移植到Android手机开发平台。
该播放器不仅支持本地文件及流媒体文件的播放,还提供了对外部摄像头的控制功能。
关键词:
Andorid;FFmpeg;PELCO-D协议
1引言:
随着移动通信技术和多媒体技术的迅速发展,融合手机、网络、多媒体技术为一体的视频监控技术也有了长足的进步,通过移动通信网络提供流媒体服务已经成为可能。
全球移动用户数非常庞大,因此移动流媒体服务具有巨大的市场潜力,也正成为移动业务的研究热点之一。
在这一背景下,针对移动网络和移动终端的特点,提出移动流媒体客户端的解决方案很有现实意义。
2播放器整体设计方案:
播放器无论播放本地文件或是网络流媒体文件,都需要有获取媒体数据,解码音视频媒体流,将解码后媒体数据显示给用户三个处理阶段,根据0文件播放的流程中这三个明显的处理阶段,本文提出基于层次的播放器结构设计。
由于本地文件和网络流媒体文件的数据获取方式是不相同的,若要保持上层解码的一致性,需要对两类文件进行预处理,形成相同格式的数据提供给上层解码。
根据以上特性,结合文件解码流程本文中面向实时监控的播放器设计采用分层结构,每层独立完成任务,使系统的耦合度降低,利于各层独立扩展而不影响上下层的应用。
从下至上依次是数据提取层、数据预处理层、音视频解码层和用户界面。
该流媒体播放器分层结构如图1所示。
用户界面层主要提供用户和播放器之间的交互接口,如播放本地文件时可以实现暂停、快进、快退等功能,在观看流媒体文件时可以通过数字键、导航键或者播放器上方向按钮控制摄像头的焦距、方向等信息。
音视频解码层主要有解码选择组件、各种主流音视频格式的解码器和多路媒体流之间同步的功能。
解码选择组件从本地文件或者流媒体文件头中获取到媒体的解码格式信息,根据该格式信息选择相应的解码器对压缩后媒体流进行解码。
该部分是由FFmpeg修剪优化后作为播放器的解码模块的。
多路媒体之间同步包括视频流和音频流的同步,在播放本地文件时可能还需要字幕的同步。
数据获取层的功能包括本地文件、流媒体文件的获取和摄像头控制信息的发送,前者只需读取本地文件即可,流媒体文件的获取需要从流媒体服务器获取媒体数据信息。
流媒体文件获取部分包括前期会话协商部分、数据发送部分和数据缓冲部分。
其中媒体信息协商部分需要使用RTSP协议[2]协商媒体流常规信息,如媒体类型(视频和音频)、传输协议(RTP/UDP/IP…)和媒体格式(H263、mpeg…)和媒体传输端口等信息。
3FFmpeg到Android平台的移植
FFmpeg是一个集录制、转换、音/视频编码解码功能为一体的完整的开源解决方案。
但本文中播放器只需要FFmpeg中对文件解封装及音视频解码部分的功能,若将FFmpeg整个解决方案全部移植到目标平台上会造成大量的代码冗余。
并且FFmpeg代码的开发时基于Linux操作系统的,并没有考虑到手机平台的处理能力小,能源不足等限制,因此针对手机上特定功能需求将FFmpeg代码进行修剪及优化是十分重要的。
3.1FFmpeg修剪及优化
从FFmpeg如此庞大并且代码结构复杂的源代码中找出本文需要的代码确实是一项非
常艰难的工作。
在Linux下编译运行FFmpeg代码时需要经过configure、make、makeinstall三步才能将FFmpeg正确的编译到Linux系统当中。
其中configure阶段会生成一个configure.h和make文件,从这两个文件中可以查找出该次编译都编译了那些文件。
以上所示的参数配置编译源文件时,系统只将h263、amr_nb的编码方法和3gp的文件封装格式及其所有的解码格式、解封装文件的源代码部分编译到了链接库。
此时被编译到链接库的源代码集合即为本文所需的源代码有效集,通过查找configure.h和make文件中的后缀名为.o文件,后缀名为.o的文件是编译.c代码时生成的目标文件,每一个被编译的.c文件都会生成.o文件,所以通过查看所有的后缀名为.o的文件名,便可得知在该配置参数下被编译源文件有哪些,因此可以得出本文所需编译的源文件最小集合。
3.2FFmpeg移植
Google发布的NDK的makefile文件即Android.mk文件语法和普通的makefile文件有很多不同之处,在跨平台编译FFmpeg源代码时并不能使用原有的makefile文件。
所以移植的先决条件就是将FFmpeg里的makefile文件全部替换为NDK中的Android.mk文件。
通过分析FFmpeg的模块结构得知avutil是基础模块,avcodec模块的编译基于已经编译好的avutil模块,avformat基于前两者,按照这种模块结构本文编译移植的顺序为avutil、avcoedec、avformat,编译的步骤详细说明如下:
1.关于config.h和config.mak
首先说明一下FFmpeg自带的makefile的框架,FFmpeg在经过configure命令之后会产生一个config.h文件和一个config.mak文件,这两个文件加起来共有600-700个宏定义,用来描述编译后代码的各个方面参数设置,其中有关于体系架构、编译器、链接库、头文件、版本、编解码器等等相关的宏定义。
在这一部分必须要修改关于平台差异方面的定义,比如必须把体系架构改成Android平台的ARMv5TE,这时文件编译的时候指令集就会选择ARM的指令集而不是X86的指令集。
这两个文件很重要,以后很多文件都要includeconfig.h这个文件,编译器会根据这个文件而选择性对代码进行编译。
2.编译libavutil.a
在libavutil建立一个Android.mk的文件,libavutil里的makefile文件需要调用subdir.mak,这个其实就是真正的编译,但是书写在Android.mk下,这个make文件可以不要,但需要直接把对应的源文件引入,标准的makefile是指定.o目标文件,但在Android.mk中需要直接接把对应的源文件引入,标准的makefile是指定.o目标文件,但在Android.mk中需要直接指定.c源文件,Android.mk文件如下所示:
LOCAL_PATH:
=$(callmy-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:
=avutil
LOCAL_SRC_FILES:
=adler32.c\
……\
include$(BUILD_STATIC_LIBRARY)
编译时可能会出现很多错误,但这些问题归结起来大部分都是因为有些头文件没有引入
而产生的问题,只要引入相应的头文件后就可以了。
比如不识别某些文件的size_t关键字,
在该文件includestdio.h后就不报错了。
4各层模块详解
4.1数据获取层
该层完成主要功能为与流媒体服务器协商媒体信息细节,并根据协商结果从服务器端获
取流媒体数据,将流媒体数据存入缓冲区,按照本文中缓冲策略将数据包发送给数据预处理层,其结构图如图2所示:
本文中该层一共启动五个线程,其中一个线程中启动TCP连接,用于RTSP会话协商,并且在RTP数据传输期间,TCP连接必须一直保留。
两个线程分别为接收音频和视频RTP数据的线程,另外两个线程分别为接收以及发送音频和视频的RTCP数据包。
4.2数据预处理层
本层对本地文件的预处理完全依赖于FFmpeg提供的功能文件解封装功能,而流媒体文件的预处理需将一个或多个RTP数据包整合在一起。
本文中流媒体播放器区别于其他普通流媒体播放器的最大特点即为能对外部带有云台
的摄像头进行控制,例如焦距、上、下、左、右等方面的设置。
所以本文中使用PELCO-D
协议作为云台控制协议[3]。
其协议格式如表1所示:
表1中第一字节为同步字也称起始符号,通常都是0xFF。
该符号字节用来检测所采用的收发方式正确与否。
第二字节填写为目标设备的地址,在命令字1字节中为对摄像头光圈及焦距的控制。
在命令字2字节为焦距及变倍控制,其中Bit4,Bit3,Bit2,Bitl为上下左右控制位,最后一个Bit0位总是0。
数据1字节中,水平方向速度(00-3F)。
数据2字节,垂直方向速度,其数值同数据字节1。
校验码字节为前六个字节之和。
本文设计的PELCO-D协议文本,最初默认情况下位命令字1、命令字2全部为0,数据字1和数据字2值为20H。
通过上层发送的按键消息修改相应命令字1、命令字2的相应位。
根据用户通常输入习惯,本文设计按键消息和屏幕上导航键与摄像头控制对应表如表2所示:
目前本文中流媒体播放器只提供以上六种控制功能,该模块根据上层出发的按键信息设置相应位为1,计算字节的值,形成七个字节文本发送至外部设备,当接收到上层按键停止
的消息后,统一发送{0xff,0x01,0x00,0x00,0x00,0x00,0x01,}停止命令。
4.3解码及显示层
解码层主要应用FFmpeg移植到Android平台的代码作为播放器的解码模块,该部分代码支持包括avi、3gp、MPEG-4等90多种解码格式及文件格式,并且经过修剪优化后的FFmpeg代码效率和效能都得到了很大的提高。
显示层本文主要应用开源的SDL函数库实现,SDL(SimpleDirectMediaLayer)是一个跨平台的,免费的开源软件。
该软件应用C语言开发,对外提供多种平台上图像、声音和其它输入设备的简单接口。
经常用于游戏和其他多媒体应用的开发,该开源软件可以运行于多种操作系统上,其中包括Linux、PSP、Windows、MacOSX等。
同时SDL还具有视频,音频,线程,定时器,事件等功能。
5总结
本文介绍了基于Android平台的流媒体播放器的分层设计结构及其各层的详细设计,本文中的播放器提供了对外部摄像头的控制功能,是其应用范围更为广泛,播放器播放本地文件及流媒体文件的效果图分别如图3、图4所示:
6.代码部分:
packageorg.apache.android.media;
importjava.io.File;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
import.MalformedURLException;
import.URL;
import.URLConnection;
importjava.util.prefs.Preferences;
importandroid.media.MediaPlayer;
importandroid.media.MediaPlayer.OnBufferingUpdateListener;
importandroid.media.MediaPlayer.OnErrorListener;
importandroid.os.Handler;
importandroid.util.Log;
importandroid.webkit.URLUtil;
publicclassAudioPlayerimplementsOnErrorListener,OnBufferingUpdateListener,
MediaPlayer.OnCompletionListener{
privatestaticfinalStringTAG="AudioPlayer";
privateMediaPlayermPlayer;
privateStringcurrent;
privatestaticfinalintMIN_BUFF=100*1024;
privateinttotalKbRead=0;
privateHandlerhandler=newHandler();
privateFileDLTempFile;
privateFileBUFFTempFile;
privatefinalStringTEMP_DOWNLOAD_FILE_NAME="tempMediaData";
privatefinalStringTEMP_BUFF_FILE_NAME="tempBufferData";
privatefinalStringFILE_POSTFIX=".mp4";
privatefinalintPER_READ=1024;
privatebooleanpause;
privatebooleanstop;
privatefinalintUNKNOWN_LENGTH=-1;
privateHandlermHandler=null;
publicvoidsetHandler(Handlerhandler){
mHandler=handler;}
publicvoidplay(finalStringpath){
downloadOver=false;
totalKbRead=0;
try{
Log.v(TAG,"playing:
"+path);
if(path.equals(current)&&mPlayer!
=null){
mPlayer.start();
return;}
current=path;
mPlayer=null;
newPlayThread(current).start();
}catch(Exceptione){
}
}
privatevoidsetListener(){
if(mPlayer!
=null){
mPlayer.setOnErrorListener(this);
mPlayer.setOnBufferingUpdateListener(this);
mPlayer.setOnCompletionListener(this);
}
}
privatevoidplayFromNet(StringmediaUrl,intstart,intend){
URLConnectioncn=null;
FileOutputStreamout=null;
InputStreamis=null;
try{
cn=newURL(mediaUrl).openConnection();
cn.connect();
is=cn.getInputStream();
intmediaLength=cn.getContentLength();
if(is==null){
return;
}
DLTempFile=File.createTempFile(TEMP_DOWNLOAD_FILE_NAME,
FILE_POSTFIX);
out=newFileOutputStream(DLTempFile);
bytebuf[]=newbyte[PER_READ];
intreadLength=0;
while(readLength!
=-1&&!
stop){
if(pause){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
continue;
}
readLength=is.read(buf);
if(readLength>0){
try{
out.write(buf,0,readLength);
totalKbRead+=readLength;
}catch(Exceptione){
Log.e(TAG,e.toString());
}
}
dealWithBufferData();
}
if(totalKbRead==mediaLength){
downloadOver=true;
dealWithLastData();
if(DLTempFile!
=null&&DLTempFile.exists()){
DLTempFile.delete();
}
}
}catch(MalformedURLExceptione){
Log.e(TAG,e.toString());
}catch(IOExceptione){
Log.e(TAG,e.toString());
}finally{
if(out!
=null){
try{
out.close();
}
catch(IOExceptione){
e.printStackTrace();
}
}
if(is!
=null){
try{
is.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}
privatebooleandownloadOver=false;
privatebooleanwasPlayed=false;
privatevoiddealWithBufferData(){
if(mPlayer==null||!
wasPlayed){
if(totalKbRead>=MIN_BUFF){
try{
startMediaPlayer();
}catch(Exceptione){
}
}
}elseif(mPlayer.getDuration()-mPlayer.getCurrentPosition()<=1000){
//deleteTempFile(true);
transferBufferToMediaPlayer();
}
}
privatevoidstartMediaPlayer(){
try{
//deleteTempFile(true);
BUFFTempFile=File.createTempFile(TEMP_BUFF_FILE_NAME,
FILE_POSTFIX);
//FileSystemUtil.copyFile(DLTempFile,BUFFTempFile);
mPlayer=newMediaPlayer();
setListener();
mPlayer.setDataSource(BUFFTempFile.getAbsolutePath());
mPlayer.prepare();
mPlayer.start();
wasPlayed=true;
}catch(IOExceptione){
}
}
privatevoidtransferBufferToMediaPlayer(){
try{booleanwasPlaying=mPlayer.isPlaying();
intcurPosition=mPlayer.getCurrentPosition();
mPlayer.pause();
BUFFTempFile=File.createTempFile(TEMP_BUFF_FILE_NAME,
FILE_POSTFIX);
//FileSystemUtil.copyFile(DLTempFile,BUFFTempFile);
mPlayer=newMediaPlayer();
mPlayer.setDataSource(BUFFTempFile.getAbsolutePath());
mPlayer.prepare();
mPlayer.seekTo(curPosition);
booleanatEndOfFile=mPlayer.getDuration()
-mPlayer.getCurrentPosition()<=1000;
if(wasPlaying||atEndOfFile){
mPlayer.start();
}
}catch(Exceptione){
}
}
privatevoiddealWithLastData(){
Runnableupdater=newRunnable(){
publicvoidrun(){
transferBufferToMediaPlayer();
}
};
handler.post(updater);
}
publicvoidonCompletion(MediaPlayermp){
if(mHandler!
=null){
//mHandler.sendEmptyMessage(Preferences.MEDIA_ENDED);
}
}
publicbooleanonError(MediaPlayermediaPlayer,intwhat,intextra){
if(mediaPlayer!
=null){
mediaPlayer.stop();
mediaPlayer.release();
}
if(mHandler!
=null){
//mHandler.sendEmptyMessage(Preferences.MEDIA_ERROR);
}
returntrue;
}
publicvoidonBufferingUpdate(MediaPlayerarg0,intpercent){
Log.d(TAG,"onBufferingUpdatecalled--->