Android录音、试听功能实现

1.音频录制(pcm录制)

安卓中可使用AudioRecord进行音频录制,录制的结果是pcm文件,也就是音频裸数据(裸流)。可调用AudioRecord.startRecording进行录制,不过使用前需要初始化AudioRecord。

Java层构造AudioRecord,参数有音频源,采样率,声道数,采样位深,缓冲区大小(通过 AudioRecord.getMinBufferSize()计算所需的最小缓冲区大小)。参数最终会传递给 Native 层的 AudioRecord对象,并通过AudioSystem与 Android系统的音频服务 (AudioFlinger) 进行交互,申请和配置音频输入资源。初始化成功后native层的AudioRecord会拿到AudioFlinger的 Binder 代理对象,之后就是使用binder进行通信。

1.1 音频源

java 复制代码
//位于MediaRecorder类
public final class AudioSource {
        public static final int CAMCORDER = 5;
        public static final int DEFAULT = 0;
        public static final int MIC = 1;
        public static final int REMOTE_SUBMIX = 8;
        public static final int UNPROCESSED = 9;
        public static final int VOICE_CALL = 4;
        public static final int VOICE_COMMUNICATION = 7;
        public static final int VOICE_DOWNLINK = 3;
        public static final int VOICE_PERFORMANCE = 10;
        public static final int VOICE_RECOGNITION = 6;
        public static final int VOICE_UPLINK = 2;

        AudioSource() {
            throw new RuntimeException("Stub!");
        }
}
音频源 主要特点 典型应用场景 延迟 音质
**DEFAULT (0)**​ 系统默认音频源,行为可能因设备而异 通用录音,不明确要求时使用 一般 一般
**MIC (1)**​ 主麦克风,未经特殊处理 普通录音、语音备忘录 较好
**VOICE_CALL (4)**​ 通话上下行音频(双向) 录制整个通话过程(双方声音) 最低 电话音质(窄频)
**VOICE_UPLINK (2)**​ 只录制发送给对方的声音 只录自己说话,不录对方 电话音质
**VOICE_DOWNLINK (3)**​ 只录制从对方接收的声音 只录对方声音,不录自己 电话音质
**VOICE_COMMUNICATION (7)**​ VoIP 通话优化,有回声消除和降噪 微信/QQ 语音通话、视频会议 较低 语音优化
**VOICE_RECOGNITION (6)**​ 语音识别优化,清晰化人声 语音助手、语音转文字 语音清晰
**CAMCORDER (5)**​ 视频录制专用,通常指向后置麦克风 相机 App 录像,指向性更好 一般 立体声/环境声
**UNPROCESSED (9)**​ 原始、未加工的麦克风信号 专业音频处理、声学分析 最高(但含噪音)
**REMOTE_SUBMIX (8)**​ 录制系统播放的音频,不录麦克风 屏幕录制、内部音频捕获 同系统输出
**VOICE_PERFORMANCE (10)**​ 高性能低延迟语音处理 专业音乐/实时处理应用 最低 高质量语音

1.2 采样率

代表1s内的采样次数,采样率越高,越接近真实音频,但是占用空间越大,一般的高质量音乐会使用44.1KHz或者48KHz,如果只是单纯录音听个响,不要求音质,可用使用8KHz或者16KHz

1.3 声道数

即有多少个喇叭播放声音,有单声道、双声道等

java 复制代码
//对应的宏定义在AudioFormat类中   
 public static final int CHANNEL_INVALID = 0;             //无效的通道配置。
 public static final int CHANNEL_IN_BACK = 32;            //后置麦克风输入通道。
 public static final int CHANNEL_IN_BACK_PROCESSED = 512; //处理后的后置麦克风输入。
 public static final int CHANNEL_IN_DEFAULT = 1;          //默认的音频输入通道(通常是主麦克风)。
 public static final int CHANNEL_IN_FRONT = 16;           //前置麦克风输入通道。
 public static final int CHANNEL_IN_FRONT_PROCESSED = 256;//处理后的前置麦克风输入。
 public static final int CHANNEL_IN_LEFT = 4;             //左声道输入。
 public static final int CHANNEL_IN_LEFT_PROCESSED = 64;  //处理后的左声道输入。
 public static final int CHANNEL_IN_MONO = 16;            //单声道输入(通常与 CHANNEL_IN_FRONT相同值,但含义不同)。
 public static final int CHANNEL_IN_PRESSURE = 1024;      //压力传感器输入(非音频,可能是用于某些特殊传感器数据)。
 public static final int CHANNEL_IN_RIGHT = 8;            //右声道输入。
 public static final int CHANNEL_IN_RIGHT_PROCESSED = 128;//处理后的右声道输入。
 public static final int CHANNEL_IN_STEREO = 12;           //立体声输入(左 + 右即 4 + 8)。 
 public static final int CHANNEL_IN_VOICE_DNLINK = 32768;  //下行语音通道(例如通话时的听筒输入)。
 public static final int CHANNEL_IN_VOICE_UPLINK = 16384;  //上行语音通道(例如通话时的麦克风输入)。 
 public static final int CHANNEL_IN_X_AXIS = 2048;         //X轴运动传感器输入(例如加速度计)。
 public static final int CHANNEL_IN_Y_AXIS = 4096;         //Y轴运动传感器输入。
 public static final int CHANNEL_IN_Z_AXIS = 8192;         //Z轴运动传感器输入。

1.4 采样位深

一次采样数据使用多少个bit存放

java 复制代码
//宏定义位于AudioFormat中
 
  public static final int ENCODING_PCM_16BIT = 2;          //16bit
  public static final int ENCODING_PCM_24BIT_PACKED = 21;  //24bit
  public static final int ENCODING_PCM_32BIT = 22;         //32bit
  public static final int ENCODING_PCM_8BIT = 3;           //8bit
  public static final int ENCODING_PCM_FLOAT = 4;          //32位浮点数

1.5 示例代码

java 复制代码
public class MicroPhoneRecorder {
    private static String TAG = "MicroPhoneRecoder";

    private int mFrequency = 8000;
    private int mChannel = AudioFormat.CHANNEL_IN_MONO;
    private int mSampleBit = AudioFormat.ENCODING_PCM_16BIT;
    private String mSavePath;
    
    private AudioRecord mAudioRecorder;
    private byte[] mBuffers = null;
    private boolean isInit = false;
    private boolean mIsRecording = false;

    public MicroPhoneRecorder(int freq, int channel, int sampleBit, String savePath) {
        Log.d(TAG, "MicroPhoneRecorder() called with: freq = [" + freq + "]" +
                ", channel= [" + channel + "]" + ", sampleBit= [" + sampleBit + "]" + ", savePath= [" + savePath + "]");
        mFrequency = freq;
        mChannel = channel;
        mSampleBit = sampleBit;
        mSavePath = savePath;
        init();
    }

    @SuppressLint("MissingPermission")
    private void init() {
        //使用前需要获取音频录制权限  permission.RECORD_AUDIO
        if (!isInit) {
            int size = AudioRecord.getMinBufferSize(mFrequency, mChannel, mSampleBit);
            mBuffers = new byte[size * 4];
            //如果没权限,audioRecord创建会有问题,后面startRecording会抛异常
            mAudioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, mFrequency, mChannel, mSampleBit, size);
            isInit = true;
        }
    }

    public void setSavePath(String path) {
        mSavePath = path;
    }

    public void release() {
        Log.d(TAG, "recorder release");
        mAudioRecorder.release();
        mAudioRecorder = null;
    }

    public boolean StartRecord() {
        Log.d(TAG, "StartRecord");
        boolean result = false;
        if (mAudioRecorder != null) {
            try {
                mIsRecording = true;

                //读取从AudioRecord读取pcm数据写入文件
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int nDataRead = 0;
                        DataOutputStream mDataOutputStream = null;
                        //注:如果使用的文件存储目录不是app私有目录,需要申请存储权限
                        try {
                            mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(
                                    mSavePath, true)));
                        } catch (Exception e) {
                            Log.d(TAG, "mDataOutputStream create fail Exception= " + e);
                        }

                        while (mIsRecording) {
                            if (mAudioRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
                                Arrays.fill(mBuffers, (byte) 0);
                                try {
                                    nDataRead = mAudioRecorder.read(mBuffers, 0, mBuffers.length);
                                } catch (Exception e) {
                                    nDataRead = 0;
                                }
                                if (nDataRead > 0) {
                                    try {
                                        // 将采集到的PCM数据写入文件
                                        if (mDataOutputStream != null) {
                                            mDataOutputStream.write(mBuffers, 0, nDataRead);
                                        }
                                    } catch (Exception e) {
                                        Log.e(TAG, "pcm data write fail Exception= " + e);
                                    }
                                }

                            } else {
                                //AudioRecord未初始化完,等待50ms
                                try {
                                    Thread.sleep(50);
                                } catch (InterruptedException e) {
                                }
                            }
                        }

                        if (mDataOutputStream != null) {
                            try {
                                mDataOutputStream.close();
                                mDataOutputStream = null;
                            } catch (IOException e) {
                                Log.e(TAG, "mDataOutputStream close fail IOException= " + e);
                            }
                        }


                    }
                }, "recorder work thread").start();

                mAudioRecorder.startRecording();

                result = true;
                Log.d(TAG, "recording...");
            } catch (Exception e) {
                mIsRecording = false;
                Log.i(TAG, "StartRecord: Exception " + e.toString());
            }
        } else {
            Log.i(TAG, "StartRecord: mAudioRecorder == null");
        }

        return result;
    }

    public void StopRecord() {
        Log.d(TAG, "StopRecord");
        if (mAudioRecorder != null) {
            mAudioRecorder.stop();
        }
        mIsRecording = false;
    }

    public boolean isInitialized() {
        if (mAudioRecorder == null) {
            Log.i(TAG, "isInitialized: mAudioRecorder == null");
        } else {
            Log.i(TAG, "isInitialized: state " + mAudioRecorder.getState());
        }
        return mAudioRecorder != null && mAudioRecorder.getState() != AudioRecord.STATE_UNINITIALIZED;
    }

}

使用示例

java 复制代码
MicroPhoneRecorder microPhoneRecorder;


public void startRecord() { 
     //app私有目录 不需要存储权限
     File file = new File(getContext().getFilesDir(), "temp.pcm");
     if (file.exists()) {
     file.delete();
     }
    
     //记得获取录音权限
     microPhoneRecorder = new MicroPhoneRecorder(
           8000,
           AudioFormat.CHANNEL_IN_MONO,
           AudioFormat.ENCODING_PCM_16BIT,
           file.getPath()
     );
     boolean result = microPhoneRecorder.StartRecord();
     Log.d(TAG, "result = " + result);
}

public void stopRecord() {
    microPhoneRecorder.StopRecord();
}


public void releaseRecorder() {
    microPhoneRecorder.release();
}

2.音频试听(pcm播放)

安卓中可以使用AudioTrack进行pcm的播放,但是此类不支持解码,所以不能播放mp3等编码后的音频。

AudioTrack,它是Audio系统对外提供的API,MediaPlayer在播放音乐时最终用到的也是它,它是最基本的音频数据输出类。在java层构造函数需要传音频流类型,采样率,声道数,缓冲区大小,数据加载模式。

2.1 音频流类型,一般用music即可

java 复制代码
//宏定义位于AudioManager
 
 public static final int STREAM_ACCESSIBILITY = 10;
 public static final int STREAM_ALARM = 4;
 public static final int STREAM_DTMF = 8;
 public static final int STREAM_MUSIC = 3;
 public static final int STREAM_NOTIFICATION = 5;
 public static final int STREAM_RING = 2;
 public static final int STREAM_SYSTEM = 1;
 public static final int STREAM_VOICE_CALL = 0;
流类型 常量值 用途说明 音量控制 特点
STREAM_VOICE_CALL 0 语音通话 通话音量 最高优先级,通话时其他音频会被暂停/降低
STREAM_SYSTEM 1 系统声音 系统音量 按键音、锁屏音、UI反馈音
STREAM_RING 2 来电铃声 铃声音量 来电、消息铃声
STREAM_MUSIC 3 媒体播放 媒体音量 最常用,音乐、视频、游戏音效
STREAM_ALARM 4 闹钟 闹钟音量 优先级高,即使静音也会响
STREAM_NOTIFICATION 5 通知音 通知音量 短信、推送通知声音
STREAM_DTMF 8 拨号音 DTMF音量 电话拨号键盘音(0-9, *, #)
STREAM_ACCESSIBILITY 10 无障碍 媒体音量 屏幕阅读器等(API 26+)

2.2 加载模式,其中加载模式有流式和静态。

流式通过write一次次把音频数据写到AudioTrack中,也就是先写进去多少,AudioTrack就播放多少,后面再继续写继续播放;这种工作方式由于每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会引起延时;

静态在播放之前就把所有的数据通过一次write调用传递到AudioTrack的内部缓冲区中,后续就不必再传递数据了。

基于此区别,流式适合长音频、实时音频、占用内存空间小,但是延迟较高;静态适合短音频,延迟较低,占用空间较多。

java 复制代码
//宏定义位于AudioTrack类中    
public static final int MODE_STATIC = 0;  //静态
public static final int MODE_STREAM = 1;  //流式

2.3 示例代码

java 复制代码
public class PcmPlayer {
    private static String TAG = "PcmPlayer";

    private int mChannel = AudioFormat.CHANNEL_OUT_MONO;
    private int mSampleBit = AudioFormat.ENCODING_PCM_16BIT;
    private int mFrequency = 8000;
    private String mFilePath;

    private AudioTrack audioTrack;
    private int mPausePosition = 0;  //暂停的位置
    private FileInputStream fileInputStream = null;  //播放文件的输入流

    public PcmPlayer( String filePath, int frequency, int channel, int sampleBit) {
        Log.d(TAG, "PcmPlayer call with  filePath=[" + filePath + "]" +
                ", frequency=[" + frequency + "]" + ", channel=[" + channel + "]" +
                ", sampleBit=[" + sampleBit + "]");

        mFilePath = filePath;
        mFrequency = frequency;
        mChannel = channel;
        mSampleBit = sampleBit;
        init();
    }

    private void init() {
        int minBufSize = AudioTrack.getMinBufferSize(mFrequency, mChannel, mSampleBit);
        audioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                mFrequency,
                mChannel,
                mSampleBit,
                minBufSize * 4,
                AudioTrack.MODE_STREAM
        );

        //如果使用static加载模式,buffer至少要能存储整个pcm文件
    }

    //修改要播放的文件路径
    public void setFilePath(String filePath) {
        mFilePath = filePath;
    }

    //播放pcm
    public void playPcm(){
        Thread playThrad = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "playPcm");
                if (audioTrack == null) {
                    Log.d(TAG, "audioTrack == null");
                    return;
                }

                try {
                    //如果不是app私有目录,需要先获取存储权限
                    File recordFile = new File(mFilePath);
                    if (!recordFile.exists()) {
                        Log.e(TAG, "file not exist");
                        audioTrack.stop();
                        return;
                    }

                    //如果是暂停状态,不重新创建文件流,因为需要恢复播放
                    if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PAUSED) {
                        fileInputStream = new FileInputStream(recordFile);
                    }

                    byte[] buffer = new byte[1024]; // 使用合适的缓冲区大小
                    int bytesRead;

                    audioTrack.play();
                    // 读取并播放PCM数据
                    while (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING &&
                            (bytesRead = fileInputStream.read(buffer)) >= 0) {
                        audioTrack.write(buffer, 0, bytesRead); // 直接播放读取的数据
                    }

                    Log.d(TAG, "audioTrack.getPlayState()= " + audioTrack.getPlayState());

                    if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                        LogUtil.d(TAG, "正常播放完,没有被外部取消");
                        fileInputStream.close();
                        fileInputStream = null;
                        //listener.onfinished();    //如果需要在播放后执行一些逻辑,可以setListener进来
                    } else if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
                        LogUtil.d(TAG, "外部停止播放");
                        fileInputStream.close();
                        fileInputStream = null;
                        //listener.onStop();
                    } else if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
                        LogUtil.d(TAG, "外部暂停播放");
                        //listener.onPause();
                    }

                } catch (IOException e) {
                    Log.e(TAG, "FileInputStream fail  IOException= " + e);
                }
            }
        }, "pcmPlayer thread");
        playThrad.start();
    }

    //停止pcm播放,下次调用playPcm时从头开始播
    public void stopPcm() {
        Log.d(TAG, "stopPcm");
        if (audioTrack != null) {
            audioTrack.stop();
            mPausePosition = 0;
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                    fileInputStream = null;
                }
            } catch (IOException e) {
                Log.e(TAG, "fileInputStream.close fail  IOException= " + e);
            }
        }
    }

    //暂停播放pcm, 下次调用playPcm时从暂停地方继续
    public void pausePcm() {
        Log.d(TAG, "pausePcm");
        if (audioTrack != null) {
            audioTrack.pause();
            mPausePosition = audioTrack.getPlaybackHeadPosition();
            Log.d(TAG, "mPausePosition = " + mPausePosition);
        }
    }

    //释放资源
    public void releasePlayer() {
        Log.d(TAG, "releasePlayer");
        if (audioTrack != null) {
            audioTrack.stop();
            audioTrack.release();
            audioTrack = null;
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                    fileInputStream = null;
                }
            } catch (IOException e) {
                Log.e(TAG, "fileInputStream.close fail  IOException= " + e);
            }
        }
    }

    //设置播放结束的listener
//    public void setListener(Listener listener) {
//
//    }

}

使用示例

java 复制代码
PcmPlayer pcmPlayer;

//播放pcm
public void startPlayPcm() {

    //app私有目录 不需要存储权限
    File file = new File(getContext().getFilesDir(), "temp.pcm");

    pcmPlayer = new PcmPlayer(file.getPath(), 8000, AudioFormat.CHANNEL_OUT_MONO, 
        AudioFormat.ENCODING_PCM_16BIT);

    pcmPlayer.playPcm();
}

//暂停
public void pausePcm() {
    pcmPlayer.pausePcm();
}

//停止播放
public void stopPcm() {
    pcmPlayer.stopPcm();
}

//恢复播放, 如果之前是停止播放,那么这里从头开始播放
public void resumePcm() {
    pcmPlayer.playPcm();
}

//释放资源
public void releasePlayer() {
    pcmPlayer.releasePlayer();
}

3. 音频试听(mp3等编码格式播放)

播放已经编码的音频文件,需要播放器支持解码,一般使用MediaPlayer即可,MediaPlayer支持播放视频和音频,可以自动识别文件类型解码,支持网络流播放。

使用MediaPlayer需要注意状态的变化,特别是涉及暂停,恢复,重新播放,切换文件等,因为MediaPlayer使用状态机实现,状态不对的话会抛异常

3.1 示例代码

java 复制代码
public class AudioPlayer {
    private static String TAG = "AudioPlayer";
    private MediaPlayer mPlayer;
    private boolean isPaused = false;  //是否处于暂停状态
    
    private PlayStateListener listener;   //外部传入的监听器,用于将播放状态通知外部调用

    public interface PlayStateListener {
        void onPrepared();
        void onCompletion();
        void onError(int what, int extra);
    }


    public AudioPlayer() {
        mPlayer = new MediaPlayer();
        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流类型
    }

    //设置MediaPlayer播放状态的监听器
    private void initPlayerListener() {
        Log.d(TAG, "initPlayerListener");
        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        // 准备完成监听器
        mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                LogUtil.d(TAG, "准备完成,开始播放");
                mp.start(); // 准备完成后开始播放
                if (listener != null) {
                    listener.onPrepared();  //回调通知外部,已经准备完成开始播放
                }
            }
        });

        // 播放完成监听器
        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                LogUtil.d(TAG, "播放完成");
                if (listener != null) {
                    listener.onCompletion();  //通知外部播放完成
                }
            }
        });

        // 播放错误监听器
        mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                LogUtil.e(TAG, "播放发生错误");
                LogUtil.e(TAG, "MediaPlayer error: what=" + what + ", extra=" + extra);
                if (listener != null) {
                    listener.onError(what, extra);  //通知外部播放器错误
                }
                releasePlayer();
                return true; // 返回true表示错误已被处理
            }
        });
        LogUtil.d(TAG, "监听器设置完成");
    }

    //设置音频文件路径
    public void setFilePath(String filePath) {
        Log.d(TAG, "setFilePath  filePath= " + filePath);

        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        //正在播放,先停止并重置
        if (mPlayer.isPlaying()) {
            stopPlay();
            mPlayer.reset();
        }

        //如果播放的音频不在app私有目录,需要存储权限
        try {
            mPlayer.setDataSource(filePath); // 设置音频文件路径
            initPlayerListener();   //重新设置监听,setDataSource监听器会被清除
            isPaused = false;
        } catch (IOException e) {
            Log.e(TAG, "setDataSource fail IOException= " + e);
            releasePlayer();
        }

    }

    //设置回调,用于获取播放状态
    public void setPlayStateListener(PlayStateListener listener) {
        this.listener = listener;
    }


    //开始播放
    public void startPlay() {
        LogUtil.d(TAG, "startPlay");
        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        // 异步准备,防止阻塞UI线程
        mPlayer.prepareAsync();
    }


    //停止播放
    public void stopPlay() {
        LogUtil.d(TAG, "stopPlay");
        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        mPlayer.stop();
        isPaused = false;

    }

    //暂停播放
    public void pausePlay() {
        LogUtil.d(TAG, "pausePlay");
        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        mPlayer.pause();
        isPaused = true;
        Log.d(TAG, "当前播放的时间(ms) mPlayer.getCurrentPosition()= " + mPlayer.getCurrentPosition());

    }

    //恢复播放
    public void resumePlay() {
        LogUtil.d(TAG, "resumePlay");
        if (mPlayer == null) {
            Log.e(TAG, "mPlayer == null");
            return;
        }
        if (isPaused) {
            Log.d(TAG, "继续播放");
            mPlayer.start();
            isPaused = false;
        } else {
            Log.d(TAG, "不在暂停状态,重新开始播放");
            startPlay();
        }

    }

    //释放资源
    public void releasePlayer() {
        LogUtil.d(TAG, "releasePlayer");
        if (mPlayer != null) {
            mPlayer.stop();
            mPlayer.release();
            mPlayer = null;
        }
    }
    
}

使用示例

java 复制代码
AudioPlayer audioPlayer = new AudioPlayer();


public void startPlayAudio() {
    //app私有目录 不需要存储权限
    File file = new File(getContext().getFilesDir(), "sample.mp3");
    //设置播放路径        
    audioPlayer.setFilePath(file.getPath());  
    //设置播放状态监听
    audioPlayer.setPlayStateListener(new AudioPlayer.PlayStateListener() {
         @Override
         public void onPrepared() {
             //播放器准备完成,开始播放了
             Log.d(TAG, "onPrepared  播放器准备完成,开始播放了");
         }

         @Override
         public void onCompletion() {
             //已经播放完成
             Log.d(TAG, "onCompletion  播放完成");
         }

         @Override
         public void onError(int what, int extra) {
             //播放过程发生错误
             Log.e(TAG, "onError  播放错误  what= " + what + ", extra= " + extra);
         }
     });
     //播放
     audioPlayer.startPlay();
}

public void stopPlayAudio() {
    audioPlayer.stopPlay();
}

public void pasuePlayAudio() {
    audioPlayer.pausePlay();
}

public void resumePlayAudio() {
    auidoPlayer.resumePlay();
}

public void releasePlayer() {
    audioPlayer.releasePlayer();
}
相关推荐
Zhu7585 小时前
在k8s集群部署MySQL单实例,支持多个主流发行版
android·mysql·kubernetes
AFinalStone8 小时前
Android 7系统异常问题排查(四)Framework层(上)—System Server Watchdog
android·系统异常
宝杰X79 小时前
Android Room3 多平台数据库
android·数据库
huibin14785236912 小时前
热缓解学习记录
android·学习
小田的博客15 小时前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
朝阳58116 小时前
RunBeat 跑步节拍器 · 技术实现拆解
安卓
一笑的小酒馆16 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端18 小时前
七巧低代码服务端脚本使用方法
android·低代码·rxjava
18 小时前
Android 自定义 View 实战:从零打造工业级全向摇杆控件(OmniJoystickView)
android·kotlin·自定义view·摇杆控件
雨白18 小时前
深入理解 Kotlin 协程 (十一):以逸待劳,探秘 select 多路复用与并发安全策略
android·kotlin