Android视频技术探索之旅:美团外卖商家端的实践

环境

2013年美团创立,迄今一直飞速发展。伴随着外卖送餐业务数量级与日俱增,单一的文字内容和照片已不能满足企业的要求,店家急需解决更丰富的宝贝描述方式吸引住客户,提升总流量,从而提升下单转化率和提交订单量。产品视频的引进,在一定水平上可以提高产品信息叙述丰富度,以更为直接的方法为卖家引流方法,提升盈利。因此,店家端加入了视频作用,开展了一系列视频作用开发设计,关键作用包括视频解决(混响,ps滤镜,添加水印,动漫等)、视频拍照、生成等,最后设计效果图如下所示所显示:

自视频作用发布后,每星期视频样本数及应用视频的店家量急剧提升,视频视频录制通过率达99.533%,视频解决通过率98.818%,音频处理成功率99.959%,Crash率平稳在0.1‰,可靠性高且易用性强。现阶段,视频作用已在蜜峰App、限时抢业务和店家业务上应用。

针对视频链接的开发设计,大家经历了方案型号选择、软件架构设计及提升、业务实践活动、软件性能测试、监管运维管理、升级维护保养等各个阶段,关键阶段如下图所示。在研发流程中,碰到了各种各样工艺问题和挑戰,下面会对于碰到的问题、挑戰,以及处理方案开展关键论述。

方案型号选择

在方案选型时,关键对核心步骤和视频文件格式开展型号选择。大家以作用覆盖率、可靠性及高效率、可定制性、成本费及开源系统性作为关键指标值,进而考量方案的可扩展性和可行性分析。

1. 关键步骤型号选择

视频开发设计涉及到的关键步骤包含播放视频、视频录制、生成、剪裁、后期处理(编解码、ps滤镜、混响、动漫、图片水印)等。融合店家端业务场景,大家有目的性的开展方案调查。关键调查了业内目前方案,如阿里巴巴的云视频播放方案、腾讯云服务视频播放方案、大众点评网App的UGC方案,以及它的一些第三方开源系统方案等,并开展了总体匹配度的比照,如下图所示:

阿里巴巴和腾迅的云视频播放方案较为完善,处理速度高,且能力丰富多彩,可靠性及高效率也很高。但二者成本费较高,必须收费标准,且SDK尺寸均在15M以上,针对咱们的业务场景而言有一些过度松垮,定制性较差,没法快速的支撑大家做定制性拓展。

那时候的大众点评网App UGC方案,基本能力是达到的,但因业务场景差别:

例如外卖送餐的视频拍照作用规定在坚屏下确保16:9的视频高宽比,这就必须对原来的收集地区开展提取,视频文段的剪裁适用不足等,业务场景的差别造成了完成方案存有极大的差别,故放弃了大众点评网App UGC方案。别的的一些开源系统方案(例如Grafika等),也没法符合要求,这儿不会再一一赘述。

根据技术性调查和剖析,汲取各开源软件的优势,并参照大众点评网App UGC、Google CTS方案,对关键步骤干了最后的方案型号选择,打造出一个合适大家业务场景的方案,如下所示表所显示:

2. 视频文件格式型号选择

选用H.2 ** 的视频协议书:H.2 ** 的规范完善平稳,覆盖率高。其最大的的优点是有着很高的数据编码比例,在同样图象品质的前提下,H.2 ** 的发动机压缩比是MPEG-2的2倍以上,是MPEG-4的1.5~2倍。选用AAC的声频协议书:AAC是一种专为响声数据信息设计方案的文档压缩格式。它采取了全新升级的优化算法开展编号,是新一代的声频有损压缩技术性,具备更为高效率,更具备“性价比高”的特性。

总体构架

大家总体的软件架构设计,用于达到业务拓展和平台化必须,可重复使用、可拓展,且可迅速连接。构架选用分层次设计方案,基本能力和部件开展下移,业务和视频能力做分离出来,利润最大化减少业务方的连接成本费,三方业务只必须连接视频基本SDK,立即采用有关能力部件或是专用工具就可以。

总体构架分成四层,各自为服务平台层、关键能力层、基本部件层、业务层。

服务平台层:依靠系统软件给予的服务平台能力,例如Camera、OpenGL、MediaCodec和MediaMuxer等,也包含引进的服务平台能力,例如ijkplayer播放器、mp4parser。关键能力层:该层带来了视频服务项目的关键能力,包含音视频编解码、音视频的转换格式模块、ps滤镜3D渲染能力等。基本能力层:曝露了基本部件和能力,给予了播放视频、剪裁、屏幕录制等基本部件和相对应的基本java工具,并带来了可定制的播放视频控制面板,可定制的缓存文件插口等。业务层:包含文段拍照、随意拍照、视频室内空间、拍照模板浏览及载入等。

大家的视频能力层对业务层是通透的,业务层与能力层防护,并对业务层带来了一部分定制化的插口适用,那样的设计减少了业务方的连接成本费,并便捷业务方的拓展,例如适用蜜峰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 Su ** ceMediaRecorder 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); } // ** -C9000,在1280 x 720 屏幕分辨率时有一条绿边。在网上有一种观点是GPU对数据资料做好了提升,促使GPU造成的图片分辨率 //和基本屏幕分辨率存有细微差别,导致图象颜色错乱,修补后存有绿边问题。 //检测发觉,减少屏幕分辨率或是上升屏幕分辨率都能够避开这个问题。 if (VideoAdapt.MODEL_ ** _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 S ** ple和Audio Sample。Video S ** ple代表一帧或一组持续视频帧,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. 偶数宽高的编解码器

视频经过编码后输出特定宽高的视频文件时出现了如下错误,信息里仅提示了Colorfor ** t错误,具体如下:

查阅大量资料,也没能解释清楚这个异常的存在。基于日志错误信息,并通过系统源码定位,也只是发现是了和设置的参数不兼容导致的。经过反复的试错,最后确认是部分编解码器只支持偶数的视频宽高,所以我们对视频的宽高做了偶数限制。引起该问题的核心代码如下:

status_t ACodec::setupVideoEncoder(const char *mime, const sp<AMessage> &msg, sp<AMessage> &outputFor ** t, sp<AMessage> &inputFor ** t) { if (!msg->findInt32("color-for ** t", &tmp)) { return INVALID_OPERATION; } OMX_COLOR_FORMATTYPE colorFor ** t = static_cast<OMX_COLOR_FORMATTYPE>(tmp); status_t err = setVideoPortFor ** tType( kPortIndexInput, OMX_VIDEO_CodingUnused, colorFor ** t); if (err != OK) { ALOGE("[%s] does not support color for ** t %d", mComponentName.c_str(), colorFor ** t); return err; } .......}status_t ACodec::setVideoPortFor ** tType(OMX_U32 portIndex,OMX_VIDEO_CODINGTYPE compressionFor ** t, OMX_COLOR_FORMATTYPE colorFor ** t,bool usingNativeBuffers) { ...... for (OMX_U32 index = 0; index <= kMaxIndicesToCheck; ++index) { for ** t.nIndex = index; status_t err = mOMX->getParameter( mNode, OMX_IndexParamVideoPortFor ** t, &for ** t, sizeof(for ** t)); if (err != OK) { return err; } ......}2. 颜色格式

我们在处理视频帧的时候,一开始获得的是从Camera读取到的基本的YUV格式数据,如果给编码器设置YUV帧格式,需要考虑YUV的颜色格式。这是因为YUV根据其采样比例,UV分量的排列顺序有很多种不同的颜色格式,Android也支持不同的YUV格式,如果颜色格式不对,会导致花屏等问题。

3. 16位对齐

这也是硬编码中老生常谈的问题了,因为H2 ** 编码需要16*16的编码块大小。如果一开始设置输出的视频宽高没有进行16字节对齐,在某些设备(华为,三星等)就会出现绿边,或者花屏。

4. 二次渲染4.1 视频旋转

在最后的视频处理阶段,用户可以实时的看到加滤镜后的视频效果。这就需要对原始的视频帧进行二次处理,然后在播放器的Su ** ce上渲染。首先我们需要OpenGL 的渲染环境(通过OpenGL的固有流程创建),渲染环境完成后就可以对视频的帧数据进行二次处理了。通过Su ** ceTexture的updateTexI ** ge接口,可将视频流中最新的帧数据更新到对应的GL纹理,再操作GL纹理进行滤镜、动画等处理。在处理视频帧数据的时候,首先遇到的是角度问题。在正常播放下(不利用OpenGL处理情况下)通过设置TextureView的角度(和视频的角度做转换)就可以解决,但是加了滤镜后这一方案就失效了。原因是视频的原始数据经过纹理处理再渲染到Su ** ce上,单纯设置TextureView的角度就失效了,解决方案就是对OpenGL传入的纹理坐标做相应的旋转(依据视频的本身的角度)。

4.2 渲染停滞

视频在二次渲染后会出现偶现的画面停滞现象,主要是Su ** ceTexture的OnFrameAvailableListener不返回数据了。该问题的根本原因是GPU的渲染和视频帧的读取不同步,进而导致Su ** ceTexture的底层核心BufferQueue读取Buffer出了问题。下面我们通过BufferQueue的机制和核心源码深入研究下:

首先从二次渲染的工作流程入手。从图像流(来自Camera预览、视频解码、GL绘制场景等)中获得帧数据,此时OnFrameAvailableListener会回调。再调用updateTexI ** ge(),会根据内容流中最近的图像更新Su ** ceTexture对应的GL纹理对象。我们再对纹理对象做处理,比如添加滤镜等效果。Su ** ceTexture底层核心管理者是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会根据场景、申请的内存大小、申请方式等的不同而有所不同。

Su ** ceTexture的核心流程如下图:

通过上图可知,我们的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, PixelFor ** t for ** t, 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 ** x 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 || mIsI ** ge) { 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 CTSGrafikaBufferQueue原理介绍MediaCodec原理微信Android 视频编码爬过的坑mp4文件结构(一)、(二)、(三)、(四)AndroidVideoCache 代理策略ijkplayermp4parserGPUI ** ge

作者简介

金辉、李琼,美团外卖商家终端研发工程师。

---------- END ----------

招聘信息

美团外卖商家终端研发团队的主要职责是为商家提供稳定可靠的生产经营工具,在保障稳定的需求迭代的基础之上,持续优化APP、PC和H5的性能和用户体验,并不断优化提升团队的研发效率。团队主要负责的业务主要包括外卖订单、商品管理、门店装修、服务市场、门店运营、三方会话、蓝牙打印、自动接单、视频、语音和实时消息触达等基础业务,支撑整个外卖链路的高可用性及稳定发展。团队通过架构演进及平台化体系化建设,有效支撑业务发展,提升了业务的可靠性和安全性;通过大规模落地跨平台和动态化技术,加快了业务迭代效率,帮助产品(PM)加快产品方案的落地及上线;通过监控容灾体系建设,有效保障业务的高可用性和稳定性;通过性能优化建设,保证APP的流畅性和良好用户体验。团队开发的技术栈包括Android、iOS、React、Flutter和React Native。美团外卖商家端研发团队长期招聘Android、iOS、和前端工程师,欢迎有兴趣的同学投简历至:tech@meituan.com(邮件标题注明:美团外卖商家端)

扫码免费用

源码支持二开

申请免费使用

在线咨询