Android 音视频开发

AudioRecord 和AudioTrack 以及PCM转Wa

2018-02-28  本文已影响0人  超威蓝猫l

使用AudioRecord 和 AudioTrack 可以录制以及播放pcm格式得音频 可以用于实时语音等

以下是封装得一些代码

录制得代码

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by l on 2018/2/28.
 */

public class AudioRecordUtils {

    private int bufferSize;
    private boolean isRecording;
    private DataOutputStream dos;
    private AudioRecord audioRecord;
    private int sampleRateInHz = 44100;

    private AudioRecordUtils() {
        //设置音频的录制声道,CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
        bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
    }

    private static AudioRecordUtils mInstance;

    public static AudioRecordUtils getInstance() {
        if (mInstance == null) {
            synchronized (AudioRecordUtils.class) {
                if (mInstance == null) {
                    mInstance = new AudioRecordUtils();
                }
            }
        }
        return mInstance;
    }

    class RecordRunnable implements Runnable {

        private String originFilePath = null;
        private String wavFilePath = null;

        public RecordRunnable() {

        }

        public RecordRunnable(String originFilePath, String wavFilePath) {

            this.originFilePath = originFilePath;
            this.wavFilePath = wavFilePath;
        }

        @Override
        public void run() {
            isRecording = true;
            try {
                byte[] buffer = new byte[bufferSize];
                audioRecord.startRecording();
                while (isRecording) {
                    int read = audioRecord.read(buffer, 0, buffer.length);
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
//                        for (int i = 0; i < read; i++) {
//                            dos.writeShort(buffer[i]);
//                        }
                        dos.write(buffer);
                    }
                }
                audioRecord.stop();
                dos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (wavFilePath != null) {
                copyWaveFile(originFilePath, wavFilePath);
            }
        }
    }

    ;

    private void setFilePath(String path) {
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
        try {
            file.createNewFile();
            dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
    任何一种文件在头部添加相应的头文件才能够确定的表示这种文件的格式,wave是RIFF文件结构,每一部分为一个chunk,其中有RIFF WAVE chunk,
    FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以选择的,
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate,
                                     int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);//数据大小
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';//WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        //FMT Chunk
        header[12] = 'f'; // 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';//过渡字节
        //数据大小
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        //编码方式 10H为PCM编码格式
        header[20] = 1; // format = 1
        header[21] = 0;
        //通道数
        header[22] = (byte) channels;
        header[23] = 0;
        //采样率,每个通道的播放速度
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        //音频数据传送速率,采样率*通道数*采样深度/8
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
        header[32] = (byte) (1 * 16 / 8);
        header[33] = 0;
        //每个样本的数据位数
        header[34] = 16;
        header[35] = 0;
        //Data chunk
        header[36] = 'd';//data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }


    private void copyWaveFile(String inFileName, String outFileName) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long longSampleRate = sampleRateInHz;
        long totalDataLen = totalAudioLen + 36;
        int channels = 1;//你录制是单声道就是1 双声道就是2(如果错了声音可能会急促等)
        long byteRate = 16 * longSampleRate * channels / 8;

        byte[] data = new byte[bufferSize];
        try {
            in = new FileInputStream(inFileName);
            out = new FileOutputStream(outFileName);

            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }

            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void startRecord(String path) {
        if (isRecording) {
            return;
        }
        setFilePath(path);
        startThread();
    }

    public void startRecord(String path, String wavFile) {
        if (isRecording) {
            return;
        }
        setFilePath(path);
        startThread(path, wavFile);
    }

    private void startThread() {
        Thread recordThread = new Thread(new RecordRunnable());
        recordThread.start();
    }

    private void startThread(String originFilePath, String wavFilePath) {
        Thread recordThread = new Thread(new RecordRunnable(originFilePath, wavFilePath));
        recordThread.start();
    }


    public void stopRecord() {
        isRecording = false;
    }
}

播放得代码

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * Created by l on 2018/2/28.
 */

public class AudioTrackUtils {
    private static AudioTrackUtils mInstance;
    private boolean isPlaying;
    private int bufferSize;
    private final AudioTrack audioTrack;
    private DataInputStream dis;

    private AudioTrackUtils() {
        bufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);

    }

    public static AudioTrackUtils getInstance() {
        if (mInstance == null) {
            synchronized (AudioTrackUtils.class) {
                if (mInstance == null) {
                    mInstance = new AudioTrackUtils();
                }
            }
        }
        return mInstance;
    }

    public void play(String filepath) {
        File file = new File(filepath);
        if (!file.exists()) {
            return;
        }
        try {
            dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        new Thread(new PlayThread()).start();
    }

    public void stop() {
        isPlaying = false;
    }

    private class PlayThread implements Runnable {
        @Override
        public void run() {
            isPlaying = true;

            byte[] buffer = new byte[bufferSize / 4];

            try {
                audioTrack.play();

                while (isPlaying && dis.available() > 0) {
//                    int i = 0;
//                    while (dis.available() > 0 && i < buffer.length) {
//                        buffer[i] = dis.readShort();
//                        i++;
//                    }
                    int read = dis.read(buffer);
                    if (read == AudioTrack.ERROR_INVALID_OPERATION || read == AudioTrack.ERROR_BAD_VALUE) {
                        continue;
                    }

                    audioTrack.write(buffer, 0, read);
                }

                audioTrack.stop();
                dis.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

一些需要注意得点:

  1. 之前录制和播放都用得short[] 进行读写 如果仅仅是播放和录制时没有问题得 但是pcm格式装wav格式得时候 会变成噪音
  2. pcm转wav得时候 注意参数(比如录制用单声道 转换也要用单声道)

解决方式:
用byte[] 方式进行读取即可

如果想用short得话 转换过来即可(参考:http://blog.csdn.net/VikingMei/article/details/8349661)

dos.writeShort(Short.reverseBytes(audioBuffer[i]));  

DataOutputStream.java

/**
     * Writes a <code>short</code> to the underlying output stream as two
     * bytes, high byte first. If no exception is thrown, the counter
     * <code>written</code> is incremented by <code>2</code>.
     *
     * @param      v   a <code>short</code> to be written.
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FilterOutputStream#out
     */
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }
上一篇 下一篇

猜你喜欢

热点阅读