Android AudioRecord、AudioTrack录制播放音频详解手机开发

AudioRecord 录制PCM

AudioRecord 是 Android 提供的用于实现录音功能,录制得到无损的PCM音频数据。

从AudioRecord构造函数就可以看出:

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig,  
                   int audioFormat, int bufferSizeInBytes) 
  • audioSource 音频源,如麦克风MIC
  • sampleRateInHz 采样率,每秒采样次数,常用有8000、44100
  • channelConfig 声道,有单声道MONO和立体声STEREO
  • audioFormat 采样大小,8bit或16bit,采样大小越大,音质越好
  • bufferSizeInBytes 采集数据的缓冲区,可以通过getMinBufferSize()获得最小的buffer size
    (参考:云图网)

1.初始化

private final static int AudioSource = MediaRecorder.AudioSource.DEFAULT; 
private final static int AudioRate = 44100; 
private final static int AudioInChannel = AudioFormat.CHANNEL_IN_MONO; 
private final static int AudioOutChannel = AudioFormat.CHANNEL_OUT_MONO; 
private final static int AudioFormater = AudioFormat.ENCODING_PCM_16BIT; 
private AudioRecord mAudioRecord;      
private int recordBufferMinSize; 
private AudioTrack mAudioTrack; 

2.开始录制

private void startRecord() { 
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) !=   PackageManager.PERMISSION_GRANTED) { 
       ActivityCompat.requestPermissions(this, MICROPHONE, 2); 
       return; 
    } 
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 
      ActivityCompat.requestPermissions(this, STORAGE, 2); 
      return; 
    } 
    isRecording = true; 
    mExecutor.execute(new RecordRunnable()); // mExecutor是一个简单的线程池 
} 
RecordRunnable实现:
  • 调用 startRecording() 开始录制
  • 读取 AudioRecord音频数据:int readSize = mAudioRecord.read(bufferbytes, 0, recordBufferMinSize);
  • 往 FileOutputStream写数据:mOutputStream.write(bufferbytes, 0, readSize);
  • 关闭 AudioRcord
private class RecordRunnable implements Runnable { 
        private OutputStream mOutputStream; 
        private byte[] bufferbytes; 
        private File mFile; 
 
        public RecordRunnable() { 
            try { 
                mFile = getRecordFile(true); 
                bufferbytes = new byte[recordBufferMinSize]; 
                mOutputStream = new FileOutputStream(mFile); 
            } catch (FileNotFoundException e) { 
                e.printStackTrace(); 
            } 
        } 
 
        @Override 
        public void run() { 
            try { 
                mAudioRecord.startRecording(); 
                while(isRecording) { 
                    int readSize = mAudioRecord.read(bufferbytes, 0, recordBufferMinSize); 
                    if (readSize > 0) { 
                        mOutputStream.write(bufferbytes, 0, readSize); 
                    } 
                } 
                mOutputStream.close(); 
                View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content); 
                contentView.post(new Runnable() { 
                    @Override 
                    public void run() { 
                        Toast.makeText(RecordingActivity.this, "录制完成: " + mFile.getAbsolutePath(),Toast.LENGTH_SHORT).show(); 
                    } 
                }); 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
        } 
    }; 

3.停止录制

private void stopRecord() { 
    if (isRecording) { 
        isRecording = false; 
        mAudioRecord.stop(); 
    } 
} 

AudioTrack 播放PCM

Android中可以使用AudioTrack播放PCM,播放的流程跟AudioRecord很类似:

AudioTrack 有两种数据加载模式:MODE_STREAM 和 MODE_STATIC,

MODE_STREAM 数据加载模式,将音频数据不断写入AudioTrack中,缺点是会有延迟
MODE_STATIC 音频流类型,将音频数据一次性写入AudioTrack中,不会有延迟,适合小文件,缺点是对大文件可能内存不足,需要 先write写数据,最后再调用 play()

1 初始化

playBufferMinSize = AudioTrack.getMinBufferSize(AudioRate, AudioOutChannel, AudioFormater); 
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AudioRate, AudioOutChannel, AudioFormater, playBufferMinSize, AudioTrack.MODE_STREAM); 

####2 播放

 private void playPcm() { 
    isPlaying = true; 
    mExecutor.execute(new PlayerRunnable(PlayerMode.PCM)); 
 } 
PlayerRunnable 是同样三个流程:
  • 开始播放 mAudioTrack.play()
  • 输出数据
    int readSize = mInputStream.read(bufferbytes);
    mAudioTrack.write(bufferbytes, 0, readSize);
  • 关闭 mAudioTrack
 
public enum PlayerMode { WAV, PCM}; 
 
private class PlayerRunnable implements Runnable { 
    private InputStream mInputStream; 
    private File mFile; 
    private byte[] bufferbytes; 
    private RecordingActivity.PlayerMode playerMode; 
 
    public PlayerRunnable(RecordingActivity.PlayerMode playerMode) { 
        this.playerMode = playerMode; 
    } 
 
    @Override 
    public void run() { 
        View contentView = RecordingActivity.this.getWindow().getDecorView().findViewById(android.R.id.content); 
        try { 
            mFile = playerMode == RecordingActivity.PlayerMode.PCM ? getPlayerFile() : getWAVFile(); 
            if (mFile == null || !mFile.exists()) { 
                contentView.post(new Runnable() { 
                    @Override 
                    public void run() { 
                        Toast.makeText(RecordingActivity.this, "音频不存在", Toast.LENGTH_SHORT).show(); 
                    } 
                }); 
                return; 
            } 
            mAudioTrack.play(); 
            bufferbytes = new byte[playBufferMinSize]; 
            mInputStream = new FileInputStream(mFile); 
            if (playerMode == WAV) { 
                mInputStream.skip(44); // 去除WAV头部 
            } 
            while (mInputStream.available() > 0) { 
                int readSize = mInputStream.read(bufferbytes); 
                mAudioTrack.write(bufferbytes, 0, readSize); 
            } 
            mInputStream.close(); 
        } catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        mAudioTrack.stop(); 
        isPlaying = false; 
    } 
} 

PCM转WAV

在PCM增加WAV的头部就可以了,因为pcm 是wav 文件中音频数据的一种编码方式,事实上 wav 还有很多其它的方式编码

public class Pcm2WavUtil { 
    private int mSampleRate; 
    private int minBufferSize; 
 
    public Pcm2WavUtil(int mSampleRate, int mChannels, int mFormater) { 
        this.mSampleRate = mSampleRate; 
        //this.mChannels = mChannels; 
        this.minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannels, mFormater); 
    } 
 
    public void pcm2wav(File inFile, File outFile) { 
        FileInputStream pcmFileStream; 
        FileOutputStream wavFileStream; 
 
        byte[] buffer = new byte[minBufferSize]; 
        try { 
            wavFileStream = new FileOutputStream(inFile); 
            pcmFileStream = new FileInputStream(outFile); 
            long pcmLen = pcmFileStream.getChannel().size(); 
 
            byte[] head = wavHeader(pcmLen, 1, mSampleRate, 16); 
            wavFileStream.write(head); 
 
            while(pcmFileStream.available() > 0) { 
                int readSize = pcmFileStream.read(buffer); 
                wavFileStream.write(buffer); 
            } 
 
            pcmFileStream.close(); 
            wavFileStream.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    /** 
     * @param pcmLen pcm 数据长度 
     * @param numChannels 声道设置, mono = 1, stereo = 2 
     * @param sampleRate 采样频率 
     * @param bitPerSample 单次数据长度, 例如 8bits 
     * @return wav 头部信息 
     */ 
    public static byte[] wavHeader(long pcmLen, int numChannels, int sampleRate, int bitPerSample) { 
        byte[] header = new byte[44]; 
        // ChunkID, RIFF, 占 4bytes 
        header[0] = 'R'; 
        header[1] = 'I'; 
        header[2] = 'F'; 
        header[3] = 'F'; 
        // ChunkSize, pcmLen + 36, 占 4bytes 
        long chunkSize = pcmLen + 36; 
        header[4] = (byte) (chunkSize & 0xff); 
        header[5] = (byte) ((chunkSize >> 8) & 0xff); 
        header[6] = (byte) ((chunkSize >> 16) & 0xff); 
        header[7] = (byte) ((chunkSize >> 24) & 0xff); 
        // Format, WAVE, 占 4bytes 
        header[8] = 'W'; 
        header[9] = 'A'; 
        header[10] = 'V'; 
        header[11] = 'E'; 
        // Subchunk1ID, 'fmt', 占 4bytes 
        header[12] = 'f'; 
        header[13] = 'm'; 
        header[14] = 't'; 
        header[15] = ' '; 
        // Subchunk1Size, 16, 占 4bytes 
        header[16] = 16; 
        header[17] = 0; 
        header[18] = 0; 
        header[19] = 0; 
        // AudioFormat, pcm = 1, 占 2bytes 
        header[20] = 1; 
        header[21] = 0; 
        // NumChannels, mono = 1, stereo = 2, 占 2bytes 
        header[22] = (byte) numChannels; 
        header[23] = 0; 
        // SampleRate, 占 4bytes 
        header[24] = (byte) (sampleRate & 0xff); 
        header[25] = (byte) ((sampleRate >> 8) & 0xff); 
        header[26] = (byte) ((sampleRate >> 16) & 0xff); 
        header[27] = (byte) ((sampleRate >> 24) & 0xff); 
        // ByteRate = SampleRate * NumChannels * BitsPerSample / 8, 占 4bytes 
        long byteRate = sampleRate * numChannels * bitPerSample / 8; 
        header[28] = (byte) (byteRate & 0xff); 
        header[29] = (byte) ((byteRate >> 8) & 0xff); 
        header[30] = (byte) ((byteRate >> 16) & 0xff); 
        header[31] = (byte) ((byteRate >> 24) & 0xff); 
        // BlockAlign = NumChannels * BitsPerSample / 8, 占 2bytes 
        header[32] = (byte) (numChannels * bitPerSample / 8); 
        header[33] = 0; 
        // BitsPerSample, 占 2bytes 
        header[34] = (byte) bitPerSample; 
        header[35] = 0; 
        // Subhunk2ID, data, 占 4bytes 
        header[36] = 'd'; 
        header[37] = 'a'; 
        header[38] = 't'; 
        header[39] = 'a'; 
        // Subchunk2Size, 占 4bytes 
        header[40] = (byte) (pcmLen & 0xff); 
        header[41] = (byte) ((pcmLen >> 8) & 0xff); 
        header[42] = (byte) ((pcmLen >> 16) & 0xff); 
        header[43] = (byte) ((pcmLen >> 24) & 0xff); 
 
        return header; 
    } 
} 

AudioTrack播放PCM编码的WAV

与播放pcm的代码一致,只需要过滤掉wav的头部 mInputStream.skip(44)

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6282.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论