美文网首页Android音频
音视频学习系列第(二)篇---音频采集和播放

音视频学习系列第(二)篇---音频采集和播放

作者: sofarsogoo_932d | 来源:发表于2018-04-18 11:39 被阅读0次

    音视频系列

    音频采集AudioRecord

    AudioRecord与MediaRecorder区别
    前者采集的是原始的音频数据,后者会对音频数据进行编码压缩并存储成文件

    AudioRecord的使用

    1.AudioRecord参数配置

    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes)
    

    audioSource
    音频采集的输入源,可选值在MediaRecorder.AudioSource中以常量值定义,如

    public static final int MIC = 1;   //表示手机麦克风输入
    

    sampleRateInHz
    采样率,录音设备1S内对声音信号的采集次数,单位Hz,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。
    背景知识
    Hz,物质在1S内周期性变化的次数
    我们知道人耳能听到的声音频率范围在20Hz到20KHz之间,为了不失真,采样频率应该在40KHz以上

    channelConfig
    通道数的配置,可选值在AudioFormat中以常量值定义,常用的如下

    public static final int CHANNEL_IN_LEFT = 0x4;
    public static final int CHANNEL_IN_RIGHT = 0x8;
    public static final int CHANNEL_IN_FRONT = 0x10;
    //单通道
    public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;   
    //双通道
    public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
    

    audioFormat
    用来配置数据位宽,可选值在可选值在AudioFormat中以常量值定义,常用的如下

    public static final int ENCODING_PCM_16BIT = 2;
    public static final int ENCODING_PCM_8BIT = 3;
    

    背景知识
    PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。

    bufferSizeInBytes
    配置的是AudioRecord内部音频缓冲区的大小,该值不能低于一帧音频帧的大小,一帧音频帧的大小计算如下
    int size=采样率 * 采样时间 * 位宽 * 通道数
    其中采样时间一般取2.5ms~120ms,具体取多少由厂商或者应用决定
    每一帧采样的时间越短,产生的延时越小,但碎片化的数据也会越多
    在Android开发中,应该使用AudioRecord类中的方法

    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
    

    来计算音频缓冲区的大小

    2.音频采集方法

    audioRecord.startRecording();   //开始录制
    audioRecord.stop();    //停止录制
    audioRecord.read(bytes,0,bytes.length);  //读取录音数据
    

    3.示例代码

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    

    并且该权限属于危险权限,需要动态获取权限

    public class AudioCapture {
    private static final String TAG = "AudioCapture";
    
    private final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;  //麦克风
    private final int DEFAULT_RATE = 44100;    //采样率
    private final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO;   //双通道(左右声道)
    private final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT;   //数据位宽16位
    
    private AudioRecord mAudioRecord;
    private int mMinBufferSize;
    private onAudioFrameCaptureListener mOnAudioFrameCaptureListener;
    
    private boolean isRecording = false;
    
    public void startRecord() {
        startRecord(DEFAULT_SOURCE, DEFAULT_RATE, DEFAULT_CHANNEL, DEFAULT_FORMAT);
    }
    
    
    public void startRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
    
        mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
            Log.d(TAG, "Invalid parameter");
            return;
        }
    
        mAudioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig,
                audioFormat, mMinBufferSize);
        if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
            Log.d(TAG, "AudioRecord initialize fail");
            return;
        }
    
        mAudioRecord.startRecording();
        isRecording = true;
        CaptureThread t = new CaptureThread();
        t.start();
        Log.d(TAG, "AudioRecord Start");
    }
    
    
    public void stopRecord() {
        isRecording = false;
        if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
            mAudioRecord.stop();
        }
        mAudioRecord.release();
        mOnAudioFrameCaptureListener = null;
        Log.d(TAG, "AudioRecord Stop");
    }
    
    
    private class CaptureThread extends Thread {
    
        @Override
        public void run() {
            while (isRecording) {
                byte[] buffer = new byte[mMinBufferSize];
                int result = mAudioRecord.read(buffer, 0, buffer.length);
                Log.d(TAG, "Captured  " + result + "  byte");
                if (mOnAudioFrameCaptureListener != null) {
                    mOnAudioFrameCaptureListener.onAudioFrameCapture(buffer);
                }
            }
        }
    }
    
    
    public interface onAudioFrameCaptureListener {
        void onAudioFrameCapture(byte[] audioData);
    }
    
    public void setOnAudioFrameCaptureListener(onAudioFrameCaptureListener listener) {
        mOnAudioFrameCaptureListener = listener;
      }
    }
    

    调用方式

    audioCapture=new AudioCapture();
    audioCapture.startRecord();
    

    音频播放AudioTrack

    AudioTrack,MediaPlayer,SoundPool的区别

    mediaplayer适合长时间播放音乐
    soundpool适合短时间的音频片段,如游戏声音,按键声音
    audiotrack更接近底层,更灵活,播放的是pcm音频数据

    AudioTrack的使用

    1.AudioTrack参数配置

    public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
            int bufferSizeInBytes, int mode)
    

    streamType
    音频管理策略,如我们在小米手机调节音量时,会出现3种声音调节的类型,音乐,铃声,闹钟
    该参数的可选值在AudioManager类中,如:

    STREAM_MUSCI:音乐声
    STREAM_RING:铃声
    STREAM_NOTIFICATION:通知声
    

    sampleRateInHz
    采样率,看源码知道,范围在4000~192000

    public static final int SAMPLE_RATE_HZ_MIN = 4000;
    public static final int SAMPLE_RATE_HZ_MAX = 192000;
    

    channelConfig
    通道数的配置,可选值在AudioFormat中以常量值定义,常用的如下

    public static final int CHANNEL_IN_LEFT = 0x4;
    public static final int CHANNEL_IN_RIGHT = 0x8;
    public static final int CHANNEL_IN_FRONT = 0x10;
    //单通道
    public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;   
    //双通道
    public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
    

    audioFormat
    用来配置数据位宽,可选值在可选值在AudioFormat中以常量值定义,常用的如下

    public static final int ENCODING_PCM_16BIT = 2;
    public static final int ENCODING_PCM_8BIT = 3;
    

    bufferSizeInBytes
    配置的是AudioTrack内部音频缓冲区的大小,同样AudioTrack提供了获取缓冲区大小的方法

    AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
    

    mode
    AudioTrack有两种播放方式 MODE_STATIC和MODE_STREAM
    前者是一次性将所有数据写入播放缓冲区,然后播放
    后者是一边写入一边播放

    2.音频播放方法

    mAudioTrack.play();  //开始播放
    mAudioTrack.stop(); //停止播放
    mAudioTrack.write(audioData,offsetInBytes,sizeInBytes);//将pcm数据写入缓冲区
    

    3.示例代码

    public class AudioPlayer {
    
    private static final String TAG = "AudioPlayer";
    
    private final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;  //流音乐
    private final int DEFAULT_RATE = 44100;    //采样率
    private final int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_STEREO;   //双通道(左右声道)
    private final int DEFAULT_FORMAT = AudioFormat.ENCODING_PCM_16BIT;   //数据位宽16位
    private static final int DEFAULT_PLAY_MODE = AudioTrack.MODE_STREAM;
    
    
    private AudioTrack mAudioTrack;
    private int mMinBufferSize;
    
    
    private boolean isPlaying=false;
    
    
    
    public void startPlay(){
        startPlay(DEFAULT_STREAM_TYPE,DEFAULT_RATE,DEFAULT_CHANNEL,DEFAULT_FORMAT);
    
    }
    
    public void startPlay(int streamType, int sampleRateInHz, int channelConfig, int audioFormat){
        if(isPlaying){
            Log.d(TAG,"AudioPlayer has played");
            return;
        }
    
        mMinBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
            Log.d(TAG, "Invalid parameter");
            return;
        }
    
    
        mAudioTrack=new AudioTrack(streamType,sampleRateInHz,channelConfig,audioFormat,
                mMinBufferSize,DEFAULT_PLAY_MODE);
        if(mAudioTrack.getState()==AudioTrack.STATE_UNINITIALIZED){
            Log.d(TAG, "AudioTrack initialize fail");
            return;
        }
    
        isPlaying=true;
    }
    
    public void stopPlay(){
        if(!isPlaying){
            Log.d(TAG, "AudioTrack is not playing");
            return;
        }
    
        if(mAudioTrack.getPlayState()==AudioTrack.PLAYSTATE_PLAYING){
            mAudioTrack.stop();
        }
    
        mAudioTrack.release();
        isPlaying=false;
    }
    
    
    private void play(byte[] audioData,int offsetInBytes, int sizeInBytes){
        if(!isPlaying){
            Log.d(TAG, "AudioTrack not start");
            return;
        }
    
        if(sizeInBytes<mMinBufferSize){
            Log.d(TAG, "audio data not enough");
            //return;
        }
    
        if(mAudioTrack.write(audioData,offsetInBytes,sizeInBytes)!=mMinBufferSize){
            Log.d(TAG, "AudioTrack can not write all the data");
        }
    
        mAudioTrack.play();
        Log.d(TAG, "played  "+sizeInBytes+"  bytes");
      }
    }
    
    测试
    //原始音频的录入和播放
    public class AudioPCMActivity extends DemoActivity {
    
    private Button btn_audio_record;
    private Button btn_audio_record_play;
    
    
    private AudioCapture audioCapture;
    private AudioPlayer audioPlayer;
    
    private PcmFileWriter pcmFileWriter;
    private PcmFileReader pcmFileReader;
    private boolean isReading;
    
    private String path="";
    
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        setContentView(R.layout.activity_media_audio);
        super.onCreate(savedInstanceState);
    }
    
    @Override
    public void initHead() {
    
    }
    
    @Override
    public void initView() {
        btn_audio_record=findViewById(R.id.btn_audio_record);
        btn_audio_record_play=findViewById(R.id.btn_audio_record_play);
    }
    
    @Override
    public void initData() {
        path=FileUtil.getAudioDir(this)+"/audioTest.pcm";
        audioCapture=new AudioCapture();
        audioPlayer=new AudioPlayer();
        pcmFileReader=new PcmFileReader();
        pcmFileWriter=new PcmFileWriter();
    
        String des = "录音权限被禁止,我们需要打开录音权限";
        String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO};
        baseAt.requestPermissions(des, permissions, 100, new PermissionsResultListener() {
            @Override
            public void onPermissionGranted() {
    
            }
            @Override
            public void onPermissionDenied() {
                finish();
    
            }
        });
    
    }
    
    @Override
    public void initEvent() {
        btn_audio_record.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction()==MotionEvent.ACTION_DOWN){
                    Log.d("TAG","按住");
                    start();
                }else if(event.getAction()==MotionEvent.ACTION_UP){
                    Log.d("TAG","松开");
                    stop();
                }
                return false;
            }
        });
    
        btn_audio_record_play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                play();
    
            }
        });
    
    }
    
    //播放录音
    private void play(){
        isReading=true;
        pcmFileReader.openFile(path);
        audioPlayer.startPlay();
        new AudioTrackThread().start();
    }
    
    private class AudioTrackThread extends Thread{
        @Override
        public void run() {
            byte[] buffer = new byte[1024];
            while (isReading && pcmFileReader.read(buffer,0,buffer.length)>0){
                audioPlayer.play(buffer,0,buffer.length);
            }
            audioPlayer.stopPlay();
            pcmFileReader.closeFile();
        }
    }
    
    
    //开始录音
    private void start(){
        pcmFileWriter.openFile(path);
        btn_audio_record.setText("松开 结束");
        audioCapture.startRecord();
        audioCapture.setOnAudioFrameCaptureListener(new AudioCapture.onAudioFrameCaptureListener() {
            @Override
            public void onAudioFrameCapture(byte[] audioData) {
                pcmFileWriter.write(audioData,0,audioData.length);
    
            }
        });
    }
    
    //结束录音
    private void stop(){
        btn_audio_record.setText("按住 录音");
        audioCapture.stopRecord();
        pcmFileWriter.closeFile();
      }
    }
    

    github
    测试代码在com.sf.sofarmusic.demo.media下
    其他代码在libplayer模块中

    相关文章

      网友评论

        本文标题:音视频学习系列第(二)篇---音频采集和播放

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