亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

2013年美團外賣成立,至今一直迅猛發(fā)展。隨著外賣業(yè)務(wù)量級與日俱增,單一的文字和圖片已無法滿足商家的需求,商家迫切需要更豐富的商品描述手段吸引用戶,增加流量,進而提高下單轉(zhuǎn)化率和下單量。商品視頻的引入,在一定程度上可以提升商品信息描述豐富度,以更加直觀的方式為商家引流,增加收益。為此,商家端引入了視頻功能,進行了一系列視頻功能開發(fā),核心功能包含視頻處理(混音,濾鏡,加水印,動畫等)、視頻拍攝、合成等,最終效果圖如下所示:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

自視頻功能上線后,每周視頻樣本量及使用視頻的商家量大幅增加,視頻錄制成功率達99.533%,視頻處理成功率98.818%,音頻處理成功率99.959%,Crash率穩(wěn)定在0.1‰,穩(wěn)定性高且可用性強。目前,視頻功能已在蜜蜂App、閃購業(yè)務(wù)和商家業(yè)務(wù)上使用。

對于視頻鏈路的開發(fā),我們經(jīng)歷了方案選型、架構(gòu)設(shè)計及優(yōu)化、業(yè)務(wù)實踐、功能測試、監(jiān)控運維、更新維護等各個環(huán)節(jié),核心環(huán)節(jié)如下圖所示。在開發(fā)過程中,遇到了各種技術(shù)問題和挑戰(zhàn),下文會針對遇到的問題、挑戰(zhàn),及其解決方案進行重點闡述。

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

方案選型

在方案選型時,重點對核心流程和視頻格式進行選型。我們以功能覆蓋度、穩(wěn)定性及效率、可定制性、成本及開源性做為核心指標(biāo),從而衡量方案的高可用性和可行性。

1. 核心流程選型

視頻開發(fā)涉及的核心流程包括播放、錄制、合成、裁剪、后期處理(編解碼、濾鏡、混音、動畫、水印)等。結(jié)合商家端業(yè)務(wù)場景,我們有針對性的進行方案調(diào)研。重點調(diào)研了業(yè)界現(xiàn)有方案,如阿里的云視頻點播方案、騰訊云視頻點播方案、大眾點評App的UGC方案,及其它的一些第三方開源方案等,并進行了整體匹配度的對比,如下圖所示:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

阿里和騰訊的云視頻點播方案比較成熟,集成度高,且能力豐富,穩(wěn)定性及效率也很高。但兩者成本較高,需要收費,且SDK大小均在15M以上,對于我們的業(yè)務(wù)場景來說有些過于臃腫,定制性較弱,無法迅速的支持我們做定制性擴展。

當(dāng)時的大眾點評App UGC方案,基礎(chǔ)能力是滿足的,但因業(yè)務(wù)場景差異:

  • 比如外賣的視頻拍攝功能要求在豎屏下保證16:9的視頻寬高比,這就需要對原有的采集區(qū)域進行截取,視頻段落的裁剪支持不夠等,業(yè)務(wù)場景的差異導(dǎo)致了實現(xiàn)方案存在巨大的差異,故放棄了大眾點評App UGC方案。其他的一些開源方案(比如Grafika等),也無法滿足要求,這里不再一一贅述。

 

通過技術(shù)調(diào)研和分析,吸取各開源項目的優(yōu)點,并參考大眾點評App UGC、google CTS方案,對核心流程做了最終的方案選型,打造一個適合我們業(yè)務(wù)場景的方案,如下表所示:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

2. 視頻格式選型

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

  • 采用H.264的視頻協(xié)議:H.264的標(biāo)準(zhǔn)成熟穩(wěn)定,普及率高。其最大的優(yōu)勢是具有很高的數(shù)據(jù)壓縮比率,在同等圖像質(zhì)量的條件下,H.264的壓縮比是MPEG-2的2倍以上,是MPEG-4的1.5~2倍。
  • 采用AAC的音頻協(xié)議:AAC是一種專為聲音數(shù)據(jù)設(shè)計的文件壓縮格式。它采用了全新的算法進行編碼,是新一代的音頻有損壓縮技術(shù),具有更加高效,更具有“性價比”的特點。

整體架構(gòu)

我們整體的架構(gòu)設(shè)計,用以滿足業(yè)務(wù)擴展和平臺化需要,可復(fù)用、可擴展,且可快速接入。架構(gòu)采用分層設(shè)計,基礎(chǔ)能力和組件進行下沉,業(yè)務(wù)和視頻能力做分離,最大化降低業(yè)務(wù)方的接入成本,三方業(yè)務(wù)只需要接入視頻基礎(chǔ)SDK,直接使用相關(guān)能力組件或者工具即可。

整體架構(gòu)分為四層,分別為平臺層、核心能力層、基礎(chǔ)組件層、業(yè)務(wù)層。

  • 平臺層:依賴系統(tǒng)提供的平臺能力,比如Camera、OpenGL、MediaCodec和MediaMuxer等,也包括引入的平臺能力,比如ijkplayer播放器、mp4parser。
  • 核心能力層:該層提供了視頻服務(wù)的核心能力,包括音視頻編解碼、音視頻的轉(zhuǎn)碼引擎、濾鏡渲染能力等。
  • 基礎(chǔ)能力層:暴露了基礎(chǔ)組件和能力,提供了播放、裁剪、錄屏等基礎(chǔ)組件和對應(yīng)的基礎(chǔ)工具類,并提供了可定制的播放面板,可定制的緩存接口等。
  • 業(yè)務(wù)層:包括段落拍攝、自由拍攝、視頻空間、拍攝模版預(yù)覽及加載等。

我們的視頻能力層對業(yè)務(wù)層是透明的,業(yè)務(wù)層與能力層隔離,并對業(yè)務(wù)層提供了部分定制化的接口支持,這樣的設(shè)計降低了業(yè)務(wù)方的接入成本,并方便業(yè)務(wù)方的擴展,比如支持蜜蜂App的播放面板定制,還支持緩存策略、編解碼策略的可定制。整體設(shè)計如下圖所示:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

實踐經(jīng)驗

在視頻開發(fā)實踐中,因業(yè)務(wù)場景的復(fù)雜性,我們遇到了多種問題和挑戰(zhàn)。下面以核心功能為基點,圍繞各功能遇到的問題做詳細介紹。

視頻播放

播放器是視頻播放基礎(chǔ)。針對播放器,我們進行了一系列的方案調(diào)研和選擇。在此環(huán)節(jié),遇到的挑戰(zhàn)如下:

1. 兼容性問題

2. 緩存問題

針對兼容性問題,Android有原生的MediaPlayer,但其版本兼容問題偏多且支持格式有限,而我們需要支持播放本地視頻,本地視頻格式又無法控制,故該方案被舍棄。ijkplayer基于FFmpeg,與MediaPlayer相比,優(yōu)點比較突出:具備跨平臺能力,支持Android與IOS;提供了類似MediaPlayer的API,可兼容不同版本;可實現(xiàn)軟硬解碼自由切換,擁有FFmpeg的能力,支持多種流媒體協(xié)議。基于上述原因,我們最終決定選用ijkplayer。

但緊接著又發(fā)現(xiàn)ijkplayer本身不支持邊緩存邊播放,頻繁的加載視頻導(dǎo)致耗費大量的流量,且在弱網(wǎng)或者3G網(wǎng)絡(luò)下很容易導(dǎo)致播放卡頓,所以這里就衍生出了緩存的問題。

針對緩存問題,引入AndroidVideoCache的技術(shù)方案,利用本地的代理去請求數(shù)據(jù),先本地保存文件緩存,客戶端通過Socket讀取本地的文件緩存進行視頻播放,這樣就做到了邊播放邊緩存的策略,流程如下圖:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

此外,我們還對AndroidVideoCache做了一些技術(shù)改造:

  • 優(yōu)化緩存策略。針對緩存策略的單一性,支持有限的最大文件數(shù)和文件大小問題,調(diào)整為由業(yè)務(wù)方可以動態(tài)定制緩存策略;
  • 解決內(nèi)存泄露隱患。對其頁面退出時請求不關(guān)閉會導(dǎo)致的內(nèi)存泄露,為其添加了完整的生命周期監(jiān)控,解決了內(nèi)存泄露問題。

視頻錄制

在視頻拍攝的時候,最為常用的方式是采用MediaRecorder+Camera技術(shù),采集攝像頭可見區(qū)域。但因我們的業(yè)務(wù)場景要求視頻采集的時候,只錄制采集區(qū)域的部分區(qū)域且比例保持寬高比16:9,在保證預(yù)覽圖像不拉伸的情況下,只能對完整的采集區(qū)域做裁剪,這無形增加了開發(fā)難度和挑戰(zhàn)。通過大量的資料分析,重點調(diào)研了有兩種方案:

  1. Camera+AudioRecord+MediaCodec+Surface
  2. MediaRecorder+MediaCodec

方案1需要Camera采集YUV幀,進行截取采集,最后再將YUV幀和PCM幀進行編碼生成mp4文件,雖然其效率高,但存在不可把控的風(fēng)險。

方案2綜合評估后是改造風(fēng)險最小的。綜合成本和風(fēng)險考量,我們保守的采用了方案2,該方案是對裁剪區(qū)域進行坐標(biāo)換算(如果用前置攝像頭拍攝錄制視頻,會出現(xiàn)預(yù)覽畫面和錄制的視頻是鏡像的問題,需要處理)。當(dāng)錄制完視頻后,生成了mp4文件,用MediaCodec對其編碼,在編碼階段再利用OpenGL做內(nèi)容區(qū)域的裁剪來實現(xiàn)。但該方案又引發(fā)了如下挑戰(zhàn)。

(1)對焦問題

因我們對采集區(qū)域做了裁剪,引發(fā)了點觸對焦問題。比如用戶點擊了相機預(yù)覽畫面,正常情況下會觸發(fā)相機的對焦動作,但是用戶的點擊區(qū)域只是預(yù)覽畫面的部分區(qū)域,這就導(dǎo)致了相機的對焦區(qū)域錯亂,不能正常進行對焦。后期經(jīng)過問題排查,對點觸區(qū)域再次進行相應(yīng)的坐標(biāo)變換,最終得到正確的對焦區(qū)域。

(2)兼容適配

我們的視頻錄制利用MediaRecorder,在獲取配置信息時,由于Android碎片化問題,不同的設(shè)備支持的配置信息不同,所以就會出現(xiàn)設(shè)備適配問題。

 // VIVO Y66 模版拍攝時候,播放某些有問題的視頻文件的同時去錄制視頻,會導(dǎo)致MediaServer掛掉的問題
 // 發(fā)現(xiàn)將1080P尺寸的配置降低到720P即可避免此問題
 // 但是720P尺寸的配置下,又存在綠邊問題,因此再降到480
 if(isVIVOY66() && mMediaServerDied) {
 return getCamcorderProfile(CamcorderProfile.QUALITY_480P);
 }
 //SM-C9000,在1280 x 720 分辨率時有一條綠邊。網(wǎng)上有種說法是GPU對數(shù)據(jù)進行了優(yōu)化,使得GPU產(chǎn)生的圖像分辨率
 //和常規(guī)分辨率存在微小差異,造成圖像色彩混亂,修復(fù)后存在綠邊問題。
 //測試發(fā)現(xiàn),降低分辨率或者升高分辨率都可以繞開這個問題。
 if (VideoAdapt.MODEL_SM_C9000.equals(Build.MODEL)) {
 return getCamcorderProfile(CamcorderProfile.QUALITY_HIGH);
 }
 // 優(yōu)先選擇 1080 P的配置
 CamcorderProfile camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_1080P);
 if (camcorderProfile == null) {
 camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_720P);
 }
 // 某些機型上這個 QUALITY_HIGH 有點問題,可能通過這個參數(shù)拿到的配置是1080p,所以這里也可能拿不到
 if (camcorderProfile == null) {
 camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_HIGH);
 }
 // 兜底
 if (camcorderProfile == null) {
 camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_480P);
 }

視頻合成

我們的視頻拍攝有段落拍攝這種場景,商家可根據(jù)事先下載的模板進行分段拍攝,最后會對每一段的視頻做拼接,拼接成一個完整的mp4文件。mp4由若干個Box組成,所有數(shù)據(jù)都封裝在Box中,且Box可再包含Box的被稱為Container Box。mp4中Track表示一個視頻或音頻序列,是Sample的集合,而Sample又可分為Video Smaple和Audio Sample。Video Smaple代表一幀或一組連續(xù)視頻幀,Audio Sample即為一段連續(xù)的壓縮音頻數(shù)據(jù)。(詳見mp4文件結(jié)構(gòu)。)

基于上面的業(yè)務(wù)場景需要,視頻合成的基礎(chǔ)能力我們采用mp4parser技術(shù)實現(xiàn)(也可用FFmpeg等其他手段)。mp4parser在拼接視頻時,先將視頻的音軌和視頻軌進行分離,然后進行視頻和音頻軌的追加,最終將合成后的視頻軌和音頻軌放入容器里(這里的容器就是mp4的Box)。采用mp4parser技術(shù)簡單高效,API設(shè)計簡潔清晰,滿足需求。

但我們發(fā)現(xiàn)某些被編碼或處理過的mp4文件可能會存在特殊的Box,并且mp4parser是不支持的。經(jīng)過源碼分析和原因推導(dǎo),發(fā)現(xiàn)當(dāng)遇到這種特殊格式的Box時,會申請分配一個比較大的空間用來存放數(shù)據(jù),很容易造成OOM(內(nèi)存溢出),見下圖所示。于是,我們對這種拼接場景下做了有效規(guī)避,僅在段落拍攝下使用mp4parser的拼接功能,保證處理過的文件不會包含這種特殊的Box。

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

視頻裁剪

我們剛開始采用mp4parser技術(shù)完成視頻裁剪,在實踐中發(fā)現(xiàn)其精度誤差存在很大的問題,甚至?xí)绊懻5臉I(yè)務(wù)需求。比如禁止裁剪出3s以下的視頻,但是由于mp4parser產(chǎn)生的精度誤差,導(dǎo)致4-5s的視頻很容易裁剪出少于3s的視頻。究其原因,mp4parser只能在關(guān)鍵幀(又稱I幀,在視頻編碼中是一種自帶全部信息的獨立幀)進行切割,這樣就可能存在一些問題。比如在視頻截取的起始時間位置并不是關(guān)鍵幀,會造成誤差,無法保證精度而且是秒級誤差。以下為mp4parser裁剪的關(guān)鍵代碼:

public static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
 double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
 long currentSample = 0;
 double currentTime = 0;
 for (int i = 0; i < track.getSampleDurations().length; i++) {
 long delta = track.getSampleDurations()[i];
 int index = Arrays.binarySearch(track.getSyncSamples(), currentSample + 1);
 if (index >= 0) {
 timeOfSyncSamples[index] = currentTime;
 }
 currentTime += ((double) delta / (double) track.getTrackMetaData().getTimescale());
 currentSample++;
 }
 double previous = 0;
 for (double timeOfSyncSample : timeOfSyncSamples) {
 if (timeOfSyncSample > cutHere) {
 if (next) {
 return timeOfSyncSample;
 } else {
 return previous;
 }
 }
 previous = timeOfSyncSample;
 }
 return timeOfSyncSamples[timeOfSyncSamples.length - 1];
}

為了解決精度問題,我們廢棄了mp4parser,采用MediaCodec的方案,雖然該方案會增加復(fù)雜度,但是誤差精度大大降低。

方案具體實施如下:先獲得目標(biāo)時間的上一幀信息,對視頻解碼,然后根據(jù)起始時間和截取時長進行切割,最后將裁剪后的音視頻信息進行壓縮編碼,再封裝進mp4容器中,這樣我們的裁剪精度從秒級誤差降低到微秒級誤差,大大提高了容錯率。

視頻處理

視頻處理是整個視頻能力最核心的部分,會涉及硬編解碼(遵循OpenMAX框架)、OpenGL、音頻處理等相關(guān)能力。

下圖是視頻處理的核心流程,會先將音視頻做分離,并行處理音視頻的編解碼,并加入特效處理,最后合成進一個mp4文件中。

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

在實踐過程中,我們遇到了一些需要特別注意的問題,比如開發(fā)時遇到的坑,嚴重的兼容性問題(包括硬件兼容性和系統(tǒng)版本兼容性問題)等。下面重點講幾個有代表性的問題。

1. 偶數(shù)寬高的編解碼器

視頻經(jīng)過編碼后輸出特定寬高的視頻文件時出現(xiàn)了如下錯誤,信息里僅提示了Colorformat錯誤,具體如下:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

查閱大量資料,也沒能解釋清楚這個異常的存在。基于日志錯誤信息,并通過系統(tǒng)源碼定位,也只是發(fā)現(xiàn)是了和設(shè)置的參數(shù)不兼容導(dǎo)致的。經(jīng)過反復(fù)的試錯,最后確認是部分編解碼器只支持偶數(shù)的視頻寬高,所以我們對視頻的寬高做了偶數(shù)限制。引起該問題的核心代碼如下:

status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg,
 sp<AMessage> &outputFormat, sp<AMessage> &inputFormat) {
 if (!msg->findInt32("color-format", &tmp)) {
 return INVALID_OPERATION;
 }
 OMX_COLOR_FORMATTYPE colorFormat =
 static_cast<OMX_COLOR_FORMATTYPE>(tmp);
 status_t err = setVideoPortFormatType(
 kPortIndexInput, OMX_VIDEO_CodingUnused, colorFormat);
 if (err != OK) {
 ALOGE("[%s] does not support color format %d",
 mComponentName.c_str(), colorFormat);
 return err;
 }
 .......
}
status_t ACodec::setVideoPortFormatType(OMX_U32 portIndex,OMX_VIDEO_CODINGTYPE compressionFormat,
 OMX_COLOR_FORMATTYPE colorFormat,bool usingNativeBuffers) {
 ......
 for (OMX_U32 index = 0; index <= kMaxIndicesToCheck; ++index) {
 format.nIndex = index;
 status_t err = mOMX->getParameter(
 mNode, OMX_IndexParamVideoPortFormat,
 &format, sizeof(format));
 if (err != OK) {
 return err;
 }
 ......
}

2. 顏色格式

我們在處理視頻幀的時候,一開始獲得的是從Camera讀取到的基本的YUV格式數(shù)據(jù),如果給編碼器設(shè)置YUV幀格式,需要考慮YUV的顏色格式。這是因為YUV根據(jù)其采樣比例,UV分量的排列順序有很多種不同的顏色格式,Android也支持不同的YUV格式,如果顏色格式不對,會導(dǎo)致花屏等問題。

3. 16位對齊

這也是硬編碼中老生常談的問題了,因為H264編碼需要16*16的編碼塊大小。如果一開始設(shè)置輸出的視頻寬高沒有進行16字節(jié)對齊,在某些設(shè)備(華為,三星等)就會出現(xiàn)綠邊,或者花屏。

4. 二次渲染

4.1 視頻旋轉(zhuǎn)

在最后的視頻處理階段,用戶可以實時的看到加濾鏡后的視頻效果。這就需要對原始的視頻幀進行二次處理,然后在播放器的Surface上渲染。首先我們需要OpenGL 的渲染環(huán)境(通過OpenGL的固有流程創(chuàng)建),渲染環(huán)境完成后就可以對視頻的幀數(shù)據(jù)進行二次處理了。通過SurfaceTexture的updateTexImage接口,可將視頻流中最新的幀數(shù)據(jù)更新到對應(yīng)的GL紋理,再操作GL紋理進行濾鏡、動畫等處理。在處理視頻幀數(shù)據(jù)的時候,首先遇到的是角度問題。在正常播放下(不利用OpenGL處理情況下)通過設(shè)置TextureView的角度(和視頻的角度做轉(zhuǎn)換)就可以解決,但是加了濾鏡后這一方案就失效了。原因是視頻的原始數(shù)據(jù)經(jīng)過紋理處理再渲染到Surface上,單純設(shè)置TextureView的角度就失效了,解決方案就是對OpenGL傳入的紋理坐標(biāo)做相應(yīng)的旋轉(zhuǎn)(依據(jù)視頻的本身的角度)。

4.2 渲染停滯

視頻在二次渲染后會出現(xiàn)偶現(xiàn)的畫面停滯現(xiàn)象,主要是SurfaceTexture的OnFrameAvailableListener不返回數(shù)據(jù)了。該問題的根本原因是GPU的渲染和視頻幀的讀取不同步,進而導(dǎo)致SurfaceTexture的底層核心BufferQueue讀取Buffer出了問題。下面我們通過BufferQueue的機制和核心源碼深入研究下:

首先從二次渲染的工作流程入手。從圖像流(來自Camera預(yù)覽、視頻解碼、GL繪制場景等)中獲得幀數(shù)據(jù),此時OnFrameAvailableListener會回調(diào)。再調(diào)用updateTexImage(),會根據(jù)內(nèi)容流中最近的圖像更新SurfaceTexture對應(yīng)的GL紋理對象。我們再對紋理對象做處理,比如添加濾鏡等效果。SurfaceTexture底層核心管理者是BufferQueue,本身基于生產(chǎn)者消費者模式。

BufferQueue管理的Buffer狀態(tài)分為:FREE、DEQUEUED、QUEUED、ACQUIRED、SHARED。當(dāng)Producer需要填充數(shù)據(jù)時,需要先Dequeue一個Free狀態(tài)的Buffer,此時Buffer的狀態(tài)為DEQUEUED,成功后持有者為Producer。隨后Producer填充數(shù)據(jù)完畢后,進行Queue操作,Buffer狀態(tài)流轉(zhuǎn)為QUEUED,且Owner變?yōu)锽ufferQueue,同時會回調(diào)BufferQueue持有的ConsumerListener的onFrameAvailable,進而通知Consumer可對數(shù)據(jù)進行二次處理了。Consumer先通過Acquire操作,獲取處于QUEUED狀態(tài)的Buffer,此時Owner為Consumer。當(dāng)Consumer消費完Buffer后,會執(zhí)行Release,該Buffer會流轉(zhuǎn)回BufferQueue以便重用。BufferQueue核心數(shù)據(jù)為GraphicBuffer,而GraphicBuffer會根據(jù)場景、申請的內(nèi)存大小、申請方式等的不同而有所不同。

SurfaceTexture的核心流程如下圖:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

通過上圖可知,我們的Producer是Video,填充視頻幀后,再對紋理進行特效處理(濾鏡等),最后再渲染出來。前面我們分析了BufferQueue的工作流程,但是在Producer要填充數(shù)據(jù)、執(zhí)行dequeueBuffer操作時,如果有Buffer已經(jīng)QUEUED,且申請的dequeuedCount大于mMaxDequeuedBufferCount,就不會再繼續(xù)申請Free Buffer了,Producer就無法DequeueBuffer,也就導(dǎo)致onFrameAvailable無法最終調(diào)用,核心源碼如下:

status_t BufferQueueProducer::dequeueBuffer(int *outSlot,sp<android::Fence> *outFence, uint32_t width, uint32_t height,
 PixelFormat format, uint32_t usage,FrameEventHistoryDelta* outTimestamps) {
 ......
 int found = BufferItem::INVALID_BUFFER_SLOT;
 while (found == BufferItem::INVALID_BUFFER_SLOT) {
 status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue,
 & found);
 if (status != NO_ERROR) {
 return status;
 }
 }
 ......
}
status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
 int*found) const{
 ......
 while (tryAgain) {
 int dequeuedCount = 0;
 int acquiredCount = 0;
 for (int s : mCore -> mactiveBuffers) {
 if (mSlots[s].mBufferState.isDequeued()) {
 ++dequeuedCount;
 }
 if (mSlots[s].mBufferState.isAcquired()) {
 ++acquiredCount;
 }
 }
 // Producers are not allowed to dequeue more than
 // mMaxDequeuedBufferCount buffers.
 // This check is only done if a buffer has already been queued
 if (mCore -> mBufferHasBeenQueued &&
 dequeuedCount >= mCore -> mMaxDequeuedBufferCount) {
 BQ_LOGE("%s: attempting to exceed the max dequeued buffer count "
 "(%d)", callerString, mCore -> mMaxDequeuedBufferCount);
 return INVALID_OPERATION;
 }
 }
 .......
 }

5. 碼流適配

視頻的監(jiān)控體系發(fā)現(xiàn),Android 9.0的系統(tǒng)出現(xiàn)大量的編解碼失敗問題,錯誤信息都是相同的。在MediaCodec的Configure時候出異常了,主要原因是我們強制使用了CQ碼流,Android 9.0以前并無問題,但9.0及以后對CQ碼流增加了新的校驗機制而我們沒有適配。核心流程代碼如下:

status_t ACodec::configureCodec(
 const char *mime, const sp<AMessage> &msg) {
 .......
 if (encoder) {
 if (mIsVideo || mIsImage) {
 if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
 return INVALID_OPERATION;
 }
 } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
 && !msg->findInt32("bitrate", &bitrate)) {
 return INVALID_OPERATION;
 }
 }
 .......
}
static bool findVideoBitrateControlInfo(const sp<AMessage> &msg,
 OMX_VIDEO_CONTROLRATETYPE *mode, int32_t *bitrate, int32_t *quality) {
 *mode = getVideoBitrateMode(msg);
 bool isCQ = (*mode == OMX_Video_ControlRateConstantQuality);
 return (!isCQ && msg->findInt32("bitrate", bitrate))
 || (isCQ && msg->findInt32("quality", quality));
}
9.0前并無對CQ碼流的強校驗,如果不支持該碼流也會使用默認支持的碼流,
static OMX_VIDEO_CONTROLRATETYPE getBitrateMode(const sp<AMessage> &msg) {
 int32_t tmp;
 if (!msg->findInt32("bitrate-mode", &tmp)) {
 return OMX_Video_ControlRateVariable;
 }
 return static_cast<OMX_VIDEO_CONTROLRATETYPE>(tmp);
}

關(guān)于碼流還有個問題,就是如果通過系統(tǒng)的接口isBitrateModeSupported(int mode),判斷是否支持該碼流可能會出現(xiàn)誤判,究其原因是framework層寫死了該返回值,而并沒有從硬件層或從media_codecs.xml去獲取該值。關(guān)于碼流各硬件廠商支持的差異性,可能谷歌也認為碼流的兼容性太碎片化,不建議用非默認的碼流。

6. 音頻處理

音頻處理還括對音頻的混音、消聲等操作。在混音操作的時候,還要注意音頻文件的單聲道轉(zhuǎn)換等問題。

其實視頻問題總結(jié)起來,大部分是都會牽扯到編解碼(尤其是使用硬編碼),需要大量的適配工作(以上也只是部分問題,碎片化還是很嚴峻的),所以就需要兜底容錯方案,比如加入軟編。

線上監(jiān)控

視頻功能引入了埋點、日志、鏈路監(jiān)控等技術(shù)手段進行線上的監(jiān)控,我們可以針對監(jiān)控結(jié)果進行降級或維護更新。埋點更多的是產(chǎn)品維度的數(shù)據(jù)收集,日志是輔助定位問題的,而鏈路監(jiān)控則可以做到監(jiān)控預(yù)警。

我們加了拍攝流程、音視頻處理、視頻上傳流程的全鏈路監(jiān)控,整個鏈路如果任何一個節(jié)點出問題都認為是整個鏈路的失敗,若失敗次數(shù)超過閾值就會通過大象或郵件進行報警,我們在適配Andorid 9.0碼流問題時,最早發(fā)現(xiàn)也是由于鏈路監(jiān)控的預(yù)警。所有全鏈路的成功率目標(biāo)值均為98%,若成功率低于92%的目標(biāo)閾值就會觸發(fā)報警,我們會根據(jù)報警的信息和日志定位分析,該異常的影響范圍,再根據(jù)影響范圍確定是否熱修復(fù)或者降級。

我們以拍攝流程為例,來看看鏈路各核心節(jié)點的監(jiān)控,如下圖:

 

Android視頻技術(shù)探索之旅:美團外賣商家端的實踐

 

 

容災(zāi)降級

視頻功能目前只支持粗粒度的降級策略。我們在視頻入口處做了開關(guān)控制,關(guān)掉后所有的視頻功能都無法使用。我們通過線上監(jiān)控到視頻的穩(wěn)定性和成功率在特定機型無法保證,導(dǎo)致影響用戶正常的使用商家端App,可以支持針對特定設(shè)備做降級。后續(xù)我們可以做更細粒度的降級策略,比如根據(jù)P0級功能做降級,或者編解碼策略的降級等。

維護更新

視頻功能上線后,經(jīng)歷了幾個穩(wěn)定的版本,保持著較高的成功率。但近期收到了Sniffer(美團內(nèi)部監(jiān)控系統(tǒng))的郵件報警,發(fā)現(xiàn)視頻處理鏈路的失敗次數(shù)明顯增多,通過Sniffer收集的信息發(fā)現(xiàn)大部分都是Android 9.0的問題(也就是上面講的Android 9.0碼流適配的問題),我們在商家端5.2版本進行了修復(fù)。該問題解決后,我們的視頻處理鏈路成功率也恢復(fù)到了98%以上。

總結(jié)和規(guī)劃

視頻功能上線后,穩(wěn)定性、內(nèi)存、CPU等一些相關(guān)指標(biāo)數(shù)據(jù)比較理想。我們建設(shè)的監(jiān)控體系,覆蓋了視頻核心業(yè)務(wù),一些異常報警讓我們能夠及時發(fā)現(xiàn)問題并迅速對異常進行維護更新。但視頻技術(shù)棧遠比本文介紹的要龐大,怎么提高秒播率,怎么提高編解碼效率,還有硬編解碼過程中可能造成的花屏、綠邊等問題都是挑戰(zhàn),需要更深入的研究解決。

未來我們會繼續(xù)致力于提高視頻處理的兼容性和效率,優(yōu)化現(xiàn)有流程,我們會對音頻和視頻處理合并處理,也會引入軟編和自定義編解碼算法。

美團外賣大前端團隊將來也會繼續(xù)致力于提高用戶的體驗,將在實踐過程中遇到的問題進行總結(jié),繼續(xù)和大家分享。敬請關(guān)注。

如果你也對視頻技術(shù)感興趣,歡迎加入我們。

參考資料

  • Android開發(fā)者官網(wǎng)
  • Google CTS
  • Grafika
  • BufferQueue原理介紹
  • MediaCodec原理
  • 微信Android 視頻編碼爬過的坑
  • mp4文件結(jié)構(gòu)(一)、(二)、(三)、(四)
  • AndroidVideoCache 代理策略
  • ijkplayer
  • mp4parser
  • GPUImage

作者簡介

金輝、李瓊,美團外賣商家終端研發(fā)工程師。

分享到:
標(biāo)簽:技術(shù) 視頻 Android
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定