add simpler addFrame api (helps with threaded producers)

Change-Id: I458dc2fb59aa32e084b0b03945afd74ff5ee42ae
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/217861
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Reed <reed@google.com>
Auto-Submit: Mike Reed <reed@google.com>
diff --git a/experimental/ffmpeg/SkVideoEncoder.cpp b/experimental/ffmpeg/SkVideoEncoder.cpp
index 8ede90d..bb5c886 100644
--- a/experimental/ffmpeg/SkVideoEncoder.cpp
+++ b/experimental/ffmpeg/SkVideoEncoder.cpp
@@ -110,7 +110,9 @@
     return pos;
 }
 
-SkVideoEncoder::SkVideoEncoder() {}
+SkVideoEncoder::SkVideoEncoder() {
+    fInfo = SkImageInfo::MakeUnknown();
+}
 
 SkVideoEncoder::~SkVideoEncoder() {
     this->reset();
@@ -137,14 +139,11 @@
     av_packet_free(&fPacket);
     fPacket = nullptr;
 
+    fSurface.reset();
     fWStream.reset();
 }
 
-bool SkVideoEncoder::init(const SkImageInfo& info, int fps) {
-    if ((info.width() & 1) || (info.height() & 1)) {
-        SkDebugf("dimensinos must be even (it appears)\n");
-        return false;
-    }
+bool SkVideoEncoder::init(int fps) {
     // only support this for now
     AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P;
 
@@ -182,8 +181,8 @@
     SkASSERT(fEncoderCtx);
 
     fEncoderCtx->codec_id = output_format->video_codec;
-    fEncoderCtx->width    = info.width();
-    fEncoderCtx->height   = info.height();
+    fEncoderCtx->width    = fInfo.width();
+    fEncoderCtx->height   = fInfo.height();
     fEncoderCtx->time_base = fStream->time_base;
     fEncoderCtx->pix_fmt  = pix_fmt;
 
@@ -219,18 +218,26 @@
 #include "include/core/SkColorFilter.h"
 #include "src/core/SkYUVMath.h"
 
-bool SkVideoEncoder::beginRecording(SkISize dim, int fps) {
+static bool is_valid(SkISize dim) {
     if (dim.width() <= 0 || dim.height() <= 0) {
         return false;
     }
+    // need the dimensions to be even for YUV 420
+    return ((dim.width() | dim.height()) & 1) == 0;
+}
+
+bool SkVideoEncoder::beginRecording(SkISize dim, int fps) {
+    if (!is_valid(dim)) {
+        return false;
+    }
 
     // need opaque and bgra to efficiently use libyuv / convert-to-yuv-420
-    auto info = SkImageInfo::Make(dim.width(), dim.height(),
-                                  kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr);
-    if (!this->init(info, fps)) {
+    SkAlphaType alphaType = kOpaque_SkAlphaType;
+    sk_sp<SkColorSpace> cs = nullptr;   // should we use this?
+    fInfo = SkImageInfo::Make(dim.width(), dim.height(), kRGBA_8888_SkColorType, alphaType, cs);
+    if (!this->init(fps)) {
         return false;
     }
-    fSurface = SkSurface::MakeRaster(info);
 
     fCurrentPTS = 0;
     fDeltaPTS = 1;
@@ -248,14 +255,23 @@
     return fSWScaleCtx != nullptr;
 }
 
-SkCanvas* SkVideoEncoder::beginFrame() {
-    if (!fSurface) {
-        return nullptr;
+bool SkVideoEncoder::addFrame(const SkPixmap& pm) {
+    if (!is_valid(pm.dimensions())) {
+        return false;
     }
-    SkCanvas* canvas = fSurface->getCanvas();
-    canvas->restoreToCount(1);
-    canvas->clear(0);
-    return canvas;
+    /* make sure the frame data is writable */
+    if (check_err(av_frame_make_writable(fFrame))) {
+        return false;
+    }
+
+    fFrame->pts = fCurrentPTS;
+    fCurrentPTS += fDeltaPTS;
+
+    const uint8_t* src[] = { (const uint8_t*)pm.addr() };
+    const int strides[] = { SkToInt(pm.rowBytes()) };
+    sws_scale(fSWScaleCtx, src, strides, 0, fInfo.height(), fFrame->data, fFrame->linesize);
+
+    return this->sendFrame(fFrame);
 }
 
 bool SkVideoEncoder::sendFrame(AVFrame* frame) {
@@ -283,22 +299,25 @@
     return true;
 }
 
+SkCanvas* SkVideoEncoder::beginFrame() {
+    if (!fSurface) {
+        fSurface = SkSurface::MakeRaster(fInfo);
+        if (!fSurface) {
+            return nullptr;
+        }
+    }
+    SkCanvas* canvas = fSurface->getCanvas();
+    canvas->restoreToCount(1);
+    canvas->clear(0);
+    return canvas;
+}
+
 bool SkVideoEncoder::endFrame() {
-    /* make sure the frame data is writable */
-    if (check_err(av_frame_make_writable(fFrame))) {
+    if (!fSurface) {
         return false;
     }
-
-    fFrame->pts = fCurrentPTS;
-    fCurrentPTS += fDeltaPTS;
-
     SkPixmap pm;
-    SkAssertResult(fSurface->peekPixels(&pm));
-    const uint8_t* src[] = { (const uint8_t*)pm.addr() };
-    const int strides[] = { SkToInt(pm.rowBytes()) };
-    sws_scale(fSWScaleCtx, src, strides, 0, fSurface->height(), fFrame->data, fFrame->linesize);
-
-    return this->sendFrame(fFrame);
+    return fSurface->peekPixels(&pm) && this->addFrame(pm);
 }
 
 sk_sp<SkData> SkVideoEncoder::endRecording() {
diff --git a/experimental/ffmpeg/SkVideoEncoder.h b/experimental/ffmpeg/SkVideoEncoder.h
index cd5102a..6a35061 100644
--- a/experimental/ffmpeg/SkVideoEncoder.h
+++ b/experimental/ffmpeg/SkVideoEncoder.h
@@ -27,16 +27,45 @@
     SkVideoEncoder();
     ~SkVideoEncoder();
 
+    /**
+     *  Begina a new recording. Balance this (after adding all of your frames) with a call
+     *  to endRecording().
+     */
     bool beginRecording(SkISize, int fps);
 
+    /**
+     *  Returns the preferred ImageInfo for this recording. Only valid if beginRecording() has
+     *  been called, and endRecording has not been called (yet).
+     */
+    SkImageInfo preferredInfo() const { return fInfo; }
+
+    /**
+     *  If you have your own pixmap, call addFrame(). Note this may fail if it uses an unsupported
+     *  ColorType or AlphaType, or the dimensions don't match those set in beginRecording.
+     *  For best results, use the SkImageInfo returned by preferredInfo().
+     */
+    bool addFrame(const SkPixmap&);
+
+    /**
+     *  As an alternative to calling addFrame(), you can call beginFrame/endFrame, and the encoder
+     *  will manage allocating a surface/canvas for you.
+     *
+     *  SkCanvas* canvas = encoder.beginFrame();
+     *  // your drawing code here, drawing into canvas
+     *  encoder.endFrame();
+     */
     SkCanvas* beginFrame();
     bool endFrame();
 
+    /**
+     *  Call this after having added all of your frames. After calling this, no more frames can
+     *  be added to this recording. To record a new video, call beginRecording().
+     */
     sk_sp<SkData> endRecording();
 
 private:
     void reset();
-    bool init(const SkImageInfo&, int fps);
+    bool init(int fps);
     bool sendFrame(AVFrame*);   // frame can be null
 
     double computeTimeStamp(const AVFrame*) const;
@@ -49,10 +78,13 @@
     AVFrame*        fFrame = nullptr;
     AVPacket*       fPacket = nullptr;
 
-    sk_sp<SkSurface> fSurface;
+    SkImageInfo     fInfo;  // only defined between beginRecording() and endRecording()
     std::unique_ptr<SkRandomAccessWStream> fWStream;
+    int64_t         fCurrentPTS, fDeltaPTS;
 
-    int64_t          fCurrentPTS, fDeltaPTS;
+    // Lazily allocated, iff the client has called beginFrame() for a given recording session.
+    sk_sp<SkSurface> fSurface;
+
 };
 
 #endif
diff --git a/include/core/SkPixmap.h b/include/core/SkPixmap.h
index d058da2..121110a 100644
--- a/include/core/SkPixmap.h
+++ b/include/core/SkPixmap.h
@@ -149,6 +149,11 @@
     */
     int height() const { return fInfo.height(); }
 
+    /**
+     *  Return the dimensions of the pixmap (from its ImageInfo)
+     */
+    SkISize dimensions() const { return fInfo.dimensions(); }
+
     /** Returns SkColorType, one of:
         kUnknown_SkColorType, kAlpha_8_SkColorType, kRGB_565_SkColorType,
         kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kRGB_888x_SkColorType,
diff --git a/tools/skottie2movie.cpp b/tools/skottie2movie.cpp
index c822eb9..9ecbb3c 100644
--- a/tools/skottie2movie.cpp
+++ b/tools/skottie2movie.cpp
@@ -7,6 +7,8 @@
 
 #include "modules/skottie/include/Skottie.h"
 #include "modules/skottie/utils/SkottieUtils.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkSurface.h"
 #include "include/core/SkStream.h"
 #include "include/core/SkTime.h"
 #include "src/utils/SkOSPath.h"
@@ -79,14 +81,20 @@
 
     encoder.beginRecording(dim, fps);
 
+    auto surf = SkSurface::MakeRaster(encoder.preferredInfo());
+
     for (int i = 0; i <= frames; ++i) {
         double ts = i * 1.0 / fps;
         if (FLAGS_verbose) {
             SkDebugf("rendering frame %d ts %g\n", i, ts);
         }
         animation->seek(i * 1.0 / frames);  // normalized time
-        animation->render(encoder.beginFrame());
-        encoder.endFrame();
+        surf->getCanvas()->clear(SK_ColorWHITE);
+        animation->render(surf->getCanvas());
+
+        SkPixmap pm;
+        SkAssertResult(surf->peekPixels(&pm));
+        encoder.addFrame(pm);
     }
 
     if (FLAGS_output.count() == 0) {