咖啡拿铁 發表於 2023-4-26 14:46:00

Android音频开发之AudioTrack

<blockquote>
<p>原文地址 www.jianshu.com</p>
</blockquote>
<p>在前两节中分享了Android音频开发之音频基本概念和Android音频开发之音频采集,本文分享的是如何使用 AudioTrack 来播放 使用AudioRecord 采集后的 PCM 数据。</p>
<ol>
<li>构造 AudioTrack 实例</li>
</ol>
<hr>
<pre><code>public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
</code></pre>
<blockquote>
<p>在采样 <code>pcm</code> 音频数据需要设置对应的采样率,采样精度,采样的通道数和采样的缓冲区大小,如果播放的音频是使用 <code>AudioRecord</code> 录制的,那么这些参数配置信息需要和 <code>AudioRerord</code>一致,不然播放就会出现奇怪的问题。</p>
</blockquote>
<h3 id="11-audiotrack-播放音频时会有两种方式">1.1 AudioTrack 播放音频时会有两种方式:</h3>
<blockquote>
<p>音频播放的方式,有两种方式 MODE_STATIC 或者 MODE_STREAM 。</p>
</blockquote>
<ul>
<li>MODE_STATIC 预先将需要播放的音频数据读取到内存中,然后才开始播放。</li>
<li>MODE_STREAM 边读边播,不会将数据直接加载到内存</li>
</ul>
<h3 id="12-mode_stream-的方式构建-audiotrack-实例">1.2 MODE_STREAM 的方式构建 AudioTrack 实例</h3>
<pre><code>/**
* 构建 AudioTrack 实例对象
*/
private void createStreamModeAudioTrack() {
    if (audioTrack == null) {
      bufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
      audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
    }
}
</code></pre>
<h3 id="13-mode_static-的方式构建-audiotrack-实例">1.3 MODE_STATIC 的方式构建 AudioTrack 实例</h3>
<pre><code>/**
* 构建 AudioTrack 实例对象
*/
//file 就是需要播放的音频文件,这里的buffersize就是文件的大小
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
      44100, AudioFormat.CHANNEL_OUT_STEREO,
      AudioFormat.ENCODING_PCM_16BIT, (int) file.length(), AudioTrack.MODE_STATIC);
</code></pre>
<ol start="2">
<li>写入数据</li>
</ol>
<hr>
<blockquote>
<p>不间断通过 <code>write</code> 方法的写数据给 AudioTrack .</p>
</blockquote>
<p><strong>注意:</strong> 对于 MODE_STREAM 写入数据,会阻塞,直到写入的数据都传输给 AudioTrack。<br>
对于 MODE_STATIC 会将数据拷贝到缓冲区中,并在该方法返回后执行 play() 方法播放音频数据。</p>
<ul>
<li>int write (byte[] audioData, int offsetInBytes,int sizeInBytes)</li>
<li>int write (short[] audioData, int offsetInShorts, int sizeInShorts)</li>
<li>int write (float[] audioData, int offsetInFloats, int sizeInFloats,int writeMode)</li>
</ul>
<p>该方法的返回值:</p>
<ul>
<li>
<p>正确:&gt;=0 该值表示写入的数据量。</p>
</li>
<li>
<p>错误:&lt;0</p>
<ul>
<li>ERROR_INVALID_OPERATION</li>
<li>ERROR_BAD_VALUE</li>
<li>ERROR_DEAD_OBJECT</li>
<li>ERROR</li>
</ul>
</li>
</ul>
<h3 id="21-两种方式写入数据的区别">2.1 两种方式写入数据的区别</h3>
<ul>
<li>MODE_STATIC</li>
</ul>
<p>在 AudioTrack 创建之处,会初始化一个与其相关联的 buffer 缓冲区,这个缓冲区的大小是在构造方法指定的。这个大小表示 AudioTrack 可以播放多久。对于 <code>MODE_STATIC</code> 这种模式下,这个 buffer 的大小就是需要播放的文件或者流的大小。</p>
<pre><code>//写入数据大小 array 就是预先将音频数据加载到array数组中
int writeResult = audioTrack.write(array, 0, array.length);
//检查写入的结果,如果是异常情况,则直接需要释放资源
if (writeResult == AudioTrack.ERROR_INVALID_OPERATION || writeResult == AudioTrack.ERROR_BAD_VALUE
      || writeResult == AudioTrack.ERROR_DEAD_OBJECT || writeResult == AudioTrack.ERROR) {
    //出异常情况
    isPlaying = false;
    release();
    return;
}
</code></pre>
<ul>
<li>MODE_STREAM</li>
</ul>
<p>使用这种方式是通过将数据写入到缓冲区中,而需要注意写入到这个缓冲区的数据大小,需要确保小于或者等于这个构造 AudioTrack 的缓冲区大小。</p>
<p>AudioTrack 不是 final 类型,也就是说可以使用继承实现自己的功能,但是官方文档表示不建议这样做。</p>
<pre><code> //边读边播
byte[] buffer = new byte;
while (fis.available() &gt; 0) {
   int readCount = fis.read(buffer);
   if (readCount == -1) {
         Log.e(TAG, "没有更多数据可以读取了");
         break;
   }
   int writeResult = audioTrack.write(buffer, 0, readCount);
   if (writeResult &gt;= 0) {
         //success
   } else {
         //fail
         //丢掉这一块数据
         continue;
   }
}
</code></pre>
<p>这个缓冲区大小可以通过 AudioTrack.getMinBufferSize 来获取</p>
<pre><code>bufferSize = AudioTrack.getMinBufferSize(44000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
</code></pre>
<ol start="3">
<li>状态判断</li>
</ol>
<hr>
<h3 id="31-audiotrack-状态判断">3.1 AudioTrack 状态判断</h3>
<blockquote>
<p>检测一个已经创建好的 AudioTrack 的状态,确保操作在正确初始化之后进行。当需要进行播放前,校验 AudioTrack 是否处于正确的状态。</p>
</blockquote>
<pre><code>int getState ()
</code></pre>
<p>返回值介绍:</p>
<ul>
<li>STATE_INITIALIZED 表示 AudioTrack 已经是可以使用了。</li>
<li>STATE_UNINITIALIZED 表示 AudioTrack 创建时没有成功地初始化。</li>
<li>STATE_NO_STATIC_DATA 表示当前是使用 MODE_STATIC ,但是还没往缓冲区中写入数据。当接收数据之后会变为 STATE_INITIALIZED 状态。</li>
</ul>
<pre><code>//播放时,状态校验
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
    Log.e(TAG, "不能播放,当前播放器未处于初始化状态..");
    return;
}
</code></pre>
<h3 id="32-audiotrack-播放状态">3.2 AudioTrack 播放状态</h3>
<pre><code>int getPlayState()
</code></pre>
<ul>
<li>PLAYSTATE_STOPPED 停止</li>
<li>PLAYSTATE_PAUSED 暂停</li>
<li>PLAYSTATE_PLAYING 正在播放</li>
</ul>
<ol start="4">
<li>播放 play</li>
</ol>
<hr>
<ul>
<li>对于 MODE_STATIC 模式,必须要调用 <code>write(...)</code> 相关方法将数据写入到对应的缓冲区中,然后才可以调用 <code>paly(...)</code> 方法进行播放操作。</li>
</ul>
<pre><code>//先将所有的数据写入到缓冲区
write(...)
//然后在播放
play(..)
</code></pre>
<ul>
<li>对于 MODE_STREAM 模式</li>
</ul>
<pre><code>paly(...)

new Thread() {
    public void run() {
      //一系列的 write 操作
      `write(...)`
    }
   
}.start();
</code></pre>
<ol start="5">
<li>AudioTrack 状态</li>
</ol>
<hr>
<h3 id="51-停止播放">5.1 停止播放</h3>
<blockquote>
<p>对于 <code>MODE_STREAM</code> 模式,如果单是调用 stop 方法, AudioTrack 会等待缓冲的最后一帧数据播放完毕之后,才会停止,如果需要立即停止,那么就需要调用 <code>pause</code> 然后调用 <code>flush</code> 这两个方法,那么 AudioTrack 就是丢缓冲区中剩余的数据。</p>
</blockquote>
<pre><code>void stop ()
</code></pre>
<h3 id="52-暂停">5.2 暂停</h3>
<blockquote>
<p>暂停播放,但是缓冲区中没有被播放的数据不会被舍弃,调用 play 方法即可接着播放,</p>
</blockquote>
<pre><code>void pause ()
</code></pre>
<h3 id="53-刷新">5.3 刷新</h3>
<blockquote>
<p>刷新正在排队播放的音频数据,调用该方法会将写入到缓冲区但没有被播放的音频数据都会被丢弃。如果是非 STREAM 或者没有执行 pasuse 或者 stop 将不会有任何效果。</p>
</blockquote>
<pre><code>void flush()
</code></pre>
<h3 id="54-释放">5.4 释放</h3>
<blockquote>
<p>释放本地 AudioTrack 对象。</p>
</blockquote>
<pre><code>void release ()
</code></pre>
<p>示例代码</p>
<pre><code>public void stop() {
    if ((audioTrack != null) &amp;&amp; (audioTrack.getState() == AudioTrack.STATE_INITIALIZED)) {
      if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED) {
            audioTrack.flush();
            audioTrack.stop();
      }
}
</code></pre>
<ol start="6">
<li>AudioTrack 和 MediaPlayer 的区别?</li>
</ol>
<hr>
<ul>
<li>
<p>AudioTrack 只能播放 pcm 原始数据,不能播放视频。</p>
</li>
<li>
<p>MediaPlayer 可以播放视频和音频。</p>
</li>
<li>
<p>AudioTrack 只支持 pcm 原始音频数据。</p>
</li>
<li>
<p>MediaPlayer 支持 mp3,wav,aac...</p>
</li>
<li>
<p>MediaPlayer 在底层会创建指定的格式的解码器,将音频数据转化为 pcm 然后再交给 pcm 去播放。MediaPlayer底层会创建 AudioTrack,将解码后的数据交给 AudioTrack 播放。</p>
</li>
<li>
<p>每一个音频流对应着一个AudioTrack类的一个实例,<br>
每个AudioTrack会在创建时注册到 AudioFlinger中,<br>
由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流。</p>
</li>
</ul>
<ol start="7">
<li>参考文档</li>
</ol>
<hr>
<ul>
<li>https://juejin.im/entry/5a1cda475188254a701ec89b 深入理解Android音频框架AudioTrack到AudioFlinger及Mix过程。</li>
<li>https://www.ktanx.com/blog/p/2408 Android音频: 如何使用AudioTrack播放一个WAV格式文件?</li>
</ul><br><br>
来源:https://www.cnblogs.com/cps666/p/17356055.html
頁: [1]
查看完整版本: Android音频开发之AudioTrack