• Android中播放音乐的几种方式


    前言

    前几天一直在研究RxJava2,也写了记录了几篇博客,但因为工作任务原因,需要研究音频相关的知识,暂时放下Rxjava,本文的demo中,MediaPalyer 部分使用RxJava编写一点逻辑,其中涉及,RxJava2的被压、解除订阅等知识点,虽然简单,最起码没有丢了RxJava,后续Rxjava会继续研究,做记录.

    andorid提供了对声音和视频处理的api包android.media.本文编写了针对这几种方式播放的Demo,文章最后贴出。

    一、MediaPlayer播放音频

    对于android音频的播放,这个类可能是大家最熟悉的了,从入门就一直想编写一个自己的音乐播放器,有木有?MediaPlayer确实强大,提供了对音频播放的各种控制,生命周期:

     


    这里写图片描述

    都很熟悉了,讲几个重点,其余不啰嗦了

    1. MediaPlayer支持:AAC、AMR、FLAC、MP3、MIDI、OGG、PCM等格式 
    2. 播放Raw下的元数据

    //直接创建,不需要设置setDataSource
    mMediaPlayer=MediaPlayer.create(this, R.raw.audio);
    mMediaPlayer.start();

    3. MediaPlayer设置播放源的4中方式

    • setDataSource (String path)
    //从sd卡中加载音乐
    mMediaPlayer.setDataSource("../music/samsara.mp3") ;
    //从网路加载音乐
    mMediaPlayer.setDataSource("http://..../xxx.mp3") ;
    //需使用异步缓冲
    mMediaPlayer.prepareAsync() ;
    • setDataSource (FileDescriptor fd)
      //需将资源文件放在assets文件夹
       AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
       mMediaPlayer.setDataSource(fd)
       mMediaPlayer.prepare() ;
       
       Ps:此方法系统需大于等于android 
    • setDataSource (Context context, Uri uri)
      这个方法没什么好说的,一般通过ContentProvider获取Android系统提供
      的共享music获取uri,然后设置数据播放
    • setDataSource (FileDescriptor fd, long offset, long length)
      //需将资源文件放在assets文件夹
       AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
       mMediaPlayer.setDataSource(fd, fd.getStartOffset(), fd.getLegth())
       mMediaPlayer.prepare() ;

      4. 注意点

    • 设置完数据源,不要忘记prepare(),尽量使用异步prepareAync(),这样不会阻塞UI线程。
    • 播放完毕即使释放资源 

      mediaPlayer.stop(); 
      mediaPlayer.release(); 
      mediaPlayer = null; 
    • 不足

      资源占用量较高、延迟时间较长、不支持多个音频同时播放等

    • ### MeidaPlayer demo片段
    //创建播放时间格式化工具
    mFormat = new SimpleDateFormat("mm:ss");
    //创建MediaPlayer和设置监听
    mPlayer = new MediaPlayer() ;
    mSeekBar.setOnSeekBarChangeListener(new MySeekBarChangeListener());
    mPlayer.setOnPreparedListener(new MyOnPrepareListener());
    mPlayer.setOnCompletionListener(new MyOnCompletionListener());
     
     
    /**
        * 从assets资源文件夹中播放
         * @param name
         */
        private void playSoundFromA(String name) {
            if (mPlayer.isPlaying()) {
                mPlayer.stop();
            }
            // 设置当前播放歌曲的名字
            title.setText(names[current]);
            mPlayer.reset();
            AssetFileDescriptor afd = getAssetFileDescriptor(name);
            try {
                mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                hasResource = true;
                mPlayer.prepareAsync();
            } catch (IOException e) {
                e.printStackTrace();
            }
     
     
        }

    二、SoundPool播放音频

    SoundPool支持多个音频文件同时播放(组合音频也是有上限的),延时短,比较适合短促、密集的场景,是游戏开发中音效播放的福音。

    SoundPool实例化方式

    1. new SoundPool(适用与5.0以下)
    SoundPool(int maxStreams, int streamType, int srcQuality)
    从android5.0开始此方法被标记为过时,稍微说以下几个参数。
    1.maxStreams :允许同时播放的流的最大值
     
    2.streamType :音频流的类型描述,
                   在Audiomanager中有种类型声明,游戏应用通常会使用流媒体音乐。
    3. srcQuality:采样率转化质量
    2. SoundPool.Builder(从5.0开始支持)
    //设置描述音频流信息的属性
    AudioAttributes abs = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build() ;
    SoundPool mSoundPoll =  new SoundPool.Builder()
                    .setMaxStreams(100)   //设置允许同时播放的流的最大值
                    .setAudioAttributes(abs)   //完全可以设置为null
                    .build() ;
    3. 几个重要的方法
    // 几个load方法和上文提到的MediaPlayer基本一致,不做多的解释
    //------------------------------------------------------------
     
    int load(AssetFileDescriptor afd, int priority)
     
    int load(Context context, int resId, int priority)
     
    int load(String path, int priority)
     
    int load(FileDescriptor fd, long offset, long length, int priority)
     
    //-------------------------------------------------------------
     
    // 通过流id暂停播放
    final void pause(int streamID)
     
    // 播放声音,soundID:音频id; left/rightVolume:左右声道(默认1,1);loop:循环次数(-1无限循环);rate:播放速率(1为标准)
    final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
     
    //释放资源(很重要)
    final void release()
     
    //恢复播放
    final void resume(int streamID)
     
    //设置指定id的音频循环播放次数
    final void setLoop(int streamID, int loop)
     
    //设置加载监听(因为加载是异步的,需要监听加载,完成后再播放)
    void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener)
     
    //设置优先级(同时播放个数超过最大值时,优先级低的先被移除)
    final void setPriority(int streamID, int priority)
     
    //设置指定音频的播放速率,0.5~2.0(rate>1:加快播放,反之慢速播放)
    final void setRate(int streamID, float rate)
     
    //停止指定音频播放
    final void stop(int streamID)
     
    //卸载指定音频
    final boolean unload(int soundID)
     
    //暂停所有音频的播放
    final void autoPause()
     
    //恢复所有暂停的音频播放
    final void autoResum()

    以上方法基本上是SoundPool的所有方法了,也都很常用。

    4. 区分2个概念

    看了Sounpool的api,是不是感觉对 streamID 和 soundID 一脸懵逼? 
    1. soundID:加载音乐资源时的返回值,int load(String path, int priority),这个int返回值就是soundID 
    2. streamID:播放时返回的值,即play()方法的返回值

    这两个值都很重要,需要缓存下来

    5. SoundPool Demo片段
    注:我把SoundPool做了简单封装,SoundPoolUtil,会在文末上传,
    有兴趣可下载看一下,时间比较急,还有很多不足的地方
     
    //初始化SoundPool
    if(Build.VERSION.SDK_INT >=  Build.VERSION_CODES.LOLLIPOP){
                AudioAttributes aab = new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .build() ;
                mSoundPool = new SoundPool.Builder()
                        .setMaxStreams(10)
                        .setAudioAttributes(aab)
                        .build() ;
            }else{
                mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ;
            }
            mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ;
            //设置资源加载监听
            mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());
     
    //加载资源
    /**
         * 加载指定路径列表的资源
         * @param map
         */
        public void loadR(Map<String, String> map){
            Set<Map.Entry<String, String>> entries = map.entrySet();
            for(Map.Entry<String, String> entry : entries){
                String key = entry.getKey() ;
                if(checkSoundPool()){
                    if(!idCache.containsKey(key)){
                        idCache.put(key, mSoundPool.load(entry.getValue(),1)) ;
                    }
                }
            }
        }
     
        /**
         * 播放指定音频,并返用于停止、暂停、恢复的StreamId
         * @param name
         * @param times
         * @return
         */
        public int play(String name, int times){
            return this.play(name,1,1,1,times,1) ;
        }

    三、AudioTrack播放音频

    AudioTrack属于更偏底层的音频播放,MediaPlayerService的内部就是使用了AudioTrack。
     
    AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有:精炼、高效的优点。
    更适合实时产生播放数据的情况,如加密的音频,
    MediaPlayer是束手无策的,AudioTrack却可以。
     
    AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,
    如果需要播放其它格式音频,需要响应的解码器,
    这也是AudioTrack用的比较少的原因,需要自己解码音频。

    AudioTreack的2种播放模式

    静态模式—static

    静态的言下之意就是数据一次性交付给接收方。好处是简单高效,只需要进行一次操作就完成了数据的传递;缺点当然也很明显,对于数据量较大的音频回放,显然它是无法胜任的,因而通常只用于播放铃声、系统提醒等对内存小的操作

    流模式streaming流模式和网络上播放视频是类似的,即数据是按照一定规律不断地传递给接收方的。理论上它可用于任何音频播放的场景,不过我们一般在以下情况下采用:

    • 音频文件过大

    • 音频属性要求高,比如采样率高、深度大的数据

    • 音频数据是实时产生的,这种情况就只能用流模式了
    通过write(byte[], int, int), write(short[], int, int)
    write(float[], int, int, int)等方法推送解码数据到AudioTrack

    使用Demo

    private void jetPlayStream(){
            new Thread(new Runnable() {
                @RequiresApi(api = Build.VERSION_CODES.M)
                @Override
                public void run() {
                    // 获取最小缓冲区
                    int bufSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
                    // 实例化AudioTrack(设置缓冲区为最小缓冲区的2倍,至少要等于最小缓冲区)
                    AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO,
                            AudioFormat.ENCODING_PCM_16BIT, bufSize*2, AudioTrack.MODE_STREAM);
                    // 设置音量
                    audioTrack.setVolume(2f) ;
                    // 设置播放频率
                    audioTrack.setPlaybackRate(10) ;
                    audioTrack.play();
                    // 获取音乐文件输入流
                    InputStream is = getResources().openRawResource(R.raw.zbc);
                    byte[] buffer = new byte[bufSize*2] ;
                    int len ;
                    try {
                        while((len=is.read(buffer,0,buffer.length)) != -1){
                            System.out.println("读取数据中...");
                            // 将读取的数据,写入Audiotrack
                            audioTrack.write(buffer,0,buffer.length) ;
                        }
                        is.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

    更深入的研究,请参考以下博客 
    http://blog.csdn.net/edmond999/article/details/18600323 
    http://blog.csdn.net/conowen/article/details/7799155/

    四、AsyncPlayer播放音频

    1.介绍

    从名字就可看出AsyncPlayer属于异步播放器,官方给出的说明是:所有工作都在子线程进行,不影响调用线程任何操作。

    AsyncPlayer就是对MediaPlayer的一次简单的封装,对MediaPlaer所有的操作都在新开线程中执行。

    AsyncPlayer只适合简单的异步播放,不能控制进度,只能开始或停止播放。如果播放在此调用play()方法,AsyncPlayer会停止当前播放,开始新的播放。

    播放源码

     
    /**
    *内部线程类
    **/
    private final class Thread extends java.lang.Thread {
     
            public void run() {
                while (true) {
                    Command cmd = null;
                    synchronized (mCmdQueue) {
     
                        //取出链表中新加入的cmd
                        cmd = mCmdQueue.removeFirst();
                    }
     
                    switch (cmd.code) {
                    case PLAY:
                        if (mDebug) Log.d(mTag, "PLAY");
     
                        //调用MediaPlayer播放
                        startSound(cmd);
                        break;
     
                    }
                    ....
                }
            }
        }
     
     private void startSound(Command cmd) {
            // Preparing can be slow, so if there is something else
            // is playing, let it continue until we're done, so 
                if (mDebug) Log.d(mTag, "Starting playback");
                MediaPlayer player = new MediaPlayer();
                player.setAudioStreamType(cmd.stream);
                player.setDataSource(cmd.context, cmd.uri);
                player.setLooping(cmd.looping);
                player.prepare();
                player.start();
                ....
     
        }

    2.简单demo

    JetPlayer播放音频

    Jet是由OHA联盟成员SONiVOX开发的一个交互音乐引擎。其包括两部分:JET播放器和JET引擎。JET常用于控制游戏的声音特效,采用MIDI(Musical Instrument Digital Interface)格式。

    ps:以后遇到手机中的”.jet”文件,就只到它究竟是什么东东了。。。

    • 获取实例
      //获取JetPlayer播放器
      JetPlayer mJet = JetPlayer.getJetPlayer() ;
    • 几个重要方法
      // 清空分段队列,并清除所有要进行播放的剪辑。
      1. boolean clearQueue()  //每次播放前,记得做一次清空操作
       
      // 加载jet文件的方法
      2. boolean loadJetFile(String path)
         boolean loadJetFile(AssetFileDescriptor afd)
       
      // 开始播放
      3. boolean play()
       
      // 暂停播放
      4. boolean pause()
       
      // 释放资源
      5. void release()
       
      // 指定jet队列的播放序列(调用play()前需要调用此方法)
      6. boolean queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)

    JetPlayer Demo

    private void jetPlayer(){
            // 获取JetPlayer播放器
            JetPlayer mJet = JetPlayer.getJetPlayer() ;
            //清空播放队列
            mJet.clearQueue() ;
            //绑定事件监听
            mJet.setEventListener(new JetPlayer.OnJetEventListener() {
                //播放次数记录
                int playNum = 1 ;
                @Override
                public void onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value) {
                    Log.i(TAG,"----->onJetEvent") ;
                }
     
                @Override
                public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
                    Log.i(TAG,"----->onJetUserIdUpdate") ;
                }
     
                @Override
                public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
                    Log.i(TAG,"----->onJetNumQueuedSegmentUpdate") ;
                }
     
                @Override
                public void onJetPauseUpdate(JetPlayer player, int paused) {
                    Log.i(TAG,"----->onJetPauseUpdate") ;
                    if(playNum == 2){
                        playNum = -1 ;
                        //释放资源,并关闭jet文件
                        player.release();
                        player.closeJetFile() ;
                    }else{
                        playNum++ ;
                    }
                }
            });
            //加载资源
            mJet.loadJetFile(getResources().openRawResourceFd(R.raw.level1)) ;
            byte sSegmentID = 0 ;
            //指定播放序列
            mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID);
            mJet.queueJetSegment(1, 0, 1, 0, 0, sSegmentID);
            //开始播放
            mJet.play() ;
        }

    五、Ringtone

    Ringtone为铃声、通知和其他类似声音提供快速播放的方法,这里还不得不提到一个管理类”RingtoneManager”,提供系统铃声列表检索方法,并且,Ringtone实例需要从RingtoneManager获取。

    1. 获取实例

    获取实例方法,均为RingtoneManager类提供
     
    //通过铃声uri获取
    static Ringtone getRingtone(Context context, Uri ringtoneUri)
     
    //通过铃声检索位置获取
    Ringtone getRingtone(int position)

    其实,Rington这个类比较简单,只需要掌握,播放、停止(paly(),stop())等方法就可以了,而RingtoneManager却是比较重要的。

    2. RingtoneManager几个钟要的方法

    1. // 两个构造方法
    RingtoneManager(Activity activity)
    RingtoneManager(Context context)
     
    2. // 获取指定声音类型(铃声、通知、闹铃等)的默认声音的Uri
    static Uri getDefaultUri(int type)
     
    3. // 获取系统所有Ringtone的cursor
    Cursor getCursor()
     
    4. // 获取cursor指定位置的Ringtone uri
    Uri getRingtoneUri(int position)
     
    5. // 判断指定Uri是否为默认铃声
    static boolean isDefault(Uri ringtoneUri)
     
    6. //获取指定uri的所属类型
    static int getDefaultType(Uri defaultRingtoneUri)
     
    7. //将指定Uri设置为指定声音类型的默认声音
    static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)

    从api看,Ringtone和RingtoneManager还是比较简单的,不多做解释了,直接放上一段使用代码。

    /**
     * 播放来电铃声的默认音乐
    */
    private void playRingtoneDefault(){
        Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) ;
        Ringtone mRingtone = RingtoneManager.getRingtone(this,uri);
        mRingtone.play();
    }
     
     
    /**
    * 随机播放一个Ringtone(有可能是提示音、铃声等)
    */
    private void ShufflePlayback(){
        RingtoneManager manager = new RingtoneManager(this) ;
        Cursor cursor = manager.getCursor();
        int count = cursor.getCount() ;
        int position = (int)(Math.random()*count) ;
        Ringtone mRingtone = manager.getRingtone(position) ;
        mRingtone.play();
        }
     
        //记得添加下面两个权限
        <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    总结

    以上介绍了几种播放方式,可以说各有优劣。 
    本文写的不够详细,只是大概的介绍一下几种播放方式。在此做下简单的总结。

    1. 对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合
    2. 声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool
    3. 对于简单的播放,不需要复杂控制的播放,可以给使用AsyncPlayer,所有操作均在子线程不阻塞UI
    4. 播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,却播放延迟较小。 
      ps:据我测试AudioTrack直接支持WAV和PCM,其他音频需要解码成PCM格式才能播放。(其他无损格式没有尝试,有兴趣可以使本文提供的例子测试一下)
    5. .jet的音频比较少见(有的游戏中在使用),可使用专门的播放器JetPlayer播放
    6. 对于系统类声音的播放和操作,Ringtone更适合(主要是掌握好RingtoneManager)

    android.media包中提供的播放音频的方式,远不止这些,本文只是参考api和其他大牛的博客做一些研究和记录,android.media种还有很多只是等着我们探索……

  • 相关阅读:
    spring读取properties配置文件
    WxCpMessageRouter SpringContextHolder
    Maven和Spring mvc下的页面的跳转与取值
    struts2里UI页面标签
    Struts2中集合收集表单数据
    struts2的集合标签
    Struts2的通用标签(数据标签和控制标签)
    多个XML文件的包含与继承关系
    当出现异常时跳转到指定页面
    关于Struts2的动态URL和动态参数
  • 原文地址:https://www.cnblogs.com/Im-Victor/p/11451299.html
Copyright © 2020-2023  润新知