Tutorial: 如何实现音频自定义处理

如何实现音频自定义处理

本文介绍如何实现音频数据的自定义处理

原理说明

TRTC Electron SDK 从 11.9.604 版本开始,支持以动态库插件的方式,实现用户对音频数据的自定义处理。

实现步骤:

  1. 基于 TRTC Electron SDK 提供的 C++ 头文件,开发音频自定义处理库。
  2. Javascript 层调用 addPlugin() 接口添加音频自定义处理插件,传入构建好的音频自定义处理库文件路径;再用插件的 enable() 接口开启音频自定义处理。

开发音频自定义处理插件(C++)

SDK 提供了两个 C++ 头文件实现音频自定义处理:

  • ITRTCPlugin.h 头文件声明了插件对象的创建、销毁、初始化、设置参数等接口,用于管理插件的生命周期。
  • IAudioFramePlugin.h 头文件定义了监听 SDK 音频自定义回调的接口,用于完成对音频数据的自定义处理。

AudioFramePlugin.zip是一个示例工程,实现了一个简单的音频自定义处理插件,支持 Windows 和 Mac OS 下构建。

ITRTCPlugin.h

class ITRTCPlugin {
public:
    virtual ~ITRTCPlugin() {}

    /**
     * 初始化插件
     *
     * 创建插件后,会立刻触发该初始化函数,用户可以在这里做一些初始化操作。
     * @return true: 插件初始化成功,false: 插件初始化失败。
     *   如果返回 false 表示初始化失败,Javascript 层通过 setPluginCallback() 函数设置回调函数会收到错误码:-4.
     */
    virtual bool init() = 0;

    /**
     * 反初始化插件
     *
     * 插件销毁时,会触发该函数,用户可以在这里做一些清理操作。
     *
     * @return true: 插件反初始化成功,false: 插件反初始化失败。
     */
    virtual bool uninit() = 0;

    /**
     * 资源加载
     *
     * 会在 init() 函数之后被调用。
     *
     * @param path 插件资源目录。该目录为当前插件库文件所在目录,如果插件有其它资源文件依赖,可以放在插件库文件同目录下,在这里加载。
     * @return true: 资源加载成功,false: 资源加载失败。
     *   如果返回 false 表示资源加载失败,Javascript 层通过 setPluginCallback() 函数设置回调函数会收到错误码:-5.
     */
    virtual bool load(const char* path) = 0;

    /**
     * 资源卸载
     *
     * 会在 uninit() 函数之前被调用。
     */
    virtual bool unload() = 0;

    /**
     * 开启/关闭当前插件
     *
     * Javascript 层的 TRTCPluginInfo.enable/disable() 接口调用会触发该函数。
     *
     * @param enabled true: 开启插件,false: 关闭插件。
     * @return true: 成功开启/关闭插件,false: 失败。
     */
    virtual bool enable(bool enabled) = 0;

    /**
     * 设置插件参数
     *
     * Javascript 层的 TRTCPluginInfo.setParameter() 接口调用会触发该函数。
     *
     * @param param 插件参数,格式为 json 字符串。
     * @return true: 设置成功,false: 设置失败。
     */
    virtual bool setParameter(const char* param) = 0;

    /**
     * 获取插件类型
     *
     * 具体的插件子类中需要重写。
     */
    virtual TRTC_PLUGIN_TYPE getPluginType() {
        return TRTC_PLUGIN_TYPE::TRTC_PLUGIN_UNKNOWN;
    }

    /**
     * 设置错误回调函数
     *
     * @param callback 回调函数。是对 Javascript 层 setPluginCallback() 接口设置回调函数的封装。
     */
    void setErrorCallback(PluginErrorCallbackFunc callback, void* param) {
        error_callback_ = callback;
        param_ = param;
    }

    /**
     * 错误码上报函数
     *
     * 该函数调用后,会触发 Javascript 层 setPluginCallback() 接口设置的回调函数,将错误码和错误信息透传给 Javascript 层,用户可以根据错误码做相应处理。
     *
     * @param error 错误码。
     * @param msg 错误描述。
     */
    void onError(int error, const char* msg) {
        if (error_callback_) {
            error_callback_(error, msg, param_);
        }
    }

  private:
      PluginErrorCallbackFunc error_callback_ = nullptr;
      void* param_ = nullptr;
};

// 创建插件实例对象,Javascript 层 addPlugin() 接口最终会调用到该函数
extern "C" EXPORTS ITRTCPlugin* createTRTCPlugin();

// 销毁插件实例对象,Javascript 层 removePlugin() 接口最终会调用到该函数
extern "C" EXPORTS void destoryTRTCPlugin(ITRTCPlugin*);

IAudioFramePlugin.h

struct TRTCAudioFrame {
  int audioFormat;              /// 【字段含义】音频帧的格式。
  char* data;                   /// 【字段含义】音频数据。
  uint32_t length;              /// 【字段含义】音频数据的长度。
  uint32_t sampleRate;          /// 【字段含义】采样率。
  uint32_t channel;             /// 【字段含义】声道数。
  uint64_t timestamp;           /// 【字段含义】时间戳,单位ms。
  char* extraData;              /// 【字段含义】音频额外数据,远端用户通过 `onLocalProcessedAudioFrame` 写入的数据会通过该字段回调。
  uint32_t extraDataLength;     /// 【字段含义】音频消息数据的长度。
};

class IAudioFramePlugin : public ITRTCPlugin {
 public:
  ~IAudioFramePlugin() override {}

  /**
   * 本地采集并经过音频模块前处理后的音频数据回调
   *
   * 当您设置完音频数据自定义回调之后,SDK 内部会把刚采集到并经过前处理(ANS、AEC、AGC)之后的数据,以 PCM 格式的形式通过本接口回调给您。
   * - 此接口回调出的音频时间帧长固定为0.02s,格式为 PCM 格式。
   * - 由时间帧长转化为字节帧长的公式为 `采样率 × 时间帧长 × 声道数 × 采样点位宽`。
   * - 以 TRTC 默认的音频录制格式48000采样率、单声道、16采样点位宽为例,字节帧长为 `48000 × 0.02s × 1 × 16bit = 15360bit = 1920字节`。
   * @param frame PCM 格式的音频数据帧。
   * @note
   * 1. 请不要在此回调函数中做任何耗时操作,由于 SDK 每隔 20ms 就要处理一帧音频数据,如果您的处理时间超过 20ms,就会导致声音异常。
   * 2. 此接口回调出的音频数据是可读写的,也就是说您可以在回调函数中同步修改音频数据,但请保证处理耗时。
   * 3. 此接口回调出的音频数据已经经过了前处理(ANS、AEC、AGC),\\但**不包含**背景音、音效、混响等前处理效果,延迟较低。
   */
  virtual void onCapturedAudioFrame(TRTCAudioFrame* frame) = 0;

  /**
   * 本地采集并经过音频模块前处理、音效处理和混 BGM 后的音频数据回调
   *
   * 当您设置完音频数据自定义回调之后,SDK 内部会把刚采集到并经过前处理、音效处理和混 BGM 之后的数据,在最终进行网络编码之前,以 PCM 格式的形式通过本接口回调给您。
   * - 此接口回调出的音频时间帧长固定为0.02s,格式为 PCM 格式。
   * - 由时间帧长转化为字节帧长的公式为`采样率 × 时间帧长 × 声道数 × 采样点位宽`。
   * - 以 TRTC 默认的音频录制格式48000采样率、单声道、16采样点位宽为例,字节帧长为`48000 × 0.02s × 1 × 16bit = 15360bit = 1920字节`。
   * 特殊说明:
   * 您可以通过设置接口中的 `TRTCAudioFrame.extraData` 字段,达到传输信令的目的。由于音频帧头部的数据块不能太大,建议您写入 `extraData` 时,尽量将信令控制在几个字节的大小,如果超过 100 个字节,写入的数据不会被发送。
   * 房间内其他用户可以通过 ITRTCAudioFrameCallback 中的 `onRemoteUserAudioFrame` 中的 `TRTCAudioFrame.extraData` 字段回调接收数据。
   * @param frame PCM 格式的音频数据帧。
   * @note
   * 1. 请不要在此回调函数中做任何耗时操作,由于 SDK 每隔 20ms 就要处理一帧音频数据,如果您的处理时间超过 20ms,就会导致声音异常。
   * 2. 此接口回调出的音频数据是可读写的,也就是说您可以在回调函数中同步修改音频数据,但请保证处理耗时。
   * 3. 此接口回调出的数据已经经过了前处理(ANS、AEC、AGC)、音效和混 BGM 处理,声音的延迟相比于 onCapturedAudioFrame 要高一些。
   */
  virtual void onLocalProcessedAudioFrame(TRTCAudioFrame* frame) = 0;

  /**
   * 混音前的每一路远程用户的音频数据
   *
   * 当您设置完音频数据自定义回调之后,SDK 内部会把远端的每一路原始数据,在最终混音之前,以 PCM 格式的形式通过本接口回调给您。
   * - 此接口回调出的音频时间帧长固定为0.02s,格式为 PCM 格式。
   * - 由时间帧长转化为字节帧长的公式为`采样率 × 时间帧长 × 声道数 × 采样点位宽`。
   * - 以 TRTC 默认的音频录制格式48000采样率、单声道、16采样点位宽为例,字节帧长为`48000 × 0.02s × 1 × 16bit = 15360bit = 1920字节`。
   * @param frame PCM 格式的音频数据帧。
   * @param userId 用户标识。
   * @note 此接口回调出的音频数据是只读的,不支持修改。
   */
  virtual void onPlayAudioFrame(TRTCAudioFrame* frame, const char* userId) = 0;

  /**
   * 将各路待播放音频混合之后并在最终提交系统播放之前的数据回调
   *
   * 当您设置完音频数据自定义回调之后,SDK 内部会把各路待播放的音频混合之后的音频数据,在提交系统播放之前,以 PCM 格式的形式通过本接口回调给您。
   * - 此接口回调出的音频时间帧长固定为0.02s,格式为 PCM 格式。
   * - 由时间帧长转化为字节帧长的公式为 `采样率 × 时间帧长 × 声道数 × 采样点位宽`。
   * - 以 TRTC 默认的音频录制格式48000采样率、单声道、16采样点位宽为例,字节帧长为 `48000 × 0.02s × 1 × 16bit = 15360bit = 1920字节`。
   * @param frame PCM 格式的音频数据帧。
   * @note
   * 1. 请不要在此回调函数中做任何耗时操作,由于 SDK 每隔 20ms 就要处理一帧音频数据,如果您的处理时间超过 20ms,就会导致声音异常。
   * 2. 此接口回调出的音频数据是可读写的,也就是说您可以在回调函数中同步修改音频数据,但请保证处理耗时。
   * 3. 此接口回调出的是对各路待播放音频数据的混合,但其中并不包含耳返的音频数据。
   */
  virtual void onMixedPlayAudioFrame(TRTCAudioFrame* frame) = 0;

  /**
   * SDK 所有音频混合后的音频数据(包括采集到的和待播放的)
   *
   * 当您设置完音频数据自定义回调之后,SDK 内部会把所有采集到的和待播放的音频数据混合起来,以 PCM 格式的形式通过本接口回调给您,便于您进行自定义录制。
   * - 此接口回调出的音频时间帧长固定为0.02s,格式为 PCM 格式。
   * - 由时间帧长转化为字节帧长的公式为 `采样率 × 时间帧长 × 声道数 × 采样点位宽`。
   * - 以 TRTC 默认的音频录制格式48000采样率、单声道、16采样点位宽为例,字节帧长为 `48000 × 0.02s × 1 × 16bit = 15360bit = 1920字节`。
   * @param frame PCM 格式的音频数据帧。
   * @note
   * 1. 此接口回调出的是SDK所有音频数据的混合数据,包括:经过 3A 前处理、特效叠加以及背景音乐混音后的本地音频,所有远端音频,但不包括耳返音频。
   * 2. 此接口回调出的音频数据不支持修改。
   */
  virtual void onMixedAllAudioFrame(TRTCAudioFrame* frame) = 0;
};

使用音频自定义处理插件(Javascript)

通过 SDK 提供的 addPlugin() 接口创建一个音频自定义处理插件,返回一个 TRTCPluginInfo 对象,调用该对象的 enable() 接口开启音频自定义处理。

import TRTCCloud, { TRTCPluginType, TRTCPluginInfo } from 'trtc-electron-sdk';

const trtcCloud = TRTCCloud.getTRTCShareInstance();

// 开启音频自定义处理回调
trtcCloud.setPluginParams(TRTCPluginType.TRTCPluginTypeAudioProcess, {
  enable: true // 设置为 false 后,会停止发送音视频数据到音频自定义处理插件
});

// 注册插件回调监听
trtcCloud.setPluginCallback((pluginId, errorCode, msg) => {
  console.log(`plugin callback: ${pluginId}, errorCode: ${errorCode}, msg: ${msg}`);
});

// 创建插件
const plugin: TRTCPluginInfo = trtcCloud.addPlugin({
  id: 'custom-audio-frame-process', // 用户必须保证 ID 的唯一性
  path: '', // 构建好的插件文件路径,Windows 下是 ‘.dll’ 文件,MacOS 下是 ‘.dylib’ 文件
  type: TRTCPluginType.TRTCPluginTypeAudioProcess // 自定义音频处理插件类型
});
// 启动插件
plugin.enable();
// 设置插件参数
plugin.setParameter(JSON.stringify({'key1':'value1', 'key2':123}));
  • TRTCCloud.setPluginParams() 接口

    支持通过 enable 参数控制是否开启 SDK 的音频自定义处理功能。

  • TRTCCloud.addPlugin() 接口

    添加插件,依次触发 C++ ITRTCPlugin.h 类中的 createTRTCPlugin()、init()、load() 函数。

  • TRTCCloud.removePlugin() 接口

    删除插件,依次触发 C++ ITRTCPlugin.h 类中的 unload()、uninit()、destoryTRTCPlugin() 函数。

  • TRTCCloud.setPluginCallback() 接口

    设置插件回调函数,监听插件创建、销毁、运行情况。设置的回调函数有两个触发源头:

    1. addPlugin/removePlugin() 时的处理结果通知;
    2. 插件内部 C++ ITRTCPlugin.h 类中的 onError() 函数触发的异常通知。
  • TRTCPluginInfo.enable/disable() 接口

    开启/关闭当前插件,触发 C++ ITRTCPlugin.h 类中的 enable() 函数。

  • TRTCPluginInfo.setParameter() 接口

    给当前插件设置参数,触发 C++ ITRTCPlugin.h 类中的 setParameter() 函数。