1.使用AudioRecord錄制原始音頻
除了通過意圖啟動錄音機和使用MediaRecorder之外,Android還提供了第三種方法來捕獲音頻:使用成為AudioRecord的類。AudioRecord是三種方法裡最靈活的(因為允許通路原始音頻流),但是它擁有的内置功能也是最少的,如不會自動壓縮音頻。
使用AudioRecord的基礎知識非常簡單。我們隻需要構造一個AudioRecord類型的對象,并傳入各種不同的配置參數。
需要指定的第一個值是音頻源。下面使用的值與用于MediaRecorder的值相同,其在MediaRecorder.AudioSource中定義。實際上,這意味着可以使用MediaRecorder.AudioSource.MIC。
int audioSource=MediaRecorder.AudioSource.MIC;
需要指定的第二個值是 錄制的采樣率。以赫茲(Hz)為機關指定它。MediaRecorder的采樣音頻是8kHz,而CD品質的音頻通常是44.1Hz。不同的安卓手機硬體能夠以不同的采樣率進行采樣。而本示例則以11025Hz的采樣率進行采樣,這是另外一個常用的采樣率。
int frequency = 11025;
接下來,需要指定的第三個值是 捕獲的音頻通道的數量。在AudioFormat類中指定了用于此參數的常量,而且可根據名稱了解它們。
AudioFormat.CHANNEL_CONFIGURATION_MONO;//channelConfiguration
AudioFormat.CHANNEL_CONFIGURATION_STEREO;
AudioFormat.CHANNEL_CONFIGURATION_INVALID;
AudioFormat.CHANNEL_CONFIGURATION_DEFAULT;
PCM代表脈沖編碼調制,它實際上是原始的音頻樣本。16位将占用更多的空間和處理能力,但是表示的音頻将更接近真實。
最後,需要指定的是第五個值是緩沖區大小。實際上可以查詢AudioRecord類以獲得最小緩沖區大小,查詢方式是調用getMinBufferSize靜态方法,同時傳入采樣率、通道配置以及音頻格式。
int bufferSize = AudioTrack.getMinBufferSize(frequency,channelConfiguration, audioEncoding);
完成了以上步驟,我們就可以構造實際的AudioRecord對象。
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
channelConfiguration, audioEncoding, bufferSize);
但值得注意的是,AudioRecord類實際上 并不儲存捕獲的音頻,是以需要手動儲存捕獲的音頻。可以首先利用以下方法來建立一個檔案。
File path = new File(
Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/AudioRecorder/files/");
path.mkdirs();
try
{
recordingFile = File.createTempFile("recording", ".pcm", path);
} catch (IOException e)
{
throw new RuntimeException("Couldn't create file on SD card", e);
}
接下來再建立該檔案對應的OutputStream輸出流,尤其是出于 性能和便利的原因,可以将它包裝在BufferedOutputStream和DataOutputStream中。
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(
recordingFile)));
現在就可以啟動捕獲,同時将音頻樣本寫入到檔案中。可以使用short數組來儲存從AudioRecord對象讀取而來的音頻。同時, 将采用比AudioRecord對象的緩沖區更小的數組,進而在確定将音頻讀出來之前緩沖區沒有被填滿。
為了確定此數組小于緩沖區大小,需要将緩沖區大小除以4.因為緩沖區的大小以位元組為機關,而每個short類型的資料占用2個位元組,是以除以2并不足夠。是以除以4就剛好使得該數組是AudioRecord對象内部緩沖區大小的一半了。
short[] audiodata = new short[bufferSize / 4];
至此,隻需要調用AudioRecord對象上的startRecording方法即可。
錄制開始之後,可以構造一個循環,不斷從AudioRecorder對象讀取音頻并放入short數組中,同時寫入對應檔案的DataOutputStream
while (isRecording)
{
int bufferReadResult = audioRecord.read(buffer, 0,bufferSize);
for (int i = 0; i < bufferReadResult; i++)
{
dos.writeShort(buffer[i]);
}
}
當想結束錄音的時候,調用AudioRecord對象上的stop方法和DataOutputStream上的close方法。
2.使用AudioTrack來播放原始音頻
AudioTrack允許播放AudioRecord捕獲的原始音頻類,而它們并不能使用MediaPlayer對象播放。
為了構造一個AudioTrack對象,需要傳入以下一系列配置變量來描述待播放的音頻
第一個參數是流類型。可能的值定義為AudioManager類的常量。例如
AudioManager.STREAM_MUSIC//正常播放音樂的音頻流
第二個參數是播放音頻資料的采樣率,這裡的參數需要和AudioRecord指定的參數一緻。
第三個參數是通道配置。可能的值與構造AudioRecord的值相同。
第四個參數是音頻格式。可能的值與構造AudioRecord的值相同。
第五個參數是将在對象中用于存儲音頻的緩沖區大小。為了确定使用最小的緩沖區大小,可以調用getMinBufferSize方法,同時傳入采樣率、通道配置和音頻格式。
最後一個參數是模式。可能的值定義為AudioTrack類中的常量。
AudioTrack.MODE_STATIC://在播放發生之前将所有的音頻資料轉移到AudioTrack對象
AudioTrack.MODE_STREAM://在播放的同時将音頻資料持續地轉移到AudioTrack對象。
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, frequency,
channelConfiguration, audioEncoding, bufferSize,
AudioTrack.MODE_STREAM);
構造了AudioTrack對象之後,就需要打開音頻源,将音頻資料讀取到緩沖區中,并将它傳遞給AudioTrack對象。我們可以根據一個包含了正确格式的原始PCM資料的檔案,構造DataInputStream。
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
recordingFile)));
然後調用AudioTrack對象上的play方法,并開始從DataInputStream讀取音頻。
audioTrack.play();
while (isPlaying && dis.available() > 0)
{
int i = 0;
while (dis.available() > 0 && i < audiodata.length)
{
audiodata[i] = dis.readShort();
i++;
}
audioTrack.write(audiodata, 0, audiodata.length);
}
dis.close();
下面是一個完整的示例,通過使用異步任務,每個操作都在它們各自的線程中完成,是以并不會阻礙UI線程。
public class AltAudioRecorder extends Activity implements OnClickListener
{
RecordAudio recordTask;
PlayAudio playTask;
Button startRecordingButton, stopRecordingButton, startPlaybackButton,
stopPlaybackButton;
TextView statusText;
File recordingFile;
boolean isRecording = false;
boolean isPlaying = false;
int frequency = 11025;
int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
statusText = (TextView) this.findViewById(R.id.StatusTextView);
startRecordingButton = (Button) this
.findViewById(R.id.StartRecordingButton);
stopRecordingButton = (Button) this
.findViewById(R.id.StopRecordingButton);
startPlaybackButton = (Button) this
.findViewById(R.id.StartPlaybackButton);
stopPlaybackButton = (Button) this
.findViewById(R.id.StopPlaybackButton);
startRecordingButton.setOnClickListener(this);
stopRecordingButton.setOnClickListener(this);
startPlaybackButton.setOnClickListener(this);
stopPlaybackButton.setOnClickListener(this);
stopRecordingButton.setEnabled(false);
startPlaybackButton.setEnabled(false);
stopPlaybackButton.setEnabled(false);
File path = new File(
Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/Android/data/AudioRecorder/files/");
path.mkdirs();
try
{
recordingFile = File.createTempFile("recording", ".pcm", path);
} catch (IOException e)
{
throw new RuntimeException("Couldn't create file on SD card", e);
}
}
public void onClick(View v)
{
if (v == startRecordingButton)
{
record();
} else if (v == stopRecordingButton)
{
stopRecording();
} else if (v == startPlaybackButton)
{
play();
} else if (v == stopPlaybackButton)
{
stopPlaying();
}
}
public void play()
{
startPlaybackButton.setEnabled(true);
playTask = new PlayAudio();
playTask.execute();
stopPlaybackButton.setEnabled(true);
}
public void stopPlaying()
{
isPlaying = false;
stopPlaybackButton.setEnabled(false);
startPlaybackButton.setEnabled(true);
}
public void record()
{
startRecordingButton.setEnabled(false);
stopRecordingButton.setEnabled(true);
// For Fun
startPlaybackButton.setEnabled(true);
recordTask = new RecordAudio();
recordTask.execute();
}
public void stopRecording()
{
isRecording = false;
}
private class PlayAudio extends AsyncTask<Void, Integer, Void>
{
@Override
protected Void doInBackground(Void... params)
{
isPlaying = true;
int bufferSize = AudioTrack.getMinBufferSize(frequency,
channelConfiguration, audioEncoding);
short[] audiodata = new short[bufferSize / 4];
try
{
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
recordingFile)));
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, frequency,
channelConfiguration, audioEncoding, bufferSize,
AudioTrack.MODE_STREAM);
audioTrack.play();
while (isPlaying && dis.available() > 0)
{
int i = 0;
while (dis.available() > 0 && i < audiodata.length)
{
audiodata[i] = dis.readShort();
i++;
}
audioTrack.write(audiodata, 0, audiodata.length);
}
dis.close();
startPlaybackButton.setEnabled(false);
stopPlaybackButton.setEnabled(true);
} catch (Throwable t)
{
Log.e("AudioTrack", "Playback Failed");
}
return null;
}
}
private class RecordAudio extends AsyncTask<Void, Integer, Void>
{
@Override
protected Void doInBackground(Void... params)
{
isRecording = true;
try
{
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(
recordingFile)));
int bufferSize = AudioRecord.getMinBufferSize(frequency,
channelConfiguration, audioEncoding);
AudioRecord audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, frequency,
channelConfiguration, audioEncoding, bufferSize);
short[] buffer = new short[bufferSize];
audioRecord.startRecording();
int r = 0;
while (isRecording)
{
int bufferReadResult = audioRecord.read(buffer, 0,
bufferSize);
for (int i = 0; i < bufferReadResult; i++)
{
dos.writeShort(buffer[i]);
}
publishProgress(new Integer(r));
r++;
}
audioRecord.stop();
dos.close();
} catch (Throwable t)
{
Log.e("AudioRecord", "Recording Failed");
}
return null;
}
protected void onProgressUpdate(Integer... progress)
{
statusText.setText(progress[0].toString());
}
protected void onPostExecute(Void result)
{
startRecordingButton.setEnabled(true);
stopRecordingButton.setEnabled(false);
startPlaybackButton.setEnabled(true);
}
}
}