第17回 音视频,一碟小菜

关羽拿着一份报纸冲进屋来,“大哥,大哥,快来看啊!”

刘备:“二弟何事慌张?”

关羽:“慌张啥,我那是激动的,黄巾教真的倒了!”

刘备:“我勒个去,我们举报这么管用?”

张飞:“大哥,这么说我们定金不用退回去啦?”

刘备:“退个屁,都被我们花光了,再有这种好事送上门就好了~”

关羽:“大哥,你做开发的态度好不端正……不过,我喜欢!”

只听门外又有人叫门,“屋……屋里有人吗?”

刘备心中一喜,“莫非又有傻子临门?”赶忙去开门,只见门外一位两米多高大汉,英俊潇洒,玉树临风。

张飞问道:“来者何人?”

大汉道:“我……我叫吕布!”

刘备:“莫非就是那人中赤兔,马中吕布的吕布?!”

吕布:“擦……擦了!”

关羽:“你来所为何事?”

吕布:“听……听说这里有人能帮忙做应用。我……我就来了!”

刘备一脸媚笑:“哟,大爷,您算来对地方了!”

孔明不知何时冒了出来,嘀咕道:“天呐,我这啥时候变妓院了……我有一种拉皮条的感觉……”

吕布:“我……我老板董卓布置我做一个音视频项目。我……我不会呀,所以找人代做,我……我好交差。”

刘备:“音视频小菜一碟,您放心好了,至于开发经费……”

吕布:“老……老板给我的钱我可以都给你们,我……我能交差就行了。”

刘备:“我……我就欣赏您这种爽快人!”

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来建立一个声音池来播放音效了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值