PAG框架支持单PAGView同时渲染多个PAGFile,相较于渲染单一文件,框架首先需要解决多文件渲染同步问题
1、多文件渲染帧率同步
多文件有着不同的帧率(FPS) ,为了实现同一容器渲染不同帧率文件,传统的基于定时器模式(定时回调,间隔与帧率同步)的回调已无法满足该场景
2、多文件渲染进度同步
多文件渲染需要避免多图层渲染进度不一致问题;为了保证播放流畅度,通常会在子线程解码视频帧,当渲染多视频图层时,如何保证多解码线程下的帧同步?
PAG支持File自定义显示区间,每个File文件时长也不一致,如何控制不同文件的播放区间?
播放进度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
bool PAGComposition::gotoTime(int64_t layerTime) { auto changed = PAGLayer::gotoTime(layerTime); auto compositionOffset = // 相对起始时间 static_cast<PreComposeLayer*>(layer)->compositionStartTime - layer->startTime + startFrame; auto compositionOffsetTime = static_cast<Frame>(floor(compositionOffset * 1000000.0 / frameRateInternal())); for (auto& layer : layers) { // 各图层记录各自的播放进度 if (layer->gotoTime(layerTime - compositionOffsetTime)) { changed = true; } } return changed; } bool PAGLayer::gotoTime(int64_t layerTime) { ... // 使用各自帧率转化成对应帧 auto layerFrame = TimeToFrame(layerTime, frameRateInternal()); auto oldContentFrame = contentFrame; contentFrame = layerFrame - startFrame; ... return changed; } |
采用时长百分比记录进度会导致flush绘制重复帧,为了解决性能问题,PAG引入了LayerCache角色,当重复绘制同一帧时可以直接使用缓存数据
1 2 3 |
Content* PAGLayer::getContent() { return layerCache->getContent(contentFrame); } |
PAG信号源PAGValueAnimator通过绝对时间差值计算播放进度,多个PAGView共用一个全局信号触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// 获取绝对时间戳 static int64_t GetCurrentTimeUS() { static auto START_TIME = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now(); auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now - START_TIME); return static_cast<int64_t>(ns.count() * 1e-3); } - (void)start { ... // PAG支持重复播放 if (repeatedTimes >= (repeatCount + 1)) { repeatedTimes = 0; } self.animatorId = [PAGValueAnimator AddAnimator:self]; // startTime不是最初开始时间,每次暂停恢复播放后会重新记录 startTime = GetCurrentTimeUS() - playTime % duration - repeatedTimes * duration; animatedFraction = static_cast<double>(playTime) / duration; ... } - (void)onAnimationFrame:(int64_t)timestamp { auto count = (timestamp - startTime) / duration; if (repeatCount >= 0 && count > repeatCount) { // 播放结束 playTime = duration; animatedFraction = 1.0; ... } else { // 当次播放时间戳 playTime = (timestamp - startTime) % duration; animatedFraction = static_cast<double>(playTime) / duration; ... } |
播放流程
1、信号触发
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)onAnimationUpdate { // 触发更新 [self updateView]; } - (BOOL)flush { ... // 更新播放进度 [pagPlayer setProgress:[valueAnimator getAnimatedFraction]]; // 触发刷新 result = [pagPlayer flush]; ... } |
2、更新播放进度
1 2 3 4 5 6 7 8 9 10 |
void PAGPlayer::setProgress(double percent) { // 获取渲染图层,递归更新各图层播放进度 auto pagComposition = stage->getRootComposition(); pagComposition->setProgressInternal(realProgress); } void PAGLayer::setProgressInternal(double percent) { // 各图层起始播放时间不同,转化为全局整体播放时间戳 gotoTimeAndNotifyChanged(startTimeInternal() + ProgressToTime(percent, durationInternal())); } |
3、触发渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
bool PAGPlayer::flushInternal(BackendSemaphore* signalSemaphore) { // 图层预处理,生成播放时间戳对应的图形模型,模型转化参考上一章层级视图讲解 prepareInternal(); // 绘制图层对象 if (!pagSurface->draw(renderCache, lastGraphic, signalSemaphore, _autoClear)) { return false; } ... } void PAGSurface::onDraw(std::shared_ptr<Graphic> graphic, std::shared_ptr<tgfx::Surface> target, RenderCache* cache) { auto canvas = target->getCanvas(); if (graphic) { // 预处理,比如视频帧解码 graphic->prepare(cache); // 渲染到画布Canvas graphic->draw(canvas, cache); } } |
4、图形解码(以视频帧为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// 预处理解码 void RenderCache::prepareSequenceImage(std::shared_ptr<SequenceInfo> sequence, Frame targetFrame) { // 解码队列,每个资源对应一个解码队列 auto queue = getSequenceImageQueue(sequence, targetFrame); if (queue != nullptr) { queue->prepare(targetFrame); } } void SequenceImageQueue::prepare(Frame targetFrame) { // 获取当前帧对应的image数据,开始解码(比如AVC解码) auto image = sequence->makeFrameImage(reader, targetFrame); preparedImage = image->makeDecoded(); preparedFrame = targetFrame; } // 创建异步解码任务 AsyncSource::AsyncSource(UniqueKey uniqueKey, std::shared_ptr<ImageGenerator> imageGenerator, bool mipMapped) { ... imageTask = ImageGeneratorTask::MakeFrom(generator, tryHardware); } ImageGeneratorTask::ImageGeneratorTask(std::shared_ptr<ImageGenerator> generator, bool tryHardware) : imageGenerator(std::move(generator)) { // 解码函数 task = Task::Run([=] { imageBuffer = imageGenerator->makeBuffer(tryHardware); }); } std::shared_ptr<tgfx::ImageBuffer> VideoReader::onMakeBuffer(Frame targetFrame) { auto targetTime = FrameToTime(targetFrame, frameRate); ... // 解码 auto sampleTime = demuxer->getSampleTimeAt(targetTime); auto success = decodeFrame(sampleTime); lastBuffer = videoDecoder->onRenderFrame(); return lastBuffer; } |
5、渲染图形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void draw(tgfx::Canvas* canvas, RenderCache* cache) const override { ... // 获取解码图形 auto image = proxy->getImage(cache); canvas->drawImage(std::move(image)); } std::shared_ptr<tgfx::Image> SequenceImageQueue::getImage(Frame targetFrame) { // 目标帧已解码直接返回 if (targetFrame == preparedFrame) { currentImage = preparedImage; return currentImage; } // 同步等待解码后的数据 auto image = sequence->makeFrameImage(reader, targetFrame); currentImage = image->makeDecoded(); return currentImage; } std::shared_ptr<ImageBuffer> ImageGeneratorTask::getBuffer() const { // 等待直到解码完成 task->wait(); return imageBuffer; } void Task::wait() { std::unique_lock<std::mutex> autoLock(locker); if (!_executing) { return; } // 等待解码锁释放 condition.wait(autoLock); } |
总结
为了优化播放体验,PAG使用了多种性能优化策略,包括提前预解码、渲染帧复用、GPU优化等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void RenderCache::prepareLayers() { // 提前 500ms 开始解码 int64_t timeDistance = DECODING_VISIBLE_DISTANCE; auto layerDistances = stage->findNearlyVisibleLayersIn(timeDistance); for (auto& item : layerDistances) { for (auto pagLayer : item.second) { if (pagLayer->layerType() == LayerType::PreCompose) { preparePreComposeLayer(static_cast<PreComposeLayer*>(pagLayer->layer)); } else if (pagLayer->layerType() == LayerType::Image) { prepareImageLayer(static_cast<PAGImageLayer*>(pagLayer)); } } } } |
PAG应用框架层主要负责上层业务逻辑处理,包括文件视频解码、播放流程控制以及生成渲染引擎所需要的数据源等,接下来将结合OpenGL讲解TGFX渲染引擎部分
博主讲解的非常透彻,特别解码这块,之前看了好久,现在终于懂了
感谢,欢迎关注右上角微信公众号