前幾篇的文章都是camera下采集視頻數據進行顯示,保存下來的文件也是h264格式的,並無包含音頻數據,因此多多少少有點單調的感受。沒有聲音的視頻是沒有靈魂的,因此最近了解了一下音頻相關的開發,給視頻注入靈魂。java
開始音頻學習以前,有必要先了解一下基礎知識,由於在音頻開發過程當中,常常會涉及到這些。掌握了這些重要的概念,在學習中不少參數的配置會更容易理解。android
PCM編碼格式ide
首先看看百度百科給出的解釋:PCM 脈衝編碼調製是Pulse Code Modulation的縮寫。脈衝編碼調製是數字通訊的編碼方式之一。主要過程是將話音、圖像等模擬信號每隔必定時間進行取樣,使其離散化,同時將抽樣值按分層單位四捨五入取整量化,同時將抽樣值按一組二進制碼來表示抽樣脈衝的幅值。 這個解釋得很是抽象,反正我是沒看懂⊙﹏⊙。簡單的來講就是將聲音數字化,轉換爲二進制序列,這樣就能夠把聲音保存下來了,而保存它的容器能夠是mp3,wav等等容器。函數
音頻採集輸入源學習
這個就至關於聲明孩子他媽是誰,也就是說聲音的源頭在哪兒。可選的類型以常量的形式定義在 MediaRecorder.AudioSource 類中 ,比較經常使用的是下面幾個this
採樣率編碼
咱們把採樣到的一個個靜止畫面再以採樣率一樣的速度回放時,看到的就是連續的畫面。一樣的道理,把以44.1kHZ採樣率記錄的CD以一樣的速率播放時,就能聽到連續的聲音。顯然,這個採樣率越高,聽到的聲音和看到的圖像就越連貫。固然,人的聽覺和視覺器官能分辨的採樣率是有限的,基本上高於44.1kHZ採樣的聲音,絕大部分人已經覺察不到其中的分別了。 而目前44100Hz是惟一能夠保證兼容全部Android手機的採樣率 。因此,若是不是特殊設備和用途,這個值建議設置爲44100。spa
通道數線程
聲道數通常表示聲音錄製時的音源數量或回放時相應的揚聲器數量。經常使用的有:單通道和雙通道。 可選的值以常量的形式定義在 AudioFormat 類中,經常使用的是 CHANNEL_IN_MONO(單通道),CHANNEL_IN_STEREO(雙通道)code
量化精度
幀間隔
音頻不像視頻那樣,有一幀一幀的概念。它是約定一個時間爲單位,而後這個時間內的數據爲一幀,這個時間被稱爲採樣時間。這個時間沒有特別的標準,要看具體的編解碼器。
瞭解音頻開發相關基礎知識以後,咱們就能夠開始使用android提供的相關API實現音頻的錄製了,android提供了兩套音頻錄製的API:
MediaRecorder
:比較上層的 API
,它能夠直接把手機麥克風的音頻數據進行編碼而後儲存成文件。使用簡單,可是支持的格式有限,而且不支持對音頻進行進一步的處理,例如變聲、混音等。AudioRecord
:比較底層的一個 API
,可以獲得原始的 PCM
音頻數據。因爲咱們獲得的是原始的 PCM
數據,咱們能夠對音頻進行進一步的處理,例如編碼、混音和變聲等。這裏主要介紹的是使用AudioRecord
進行錄製,MediaRecorder
的錄製比較簡單,就不作過多介紹了。首先看看AudioRecord的構造函數:
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes) 複製代碼
須要傳入5個參數,分別是輸入源,採樣率,通道數,量化精度,音頻緩衝區的大小 。其中最後一個也是最重要的一個參數,它表明音頻緩衝區的大小,該緩衝區的值不能低於一幀音頻幀的大小,
一幀音頻幀大小 = 採樣率 x 位寬 x 採樣時間 x 通道數 ,這個值不用咱們本身計算,AudioRecord 類提供了一個幫助你肯定這個值的函數 :
public static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) 複製代碼
當建立好AudioRecord對象以後,就能夠開始進行音頻數據的採集了,控制採集的開始和中止的方法是下面這兩個函數:
public void startRecording() public void stop() 複製代碼
開始採集以後,經過線程循環取走音頻數據:
public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) 複製代碼
最終錄製的代碼以下,注意該方法須要在子線程中運行:
private File mAudioFile;
private FileOutputStream mAudioFileOutput;
private boolean isRecording = false;
private int sampleRate = 44100;//全部android系統都支持 採樣率
//單聲道輸入
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//PCM_16是全部android系統都支持的 16位的聲音就是人類能聽到的極限了,再高就聽不見了 位數越高聲音越清晰
private int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int recordBufSize = 0; // 聲明recoordBufffer的大小字段
private AudioRecord audioRecord = null;
private boolean startAudioRecord(String fileName) {
isRecording = true;
mAudioFile = new File(mPath+fileName+System.currentTimeMillis()+".pcm");
if (!mAudioFile.getParentFile().exists()){
mAudioFile.getParentFile().mkdirs();
}
try {
mAudioFile.createNewFile();
//建立文件輸出流
mAudioFileOutput = new FileOutputStream(mAudioFile);
//計算audioRecord能接受的最小的buffer大小
recordBufSize = AudioRecord.getMinBufferSize(sampleRate,
channelConfig,
autioFormat);
Log.e(TAG, "最小的buffer大小: " + recordBufSize);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
autioFormat, recordBufSize);
//初始化一個buffer 用於從AudioRecord中讀取聲音數據到文件流
byte data[] = new byte[recordBufSize];
//開始錄音
audioRecord.startRecording();
while (isRecording){
//只要還在錄音就一直讀取
int read = audioRecord.read(data,0,recordBufSize);
if (read > 0){
mAudioFileOutput.write(data,0,read);
}
}
//stopRecorder();
} catch (IOException e) {
e.printStackTrace();
stopAudioRecord();
return false;
}
return true;
}
public boolean stopAudioRecord(){
isRecording = false;
if (audioRecord != null){
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
try {
mAudioFileOutput.flush();
mAudioFileOutput.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
複製代碼
播放pcm格式的音頻和錄製不一樣的地方有三個:
一個是建立AudioTrack對象,和AudioRecord的構造方法不一樣,第一個參數是音樂類型,AudioManager.STREAM_MUSIC表示用揚聲器播放,最後多出的一個參數是播放模式,通常使用AudioTrack.MODE_STREAM,適用於大多數的場景,將audio buffers從java層傳遞到native層即返回。 若是audio buffers佔用內存多,應該使用MODE_STREAM。 好比播放時間很長的聲音文件, 好比音頻文件使用高採樣率等:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) 複製代碼
另外兩個是把錄製的startRecording()和read()方法換成下面兩個:
public void play() public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) 複製代碼
最終播放pcm的代碼以下:
private FileInputStream mAudioPlayInputStream;
public void doPlay(File audioFile) {
if (audioFile.isDirectory() || !audioFile.exists()) return;
mIsPlaying = true;
//配置播放器
//音樂類型,揚聲器播放
int streamType= AudioManager.STREAM_MUSIC;
//錄音時採用的採樣頻率,因此播放時一樣的採樣頻率
int sampleRate=44100;
//單聲道,和錄音時設置的同樣
int channelConfig=AudioFormat.CHANNEL_OUT_MONO;
//錄音時使用16bit,因此播放時一樣採用該方式
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode= AudioTrack.MODE_STREAM;
//計算最小buffer大小
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
byte data[] = new byte[minBufferSize];
//構造AudioTrack 不能小於AudioTrack的最低要求,也不能小於咱們每次讀的大小
mAudioTrack = new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,data.length),mode);
//從文件流讀數據
try{
//循環讀數據,寫到播放器去播放
mAudioPlayInputStream = new FileInputStream(audioFile);
//循環讀數據,寫到播放器去播放
int read;
//只要沒讀完,循環播放
mAudioTrack.play();
while (mIsPlaying){
int ret = 0;
read = mAudioPlayInputStream.read(data);
if (read > 0){
ret = mAudioTrack.write(data,0,read);
}
//mAudioFileOutput.write(data,0,read);
//檢查write的返回值,處理錯誤
switch (ret){
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
Log.d(TAG, "doPlay: 失敗,錯誤碼:"+ret);
return;
default:
break;
}
}
}catch (Exception e){
e.printStackTrace();
//讀取失敗
Log.d(TAG, "doPlay: 失敗");
}finally {
stopPlay();
Log.d(TAG, "結束播放");
}
}
public void stopPlay() {
mIsPlaying = false;
//播放器釋放
if (mAudioTrack != null) {
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
//關閉文件輸入流
if (mAudioPlayInputStream != null) {
try {
mAudioPlayInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
複製代碼
最後貼上全部的代碼:
public class AudioUtil {
private static final String TAG = "AudioUtil";
private AudioRecord audioRecord = null; // 聲明 AudioRecord 對象
private int recordBufSize = 0; // 聲明recoordBufffer的大小字段
//全部android系統都支持 採樣率:採樣率越高,聽到的聲音和看到的圖像就越連貫
// 基本上高於44.1kHZ採樣的聲音,絕大部分人已經覺察不到其中的分別了
private int sampleRate = 44100;
//單聲道輸入
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
//PCM_16是全部android系統都支持的 16位的聲音就是人類能聽到的極限了,再高就聽不見了 位數越高聲音越清晰
private int autioFormat = AudioFormat.ENCODING_PCM_16BIT;
private long mStartTimeStamp;
private File mAudioFile;
private String mPath = ContentValue.MAIN_PATH + "/AudioSimple/";
private FileOutputStream mAudioFileOutput; //存儲錄音文件
private FileInputStream mAudioPlayInputStream; //播放錄音文件
private boolean isRecording = false;
private static AudioUtil audioUtil;
private boolean mIsPlaying;
private AudioTrack mAudioTrack;
private String mRecordFileName; //錄音保存的文件路徑
private String mPlayFileName; //播放錄音的文件路徑
private Runnable mAudioRunnableTask = new Runnable() {
@Override
public void run() {
boolean result = startAudioRecord(mRecordFileName);
if (result){
Log.e(TAG, "錄音結束");
}else {
Log.e(TAG, "錄音失敗");
}
}
};
private Runnable mAudioPlayRunnableTask = new Runnable() {
@Override
public void run() {
File file = new File(mPlayFileName);
doPlay(file);
}
};
private AudioUtil(){}
public static AudioUtil getInstance(){
if (audioUtil == null){
synchronized (AudioUtil.class){
if (audioUtil == null){
audioUtil = new AudioUtil();
}
}
}
return audioUtil;
}
private AudioEncoder mAudioEncoder;
private boolean startAudioRecord(String fileName) {
isRecording = true;
mStartTimeStamp = System.currentTimeMillis();
mAudioFile = new File(mPath+fileName+mStartTimeStamp+".pcm");
if (!mAudioFile.getParentFile().exists()){
mAudioFile.getParentFile().mkdirs();
}
try {
mAudioFile.createNewFile();
//建立文件輸出流
mAudioFileOutput = new FileOutputStream(mAudioFile);
//計算audioRecord能接受的最小的buffer大小
recordBufSize = AudioRecord.getMinBufferSize(sampleRate,
channelConfig,
autioFormat);
Log.e(TAG, "最小的buffer大小: " + recordBufSize);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleRate,
channelConfig,
autioFormat, recordBufSize);
//初始化一個buffer 用於從AudioRecord中讀取聲音數據到文件流
byte data[] = new byte[recordBufSize];
//開始錄音
audioRecord.startRecording();
while (isRecording){
//只要還在錄音就一直讀取
int read = audioRecord.read(data,0,recordBufSize);
if (read > 0){
mAudioFileOutput.write(data,0,read);
}
}
//stopRecorder();
} catch (IOException e) {
e.printStackTrace();
stopAudioRecord();
return false;
}
return true;
}
public void doPlay(File audioFile) {
if(audioFile !=null){
mIsPlaying = true;
//配置播放器
//音樂類型,揚聲器播放
int streamType= AudioManager.STREAM_MUSIC;
//錄音時採用的採樣頻率,因此播放時一樣的採樣頻率
int sampleRate=44100;
//單聲道,和錄音時設置的同樣
int channelConfig=AudioFormat.CHANNEL_OUT_MONO;
//錄音時使用16bit,因此播放時一樣採用該方式
int audioFormat=AudioFormat.ENCODING_PCM_16BIT;
//流模式
int mode= AudioTrack.MODE_STREAM;
//計算最小buffer大小
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate,channelConfig,audioFormat);
byte data[] = new byte[minBufferSize];
//構造AudioTrack 不能小於AudioTrack的最低要求,也不能小於咱們每次讀的大小
mAudioTrack = new AudioTrack(streamType,sampleRate,channelConfig,audioFormat,
Math.max(minBufferSize,data.length),mode);
//從文件流讀數據
try{
//循環讀數據,寫到播放器去播放
mAudioPlayInputStream = new FileInputStream(audioFile);
//循環讀數據,寫到播放器去播放
int read;
//只要沒讀完,循環播放
mAudioTrack.play();
while (mIsPlaying){
int ret = 0;
read = mAudioPlayInputStream.read(data);
if (read > 0){
ret = mAudioTrack.write(data,0,read);
}
//mAudioFileOutput.write(data,0,read);
//檢查write的返回值,處理錯誤
switch (ret){
case AudioTrack.ERROR_INVALID_OPERATION:
case AudioTrack.ERROR_BAD_VALUE:
case AudioManager.ERROR_DEAD_OBJECT:
Log.d(TAG, "doPlay: 失敗,錯誤碼:"+ret);
return;
default:
break;
}
}
}catch (Exception e){
e.printStackTrace();
//讀取失敗
Log.d(TAG, "doPlay: 失敗");
}finally {
stopPlay();
Log.d(TAG, "結束播放");
}
}
}
public void startRecord(String fileName){
this.mRecordFileName = fileName;
new Thread(mAudioRunnableTask).start();
}
public void startPlay(String fileName){
this.mPlayFileName = fileName;
new Thread(mAudioPlayRunnableTask).start();
}
public boolean stopAudioRecord(){
isRecording = false;
if (audioRecord != null){
audioRecord.stop();
audioRecord.release();
audioRecord = null;
}
if (mAudioEncoder != null){
mAudioEncoder.stopEncodeAac();
}
try {
mAudioFileOutput.flush();
mAudioFileOutput.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean isRecording(){
return isRecording;
}
public boolean isPlaying(){
return mIsPlaying;
}
public void stopPlay(){
mIsPlaying = false;
//播放器釋放
if(mAudioTrack != null){
mAudioTrack.stop();
mAudioTrack.release();
mAudioTrack = null;
}
//關閉文件輸入流
if(mAudioPlayInputStream !=null){
try {
mAudioPlayInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
private AudioListener mAudioListener;
public void setAudioListener(AudioListener listener){
this.mAudioListener = listener;
}
public interface AudioListener{
void onRecordFinish();
void onPlayFinish();
}
}
複製代碼