zoukankan      html  css  js  c++  java
  • Android 音视频开发(三):使用 AudioTrack 播放PCM音频

    一、AudioTrack 基本使用

    AudioTrack 类可以完成Android平台上音频数据的输出任务。AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对应的是数据加载模式和音频流类型, 对应着两种完全不同的使用场景。

    • MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。
    • MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

    1.1 MODE_STATIC模式

    MODE_STATIC模式输出音频的方式如下(注意:如果采用STATIC模式,须先调用write写数据,然后再调用play。):

    public class AudioTrackPlayerDemoActivity extends Activity implements
            OnClickListener {
    
        private static final String TAG = "AudioTrackPlayerDemoActivity";
        private Button button;
        private byte[] audioData;
        private AudioTrack audioTrack;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            super.setContentView(R.layout.main);
            this.button = (Button) super.findViewById(R.id.play);
            this.button.setOnClickListener(this);
            this.button.setEnabled(false);
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                    try {
                        InputStream in = getResources().openRawResource(R.raw.ding);
                        try {
                            ByteArrayOutputStream out = new ByteArrayOutputStream(
                                    264848);
                            for (int b; (b = in.read()) != -1;) {
                                out.write(b);
                            }
                            Log.d(TAG, "Got the data");
                            audioData = out.toByteArray();
                        } finally {
                            in.close();
                        }
                    } catch (IOException e) {
                        Log.wtf(TAG, "Failed to read", e);
                    }
                    return null;
                }
    
                @Override
                protected void onPostExecute(Void v) {
                    Log.d(TAG, "Creating track...");
                    button.setEnabled(true);
                    Log.d(TAG, "Enabled button");
                }
            }.execute();
        }
    
        public void onClick(View view) {
            this.button.setEnabled(false);
            this.releaseAudioTrack();
            this.audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                    AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                    audioData.length, AudioTrack.MODE_STATIC);
            Log.d(TAG, "Writing audio data...");
            this.audioTrack.write(audioData, 0, audioData.length);
            Log.d(TAG, "Starting playback");
            audioTrack.play();
            Log.d(TAG, "Playing");
            this.button.setEnabled(true);
        }
    
        private void releaseAudioTrack() {
            if (this.audioTrack != null) {
                Log.d(TAG, "Stopping");
                audioTrack.stop();
                Log.d(TAG, "Releasing");
                audioTrack.release();
                Log.d(TAG, "Nulling");
            }
        }
    
        public void onPause() {
            super.onPause();
            this.releaseAudioTrack();
        }
    }

    1.2 MODE_STREAM模式

    MODE_STREAM 模式输出音频的方式如下:

    byte[] tempBuffer = new byte[bufferSize];
    int readCount = 0;
    while (dis.available() > 0) {
        readCount = dis.read(tempBuffer);
        if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
            continue;
        }
        if (readCount != 0 && readCount != -1) {
            audioTrack.play();
            audioTrack.write(tempBuffer, 0, readCount);
        }
    } 

    二、AudioTrack 详解

     2.1  音频流的类型

    在AudioTrack构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。它的含义与Android系统对音频流的管理和分类有关。

    Android将系统的声音分为好几种流类型,下面是几个常见的:

    ·  STREAM_ALARM:警告声

    ·  STREAM_MUSIC:音乐声,例如music等

    ·  STREAM_RING:铃声

    ·  STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等

    ·  STREAM_VOCIE_CALL:通话声

    注意:上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。音频流类型的划分和Audio系统对音频的管理策略有关。

    2.2 Buffer分配和Frame的概念

    在计算Buffer分配的大小的时候,我们经常用到的一个方法就是:getMinBufferSize。这个函数决定了应用层分配多大的数据Buffer。

    AudioTrack.getMinBufferSize(8000,//每秒8K个采样点                              
            AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道                  
            AudioFormat.ENCODING_PCM_16BIT);

    从AudioTrack.getMinBufferSize开始追溯代码,可以发现在底层的代码中有一个很重要的概念:Frame(帧)。Frame是一个单位,用来描述数据量的多少。1单位的Frame等于1个采样点的字节数×声道数(比如PCM16,双声道的1个Frame等于2×2=4字节)。1个采样点只针对一个声道,而实际上可能会有一或多个声道。由于不能用一个独立的单位来表示全部声道一次采样的数据量,也就引出了Frame的概念。Frame的大小,就是一个采样点的字节数×声道数。另外,在目前的声卡驱动程序中,其内部缓冲区也是采用Frame作为单位来分配和管理的。

    下面是追溯到的native层的方法:

     // minBufCount表示缓冲区的最少个数,它以Frame作为单位
       uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate);
        if(minBufCount < 2) minBufCount = 2;//至少要两个缓冲
     
       //计算最小帧个数
       uint32_tminFrameCount =               
             (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
      //下面根据最小的FrameCount计算最小的缓冲大小   
       intminBuffSize = minFrameCount //计算方法完全符合我们前面关于Frame的介绍
               * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
               * nbChannels;
     
        returnminBuffSize;

    getMinBufSize会综合考虑硬件的情况(诸如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。

    2.3 AudioTrack构造过程

    每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流。 

    三、 AudioTrack 与 MediaPlayer 的对比

    播放声音可以用MediaPlayer和AudioTrack,两者都提供了Java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的。

    3.1 区别

    其中最大的区别是MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。而AudioTrack只能播放已经解码的PCM流,如果对比支持的文件格式的话则是AudioTrack只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只能播放不需要解码的wav文件。

    3.2 联系

    MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以是MediaPlayer包含了AudioTrack。

    3.3 SoundPool

    在接触Android音频播放API的时候,发现SoundPool也可以用于播放音频。下面是三者的使用场景:MediaPlayer 更加适合在后台长时间播放本地音乐文件或者在线的流式资源; SoundPool 则适合播放比较短的音频片段,比如游戏声音、按键声、铃声片段等等,它可以同时播放多个音频; 而 AudioTrack 则更接近底层,提供了非常强大的控制能力,支持低延迟播放,适合流媒体和VoIP语音电话等场景。

    四、源码 

    https://github.com/renhui/AudioDemo 

  • 相关阅读:
    Codeforces Beta Round #92 (Div. 2 Only) B. Permutations 模拟
    POJ 3281 Dining 最大流 Dinic算法
    POJ 2441 Arrange the BUlls 状压DP
    URAL 1152 Faise Mirrors 状压DP 简单题
    URAL 1039 Anniversary Party 树形DP 水题
    URAL 1018 Binary Apple Tree 树形DP 好题 经典
    pytorch中的forward前向传播机制
    .data()与.detach()的区别
    Argparse模块
    pytorch代码调试工具
  • 原文地址:https://www.cnblogs.com/renhui/p/7463287.html
Copyright © 2011-2022 走看看