背景android
2013年美團外賣成立,至今一直迅猛發展。隨着外賣業務量級與日俱增,單一的文字和圖片已沒法知足商家的需求,商家迫切須要更豐富的商品描述手段吸引用戶,增長流量,進而提升下單轉化率和下單量。商品視頻的引入,在必定程度上能夠提高商品信息描述豐富度,以更加直觀的方式爲商家引流,增長收益。爲此,商家端引入了視頻功能,進行了一系列視頻功能開發,核心功能包含視頻處理(混音,濾鏡,加水印,動畫等)、視頻拍攝、合成等,最終效果圖以下所示:算法
自視頻功能上線後,每週視頻樣本量及使用視頻的商家量大幅增長,視頻錄製成功率達99.533%,視頻處理成功率98.818%,音頻處理成功率99.959%,Crash率穩定在0.1‰,穩定性高且可用性強。目前,視頻功能已在蜜蜂App、閃購業務和商家業務上使用。緩存
對於視頻鏈路的開發,咱們經歷了方案選型、架構設計及優化、業務實踐、功能測試、監控運維、更新維護等各個環節,核心環節以下圖所示。在開發過程當中,遇到了各類技術問題和挑戰,下文會針對遇到的問題、挑戰,及其解決方案進行重點闡述。安全
方案選型性能優化
在方案選型時,重點對核心流程和視頻格式進行選型。咱們以功能覆蓋度、穩定性及效率、可定製性、成本及開源性作爲核心指標,從而衡量方案的高可用性和可行性。微信
1. 核心流程選型網絡
視頻開發涉及的核心流程包括播放、錄製、合成、裁剪、後期處理(編解碼、濾鏡、混音、動畫、水印)等。結合商家端業務場景,咱們有針對性的進行方案調研。重點調研了業界現有方案,如阿里的雲視頻點播方案、騰訊雲視頻點播方案、大衆點評App的UGC方案,及其它的一些第三方開源方案等,並進行了總體匹配度的對比,以下圖所示:前端工程師
阿里和騰訊的雲視頻點播方案比較成熟,集成度高,且能力豐富,穩定性及效率也很高。但二者成本較高,須要收費,且SDK大小均在15M以上,對於咱們的業務場景來講有些過於臃腫,定製性較弱,沒法迅速的支持咱們作定製性擴展。架構
當時的大衆點評App UGC方案,基礎能力是知足的,但因業務場景差別:
- 好比外賣的視頻拍攝功能要求在豎屏下保證16:9的視頻寬高比,這就須要對原有的採集區域進行截取,視頻段落的裁剪支持不夠等,業務場景的差別致使了實現方案存在巨大的差別,故放棄了大衆點評App UGC方案。其餘的一些開源方案(好比Grafika等),也沒法知足要求,這裏再也不一一贅述。
經過技術調研和分析,吸收各開源項目的優勢,並參考大衆點評App UGC、Google CTS方案,對核心流程作了最終的方案選型,打造一個適合咱們業務場景的方案,以下表所示:
2. 視頻格式選型
- 採用H.264的視頻協議:H.264的標準成熟穩定,普及率高。其最大的優點是具備很高的數據壓縮比率,在同等圖像質量的條件下,H.264的壓縮比是MPEG-2的2倍以上,是MPEG-4的1.5~2倍。
- 採用AAC的音頻協議:AAC是一種專爲聲音數據設計的文件壓縮格式。它採用了全新的算法進行編碼,是新一代的音頻有損壓縮技術,具備更加高效,更具備「性價比」的特色。
總體架構
咱們總體的架構設計,用以知足業務擴展和平臺化須要,可複用、可擴展,且可快速接入。架構採用分層設計,基礎能力和組件進行下沉,業務和視頻能力作分離,最大化下降業務方的接入成本,三方業務只須要接入視頻基礎SDK,直接使用相關能力組件或者工具便可。
總體架構分爲四層,分別爲平臺層、核心能力層、基礎組件層、業務層。
- 平臺層:依賴系統提供的平臺能力,好比Camera、OpenGL、MediaCodec和MediaMuxer等,也包括引入的平臺能力,好比ijkplayer播放器、mp4parser。
- 核心能力層:該層提供了視頻服務的核心能力,包括音視頻編解碼、音視頻的轉碼引擎、濾鏡渲染能力等。
- 基礎能力層:暴露了基礎組件和能力,提供了播放、裁剪、錄屏等基礎組件和對應的基礎工具類,並提供了可定製的播放面板,可定製的緩存接口等。
- 業務層:包括段落拍攝、自由拍攝、視頻空間、拍攝模版預覽及加載等。
咱們的視頻能力層對業務層是透明的,業務層與能力層隔離,並對業務層提供了部分定製化的接口支持,這樣的設計下降了業務方的接入成本,並方便業務方的擴展,好比支持蜜蜂App的播放面板定製,還支持緩存策略、編解碼策略的可定製。總體設計以下圖所示:
實踐經驗
在視頻開發實踐中,因業務場景的複雜性,咱們遇到了多種問題和挑戰。下面以核心功能爲基點,圍繞各功能遇到的問題作詳細介紹。
視頻播放
播放器是視頻播放基礎。針對播放器,咱們進行了一系列的方案調研和選擇。在此環節,遇到的挑戰以下:
1. 兼容性問題
2. 緩存問題
針對兼容性問題,Android有原生的MediaPlayer,但其版本兼容問題偏多且支持格式有限,而咱們須要支持播放本地視頻,本地視頻格式又沒法控制,故該方案被捨棄。ijkplayer基於FFmpeg,與MediaPlayer相比,優勢比較突出:具有跨平臺能力,支持Android與iOS;提供了相似MediaPlayer的API,可兼容不一樣版本;可實現軟硬解碼自由切換,擁有FFmpeg的能力,支持多種流媒體協議。基於上述緣由,咱們最終決定選用ijkplayer。
但緊接着又發現ijkplayer自己不支持邊緩存邊播放,頻繁的加載視頻致使耗費大量的流量,且在弱網或者3G網絡下很容易致使播放卡頓,因此這裏就衍生出了緩存的問題。
針對緩存問題,引入AndroidVideoCache的技術方案,利用本地的代理去請求數據,先本地保存文件緩存,客戶端經過Socket讀取本地的文件緩存進行視頻播放,這樣就作到了邊播放邊緩存的策略,流程以下圖:
此外,咱們還對AndroidVideoCache作了一些技術改造:
- 優化緩存策略。針對緩存策略的單一性,支持有限的最大文件數和文件大小問題,調整爲由業務方能夠動態定製緩存策略;
- 解決內存泄露隱患。對其頁面退出時請求不關閉會致使的內存泄露,爲其添加了完整的生命週期監控,解決了內存泄露問題。
視頻錄製
在視頻拍攝的時候,最爲經常使用的方式是採用MediaRecorder+Camera技術,採集攝像頭可見區域。但因咱們的業務場景要求視頻採集的時候,只錄制採集區域的部分區域且比例保持寬高比16:9,在保證預覽圖像不拉伸的狀況下,只能對完整的採集區域作裁剪,這無形增長了開發難度和挑戰。經過大量的資料分析,重點調研了有兩種方案:
- Camera+AudioRecord+MediaCodec+Surface
- MediaRecorder+MediaCodec
方案1須要Camera採集YUV幀,進行截取採集,最後再將YUV幀和PCM幀進行編碼生成mp4文件,雖然其效率高,但存在不可把控的風險。
方案2綜合評估後是改造風險最小的。綜合成本和風險考量,咱們保守的採用了方案2,該方案是對裁剪區域進行座標換算(若是用前置攝像頭拍攝錄製視頻,會出現預覽畫面和錄製的視頻是鏡像的問題,須要處理)。當錄製完視頻後,生成了mp4文件,用MediaCodec對其編碼,在編碼階段再利用OpenGL作內容區域的裁剪來實現。但該方案又引起了以下挑戰。
(1)對焦問題
因咱們對採集區域作了裁剪,引起了點觸對焦問題。好比用戶點擊了相機預覽畫面,正常狀況下會觸發相機的對焦動做,可是用戶的點擊區域只是預覽畫面的部分區域,這就致使了相機的對焦區域錯亂,不能正常進行對焦。後期通過問題排查,對點觸區域再次進行相應的座標變換,最終獲得正確的對焦區域。
(2)兼容適配
咱們的視頻錄製利用MediaRecorder,在獲取配置信息時,因爲Android碎片化問題,不一樣的設備支持的配置信息不一樣,因此就會出現設備適配問題。
// VIVO Y66 模版拍攝時候,播放某些有問題的視頻文件的同時去錄製視頻,會致使MediaServer掛掉的問題
// 發現將1080P尺寸的配置下降到720P便可避免此問題
// 可是720P尺寸的配置下,又存在綠邊問題,所以再降到480
if(isVIVOY66() && mMediaServerDied) {
return getCamcorderProfile(CamcorderProfile.QUALITY_480P);
}
//SM-C9000,在1280 x 720 分辨率時有一條綠邊。網上有種說法是GPU對數據進行了優化,使得GPU產生的圖像分辨率
//和常規分辨率存在微小差別,形成圖像色彩混亂,修復後存在綠邊問題。
//測試發現,下降分辨率或者升高分辨率均可以繞開這個問題。
if (VideoAdapt.MODEL_SM_C9000.equals(Build.MODEL)) {
return getCamcorderProfile(CamcorderProfile.QUALITY_HIGH);
}
// 優先選擇 1080 P的配置
CamcorderProfile camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_1080P);
if (camcorderProfile == null) {
camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_720P);
}
// 某些機型上這個 QUALITY_HIGH 有點問題,可能經過這個參數拿到的配置是1080p,因此這裏也可能拿不到
if (camcorderProfile == null) {
camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_HIGH);
}
// 兜底
if (camcorderProfile == null) {
camcorderProfile = getCamcorderProfile(CamcorderProfile.QUALITY_480P);
}
視頻合成
咱們的視頻拍攝有段落拍攝這種場景,商家可根據事先下載的模板進行分段拍攝,最後會對每一段的視頻作拼接,拼接成一個完整的mp4文件。mp4由若干個Box組成,全部數據都封裝在Box中,且Box可再包含Box的被稱爲Container Box。mp4中Track表示一個視頻或音頻序列,是Sample的集合,而Sample又可分爲Video Smaple和Audio Sample。Video Smaple表明一幀或一組連續視頻幀,Audio Sample即爲一段連續的壓縮音頻數據。(詳見mp4文件結構。)
基於上面的業務場景須要,視頻合成的基礎能力咱們採用mp4parser技術實現(也可用FFmpeg等其餘手段)。mp4parser在拼接視頻時,先將視頻的音軌和視頻軌進行分離,而後進行視頻和音頻軌的追加,最終將合成後的視頻軌和音頻軌放入容器裏(這裏的容器就是mp4的Box)。採用mp4parser技術簡單高效,API設計簡潔清晰,知足需求。
但咱們發現某些被編碼或處理過的mp4文件可能會存在特殊的Box,而且mp4parser是不支持的。通過源碼分析和緣由推導,發現當遇到這種特殊格式的Box時,會申請分配一個比較大的空間用來存放數據,很容易形成OOM(內存溢出),見下圖所示。因而,咱們對這種拼接場景下作了有效規避,僅在段落拍攝下使用mp4parser的拼接功能,保證處理過的文件不會包含這種特殊的Box。
視頻裁剪
咱們剛開始採用mp4parser技術完成視頻裁剪,在實踐中發現其精度偏差存在很大的問題,甚至會影響正常的業務需求。好比禁止裁剪出3s如下的視頻,可是因爲mp4parser產生的精度偏差,致使4-5s的視頻很容易裁剪出少於3s的視頻。究其緣由,mp4parser只能在關鍵幀(又稱I幀,在視頻編碼中是一種自帶所有信息的獨立幀)進行切割,這樣就可能存在一些問題。好比在視頻截取的起始時間位置並非關鍵幀,會形成偏差,沒法保證精度並且是秒級偏差。如下爲mp4parser裁剪的關鍵代碼:
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的方案,雖然該方案會增長複雜度,可是偏差精度大大下降。
方案具體實施以下:先得到目標時間的上一幀信息,對視頻解碼,而後根據起始時間和截取時長進行切割,最後將裁剪後的音視頻信息進行壓縮編碼,再封裝進mp4容器中,這樣咱們的裁剪精度從秒級偏差下降到微秒級偏差,大大提升了容錯率。
視頻處理
視頻處理是整個視頻能力最核心的部分,會涉及硬編解碼(遵循OpenMAX框架)、OpenGL、音頻處理等相關能力。
下圖是視頻處理的核心流程,會先將音視頻作分離,並行處理音視頻的編解碼,並加入特效處理,最後合成進一個mp4文件中。
在實踐過程當中,咱們遇到了一些須要特別注意的問題,好比開發時遇到的坑,嚴重的兼容性問題(包括硬件兼容性和系統版本兼容性問題)等。下面重點講幾個有表明性的問題。
1. 偶數寬高的編解碼器
視頻通過編碼後輸出特定寬高的視頻文件時出現了以下錯誤,信息裏僅提示了Colorformat錯誤,具體以下:
查閱大量資料,也沒能解釋清楚這個異常的存在。基於日誌錯誤信息,並經過系統源碼定位,也只是發現是了和設置的參數不兼容致使的。通過反覆的試錯,最後確認是部分編解碼器只支持偶數的視頻寬高,因此咱們對視頻的寬高作了偶數限制。引發該問題的核心代碼以下:
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格式數據,若是給編碼器設置YUV幀格式,須要考慮YUV的顏色格式。這是由於YUV根據其採樣比例,UV份量的排列順序有不少種不一樣的顏色格式,Android也支持不一樣的YUV格式,若是顏色格式不對,會致使花屏等問題。
3. 16位對齊
這也是硬編碼中老生常談的問題了,由於H264編碼須要16*16的編碼塊大小。若是一開始設置輸出的視頻寬高沒有進行16字節對齊,在某些設備(華爲,三星等)就會出現綠邊,或者花屏。
4. 二次渲染
4.1 視頻旋轉
在最後的視頻處理階段,用戶能夠實時的看到加濾鏡後的視頻效果。這就須要對原始的視頻幀進行二次處理,而後在播放器的Surface上渲染。首先咱們須要OpenGL 的渲染環境(經過OpenGL的固有流程建立),渲染環境完成後就能夠對視頻的幀數據進行二次處理了。經過SurfaceTexture的updateTexImage接口,可將視頻流中最新的幀數據更新到對應的GL紋理,再操做GL紋理進行濾鏡、動畫等處理。在處理視頻幀數據的時候,首先遇到的是角度問題。在正常播放下(不利用OpenGL處理狀況下)經過設置TextureView的角度(和視頻的角度作轉換)就能夠解決,可是加了濾鏡後這一方案就失效了。緣由是視頻的原始數據通過紋理處理再渲染到Surface上,單純設置TextureView的角度就失效了,解決方案就是對OpenGL傳入的紋理座標作相應的旋轉(依據視頻的自己的角度)。
4.2 渲染停滯
視頻在二次渲染後會出現偶現的畫面停滯現象,主要是SurfaceTexture的OnFrameAvailableListener不返回數據了。該問題的根本緣由是GPU的渲染和視頻幀的讀取不一樣步,進而致使SurfaceTexture的底層核心BufferQueue讀取Buffer出了問題。下面咱們經過BufferQueue的機制和核心源碼深刻研究下:
首先從二次渲染的工做流程入手。從圖像流(來自Camera預覽、視頻解碼、GL繪製場景等)中得到幀數據,此時OnFrameAvailableListener會回調。再調用updateTexImage(),會根據內容流中最近的圖像更新SurfaceTexture對應的GL紋理對象。咱們再對紋理對象作處理,好比添加濾鏡等效果。SurfaceTexture底層核心管理者是BufferQueue,自己基於生產者消費者模式。
BufferQueue管理的Buffer狀態分爲:FREE、DEQUEUED、QUEUED、ACQUIRED、SHARED。當Producer須要填充數據時,須要先Dequeue一個Free狀態的Buffer,此時Buffer的狀態爲DEQUEUED,成功後持有者爲Producer。隨後Producer填充數據完畢後,進行Queue操做,Buffer狀態流轉爲QUEUED,且Owner變爲BufferQueue,同時會回調BufferQueue持有的ConsumerListener的onFrameAvailable,進而通知Consumer可對數據進行二次處理了。Consumer先經過Acquire操做,獲取處於QUEUED狀態的Buffer,此時Owner爲Consumer。當Consumer消費完Buffer後,會執行Release,該Buffer會流轉回BufferQueue以便重用。BufferQueue核心數據爲GraphicBuffer,而GraphicBuffer會根據場景、申請的內存大小、申請方式等的不一樣而有所不一樣。
SurfaceTexture的核心流程以下圖:
經過上圖可知,咱們的Producer是Video,填充視頻幀後,再對紋理進行特效處理(濾鏡等),最後再渲染出來。前面咱們分析了BufferQueue的工做流程,可是在Producer要填充數據、執行dequeueBuffer操做時,若是有Buffer已經QUEUED,且申請的dequeuedCount大於mMaxDequeuedBufferCount,就不會再繼續申請Free Buffer了,Producer就沒法DequeueBuffer,也就致使onFrameAvailable沒法最終調用,核心源碼以下:
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. 碼流適配
視頻的監控體系發現,Android 9.0的系統出現大量的編解碼失敗問題,錯誤信息都是相同的。在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);
}
關於碼流還有個問題,就是若是經過系統的接口isBitrateModeSupported(int mode),判斷是否支持該碼流可能會出現誤判,究其緣由是framework層寫死了該返回值,而並無從硬件層或從media_codecs.xml去獲取該值。關於碼流各硬件廠商支持的差別性,可能谷歌也認爲碼流的兼容性太碎片化,不建議用非默認的碼流。
6. 音頻處理
音頻處理還括對音頻的混音、消聲等操做。在混音操做的時候,還要注意音頻文件的單聲道轉換等問題。
其實視頻問題總結起來,大部分是都會牽扯到編解碼(尤爲是使用硬編碼),須要大量的適配工做(以上也只是部分問題,碎片化仍是很嚴峻的),因此就須要兜底容錯方案,好比加入軟編。
線上監控
視頻功能引入了埋點、日誌、鏈路監控等技術手段進行線上的監控,咱們能夠針對監控結果進行降級或維護更新。埋點更多的是產品維度的數據收集,日誌是輔助定位問題的,而鏈路監控則能夠作到監控預警。
咱們加了拍攝流程、音視頻處理、視頻上傳流程的全鏈路監控,整個鏈路若是任何一個節點出問題都認爲是整個鏈路的失敗,若失敗次數超過閾值就會經過大象或郵件進行報警,咱們在適配Andorid 9.0碼流問題時,最先發現也是因爲鏈路監控的預警。全部全鏈路的成功率目標值均爲98%,若成功率低於92%的目標閾值就會觸發報警,咱們會根據報警的信息和日誌定位分析,該異常的影響範圍,再根據影響範圍肯定是否熱修復或者降級。
咱們以拍攝流程爲例,來看看鏈路各核心節點的監控,以下圖:
容災降級
視頻功能目前只支持粗粒度的降級策略。咱們在視頻入口處作了開關控制,關掉後全部的視頻功能都沒法使用。咱們經過線上監控到視頻的穩定性和成功率在特定機型沒法保證,致使影響用戶正常的使用商家端App,能夠支持針對特定設備作降級。後續咱們能夠作更細粒度的降級策略,好比根據P0級功能作降級,或者編解碼策略的降級等。
維護更新
視頻功能上線後,經歷了幾個穩定的版本,保持着較高的成功率。但近期收到了Sniffer(美團內部監控系統)的郵件報警,發現視頻處理鏈路的失敗次數明顯增多,經過Sniffer收集的信息發現大部分都是Android 9.0的問題(也就是上面講的Android 9.0碼流適配的問題),咱們在商家端5.2版本進行了修復。該問題解決後,咱們的視頻處理鏈路成功率也恢復到了98%以上。
總結和規劃
視頻功能上線後,穩定性、內存、CPU等一些相關指標數據比較理想。咱們建設的監控體系,覆蓋了視頻核心業務,一些異常報警讓咱們可以及時發現問題並迅速對異常進行維護更新。但視頻技術棧遠比本文介紹的要龐大,怎麼提升秒播率,怎麼提升編解碼效率,還有硬編解碼過程當中可能形成的花屏、綠邊等問題都是挑戰,須要更深刻的研究解決。
將來咱們會繼續致力於提升視頻處理的兼容性和效率,優化現有流程,咱們會對音頻和視頻處理合並處理,也會引入軟編和自定義編解碼算法。
美團外賣大前端團隊未來也會繼續致力於提升用戶的體驗,將在實踐過程當中遇到的問題進行總結,繼續和你們分享。敬請關注。
若是你也對視頻技術感興趣,歡迎加入咱們。
參考資料
- Android開發者官網
- Google CTS
- Grafika
- BufferQueue原理介紹
- MediaCodec原理
- 微信Android 視頻編碼爬過的坑
- mp4文件結構(一)、(二)、(三)、(四)
- AndroidVideoCache 代理策略
- ijkplayer
- mp4parser
- GPUImage
做者簡介
金輝、李瓊,美團外賣商家終端研發工程師。
招聘信息
美團外賣商家終端研發團隊的主要職責是爲商家提供穩定可靠的生產經營工具,在保障穩定的需求迭代的基礎之上,持續優化APP、PC和H5的性能和用戶體驗,並不斷優化提高團隊的研發效率。團隊主要負責的業務主要包括外賣訂單、商品管理、門店裝修、服務市場、門店運營、三方會話、藍牙打印、自動接單、視頻、語音和實時消息觸達等基礎業務,支撐整個外賣鏈路的高可用性及穩定發展。
團隊經過架構演進及平臺化體系化建設,有效支撐業務發展,提高了業務的可靠性和安全性;經過大規模落地跨平臺和動態化技術,加快了業務迭代效率,幫助產品(PM)加快產品方案的落地及上線;經過監控容災體系建設,有效保障業務的高可用性和穩定性;經過性能優化建設,保證APP的流暢性和良好用戶體驗。團隊開發的技術棧包括Android、iOS、React、Flutter和React Native。
美團外賣商家端研發團隊長期招聘Android、iOS、和前端工程師,歡迎有興趣的同窗投簡歷至:tech@meituan.com(郵件標題註明:美團外賣商家端)