WebRTC-Android改变音频输入源

前言

该文章面向对WebRTC有一定基础的码农,如果完全没了解过WebRTC的朋友,可以点赞并私信我进行一对一辅导哦。
因为项目需要直播功能,所以用到了WebRTC,但是由于项目本身有一个特殊功能需要一直使用麦克风,而此时直播无法传输声音。经过一番定位后,应该是该特殊功能在底层直接占用了声卡的驱动,导致WebRTC去实例化AudioRecord时卡住,具体卡住的位置是:

public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId) throws IllegalArgumentException {
    ......
 
    int initResult = native_setup( new WeakReference<AudioRecord>(this),
        mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
        mAudioFormat, mNativeBufferSizeInBytes,
        session, ActivityThread.currentOpPackageName(), 0 /*nativeRecordInJavaObj*/);
 
    ......
}

因此如果要该特殊功能和直播都能正常使用,就需要改变WebRTC的音频输入源为从特殊功能获取到的音频PCM裸流。

思路

既然要改变音频输入源,那么首先找一下WebRTC有没有相关接口,找了一圈发现没有,无奈只能继续深挖WebRTC究竟是在哪里采集音频数据。

最终找到了org.webrtc.audio.WebRtcAudioRecord.java这个类(具体的音频采集流程参考:https://blog.piasy.com/2018/09/14/WebRTC-ADM/index.html#%E9%9F%B3%E9%A2%91%E9%87%87%E9%9B%86)。

大致流程就是:

  1. 创建AudioRecord实例
  2. 创建ByteBuffer实例并且native层缓存ByteBuffer的地址
  3. 开启采集线程
  4. 在采集线程中不断读取AudioRecord的数据到ByteBuffer中,再把ByteBuffer的数据传递给native层

到这里,思路就明确了,关键核心就是在这个ByteBuffer,要改变音频输入源,只需要把音频数据填充到这个ByteBuffer就可以了。

流程

既然找到了思路,那么我们就开始对WebRTC的SDK进行改造,首先目前要修改的目标已经很明确了,就是org.webrtc.audio.WebRtcAudioRecord.java这个类,那么首先就聊聊要怎么把这个类替换成自己的代码:

  1. WebRTC-Android的SDK实际只有一个jar包和每个平台一个的so文件,那么直接把这个jar和对应平台的so文件(譬如armeabi-v7a)拷贝到自己的module里
  2. 使用压缩工具把jar中org.webrtc.audio.WebRtcAudioRecord相关的class文件全部删掉
  3. 在自己的module下创建一个路径和类名跟org.webrtc.audio.WebRtcAudioRecord一模一样的java文件
  4. 把org.webrtc.audio.WebRtcAudioRecord的代码拷贝到自己刚创建的java文件中

通过上述步骤,我们就成功把WebRTC-Android SDK中的org.webrtc.audio.WebRtcAudioRecord类替换成自己的代码了。

然后我们就改里面的代码就好了,下面是核心代码:

class WebRtcAudioRecord {
    ......
 
    // 自己的音频输入源
    private byte[] mAudioData;
 
	// 特殊功能回调音频数据的回调方法
    @Override
    public void onAudioData(byte[] audioData) {
        this.mAudioData = audioData;
    }
 
    private class AudioRecordThread extends Thread {
        private volatile boolean keepAlive = true;
 
        public AudioRecordThread(String name) {
            super(name);
        }
 
        public void run() {
            Process.setThreadPriority(-19);
            Logging.d("WebRtcAudioRecordExternal", "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo());
            WebRtcAudioRecord.this.doAudioRecordStateCallback(0);
 
            // 开始接收特殊功能的音频数据
            RobotSDK.getInstance().setMediaDataCallback(WebRtcAudioRecord.this);
 
            byte[] feedingAudioData = null;
            while (this.keepAlive) {
                while (feedingAudioData == mAudioData || mAudioData == null) {
                    // wait for new audio data
                }
 
                feedingAudioData = mAudioData;
                byteBuffer.clear();
                int offset = 0;
                int length;
                // 因为从特殊功能接收到的音频字节数组的长度固定是1024,而byteBuffer的长度是320
                // 所以要做个循环分段把数据填充给byteBuffer
                while (offset < feedingAudioData.length) {
                    length = Math.min(byteBuffer.capacity(), feedingAudioData.length - offset);
                    byteBuffer.position(0);
                    if (microphoneMute) {
                        byteBuffer.clear();
                        byteBuffer.put(emptyBytes);
                    } else {
                        // 填充音频数据给byteBuffer
                        byteBuffer.put(Arrays.copyOfRange(feedingAudioData, offset, length + offset));
                    }
                    offset += length;
 
                    if (this.keepAlive) {
                        // 把byteBuffer的数据传递给native层
                        nativeDataIsRecorded(nativeAudioRecord, length);
                    }
 
                    if (audioSamplesReadyCallback != null) {
                        byte[] data = Arrays.copyOfRange(byteBuffer.array(),
                                byteBuffer.arrayOffset(), byteBuffer.capacity() + byteBuffer.arrayOffset());
                        audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
                                new JavaAudioDeviceModule.AudioSamples(audioFormat, audioChannelCount, audioSampleRate, data));
                    }
                }
 
            }
 
            WebRtcAudioRecord.this.byteBuffer = null;
            WebRtcAudioRecord.this.doAudioRecordStateCallback(1);
 
            // 停止接收特殊功能的音频数据
            RobotSDK.getInstance().setMediaDataCallback(null);
        }
 
        public void stopThread() {
            Logging.d("WebRtcAudioRecordExternal", "stopThread");
            this.keepAlive = false;
        }
    }
 
    ......
}

完美结束~

你可能感兴趣的:(WebRTC,java,android)