文档反馈
文档反馈

原始音频数据处理

NERTC SDK 的音频模块会严格控制声音设备的采集和播放逻辑,同时支持对采集到的音视频原始数据进行自定义的前处理和后处理,获取想要的播放效果。适用于非标设备接入、自定义音频效果、语音处理、语音识别等场景。

NERTC SDK 通过提供 NERtcAudioFrameObserver 类,实现采集、修改原始音频数据功能。

Android

注意事项

实现方法

在使用原始数据处理功能前,请确保您已在项目中实现基本的实时音视频功能。

参考以下步骤,在您的项目中实现原始音频数据处理功能:

  1. 加入频道前调用 setAudioFrameObserver 方法注册语音观测器,并在该方法中实现一个 NERtcEngineAudioFrameObserver 类。
  2. 设置回调的音频采样率。
  3. SDK 返回回调。
  4. 用户拿到音频数据后,根据场景需要自行进行处理。
  5. 完成音频数据处理后,您可以直接进行自播放,或根据场景需求再通过 onRecordFrameonPlaybackFrame 回调发送给 SDK。

示例代码

///设置音频回调参数
NERtcAudioFrameRequestFormat formatMix = new NERtcAudioFrameRequestFormat();
    formatMix.setChannels(channel);
    formatMix.setSampleRate(sampleRate);
    formatMix.setOpMode(readOnly.isChecked() ? NERtcAudioFrameOpMode.kNERtcAudioFrameOpModeReadOnly : NERtcAudioFrameOpMode.kNERtcAudioFrameOpModeReadWrite);
    Log.i(TAG, "AudioCallback ,channel: "+formatMix.getChannels()+ " Mixed Sample:" + formatMix.getSampleRate() + " ReadWrite:" + formatMix.getOpMode());
NERtcEx.getInstance().setMixedAudioFrameParameters(formatMix);
NERtcEx.getInstance().setPlaybackAudioFrameParameters(formatMix);
NERtcEx.getInstance().setRecordingAudioFrameParameters(formatMix);
NERtcEx.getInstance().setAudioFrameObserver(observer);

///音频数据回调处理
observer = new NERtcAudioFrameObserver() {
            @Override
            public void onRecordFrame(NERtcAudioFrame audioFrame) {
                try {
                    if(!isAudioCallbackDump){
                        return;
                    }
                    if (pcmCallbackRecordDump == null) {
                        pcmCallbackRecordDump = createPCMDump("Record_" +audioFrame.getFormat().getChannels()
                                +"_"+ audioFrame.getFormat().getSampleRate());

                        if(pcmCallbackMixDump == null) {
                            Log.e(TAG, "create dump file failed!");
                            return;
                        }
                    }

                    byte[] remaining = new byte[audioFrame.getData().remaining()];
                    audioFrame.getData().get(remaining);
                    pcmCallbackRecordDump.write(remaining);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onPlaybackFrame(NERtcAudioFrame audioFrame) {
                if(!isAudioCallbackDump){
                    return;
                }
                try {

                    if (pcmCallbackPlaybackDump == null) {
                        pcmCallbackPlaybackDump = createPCMDump("PlayBack_" +audioFrame.getFormat().getChannels()
                                +"_"+ audioFrame.getFormat().getSampleRate());
                        if(pcmCallbackMixDump == null) {
                            Log.e(TAG, "create dump file failed!");
                            return;
                        }
                    }

                    byte[] remaining = new byte[audioFrame.getData().remaining()];
                    audioFrame.getData().get(remaining);
                    pcmCallbackPlaybackDump.write(remaining);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onPlaybackAudioFrameBeforeMixingWithUserID(long userID, NERtcAudioFrame audioFrame) {
                if(!isAudioCallbackDump){
                    return;
                }
                try {
                    if(mRemoteUserMap.get(userID) != null){

                        if(mRemoteUserMap.get(userID).audioPCMDump == null){

                            mRemoteUserMap.get(userID).audioPCMDump = createPCMDump("PlayBackUid_"+ userID +"_"+ audioFrame.getFormat().getChannels()
                                    +"_"+ audioFrame.getFormat().getSampleRate());
                            if(mRemoteUserMap.get(userID).audioPCMDump == null){
                                Log.e(TAG, "create dump file failed!");
                                return;
                            }
                        }

                        byte[] remaining = new byte[audioFrame.getData().remaining()];
                        audioFrame.getData().get(remaining);
                        mRemoteUserMap.get(userID).audioPCMDump.write(remaining);

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onMixedAudioFrame(NERtcAudioFrame audioFrame) {
                if(!isAudioCallbackDump){
                    return;
                }
                try {
                    if (pcmCallbackMixDump == null) {
                        pcmCallbackMixDump = createPCMDump("Mix_" +audioFrame.getFormat().getChannels()
                                +"_"+ audioFrame.getFormat().getSampleRate());
                        if(pcmCallbackMixDump == null) {
                            Log.e(TAG, "create dump file failed!");
                            return;
                        }
                    }

                    byte[] remaining = new byte[audioFrame.getData().remaining()];
                    audioFrame.getData().get(remaining);
                    pcmCallbackMixDump.write(remaining);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

iOS

注意事项

实现方法

在使用原始数据功能前,请确保你已在项目中完成基本的实时音视频功能。

参考如下步骤,在你的项目中实现原始音频数据功能:

  1. 加入频道前调用 setAudioFrameObserver 方法注册语音观测器,并在该方法中实现一个 NERtcEngineAudioFrameObserver 类。
  2. 设置回调的音频采样率。
  3. SDK 返回回调。
  4. 用户拿到音频数据后,根据场景需要自行进行处理。
  5. 完成音频数据处理后,可以直接进行自播放,或根据场景需求再通过 onNERtcEngineAudioFrameDidRecordonNERtcEngineAudioFrameWillPlayback 回调发送给 SDK。

示例代码

//1. 注册语音观测器
 [[NERtcEngine sharedEngine] setAudioFrameObserver:self];

//2.设置回调的音频格式参数
  NERtcAudioFrameRequestFormat *format = [[NERtcAudioFrameRequestFormat alloc] init];
  format.channels = 1;
  format.sampleRate = 48000;
  format.mode = kNERtcAudioFrameOpModeReadWrite; //读写模式
  [[NERtcEngine sharedEngine] setRecordingAudioFrameParameters:format];

//3.在回调中处理数据
- (void)onNERtcEngineAudioFrameDidRecord:(NERtcAudioFrame *)frame {
    if (!frame) {
        return;
    }

    if (!_enableRecordAudioObserver) {
        return;
    }

    if (!_recordSoundTouch) {
        _recordSoundTouch = [[NTESSoundTouch alloc] initWithSampleRate:frame.format.sample_rate];
    }

    //若读写模式,SDK将会对处理后的音频数据发送,反之不会。
    [_recordSoundTouch processSound:frame.data number:frame.format.samples_per_channel];
}

- (void)onNERtcEngineAudioFrameWillPlayback:(NERtcAudioFrame *)frame {
    if (!frame) {
        return;
    }

    if (!_enablePlaybackAudioObserver) {
        return;
    }

    if (!_playbackSoundTouch) {
        _playbackSoundTouch = [[NTESSoundTouch alloc] initWithSampleRate:frame.format.sample_rate];
    }

    //若读写模式,SDK将会对处理后的音频数据发送,反之不会。
    [_playbackSoundTouch processSound:frame.data number:frame.format.samples_per_channel];
}

Windows/macOS

注意事项

实现方法

在使用原始数据功能前,请确保你已在项目中完成基本的实时音视频功能。

参考如下步骤,在你的项目中实现原始音频数据回调功能:

  1. 加入频道前基于 INERtcAudioFrameObserver 接口类实现一个 NERtcAudioFrameObserver 类,并调用 setAudioFrameObserver 方法注册语音观测器。
  2. 设置回调的音频采样率。
  3. SDK 返回回调。
  4. 用户拿到音频数据后,根据场景需要自行进行处理。
  5. 完成音频数据处理后,可以直接进行自播放,或根据场景需求再通过回调发送给 SDK。

示例代码

你可以参考下面的示例代码片段,在项目中实现音频原始数据功能:

class AudioFrameObserver : public INERtcAudioFrameObserver
{
public:
    void onAudioFrameDidRecord(NERtcAudioFrame *frame) {
        FILE *fd = getFileDescr("Record", frame->format);

        if (NULL != fd) {
            fwrite(frame->data, frame->format.bytes_per_sample * frame->format.channels,
                   frame->format.samples_per_channel, fd);
        }
    }

    void onAudioFrameWillPlayback(NERtcAudioFrame *frame) {
        FILE *fd = getFileDescr("Playback", frame->format);

        if (NULL != fd) {
            fwrite(frame->data, frame->format.bytes_per_sample * frame->format.channels,
                   frame->format.samples_per_channel, fd);
        }
    }

    void onMixedAudioFrame(nertc::NERtcAudioFrame *frame) {
        FILE *fd = getFileDescr("Mixed", frame->format);

        if (NULL != fd) {
            fwrite(frame->data, frame->format.bytes_per_sample * frame->format.channels,
                   frame->format.samples_per_channel, fd);
        }
    }

    void onPlaybackAudioFrameBeforeMixing(nertc::uid_t uid, nertc::NERtcAudioFrame *frame) {
        FILE *fd = getFileDescr(std::to_string(uid), frame->format);

        if (NULL != fd) {
            fwrite(frame->data, frame->format.bytes_per_sample * frame->format.channels,
                   frame->format.samples_per_channel, fd);
        }
    }

    AudioFrameObserver(const std::string &record_file_dir) : m_record_file_dir(record_file_dir) {}

    virtual ~AudioFrameObserver()
    {
        for (auto &fd : m_fds)
        {
            fclose(fd.second.fd);
            fd.second.fd = NULL;
        }
        m_fds.clear();
    }

private:
    struct file_descr {
        FILE *fd;
        nertc::NERtcAudioFormat fmt;
    };

    FILE *getFileDescr(const std::string &id, const nertc::NERtcAudioFormat &fmt) {
        if (m_fds.end() == m_fds.find(id)) {
            m_fds[id] = file_descr();
            m_fds[id].fd = NULL;
        } else if ((m_fds[id].fmt.sample_rate != fmt.sample_rate) ||
                   (m_fds[id].fmt.channels != fmt.channels) ||
                   (m_fds[id].fmt.type != fmt.type)) {
            if (NULL != m_fds[id].fd) {
                fclose(m_fds[id].fd);
                m_fds[id].fd = NULL;
            }
        }

        if (NULL == m_fds[id].fd) {
            time_t now = time(0);
            tm *ltm = localtime(&now);
            std::ostringstream oss;
            oss << m_record_file_dir << "/audio-frame-" << id << "-" << fmt.sample_rate << "-ch" << fmt.channels;
            oss << "-" << ltm->tm_year + 1900;
            oss << std::setfill('0') << std::setw(2) << ltm->tm_mon + 1;
            oss << std::setfill('0') << std::setw(2) << ltm->tm_mday;
            oss << std::setfill('0') << std::setw(2) << ltm->tm_hour;
            oss << std::setfill('0') << std::setw(2) << ltm->tm_min;
            oss << std::setfill('0') << std::setw(2) << ltm->tm_sec;
            oss << ".pcm";

            m_fds[id].fd = fopen(oss.str().c_str(), "wb");
            m_fds[id].fmt = fmt;
        }

        return m_fds[id].fd;
    }

    std::map<std::string /*id*/, file_descr> m_fds;
    std::string m_record_file_dir;
};

AudioFrameObserver *audioFrameObserver = new AudioFrameObserver("/dir/for/recor_file");
rtc_engine_->setAudioFrameObserver(audioFrameObserver);
×

反馈成功

非常感谢您的反馈,我们会继续努力做得更好。