Android投屏(屏幕共享)设计需要考虑的关键因素
許多開(kāi)發(fā)者,在做智慧教室同屏、會(huì)議同屏之類的方案時(shí),基于Andriod平臺(tái)的采集,往往遇到各種各樣的問(wèn)題,以下就幾個(gè)點(diǎn),拋磚引玉:
1. 內(nèi)網(wǎng)環(huán)境下,組播還是RTMP?
回答:這個(gè)問(wèn)題,被無(wú)數(shù)的開(kāi)發(fā)者問(wèn)到,為此,單獨(dú)寫了篇博客論證:https://blog.csdn.net/renhui1112/article/details/86741428,感興趣的可以參考下,簡(jiǎn)單來(lái)說(shuō),能RTMP的,就RTMP,如果真是內(nèi)網(wǎng)環(huán)境下,沒(méi)有并發(fā)瓶頸的同屏,可以啟動(dòng)內(nèi)置RTSP服務(wù)(走單播),然后,其他終端拉流也不失為一個(gè)好的方案。
2. 推送分辨率如何設(shè)定或縮放?
回答:一般來(lái)說(shuō),好多Android設(shè)備,特別是高分屏,拿到的視頻原始寬高非常大,如果推原始分辨率,編碼和上行壓力大,所以,一般建議,適當(dāng)縮放,比如寬高縮放至2/3,縮放一般建議等比例縮放,此外,縮放寬高建議16字節(jié)對(duì)齊。
廢話不多說(shuō),上實(shí)例代碼:
private void createScreenEnvironment() {sreenWindowWidth = mWindowManager.getDefaultDisplay().getWidth();screenWindowHeight = mWindowManager.getDefaultDisplay().getHeight();Log.i(TAG, "screenWindowWidth: " + sreenWindowWidth + ",screenWindowHeight: "+ screenWindowHeight);if (sreenWindowWidth > 800){if (screenResolution == SCREEN_RESOLUTION_STANDARD){scale_rate = SCALE_RATE_HALF;sreenWindowWidth = align(sreenWindowWidth / 2, 16);screenWindowHeight = align(screenWindowHeight / 2, 16);}else if(screenResolution == SCREEN_RESOLUTION_LOW){scale_rate = SCALE_RATE_TWO_FIFTHS;sreenWindowWidth = align(sreenWindowWidth * 2 / 5, 16);}}Log.i(TAG, "After adjust mWindowWidth: " + sreenWindowWidth + ", mWindowHeight: " + screenWindowHeight);int pf = mWindowManager.getDefaultDisplay().getPixelFormat();Log.i(TAG, "display format:" + pf);DisplayMetrics displayMetrics = new DisplayMetrics();mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);mScreenDensity = displayMetrics.densityDpi;mImageReader = ImageReader.newInstance(sreenWindowWidth,screenWindowHeight, 0x1, 6);mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);}3. 橫豎屏自動(dòng)適配
回答:因?yàn)闄M豎屏狀態(tài)下,采集的屏幕寬高不一樣,如果橫豎屏切換,這個(gè)時(shí)候,需要考慮到橫豎屏適配問(wèn)題,確保比如豎屏狀態(tài)下,切換到橫屏?xí)r,推拉流兩端可以自動(dòng)適配,橫豎屏自動(dòng)適配,編碼器需要重啟,拉流端,需要能自動(dòng)適配寬高變化,自動(dòng)播放。
4. 一定的補(bǔ)幀策略
回答:好多人不理解為什么要補(bǔ)幀,實(shí)際上,屏幕采集的時(shí)候,屏幕不動(dòng)的話,不會(huì)一直有數(shù)據(jù)下去,這個(gè)時(shí)候,比較好的做法是,保存最后一幀數(shù)據(jù),設(shè)定一定的補(bǔ)幀間隔,確保不會(huì)因?yàn)閹g距太大,導(dǎo)致播放端幾秒都收不到數(shù)據(jù),當(dāng)然,如果服務(wù)器可以緩存GOP,這個(gè)問(wèn)題迎刃而解。
5. 異常網(wǎng)絡(luò)處理、事件回調(diào)機(jī)制
回答:如果是走RTMP,網(wǎng)絡(luò)抖動(dòng)或者其他網(wǎng)絡(luò)異常,需要有好重連機(jī)制和狀態(tài)回饋機(jī)制。
class EventHandeV2 implements NTSmartEventCallbackV2 {@Overridepublic void onNTSmartEventCallbackV2(long handle, int id, long param1, long param2, String param3, String param4, Object param5) {Log.i(TAG, "EventHandeV2: handle=" + handle + " id:" + id);String publisher_event = "";switch (id) {case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STARTED:publisher_event = "開(kāi)始..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTING:publisher_event = "連接中..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTION_FAILED:publisher_event = "連接失敗..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CONNECTED:publisher_event = "連接成功..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_DISCONNECTED:publisher_event = "連接斷開(kāi)..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_STOP:publisher_event = "關(guān)閉..";break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RECORDER_START_NEW_FILE:publisher_event = "開(kāi)始一個(gè)新的錄像文件 : " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_ONE_RECORDER_FILE_FINISHED:publisher_event = "已生成一個(gè)錄像文件 : " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_SEND_DELAY:publisher_event = "發(fā)送時(shí)延: " + param1 + " 幀數(shù):" + param2;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_CAPTURE_IMAGE:publisher_event = "快照: " + param1 + " 路徑:" + param3;if (param1 == 0) {publisher_event = publisher_event + "截取快照成功..";} else {publisher_event = publisher_event + "截取快照失敗..";}break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUBLISHER_RTSP_URL:publisher_event = "RTSP服務(wù)URL: " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_RESPONSE_STATUS_CODE:publisher_event ="RTSP status code received, codeID: " + param1 + ", RTSP URL: " + param3;break;case NTSmartEventID.EVENT_DANIULIVE_ERC_PUSH_RTSP_SERVER_NOT_SUPPORT:publisher_event ="服務(wù)器不支持RTSP推送, 推送的RTSP URL: " + param3;break;}String str = "當(dāng)前回調(diào)狀態(tài):" + publisher_event;Log.i(TAG, str);Message message = new Message();message.what = PUBLISHER_EVENT_MSG;message.obj = publisher_event;handler.sendMessage(message);}}6. 部分屏幕數(shù)據(jù)采集
回答:我們遇到的好多場(chǎng)景下,教室端,會(huì)拿出來(lái)3/4的區(qū)域用來(lái)投遞給學(xué)生看,1/4的區(qū)域,用來(lái)做一些指令等操作,這個(gè)時(shí)候,就需要考慮屏幕區(qū)域裁剪,接口可做如下設(shè)計(jì):
/*** 投遞裁剪過(guò)的RGBA數(shù)據(jù)** @param data: RGBA data** @param rowStride: stride information** @param width: width** @param height: height** @param clipedLeft: 左; clipedTop: 上; clipedwidth: 裁剪后的寬; clipedHeight: 裁剪后的高; 確保傳下去裁剪后的寬、高均為偶數(shù)** @return {0} if successful*/public native int SmartPublisherOnCaptureVideoClipedRGBAData(long handle, ByteBuffer data, int rowStride, int width, int height, int clipedLeft, int clipedTop, int clipedWidth, int clipedHeight); //實(shí)際裁剪比例,可酌情自行調(diào)整int left = 100;int cliped_left = 0;int top = 0;int cliped_top = 0;int cliped_width = width_;int cliped_height = height_;if(scale_rate == SCALE_RATE_HALF){cliped_left = left / 2;cliped_top = top / 2;//寬度裁剪后,展示3/4比例cliped_width = (width_ *3)/4;//高度不做裁剪cliped_height = height_;}else if(scale_rate == SCALE_RATE_TWO_FIFTHS){cliped_left = left * 2 / 5;cliped_top = top * 2 / 5;//寬度裁剪后,展示3/4比例cliped_width = (width_ *3)/4;//高度不做裁剪cliped_height = height_;}if(cliped_width % 2 != 0){cliped_width = cliped_width + 1;}if(cliped_height % 2 != 0){cliped_height = cliped_height + 1;}if ( (cliped_left + cliped_width) > width_){Log.e(TAG, " invalid cliped region settings, cliped_left: " + cliped_left + " cliped_width:" + cliped_width + " width:" + width_);return;}if ( (cliped_top + cliped_height) > height_){Log.e(TAG, "invalid cliped region settings, cliped_top: " + cliped_top + " cliped_height:" + cliped_height + " height:" + height_);return;}//Log.i(TAG, " clipLeft: " + cliped_left + " clipTop: " + cliped_top + " clipWidth: " + cliped_width + " clipHeight: " + cliped_height);libPublisher.SmartPublisherOnCaptureVideoClipedRGBAData(publisherHandle, last_buffer, row_stride_,width_, height_, cliped_left, cliped_top, cliped_width, cliped_height );7. 文字、圖片水印
回答:好多場(chǎng)景下,同屏者會(huì)把公司logo,和一定的文字信息展示在推送端,這個(gè)時(shí)候,需要考慮到文字和圖片水印問(wèn)題,具體可參考如下接口設(shè)置:
/*** Set Text water-mark(設(shè)置文字水印)* * @param fontSize: it should be "MEDIUM", "SMALL", "BIG"* * @param waterPostion: it should be "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT".* * @param xPading, yPading: the distance of the original picture.* * <pre> The interface is only used for setting font water-mark when publishing stream. </pre> * * @return {0} if successful*/public native int SmartPublisherSetTextWatermark(long handle, String waterText, int isAppendTime, int fontSize, int waterPostion, int xPading, int yPading);/*** Set Text water-mark font file name(設(shè)置文字水印字體路徑)** @param fontFileName: font full file name, e.g: /system/fonts/DroidSansFallback.ttf** @return {0} if successful*/public native int SmartPublisherSetTextWatermarkFontFileName(long handle, String fontFileName);/*** Set picture water-mark(設(shè)置png圖片水印)* * @param picPath: the picture working path, e.g: /sdcard/logo.png* * @param waterPostion: it should be "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT".* * @param picWidth, picHeight: picture width & height* * @param xPading, yPading: the distance of the original picture.* * <pre> The interface is only used for setting picture(logo) water-mark when publishing stream, with "*.png" format </pre> * * @return {0} if successful*/public native int SmartPublisherSetPictureWatermark(long handle, String picPath, int waterPostion, int picWidth, int picHeight, int xPading, int yPading);總結(jié):其實(shí)一個(gè)好的同屏系統(tǒng),需要考慮的地方遠(yuǎn)不止以上幾點(diǎn),比如編碼參數(shù)策略等,都需要考量,后續(xù)有機(jī)會(huì)再和大家做進(jìn)一步分享。
總結(jié)
以上是生活随笔為你收集整理的Android投屏(屏幕共享)设计需要考虑的关键因素的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【深度学习】什么是目标检测中的平均精度均
- 下一篇: 【Python】刚刚,Python3.1