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

3,153 阅读4分钟

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

PAG框架支持单PAGView同时渲染多个PAGFile,相较于渲染单一文件,框架首先需要解决多文件渲染同步问题

1、多文件渲染帧率同步

多文件有着不同的帧率(FPS) ,为了实现同一容器渲染不同帧率文件,传统的基于定时器模式(定时回调,间隔与帧率同步)的回调已无法满足该场景

2、多文件渲染进度同步

多文件渲染需要避免多图层渲染进度不一致问题;为了保证播放流畅度,通常会在子线程解码视频帧,当渲染多视频图层时,如何保证多解码线程下的帧同步?

3、多文件播放区间控制

PAG支持File自定义显示区间,每个File文件时长也不一致,如何控制不同文件的播放区间?

播放进度

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渲染引擎部分