在即時視頻互動過程中,您可能有對 SDK 採集到的原始視頻資料進行處理的需求,也可能有擷取 SDK 採集並編碼的視頻資料的需求。本文介紹如何通過不同類型的視頻觀測器擷取 SDK 採集到的視頻資料,並對其進行處理。
使用情境
自訂視頻資料處理適用於以下情境。
自訂美顏處理:拉取本地採集的原始視頻流,並使用第三方美顏處理SDK進行處理。處理後的視頻流可以再次推送到頻道中,供觀看者即時觀看。
自訂視頻編輯:在視頻處理鏈路的不同階段對視頻流進行編輯處理,如添加特效、浮水印、裁剪、剪輯等。使用者可以根據需求對視頻流進行靈活編輯,編輯後的視頻流可用於直播或儲存。
拉流預覽或監控:在視頻處理鏈路的不同階段拉取視頻流進行本地或雲端的監控和預覽。使用者可以即時查看視頻流的內容、品質和狀態,確保視頻流的正常傳輸和播放。
前提條件
在設定視頻配置之前,請確保達成以下條件:
技術原理
視頻採集:
解碼渲染:
觀測點對應的回調如下所示:
觀測點 1:通過 onCaptureVideoSample(iOS)或 onLocalVideoSample(Android)回調擷取未經過縮放的資料。
觀測點 2:通過 onPreEncodeVideoSample 回調擷取編碼前的資料。
觀測點 3: 通過 onRemoteVideoSample 回調擷取解碼後的資料。
觀測點 4:通過 onTextureCreate 回調擷取紋理的glcontext,綁定到美顏模組。
觀測點 5:通過 onTextureUpdate 回調擷取採集到的紋理資料。
觀測點 6:通過 onTextureDestroy 回調擷取釋放glcontext時機。
觀測點1、2、3傳回值返回True表示資料更新,否則表示不更新資料;
觀測點5必須返回一個有效 textureId,表示SDK需要使用返回的t extureId進行編碼和處理。如果不做任何處理必須返回參數 textureId。
功能實現
Android
一、視訊框架回調
1.註冊回調
回調通過AliRtcEngine.AliRtcVideoObserver。
public abstract void registerVideoSampleObserver(AliRtcVideoObserver observer);2.返回關注的位置
SDK根據該回調傳回值確認回調位置。
public enum AliRtcVideoObserPosition{
/*! 採集視頻資料,對應輸出回調 onLocalVideoSample */
AliRtcPositionPostCapture(1),
/*! 渲染視頻資料,對應輸出回調 onRemoteVideoSample */
AliRtcPositionPreRender(2),
/*! 編碼前視頻資料,對應輸出回調 onPreEncodeVideoSample */
AliRtcPositionPreEncoder(4);
}
public int onGetObservedFramePosition(){
return AliRtcVideoObserPosition.AliRtcPositionPostCapture.getValue() | AliRtcVideoObserPosition.AliRtcPositionPreRender.getValue();
}3.返回需要輸出格式
/**
* @brief 視頻資料格式
*/
public enum AliRtcVideoFormat{
AliRtcVideoFormatUNKNOW(-1),
AliRtcVideoFormatBGRA(0),
AliRtcVideoFormatI420(1),
AliRtcVideoFormatNV21(2),
AliRtcVideoFormatNV12 (3),
AliRtcVideoFormatRGBA(4),
AliRtcVideoFormatI422 (5),
AliRtcVideoFormatARGB(6),
AliRtcVideoFormatABGR (7),
AliRtcVideoFormatRGB24(8),
AliRtcVideoFormatBGR24(9),
AliRtcVideoFormatRGB565(10),
AliRtcVideoFormatTextureOES(11),
AliRtcVideoFormatTexture2D(12),
AliRtcVideoFormatH264(13),
AliRtcVideoFormatH265(14),
AliRtcVideoFormatFile(15);
};
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
/**
* @brief 視頻資料輸出格式
* @return 期望視頻輸出格式,參考 {@link AliRtcVideoFormat}
*/
public AliRtcVideoFormat onGetVideoFormatPreference(){
return AliRtcVideoFormat.AliRtcVideoFormatI420;
}4.返回需要的對齊
public enum AliRtcVideoObserAlignment{
/*! 保持原有視頻寬度(預設值) */
AliRtcAlignmentDefault(0),
/*! 寬度偶數對齊 */
AliRtcAlignmentEven(1),
/*! 寬度是4的倍數 */
AliRtcAlignment4(2),
/*! 寬度是8的倍數 */
AliRtcAlignment8(3),
/*! 寬度是16的倍數 */
AliRtcAlignment16(4);
};
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
public int onGetVideoAlignment(){
return AliRtcVideoObserAlignment.AliRtcAlignmentDefault.getValue();
}5.擷取的視訊框架是否需要鏡像
public boolean onGetObserverDataMirrorApplied(){
return false;
}6.執行回調
/*
* 返回true表示寫回
* 預設寫回,需要操作AliRtcVideoSample.data時必須要寫回
*/
@Override
public boolean onLocalVideoSample(AliRtcEngine.AliRtcVideoSourceType sourceType, AliRtcEngine.AliRtcVideoSample videoSample) {
boolean ret = false;
/*
* 處理本地採集的資料
*/
return ret;
}
@Override
public boolean onRemoteVideoSample(String userId, AliRtcEngine.AliRtcVideoSourceType sourceType, AliRtcEngine.AliRtcVideoSample videoSample) {
/*
* 處理遠端資料
*/
return false;
}
@Override
public boolean onPreEncodeVideoSample(AliRtcEngine.AliRtcVideoSourceType aliVideoSourceType, AliRtcEngine.AliRtcVideoSample videoSample) {
boolean ret = false;
/*
* 處理編碼前資料
*/
return false ;
}7.取消回調註冊
unregisterVideoSampleObserver二、紋理處理
因為openGL執行緒模式的原因,所有的紋理回調都是在同一個線程callback。所有的紋理回調都是通過對象AliRtcTextureObserver實現。
前提條件
開啟網路攝影機的紋理採集和紋理編碼。
String extras = "{\"user_specified_camera_texture_capture\":\"TRUE\",\"user_specified_texture_encode\":\"TRUE\" }";
_engine = AliRtcEngine.getInstance(getApplicationContext(), extras);
1.註冊紋理回調
public abstract void registerLocalVideoTextureObserver(AliRtcTextureObserver observer);2.執行回調
openGL context建立後回調
@Override
public void onTextureCreate(long context) {
context_ = context ;
Log.d(TAG, "texture context: "+context_+" create!") ;
}openGL 紋理變更回調
@Override
public int onTextureUpdate(int textureId, int width, int height, AliRtcEngine.AliRtcVideoSample videoSample) {
/*
* 進行 textureid處理
*/
++log_ref ;
return textureId;
} openGL 上下文刪除回調
@Override
public void onTextureDestroy() {
Log.d(TAG, "texture context: "+context_+" destory!") ;
}3.取消紋理回調
public abstract void unRegisterLocalVideoTextureObserver();iOS
一、視訊框架回調
1.註冊回調
回調通過AliRtcEngineDelegate回調。
registerVideoSampleObserver2.返回關注的位置
SDK根據該回調傳回值確認回調位置。
/**
* @brief 視頻資料輸出位置
*/
typedef NS_ENUM(NSInteger, AliRtcVideoObserPosition) {
/** 採集視頻資料,對應輸出回調onCaptureVideoSample */
AliRtcPositionPostCapture = 1 << 0,
/** 渲染視頻資料,對應輸出回調onRemoteVideoSample */
AliRtcPositionPreRender = 1 << 1,
/** 編碼前視頻資料,對應輸出回調onPreEncodeVideoSample */
AliRtcPositionPreEncoder = 1 << 2,
};
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
- (NSInteger)onGetVideoObservedFramePosition;3.返回需要輸出格式
iOS 平台如果需要返回cvPixelBuffer需要建立引擎設定extra欄位增加下列選項,"user_specified_native_buffer_observer"="TRUE"。
/**
* @brief 視頻資料格式
*/
typedef NS_ENUM(NSInteger, AliRtcVideoFormat) {
AliRtcVideoFormat_UNKNOW = -1,
AliRtcVideoFormat_BGRA = 0,
AliRtcVideoFormat_I420,
AliRtcVideoFormat_NV21,
AliRtcVideoFormat_NV12,
AliRtcVideoFormat_RGBA,
AliRtcVideoFormat_I422,
AliRtcVideoFormat_ARGB,
AliRtcVideoFormat_ABGR,
AliRtcVideoFormat_RGB24,
AliRtcVideoFormat_BGR24,
AliRtcVideoFormat_RGB565,
AliRtcVideoFormat_TextureOES,
AliRtcVideoFormat_Texture2D,
AliRtcVideoFormat_H264,
AliRtcVideoFormat_H265,
AliRtcVideoFormat_File,
AliRtcVideoFormat_cvPixelBuffer,
};
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
- (AliRtcVideoFormat)onGetVideoFormatPreference {
return AliRtcVideoFormat_I420;
}4.返回需要的對齊
/**
* @brief 視頻輸出寬度對齊
*/
typedef enum {
/** 保持原有視頻寬度(預設值) */
AliRtcAlignmentDefault = 0,
/** 寬度偶數對齊 */
AliRtcAlignmentEven = 1,
/** 寬度是4的倍數 */
AliRtcAlignment4 = 2,
/** 寬度是8的倍數 */
AliRtcAlignment8 = 3,
/** 寬度是16的倍數 */
AliRtcAlignment16 = 4,
} AliRtcVideoObserAlignment;
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
- (AliRtcVideoObserAlignment)onGetVideoAlignment {
return AliRtcAlignmentDefault;
}5.擷取的視訊框架是否需要鏡像
/**
* @brief 視頻輸出資料是否需要鏡像
* @return
* - YES: 鏡像
* - NO: 不鏡像(預設)
*/
/*
* 在AliRtcEngine::registerVideoSampleObserver之後觸發
*/
- (BOOL)onGetObserverDataMirrorApplied {
return FLASE ;
}6.執行回調
/**
* @brief 訂閱的本地採集視頻資料回調
* @param videoSource 視頻流類型
* @param videoSample 視頻裸資料
* @return
* - YES: 需要寫回SDK(只對I420和CVPixelBuffer(ios/mac)有效)
* - NO: 不需要寫回SDK
*/
- (BOOL)onCaptureVideoSample:(AliRtcVideoSource)videoSource videoSample:(AliRtcVideoDataSample *_Nonnull)videoSample {
/*
* do....
*/
return FALSE ;
}
/**
* @brief 訂閱的本地編碼前視頻資料回調
* @param videoSource 視頻流類型
* @param videoSample 視頻裸資料
* @return
* - YES: 需要寫回SDK(只對I420和CVPixelBuffer(ios/mac)有效)
* - NO: 不需要寫回SDK
*/
- (BOOL)onPreEncodeVideoSample:(AliRtcVideoSource)videoSource videoSample:(AliRtcVideoDataSample *_Nonnull)videoSample {
/*
* do....
*/
return FALSE ;
}
/**
* @brief 訂閱的遠端視頻資料回調
* @param uid 使用者ID
* @param videoSource 視頻流類型
* @param videoSample 視頻裸資料
* @return
* - YES: 需要寫回SDK(只對I420和CVPixelBuffer(ios/mac)有效)
* - NO: 不需要寫回SDK
*/
- (BOOL)onRemoteVideoSample:(NSString *_Nonnull)uid videoSource:(AliRtcVideoSource)videoSource videoSample:(AliRtcVideoDataSample *_Nonnull)videoSample {
/*
* do....
*/
return TRUE ;
}
7.取消回調註冊
unregisterVideoSampleObserver二、紋理處理
因為openGL執行緒模式的原因,所有的紋理回調都是在同一個線程callback。
1.註冊紋理回調
registerLocalVideoTexture2.執行回調
openGL context建立後回調。
/**
* @brief OpenGL上下文建立回調
* @param context OpenGL上下文
* @note 該回調是在SDK內部OpenGL上下文建立的時候觸發
*/
- (void)onTextureCreate:(void *_Nullable)context {
[[beautifyMoudle_ shared] create];
}openGL 紋理變更回調
/**
* @brief OpenGL紋理更新回調
* @param textureId OpenGL紋理ID
* @param width OpenGL紋理寬
* @param height OpenGL紋理高
* @param videoSample 視訊框架資料,詳見 {@link AliRtcVideoDataSample}
* @return OpenGL紋理ID
* @note
* - 該回調會在每一幀視頻資料上傳到OpenGL紋理之後觸發,當外部註冊了OpenGL紋理資料觀測器,在該回調中可以對紋理進行處理,並返回處理後的紋理ID
* - 注意該回調傳回值必須為有效紋理ID,如果不做任何處理必須返回參數textureId
* 回調的TextureID是 AliRtcVideoFormat_Texture2D 格式
*/
- (int)onTextureUpdate:(int)textureId width:(int)width height:(int)height videoSample:(AliRtcVideoDataSample *_Nonnull)videoSample {
if ( [[beautifyMoudle_ shared] enabled] == NO) {
return textureId;
}
int texId = [[beautifyMoudle_ shared] processTextureToTexture:textureId Width:width Height:height];
if(texId<0) {
texId = textureId;
}
return texId;
}openGL 上下文刪除回調
- (void)onTextureDestory
{
if (self.settingModel.chnnelType == ChannelTypePrimary) {
[[beautifyMoudle_ shared] destroy];
}
}3.取消紋理回調
unregisterLocalVideoTexture