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) {