音视频数据回调
简介
Pano SDK 支持将音视频数据回调给开发者进行处理,例如视频美颜、音频变声、语音识别等。
对于音频,一个简化的音频处理流程如下:
采集 --(1)--> 3A前处理* --(2)--> 编码 --> 发送 --> 接收 --> Jitter Buffer --> 解码 --(3)--> 播放
* 3A前处理是指:AEC (Acoustic Echo Cancellation 回声消除)、AGC (Automatic Gain Control 自动增益控制)、ANS (Automatic Noise Suppression 自动降噪)
Pano SDK 支持在 3 个时机回调数据:
- 本地采集后,前处理之前
- 本地采集后,编码之前
- 播放之前
对于视频,Pano SDK 在本地采集后,编码之前回调数据。
对于不需要回传数据给 SDK 的情况,如果计算量较大,建议拷贝数据到其他线程单独处理,以免阻塞 SDK 的回调线程。
各端 SDK 相关接口,请查看下列介绍:
Windows (C++)
通过 RtcEngine
实例的 setMediaProcessor
方法设置媒体处理模块。
[注意] 上述接口只对当前选中的一个摄像头生效。如果有多个摄像头同时发送视频,或者同时预览和发送不同摄像头的视频等复杂场景,请更换为指定目标设备的方式:
- 先调用
RtcEngine
实例的getVideoDeviceManager
方法获取视频设备管理器(VideoDeviceManager
)- 通过其
enumerateCaptureDevices
方法枚举视频采集设备,通过回调函数获取deviceId
- 再通过其
setDeviceBoundMediaProcessor
方法进行设置
- 对于音频,如需在本地采集后,前处理之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioCaptureExProcessor
,同时processor
处理模块必须为RtcExternalAudioProcess
类型,param
设置为空。- 开发者需要实现
RtcExternalAudioProcess
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在本地采集后,编码之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioCaptureExEffectProcessor
,同时processor
处理模块必须为RtcExternalAudioProcess
类型,param
设置为空。- 开发者需要实现
RtcExternalAudioProcess
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在播放之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioRenderExProcessor
,同时processor
处理模块必须为RtcExternalAudioProcess
类型,param
设置为空。- 开发者需要实现
RtcExternalAudioProcess
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于视频,如需对数据进行处理:
示例代码:
// .h 文件中:
// 以处理视频数据回到为例,继承 RtcExternalVideoProcessor
class RtcTester : public panortc::RtcExternalVideoProcessor
...
// 声明回调方法
panortc::QResult onVideoFrame(void* buffer, int length, panortc::VideoFormat& fmt);
panortc::QResult onVideoFrame(void* native, panortc::VideoFormat& fmt);
// .cpp 文件中:
// 设置视频处理模块
rtcEngine_->setMediaProcessor(MediaProcessorType::VideoPreprocessor, (RtcExternalVideoProcessor*)this, nullptr);
// 实现回调方法
panortc::QResult RtcTester::onVideoFrame(void* buffer, int length, panortc::VideoFormat& fmt) {
// 此处展示查看视频信息
printf("--> onVideoFrame: buffer = %p \n", buffer);
printf("--> onVideoFrame: length = %d \n", length);
printf("--> onVideoFrame: fmt.type = %d \n", (int)fmt.type);
printf("--> onVideoFrame: fmt.width = %u \n", fmt.width);
printf("--> onVideoFrame: fmt.height = %u \n", fmt.height);
printf("--> onVideoFrame: fmt.count = %u \n", fmt.count);
for (int i = 0; i < fmt.count; i++)
{
printf("--> onVideoFrame: fmt.offset[%d] = %u \n", i, fmt.offset[i]);
printf("--> onVideoFrame: fmt.stride[%d] = %u \n", i, fmt.stride[i]);
}
printf("--> onVideoFrame: fmt.rotation = %u \n", (int)fmt.rotation);
printf("--> onVideoFrame: fmt.mirror = %d \n", fmt.mirror);
printf("--> onVideoFrame: ---------- \n");
return panortc::QResult::OK;
}
panortc::QResult RtcTester::onVideoFrame(void* native, panortc::VideoFormat& fmt) {
// 不需要处理此回调,直接返回 QResult::OK
return panortc::QResult::OK;
}
macOS/iOS (Objective-C)
通过 PanoRtcEngineKit
实例的 - setMediaProcessor:processor:param:
方法设置媒体处理模块。
[注意] 上述接口只对当前选中的一个摄像头生效。如果有多个摄像头同时发送视频,或者同时预览和发送不同摄像头的视频等复杂场景,请更换为指定目标设备的方式:
- 先通过
PanoRtcEngineKit
实例的- enumerateDevices:
方法(macOS) 或- getCameraDeviceId:
方法(iOS) 获取摄像头的deviceId
- 再通过
- setDeviceBoundMediaProcessor:processor:param:forDevice:
方法进行设置
- 对于音频,如需在本地采集后,前处理之前,对数据进行处理:
PanoMediaProcessorType
媒体处理类型需要设置为kAudioCaptureExProcessor
,同时processor
处理模块必须为PanoRtcAudioDataExProcessorDelegate
类型,param
设置为空。- 开发者需要实现
PanoRtcAudioDataExProcessorDelegate
协议,并实现- onAudioFrame:withLength:withMaxLength:withFormat:
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在本地采集后,编码之前,对数据进行处理:
PanoMediaProcessorType
媒体处理类型需要设置为kAudioCaptureExEffectProcessor
,同时processor
处理模块必须为PanoRtcAudioDataExProcessorDelegate
类型,param
设置为空。- 开发者需要实现
PanoRtcAudioDataExProcessorDelegate
协议,并实现- onAudioFrame:withLength:withMaxLength:withFormat:
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在播放之前,对数据进行处理:
PanoMediaProcessorType
媒体处理类型需要设置为kAudioRenderExProcessor
,同时processor
处理模块必须为PanoRtcAudioDataExProcessorDelegate
类型,param
设置为空。- 开发者需要实现
PanoRtcAudioDataExProcessorDelegate
协议,并实现- onAudioFrame:withLength:withMaxLength:withFormat:
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于视频,如需对数据进行处理:
PanoMediaProcessorType
媒体处理类型需要设置为kVideoPreprocessor
,同时processor
处理模块必须为PanoRtcVideoFilterDelegate
类型,param
设置为空。- 开发者需要实现
PanoRtcVideoFilterDelegate
类,并实现- onNativeVideoFrame:withRotation:
回调方法,在其中对 SDK 提供的视频数据进行处理。 - 需要先设置媒体处理模块,再 开启本地预览 或者 发送本地视频,之后才会回调视频数据。
示例代码:
#import "PanoRtc/PanoRtcEngineKit.h"
// 实现 PanoRtcAudioDataExProcessorDelegate / PanoRtcVideoFilterDelegate 委托协议
@interface ChannelViewController () <PanoRtcEngineDelegate, PanoRtcAudioDataExProcessorDelegate, PanoRtcVideoFilterDelegate>
...
@end
@implementation ChannelViewController
...
self.engineKit = [PanoRtcEngineKit engineWithConfig:engineConfig delegate:self]; // 初始化
// 设置音频处理模块,以音频采集外部处理(本地采集后,前处理之前)为例
[self.engineKit setMediaProcessor:kAudioCaptureExProcessor processor:self param:nil];
// 设置视频处理模块
[self.engineKit setMediaProcessor:kVideoPreprocessor processor:self param:nil];
...
- (void)onAudioFrame:(void *)buffer withLength:(SInt32)length withMaxLength:(SInt32)maxBufferByteLens withFormat:(PanoRtcAudioFormat *)format{
// 在这里处理 SDK 回调的音频数据
// 对于不需要回传数据给 SDK 的情况,如果计算量较大,建议拷贝数据到其他线程单独处理,以免阻塞 SDK 的回调线程
}
- (void)onNativeVideoFrame:(CVPixelBufferRef)frame withRotation:(PanoVideoRotation)rotation{
// 在这里处理 SDK 回调的视频数据
// 对 frame 同步处理完成后,SDK 会自动发送处理后的视频数据
// 对于不需要回传数据给 SDK 的情况,如果计算量较大,建议拷贝数据到其他线程单独处理,以免阻塞 SDK 的回调线程
}
@end
Android (Java)
通过 RtcEngine
实例的 setMediaProcessor(MediaProcessorType type, Object processor, Object param)
方法设置媒体处理模块。
[注意] 上述接口只对当前选中的一个摄像头生效。如果有多个摄像头同时发送视频,或者同时预览和发送不同摄像头的视频等复杂场景,请更换为指定目标设备的方式:
- 先调用
RtcEngine
实例的getVideoDeviceManager
方法获取视频设备管理器(RtcVideoDeviceManager
),通过其getCaptureDeviceList
方法获取视频采集设备列表(RtcDeviceInfo
),其中包含deviceId
- 再通过
RtcEngine
实例的setDeviceBoundMediaProcessor (String deviceId, MediaProcessorType type, Object processor, Object param)
方法进行设置
- 对于音频,如需在本地采集后,前处理之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioCaptureExProcessor
,同时processor
处理模块必须为RtcAudioExProcessor
类型,param
设置为空。- 开发者需要实现
RtcAudioExProcessor
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在本地采集后,编码之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioCaptureExEffectProcessor
,同时processor
处理模块必须为RtcAudioExProcessor
类型,param
设置为空。- 开发者需要实现
RtcAudioExProcessor
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于音频,如需在播放之前,对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为AudioRenderExProcessor
,同时processor
处理模块必须为RtcAudioExProcessor
类型,param
设置为空。- 开发者需要实现
RtcAudioExProcessor
类,并实现onAudioFrame
回调方法,在其中对 SDK 提供的音频数据进行处理。
- 对于视频,如需对数据进行处理:
MediaProcessorType
媒体处理类型需要设置为VideoTexturePreprocessor
,同时processor
处理模块必须为RtcVideoTextureFilter
类型,param
设置为RtcVideoTextureFilterConfig
对象。- 开发者需要创建一个
RtcVideoTextureFilterConfig
对象,可以设置以下属性:textureType
(TextureOES
或Texture2D
)和withYuv
(是否同时返回 YUV 数据),不指定的话将使用默认值,具体请参考 SDK API 文档。注意:当
textureType
属性值为TextureOES
时,"音视频数据回调"和 SDK 自带的 美颜 功能无法同时使用。 - 开发者需要实现
RtcVideoTextureFilter
类,并实现onVideoFrame (int textureId, int width, int height, int rotation, byte[] yuvData)
回调方法,在其中对 SDK 提供的视频数据进行处理。 - 需要先设置媒体处理模块,再 开启本地预览 或者 发送本地视频,之后才会回调视频数据。
示例代码:
mRtcEngine = RtcEngine.create(engineConfig); // 初始化
// 设置音频处理模块(本地采集后,前处理之前)
mRtcEngine.setMediaProcessor(AudioCaptureExProcessor, new MyRtcAudioExProcessor(), null);
// 设置视频处理模块
mRtcEngine.setMediaProcessor(
VideoTexturePreprocessor,
new MyRtcVideoTextureFilter(),
new RtcVideoTextureFilterConfig()
);
MyRtcAudioExProcessor中:
public class MyRtcAudioExProcessor implements RtcAudioExProcessor {
// 配合下文演示将音频数据写入文件保存到存储卡根目录
private File file = new File(Environment.getExternalStorageDirectory() + File.separator + "test.pcm");
private FileOutputStream out = null;
@Override
public int onAudioFrame(ByteBuffer byteBuffer, int length, int maxBufferByteLens, RtcAudioDataFormat rtcAudioDataFormat) {
/* # 需要调用 startAudio 采集音频数据之后,才会进入此回调
# 在这里处理 Pano SDK 回调的数据(16 bit、LITTLE_ENDIAN)
# 如果用于实时处理,请注意数据长度不要超过 byteBuffer 的 limit
# 对于不需要回传数据给 SDK 的情况,如果计算量较大,建议拷贝数据到其他线程单独处理,
以免阻塞 SDK 的回调线程
# Log.i(TAG, "onAudioFrame, byteBuffer: " + byteBuffer + ", length: " + length +
", rtcAudioDataFormat-type: " + rtcAudioDataFormat.type + ", channels: " +
rtcAudioDataFormat.channels + ", sampleRate: " + rtcAudioDataFormat.sampleRate +
", bytesPerSample: " + rtcAudioDataFormat.bytesPerSample);
# RtcAudioDataFormat 中,type: 数据类型(0: PCM),channels: 声道数,
sampleRate: 采样率,bytesPerSample: 采样位宽(2 bytes = 16 bit)
# 可以通过 `RtcEngine.setOption` 配置 `EnablePanoAudioProfile` 和
`RtcAudioProfile` 来调整 `sampleRate` 和 `channel` 参数值
*/
// 下面演示将音频数据写入文件,以下为简化示例,请自行处理权限申请、逻辑判断等
byte[] byte_array = new byte[length];
byteBuffer.get(byte_array);
try {
out = new FileOutputStream(file, true); // 以追加方式写入
out.write(byte_array);
} catch (IOException e) {
e.printStackTrace();
}
// 以采样率48000、单声道的数据为例,可以使用 ffplay 通过以下命令进行播放:
// ffplay -ar 48000 -ac 1 -f s16le -i YourFilePath
// -ar: 音频采样率,-ac: 声道数,-f: 格式(s16le: signed 16 bits little endian, 有符号 16 位小端)
return 0; // SDK 不关心此处返回值
}
}
MyRtcVideoTextureFilter中:
public class MyRtcVideoTextureFilter implements RtcVideoTextureFilter {
private boolean isFirst = true; // 配合下文演示对接第三方美颜功能
@Override
public int onVideoFrame(int textureId, int width, int height, int rotation, byte[] yuvData) {
/* # 在这里处理 Pano SDK 回调的数据,默认情况下 yuvData 为空
可以配置 RtcVideoTextureFilterConfig 的 withYuv 属性为 true 开启拷贝 yuvData
# 后续编码传输使用纹理数据,yuvData 仅为拷贝(不会回传给 SDK)
所以修改 yuvData 不会影响编码和传输的数据(不会有任何效果)
# 对于不需要回传数据给 SDK 的情况,如果计算量较大,建议拷贝数据到其他线程单独处理
以免阻塞 SDK 的回调线程
*/
// 下面演示对接 FaceUnity 的美颜功能(请先自行处理美颜 SDK 初始化工作)
if (isFirst) {
mFuRenderer.onSurfaceCreated();
isFirst = false;
}
return mFuRenderer.onDrawFrameSingleInput(textureId, width, height);
// return newTextureId; // 返回处理后输出的 textureId
}
@Override
public void releaseOnGlThread() {
/* # 若使用 OpenGL 处理纹理,则需要在此进行 OpenGL 资源的释放;如无需要,可以不处理此回调
# 注意:当使用第三方美颜时,由于释放操作有线程要求,必须在此处调用第三方美颜的释放接口
否则可能导致释放失败,产生内存泄漏
# 通过调用 RtcEngine 对象的 setMediaProcessor 方法将 processor 设置为 null 来触发此回调
例如:mRtcEngine.setMediaProcessor(VideoTexturePreprocessor, null, null);
*/
mFuRenderer.onSurfaceDestroyed(); // 此行演示对接 FaceUnity 美颜的处理
}
}
Web
- Web SDK 目前仅支持在抓取麦克风音频之后发送之前进行数据回调。
- 调用 stopAudio 后,此回调会自动停止;再次 startAudio 后,此回调会自动继续。
- 调用 leaveChannel 后,此回调会被销毁;再次 joinChannel 后,需要重新设置。
通过 RtcEngine 的 setMediaProcessor 方法设置媒体处理函数。
示例代码:
rtcEngine.setMediaProcessor('AudioCaptureExProcessor', (dataView, rtcAudioDataFormat) => {
console.log('AudioCaptureExProcessor: ', dataView, rtcAudioDataFormat);
// dataView: Int16Array(2048) [ ... ]
// rtcAudioDataFormat: {type: 0, sampleRate: 48000, channels: 1, bytesPerSample: 16}
});