关羽拿着一份报纸冲进屋来,“大哥,大哥,快来看啊!”
刘备:“二弟何事慌张?”
关羽:“慌张啥,我那是激动的,黄巾教真的倒了!”
刘备:“我勒个去,我们举报这么管用?”
张飞:“大哥,这么说我们定金不用退回去啦?”
刘备:“退个屁,都被我们花光了,再有这种好事送上门就好了~”
关羽:“大哥,你做开发的态度好不端正……不过,我喜欢!”
只听门外又有人叫门,“屋……屋里有人吗?”
刘备心中一喜,“莫非又有傻子临门?”赶忙去开门,只见门外一位两米多高大汉,英俊潇洒,玉树临风。
张飞问道:“来者何人?”
大汉道:“我……我叫吕布!”
刘备:“莫非就是那人中赤兔,马中吕布的吕布?!”
吕布:“擦……擦了!”
关羽:“你来所为何事?”
吕布:“听……听说这里有人能帮忙做应用。我……我就来了!”
刘备一脸媚笑:“哟,大爷,您算来对地方了!”
孔明不知何时冒了出来,嘀咕道:“天呐,我这啥时候变妓院了……我有一种拉皮条的感觉……”
吕布:“我……我老板董卓布置我做一个音视频项目。我……我不会呀,所以找人代做,我……我好交差。”
刘备:“音视频小菜一碟,您放心好了,至于开发经费……”
吕布:“老……老板给我的钱我可以都给你们,我……我能交差就行了。”
刘备:“我……我就欣赏您这种爽快人!”
1.1. Android音视频介绍
在Android上音视频频的播放主要靠MediaPlayer类来实现,音频的录制则靠MediaRecorder类来实现。由于硬件和Linux系统的限制,目前Android上只支持MIDI/MP3/AAC格式的音频,其他格式的音频需要第三方的解码器来实现。
MediaPlayer类可以用来播放音频、视频和流媒体。MediaPlayer类的常用方法如下表17-1所示:
表17-1 MediaPlayer类的常用方法
方法 | 功能 |
public MediaPlayer () | 构造方法。 |
public static MediaPlayer create (Context context, int resid) | 工厂方法,创建一个与播放媒体关联的MediaPlayer对象。 |
public int getCurrentPosition () | 获得当前的播放位置。 |
public int getDuration () | 获得播放时间。 |
public int getVideoHeight () | 获得视频View的高度。 |
public int getVideoWidth () | 获得视频View的宽度。 |
public boolean isLooping () | 是否循环播放。 |
public boolean isPlaying () | 是否正在播放。 |
public void pause () | 暂停。 |
public void prepare () | 准备(同步)。 |
public void prepareAsync () | 准备(异步)。 |
public void release () | 释放MediaPlayer对象。 |
public void reset () | 重置MediaPlayer对象。 |
public void seekTo (int msec) | 指定播放的位置。 |
public void setAudioStreamType (int streamtype) | 设置流媒体的类型。 |
public void setDataSource (String path) | 设置多媒体数据来源。 |
public void setDisplay (SurfaceHolder sh) | 设置用SurfaceHolder来显示多媒体。 |
public void setLooping (boolean looping) | 设置是否循环播放。 |
public void setOnBufferingUpdateListener (MediaPlayer.OnBufferingUpdateListener listener) | 网络流媒体的缓冲监听。 |
public void setOnErrorListener (MediaPlayer.OnErrorListener listener) | 设置错误信息监听。 |
public void setOnVideoSizeChangedListener (MediaPlayer.OnVideoSizeChangedListener listener) | 视频尺寸监听。 |
public void setScreenOnWhilePlaying (boolean screenOn) | 设置是否使用SurfaceHolder来显示。 |
public void setVolume (float leftVolume, float rightVolume) | 设置音量。 |
public void start () | 开始播放。 |
public void stop () | 停止播放。 |
开发音视频时,首先需要创建一个MediaPlayer对象,然后顺序调用MediaPlayer的reset()方法,setDataSource()方法和prepare()方法,最后调用start()方法播放音视频。
MediaRecorder类主要负责录制音视频。MediaRecorder类的常用方法如下表17-2所示:
表17-2 MediaRecorder类的常用方法
方法名 | 功能 |
public MediaRecorder () | 构造方法。 |
public int getMaxAmplitude () | 获得目前为止最大的幅度。 |
public void prepare () | 准备录音机。 |
public void release () | 释放MediaRecorder对象。 |
public void reset () | 重置MediaRecorder对象。 |
public void setAudioEncoder (int audio_encoder) | 设置音频编码。 |
public void setAudioSource (int audio_source) | 设置音频源。 |
public void setCamera (Camera c) | 设置摄像机。 |
public void setMaxDuration (int max_duration_ms) | 设置最大期限。 |
public void setMaxFileSize (long max_filesize_bytes) | 设置文件的最大尺寸。 |
public void setOnErrorListener (MediaRecorder.OnErrorListener l) | 错误监听。 |
public void setOutputFile (String path) | 设置输出文件。 |
public void setOutputFormat (int output_format) | 设置输出文件格式。 |
public void setPreviewDisplay (Surface sv) | 设置预览。 |
public void setVideoEncoder (int video_encoder) | 设置预览编码。 |
public void setVideoFrameRate (int rate) | 设置视频帧的频率。 |
public void setVideoSize (int width, int height) | 设置视频的宽度和高度。 |
public void setVideoSource (int video_source) | 设置视频源。 |
public void start () | 开始录制。 |
public void stop () | 停止录制。 |
录制音视频时,首先要创建MediaRecorder类对象,然后调用setAudioSource()方法设置音频源,调用prepare()方法准备开启录音,最后调用start()方法开始录制。需要注意的是,在录音完成之后需要调用stop()方法停止录音,并调用release()方法释放资源。
1.2. 音频播放实例
本小节将通过一个实例展示如何播放不同数据源的音频文件。实例分别从资源文件、文件系统和网络地址中实现了播放音频功能。运行程序,结果如图17-1所示:
图17-1 应用运行界面图
从上图17-1可以看出,实例包含了一个下拉列表和四个控制按钮,下拉列表用于选择播放源,四个按钮用于播放、暂停(继续)、重播和停止。
新建一个Activity,命名为PlayerActivity,其代码如下所示:
PlayerActivity.java代码清单17-2-0:
/**
* 音频播放类示例
* @author关羽:大哥,三弟,你们快点!一会我要上星光大道了!
*/
public classPlayerActivity extends Activity implements OnClickListener {
private staticfinalString TAG ="PlayerActivity";
private MediaPlayer mp;
private int src;
// 定义控制按钮
private Button mPlayButton;
private Button mPauseButton;
private Button mResetButton;
private Button mStopButton;
// 下拉列表选取播放源
private Spinner mSrcSpinner;
private ArrayAdapter<String>adapter;
private List<String> list = new ArrayList<String>();
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
mPlayButton =(Button)findViewById(R.id.play_btn);
mPauseButton = (Button)findViewById(R.id.pause_btn);
mResetButton =(Button)findViewById(R.id.reset_btn);
mStopButton =(Button)findViewById(R.id.stop_btn);
mPlayButton.setOnClickListener(this);
mPauseButton.setOnClickListener(this);
mResetButton.setOnClickListener(this);
mStopButton.setOnClickListener(this);
mSrcSpinner =(Spinner)findViewById(R.id.spinner_src);
list.add("从sd卡中的dota.mp3文件播放");
list.add("从程序包中的dota.mp3文件播放");
list.add("从网络上中的dota.mp3文件播放");
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,list);
mSrcSpinner.setAdapter(adapter);
mSrcSpinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
@Override
public voidonItemSelected(AdapterView<?> arg0, View arg1, int arg2, longarg3) {
Log.d(TAG,adapter.getItem(arg2));
src = arg2;
playerSetUp(arg2);
}
@Override
public void onNothingSelected(AdapterView<?>arg0) {
}
});
Log.d(TAG,"onCreate");
}
@Override
protected voidonDestroy() {
mp.release();
super.onDestroy();
Log.i(TAG,"onDestroy()");
}
@Override
public void onClick(View v) {
Button button = (Button)v;
try {
switch (v.getId()) {
case R.id.play_btn:
play();
break;
case R.id.pause_btn:
if (mp.isPlaying()) {
mp.pause();
// 设置按钮文字
button.setText(R.string.continue1);
} else {
mp.start();
button.setText(R.string.pause);
}
break;
case R.id.reset_btn:
if (mp.isPlaying()) {
// 设置从头开始播放
mp.seekTo(0);
} else {
play();
}
break;
case R.id.stop_btn:
// 如果它正在播放的话,就让他停止
if (mp.isPlaying())
mp.stop();
break;
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
private voidplay() throws IOException {
if (src != 1) {
mp.prepare();
}
mp.start();
}
private voidplayerSetUp(int src) {
if (mp != null) {
mp.release();
Log.d(TAG,"mp.release()");
}
Log.d(TAG,"src" + src);
switch (src) {
case 0:
// sd卡播放
mp = new MediaPlayer();
File audioFile = new File(Environment.getExternalStorageDirectory(),"dota.mp3");
mp.reset();
try {
mp.setDataSource(audioFile.getAbsolutePath());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
case 1:
// 资源文件播放
mp = MediaPlayer.create(PlayerActivity.this, R.raw.dota);
break;
case 2:
// 网络文件播放
mp = new MediaPlayer();
// 这里给一个歌曲的网络地址就行了
String path = "http://localhost:8080/music/test.mp3";
mp.reset();
try {
mp.setDataSource(path);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
1.2.1.从资源文件播放
从资源文件播放音频指的是将音频文件加入到应用程序中,然后在代码中实现播放的功能。由于音频文件往往较大,可以在res文件夹下新建一个命名为raw的文件夹,然后将音频文件拷贝到该文件夹下,通过R.raw直接访问这个音频文件。
资源文件播放的优点是普通用户接触不到音频文件,但缺点是资源文件过大会使应用程序过大,因此如果没有用户权限的限制不建议从资源文件播放音频。
从资源文件播放音频,MediaPlayer类的创建代码如下:
MediaPlayer mp = MediaPlayer.create(PlayerActivity.this, R.raw.dota);
其中create()方法是MediaPlayer类的静态工厂方法,而PlayerActivity是当前的Activity,R.raw.dota是指资源文件。
1.2.2.从文件系统播放音频
从文件系统播放音频指的是从Android手机文件系统的某一个目录下播放一个或者一些指定的音频。实例播放了SD卡目录下的dota.mp3文件。关键代码如下所示:
MediaPlayer mp =new MediaPlayer();
File audioFile =new File(Environment.getExternalStorageDirectory(),"dota.mp3");
mp.reset();
mp.setDataSource(audioFile.getAbsolutePath());
1.2.3.从网络地址播放音频
从网络地址播放音频指的是播放网络地址指向的音频文件。有两种创建MediaPlayer的方式:一是通过Uri,二是通过设置数据源。
Uri方式
通过Uri方式来创建MediaPlayer的关键代码如下所示:
//指定歌曲的网络地址
Stringpath="http://**************.mp3";
Uri uri =Uri.parse(path);
MediaPlayer player =new MediaPlayer.create(this,uri);
player.start();
setDataSource方式
通过设置数据源的方式播放音频的代码如下:
MediaPlayer player=newMediaPlayer.create();
//指定歌曲的网络地址
Stringpath="http://**************.mp3";
player.setDataSource(path);
player.prepare();
player.start();
1.3.视频播放开发
在Android上,视频的播放可以通过VideoView类结合MediaController类来实现。VideoView用于播放界面,而MediaController用于控制播放。也可以使用MediaPlayer类和SurfaceView类来实现Android上的视频播放。SurfaceView用于播放界面,MediaPlayer用于控制播放。由于硬件和Linux系统的限制,目前Android只支持MP4的H.264、3GP和WMV格式的视频播放。
1.3.1.VideoView播放视频实例
使用VideoView和MediaController来播放视频时,需要在布局中定义一个VideoView,然后用一个MediaController来控制播放。下面就通过一个实例来演示如何使用VideoView。运行程序,结果如图17-2所示:
图17-2 VideoView播放界面图
如上图17-2所示,代码在界面中定义了一个VideoView,对应的布局文件如下所示:
activity_video_view_player.xml代码清单17-3-1:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/video_view"
android:layout_width="320dip"
android:layout_height="240dip"/>
</LinearLayout>
新建一个Activity,命名为VideoViewPlayerActivity,在该Activity中新建一个MediaController来控制播放,代码如下所示:
VideoViewPlayerActivity.java代码清单17-3-1:
/**
* VideoView播放视频实例
* @author关云长:哎呀,上完星光大道,我终于成为中老年妇女的偶像啦!
*/
public class VideoViewPlayerActivity extends Activity {
private VideoView mVideoView;
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_view_player);
mVideoView = (VideoView)this.findViewById(R.id.video_view);
MediaController mc = newMediaController(this);
// 设置控制器
mVideoView.setMediaController(mc);
// 设置视频路径
mVideoView.setVideoPath("/sdcard/android_video.mp4");
// 获取焦点
mVideoView.requestFocus();
}
}
实例首先将视频文件放入SD卡,然后通过VideoView类设置控制器、视频路径和获取焦点。运行程序,点击视频区域,就会出现视频控制器,点击播放按钮就可以播放视频了。VideoView的其他常用方法如表17-3所示:
表17-3 VideoView类的常用方法
方法名 | 功能 |
public int getBufferPercentage () | 获得缓冲的百分比。 |
public int getCurrentPosition () | 获得当前播放的位置。 |
public int getDuration () | 获得视频的时间。 |
public boolean isPlaying () | 是否正在播放。 |
public void pause () | 暂停。 |
public int resolveAdjustedSize (int desiredSize, int measureSpec) | 调整视频显示大小。 |
public void seekTo (int msec) | 指定播放位置。 |
public void setMediaController (MediaController controller) | 设置播放控制器模式(播放进度条)。 |
public void setOnCompletionListener (MediaPlayer.OnCompletionListener l) | 当媒体文件播放完成时触发事件。 |
public void setOnErrorListener (MediaPlayer.OnErrorListener l) | 错误监听。 |
public void setVideoPath (String path) | 设置视频源路径。 |
public void setVideoURI (Uri uri) | 设置视频源地址。 |
public void start () | 开始播放。 |
1.3.2.MediaPlayer播放视频实例
使用MediaPlayer类和SurfaceView类来实现Android上的视频播放相对复杂一些,但是这种方法更灵活。下面通过一个实例来展示这种播放方式。运行程序,结果如图17-3所示:
图17-3 MediaPlayer播放界面
如图17-3所示,实例的布局文件包括一个SurfaceView,一个播放按钮和一个暂停按钮,代码如下所示:
activity_media_video_player.xml代码清单17-3-2:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id = "@+id/surface_view"
android:layout_width="320dp"
android:layout_height="240dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/play_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放"/>
<Button
android:id="@+id/pause_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停"/>
</LinearLayout>
</LinearLayout>
新建一个Activity,命名为MediaVideoPlayerActivity,在该Activity中新建一个MediaPlayer对象,用于播放视频,具体代码如下所示:
MediaVideoPlayerActivity.java代码清单17-3-2:
/**
* 使用MediaPlayer来播放视频示例
* @author关羽:自从当了中老年妇女的偶像之后,你懂得!
* @author张飞:羡慕嫉妒恨呀!
*/
public class MediaVideoPlayerActivity extends Activity implements OnClickListener,
SurfaceHolder.Callback {
// 视频路径
private String path ="/sdcard/android_video.mp4";
private Button mPlayBtn;
private Button mPauseBtn;
boolean isPause = false;
private SurfaceView mSurfaceView;
private MediaPlayer mMediaPlayer;
private SurfaceHoldermSurfaceHolder;
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_video_player);
mPlayBtn = (Button)findViewById(R.id.play_btn);
mPlayBtn.setOnClickListener(this);
mPauseBtn = (Button)findViewById(R.id.pause_btn);
mPauseBtn.setOnClickListener(this);
mSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mMediaPlayer = newMediaPlayer();
}
@Override
publicvoidonClick(View v) {
if (v == mPlayBtn) {
isPause = false;
playVideo(path);
} elseif (v == mPauseBtn){
if (isPause == false) {
mMediaPlayer.pause();
isPause = true;
} else {
mMediaPlayer.start();
isPause = false;
}
}
}
privatevoidplayVideo(String strPath) {
if (mMediaPlayer.isPlaying()== true) {
mMediaPlayer.reset();
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 将MediaPlayer与SurfaceView关联起来
// 设置Video影片以SurfaceHolder播放
mMediaPlayer.setDisplay(mSurfaceHolder);
try {
mMediaPlayer.setDataSource(strPath);
mMediaPlayer.prepare();
} catch (Exception e) {
e.printStackTrace();
}
mMediaPlayer.start();
}
@Override
protectedvoidonDestroy() {
mMediaPlayer.release();
super.onDestroy();
}
@Override
publicvoidsurfaceChanged(SurfaceHolder holder, intformat, int width, int height) {
}
@Override
publicvoid surfaceCreated(SurfaceHolderholder) {
}
@Override
publicvoid surfaceDestroyed(SurfaceHolderholder) {
}
}
1.4.玄德有话说
刘备:我想支持更多的音乐格式,怎么办?
孔明:如果要使Android支持更多的格式,完善功能,就需要增加第三方的解码器支持,如libmad、libspeex等。
刘备:军师啊,我用MediaPlayer播放音频不是很流畅啊,该如何优化一下才是?
孔明:MediaPlayer类只适合播放大的音频文件,而且延时也较大。对于类似于游戏这一类音效延时要求较低的应用来说,就需要使用SoundPool来建立一个声音池来播放音效了。