本文主要介绍怎么在webrtc中把一个端的音频文件像麦克风录制的音频一样在另外一端中播放,以Android开发为例,pc和ios也可参照这个方法。
Android webrtc中与音频相关的类
JavaAudioDeviceModule:音频设备管理器。在创建该类对象时会生成一个WebRtcAudioRecord对象作为音频输入,一个WebRtcAudioTrack对象作为音频输出。里面还包含一些关于音频的基本设置。
WebRtcAudioManager:提供查询采样率和缓冲区大小的方法。
WebRtcAudioRecord:录制(采集)声音。里面封装了一个AudioRecord对象。是本次讲解的重点。
WebRtcAudioTrack:播放声音。里面封装了一个AudioTrack对象。
WebRtcAudioEffects:包含音频的回声消除和降噪处理。
WebRtcAudioUtils:工具类。打印日志及获取相关音频及设备信息。
各个类之间的关系如下:
修改源码传输本地音频文件流
WebRtcAudioRecord中有一个专门的线程(AudioRecordThread)来采集设备声音,其中关键代码如下:
public void run() {
//设置线程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
Logging.d(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo());
//如果没有开始录制则报错,在audioRecord调用startRecording方法后状态会变为RECORDSTATE_RECORDING
assertTrue(audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING);
// 通知上层RecordState发生了变化
doAudioRecordStateCallback(AUDIO_RECORD_START);
long lastTime = System.nanoTime();
AudioTimestamp audioTimestamp = null;
if (Build.VERSION.SDK_INT >= 24) {
audioTimestamp = new AudioTimestamp();
}
while (keepAlive) {
//读取数据放到byteBuffer中
int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
if (bytesRead == byteBuffer.capacity()) {
//静音的时候清空byteBuffer,可以看出在静音的时候音频还是在录制,只不过没有把数据传出去
if (microphoneMute) {
byteBuffer.clear();
byteBuffer.put(emptyBytes);
}
//有可能在此过程中调用了stopRecording方法,为了更安全一点这里又判断了keepAlive的值
if (keepAlive) {
long captureTimeNs = 0;
if (Build.VERSION.SDK_INT >= 24) {
if (audioRecord.getTimestamp(audioTimestamp, AudioTimestamp.TIMEBASE_MONOTONIC)
== AudioRecord.SUCCESS) {
captureTimeNs = audioTimestamp.nanoTime;
}
}
//用此方法把数据传到底层,这是关键
nativeDataIsRecorded(nativeAudioRecord, bytesRead, captureTimeNs);
}
if (audioSamplesReadyCallback != null) {
// Copy the entire byte buffer array. The start of the byteBuffer is not necessarily
// at index 0.
byte[] data = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.arrayOffset(),
byteBuffer.capacity() + byteBuffer.arrayOffset());
//给上层的回调,不太重要
audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
new JavaAudioDeviceModule.AudioSamples(audioRecord.getAudioFormat(),
audioRecord.getChannelCount(), audioRecord.getSampleRate(), data));
}
} else {
String errorMessage = "AudioRecord.read failed: " + bytesRead;
Logging.e(TAG, errorMessage);
if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {
keepAlive = false;
reportWebRtcAudioRecordError(errorMessage);
}
}
}
try {
//释放资源
if (audioRecord != null) {
audioRecord.stop();
doAudioRecordStateCallback(AUDIO_RECORD_STOP);
}
} catch (IllegalStateException e) {
Logging.e(TAG, "AudioRecord.stop failed: " + e.getMessage());
}
}
为了能够更好的理解,这里有两个关键点需要说一下:
byteBuffer的大小是在initRecording的时候计算出来的,其大小为:声道数*位深*(采样率/每秒发送的buffer个数),不可以在java层单独更改。
在initRecording时调用nativeCacheDirectBufferAddress把byteBuffer的地址传到了底层保存,所以后面在调用nativeDataIsRecorded传送数据的时候并没有把整个buffer传过去,大大提高了效率。
为了读取本地文件内容,我们需要先建立一个File和FileInputStream对象,还需要新建一个byte数组,其大小为byteBuffer的大小。然后把关键代码做如下修改:
while(keepAlive) {
try {
long lastTime = System.currentTimeMillis();
//不断地读取文件内容到bytes数组
while (fileInputStream.read(bytes) != -1) {
//把数组里的内容放到byteBuffer
byteBuffer.put(Arrays.copyOfRange(bytes, 0, byteBuffer.capacity()));
//如果静音,则在byteBuffer里放入空内容
if (microphoneMute) {
byteBuffer.clear();
byteBuffer.put(emptyBytes);
}
// 为了安全性再次判断keepAlive值
if (keepAlive) {
//每十毫秒发送一次数据,十毫秒为类成员变量CALLBACK_BUFFER_SIZE_MS的值
while(System.currentTimeMillis() - lastTime < 10) {
}
lastTime = System.currentTimeMillis();
nativeDataIsRecorded(nativeAudioRecord, byteBuffer.capacity());
}
if (audioSamplesReadyCallback != null) {
byte[] data = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.arrayOffset(),
byteBuffer.capacity() + byteBuffer.arrayOffset());
audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
new JavaAudioDeviceModule.AudioSamples(audioRecord.getAudioFormat(),
audioRecord.getChannelCount(), audioRecord.getSampleRate(), data));
}
if (!microphoneMute) {
//避免发生BufferOverflowException错误
byteBuffer.position(0);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
需要注意的是音频文件的编码方式必须是pcm_s16le的,声道数必须是单声道的。当然编码方式和声道数也是可以自行设置的以匹配音频文件的,这里就不展开讲解了。
以上就是全部内容了,谢谢观看。