蒜鸟 發表於 2023-2-16 16:50:00

【android】音视频开发三:使用AudioTrack完成音频pcm的播放

<p>上一节已经学习记录了AudioRecord的相关定义以及pcm抓取和保存,这一节的主要目的是通过学习AudioTrack的相关知识,将保存的pcm数据播放出来。下面开始我们的学习之旅吧!</p>
<p><span style="font-size: 18px"><strong><span style="color: rgba(255, 0, 0, 1)">audiotrack基础定义</span></strong></span><br>定义:AudioTrack类管理和播放java应用程序的单个音频资源。它允许将pcm音频缓冲器流式传输到音频接收器进行播放。这是通过“推”的数据使用的所述一个的AudioTrack对象write(byte[], int, int) , write(short[], int, int) ,和write(float[], int, int, int)方法。<br>AudioTrack实例可以在两种模式下运行:静态或者流式传输。</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">static</span></strong><br>静态的言下之意就是数据一次性交付给接收方。好处是简单高效,只需要进行一次操作就完成了数据的传递;缺点当然也很明显,对于数据量较大的音频回放,显然它是无法胜任的,因而通常只用于播放铃声、系统提醒等对内存小的操作<br><strong><span style="color: rgba(255, 0, 0, 1)">streaming</span></strong><br>流模式和网络上播放视频是类似的,数据是按照一定规律不断地传递给接收方的。应用程序使用write()方法之一将连续的数据流写入write()。当数据从java层传输到本地层并排队等待播放时,它们会阻塞并返回。理论上它可用于任何音频播放的场景,不过我们一般在以下情况下采用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">音频文件过大
音频属性要求高,比如采样率高、深度大的数据
音频数据是实时产生的,这种情况就只能用流模式了
这种方式的缺点就是总是在java层和native层交互,效率损失较大。</span></pre>
</div>
<p>一旦创建,AudioTrack对象将初始化其中关联的音频缓冲区。在构建过程中指定的这个缓冲区大小决定了AudioTrack在耗尽数据之前可以播放多长时间。<br>对于static模式来说,此大小是可以从中播放的最大声音大小。<br>对于streaming,数据将以大小小于或者等于总缓冲区大小的块形式写入音频接收器。AudioTrack不是最终的,因此允许使用子类,但是不建议使用这种类型的子类</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">audiotrack API详解</span></strong></p>
<div class="cnblogs_code">
<pre>getMinBufferSize();</pre>
</div>
<p><br>获取的缓冲区大小只是一个估计值,因为它既不考虑线路也不考虑汇(因为它们都不知道),所以此大小不能保证在负载下顺畅播放,并且应根据预期的频率选择较高的值,在此频率下缓冲区将被重新填充额外的数据播放。例如,如果您打算将AudioTrack的源采样率动态设置为比初始源采样率更高的值, 请确保根据最高计划采样率配置缓冲区大小。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 参数说明
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> sampleRateInHz采样率将取决于路线。 对于AudioTrack,它通常是信源采样率,对于AudioRecord它通常是源采样率。
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> channelConfig //android支持双声道立体声和单声道。MONO单声道,STEREO立体声
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> audioFormat 音频数据格式:每个采样PCM 16位。 保证被设备支持。
   * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 返回要在MODE_STREAM模式下创建的AudioTrack对象所需的估计最小缓冲区大小
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> getMinBufferSize(<span style="color: rgba(0, 0, 255, 1)">int</span> sampleRateInHz, <span style="color: rgba(0, 0, 255, 1)">int</span> channelConfig, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> audioFormat) {

    }</span></pre>
</div>
<ul>
<li>构造方法</li>
</ul>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">类构造函数。</span>
    AudioTrack(<span style="color: rgba(0, 0, 255, 1)">int</span> streamType, <span style="color: rgba(0, 0, 255, 1)">int</span> sampleRateInHz, <span style="color: rgba(0, 0, 255, 1)">int</span> channelConfig, <span style="color: rgba(0, 0, 255, 1)">int</span> audioFormat, <span style="color: rgba(0, 0, 255, 1)">int</span> bufferSizeInBytes, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> mode)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">具有音频会话的类构造函数。</span>
    AudioTrack(<span style="color: rgba(0, 0, 255, 1)">int</span> streamType, <span style="color: rgba(0, 0, 255, 1)">int</span> sampleRateInHz, <span style="color: rgba(0, 0, 255, 1)">int</span> channelConfig, <span style="color: rgba(0, 0, 255, 1)">int</span> audioFormat, <span style="color: rgba(0, 0, 255, 1)">int</span> bufferSizeInBytes, <span style="color: rgba(0, 0, 255, 1)">int</span> mode, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> sessionId)
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 具有 AudioAttributes和 AudioFormat类构造 AudioFormat 。</span>
    AudioTrack(AudioAttributes attributes, AudioFormat format, <span style="color: rgba(0, 0, 255, 1)">int</span> bufferSizeInBytes, <span style="color: rgba(0, 0, 255, 1)">int</span> mode, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> sessionId)
   </span></pre>
</div>
<p>第一个参数StreamType,这个参数和Android中的AudioManager有关系,涉及到手机上的音频管理策略。</p>
<p>Android将系统的声音分为以下几类常见的(未写全):</p>
<p>l STREAM_ALARM:警告声</p>
<p>l STREAM_MUSCI:音乐声,例如music等</p>
<p>l STREAM_RING:铃声</p>
<p>l STREAM_SYSTEM:系统声音</p>
<p>l STREAM_VOCIE_CALL:电话声音</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">AudioAttributes类说明:</span></strong><br>用于封装描述有关音频流信息的属性集合,AudioAttributes取代了流类型的概念以定义音频播放的行为。 通过允许应用程序定义属性,属性允许应用程序指定比在流类型中传达的更多信息。</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">AudioFormat类简单说明:</span></strong><br>AudioFormat类用于访问许多音频格式和通道配置常量,它们例如在AudioRecord和AudioTrack中用作构造函数的单个参数。AudioFormat常量也用于mediaformat中指定媒体中常用的音频相关值。<br>AudioFormat.Builder类可用于创建AudioFormat格式类的实例。</p>
<p>在这里我们描述AudioFormat类允许您在每种情况下传达的主要概念,它们是:<br><strong><span style="color: rgba(255, 0, 0, 1)">Sample rate</span></strong><br>以Hz为AudioFormat表示, AudioFormat实例中的采样率表示您正在播放或录制的内容中每秒每个声道的音频采样数。 这不是内容呈现或制作的采样率。 例如,在采样速率为48000Hz的设备上可以播放8000Hz的媒体采样率的声音; 采样率转换由平台自动处理,不会以6倍速播放。</p>
<p>正如API的M ,采样率高达192kHz都支持AudioRecord和AudioTrack ,并根据需要进行采样速率转换。 为了提高效率并避免有损转换,建议将AudioRecord和AudioTrack的采样率与端点设备采样率相匹配,并将采样率限制为不超过48kHz,除非有特殊的设备功能才能保证更高的速率。</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">Encoding</span></strong><br>音频编码用于描述音频数据的位表示,可以是线性PCM或压缩音频,如AC3或DTS。</p>
<p>对于线性PCM,音频编码描述样本大小,8位,16位或32位,以及样本表示,整数或浮点数。<br>对于压缩音频,编码指定压缩方法,例如ENCODING_AC3和ENCODING_DTS 。 压缩音频数据通常作为字节存储在字节数组或ByteBuffer中。 当为AudioTrack指定压缩音频编码时,它会创建一个直接(非混合)音轨输出到能够解码压缩音频的端点(如HDMI)。 对于无法解码此类压缩音频的(大多数)其他端点,您需要先解码数据,通常是创建一个MediaCodec 。 或者,可以使用MediaPlayer播放压缩的音频文件或流。</p>
<p>当通过直接发送压缩音频AudioTrack ,它不需要以音频存取单元的精确倍数写入; 这与MediaCodec输入缓冲区不同。</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">Channel mask</span></strong><br>在AudioTrack和AudioRecord中使用通道掩码来描述音频帧中的采样及其排列。 它们也用于端点(例如USB音频接口,连接到耳机的DAC)以指定特定设备的允许配置。<br>从API M ,有两种类型的通道掩码:通道位置掩码和通道索引掩码。</p>
<p>紧密地与相关联 AudioFormat是一个概念 audio frame ,其用于在整个文档,以表示音频数据的最小尺寸完整的单元。<br><strong><span style="color: rgba(255, 0, 0, 1)">Audio Frame</span></strong><br>对于线性PCM,音频帧由一组同时捕获的样本组成,其计数和通道关联由channel mask给出,其样本内容由encoding指定。1单位的Frame等于1个采样点的字节数×声道数(比如PCM16,双声道的1个Frame等于2×2=4字节)。 对于压缩音频,音频帧可以交替地指代被压缩的数据字节的访问单元,其被逻辑地组合在一起用于解码和比特流访问(例如MediaCodec ),或单个字节的压缩数据(例如AudioTrack.getBufferSizeInFrames() )或线性PCM帧来自解码压缩数据(例如AudioTrack.getPlaybackHeadPosition() ),这取决于使用音频帧的上下文。</p>
<p><strong><span style="color: rgba(255, 0, 0, 1)">audiotrack使用过程</span></strong><br>声明相关的变量</p>
<div class="cnblogs_code">
<pre>   <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> AudioTrack audioTrack;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> FileInputStream fileInputStream;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> ReadDataThread readDataThread;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> DataInputStream dataInputStream;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> minBufferSize;</pre>
</div>
<p>初始化AudioTrack对象</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> init (){
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">大小只是一个估计值,因为它既不考虑线路也不考虑汇(因为它们都不知道),所以此大小不能保证在负载下顺畅播放
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">并且应根据预期的频率选择较高的值,在此频率下缓冲区将被重新填充额外的数据播放。
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">例如,如果您打算将AudioTrack的源采样率动态设置为比初始源采样率更高的值, 请确保根据最高计划采样率配置缓冲区大小。</span>
      minBufferSize = AudioTrack.getMinBufferSize(16000<span style="color: rgba(0, 0, 0, 1)">,
                AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);

      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (Build.VERSION.SDK_INT &gt;=<span style="color: rgba(0, 0, 0, 1)"> Build.VERSION_CODES.N){
            AudioAttributes attributes </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AudioAttributes.Builder()
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置内容类型为语音时使用的内容类型</span>
<span style="color: rgba(0, 0, 0, 1)">                  .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                  .setUsage(AudioAttributes.USAGE_ASSISTANT)
                  .build();
            AudioFormat audioFormat </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AudioFormat.Builder()
                  .setSampleRate(</span>16000<span style="color: rgba(0, 0, 0, 1)">)
                  .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                  .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                  .build();
            audioTrack </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AudioTrack(attributes,audioFormat, minBufferSize,AudioTrack.MODE_STREAM,
                  AudioManager.AUDIO_SESSION_ID_GENERATE);
      }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
            audioTrack </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> AudioTrack(AudioManager.STREAM_MUSIC,16000<span style="color: rgba(0, 0, 0, 1)">,AudioFormat.CHANNEL_OUT_MONO
                  ,AudioFormat.ENCODING_PCM_16BIT, minBufferSize,AudioTrack.MODE_STREAM);
      }
      mStatus </span>=<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_READY;
    }</span></pre>
</div>
<p>开始播放</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> startPlay(){
      Log.e(TAG, </span>"startPlay:audioTrack: "+audioTrack+":mStatus:"+<span style="color: rgba(0, 0, 0, 1)">mStatus );
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (audioTrack == <span style="color: rgba(0, 0, 255, 1)">null</span> || mStatus ==<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_NO_READY){
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mStatus ==<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_START){
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            fileInputStream </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> FileInputStream(Environment.getExternalStorageDirectory().getPath()+ File.separator+
                  "voiceTest/voiceTts.pcm"<span style="color: rgba(0, 0, 0, 1)">);
            dataInputStream </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DataInputStream(fileInputStream);
            readDataThread </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ReadDataThread();
            readDataThread.start();
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (FileNotFoundException e) {
            Log.e(TAG, </span>"startPlay: "+<span style="color: rgba(0, 0, 0, 1)">e.getMessage());
            e.printStackTrace();
      }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">class</span> ReadDataThread <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> Thread{
      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> run() {
            </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.run();
            mStatus </span>=<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_START;
            </span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] bytes = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> len = 0<span style="color: rgba(0, 0, 0, 1)">;
            Log.e(TAG, </span>"run: start play"<span style="color: rgba(0, 0, 0, 1)">);
            audioTrack.play();
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> write 是阻塞的方法</span>
            <span style="color: rgba(0, 0, 255, 1)">while</span> (mStatus ==<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_START) {
                </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span> ((len = dataInputStream.read(bytes)) == -1) <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
                  e.printStackTrace();
                  </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                        dataInputStream.close();
                  } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException ex) {
                        ex.printStackTrace();
                  }
                }
                audioTrack.write(bytes, </span>0<span style="color: rgba(0, 0, 0, 1)">, len);
            }
            Log.e(TAG, </span>"run: end play"<span style="color: rgba(0, 0, 0, 1)"> );
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">播放完成了</span>
            <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                dataInputStream.close();
            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
                e.printStackTrace();
            }
      }
    }</span></pre>
</div>
<p>结束播放以及释放资源</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> stopPlay() {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mStatus == AudioTrackPlayStatus.STATUS_NO_READY || mStatus ==<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_READY) {
            Log.e(TAG, </span>"stopPlay: 播放还未开始"<span style="color: rgba(0, 0, 0, 1)"> );
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            mStatus </span>=<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_STOP;
            audioTrack.stop();
            release();
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> release() {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (audioTrack != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            audioTrack.release();
            audioTrack </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (dataInputStream != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
            dataInputStream </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      mStatus </span>=<span style="color: rgba(0, 0, 0, 1)"> AudioTrackPlayStatus.STATUS_NO_READY;
    }</span></pre>
</div>
<p>这样就可以将之前保存的pcm数据通过audiotrack播放出来了<br>接下来我们即将学习----音视频开发四:使用 Camera API 进行视频的采集和预览</p>
<pre class="set-code-hide prettyprint" data-index="5"></pre>
<pre class="prettyprint" data-index="4"></pre>
<pre class="prettyprint" data-index="3"></pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<pre class="prettyprint" data-index="1"></pre><br><br>
来源:https://www.cnblogs.com/opensmarty/p/17127372.html
頁: [1]
查看完整版本: 【android】音视频开发三:使用AudioTrack完成音频pcm的播放