bill最近在倒腾Android上录制声音的API——AudioRecord。但始终得到初始化失败的异常(最终原因是各大android设备所支持的音频采样率、声道数等不尽相同,虽然google官方说44100Hz是所有设备均支持的,但实际上......),甚是恼火。在stackoverflow上找到了灵感,并最终在Michael-Pardo的博客里发现了一个简洁且设计较好的工具类——AudioMeter。

        Pardo对类的描述是AudioMeter包裹了AudioRecord,但bill在阅读后发现AudioMeter实际上隐藏了AudioRecord的接口(如read),当然Pardo的软件可能并不需要read接口,但作为AudioRecord的倒腾者,bill是需要的,因此对AudioMeter做了轻量级修改,开放了AudioRecord的部分常用接口,使用起来也比较方便,现分享出来,希望更多的朋友受益于此,大家可以根据自己的需要完善、扩充这个工具类:

 

  1. import java.nio.ByteBuffer; 
  2.  
  3. import android.media.AudioFormat; 
  4. import android.media.AudioRecord; 
  5. import android.media.MediaRecorder.AudioSource; 
  6. import android.os.Process; 
  7.  
  8. /** 
  9.  * Basically what this class does is construct a valid AudioRecord Object, wrap AudioRecord methods, and provide 
  10.  * conversion to decibels. It also caches the AudioRecord configuration and prevents multiple instances of the recorder. 
  11.  *  
  12.  * @author Michael-Pardo/billhoo 
  13.  * @version 0.1 2013-2-18 
  14.  */ 
  15. // TODO(billhoo) the AudioMeter.createAudioRecord() method always returned the first matched configuration currently, 
  16. // if the first combination [8000Hz + PCM16 + IN_MONO] is valid, method returns, but there is no way if I wanna use 
  17. // 16000Hz instead, so this method should be changed to a clever one in the future. 
  18. public class AudioMeter extends Thread { 
  19.  
  20.     // /////////////////////////////////////////////////////////////// 
  21.     // PRIVATE CONSTANTS 
  22.  
  23.     private static final float MAX_REPORTABLE_AMP = 32767f; 
  24.     private static final float MAX_REPORTABLE_DB  = 90.3087f; 
  25.  
  26.     // /////////////////////////////////////////////////////////////// 
  27.     // PRIVATE MEMBERS 
  28.  
  29.     private AudioRecord        mAudioRecord; 
  30.     private int                mSampleRate; 
  31.     private short              mAudioFormat; 
  32.     private short              mChannelConfig; 
  33.  
  34.     private short[]            mBuffer; 
  35.     private int                mBufferSize        = AudioRecord.ERROR_BAD_VALUE; 
  36.  
  37.     private int                mLocks             = 0
  38.  
  39.     // /////////////////////////////////////////////////////////////// 
  40.     // CONSTRUCTOR 
  41.  
  42.     private AudioMeter() { 
  43.         Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); // set the AudioMeter thread in URGENT_AUDIO 
  44.                                                                          // priority. 
  45.         createAudioRecord(); 
  46.     } 
  47.  
  48.     // /////////////////////////////////////////////////////////////// 
  49.     // PUBLIC METHODS 
  50.  
  51.     public static AudioMeter getInstance() { 
  52.         return InstanceHolder.INSTANCE; 
  53.     } 
  54.  
  55.     public float getAmplitude() { 
  56.         return (float) (MAX_REPORTABLE_DB + (20 * Math.log10(getRawAmplitude() 
  57.                 / MAX_REPORTABLE_AMP))); 
  58.     } 
  59.  
  60.     public synchronized void startRecording() { 
  61.         if (mAudioRecord == null 
  62.                 || mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { 
  63.             throw new IllegalStateException( 
  64.                     "startRecording() called on an uninitialized AudioRecord."); 
  65.         } 
  66.  
  67.         if (mLocks == 0) { 
  68.             mAudioRecord.startRecording(); 
  69.         } 
  70.  
  71.         mLocks++; 
  72.     } 
  73.  
  74.     public synchronized void stopRecording() { 
  75.         mLocks--; 
  76.  
  77.         if (mLocks == 0) { 
  78.             if (mAudioRecord != null) { 
  79.                 mAudioRecord.stop(); 
  80.                 mAudioRecord.release(); 
  81.                 mAudioRecord = null
  82.             } 
  83.         } 
  84.     } 
  85.  
  86.     /** 
  87.      * Reads audio data from the audio hardware for recording into a direct buffer. If this buffer is not a direct 
  88.      * buffer, this method will always return 0. 
  89.      *  
  90.      * @param audioBuffer 
  91.      *            the direct buffer to which the recorded audio data is written. 
  92.      * @param sizeInBytes 
  93.      *            the number of requested bytes. 
  94.      * @return the number of bytes that were read or or {@link #ERROR_INVALID_OPERATION} if the object wasn't properly 
  95.      *         initialized, or {@link #ERROR_BAD_VALUE} if the parameters don't resolve to valid data and indexes. The 
  96.      *         number of bytes will not exceed sizeInBytes. 
  97.      */ 
  98.     public int read(ByteBuffer audioBuffer, int sizeInBytes) { 
  99.         return mAudioRecord.read(audioBuffer, sizeInBytes); 
  100.     } 
  101.  
  102.     /** 
  103.      * Reads audio data from the audio hardware for recording into a buffer. 
  104.      *  
  105.      * @param audioData 
  106.      *            the array to which the recorded audio data is written. 
  107.      * @param offsetInShorts 
  108.      *            index in audioData from which the data is written expressed in shorts. 
  109.      * @param sizeInShorts 
  110.      *            the number of requested shorts. 
  111.      * @return the number of shorts that were read or or {@link #ERROR_INVALID_OPERATION} if the object wasn't properly 
  112.      *         initialized, or {@link #ERROR_BAD_VALUE} if the parameters don't resolve to valid data and indexes. The 
  113.      *         number of shorts will not exceed sizeInShorts. 
  114.      */ 
  115.     public int read(short[] audioData, int offsetInShorts, int sizeInShorts) { 
  116.         return mAudioRecord.read(audioData, offsetInShorts, sizeInShorts); 
  117.     } 
  118.  
  119.     /** 
  120.      * Reads audio data from the audio hardware for recording into a buffer. 
  121.      *  
  122.      * @param audioData 
  123.      *            the array to which the recorded audio data is written. 
  124.      * @param offsetInBytes 
  125.      *            index in audioData from which the data is written expressed in bytes. 
  126.      * @param sizeInBytes 
  127.      *            the number of requested bytes. 
  128.      * @return the number of bytes that were read or or {@link #ERROR_INVALID_OPERATION} if the object wasn't properly 
  129.      *         initialized, or {@link #ERROR_BAD_VALUE} if the parameters don't resolve to valid data and indexes. The 
  130.      *         number of bytes will not exceed sizeInBytes. 
  131.      */ 
  132.     public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) { 
  133.         return mAudioRecord.read(audioData, offsetInBytes, sizeInBytes); 
  134.     } 
  135.  
  136.     /** 
  137.      * Returns the configured audio data sample rate in Hz 
  138.      */ 
  139.     public int getSampleRate() { 
  140.         return mSampleRate; 
  141.     } 
  142.  
  143.     /** 
  144.      * Returns the configured audio data format. See {@link AudioFormat#ENCODING_PCM_16BIT} and 
  145.      * {@link AudioFormat#ENCODING_PCM_8BIT}. 
  146.      */ 
  147.     public int getAudioFormat() { 
  148.         return mAudioFormat; 
  149.     } 
  150.  
  151.     /** 
  152.      * Returns the configured channel configuration. See {@link AudioFormat#CHANNEL_IN_MONO} and 
  153.      * {@link AudioFormat#CHANNEL_IN_STEREO}. 
  154.      */ 
  155.     public int getChannelConfiguration() { 
  156.         return mChannelConfig; 
  157.     } 
  158.  
  159.     /** 
  160.      * Returns the state of the AudioRecord instance. This is useful after the AudioRecord instance has been created to 
  161.      * check if it was initialized properly. This ensures that the appropriate hardware resources have been acquired. 
  162.      *  
  163.      * @see AudioRecord#STATE_INITIALIZED 
  164.      * @see AudioRecord#STATE_UNINITIALIZED 
  165.      */ 
  166.     public int getAudioRecordState() { 
  167.         return mAudioRecord.getState(); 
  168.     } 
  169.  
  170.     // /////////////////////////////////////////////////////////////// 
  171.     // PRIVATE METHODS 
  172.  
  173.     private void createAudioRecord() { 
  174.         if (mSampleRate > 0 && mAudioFormat > 0 && mChannelConfig > 0) { 
  175.             mAudioRecord = new AudioRecord(AudioSource.MIC, mSampleRate, 
  176.                     mChannelConfig, mAudioFormat, mBufferSize); 
  177.             return
  178.         } 
  179.  
  180.         // TODO(billhoo) should try user's specific combinations first, if it's invalid, then do for loop to get a 
  181.         // available combination instead. 
  182.  
  183.         // Find best/compatible AudioRecord 
  184.         // If all combinations are invalid, throw IllegalStateException 
  185.         for (int sampleRate : new int[] { 800011025160002205032000
  186.                 441004725048000 }) { 
  187.             for (short audioFormat : new short[] { 
  188.                     AudioFormat.ENCODING_PCM_16BIT, 
  189.                     AudioFormat.ENCODING_PCM_8BIT }) { 
  190.                 for (short channelConfig : new short[] { 
  191.                         AudioFormat.CHANNEL_IN_MONO, 
  192.                         AudioFormat.CHANNEL_IN_STEREO, 
  193.                         AudioFormat.CHANNEL_CONFIGURATION_MONO, 
  194.                         AudioFormat.CHANNEL_CONFIGURATION_STEREO }) { 
  195.  
  196.                     // Try to initialize 
  197.                     try { 
  198.                         mBufferSize = AudioRecord.getMinBufferSize(sampleRate, 
  199.                                 channelConfig, audioFormat); 
  200.  
  201.                         if (mBufferSize < 0) { 
  202.                             continue
  203.                         } 
  204.  
  205.                         mBuffer = new short[mBufferSize]; 
  206.                         mAudioRecord = new AudioRecord(AudioSource.MIC, 
  207.                                 sampleRate, channelConfig, audioFormat, 
  208.                                 mBufferSize); 
  209.  
  210.                         if (mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED) { 
  211.                             mSampleRate = sampleRate; 
  212.                             mAudioFormat = audioFormat; 
  213.                             mChannelConfig = channelConfig; 
  214.                             return
  215.                         } 
  216.  
  217.                         mAudioRecord.release(); 
  218.                         mAudioRecord = null
  219.                     } catch (Exception e) { 
  220.                         // Do nothing 
  221.                     } 
  222.                 } 
  223.             } 
  224.         } 
  225.  
  226.         // ADDED(billhoo) all combinations are failed on this device. 
  227.         throw new IllegalStateException( 
  228.                 "getInstance() failed : no suitable audio configurations on this device."); 
  229.     } 
  230.  
  231.     private int getRawAmplitude() { 
  232.         if (mAudioRecord == null) { 
  233.             createAudioRecord(); 
  234.         } 
  235.  
  236.         final int bufferReadSize = mAudioRecord.read(mBuffer, 0, mBufferSize); 
  237.  
  238.         if (bufferReadSize < 0) { 
  239.             return 0
  240.         } 
  241.  
  242.         int sum = 0
  243.         for (int i = 0; i < bufferReadSize; i++) { 
  244.             sum += Math.abs(mBuffer[i]); 
  245.         } 
  246.  
  247.         if (bufferReadSize > 0) { 
  248.             return sum / bufferReadSize; 
  249.         } 
  250.  
  251.         return 0
  252.     } 
  253.  
  254.     // /////////////////////////////////////////////////////////////// 
  255.     // PRIVATE CLASSES 
  256.  
  257.     // Singleton 
  258.     private static class InstanceHolder { 
  259.         private static final AudioMeter INSTANCE = new AudioMeter(); 
  260.     }