前言

本文在《音视频学习:AudioRecord的简单使用》基础上录制成PCM文件以及转换成WAV文件。

正文

在前一篇基础上,新增录制数据回调以及保存成PCM,最后把PCM文件添加WAV头文件转出WAV文件。

PS: 部分文件前面有附上代码,这里就省略

IRecordBufferListener.java

public interface IRecordBufferListener {
    void onRecordBuffer(byte buffer[]);
}

RecordUtils.java

public class RecordUtils {

    private static String TAG = MyApp.TAG + RecordUtils.class.getSimpleName();

    private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;  //麦克风
    private static final int DEFAULT_RATE = 44100;    //采样率
    private static final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO;   //双通道(左右声道)
    private static final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT;   //数据位宽16位

    private static AudioRecord mAudioRecord = null;
    private static int mMinBufferSize = 0;
    private static boolean isRecording = false;

    private static RecordThread mRecordThread = null;

    private static IRecordBufferListener mIRecordBufferListener = null;


    /**
     * start record
     */
    public static void startRecord() {
        mMinBufferSize = AudioRecord.getMinBufferSize(DEFAULT_RATE, DEFAULT_CHANNEL, DEFAULT_FORMAT);
        Log.d(TAG, "startRecord  mMinBufferSize : " + mMinBufferSize);
        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
            Log.d(TAG, "startRecord  error 1 ");
            return;
        }
        mAudioRecord = new AudioRecord(DEFAULT_SOURCE, DEFAULT_RATE, DEFAULT_CHANNEL,
                DEFAULT_FORMAT, mMinBufferSize);
        if (null == mAudioRecord || mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
            Log.d(TAG, "startRecord  error 2 ");
            return;
        }
        //启动录音
        mAudioRecord.startRecording();
        //设置录音标志位
        isRecording = true;
        //使用线程读取录音数据
        mRecordThread = new RecordThread();
        mRecordThread.start();
        return;
    }

    /**
     * stop record
     */
    public static void stopRecord() {
        Log.d(TAG, "stopRecord mAudioRecord : " + mAudioRecord);
        isRecording = false;
        //暂停录音线程
        if (null != mRecordThread) {
            try {
                mRecordThread.join();
                mRecordThread = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //停止录音
        if (null != mAudioRecord) {
            Log.d(TAG, "stopRecord getRecordingState() : " + mAudioRecord.getRecordingState());
            mAudioRecord.stop();
            mAudioRecord.release();
            mAudioRecord = null;
            Log.d(TAG, "stopRecord: ");
        }
        return;
    }


    /**
     * 录音线程
     */

    private static class RecordThread extends Thread {

        @Override
        public void run() {
            super.run();
            byte[] buffer = null;
            while (isRecording) {
                buffer = new byte[mMinBufferSize];
                int result = mAudioRecord.read(buffer, 0, buffer.length);
                Log.d(TAG, "startRecord result : " + result + " isRecording : " + isRecording);
                if (result != AudioRecord.ERROR_INVALID_OPERATION && null != mIRecordBufferListener) {
                    //录音数据回调
                    mIRecordBufferListener.onRecordBuffer(buffer);
                }
            }
        }
    }

    public static void setIRecordBufferListener(IRecordBufferListener listener) {
        mIRecordBufferListener = listener;
        return;
    }

}

Pcm2WavUtils.java

public class Pcm2WavUtils {

    private static String TAG = MyApp.TAG + Pcm2WavUtils.class.getSimpleName();

    /**
     * PCM文件转WAV文件
     *
     * @param pcmFilePath 输入PCM文件路径
     * @param wavFilePath 输出WAV文件路径
     * @param sampleRate  采样率,例如44100
     * @param channels    声道数 单声道:1或双声道:2
     * @param bitNum      采样位数,8或16
     */
    public static void convertPcm2Wav(String pcmFilePath, String wavFilePath, int sampleRate,
                                      int channels, int bitNum) {
        if (TextUtils.isEmpty(pcmFilePath) || TextUtils.isEmpty(wavFilePath)) {
            Log.d(TAG, "convertPcm2Wav null : ");
            return;
        }
        FileInputStream mFileInputStream = null;
        FileOutputStream mFileOutputStream = null;
        byte[] data = new byte[1024];

        try {
            //采样字节byte率
            long byteRate = sampleRate * channels * bitNum / 8;
            mFileInputStream = new FileInputStream(pcmFilePath);
            mFileOutputStream = new FileOutputStream(wavFilePath);
            //PCM文件大小
            long totalAudioLen = mFileInputStream.getChannel().size();
            //总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
            long totalDataLen = totalAudioLen + 36;
            writeWaveFileHeader(mFileOutputStream, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);
            int length = 0;
            while ((length = mFileInputStream.read(data)) > 0) {
                mFileOutputStream.write(data, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mFileInputStream != null) {
                try {
                    mFileInputStream.close();
                    mFileInputStream = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (mFileOutputStream != null) {
                try {
                    mFileOutputStream.close();
                    mFileOutputStream = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return;
    }


    /**
     * 输出WAV文件
     *
     * @param out           WAV输出文件流
     * @param totalAudioLen 整个音频PCM数据大小
     * @param totalDataLen  整个数据大小
     * @param sampleRate    采样率
     * @param channels      声道数
     * @param byteRate      采样字节byte率
     * @throws IOException
     */
    private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                            long totalDataLen, int sampleRate, int channels, long byteRate) throws IOException {
        if (null == out) {
            Log.d(TAG, "writeWaveFileHeader null: ");
        }
        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) (sampleRate & 0xff);
        header[25] = (byte) ((sampleRate >> 8) & 0xff);
        header[26] = (byte) ((sampleRate >> 16) & 0xff);
        header[27] = (byte) ((sampleRate >> 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) (channels * 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);
        return;
    }
}

AudioRecordActivity.java

public class AudioRecordActivity extends AppCompatActivity implements Handler.Callback, View.OnClickListener, IRecordBufferListener {

    private String TAG = MyApp.TAG + getClass().getSimpleName();

    private int bt_ids[] = new int[]{R.id.at_bt_start_recode, R.id.at_bt_stop_recode, R.id.at_bt_change_to_wav};

    private Handler mThreadHandler = null;
    private HandlerThread mHandlerThread = null;

    private final int MSG_START_RECORD = 0x1000;
    private final int MSG_STOP_RECORD = 0x1001;
    private final int MSG_UPDATE_RECORD_BUFFER = 0x1002;
    private final int MSG_PCM_TO_WAV = 0x1003;

    private String mPcmFilePath = null;
    private String mWavFilePath = null;
    private FileOutputStream mFileOutputStream = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audiorecord);
        initUI();
        initData();
        startHandler();
        PermissionUtils.requestPermission(this, 2);
        Log.d(TAG, "onCreate: ");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop: ");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        closeFileOutputStream();
        RecordUtils.setIRecordBufferListener(null);
        stopHandler();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        Log.d(TAG, "onRequestPermissionsResult requestCode : " + requestCode);
        if (requestCode == 2) {
            for (int i = 0; i < permissions.length; i++) {
                Log.d(TAG, "onRequestPermissionsResult : " + permissions[i] + " grantResults : " + grantResults[i]);
            }
        }
    }

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what) {
            case MSG_START_RECORD:
                Log.d(TAG, "MSG_START_RECORD : ");
                initFileOutputStream();
                RecordUtils.startRecord();
                break;
            case MSG_STOP_RECORD:
                Log.d(TAG, "MSG_STOP_RECORD : ");
                RecordUtils.stopRecord();
                closeFileOutputStream();
                break;
            case MSG_UPDATE_RECORD_BUFFER:
                Log.d(TAG, "MSG_UPDATE_RECORD_BUFFER");
                writePcmFile((byte[]) msg.obj);
                break;
            case MSG_PCM_TO_WAV:
                Pcm2WavUtils.convertPcm2Wav(mPcmFilePath, mWavFilePath, 44100, 2, 16);
                break;
        }
        return false;
    }

    /**
     * init ui
     */
    private void initUI() {

        for (int i = 0; i < bt_ids.length; i++) {
            findViewById(bt_ids[i]).setOnClickListener(this);
        }

        return;
    }

    private void initData() {
        mPcmFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/125la.pcm";
        mWavFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/125la.wav";
        Log.d(TAG, "initData  mPcmFilePath : " + mPcmFilePath);
        Log.d(TAG, "initData  mWavFilePath : " + mWavFilePath);
        RecordUtils.setIRecordBufferListener(this);
        return;
    }

    /**
     * start handler
     */
    private void startHandler() {
        mHandlerThread = new HandlerThread(TAG);
        mHandlerThread.start();
        mThreadHandler = new Handler(mHandlerThread.getLooper(), this);
        return;
    }

    /**
     * stop handler
     */
    private void stopHandler() {
        if (null != mThreadHandler) {
            mThreadHandler.removeCallbacksAndMessages(null);
        }
        if (null != mHandlerThread) {
            mHandlerThread.quitSafely();
        }
        return;
    }

    @Override
    public void onClick(View v) {
        Log.d(TAG, "onClick : " + v.toString());
        switch (v.getId()) {
            case R.id.at_bt_start_recode:
                mThreadHandler.sendEmptyMessage(MSG_START_RECORD);
                break;
            case R.id.at_bt_stop_recode:
                mThreadHandler.sendEmptyMessage(MSG_STOP_RECORD);
                break;
            case R.id.at_bt_change_to_wav:
                mThreadHandler.sendEmptyMessage(MSG_PCM_TO_WAV);
                break;
        }
    }

    @Override
    public void onRecordBuffer(byte[] buffer) {
        Message message = mThreadHandler.obtainMessage();
        message.obj = buffer;
        message.what = MSG_UPDATE_RECORD_BUFFER;
        mThreadHandler.sendMessage(message);
    }

    /**
     * @param buffer
     */
    private void writePcmFile(byte[] buffer) {
        Log.d(TAG, "writePcmFile : ");
        if (null != mFileOutputStream) {
            try {
                mFileOutputStream.write(buffer);
            } catch (IOException e) {
                e.printStackTrace();
                closeFileOutputStream();
            }
        }
        return;
    }

    /**
     * init FileOutputStream
     */
    private void initFileOutputStream() {
        try {
            mFileOutputStream = new FileOutputStream(mPcmFilePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return;
    }

    /**
     * close FileOutputStream
     */
    private void closeFileOutputStream() {
        if (null != mFileOutputStream) {
            try {
                mFileOutputStream.close();
                mFileOutputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return;
    }

}

参考文章

  1. Android 音视频开发(二):使用 AudioRecord 采集音频PCM并保存到文件
  2. [摘]PCM文件转WAV文件
  3. android音频编辑之音频转换PCM与WAV

 历史上的今天

  1. 2023: JNI之自定义对象使用(0条评论)
  2. 2022: [摘]Android Studio Minimum supported Gradle version is 6.5. Current version is 6.1.1(0条评论)
  3. 2021: 周国平:孤独(0条评论)
  4. 2019: 沈从文:时间(0条评论)
版权声明 1、 本站名称: 笔友城堡
2、 本站网址: https://www.biumall.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权,请留言

暂无评论

暂无评论...

随机推荐

Android设备,当旋转旋钮时布局被绿色边框问题

前言部分Android设备是存在旋钮功能,大多是发送的是KeyEvent.KEYCODE_DPAD_UP和KeyEvent.KEYCODE_DPAD_DOWN。 /** Key code constant: Directional Pad Up key. * May also ...

尼采:孩子和婚姻

兄弟,我单独问你一个问题,这个问题就如探测之铅坠,我可据此了解你的灵魂。你年轻,渴望孩子与婚姻,但我问你,你有资格渴求孩子吗?你是胜利者吗?你战胜自己了吗?你能控制感情吗?你可以主宰你的美德吗?你的渴望是动物般的需要呢,还是孤独或内心冲突的表现?我希望你拥有胜利和自由后再渴望孩子,你应当为自己的...

梁实秋:时间即生命

最令人怵目惊心的一件事,是看着钟表上的秒针一下一下的移动,每移动一下就是表示我们的寿命已经缩短了一部分。再看看墙上挂着的可以一张张撕下的日历,每天撕下一张就是表示我们的寿命又缩短了一天。因为时间即生命。没有人不爱惜他的生命,但很少人珍视他的时间。如果想在有生之年做一点什么事,学一点什么学问,充实自己...

Android Button 字母自动变大写记录

前言开发中,Button控件的Text 自动转为大写字母,这里记录一下,方便自己查询。好记性不如烂笔头正文字母自动变大写的原因只要我们用的Theme是Material或API Level 21+的默认 Theme,Button上的Text默认就是大写。解决方式第一种方法在Xml中的...

刘瑜:适应孤独,就像适应一种残疾

前两天有个网友给我写信,问我如何克服寂寞。她跟我刚来美国时一样,英文不够好,朋友少,一个人等着天亮,一个人等着天黑。“每天学校、家、图书馆、健身房,几点一线。”我说我没什么好招儿,因为我从来就没有克服过这个问题。这些年来我学会的,就是适应它。正如有人所言:“适应孤独,就像适应一种残疾。”我觉得,...

Vim模式切换命令

前言简单记录一下 Vim命令的 AIO(或aio)的使用正文a和A的命令使用a或A是append(追加)的缩写,进入编辑状态。a : 在[光标位置之后的一个位置]开始操作A : 在[光标位置的行尾]开始开始操作i和I的命令使用i或I是 insert(插入)的缩写,进入编辑状态。...