开发音频插件

  • Android

更新时间不可用

了解云市场在不同平台上的开发音频插件方式,包括接入流程、关键配置和平台差异。

本文展示如何使用声网 SDK 提供的 API 开发一个音频插件。开发音频插件需要使用以下接口:

  • IAudioFilter:实现音频数据的处理能力,包括接收、处理和返回处理完的音频帧。
  • IExtensionProvider:将音频数据的处理能力封装为插件。

前提条件

开发前,请确保你的开发环境满足以下要求:

  • Android Studio 3.0 或以上版本。
  • Android API 级别 16 或以上。

准备开发环境

参考如下步骤将声网云市场的 API 添加到你的项目中:

  1. 下载视频 SDK v4.x,然后解压下载的 SDK 包。
  2. 将 SDK 包中的 /rtc/sdk/low_level_api/include 路径下的头文件拷贝至你的项目文件夹中。

开发音频插件

实现插件

音频插件通过 IAudioFilter 接口实现。该接口位于 NGIAgoraMediaNode.h 文件中。你需要实现 IAudioFilter 接口类的如下方法:

adaptAudioFrame

处理音频帧。该方法是 IAudioFilter 类的核心方法。调用该方法后,SDK 处理 inAudioFrame 中的音频数据,然后通过 adaptedFrame 返回处理后的数据。该方法目前仅支持输入输出 PCM 格式的音频数据。

C++
virtual bool adaptAudioFrame(const media::base::AudioPcmFrame& inAudioFrame,
                             media::base::AudioPcmFrame& adaptedFrame) = 0;
参数描述
inAudioFrame输入参数。待处理的音频帧。
adaptedFrame输出参数。处理后的音频帧。
表格内容超出当前宽度,可横向滚动查看更多列。

setEnabled

启用或关闭音频插件。

C++
virtual void setEnabled(bool enable) {}
参数描述
enable

是否启用音频插件:

  • true: 启用音频插件。
  • false: (默认) 关闭音频插件。
表格内容超出当前宽度,可横向滚动查看更多列。

isEnabled

检查音频插件是否已启用。

C++
virtual bool isEnabled() { return true; }

返回值

是否启用音频插件:

  • true: 启用音频插件。
  • false: (默认) 关闭音频插件。

setProperty

设置音频插件属性。App 开发者调用 setExtensionProperty 时,SDK 会调用该方法。你需要返回音频插件的属性。

C++
size_t setProperty(const char* key, const void* buf, size_t buf_size)
参数描述
key插件属性的 key。
buf插件属性 key 值对应的 buffer 地址,数据形式为 JSON 字符串。我们推荐使用第三方的 nlohmann/json library 开源库,帮助实现 C++ 的 struct 和 JSON 字符串之前的序列和反序列化。
buf_size插件属性 buffer 的内存大小。
表格内容超出当前宽度,可横向滚动查看更多列。

getProperty

获取音频插件属性。App 开发者调用 getProperty 时,SDK 会调用该方法获取音频插件的属性。

C++
size_t getProperty(const char* key, char* property, size_t buf_size)
参数描述
key插件属性的 key。
property插件属性指针。
buf_size插件属性 buffer 的内存大小。
表格内容超出当前宽度,可横向滚动查看更多列。

getName

查询插件的名称。你需要在该方法中返回使用的音频插件的名称。

C++
virtual const char * getName() const = 0;

getPreferredSampleRate

查询音频插件需要的音频采样率。

该方法为可选。如果你在该方法中返回一个指定的采样率,SDK 会将音频数据重采样成指定的采样率,然后传入音频插件。

C++
virtual int getPreferredSampleRate()  { return 0; };

getPreferredChannelNumbers

查询音频插件需要的音频声道数。

该方法为可选。如果你在该方法中返回一个指定的声道数,SDK 会将音频数据重采样成指定的声道数,然后传入音频插件。

C++
virtual int getPreferredChannelNumbers()  { return 0; };

示例代码

参考如下示例代码了解如何使用上述方法实现一个音频插件:

C++
// 在收到需要处理的音频数据后,调用 adaptAudioFrame 方法,对收到的音频数据进行处理。
bool ExtensionAudioFilter::adaptAudioFrame(const media::base::AudioPcmFrame& inAudioPcmFrame,
                                           media::base::AudioPcmFrame& adaptedPcmFrame) {
    return audioProcess_->processFrame(inAudioPcmFrame, adaptedPcmFrame) == 0;
}

// 调用 setProperty 设置音频插件参数。
int ExtensionAudioFilter::setProperty(const char* key, const void* buf, int buf_size) {
    std::string str_volume = "100";
    if (std::string(key) == "volume") {
        str_volume = std::string(static_cast<const char*>(buf), buf_size);
    }

    int int volume_ = atoi(str_volume.c_str());
    audioProcessor_->setVolume(int_volume_);
    return ERR_OK;
}

// 调用 getProperty 查询音频插件参数。
int ExtensionAudioFilter::getProperty(const char* key, void* buf, int buf_size) const override {return ERR_OK; }

// 调用 setEnabled 开启音频插件。
void ExtensionAudioFilter::setEnabled(bool enable) override { enabled_ = enable; }

// 调用 isEnabled 查询音频插件开启状态。
bool ExtensionAudioFilter::isEnabled() const override {return enabled_; }

// 在 getName 的返回值中传入插件名称。
const char* ExtensionAudioFilter::getName() const override { return "YourExtensionName"; }

// (可选)在 getPreferredSampleRate 的返回值中指定音频插件需要的音频采样率。
const char* ExtensionAudioFilter::getPreferredSampleRate() override { return 48000; }

// (可选)在 getPreferredChannelNumbers 的返回值中指定音频插件需要的音频声道数。
int ExtensionAudioFilter::getPreferredChannelNumbers() override { return 2; };

封装插件

封装插件通过 IExtensionProvider 接口类实现。该接口类位于 NGIAgoraExtensionProvider.h 文件中。你需要先实现这个接口类,并实现如下方法:

IExtensionProvider 是插件动态库的提供者,由全局唯一的名字标识。每一个插件动态库可以提供一个或多个有不同功能的插件,如音频前处理插件库可以提供不同功能的音频插件(Audio filter)。每个插件是一个具体的 Extension 对象。

setExtensionControl

设置插件控制。

C++
virtual void setExtensionControl(IExtensionControl* control)

成功调用该方法后,你需要保存 SDK 在该方法中返回的 IExtensionControl 对象。该对象用于触发事件或日志,实现插件与 App 之间的交互。例如,如果你调用了 IExtensionControl 中的 fireEvent 方法:

C++
void ByteDanceProcessor::dataCallback(const char* data){
    if (control_ != nullptr) {
        control_->fireEvent(id_, "beauty", data);
    }
}

App 层在初始化 RtcEngine 时注册的 IMediaExtensionObserver 实例会收到该信息,并在 App 层触发如下回调:

C++
@Override
public void onEvent(String vendor, String key, String value) {
// 其中的 vendor 为注册插件时的 VENDOR_NAME,key/value 是插件消息的键值对
...
}

enumerateExtensions

提供所有支持封装的插件的信息。SDK 在加载插件时,会调用该方法向插件发送回调。收到该回调后,你需要通过返回值提供所有支持封装的插件的信息。

C++
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
参数描述
extension_list

插件的信息,包括插件类型和插件名称。

支持的音频插件类型有:

  • AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER(上行)
  • AUDIO_POST_PROCESSING_FILTER(上行)
  • AUDIO_REMOTE_USER_PLAYBACK_FILTER(上行)

插件名由插件服务商指定,需要在 IExtensionProvider 对象内唯一。插件名为 String 型,支持如下字符:

  • 26 个小写字母 a~z
  • 数字和下划线(不能是第一个或最后一个) 如果插件名不符合规则,会无法通过编译。
extension_count支持封装的插件的总数量。
表格内容超出当前宽度,可横向滚动查看更多列。

其中,插件的信息定义如下:

C++
// 插件类型指插件在音视频传输通道中的位置,分类如下:
enum EXTENSION_TYPE {
    // 已废弃,请改用 AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER 或 AUDIO_POST_PROCESSING_FILTER
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 用于处理本地采集(如耳返采集)的,用于本地播放的音频数据
    AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER = 10000,
    // 经过 3A 算法后的本地音频数据后处理
    AUDIO_POST_PROCESSING_FILTER = 10001,
    // 用于对远端用户的音频数据进行处理
    AUDIO_REMOTE_USER_PLAYBACK_FILTER = 10002,
};

// 插件的信息,包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};

如果你在 enumerateExtensions 方法中返回的插件类型为 AUDIO_FILTER,则在 App 开发者初始化 RtcEngine 并创建 IExtensionProvider 对象后,SDK 会调用 createAudioFilter 方法。

createAudioFilter

创建音频插件。SDK 调用该方法后,你需要返回 IAudioFilter 实例。

C++
virtual agora_refptr<IAudioFilter> createAudioFilter()

成功创建 IAudioFilter 实例后,音频插件会在合适的时机通过 IAudioFilter 类对输入的音频数据进行处理。

示例代码

参考如下示例代码了解如何使用上述方法封装音频插件:

C++
void ExtensionProvider::enumerateExtensions(ExtensionMetaInfo* extension_list,
                                            int& extension_count) {
    extension_count = 1;
    ExtensionMetaInfo i;
    i.type = EXTENSION_TYPE::AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER;
    i.extension_name = "YourExtensionName";
    extension_list[0] = i;
}

agora_refptr<agora::rtc::IAudioFilter> ExtensionAudioProvider::createAudioFilter() {
    PRINTF_ERROR("ExtensionAudioProvider::createAudioFilter");
    auto audioFilter = new agora::RefCountedObject<agora::extension::ExtensionAudioFilter>(audioProcessor_);
    return audioFilter;
}

void ExtensionAudioProvider::setExtensionControl(rtc::IExtensionControl* control){
    audioProcessor_->setExtensionControl(control);
}

打包音频插件

完成插件开发后,你需要对其进行注册、打包,并将最终的 .aar 或 .so 文件,连同一个包含了插件名称、服务商名称和 Filter 名称的文件提交给声网进行验证。

注册插件

插件通过宏 REGISTER_AGORA_EXTENSION_PROVIDER 进行注册,该宏位于 AgoraExtensionProviderEntry.h 文件中。

你需要在插件的入口使用这个宏。SDK 在加载插件时,该宏会自动向 SDK 注册你的插件。注意填入 PROVIDER_NAME 时不要填写标点符号。示例:

C++
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::extension::ExtensionProvider);

在 CMakeLists.txt 文件中,按照下表指定 SDK 包中 libagora-rtc-sdk-jni.so 文件的存储路径:

依赖文件存储路径
64 位的 libagora-rtc-sdk-jni.soAgoraWithByteDanceAndroid/agora-bytedance/src/main/agoraLibs/arm64-v8a
32 位的 libagora-rtc-sdk-jni.soAgoraWithByteDanceAndroid/agora-bytedance/src/main/agoraLibs/arm64-v7a
表格内容超出当前宽度,可横向滚动查看更多列。

提供插件信息

新建一个 .java 或 .md 文件,并在其中填入如下信息:

  • EXTENSION_NAME:插件包的名称,如 libagora-bytedance.so 文件的 EXTENSION_NAME 就是 agora-bytedance。
  • EXTENSION_VENDOR_NAME:服务商名称,即插件的 Provider 对象名称,用于在 .cpp 文件中进行注册。
  • EXTENSION_FILTER_NAME:ExtensionProvider.h 文件中定义的 Filter 名称。

示例项目

声网提供一个 Android 插件开发的示例项目 agora-simple-filter 供你参考。