您现在的位置是:首页 >其他 >Android 原生播放音频有哪些方式网站首页其他

Android 原生播放音频有哪些方式

Lud_ 2024-06-07 12:00:03
简介Android 原生播放音频有哪些方式

之前一片文章简单讲述了通过TTS和Audio相关API控制文本输出语音的方法,接下来学习一下其他原生方式的语音播放功能。

使用 MediaPlayer

  • 项目中除了对需要播放文本有需求以外,有一些特殊固定音频的提示音,比如超速提醒等功能。这个功能我们使用的是通过MediaPlayer播放媒体文件的方式来提醒用户超速。主要代码如下
// 定义MediaPlayer
private var mediaPlayer: MediaPlayer?
// 初始化 MediaPlayer 传入了需要播报的音频文件 这是一个 .wav类型文件 (就是无损压缩格式,缺点文件大 优点兼容性好)
// 也可也通过方法传入新的音频文件
private fun initMediaPlayer(){
	mediaPlayer = MediaPlayer.create(TruckerPathApplication.appContext, R.raw.xxx)
}
  • 播放提示音
    /**
     * 播放提示音
     */
    private fun playSpeedWarningRing() {
        try {
        	// 上一篇讲过这个是获取音频焦点,使得提示音更突出,其他声音略微缩小
            audioFocusManager.requestAudioFocus()
            // 判断是否正在播放
            if (mediaPlayer?.isPlaying != true) {
         	    // 开始播放
                mediaPlayer?.start()
            }
        } catch (e: IllegalStateException) {
            if (BuildConfig.DEBUG) {
                e.printStackTrace()
            }
        } finally {
        	// 一定最后要释放音频焦点
            audioFocusManager.abandonAudioFocus()
        }
    }
  • 释放 Media 资源
    /**
     * 释放mediaPlayer
     */
    private fun releaseMediaPlayer() {
        mediaPlayer?.release()
        mediaPlayer = null
    }

MediaPlayer特点

MediaPlayer 更适合播放较长的音频文件,例如音乐、电影和电台等。它支持多种音频格式,如 MP3、AAC、WMA 等,同时还支持多媒体的控制,如暂停、停止、重复播放等。MediaPlayer 可以在后台运行,即使应用切换到后台或锁屏状态下仍然可以继续播放,也可以通过 Notification 和 MediaSession 等类实现前台播放通知和多媒体控制。

MediaPlayer 播放音频时,它只会在内存中缓存正在播放的音频数据,而不会一次性将整个音频文件加载到内存中。当 MediaPlayer 播放完缓存中的数据时,它会自动从硬盘中读取下一部分音频数据,并逐步地进行播放。
因此,相比于 SoundPool 在内存中预加载音频资源的方式,MediaPlayer 可以更加灵活地管理内存资源,避免资源浪费。同时,MediaPlayer 也可以播放较长的音频文件,适合用于播放音乐等长时间的音频文件。
不过,需要注意的是,MediaPlayer 在播放完音频文件后,并不会自动释放内存资源,这需要开发者手动调用 MediaPlayer 的 release() 方法来释放相关资源,避免内存泄漏的问题。

关于 SoundPool

SoundPool 对于 MediaPlayer 来说有如下特点:

SoundPool 主要用于播放短促音效,例如游戏中的背景音乐、点击声和爆炸声等。
优点:

提高播放性能:内存池可以避免频繁的 I/O 操作和内存分配,从而减少 CPU 的开销,提高播放性能。
降低功耗:由于内存池可以避免频繁的 I/O 操作,因此可以降低硬件设备的功耗,从而延长电池寿命。
支持多个音效同时播放:SoundPool 可以同时播放多个音效,这对于一些需要多种音效混合的场景非常有用。
实现音效的循环播放和变速播放等特效:SoundPool 支持音效的循环播放和变速播放等特效,这对于游戏等应用场景非常有用。

缺点:

SoundPool 也存在一些缺点:
可能存在资源浪费:由于 SoundPool 在内存中预加载音频资源,因此如果音频文件较大,可能会浪费过多的内存资源。
受限于硬件设备:SoundPool 的播放性能和效果可能受限于硬件设备的音频处理能力,因此在一些低端设备上可能会出现问题。
不支持播放较长的音频文件:由于 SoundPool 音频资源需要预加载到内存中,因此不适合播放较长的音频文件。

  • 使用 SoundPool 核心代码如下
// 在 Activity 中定义 SoundPool 变量
private SoundPool soundPool;

// 在 onCreate() 方法中初始化 SoundPool
soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
// 加载音频文件
int soundId = soundPool.load(this, R.raw.sound_effect, 1);

// 在需要播放音频文件的时候调用 play() 方法
soundPool.play(soundId, 1, 1, 0, 0, 1);

  • 下面是一些 SoundPool 常用的 API:
SoundPool(int maxStreams, int streamType, int srcQuality):构造函数,用于创建 SoundPool 对象。
maxStreams:最大能同时播放的音频数量。
streamType:指定音频流的类型,如 AudioManager.STREAM_MUSIC 等。
srcQuality:指定音频质量,取值范围为 0~4,0 表示最差,4 表示最好。
load(Context context, int resId, int priority):加载音频文件。
context:应用程序上下文。
resId:音频文件的资源 ID。
priority:指定加载音频文件的优先级。
setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener):设置加载音频文件完成的监听器。
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate):播放音频文件。
soundID:音频文件的 ID。
leftVolume:左声道音量。
rightVolume:右声道音量。
priority:指定播放音频文件的优先级。
loop:指定循环播放的次数,-1 表示无限循环。
rate:指定播放速度,1 表示正常速度。
pause(int streamID):暂停指定 streamID 的音频文件播放。
resume(int streamID):恢复指定 streamID 的音频文件播放。
stop(int streamID):停止指定 streamID 的音频文件播放。
release():释放 SoundPool 对象占用的资源。
注意:SoundPool 在 Android 5.0 以上版本已经不建议使用,建议使用 SoundPool.Builder 或者 MediaPlayer 等其他方式来播放音频。

关于 AudioTrack

AudioTrack 是一个用于播放音频的类,它可以直接控制音频数据的采样率、位宽和声道数等参数,适用于需要对音频进行实时处理的场景。
相比于 MediaPlayer,AudioTrack 的优点是:

  • 实时性更高:由于直接控制音频数据的采样率等参数,AudioTrack 可以更快地响应和处理实时的音频数据;
  • 可控性更高:可以对音频数据进行更加精细的控制,例如控制播放的速率、音量等。

但是 AudioTrack 也有一些缺点,例如需要自己实现音频文件解码等功能。

  • 下面是简单的实现代码
int sampleRate = 44100;
int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);

AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
        channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM);

byte[] buffer = new byte[bufferSize];

File file = new File(Environment.getExternalStorageDirectory(), "audio.pcm");
try {
    FileInputStream inputStream = new FileInputStream(file);
    audioTrack.play();

    int length;
    while ((length = inputStream.read(buffer)) > 0) {
        audioTrack.write(buffer, 0, length);
    }

    audioTrack.stop();
    audioTrack.release();
    inputStream.close();
} catch (IOException e) {
    e.printStackTrace();
}

关于 AudioRecord

AudioRecord是Android平台上用于实现音频采集的类。它可以从系统中的音频输入源(比如麦克风)读取音频数据,并将数据存储到内存中或写入到文件中。通常,我们可以将AudioRecord与AudioTrack一起使用,实现实时的音频录制和播放。

  • 使用AudioRecord类可以实现以下功能:
    音频数据采集:可以从不同的音频输入源(比如麦克风、电话线路、蓝牙耳机等)获取音频数据。
    音频数据读取:可以将采集到的音频数据存储到内存中或写入到文件中。
    音频格式支持:支持多种音频格式(比如PCM、AAC、MP3等)的采集和处理。
    需要注意的是,使用AudioRecord需要注意以下几个方面:
    音频源的选择:需要根据实际情况选择正确的音频输入源,比如麦克风、电话线路、蓝牙耳机等。
    音频格式的设置:需要根据实际情况设置正确的音频格式(比如采样率、位深、声道数等)。
    音频缓冲区的设置:需要根据实际情况设置正确的音频缓冲区大小,以确保能够正确地读取和处理音频数据。
    总之,AudioRecord是Android平台上非常重要的音频采集类之一,它可以帮助我们实现音频数据的采集、处理和存储等功能。

  • 通过 AudioRecord 获取音频,再通过 Google Speech-to-Text API 转换成文本 示例代码如下:

private static final int RECORDER_SAMPLERATE = 16000;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

private AudioRecord audioRecorder;
private boolean isRecording = false;

// 启动录音
private void startRecording() {
    int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
    audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING, bufferSize);

    // 开启录音线程
    isRecording = true;
    new Thread(new Runnable() {
        
        public void run() {
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);

            byte[] buffer = new byte[bufferSize];
            audioRecorder.startRecording();

            while (isRecording) {
                int bytesRead = audioRecorder.read(buffer, 0, buffer.length);

                // 将录制的音频转换成文本信息
                if (bytesRead > 0) {
                    try {
                        // 将录制的音频数据传递给语音识别引擎
                        SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(MainActivity.this);
                        recognizer.setRecognitionListener(new RecognitionListener() {
                            
                            public void onResults(Bundle results) {
                                ArrayList<String> voiceResults = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                                if (voiceResults != null && voiceResults.size() > 0) {
                                    String text = voiceResults.get(0);
                                    // 处理识别结果
                                    Log.d(TAG, "识别结果:" + text);
                                }
                            }

                            // 省略其他回调方法的实现
                        });

                        Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 1500);
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 1500);

                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);

                        for (int i = 0; i < bytesRead; i++) {
                            dataOutputStream.writeShort(buffer[i]);
                        }

                        byte[] bytes = outputStream.toByteArray();
                        recognizer.startListening(recognizerIntent, new AudioRecord.AudioInputCaptureCallback() {
                            
                            public void onInputCaptured(byte[] audioData) {
                                // 将录制的音频数据传递给语音识别引擎
                                recognizer.writeAudio(audioData, 0, audioData.length);
                            }
                        });

                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName());
                        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "en-US");
                   

AudioRecord 降噪

udioRecord默认情况下是没有提供降噪功能的,因此需要通过一些额外的处理来实现降噪。常用的方法是使用数字信号处理(DSP)算法来滤除不想要的噪声,例如高通滤波器和自适应滤波器等。
在Android中,可以通过使用AudioRecord的setAudioSource方法设置声音来源,并在构造AudioRecord对象时指定降噪的配置参数。例如,可以通过设置音频来源为麦克风并启用降噪来实现降噪录音,示例代码如下:

int audioSource = MediaRecorder.AudioSource.MIC;
int sampleRate = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
NoiseSuppressor noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSizeInBytes);
if (NoiseSuppressor.isAvailable() && noiseSuppressor != null) {
    noiseSuppressor.setEnabled(true);
}

在上面的代码中,我们通过调用NoiseSuppressor.create()方法创建一个NoiseSuppressor实例,并将其应用到当前AudioRecord实例的音频会话中。另外,我们还可以通过调用NoiseSuppressor.isAvailable()方法来检查当前设备是否支持噪声抑制功能。

总结

关于音频播放:如果长音视频更适合 MediaPlayer 或者对于一些内存比较紧张的情况也适用,因为MediaPlayer 不会将整个视频读取到内存。如果是比较短的音频更适合SoundPool,性能好,使用简单,适合循环播放,因为直接从内存读取避免重复I/O操作。如果需要增加各种复杂处理 可以使用 AudioTrack ,缺点是需要自己做解码。如果需要录音等功能则可以使用 AudioRecord ,但是还需要一些降噪处理。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。