美文网首页Android开发
2018-10-12 用audioRecord录制pcm文件并用

2018-10-12 用audioRecord录制pcm文件并用

作者: flynnny | 来源:发表于2018-10-12 17:59 被阅读3次

1问题引入

  • Android原生的AudioRecord没有自带的pause()方法(MediaRecord需要较高的版本才有,具体效果有待测试)。低版本的MediaRecord在通过stop()之类的方法暂停再继续录音,最后生成的多个音频文件如果没有通过ffmpeg合成,之前的断点会出现明显的爆音。
  • 本文通过flag,去判断AudioRecord 的写入,绕过了Android添加的报头等无效数据。基本实现无爆音的暂停和续播。
  • 其他:文件大小大概每5s 1M,录制的文件保存了txt的,然后转换成wav。之所以录制成pcm,还考虑到如果对音频进行处理的话(比如音效,伴奏,混音),pcm数据好处理,这部分研究后有机会再写吧 XD

2具体代码

  • AudioTrackToRecordActivity
import android.app.Activity;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

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

import zx.com.mytextproject.R;

/**
 * 用audioRecord录制pcm  能够无间断暂停
 * */

public class AudioTrackToRecordActivity extends Activity implements View.OnClickListener {
    Context ctx;
    Button btn_a2r_start;
    Button btn_a2r_pause;
    Button btn_a2r_stop;
    Button btn_a2r_play;
    Boolean isRecording = false;//是否开启了recorder
    Boolean isPause = false;//是否正在暂停
    Boolean isOver = false;//是否结束
    //录制相关
    AudioRecord audioRecord;
    private int audioSource = MediaRecorder.AudioSource.MIC;// 音频获取源
    private static int sampleRateInHz = 44100; // 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;// 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
    private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;// 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。
    private static int audioMode = AudioTrack.MODE_STREAM;
    int RecordbufferSize;
    //文件
    File file;
    File file2;//wav
    String filename = null;
    String filename2 = null;
    MyThread recordT;
    OutputStream outputStream;
    //播放
    AudioTrack audioTrack;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ctx = this;
        setContentView(R.layout.activity_audiotracktorecord);
        initView();
    }

    private void initView() {
        btn_a2r_start = findViewById(R.id.btn_a2r_start);
        btn_a2r_pause = findViewById(R.id.btn_a2r_pause);
        btn_a2r_stop = findViewById(R.id.btn_a2r_stop);
        btn_a2r_play = findViewById(R.id.btn_a2r_play);
        btn_a2r_start.setOnClickListener(this);
        btn_a2r_pause.setOnClickListener(this);
        btn_a2r_stop.setOnClickListener(this);
        btn_a2r_play.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_a2r_start:
                if(isRecording){
                    if(isPause==false){
                        isPause = true;//停止录制
                        if(recordT!=null){
                            recordT.setStop(true);//再次停止线程
                        }
                    }
                }
                //清理
                refreshState();
                //初始化 启动
                initRecordData();
                isRecording = true;
                break;
            case R.id.btn_a2r_pause:
               if(!isRecording||isOver){
                   Toast.makeText(this,"未在录制",Toast.LENGTH_SHORT).show();
                   return;
               }
               if(isPause){
                   isPause = false;
                   btn_a2r_pause.setText("暂停");
                   Toast.makeText(this,"继续录制中",Toast.LENGTH_SHORT).show();
                   WriteToFile();
               }else{
                   isPause = true;
                   btn_a2r_pause.setText("继续录制");
                   Toast.makeText(this,"暂停中",Toast.LENGTH_SHORT).show();
               }

                break;
            case R.id.btn_a2r_stop:
                if(!isRecording){
                    Toast.makeText(this,"未在录制!isRecording",Toast.LENGTH_SHORT).show();
                    return;
                }
                if(isOver){
                    Toast.makeText(this,"未在录制isOver",Toast.LENGTH_SHORT).show();
                    return;
                }
                if(!isPause){
                    isPause = true;
                    if(recordT!=null){
                        recordT.setStop(true);//再次停止线程
                    }
                }
                audioRecord.stop();
                audioRecord.release();
                audioRecord = null;

                isOver = true;
                isRecording = false;

                //合成
                PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(sampleRateInHz,channelConfig,audioFormat);
                pcmToWavUtil.pcmToWav(filename,filename2);
                break;
            case R.id.btn_a2r_play:
                if(isOver){
                    //播放
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            playPcm();
                        }
                    });
                    t.start();
                }else{
                    Toast.makeText(this,"还没有播放的文件",Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    /**
     * 清理
     * */
    private void refreshState() {
        isRecording = false;//是否开启了recorder
        isPause = false;//是否正在暂停
        isOver = false;//是否结束
        recordT = null;
    }

    private void  initRecordData(){
        RecordbufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);// 获得缓冲区字节大小
        audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, RecordbufferSize);// 创建AudioRecord对象

        //创建文件
        filename = ctx.getFilesDir().getAbsolutePath() + File.separator + "zzzrecord.txt";///data/user/0/zx.com.mytextproject/files/zzzrecord.aac
        filename2 = ctx.getFilesDir().getAbsolutePath() + File.separator + "zzzrecord.wav";///data/user/0/zx.com.mytextproject/files/zzzrecord.aac
        file = new File(filename);
        file2 = new File(filename2);
        Log.d("zzz", "filename == " + filename);
        if (file.exists()) {
            file.delete();
            file2.delete();
            Log.d("zzz", "删除文件");
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            Log.i("zzz", "未能创建");
            throw new IllegalStateException("未能创建" + file.toString());
        }

        audioRecord.startRecording();
        WriteToFile();
    }

    private void WriteToFile() {
        recordT = new MyThread();
        recordT.start();
    }

    private void playPcm() {
        int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig,audioFormat);// 获得音频缓冲区大小
        audioTrack=new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, audioFormat, bufferSize*2, AudioTrack.MODE_STREAM);
        DataInputStream dis = null;
        try {
//            dis=new DataInputStream(new FileInputStream(filename));
            dis=new DataInputStream(new BufferedInputStream(new FileInputStream(filename)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        byte[] data =new byte [bufferSize];
        audioTrack.play();
        while (true){
            int i=0;
            try {
                while(dis.available()>0&&i<data.length)
                {
                    data[i]=dis.readByte();//录音时write Byte 那么读取时就该为readByte要相互对应
                    i++;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            audioTrack.write(data,0,data.length);

            if(i!=bufferSize) //表示读取完了
            {
                audioTrack.stop();//停止播放
                audioTrack.release();//释放资源
                break;
            }
        }
        try {
            dis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 写入的类
     * */
    class MyThread extends Thread{
        private volatile boolean isStop = false;
        @Override
        public void run() {
            //在文本文本中追加内容
            try {
                outputStream = new FileOutputStream(filename,true)  ;    // 此处表示在文件末尾追加内容
            } catch (Exception e) {
                e.printStackTrace();
            }

            byte[] Recordbuffer = new byte[RecordbufferSize];//
            int readLen;

            while (!isPause&&!isStop) {
                readLen = audioRecord.read(Recordbuffer, 0, Recordbuffer.length);
                //录制
                try {
                    outputStream.write(Recordbuffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void setStop(boolean stop){
            this.isStop = stop;
        }
        public boolean getStop(){
            return isStop;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isRecording = false;//是否开启了recorder
        isPause = false;//是否正在暂停
        isOver = false;//是否结束
        if(recordT!=null){
            recordT.setStop(true);//再次停止线程
        }
        if(audioRecord!=null){
            audioRecord.stop();
            audioRecord.release();
            audioRecord = null;
        }

        recordT = null;

    }
}

  • activity_audiotracktorecord
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/btn_a2r_start"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y40"
        android:text="开始录制"
        />
    <Button
        android:id="@+id/btn_a2r_pause"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y40"
        android:text="暂停"
        />
    <Button
        android:id="@+id/btn_a2r_stop"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y40"
        android:text="结束录制"
        />
    <Button
        android:id="@+id/btn_a2r_play"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y40"
        android:text="播放"
        />
</LinearLayout>
  • PcmToWavUtil
import android.media.AudioFormat;
import android.media.AudioRecord;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 把录制的pcm转换成wav
 *
 * */

public class PcmToWavUtil {

    /**
     * 缓存的音频大小
     */
    private int mBufferSize;
    /**
     * 采样率
     */
    private int mSampleRate;
    /**
     * 声道数
     */
    private int mChannel;


    /**
     * @param sampleRate sample rate、采样率
     * @param channel channel、声道
     * @param encoding Audio data format、音频格式
     */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }


    /**
     * pcm文件转wav文件
     *
     * @param inFilename 源文件路径
     * @param outFilename 目标文件路径
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        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 (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        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);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 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);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        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);
    }
}

最后别忘了权限等设置。亲测可用。

相关文章

网友评论

    本文标题:2018-10-12 用audioRecord录制pcm文件并用

    本文链接:https://www.haomeiwen.com/subject/yvbgaftx.html