音频传输过程中,我们可以对采集到的音频数据进行前处理和后处理,获取想要的播放效果。
对于有自行处理音频数据需求的场景,声网提供原始数据功能,你可以在将数据发送给编码器前进行前处理,对捕捉到的语音信号进行修改;也可以在将数据发送给解码器后进行后处理,对接收到的语音信号进行修改。
我们在 GitHub 上提供调用 Java 接口获取原始音频数据的开源示例项目,你可以前往下载,或查看其中的源代码。
在使用原始数据功能前,请确保你已在项目中完成基本的实时音频功能。
参考如下步骤,在你的项目中调用 Java 接口实现原始视频数据功能:
IAudioFrameObserver
实例,并调用 registerAudioFrameObserver
方法注册音频观测器。getRecordAudioParams
、getPlaybackAudioParams
或 getMixedAudioParams
回调,你可以在这些回调的返回值中设置想要获取的音频数据格式。getObservedAudioFramePosition
和 isMultipleChannelFrameWanted
回调,你可以在这些回调的返回值中设置想要观测的音频位置和是否获取多个频道的音频数据。getObservedAudioFramePosition
和 isMultipleChannelFrameWanted
的返回值触发 onRecordFrame
、onPlaybackFrame
、onPlaybackFrameBeforeMixing
/onPlaybackFrameBeforeMixingEx
或 onMixedFrame
回调发送采集到的原始音频数据。onRecordFrame
、onPlaybackFrame
、onPlaybackFrameBeforeMixing
/onPlaybackFrameBeforeMixingEx
或 onMixedFrame
回调发送给 SDK。下图展示使用原始音频数据的 API 调用时序:
// 定义 readBuffer 方法,读取本地音频文件的音频 buffer。
private byte[] readBuffer(){
int byteSize = SAMPLES_PER_CALL * BIT_PER_SAMPLE / 8;
byte[] buffer = new byte[byteSize];
try {
if(inputStream.read(buffer) < 0){
inputStream.reset();
return readBuffer();
}
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
// 定义 audioAggregate 方法,将 onRecordFrame 回调返回音频数据与本地音频文件的 buffer 混合。
private byte[] audioAggregate(byte[] origin, byte[] buffer) {
byte[] output = new byte[origin.length];
for (int i = 0; i < origin.length; i++) {
output[i] = (byte) ((int) origin[i] + (int) buffer[i] / 2);
}
return output;
}
// 实现一个 IAudioFrameObserver 类。
private final IAudioFrameObserver audioFrameObserver = new IAudioFrameObserver() {
// 实现 getObservedAudioFramePosition 回调,在该回调的返回值中设置音频观测位置为 POSITION_RECORD,对应 onRecordFrame 回调。
@Override
public int getObservedAudioFramePosition() {
return IAudioFrameObserver.POSITION_RECORD;
}
// 实现 getRecordAudioParams 回调,在该回调的返回值中设置 onRecordFrame 回调音频的格式。
@Override
public AudioParams getRecordAudioParams() {
return new AudioParams(SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL, Constants.RAW_AUDIO_FRAME_OP_MODE_READ_WRITE, SAMPLES_PER_CALL);
}
// 实现 onRecordFrame 回调,从回调中获取音频数据,与本地音频文件混音后发送给 SDK 播放。
@Override
public boolean onRecordFrame(AudioFrame audioFrame) {
Log.i(TAG, "onRecordAudioFrame " + isWriteBackAudio);
if(isWriteBackAudio){
ByteBuffer byteBuffer = audioFrame.samples;
byte[] buffer = readBuffer();
byte[] origin = new byte[byteBuffer.remaining()];
byteBuffer.get(origin);
byteBuffer.flip();
byteBuffer.put(audioAggregate(origin, buffer), 0, byteBuffer.remaining());
}
return true;
}
};
// 传入 IAudioFrameObserver 实例,注册音频观测器。
engine.registerAudioFrameObserver(audioFrameObserver);
registerAudioFrameObserver
onRecordFrame
onPlaybackFrame
onPlaybackFrameBeforeMixing
onMixedFrame
isMultipleChannelFrameWanted
onPlaybackFrameBeforeMixingEx
getObservedAudioFramePosition
getRecordAudioParams
getPlaybackAudioParams
getMixedAudioParams
IAudioFrameObserver
类,实现采集、修改原始音频数据功能。你可以通过 JNI (Java Native Interface) 使用 Java 调用声网的 C++ 接口。由于 RTC Java SDK 封装了 RTC C++ SDK,所以可以直接通过 include SDK 中 .h
文件的方式调用 C++ 方法。参考如下步骤,在你的项目中实现原始音频数据功能:
registerAudioFrameObserver
方法注册语音观测器,并在该方法中实现一个 IAudioFrameObserver
类。onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
或 onMixedAudioFrame
回调发送采集到的原始音频数据。onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
或 onMixedAudioFrame
回调发送给 SDK。.cpp
文件)编译成的 .so
库。javac -h -jni
命令生成 .h 文件。C++ 接口文件需要 include 此文件。.so
库的 C++ 方法。下图展示使用原始音频数据的 API 调用时序:
registerAudioFrameObserver
、onRecordAudioFrame
、onPlaybackAudioFrame
、onMixedAudioFrame
和 onPlaybackAudioFrameBeforeMixing
均为 C++ 方法和回调。通过 JNI 接口分别创建 Java 和 C++ 的接口文件,并将 C++ 接口文件构建为 .so
库。
MediaPreProcessing.java
文件。// Java 接口文件,声明需要调用 C++ 的相关 Java 方法
package io.agora.advancedvideo.rawdata;
import java.nio.ByteBuffer;
public class MediaPreProcessing {
static {
// 加载 C++ .so 库。.so 库通过编译 C++ 接口文件生成。
// .so 库的名称取决于编译 C++ 接口文件生成的库名
System.loadLibrary("apm-plugin-raw-data");
}
// 定义本地方法
public interface ProgressCallback {
...
// 获取录制的音频帧
void onRecordAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength);
// 获取播放的音频帧
void onPlaybackAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength);
// 获取混音前播放的音频帧
void onPlaybackAudioFrameBeforeMixing(int uid, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength);
// 获取混音后的音频帧
void onMixedAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength);
}
public static native void setCallback(ProgressCallback callback);
public static native void setAudioRecordByteBuffer(ByteBuffer byteBuffer);
public static native void setAudioPlayByteBuffer(ByteBuffer byteBuffer);
public static native void setBeforeAudioMixByteBuffer(ByteBuffer byteBuffer);
public static native void setAudioMixByteBuffer(ByteBuffer byteBuffer);
public static native void releasePoint();
}
# JDK 10 或更高版本
javac -h -jni MediaPreProcessing.java
# JDK 9 或更早版本
javac MediaPreProcessing.java
javah -jni MediaPreProcessing.class
创建 C++ 接口文件,用于被 Java 调用。C++ 接口文件需要根据生成的 .h
文件,从 C++ SDK export 相应的方法。具体实现可参考示例项目中的 io_agora_advancedvideo_rawdata_MediaPreProcessing.cpp
文件。
// 全局变量
jobject gCallBack = nullptr;
jclass gCallbackClass = nullptr;
// Java 层的 method ID
jmethodID recordAudioMethodId = nullptr;
jmethodID playbackAudioMethodId = nullptr;
jmethodID playBeforeMixAudioMethodId = nullptr;
jmethodID mixAudioMethodId = nullptr;
// 从 onRecordAudioFrame 获取的音频帧 ByteBuffer
void *_javaDirectPlayBufferRecordAudio = nullptr;
// 从 onPlaybackAudioFrame 获取的音频帧 ByteBuffer
void *_javaDirectPlayBufferPlayAudio = nullptr;
// 从 onPlaybackAudioFrameBeforeMixing 获取的音频帧 ByteBuffer
void *_javaDirectPlayBufferBeforeMixAudio = nullptr;
// 从 onMixedAudioFrame 获取的音频帧 ByteBuffer
void *_javaDirectPlayBufferMixAudio = nullptr;
map<int, void *> decodeBufferMap;
static JavaVM *gJVM = nullptr;
// 实现 IAudioFrameObserver 类及相关回调
class AgoraAudioFrameObserver : public agora::media::IAudioFrameObserver
{
public:
AgoraAudioFrameObserver()
{
gCallBack = nullptr;
}
~AgoraAudioFrameObserver()
{
}
// 从 AudioFrame 对象获取音频帧数据,复制到 ByteBuffer,并通过 method ID 调用 Java 方法
void getAudioFrame(AudioFrame &audioFrame, _jmethodID *jmethodID, void *_byteBufferObject,
unsigned int uid)
{
if (_byteBufferObject == nullptr)
{
return;
}
AttachThreadScoped ats(gJVM);
JNIEnv *env = ats.env();
if (env == nullptr)
{
return;
}
int len = audioFrame.samples * audioFrame.bytesPerSample;
memcpy(_byteBufferObject, audioFrame.buffer, (size_t) len); // * sizeof(int16_t)
if (uid == 0)
{
env->CallVoidMethod(gCallBack, jmethodID, audioFrame.type, audioFrame.samples,
audioFrame.bytesPerSample,
audioFrame.channels, audioFrame.samplesPerSec,
audioFrame.renderTimeMs, len);
} else
{
env->CallVoidMethod(gCallBack, jmethodID, uid, audioFrame.type, audioFrame.samples,
audioFrame.bytesPerSample,
audioFrame.channels, audioFrame.samplesPerSec,
audioFrame.renderTimeMs, len);
}
}
// 将音频帧数据从 ByteBuffer 复制到 AudioFrame 对象
void writebackAudioFrame(AudioFrame &audioFrame, void *byteBuffer)
{
if (byteBuffer == nullptr)
{
return;
}
int len = audioFrame.samples * audioFrame.bytesPerSample;
memcpy(audioFrame.buffer, byteBuffer, (size_t) len);
}
public:
// 实现 onRecordAudioFrame 回调
virtual bool onRecordAudioFrame(AudioFrame &audioFrame) override
{
// 获取录制的音频帧
getAudioFrame(audioFrame, recordAudioMethodId, _javaDirectPlayBufferRecordAudio, 0);
// 将音频帧发送回 SDK
writebackAudioFrame(audioFrame, _javaDirectPlayBufferRecordAudio);
return true;
}
// 实现 onPlaybackAudioFrame 回调
virtual bool onPlaybackAudioFrame(AudioFrame &audioFrame) override
{
// 获取播放的音频帧
getAudioFrame(audioFrame, playbackAudioMethodId, _javaDirectPlayBufferPlayAudio, 0);
// 将音频帧发送回 SDK
writebackAudioFrame(audioFrame, _javaDirectPlayBufferPlayAudio);
return true;
}
// 实现 onPlaybackAudioFrameBeforeMixing 回调
virtual bool onPlaybackAudioFrameBeforeMixing(unsigned int uid, AudioFrame &audioFrame) override
{
// 获取混音前播放的音频帧
getAudioFrame(audioFrame, playBeforeMixAudioMethodId, _javaDirectPlayBufferBeforeMixAudio,
uid);
// 将音频帧发送回 SDK
writebackAudioFrame(audioFrame, _javaDirectPlayBufferBeforeMixAudio);
return true;
}
// 实现 onMixedAudioFrame 回调
virtual bool onMixedAudioFrame(AudioFrame &audioFrame) override
{
// 获取混音后的音频帧
getAudioFrame(audioFrame, mixAudioMethodId, _javaDirectPlayBufferMixAudio, 0);
// 将音频帧发送回 SDK
writebackAudioFrame(audioFrame, _javaDirectPlayBufferMixAudio);
return true;
}
};
...
// AgoraAudioFrameObserver 对象
static AgoraAudioFrameObserver s_audioFrameObserver;
// IRtcEngine 对象
static agora::rtc::IRtcEngine *rtcEngine = nullptr;
// 设置 C++ 接口
#ifdef __cplusplus
extern "C" {
#endif
int __attribute__((visibility("default")))
loadAgoraRtcEnginePlugin(agora::rtc::IRtcEngine *engine)
{
__android_log_print(ANDROID_LOG_DEBUG, "agora-raw-data-plugin", "loadAgoraRtcEnginePlugin");
rtcEngine = engine;
return 0;
}
void __attribute__((visibility("default")))
unloadAgoraRtcEnginePlugin(agora::rtc::IRtcEngine *engine)
{
__android_log_print(ANDROID_LOG_DEBUG, "agora-raw-data-plugin", "unloadAgoraRtcEnginePlugin");
rtcEngine = nullptr;
}
...
// 针对 Java 接口文件,通过 JNI 导出相应的 C++ 实现。
// Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setCallback 方法
// 对应 Java 接口文件的 setCallback 方法。
JNIEXPORT void JNICALL Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setCallback
(JNIEnv *env, jclass, jobject callback)
{
if (!rtcEngine) return;
env->GetJavaVM(&gJVM);
// 创建使用 IMediaEngine 类为 template 的 AutoPtr 实例
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
// AutoPtr 实例调用 queryInterface 方法,通过 IID 获取 IMediaEngine 实例的指针。
// AutoPtr 实例会通过箭头操作符访问 IMediaEngine 实例的指针并通过 IMediaEngine 实例调用 registerVideoFrameObserver
mediaEngine.queryInterface(rtcEngine, agora::INTERFACE_ID_TYPE::AGORA_IID_MEDIA_ENGINE);
if (mediaEngine)
{
...
// 注册音频帧观测器
int ret = mediaEngine->registerAudioFrameObserver(&s_audioFrameObserver);
}
if (gCallBack == nullptr)
{
gCallBack = env->NewGlobalRef(callback);
gCallbackClass = env->GetObjectClass(gCallBack);
// 获取回调函数的 method ID
recordAudioMethodId = env->GetMethodID(gCallbackClass, "onRecordAudioFrame", "(IIIIIJI)V");
playbackAudioMethodId = env->GetMethodID(gCallbackClass, "onPlaybackAudioFrame",
"(IIIIIJI)V");
playBeforeMixAudioMethodId = env->GetMethodID(gCallbackClass,
"onPlaybackAudioFrameBeforeMixing",
"(IIIIIIJI)V");
mixAudioMethodId = env->GetMethodID(gCallbackClass, "onMixedAudioFrame", "(IIIIIJI)V");
...
__android_log_print(ANDROID_LOG_DEBUG, "setCallback", "setCallback done successfully");
}
}
...
// Java 接口文件中 setAudioRecordByteBuffer 方法的 C++ 实现
JNIEXPORT void JNICALL
Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setAudioRecordByteBuffer
(JNIEnv *env, jclass, jobject bytebuffer)
{
_javaDirectPlayBufferRecordAudio = env->GetDirectBufferAddress(bytebuffer);
}
// Java 接口文件中 setAudioPlayByteBuffer 方法的 C++ 实现
JNIEXPORT void JNICALL Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setAudioPlayByteBuffer
(JNIEnv *env, jclass, jobject bytebuffer)
{
_javaDirectPlayBufferPlayAudio = env->GetDirectBufferAddress(bytebuffer);
}
// Java 接口文件中 setBeforeAudioMixByteBuffer 方法的 C++ 实现
JNIEXPORT void JNICALL
Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setBeforeAudioMixByteBuffer
(JNIEnv *env, jclass, jobject bytebuffer)
{
_javaDirectPlayBufferBeforeMixAudio = env->GetDirectBufferAddress(bytebuffer);
}
// Java 接口文件中 setAudioMixByteBuffer 方法的 C++ 实现
JNIEXPORT void JNICALL Java_io_agora_advancedvideo_rawdata_MediaPreProcessing_setAudioMixByteBuffer
(JNIEnv *env, jclass, jobject bytebuffer)
{
_javaDirectPlayBufferMixAudio = env->GetDirectBufferAddress(bytebuffer);
}
}
...
#ifdef __cplusplus
}
#endif
.so
库。CMake 示例内容如下。所生成的 .so 库在 Java 接口文件中通过 System.loadLibrary()
的方式加载。cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
apm-plugin-raw-data
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/io_agora_advancedvideo_rawdata_MediaPreProcessing.cpp)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.
apm-plugin-raw-data
# Links the target library to the log library
# included in the NDK.
${log-lib})
// 在 Java 中实现 ProgressCallback 接口
public class MediaDataObserverPlugin implements MediaPreProcessing.ProgressCallback {
...
// 获取录制的音频帧
@Override
public void onRecordAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
byte[] buf = new byte[bufferLength];
byteBufferAudioRecord.limit(bufferLength);
byteBufferAudioRecord.get(buf);
byteBufferAudioRecord.flip();
for (MediaDataAudioObserver observer : audioObserverList) {
observer.onRecordAudioFrame(buf, audioFrameType, samples, bytesPerSample, channels, samplesPerSec, renderTimeMs, bufferLength);
}
byteBufferAudioRecord.put(buf);
byteBufferAudioRecord.flip();
}
// 获取播放的音频帧
@Override
public void onPlaybackAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
byte[] buf = new byte[bufferLength];
byteBufferAudioPlay.limit(bufferLength);
byteBufferAudioPlay.get(buf);
byteBufferAudioPlay.flip();
for (MediaDataAudioObserver observer : audioObserverList) {
observer.onPlaybackAudioFrame(buf, audioFrameType, samples, bytesPerSample, channels, samplesPerSec, renderTimeMs, bufferLength);
}
byteBufferAudioPlay.put(buf);
byteBufferAudioPlay.flip();
}
// 获取混音前播放的音频帧
@Override
public void onPlaybackAudioFrameBeforeMixing(int uid, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
byte[] buf = new byte[bufferLength];
byteBufferBeforeAudioMix.limit(bufferLength);
byteBufferBeforeAudioMix.get(buf);
byteBufferBeforeAudioMix.flip();
for (MediaDataAudioObserver observer : audioObserverList) {
observer.onPlaybackAudioFrameBeforeMixing(uid, buf, audioFrameType, samples, bytesPerSample, channels, samplesPerSec, renderTimeMs, bufferLength);
}
byteBufferBeforeAudioMix.put(buf);
byteBufferBeforeAudioMix.flip();
}
// 获取混音后的音频帧
@Override
public void onMixedAudioFrame(int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
byte[] buf = new byte[bufferLength];
byteBufferAudioMix.limit(bufferLength);
byteBufferAudioMix.get(buf);
byteBufferAudioMix.flip();
for (MediaDataAudioObserver observer : audioObserverList) {
observer.onMixedAudioFrame(buf, audioFrameType, samples, bytesPerSample, channels, samplesPerSec, renderTimeMs, bufferLength);
}
byteBufferAudioMix.put(buf);
byteBufferAudioMix.flip();
}
}
setCallback
方法。setCallback
方法通过 JNI 调用 C++ API 的 registerAudioFrameObserver
方法,注册音频帧观测器。@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mediaDataObserverPlugin = MediaDataObserverPlugin.the();
// 注册音频帧观测器
MediaPreProcessing.setCallback(mediaDataObserverPlugin);
...
}
onRecordAudioFrame
回调,onPlaybackAudioFrame
回调, onPlaybackAudioFrameBeforeMixing
回调,和 onMixedAudioFrame
回调。从回调中获取音频帧并进行处理。// 获取录制的音频帧
@Override
public void onRecordAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
}
// 获取播放的音频帧
@Override
public void onPlaybackAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
}
// 获取混音前播放的音频帧
@Override
public void onPlaybackAudioFrameBeforeMixing(int uid, byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
}
// 获取混音后的音频帧
@Override
public void onMixedAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
}