Android 音视频开发(二) : 音频 采集和播放

android录制音频有三种方式:系统自带的应用;通过MediaRecorder来进行音频的录制;AudioRecord录制音频。

1:音频格式比较

WAV格式:是PCM格式,文件大,听起来清晰。

AAC格式:相对于mp3,AAC格式的音质更好,文件更小;有损压缩;API=16才会支持播放

AMR格式:压缩比较大,但相对其他的压缩格式质量比较差,多用于人声,和通话录音。

总结比较:

2:系统自带是通过Intent调用系统的录音器功能,然后在录制完毕保存以后在onActivityResult中返回录制的音频的uri,然后通过Mediaplayer进行播放。

优点:方便简单

缺点:不同手机会修改,不利于集成开发;可拓展性不强。

代码:

public void systemAudio(View view) {
    Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
    startActivityForResult(intent,REQUEST_RECORDER);

}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_RECORDER && resultCode == RESULT_OK) {
         mUri = data.getData();
        Log.e("sss", "onActivityResult: "+mUri.toString() );
        playAudio();
    }

}
public void playAudio() {
    if (mUri != null) {
        if (mMediaPlayer != null) {
          
            mMediaPlayer.reset();
            try {
               
                mMediaPlayer.setDataSource(this, mUri);
              
                mMediaPlayer.prepare();
              
                mMediaPlayer.start();
               
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            Toast.makeText(this,"没有成功创建Mediaplayer",Toast.LENGTH_SHORT).show();
        }
    }
}

注意:android6.0以上要动态授权。

3:通过MediaRecorder

MediaRecoder已经集成了录音、编码、压缩等,支持少量的录音格式,aac(api=16),amr,3gp

优点:大部分代码集成,直接调用相关接口即可,代码量小

缺点:无法时时处理音频;输出的音频格式不是很多,没有mp3的格式

代码:

public class MediaRecorderManger {
    private boolean isRecord = false;

    private MediaRecorder mMediaRecorder;
    private MediaRecorderManger(){
    }

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

    public int startRecordAndFile(){
        //判断是否有外部存储设备sdcard
        if(AudioFileUtil.isSdcardExit())
        {
            if(isRecord)
            {
                return ErrorCode.E_STATE_RECODING;
            }
            else
            {
                if(mMediaRecorder == null)
                    createMediaRecord();

                try{
                    mMediaRecorder.prepare();
                    mMediaRecorder.start();
                    // 让录制状态为true
                    isRecord = true;
                    return ErrorCode.SUCCESS;
                }catch(IOException ex){
                    ex.printStackTrace();
                    return ErrorCode.E_UNKOWN;
                }
            }

        }
        else
        {
            return ErrorCode.E_NOSDCARD;
        }
    }


    public void stopRecordAndFile(){
        close();
    }

    public long getRecordFileSize(){
        return AudioFileUtil.getFileSize(AudioFileUtil.getAMRFilePath());
    }


    private void createMediaRecord(){
         /* ①Initial:实例化MediaRecorder对象 */
        mMediaRecorder = new MediaRecorder();

        /* setAudioSource/setVedioSource*/
        mMediaRecorder.setAudioSource(AudioFileUtil.AUDIO_INPUT);//设置麦克风

        /* 设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default
         * THREE_GPP(3gp格式,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
         */
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);

         /* 设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default */
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);

         /* 设置输出文件的路径 */
        File file = new File(AudioFileUtil.getAMRFilePath());
        if (file.exists()) {
            file.delete();
        }
        mMediaRecorder.setOutputFile(AudioFileUtil.getAMRFilePath());
    }


    private void close(){
        if (mMediaRecorder != null) {
            System.out.println("stopRecord");
            isRecord = false;
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
    }
}

:4:通过AudioRecord

主要是实现边播边录(AudioRecord+AudioTrack)以及对音频的实时处理(如语音)

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出的PCM语音数据,如果保存成音频文件,是不能够被播放器播放的 ,必须先代码实现数据的编码和压缩

实现录音流程:

1:构造一个AudioRecord对象,其中需要的最小录音缓存buffer大小可以通过getMinBufferSize方法得到。如果buffer容量过小,降导致对象构造失败。

2:初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小

3:开始录音

4:创建一个数据流,一边从AudioRecord中读取声音数据到初始化buffer,一边将buffer中的数据导入数据流。

5:关闭数据流

6:停止录音

代码:

public class AudioRecordManger {
    private static final String TAG = "AudioRecordManger";
    // 缓冲区字节大小
    private int bufferSizeInBytes = 0;

    //AudioName裸音频数据文件 ,麦克风
    private String AudioName = "";

    //NewAudioName可播放的音频文件
    private String NewAudioName = "";

    private AudioRecord audioRecord;
    private boolean isRecord = false;// 设置正在录制的状态
    private AudioRecordThread mAudioRecordThread = new AudioRecordThread();//录制线程
    private AudioRecordPlayThread mAudioRecordPlayThead;//播放线程
    private static AudioRecordManger mInstance;
    AudioTrack mAudioTrack;
    private int BufferSize=1024;

    private AudioRecordManger() {

    }

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

    public void startRecordAndFile() {
        //判断是否有外部存储设备sdcard
        if (AudioFileUtil.isSdcardExit()) {
            if (isRecord==true) {
                Log.e(TAG, "无法开始录制,当前状态为:" + isRecord);
                return ;
            } else {
                if (audioRecord == null)
                    createAudioRecord();
                Log.e(TAG, "开始录制,当前状态为:" + isRecord);
                audioRecord.startRecording();
                // 让录制状态为true
                isRecord = true;
                // 开启音频文件写入线程
                new Thread(mAudioRecordThread).start();


            }

        } else {
            Log.e(TAG, "路径不存在");
        }

    }

    public void stopRecordAndFile() {
        close();
    }


    public long getRecordFileSize() {
        return AudioFileUtil.getFileSize(NewAudioName);
    }


    private void close() {
        if (audioRecord != null) {
            Log.e(TAG, "停止录制,当前状态为:" + isRecord);
            isRecord = false;//停止文件写入
            audioRecord.stop();
            audioRecord.release();//释放资源
            audioRecord = null;
        }
    }


    private void createAudioRecord() {
        // 获取音频文件路径
        AudioName = AudioFileUtil.getRawFilePath();
        NewAudioName = AudioFileUtil.getWavFilePath();

        // 获得缓冲区字节大小
        bufferSizeInBytes = AudioRecord.getMinBufferSize(AudioFileUtil.AUDIO_SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);

        // 创建AudioRecord对象
        audioRecord = new AudioRecord(AudioFileUtil.AUDIO_INPUT, AudioFileUtil.AUDIO_SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
    }


    class AudioRecordThread implements Runnable {
        @Override
        public void run() {
            writeDateTOFile();//往文件中写入裸数据
            copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件
        }
    }

    class AudioRecordPlayThread implements Runnable {

        @Override
        public void run() {
            Log.e(TAG, "开始播放,当前状态为:" + isRecord);
            try {
               BufferSize = AudioTrack.getMinBufferSize(11025, AudioFormat.CHANNEL_IN_STEREO,
                        AudioFormat.ENCODING_PCM_16BIT);
                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 11025,AudioFormat.CHANNEL_IN_STEREO,
                        AudioFormat.ENCODING_PCM_16BIT, BufferSize,AudioTrack.MODE_STREAM);
                FileInputStream fis = new FileInputStream(NewAudioName);
                mAudioTrack.play();
                byte[] bytes = new byte[BufferSize];

                while(true == isRecord) {
                    int read = fis.read(bytes);
                    //若读取有错则跳过
                    if (AudioTrack.ERROR_INVALID_OPERATION == read
                            || AudioTrack.ERROR_BAD_VALUE == read) {
                        continue;
                    }

                    if (read != 0 && read != -1) {
                        mAudioTrack.write(bytes, 0, BufferSize);
                    }
                }
                mAudioTrack.stop();
                mAudioTrack.release();//释放资源
                fis.close();//关流

            } catch (Exception e) {
                e.printStackTrace();
            }

            isRecord = false;
            Log.e(TAG, "播放停止");

        }
    }

    /**
    *播放音频
     */
    public void playRecord() {
        if (isRecord = false) {
            Log.e(TAG, "无法开始播放,当前状态为:" + isRecord);
            return;
        }
        Log.e(TAG, "开始播放,当前状态为"+isRecord);
        isRecord = true;
        mAudioRecordPlayThead = new AudioRecordPlayThread();
       new Thread(mAudioRecordPlayThead) .start();
    }
    /**
     * 停止播放
     */
    public void stopPlayRecord() {
        if (null != mAudioRecordPlayThead) {
            Log.d(TAG, "停止播放,当前状态为");
            mAudioRecordPlayThead = null;
        }
        isRecord = false;
    }


    /**
     * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频,
     * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理,比如你要做一个爱说话的TOM
     * 猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。
     */
    private void writeDateTOFile() {
        // new一个byte数组用来存一些字节数据,大小为缓冲区大小
        byte[] audiodata = new byte[bufferSizeInBytes];
        FileOutputStream fos = null;
        int readsize = 0;
        try {
            File file = new File(AudioName);
            if (file.exists()) {
                file.delete();
            }
            fos = new FileOutputStream(file);// 建立一个可存取字节的文件
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (isRecord == true) {
            readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
                try {
                    fos.write(audiodata);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        try {
            if (fos != null)
                fos.close();// 关闭写入流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 这里得到可播放的音频文件
    private void copyWaveFile(String inFilename, String outFilename) {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = AudioFileUtil.AUDIO_SAMPLE_RATE;
        int channels = 2;
        long byteRate = 16 * AudioFileUtil.AUDIO_SAMPLE_RATE * channels / 8;
        byte[] data = new byte[bufferSizeInBytes];
        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) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
     * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
     * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有
     * 自己特有的头文件。
     */
    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/WAVE header
        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';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        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;
        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);
        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) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16; // bits per sample
        header[35] = 0;
        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);
    }
}

源码:https://github.com/ChloeDimen/AudioAndVideo/tree/master

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值