AudioStream類
AudioStream類是音頻流的基類,重寫了MediaStream的encodeWithMediaRecorder方法,實現了MediaRecorder錄制音頻的操作。同時作為抽象類,它并沒有重寫encodeWithMediaCodec方法,而是留給子類去具體實現。
@Override
protected void encodeWithMediaRecorder() throws IOException {
// We need a local socket to forward data output by the camera to the packetizer
createSockets();
Log.v(TAG,"Requested audio with "+mQuality.bitRate/1000+"kbps"+" at "+mQuality.samplingRate/1000+"kHz");
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setAudioSource(mAudioSource);
mMediaRecorder.setOutputFormat(mOutputFormat);
mMediaRecorder.setAudioEncoder(mAudioEncoder);
mMediaRecorder.setAudioChannels(1);
mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate);
mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate);
// We write the ouput of the camera in a local socket instead of a file !
// This one little trick makes streaming feasible quiet simply: data from the camera
// can then be manipulated at the other end of the socket
mMediaRecorder.setOutputFile(mSender.getFileDescriptor());
mMediaRecorder.prepare();
mMediaRecorder.start();
try {
// mReceiver.getInputStream contains the data from the camera
// the mPacketizer encapsulates this stream in an RTP stream and send it over the network
mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
mPacketizer.setInputStream(mReceiver.getInputStream());
mPacketizer.start();
mStreaming = true;
} catch (IOException e) {
stop();
throw new IOException("Something happened with the local sockets :/ Start failed !");
}
}
重寫encodeWithMediaRecorder方法,主要是MediaRecorder錄制音頻的操作。首先啟動本地Socket,再對MediaRecorder對象設置音頻來源、音頻編碼、音頻質量等參數,然后開始錄制。這里值得一提的是,MediaRecorder輸出的音頻數據是寫入Socket中而不是文件中,這樣就可以在Socket的另一端進行操作。最后一步是使用Packetizer對象將數據打包并發送到網絡傳輸。
AACStream類
AACStream類是一個關于AAC音頻格式的AudioStream的子類,內部封裝了對AAC音頻格式的配置和編碼操作。
private static boolean AACStreamingSupported() {
if (Build.VERSION.SDK_INT<14) return false;
try {
MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
return true;
} catch (Exception e) {
return false;
}
}
構造函數中會判斷是否支持AAC編碼格式。
// Checks if the user has supplied an exotic sampling rate
int i=0;
for (;i<AUDIO_SAMPLING_RATES.length;i++) {
if (AUDIO_SAMPLING_RATES[i] == mQuality.samplingRate) {
mSamplingRateIndex = i;
break;
}
}
// If he did, we force a reasonable one: 16 kHz
if (i>12) mQuality.samplingRate = 16000;
configure()方法中的部分代碼。在start()開始時需要先檢查一下采樣率配置信息,不符合規范則強行設置默認采樣率。
@Override
protected void encodeWithMediaRecorder() throws IOException {
testADTS();
((AACADTSPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
super.encodeWithMediaRecorder();
}
重寫encodeWithMediaRecorder方法,testADTS()方法是先從麥克風記錄AAC ADTS的簡短樣本,以了解該設備支持的真實的采樣率,便于設置配置信息和防止報錯。篇幅原因,這里就不貼出testADTS()的代碼了。
下面我們開始分析重寫encodeWithMediaCodec()方法里面的內容,我把逐句分析寫在注釋里面。
//計算出緩沖區的大小
final int bufferSize = AudioRecord.getMinBufferSize(mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)*2;
//設置打包器的采樣率
((AACLATMPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
//實例化AudioRecord,參數依次為:聲音來源、采樣率、聲道數、編碼方式、緩沖區
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
//實例化MediaCodec編碼器
mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
//配置信息依次為:格式、位速率、頻道數、采樣率、AAC文件、最大輸入緩沖區
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_BIT_RATE, mQuality.bitRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mQuality.samplingRate);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//開始錄制音頻
mAudioRecord.startRecording();
mMediaCodec.start();
//設置編碼流,在后面的文章會詳細講到
final MediaCodecInputStream inputStream = new MediaCodecInputStream(mMediaCodec);
final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
//開啟一個線程,讀取和處理
mThread = new Thread(new Runnable() {
@Override
public void run() {
int len = 0, bufferIndex = 0;
try {
//無限循環讀取
while (!Thread.interrupted()) {
//從輸入流隊列中取數據進行編碼操作(出隊列)。
bufferIndex = mMediaCodec.dequeueInputBuffer(10000);
if (bufferIndex>=0) {
inputBuffers[bufferIndex].clear();
//從mAudioRecord讀取數據到inputBuffers[bufferIndex]中
len = mAudioRecord.read(inputBuffers[bufferIndex], bufferSize);
if (len == AudioRecord.ERROR_INVALID_OPERATION || len == AudioRecord.ERROR_BAD_VALUE) {
Log.e(TAG,"An error occured with the AudioRecord API !");
} else {
//Log.v(TAG,"Pushing raw audio to the decoder: len="+len+" bs: "+inputBuffers[bufferIndex].capacity());
//輸入流入隊列(往編碼器中添加數據做編碼處理)
mMediaCodec.queueInputBuffer(bufferIndex, 0, len, System.nanoTime()/1000, 0);
}
}
}
} catch (RuntimeException e) {
e.printStackTrace();
}
}
});
mThread.start();
//把編碼完成的數據流封裝打包并進行網絡傳輸
// The packetizer encapsulates this stream in an RTP stream and send it over the network
mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
mPacketizer.setInputStream(inputStream);
mPacketizer.start();
mStreaming = true;
以上可以看到,重寫encodeWithMediaCodec()方法主要流程就是:實例化和配置AudioRecord用來錄取音頻數據,實例化和配置MediaCodec用于對數據編碼,開啟一個線程循環讀取AudioRecord錄取的音頻數據流,并將原始音頻數據流添加到MediaCodec編碼器中進行編碼,然后將編碼完成的數據流通過Packetizer打包器打包并發送出去。
AMRNBStream類
AMRNBStream類是一個關于ANR音頻格式的AudioStream的子類。
@Override
protected void encodeWithMediaCodec() throws IOException {
super.encodeWithMediaRecorder();
}
AMRNBStream可操作的動作不多,這里重寫encodeWithMediaCodec()方法直接指向了super.encodeWithMediaRecorder()。關于AAC和AMR的比較可參考:AAC和AMR音頻編碼標準介紹
至此我們完整的了解了音頻數據流的配置、采集、編碼的整個過程,下一篇我們將分析VideoStream類和它的子類,詳細了解視頻流的配置、采集和編碼的流程。