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
    (参考:https://www.jianshu.com/p/80a140cf3d99)

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)

你可能感兴趣的:(android,音视频)