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 (;i12) 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类和它的子类,详细了解视频流的配置、采集和编码的流程。