转载请注明出处:www.olinone.com/

PAG结构支撑单PAGView同时烘托多个PAGFile,相较于烘托单一文件,结构首要需求处理多文件烘托同步问题

1、多文件烘托帧率同步

多文件有着不同的帧率(FPS) ,为了完成同一容器烘托不同帧率文件,传统的根据定时器形式(定时回调,间隔与帧率同步)的回调已无法满足该场景

2、多文件烘托进展同步

多文件烘托需求防止多图层烘托进展不一致问题;为了确保播映流通度,通常会在子线程解码视频帧,当烘托多视频图层时,怎么确保多解码线程下的帧同步?

3、多文件播映区间操控

PAG支撑File自定义显现区间,每个File文件时长也不一致,怎么操控不同文件的播映区间?

播映进展

PAG动效框架源码笔记 (三)播放流程

PAG没有选用类似于CMTime帧数的方式记载播映进展,而是经过时长百分比记载各层级播映进展,从而完成不同帧率(FPS)文件的进展同步

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角色,当重复制作同一帧时能够直接运用缓存数据

Content* PAGLayer::getContent() {
 return layerCache->getContent(contentFrame);
}

PAG信号源PAGValueAnimator经过肯定时刻差值核算播映进展,多个PAGView共用一个全局信号触发器

// 获取肯定时刻戳
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、信号触发
- (void)onAnimationUpdate {
 // 触发更新
  [self updateView];
}
​
- (BOOL)flush {
  ...
 // 更新播映进展
  [pagPlayer setProgress:[valueAnimator getAnimatedFraction]];
 // 触发刷新
 result = [pagPlayer flush];
  ...
}
2、更新播映进展
void PAGPlayer::setProgress(double percent) {
 // 获取烘托图层,递归更新各图层播映进展
 auto pagComposition = stage->getRootComposition();
 pagComposition->setProgressInternal(realProgress);
}
​
void PAGLayer::setProgressInternal(double percent) {
 // 各图层开端播映时刻不同,转化为全局整体播映时刻戳
 gotoTimeAndNotifyChanged(startTimeInternal() + ProgressToTime(percent, durationInternal()));
}
3、触发烘托
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、图形解码(以视频帧为例)
// 预处理解码
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、烘托图形
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优化等等

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烘托引擎部分