自定义音频采集

ARTC SDK提供了灵活的自定义音频采集功能,此功能不仅支持用户自行管理音频设备,还允许在阿里云管理音频采集机制与用户管理音频采集机制之间的动态切换。

示例代码

Android端自定义音频采集Android/ARTCExample/AdvancedUsage/src/main/java/com/aliyun/artc/api/advancedusage/CustomAudioCaptureAndRender/CustomAudioCaptureActivity.java

iOS端自定义音频采集iOS/ARTCExample/AdvancedUsage/CustomAudioCapture/CustomAudioCaptureVC.swift

前提条件

在设置视频配置之前,请确保达成以下条件:

功能实现

image

1.打开或关闭内部采集

Android

/* 动态关闭阿里内部采集 */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}";
mAliRtcEngine.setParameter(parameter);

/* 动态打开阿里内部采集 */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}"; 
mAliRtcEngine.setParameter(parameter);

iOS

/* 动态关闭阿里内部采集 */
[_engine setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}"];

/* 动态打开阿里内部采集 */
[_engine setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}"];

Windows

/* Windows支持创建时指定开启/关闭音频采集 */
/* 关闭阿里内部采集 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"TRUE\", \"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);

/* 开启阿里内部采集 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"FALSE\", \"user_specified_use_external_audio_record\":\"FALSE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);

2.添加外部音频流

建议的调用时序为:

  • 在 onAudioPublishStateChanged 回调中收到推流成功后再调用addExternalAudioStream并开启麦克风采集。

  • 或者,在onAudioPublishStateChanged收到推流成功后再开始调用 pushExternalAudioStreamRawData向 SDK 传入数据,注意要清除提前开启采集累积的音频数据。

Android

@Override
public void onAudioPublishStateChanged(AliRtcEngine.AliRtcPublishState oldState , AliRtcEngine.AliRtcPublishState newState, int elapseSinceLastState, String channel){
    super.onAudioPublishStateChanged(oldState, newState, elapseSinceLastState, channel);
    // 推荐在推流成功后再调用addExternalAudioSource接口
    if(newState == AliRtcEngine.AliRtcPublishState.AliRtcStatsPublished) {
        startAudioCapture();
    }
}
// 开启外部音频采集
private void startAudioCapture() {
    // 根据业务场景进行配置
    AliRtcEngine.AliRtcExternalAudioStreamConfig config = new AliRtcEngine.AliRtcExternalAudioStreamConfig();
    config.sampleRate = SAMPLE_RATE;
    config.channels = CHANNEL;
    config.publishVolume = 100;
    // 本地播放音量
    config.playoutVolume = isLocalPlayout ? 100 : 0;
    config.enable3A = true;

    int result = mAliRtcEngine.addExternalAudioStream(config);
    if (result <= 0) {
        return;
    }
    // 返回值为streamid
    mExternalAudioStreamId = result;

    isPushingAudio = true;
    if (isMicrophoneCapture) {
        // 启动麦克风采集输入
        startMicrophoneCapture();
    } else {
        // 启动音频文件输入
        startFileAudioCapture();
    }
}

iOS

/* 根据自己的业务设置对应的参数 */
AliRtcExternalAudioStreamConfig *config = [AliRtcExternalAudioStreamConfig new];
//需要和外部PCM音频流的声道数相同,如果是单声道设置成1,双声道设置成2
config.channels = _pcmChannels;
//需要和外部PCM音频流的采样率相同
config.sampleRate = _pcmSampleRate;
config.playoutVolume = 0;
config.publishVolume = 100;
_externalPlayoutStreamId = [self.engine addExternalAudioStream:config];

Windows

/* 获取媒体引擎 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
    
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);
/* 根据自己的业务设置对应的参数 */
AliEngineExternalAudioStreamConfig config;
config.playoutVolume = currentAudioPlayoutVolume;
config.publishVolume = currentAudioPublishVolume;
config.channels = 1;
config.sampleRate = 48000;
config.publishStream = 0;
audioStreamID = mAliRtcMediaEngine->AddExternalAudioStream(config);

mAliRtcMediaEngine->Release();

3.向音频流内送入PCM数据

调用pushExternalAudioStreamRawData接口将采集到的音频数据传入 SDK。

说明
  • 需要按照数据的实际长度设置 AliRtcAudioFrame 的numSamples。部分设备上例如通过 AudioRecord.read 获取的音频数据长度可能小于传入的 buffer,需要根据返回值确定真实的音频数据长度。

  • 调用pushExternalAudioStreamRawData传入数据时,可能出现内部缓冲区满而导致失败的情形,需要进行处理并重传。

Android

@Override
public void onAudioFrameCaptured(byte[] audioData, int bytesRead, int sampleRate, int channels, int bitsPerSample) {
    if (mAliRtcEngine != null && bytesRead > 0) {
        // 构造AliRtcAudioFrame对象
        AliRtcEngine.AliRtcAudioFrame sample = new AliRtcEngine.AliRtcAudioFrame();
        sample.data = audioData;
        sample.numSamples = bytesRead / (channels * (bitsPerSample / 8)); // 根据实际读取的字节数计算样本数
        sample.numChannels = channels;
        sample.sampleRate = sampleRate;
        sample.bytesPerSample = bitsPerSample / 8;

        int ret = mAliRtcEngine.pushExternalAudioStreamRawData(mExternalAudioStreamId, sample);

        if (ret != 0) {
            if (ret == ErrorCodeEnum.ERR_SDK_AUDIO_INPUT_BUFFER_FULL) {
                // 处理缓冲区满的情况, 等待一段时间再重试,一般最多重试几百ms
                int retryCount = 0;
                final int MAX_RETRY_COUNT = 20;
                final int BUFFER_FULL_WAIT_MS = 30;
                while (ret == ErrorCodeEnum.ERR_SDK_AUDIO_INPUT_BUFFER_FULL && retryCount < MAX_RETRY_COUNT) {
                    if(!isPushingAudio) {
                        break;
                    }
                    try {
                        Thread.sleep(BUFFER_FULL_WAIT_MS);
                        ret = mAliRtcEngine.pushExternalAudioStreamRawData(mExternalAudioStreamId, sample);
                        retryCount++;
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }

                // 如果重试后仍然失败,记录日志
                if (ret != 0) {
                    Log.w("CustomAudioCapture", "推送音频数据失败,错误码: " + ret + ",重试次数: " + retryCount);
                }
            } else {
                // 处理其他错误情况
                Log.e("CustomAudioCapture", "推送音频数据失败,错误码: " + ret);
            }
        }
    }
}

iOS

let sample = AliRtcAudioFrame()
sample.dataPtr = UnsafeMutableRawPointer(mutating: pcmData)
sample.samplesPerSec = pcmSampleRate
sample.bytesPerSample = Int32(MemoryLayout<Int16>.size)
sample.numOfChannels = pcmChannels
sample.numOfSamples = numOfSamples

var retryCount = 0

while retryCount < 20 {
    if !(pcmInputThread?.isExecuting ?? false) {
        break
    }
    
    let rc = rtcEngine?.pushExternalAudioStream(externalPublishStreamId, rawData: sample) ?? 0
    
    // 0x01070101 SDK_AUDIO_INPUT_BUFFER_FULL 缓冲区满了需要重传
    if rc == 0x01070101 && !(pcmInputThread?.isCancelled ?? true) {
        Thread.sleep(forTimeInterval: 0.03) // 30ms
        retryCount += 1;
    } else {
        if rc < 0 {
            "pushExternalAudioStream error, ret: \(rc)".printLog()
        }
        break
    }
}

Windows

/* 获取媒体引擎 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
    
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);

AliEngineAudioRawData rawData;
rawData.dataPtr = frameInfo.audio_data[0];
rawData.numOfSamples = (int) (frameInfo.audio_data[0].length / (2 * frameInfo.audio_channels));
rawData.bytesPerSample = 2;
rawData.numOfChannels = frameInfo.audio_channels;
rawData.samplesPerSec = frameInfo.audio_sample_rate;
int ret = mAliRtcMediaEngine->PushExternalAudioStreamRawData(audioStreamID, rawData);
mAliRtcMediaEngine->Release();

4.移除外部音频流

Android

mAliRtcEngine.removeExternalAudioStream(mExternalAudioStreamId);

iOS

[self.engine removeExternalAudioStream:_externalPublishStreamId];

Windows

/* 获取媒体引擎 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
    
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);

mAliRtcMediaEngine->RemoveExternalAudioStream(audioStreamID);
mAliRtcMediaEngine->Release();