zoukankan      html  css  js  c++  java
  • 音视频学习系列第(二)篇---音频采集和播放

    音视频系列

    音频采集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模块中

  • 相关阅读:
    Python基础语法 第2节课(数据类型转换、运算符、字符串)
    python基础语法 第5节课 ( if 、 for )
    python基础语法 第4节课 (字典 元组 集合)
    Python基础语法 第3节课 (列表)
    A. Peter and Snow Blower 解析(思維、幾何)
    C. Dima and Salad 解析(思維、DP)
    D. Serval and Rooted Tree (樹狀DP)
    C2. Balanced Removals (Harder) (幾何、思維)
    B. Two Fairs 解析(思維、DFS、組合)
    D. Bash and a Tough Math Puzzle 解析(線段樹、數論)
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10489362.html
Copyright © 2011-2022 走看看