第5章〓多媒体应用开发 本章导图 主要内容 使用MediaPlayer类播放音频。 使用AudioEffect类控制音乐特效。 使用VideoView控件播放视频。 使用MediaRecorder类录制视频。 控制摄像头拍照。 难点 掌握各种音频视频类和控件的使用方法。 Android相机拍照功能的实现。 随着手机硬件的不断提升,手机已经成为人们日常生活中必不可少的设备,设备里面的多媒体资源想必是很多人的兴趣所在。多媒体资源一般包括音频、视频等,Android系统针对不同的多媒体提供了不同的类进行支持。 Android提供了常见的音频、视频的编码、解码机制,支持的音频格式有MP3、WAV和3GP等,支持的视频格式有MP4和3GP等。接下来,本章将针对多媒体应用中的音频和视频操作进行讲解。 5.1音频和视频的播放 Android提供了简单的API来播放音频、视频,下面将详细介绍如何使用它们。 5.1.1使用MediaPlayer类播放音频 Android应用中播放音频文件的功能一般都是通过MediaPlayer类实现的,该类提供了全面的方法支持多种格式的音频文件。MediaPlayer类的常用方法如表5.1所示。 观看视频 表5.1MediaPlayer类的常用方法 方法 说明 setDataSource() 设置要播放音频文件的位置 prepare() 在开始播放之前调用该方法完成准备工作 start() 开始或继续播放音频 pause() 暂停播放音频 reset() 重置MediaPlayer对象 seekTo() 从指定位置开始播放音频 stop() 停止播放音频,调用该方法后MediaPlayer对象无法再播放音频 release() 释放与MediaPlayer对象相关的资源 isPlaying() 判断当前是否正在播放音频 getDuration() 获取载入的音频文件的时长 接下来通过演示使用MediaPlayer类播放音频的过程来了解MediaPlayer的使用,具体如下。 1. 实例化MediaPlayer类 使用MediaPlayer类播放音频时,首先创建一个MediaPlayer类的对象,接着调用setAudioStreamType()方法设置音频类型。示例代码如下: MediaPlayer mediaPlayer = new MediaPlayer ();//创建 MediaPlayer 类的对象 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); //设置音频类型 上述代码中的setAudioStreamType()方法中传递的参数表示音频类型。音频类型有很多种,最常用的音频类型有以下几种。 AudioManager.STREAM_MUSIC: 音乐。 AudioManager.STREAM_RING: 响铃。 AudioManager.STREAM_ALARM: 闹钟。 AudioManager.STREAM_NOTIFICTION: 提示音。 2. 设置数据源 根据音频文件存放的位置不同,将数据源的设置分为三种方式,分别为设置播放应用自带的音频文件、设置播放SD卡中的音频文件和设置播放网络音频文件。示例代码如下: //1.设置播放应用自带的音频文件 mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.xxx); //2.设置播放SD卡中的音频文件 mediaPlayer.setDataSource("SD卡中的音频文件的路径"); //3.设置播放网络音频文件 mediaPlayer.setDataSource("http://www.xxx.mp3"); 需要注意的是,播放网络中的音频文件时,需要在清单文件中添加访问网络的权限,示例代码如下。 <uses-permission android:name="android.permission.INTERNET"/> 3. 播放音频文件 一般在调用start()方法播放音频文件之前,程序会调用prepare()方法或prepareAsync()方法将音频文件解析到内存中。prepare()方法为同步操作,一般用于解析较小的文件; prepareAsync()方法为异步操作,一般用于解析较大的文件。 (1) 播放小音频文件。 示例代码如下: mediaPlayer.prepare(); mediaPlayer.start(); 需要注意的是,使用create()方法创建MediaPlayer对象并设置音频文件时,不能调用prepare()方法,直接调用start()方法播放音频文件即可。 (2) 播放大音频文件。 示例代码如下: mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new OnPreparedlistener){ public void onPrepared(MediaPlayer player){ player.start(); } } 上述代码中,prepareAsync()方法是子线程中执行的异步操作,不管它是否执行完毕,都不会影响主线程操作。setOnPreparedListener()方法用于设置MediaPlayer类的监听器,用于监听音频文件是否解析完成,如果解析完成,则会调用onPrepared()方法,在该方法内部调用start()方法播放音频文件。 4. 暂停播放 pause()方法用于暂停播放音频。在暂停播放之前,首先要判断MediaPlayer对象是否存在,并且当前是否正在播放音频。示例代码如下: if(mediaPlayer!=null && mediaPlayer.isPlaying()){ mediaPlayer.pause(); } 5. 重新播放 seekTo()方法用于定位播放。该方法用于快退或快进音频播放,方法中传递的参数表示将播放时间定在多少毫秒,如果传递的参数为0,则表示从头开始播放。示例代码如下: //1.播放状态下进行重播 if(mediaPlayer!=null && mediaPlayer.isPlaying()){ mediaPlayer.seekTo(0); //设置从头开始播放音频 return; } //2.暂停状态下进行重播,要调用start()方法 if(mediaPlayer!=null){ mediaPlayer.seekTo(0); //设置从头开始播放音频 mediaPlayer.start(); } 6. 停止播放 stop()方法用于停止播放,停止播放之后还要调用release()方法将MediaPlayer对象占用的资源释放并将该对象设置为null。示例代码如下: if(mediaPlayer!=null && mediaPlayer.isPlaying()){ mediaPlayer.stop(); //停止播放 mediaPlayer.release(); //释放MediaPlayer对象占用的资源 mediaPlayer = null; } 7. 播放不同来源音频文件的步骤 下面简单归纳一下用MediaPlayer类播放不同来源音频文件的步骤。 1) 播放应用的资源文件 播放应用的资源文件需要如下两步。 (1) 调用MediaPlayer类的create(Context context,int resid)方法加载指定资源文件。 (2) 调用MediaPlayer类的 start()、pause()、stop()等方法控制播放。 例如如下代码: MediaPlayer mPlayer = MediaPlayer.create(this,R.raw.song); mPlayer.start(); 提示: 音频资源文件一般放在Android 应用的/res/raw目录下。 2) 播放应用的原始资源文件 播放应用的原始资源文件按如下步骤执行。 (1) 调用 Context对象的getAssets()方法获取应用的AssetManager。 (2) 调用AssetManager对象的openFd(String name)方法打开指定的原始资源,该方法返回一个AssetFileDescriptor 对象。 (3) 调用AssetFileDescriptor对象的getFileDescriptor()、getStartOffset()和getLength()方法来获取音频文件的文件描述符、开始位置、长度等。 (4) 创建 MediaPlayer对象,并调用MediaPlayer对象的setDataSource(FileDescriptor fd,long offset,long length)方法来装载音频资源。 (5) 调用MediaPlayer对象的prepare()方法准备音频。 (6) 调用 MediaPlayer对象的 start()、pause()、stop()等方法控制播放。 注意,虽然MediaPlayer对象提供了setDataSource(FileDescriptor fd)方法来装载指定音频资源,但实际使用时这个方法似乎有问题: 不管程序调用openFd(String name)方法时指定打开哪个原始资源,MediaPlayer 将总是播放第一个原始的音频资源。 例如如下代码片段: AssetManager am= getAssets(); //打开指定音乐文件 AssetFileDescriptor afd=am.openFd(music); MediaPlayer mPlayer = new MediaPlayer(); //使用Mediaplayer加载指定的声音文件 mPlayer.setDataSource(afd.getFileDescriptor() , afd.getStartOffset() , afd.getLength()); //准备声音 mPlayer.prepare(); //播放 mPlayer.start(); 3) 播放外部存储器上的音频文件 播放外部存储器上的音频文件按如下步骤执行。 (1) 创建MediaPlayer对象,并调用MediaPlayer对象的setDateSource(String path)方法装载指定的音频文件。 (2) 调用MediaPlayer对象的prepare()方法准备音频。 (3) 调用MediaPlayer对象的start()、pause()、stop()等方法控制播放。 例如如下代码: MediaPlayer mPlayer = new MediaPlayer(); //使用MediaPlayer加载指定的声音文件 mPlayer.setDataSource("/mnt/sdcard/mysong.mp3"); //准备声音 mPlayer.prepare(); //播放 mPlayer.start(); 4) 播放来自网络的音频文件 播放来自网络的音频文件有两种方式: ①直接使用MediaPlayer对象的静态create(Contextcontext, Uri uri)方法; ②调用MediaPlayer对象的setDataSource(Context context, Uri uri)装载指定Uri对应的音频文件。 以第二种方式播放来自网络的音频文件的步骤如下。 (1) 根据网络上的音频文件所在的位置创建Uri对象。 (2) 创建MediaPlayer对象,并调用 MediaPlayer对象的setDateSource(Context context,Uri uri)方法装载Uri对应的音频文件。 (3) 调用MediaPlayer对象的prepare()方法准备音频。 (4) 调用MediaPlayer对象的 start()、pause()、stop()等方法控制播放。 例如如下代码片段: Uri uri=Uri.parse("http://www.crazyit.org/abc.mp3"); MediaPlayer mPlayer =new MediaPlayer(); //使用MediaPlayer根据Uri来加载指定的声音文件 mPlayer.setDataSource(this, uri); //准备声音 mPlayer.prepare(); //播放 mPlayer.start(); MediaPlayer除了调用prepare()方法来准备声音之外,还可以调用prepareAsync()方法来准备声音。prepareAsync()方法与普通prepare()方法的区别在于,prepareAsync()方法是异步的,它不会阻塞当前的UI线程。 归纳起来,MediaPlayer的状态图如图5.1所示。 图5.1MediaPlayer的状态图 5.1.2使用AudioEffect类控制音乐特效 Android可以控制播放音乐时的均衡器、重低音、音场及显示音乐波形等,这些都是靠AudioEffect及其子类来完成的,它包含如下常用子类: AcousticEchoCanceler: 取消回声控制器。 AutomaticGainControl: 自动增益控制器。 NoiseSppressor: 噪声压制控制器。 BassBoost: 重低音控制器。 Equalizer: 均衡控制器。 PresetReverb: 预设音场控制器。 Visualizer: 示波器。 上面的子类中前三个子类的用法很简单,只要调用它们的静态 create()方法创建相应的实例,然后调用它们的isAvailable()方法判断是否可用,再调用setEnabled(boolean enabled)方法启用相应效果即可。 1. AcousticEchoCanceler: 取消回声控制器 该功能的示意代码如下: //获取取消回声控制器 AcousticEchoCanceler canceler = AcousticEchoCanceler.create(0 , mPlayer.getAudioSessionId()) if(canceler.isAvailable()) { //启用取消回声功能 canceler.setEnabled(true); } 2. AutomaticGainControl: 自动增益控制器 该功能的示意代码如下: //获取自动增益控制器 AutomaticGainControl ctrl = AutomaticGainControl.create(0 , mPlayer.getAudioSessionId()) if(ctrl.isAvailable()) { //启用自动增益控制功能 ctrl.setEnabled(true); } 3. NoiseSppressor: 噪声压制控制器 该功能的示意代码如下: //获取噪声压制控制器 NoiseSuppressor suppressor = NoiseSuppressor.create(0 , mPlayer.getAudioSessionId()) if(suppressor.isAvailable()) { //启用噪声压制功能 suppressor.setEnabled(true); } BassBoost、Equalizer、PresetReverb、Visualizer这4个类,都需要调用构造器来创建实例。创建实例时,同样需要传入一个audioSession参数,为了启用它们,同样需要调用AudioEffect基类的setEnabled(true)方法。 4. BassBoost: 重低音控制器 低音增强,用于增强或放大声音的低频。它与简单的均衡器相当,但仅限于低频范围内的一个频段放大。 获取BassBoost对象之后,可调用它的setStrength(short strength)方法来设置重低音的强度。示例代码如下: BassBoost bassBoost = new BassBoost(0,mediaPlayer.getAudioSessionId()); bassBoost.setEnabled(true); if (bassBoost.getStrengthSupported()){ bassBoost.setStrength((short) 100); } 其中,getStrengthSupported()表示是否支持设置强度。如果此方法返回 false,则仅支持一种强度,并且 setStrength() 方法始终舍入到该值。 setStrength()设置效果的当前强度,强度的有效范围是[0, 1000],0 表示最温和的效果,1000 表示最强的效果。 5. Equalizer: 均衡控制器 Equalizer提供了 getNumberOfPresets()方法获取系统所有预设的音场,并提供了getPresetName()方法获取预设音场名称。获取Equalizer对象之后,可调用它的getNumberOfBands()方法获取该均器支持的总频率数,再调用getCenterFreq(short band)方法根据索引来获取频率。当用户想为某个频率的均衡器设置参数值时,可调用setBandLevel(short band, short level)方法进行设置。示例代码如下: MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.test_cbr/*音频路径*/); Equalizer equalizer = new Equalizer(0, mediaPlayer.getAudioSessionId()); equalizer.setEnabled(true); //获取均衡器引擎支持的频段数 short bands = equalizer.getNumberOfBands(); //获取最大和最小增益 final short minEQLevel = equalizer.getBandLevelRange()[0]; final short maxEQLevel = equalizer.getBandLevelRange()[1]; for (short i = 0; i < bands; i++) { final short band = i; //获取当前频段的中心频率,分别为60Hz,230Hz,910Hz,3600Hz,14000Hz int currentFreq = equalizer.getCenterFreq(band); //获取给定均衡器频段的增益 short level = equalizer.getBandLevel(band); Log.d("Equalizer","currentFreq is: " + currentFreq + ", band level is: " + level); //为给定的均衡器频带设置增益值 equalizer.setBandLevel(band,xx); } 在构造函数 Equalizer(int priority, int audioSession)中,参数如下: int priority: 优先级,多个应用可以共享同一 Equalizer引擎,该参数指出控制优先权,默认为0。 int audioSession: 音频会话ID,系统范围内唯一,Equalizer将被附加在拥有相同音频会话ID的MediaPlayer或AudioTrack上生效。 Android系统预置了一些增益参数,可通过下面代码获取: short presets= equalizer.getNumberOfPresets(); //获取系统预设的增益 for (short i = 0; i < presets; i++) { Log.d("presets",equalizer.getPresetName(i)); } 结果为Normal、Classical、Dance、Flat、Folk、Heavy Metal、Hip hop、Jazz、Pop、Rock。 然后通过 equalizer.usePreset(); 使用系统预置参数。 销毁时: if (equalizer != null) { equalizer.setEnabled(false); equalizer.release(); equalizer = null; } 6. PresetReverb: 预设音场控制器 PresetReverb使用预设混响来配置全局混响,适合于音乐。预置的常见混响场景有: PresetReverb.PRESET_LARGEHALL: 适合整个管弦乐队的大型大厅。 PresetReverb.PRESET_LARGEROOM: 适合现场表演的大型房间的混响预设。 获取 PresetReverb 对象之后,可调用它的 setPreset(short preset)方法设置使用预设置的音场。示例代码如下: PresetReverb presetReverb = new PresetReverb(0,mediaPlayer.getAudioSessionId()); presetReverb.setEnabled(true); presetReverb.setPreset(PresetReverb.PRESET_LARGEROOM); 7. Visualizer: 示波器 Visualizer对象并不用于控制音乐播放效果,它只是显示音乐的播放波形。为了实时显示该示波器的数据,需要为该组件设置一个 OnDataCaptureListener 监听器,该监听器将负责更新波形显示组件的界面。 5.1.3使用VideoView控件播放视频 播放视频与播放音频相比,播放视频需要使用视觉控件将影像展示出来。Android系统中的VideoView控件就是播放视频用的,借助它可以完成一个简易的视频播放器。 VideoView控件提供了一些用于控制视频播放的方法,如表5.2所示。 表5.2VideoView控件的常用方法 方法 说明 setVideoPath() 设置要播放的视频文件的位置 start() 开始或继续播放视频 pause() 暂停播放视频 resume() 重新开始播放视频 seekTo() 从指定位置开始播放视频 isPlaying() 判断当前是否正在播放视频 getDuration() 获取载入的视频文件的时长 接下来讲解如何通过VideoView控件播放视频的过程,具体介绍如下。 1. 在布局文件中添加VideoView控件 如果想在界面上播放视频,则首先需要在布局文件中放置1个VideoView控件用于显示视频播放界面。在布局中添加VideoView控件的示例代码如下: <VideoView android:id="@+id/videoview" android:layout_width="match_parent" android:layout_height="match_parent" /> 2. 视频的播放 使用VideoView控件既可以播放本地存放的视频,也可以播放网络中的视频。示例代码如下: VideoView videoView = (VideoView) findViewById(R.id.videoview); videoView.setVideoPath("mnt/sdcard/xxx.avi"); //播放本地视频 videoView.setVideoURI(Uri.parse("http://www.xxx.avi")); //加载网络视频 videoView.start(); //播放视频 根据上述代码可知,播放本地视频时需要调用VideoView控件的setVideoPath()方法,将本地视频地址传入该方法中即可。播放网络视频时需要调用VideoView控件的setVideoURI()方法,通过调用parse()方法将网络视频地址转换为Uri并传递到setVideoURI()方法中。 需要注意的是,播放网络视频时需要在AndroidManifest.xml文件的<manifest>标签中添加访问网络的权限。示例代码如下: <uses-permission android:name="android.permission.INTERNET"/> 3. 为VideoView控件添加控制器 使用VideoView控件播放视频时,可以通过setMediaController()方法为它添加一个控制器MediaController,该控制器中包含媒体播放器 (MediaPlayer) 中的一些典型按钮,如播放/暂停(Play/Pause)、倒带(Rewind)、快进(Fast Forward)与进度滑动器(Progress Slider)等。VideoView控件能够绑定媒体播放器,从而使播放状态和控件中显示的图像同步。示例代码如下: MediaController controller = new MediaController(context); videoView.setMediaController(controller); //为VideoView 控件绑定控制器 具体的使用详见本章实例视频播放器的实现。 5.2使用MediaRecorder类录制音频 手机一般都提供了麦克风硬件,而Android系统就可以利用该硬件来录制音频了。 为了在Android应用中录制音频,Android提供了MediaRecorder类。使用MediaRecorder类录制音频的过程很简单,按如下步骤进行即可。 (1) 创建MediaRecorder对象。 (2) 调用MediaRecorder对象的setAudioSource()方法设置声音来源,一般传入MediaRecorder.AudioSource.MIC参数指定录制来自麦克风的声音。 (3) 调用MediaRecorder对象的setOutputFormat()方法设置所录制的音频文件格式。 (4) 调用MediaRecorder对象的setAudioEncoder()、setAudioEncodingBitRate(int bitrate)、setAudioSamplingRate(int samplingRate)方法设置所录制的声音编码格式、编码位率、采样率等,这些参数将可以控制所录制的声音品质,文件大小。一般来说,声音品质越好,声音文件越大。 (5) 调用MediaRecorder对象的setOutputFile(String path)方法设置录制的音频文件的保存位置。 (6) 调用MediaRecorder对象的prepare()方法准备录制。 (7) 调用MediaRecorder对象的start()方法开始录制。 (8) 录制完成,调用MediaRecorder对象的stop()方法停止录制,并调用release()方法释放资源。 注意,上面的步骤中第(3)、(4)步千万不能搞反,否则程序将会抛出IllegalStateException异常。 下面的程序示范了如何使用MediaRecorder类来录制声音,该程序的界面布局很简单,只提供了两个简单的按钮来控制录音开始、停止,故此处不再给出界面布局文件。程序代码如下: public class RecordSound extends Activity implements OnClickListener { //程序中的两个按钮 ImageButton record , stop; //系统的音频文件 File soundFile ; MediaRecorder mRecorder; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //获取程序界面中的两个按钮 record = (ImageButton) findViewById(R.id.record); stop = (ImageButton) findViewById(R.id.stop); //为两个按钮的点击事件绑定监听器 record.setOnClickListener(this); stop.setOnClickListener(this); } @Override public void onDestroy() { if (soundFile != null && soundFile.exists()) { //停止录音 mRecorder.stop(); //释放资源 mRecorder.release(); mRecorder = null; } super.onDestroy(); } @Override public void onClick(View source) { switch (source.getId()) { //点击record按钮 case R.id.record: if (!Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { Toast.makeText(RecordSound.this , "SD卡不存在,请插入SD卡!" , 5000) .show(); return; } try { //创建保存录音的音频文件 soundFile = new File(Environment .getExternalStorageDirectory() .getCanonicalFile() + "/sound.amr"); mRecorder = new MediaRecorder(); //设置录音的声音来源 mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置录制的声音的输出格式(必须在设置声音编码格式之前设置) mRecorder.setOutputFormat(MediaRecorder .OutputFormat.THREE_GPP); //设置声音编码的格式 mRecorder.setAudioEncoder(MediaRecorder .AudioEncoder.AMR_NB); mRecorder.setOutputFile(soundFile.getAbsolutePath()); mRecorder.prepare(); //开始录音 mRecorder.start();//① } catch (Exception e) { e.printStackTrace(); } break; //点击stop按钮 case R.id.stop: if (soundFile != null && soundFile.exists()) { //停止录音 mRecorder.stop();//② //释放资源 mRecorder.release();//③ mRecorder = null; } break; } } } 上面的程序中大段的粗体字代码用于设置录音的相关参数,例如输出文件的格式、声音来源等。上面的程序中①粗体字代码控制MediaRecorder类开始录音; 当用户点击stop按钮时,程序中②号代码控制MediaRecorder类停止录音,③号粗体字代码用于释放资源。 录音完成后将可以看到/mnt/sdcard/目录下生成一个 sound.amr 文件,这就是录制的音频文件。Android 模拟器将会直接使用宿主机上的麦克风,因此如果读者的手机上有麦克风,那么该程序即可正常录制声音。 上面的程序需要使用系统的麦克风进行录音,因此需要向该程序授予录音的权限,也就是在AndroidManifest.xml文件中增加如下配置: <!--授予该程序录制声音的权限--> <uses-permission android:name="android.permission.RECORD_AUDIO"/> 5.3控制摄像头拍照 现在的手机一般都会提供相机功能,有些相机的镜头甚至支持1000万以上像素,有些甚至支持光学变焦,这些手机已经变成了专业数码相机。为了充分利用手机上的相机功能,Android应用可以控制拍照和录制视频。 5.3.1通过Camera进行拍照 Android应用提供了Camera来控制拍照,使用Camera进行拍照也比较简单,按如下步骤进行即可。 (1) 调用Camera的open()方法打开相机。 (2) 调用Camera的getParameters()方法获取拍照参数。该方法返回一个Camera.Parameters对象。 (3) 调用Camera.Parameters对象方法设置相机参数。 (4) 调用Camera的 setParameters()方法,并将Camera.Parameters 对象作为参数传入,这样即可对相机的拍照参数进行控制。 (5) 调用Camera的 startPreview()方法开始预览取景,在预览取景之前需要调用 Camera的setPreviewDisplay(SurfaceHolder holder)方法设置使用哪个 SurfaceView 来显示取景图片。 (6) 调用Camera的takePicture()方法进行拍照。 (7) 结束程序时,调用Camera的stopPreview()方法结束取景预览,并调用release()方法释放资源。 下面的程序示范了使用Camera来进行拍照,该程序的界面中只提供了一个SurfaceView组件来显示预览取景,十分简单。程序代码如下: public class CaptureImage extends Activity { SurfaceView sView; SurfaceHolder surfaceHolder; int screenWidth, screenHeight; //定义系统所用的照相机 Camera camera; //是否在浏览中 boolean isPreview = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置全屏 requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); WindowManager wm = (WindowManager) getSystemService( Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); //获取屏幕的宽和高 screenWidth = display.getWidth(); screenHeight = display.getHeight(); //获取界面中SurfaceView组件 sView = (SurfaceView) findViewById(R.id.sView); //获得SurfaceView的SurfaceHolder surfaceHolder = sView.getHolder(); //为surfaceHolder添加一个回调监听器 surfaceHolder.addCallback(new Callback() { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { //打开摄像头 initCamera(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { //如果camera不为null ,则释放摄像头 if (camera != null) { if (isPreview) camera.stopPreview(); camera.release(); camera = null; } } }); //设置该SurfaceView自己不维护缓冲 surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } private void initCamera() { if (!isPreview) { camera = Camera.open(); } if (camera != null && !isPreview) { try { Camera.Parameters parameters = camera.getParameters(); //设置预览照片的大小 parameters.setPreviewSize(screenWidth, screenHeight); //每秒显示4帧 parameters.setPreviewFrameRate(4); //设置图片格式 parameters.setPictureFormat(PixelFormat.JPEG); //设置JPG照片的质量 parameters.set("jpeg-quality", 85); //设置照片的大小 parameters.setPictureSize(screenWidth, screenHeight); camera.setParameters(parameters); //通过SurfaceView显示取景画面 camera.setPreviewDisplay(surfaceHolder);//① //开始预览 camera.startPreview();//② //自动对焦 camera.autoFocus(null); } catch (Exception e) { e.printStackTrace(); } isPreview = true; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { //当用户按照相键、中央键时执行拍照 case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_CAMERA: if (camera != null && event.getRepeatCount() == 0) { //拍照 camera.takePicture(null, null , myjpegCallback);//③ return true; } break; } return super.onKeyDown(keyCode, event); } PictureCallback myjpegCallback = new PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { //根据拍照所得的数据创建位图 final Bitmap bm = BitmapFactory.decodeByteArray(data , 0, data.length); //加载/layout/save.xml文件对应的布局资源 View saveDialog = getLayoutInflater().inflate( R.layout.save, null); final EditText photoName = (EditText) saveDialog .findViewById(R.id.phone_name); //获取saveDialog对话框上的ImageView控件 ImageView show = (ImageView) saveDialog.findViewById(R.id.show); //显示刚刚拍得的照片 show.setImageBitmap(bm); //使用对话框显示saveDialog控件 new AlertDialog.Builder(CaptureImage.this) .setView(saveDialog) .setPositiveButton("保存", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //创建一个位于SD卡上的文件 File file = new File(Environment.getExternalStorageDirectory() , photoName.getText().toString() + ".jpg"); FileOutputStream outStream = null; try { //打开指定文件对应的输出流 outStream = new FileOutputStream(file); //把位图输出到指定文件中 bm.compress(CompressFormat.JPEG, 100, outStream); outStream.close(); } catch (IOException e) { e.printStackTrace(); } } }) .setNegativeButton("取消", null) .show(); //重新浏览 camera.stopPreview(); camera.startPreview(); isPreview = true; } }; } 上面的程序中大段粗体字代码用于设置相机的拍照参数,这些参数可以控制图片的品质和图片文件的大小。 上面的程序中①号粗体字代码设置使用指定的SurfaceView来显示取景预览图片,程序中②号粗体字代码则用于开始预览取景。当用户按下相机的拍照键或中央键时,程序的③号粗体字代码调用takePicture()方法进行拍照。 调用takePicture()方法进行拍照时传入了一个PictureCallback对象——当程序获取了拍照所得的图片数据之后,PictureCallback对象将会被回调,该对象负责保存图片或将其上传到网络。 Android模拟器不会使用宿主机上的摄像头作为相机镜头,因此取景预览将总是一片空白,这是因为模拟器没有摄像头的缘故。当用户进行拍照时,系统将会使用一张已有的图片作为图片。 用户在对话框中输入图片的名称后,程序将会把拍得的图片保存到 SD 卡上。 运行该程序需要获得相机拍照的权限,因此需要在AndroidManifest.xml文件中增加如下代码片段进行授权: <!--授予程序使用摄像头的权限--> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus"/> 5.3.2录制视频短片 MediaRecorder类除了可用于录制音频之外,还可用于录制视频。使用MediaRecorder类录制视频与录制音频的步骤基本相同。只是录制视频时不仅需要采集声音,还需要采集图像。为了让MediaRecorder类录制时采集图像,应该在调用setAudioSource(int audio_source)方法时再调用setVideoSource(int video_source)方法来设置图像来源。 除此之外,还需在调用setOutputFormat()方法设置输出文件格式之后进行如下步骤。 (1) 调用 MediaRecorder 对象的 setVideoEncoder()、setVideoEncodingBitRate(int bitRate)、setVideoFrameRate()方法设置所录制的视频的编码格式、编码位率、每秒多少帧等,这些参数将可以控制所录制的视频的品质、文件的大小。一般来说,视频品质越好,视频文件越大。 (2) 调用MediaRecorder对象的setPreviewDisplay(Surface sv)方法设置使用哪个SurfaceView控件来显示视频预览。 剩下的代码则与录制音频的代码基本相同。下面的程序示范了如何录制视频,该程序的界面中提供了两个按钮用于控制开始和结束录制; 除此之外,程序界面中还提供了一个SurfaceView控件来显示视频预览。该程序的界面布局文件如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center_horizontal" > <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" > <ImageButton android:id="@+id/record" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/record" /> <ImageButton android:id="@+id/stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/stop" /> </LinearLayout> <!-- 显示视频预览的SurfaceView --> <SurfaceView android:id="@+id/sView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> 提供了上面所示的界面布局文件之后,接下来就可以在程序中使用MediaRecorder类来录制视频了。录制视频与录制音频的步骤基本相似,只是需要额外设置视频的图像来源、视频格式等,除此之外还需要设置使用SurfaceView控件显示视频预览。录制视频的程序代码如下: public class RecordVideo extends Activity implements OnClickListener { //程序中的两个按钮 ImageButton record , stop; //系统的视频文件 File videoFile ; MediaRecorder mRecorder; //显示视频预览的SurfaceView SurfaceView sView; //记录是否正在进行录制 private boolean isRecording = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //获取程序界面中的两个按钮 record = (ImageButton) findViewById(R.id.record); stop = (ImageButton) findViewById(R.id.stop); //让stop按钮不可用 stop.setEnabled(false); //为两个按钮的单击事件绑定监听器 record.setOnClickListener(this); stop.setOnClickListener(this); //获取程序界面中的SurfaceView控件 sView = (SurfaceView) this.findViewById(R.id.sView); //下面设置Surface不需要自己维护缓冲区 sView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //设置分辨率 sView.getHolder().setFixedSize(320, 280); //设置该组件让屏幕不会自动关闭 sView.getHolder().setKeepScreenOn(true); } @Override public void onClick(View source) { switch (source.getId()) { //点击record按钮 case R.id.record: if (!Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { Toast.makeText(RecordVideo.this , "SD卡不存在,请插入SD卡!" , 5000) .show(); return; } try { //创建保存录制视频的视频文件 videoFile = new File(Environment .getExternalStorageDirectory() .getCanonicalFile() + "/myvideo.mp4"); //创建MediaPlayer对象 mRecorder = new MediaRecorder(); mRecorder.reset(); //设置从麦克风采集声音 mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置从摄像头采集图像 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //设置视频文件的输出格式(必须在设置声音编码格式、 //图像编码格式之前设置) mRecorder.setOutputFormat(MediaRecorder .OutputFormat.MPEG_4); //设置声音编码的格式 mRecorder.setAudioEncoder(MediaRecorder .AudioEncoder.DEFAULT); //设置图像编码的格式 mRecorder.setVideoEncoder(MediaRecorder .VideoEncoder.MPEG_4_SP); mRecorder.setVideoSize(320, 280); //每秒4帧 mRecorder.setVideoFrameRate(4); mRecorder.setOutputFile(videoFile.getAbsolutePath()); //指定使用SurfaceView来预览视频 mRecorder.setPreviewDisplay(sView.getHolder().get Surface());//① mRecorder.prepare(); //开始录制 mRecorder.start(); System.out.println("---recording---"); //让record按钮不可用 record.setEnabled(false); //让stop按钮可用 stop.setEnabled(true); isRecording = true; } catch (Exception e) { e.printStackTrace(); } break; //点击stop按钮 case R.id.stop: //如果正在进行录制 if (isRecording) { //停止录制 mRecorder.stop(); //释放资源 mRecorder.release(); mRecorder = null; //让record按钮可用 record.setEnabled(true); //让stop按钮不可用 stop.setEnabled(false); } break; } } } 上面的程序中粗体字代码设置了视频所采集的图像来源,以及视频的压缩格式、视频分辨率等属性,程序的①号粗体字代码则用于设置使用SurfaceView控件显示指定视频预览。 运行该程序需要使用麦克风录制声音,需要使用摄像头采集图像,这些都需要授予相应的权限。不仅如此,由于该录制视频时视频文件增大得较快,可能需要使用外部存储器,因此需要对应用程序授予相应的权限。也就是需要在AndroidManifest.xml文件中增加如下授权配置: <!--授予该程序录制声音的权限--> <uses-permission android:name="android.permission,RECORD_AUDIO"/> <!--授予该程序使用摄像头的权限--> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission,MOUNT_UNMOUNT_FILESYSTEMS"/> <!--授予使用外部存储器的权限--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 当在模拟器上运行该程序时,由于模拟器上没有摄像头硬件支持,因此程序无法采集视频所需的图像,所以建议在有摄像头硬件支持的真机上运行该程序。 5.4应用实例: 视频播放器 本章前面讲解了如何通过VideoView控件播放视频的相关知识,接下来我们以图5.2所示的界面为例,讲解如何通过VideoView控件实现一个视频播放器的案例,具体步骤如下。 1. 创建程序 创建一个名为VideoView的应用程序,包名指定为cn.itcast.videoview。 2. 导入视频文件 选中res文件夹,在该文件夹中创建一个raw文件夹,将视频文件video.mp4放入raw文件夹中,如图5.3所示。 3. 放置界面控件 在activity_main.xml文件中,放置1个ImageView控件用于显示播放(暂停)按钮,1个VideoView控件用于显示视频。完整布局代码如下: 图5.2视频播放器界面 图5.3raw文件夹 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/bt_play" android:layout_width="80dp" android:layout_height="80dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="150dp" android:src="@android:drawable/ic_media_play" /> <VideoView android:id="@+id/videoview" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> 4. 编写界面交互代码 在MainActivity中创建一个play()方法,在该方法中实现视频播放的功能。具体代码如下: public class MainActivity extends AppCompatActivity implements View.OnClickListener { private VideoView videoView; private MediaController controller; ImageView iv_play; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); videoView = (VideoView) findViewById(R.id.videoview); iv_play = (ImageView) findViewById(R.id.bt_play); //拼出在资源文件夹下的视频文件路径字符串 String url = "android.resource://" + getPackageName() + "/" + R.raw.video; //字符串解析成Uri Uri uri = Uri.parse(url); //设置VideoView的播放资源 videoView.setVideoURI(uri); //VideoView绑定控制器 controller = new MediaController(this); videoView.setMediaController(controller); iv_play.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_play: play(); break; } } //播放视频 private void play() { if (videoView != null && videoView.isPlaying()) { iv_play.setImageResource(android.R.drawable.ic_media_play); videoView.stopPlayback(); return; } videoView.start(); iv_play.setImageResource(android.R.drawable.ic_media_pause); videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { iv_play.setImageResource(android.R.drawable.ic_media_play); } }); } } 上述代码中,通过setVideoURI()方法将视频文件的路径加载到VideoView控件上,并通过setMediaController()方法为VideoView控件绑定控制器,该控制器可以显示视频的播放、暂停、快进、快退和进度条等按钮。 重写了onClick()方法,在该方法中调用play()方法用于播放视频。 创建了一个play()方法,在该方法中首先通过isPlaying()方法判断当前视频是否正在播放,如果正在播放,则通过setImageResource()方法设置播放按钮的图片为ic_media_play.png,接着调用stopPlayback()方法停止播放视频,否则,调用start()方法播放视频,并设置暂停按钮的图片为ic_media_pause,png,接着通过setOnCompletionListener()方法设置VideoView控件的监听器,当视频播放完时,会调用该监听器中的onCompletion()方法,在该方法中将播放按钮的图片设置为ic_media_play.png。 运行结果如图5.4和图5.5所示。 图5.4视频播放器运行结果1 图5.5视频播放器运行结果2 5.5小结 本章主要讲解了音频、视频的播放过程以及音频的录制过程,同时介绍了使用手机的摄像头进行视频的录制过程。音频和视频都是非常重要的多媒体形式,Android 系统为音频、视频等多媒体的播放、录制提供了强大的支持。通过本章学习,需要重点掌握如何使用 MediaPlayer类播放音频,如何使用 VideoView控件播放视频。除此之外,还需要掌握通过 MediaRecorder类录制音频的方法,以及控制摄像头拍照、录制视频的方法。通过本章知识的学习,希望读者能够开发一些跟音频和视频相关的软件。 习题5 1. 简述使用MediaPlayer播放音频的步骤。 2. 简述MediaRecorder录制音频的步骤。 3. 请实现一个简单的音乐播放器,至少包含播放、暂停、上一曲、下一曲按钮以及显示播放时长的进度条。