音視頻學習系列第(二)篇---音頻采集和播放


音視頻系列

音頻采集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模塊中


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com