[TOC]
文章转自:https://mp.weixin.qq.com/s?__biz=MzU5NjkxMjE5Mg==&mid=2247483715&idx=1&sn=dbf44d72a1b5384b7f7bd984ae37b24f&chksm=fe5a306cc92db97a0ec8279f9605f7ff7e8a9bcaf945ec5f921731a9a9386794ea618452f56e&cur_album_id=1555170733142622209&scene=189#wechat_redirect
目录
- AudioTrack和MediaPlayer
- AudioTrack的API介绍(构造、操作、状态机)
- 具体实现(Static和Stream两种模式)
- 遇到的问题
- 收获
Android SDK 中提供了三种播放声音的API,常见的是MediaPlayer和AudioTrack
其中AudioTrack管理、播放单一音频资源。可以将PCM音频数据传输到音频接收器,以供播放,只能播放源码流即PCM,wav封装格式的音频也可以用AudioTrack播放,但是wav头部分在播放解析时会发出噪音。而MediaPlayer可以播放多种格式的音频文件,比如 mp3 aac等,因为MediaPlayer会在framework层创建对应的音频解码器。
既然MediaPlayer可以播放那么多的音频格式,为什么我们还要学习AudioTrack呐?
首先MediaPlayer在framwork层还是会创建AudioTrack,把解码后的PCM流传递给AudioTrack,再传递给AudioFliger进行混音播放。
每一个音频流对应一个AudioTrack,AudioTrack会在创建时注册到AudioFlinger中,AudioFlinger把所有的AudioTrack进行混合Mixer,然后输送到AudioHardware进行播放。Android最多可以同时创建32个音频流。在短视频编辑等应用领域,对视频进行添加配乐进行编辑,需要把视频中的音轨和配乐中的音轨进行解码PCM进行混合再编码。再或者我们在“剪映”等视频编辑app可以添加多个音轨,就想Audition一样强大。这些都都需要我们对AudioTrack有一定的了解掌握。
二、AudioTrack的介绍
我们先简单看下AudioTrack提供了哪些API
2.1. 构造方法
1 2
| public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
|
其中采样率sampleRateInHz、声道数channelConfig、音频格式audioFormat以及音频缓冲区大小bufferSizeInBytes 这四个概念和上一篇《AudioRecord录制PCM音频》中的介绍的AudioRecord的构造方法的参数意义以及获取方式基本一致。下面我们看下另外两个参数streamType以及mode
streamType音频流的类型,有如下几种:
AudioManager#STREAM_VOICE_CALL:电话声音AudioManager#STREAM_SYSTEM:系统声音
AudioManager#STREAM_RING:铃声
AudioManager#STREAM_MUSIC:音乐声
AudioManager#STREAM_ALARM:闹铃声
AudioManager#STREAM_NOTIFICATION:通知声
这里我们使用的是AudioManager#STREAM_MUSIC。_
下面我们重点看下mode
1 2 3 4 5 6
| @param mode streaming or static buffer. MODE_STATIC and MODE_STREAM
STATIC模式:一次性将所有的数据放到一个固定的buffer,然后直接传送给AudioTrack,简单有效,通常应用于播放铃声或者系统提示音等,占用内存较少的音频数据
STREAM模式:一次一次的将音频数据流写入到AudioTrack对象中,并持续处于阻塞状态,当数据从Java层到Native层执行播放完毕后才返回,这种方式可以避免由于音频过大导致内存占用过多。当然对应的不足就是总是在java和native层进行交互,并且阻塞直到播放完毕,效率损失较大。
|
2.2. Action 写入、播放、暂停、停止、释放
1
| write(byte audioData, int offsetInBytes, int sizeInBytes)
|
把pcm数据写入到AudioTrack对象播放、暂停、停止、释放 常规的播放Action操作。
2.3. 状态机(getState以及getPlayState)
AudioTrack中有两个state,一个是AudioTrack是否已经初始化,后续的Action操作都依赖于此,这个有点类似MediaPlayer的prepared状态,只有处于prepared状态之后才可以进行其他播放相关操作
另外一个就是playstate,用于记录判断当前处于什么播放状态。
状态的改变加速,处理多线程同步问题
1
| private final Object mPlayStateLock = new Object();
|
三、具体实现
我们在上一篇《AudioRecord录制PCM音频》中示例代码产生的pcm作为AudioTrack的数据播放源来。跟进mode不同,分别实现
3.1 STATIC模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| private void initAudioTrackParams() { sampleRateInHz = 44100; channels = AudioFormat.CHANNEL_OUT_MONO; audioFormat = AudioFormat.ENCODING_PCM_16BIT; bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channels, audioFormat);
pcmFile = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "raw.pcm"); if (pcmFile.exists()) { hasPcmFile = true; } }
private void initStaticBuff() { if (audioTrackThread != null) { audioTrackThread.interrupt(); }
audioTrackThread = new Thread(new Runnable() { @Override public void run() { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(pcmFile); long size = fileInputStream.getChannel().size(); staicBuff = new byte[(int) size];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(staicBuff.length); int byteValue = 0; long startTime = System.currentTimeMillis(); while ((byteValue = fileInputStream.read()) != -1) {
byteArrayOutputStream.write(byteValue); } Log.d(TAG, "byteArrayOutputStream write Time: " + (System.currentTimeMillis() - startTime)); staicBuff = byteArrayOutputStream.toByteArray();
isReadying = true;
} catch (IOException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } Log.d(TAG, "playWithStaicMode: end"); } } }); audioTrackThread.start();
}
private void play(byte[] staicBuff) { if (!isReadying) { Toast.makeText(this, "请稍后", Toast.LENGTH_SHORT).show(); return; }
if (isPlaying) { audioTrack.stop(); audioTrack.reloadStaticData(); Log.d(TAG, "playWithStaicMode: reloadStaticData"); audioTrack.play(); return; }
releaseAudioTrack();
int state = initAudioTrackWithMode(AudioTrack.MODE_STATIC, staicBuff.length); if (state == AudioTrack.STATE_UNINITIALIZED) { Log.e(TAG, "run: state is uninit"); return; } long startTime = System.currentTimeMillis(); int result = audioTrack.write(staicBuff, 0, staicBuff.length); Log.d(TAG, "audioTrack.write staic: result=" + result+" totaltime="+ (System.currentTimeMillis() - startTime)); audioTrack.play(); isPlaying = true; }
private void pausePlay() { if (audioTrack != null) { if (audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { audioTrack.pause(); audioTrack.flush(); } isPlaying = false; Log.d(TAG, "pausePlay: isPlaying false"); } if (audioTrackThread != null) { audioTrackThread.interrupt(); } }
private void releaseAudioTrack() { if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { audioTrack.stop(); audioTrack.release(); isPlaying = false; Log.d(TAG, "pausePlay: isPlaying false"); } if (audioTrackThread != null) { audioTrackThread.interrupt(); } }
|