Raw audio processing
Updated
Pre and post-process captured video and audio data to achieve the desired playback effect.
In some use-cases, raw audio captured through the microphone must be processed to enhance the user experience or achieve the desired functionality Video SDK enables you to pre-process and post-process the captured audio for implementation of custom playback effects.
This article shows you how to pre-process and post-process collected raw audio data.
Understand the tech
For use-cases that require self-processing of audio data, Agora Video SDK provides raw data processing functionality. You can perform pre-processing to modify the captured audio signal before sending the data to the encoder, or post-process data to modify the received audio signal after sending the data to the decoder.
To implement processing of raw audio data in your game, take the following steps.
- Register an instance of the audio frame observer before joining a channel.
- Set the format of audio frames captured by each callback.
- Implement callbacks in the frame observers to process raw audio data.
- Unregister the frame observers before you leave a channel.
The following figure shows the basic processing of raw audio data:
Process raw audio
Prerequisites
Ensure that you have implemented the SDK quickstart in your project.
Implement raw audio processing
Follow these steps to implement raw audio data processing functionality in your game:
-
Initialize MediaEngine and register the audio observer
Before joining a channel, obtain the MediaEngine interface and create an instance of
IAudioFrameObserver, then callregisterAudioFrameObserverto register it:void InitAgoraEngine(FString APP_ID, FString TOKEN, FString CHANNEL_NAME) { // Initialize RtcEngine context and event handler agora::rtc::RtcEngineContext RtcEngineContext; UserRtcEventHandlerEx = MakeShared<FUserRtcEventHandlerEx>(this); std::string StdStrAppId = TCHAR_TO_UTF8(*APP_ID); RtcEngineContext.appId = StdStrAppId.c_str(); RtcEngineContext.eventHandler = UserRtcEventHandlerEx.Get(); RtcEngineContext.channelProfile = agora::CHANNEL_PROFILE_TYPE::CHANNEL_PROFILE_LIVE_BROADCASTING; // Initialize the RtcEngine int ret = AgoraUERtcEngine::Get()->initialize(RtcEngineContext); // Query for the MediaEngine interface AgoraUERtcEngine::Get()->queryInterface(AGORA_IID_MEDIA_ENGINE, (void**)&MediaEngine); // Create and register audio frame observer UserAudioFrameObserver = MakeShared<FUserAudioFrameObserver>(this); MediaEngine->registerAudioFrameObserver(UserAudioFrameObserver.Get()); } -
Configure audio parameters
Set the audio frame format by initializing the
AudioParamsstructure. This determines the sample rate, number of channels, and other audio processing configurations.void InitConfig() { // Configure AudioParam audioParams.channels = 2; audioParams.sample_rate = 44100; audioParams.mode = agora::rtc::RAW_AUDIO_FRAME_OP_MODE_TYPE::RAW_AUDIO_FRAME_OP_MODE_READ_WRITE; audioParams.samples_per_call = 1024; } -
Implement audio frame observer class
Create a class that inherits from
IAudioFrameObserverand implement the required callbacks to receive and process audio frames. Returningfalsefrom any of these callbacks indicates that the processing of the audio frame was invalid.class FUserAudioFrameObserver : public agora::media::IAudioFrameObserver { public: FUserAudioFrameObserver(UProcessAudioRawDataWidget* InWidgetPtr) : WidgetPtr(InWidgetPtr) {} bool onPlaybackAudioFrameBeforeMixing(const char* channelId, agora::rtc::uid_t uid, AudioFrame& audioFrame) override { if (!IsWidgetValid()) return false; if (WidgetPtr->GetFirstRemoteUID() == uid) { // Process audio frames for specific remote user WidgetPtr->GetAgoraSoundWaveProcedural()->AddToFrames(audioFrame); } return true; } bool onRecordAudioFrame(const char* channelId, AudioFrame& audioFrame) override { if (!IsWidgetValid()) return false; return true; } bool onPlaybackAudioFrame(const char* channelId, AudioFrame& audioFrame) override { if (!IsWidgetValid()) return false; return true; } bool onMixedAudioFrame(const char* channelId, AudioFrame& audioFrame) override { return true; } bool onEarMonitoringAudioFrame(AudioFrame& audioFrame) override { return true; } int getObservedAudioFramePosition() override { return (int)(AUDIO_FRAME_POSITION::AUDIO_FRAME_POSITION_PLAYBACK | AUDIO_FRAME_POSITION::AUDIO_FRAME_POSITION_RECORD | AUDIO_FRAME_POSITION::AUDIO_FRAME_POSITION_BEFORE_MIXING | AUDIO_FRAME_POSITION::AUDIO_FRAME_POSITION_MIXED | AUDIO_FRAME_POSITION::AUDIO_FRAME_POSITION_EAR_MONITORING); } agora::media::IAudioFrameObserverBase::AudioParams getPlaybackAudioParams() override { if (!IsWidgetValid()) return agora::media::IAudioFrameObserverBase::AudioParams(); return WidgetPtr->GetAudioParams(); } agora::media::IAudioFrameObserverBase::AudioParams getRecordAudioParams() override { if (!IsWidgetValid()) return agora::media::IAudioFrameObserverBase::AudioParams(); return WidgetPtr->GetAudioParams(); } agora::media::IAudioFrameObserverBase::AudioParams getMixedAudioParams() override { if (!IsWidgetValid()) return agora::media::IAudioFrameObserverBase::AudioParams(); return WidgetPtr->GetAudioParams(); } agora::media::IAudioFrameObserverBase::AudioParams getEarMonitoringAudioParams() override { if (!IsWidgetValid()) return agora::media::IAudioFrameObserverBase::AudioParams(); return WidgetPtr->GetAudioParams(); } private: UProcessAudioRawDataWidget* WidgetPtr; bool IsWidgetValid() const { return WidgetPtr && IsValid(WidgetPtr); } }; -
Join channel and configure audio
Join the channel and optionally configure audio settings. You can mute the original playback to handle audio independently.
void OnBtnJoinChannelClicked() { // Mute the playback volume of SDK (not UE) to handle audio independently AgoraUERtcEngine::Get()->adjustPlaybackSignalVolume(0); AgoraUERtcEngine::Get()->enableAudio(); AgoraUERtcEngine::Get()->setClientRole(CLIENT_ROLE_BROADCASTER); int ret = AgoraUERtcEngine::Get()->joinChannel(TCHAR_TO_UTF8(*Token), TCHAR_TO_UTF8(*ChannelName), "", 0); } -
Unregister the audio frame observer
Call
registerAudioFrameObserverwithnullptrto unregister the audio frame observer before leaving the channel:void UnInitAgoraEngine() { if (AgoraUERtcEngine::Get() != nullptr) { AgoraUERtcEngine::Get()->leaveChannel(); if (MediaEngine != nullptr) { // Unregister the audio frame observer MediaEngine->registerAudioFrameObserver(nullptr); } AgoraUERtcEngine::Get()->unregisterEventHandler(UserRtcEventHandlerEx.Get()); AgoraUERtcEngine::Release(); } }
Video SDK uses a synchronous callback mechanism for processing raw audio data. When you save or rewrite data using the callbacks, consider the following best practices:
- To ensure continuity of the audio stream, do not block the SDK thread by processing data directly in the callback function. Instead, make a deep copy of the received audio data and transfer the copied data to another thread for processing.
- If you choose to process the audio data synchronously within the callback function, you must strictly control the processing time. For example, if the callback function is triggered every 10 milliseconds, then the processing time within the callback must be less than 10 milliseconds to prevent delays or interruptions in the audio stream.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
Sample project
Agora provides an open-source example project ProcessAudioRawData for your reference. Download or view the project for a more detailed example.
