blob: 5081fd48da9238642aeb4049b11863c58ff21917 [file] [log] [blame]
Mike Reedc0ee21f2019-05-28 16:11:03 -04001/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
Mike Klein4b432fa2019-06-06 11:44:05 -05008#include "experimental/ffmpeg/SkVideoEncoder.h"
9#include "include/core/SkCanvas.h"
Mike Reedf755bc72019-09-20 11:37:36 -040010#include "include/core/SkGraphics.h"
Mike Klein4b432fa2019-06-06 11:44:05 -050011#include "include/core/SkStream.h"
12#include "include/core/SkSurface.h"
13#include "include/core/SkTime.h"
Mike Klein8aa0edf2020-10-16 11:04:18 -050014#include "include/private/SkTPin.h"
Mike Reedc0ee21f2019-05-28 16:11:03 -040015#include "modules/skottie/include/Skottie.h"
Brian Osman849f4d62019-11-26 08:58:26 -050016#include "modules/skresources/include/SkResources.h"
Mike Reed5093e232019-05-30 10:10:50 -040017#include "src/utils/SkOSPath.h"
Mike Reed5f12eaa2019-09-24 12:01:58 -040018
Mike Reedc0ee21f2019-05-28 16:11:03 -040019#include "tools/flags/CommandLineFlags.h"
Mike Reed5f12eaa2019-09-24 12:01:58 -040020#include "tools/gpu/GrContextFactory.h"
21
22#include "include/gpu/GrContextOptions.h"
Mike Reedc0ee21f2019-05-28 16:11:03 -040023
24static DEFINE_string2(input, i, "", "skottie animation to render");
25static DEFINE_string2(output, o, "", "mp4 file to create");
Mike Reed5093e232019-05-30 10:10:50 -040026static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
Mike Reedc0ee21f2019-05-28 16:11:03 -040027static DEFINE_int_2(fps, f, 25, "fps");
28static DEFINE_bool2(verbose, v, false, "verbose mode");
Mike Reed5093e232019-05-30 10:10:50 -040029static DEFINE_bool2(loop, l, false, "loop mode for profiling");
Mike Reed2d241f52019-09-16 15:14:21 -040030static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
Mike Reed5f12eaa2019-09-24 12:01:58 -040031static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
Mike Reed4b203ad2019-06-17 17:45:01 -040032
Florin Malitadd5ba942019-11-24 15:42:24 -050033static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame) {
34 anim->seekFrame(frame);
Mike Reed4b203ad2019-06-17 17:45:01 -040035 surf->getCanvas()->clear(SK_ColorWHITE);
36 anim->render(surf->getCanvas());
37}
38
Mike Reed5f12eaa2019-09-24 12:01:58 -040039struct AsyncRec {
40 SkImageInfo info;
41 SkVideoEncoder* encoder;
42};
43
Mike Reedc0ee21f2019-05-28 16:11:03 -040044int main(int argc, char** argv) {
Mike Reedf755bc72019-09-20 11:37:36 -040045 SkGraphics::Init();
46
Mike Reedc0ee21f2019-05-28 16:11:03 -040047 CommandLineFlags::SetUsage("Converts skottie to a mp4");
48 CommandLineFlags::Parse(argc, argv);
49
Mike Reed5093e232019-05-30 10:10:50 -040050 if (FLAGS_input.count() == 0) {
51 SkDebugf("-i input_file.json argument required\n");
Mike Reedc0ee21f2019-05-28 16:11:03 -040052 return -1;
53 }
54
Mike Reed5f12eaa2019-09-24 12:01:58 -040055 auto contextType = sk_gpu_test::GrContextFactory::kGL_ContextType;
56 GrContextOptions grCtxOptions;
57 sk_gpu_test::GrContextFactory factory(grCtxOptions);
58
Mike Reed5093e232019-05-30 10:10:50 -040059 SkString assetPath;
60 if (FLAGS_assetPath.count() > 0) {
61 assetPath.set(FLAGS_assetPath[0]);
62 } else {
63 assetPath = SkOSPath::Dirname(FLAGS_input[0]);
64 }
65 SkDebugf("assetPath %s\n", assetPath.c_str());
66
67 auto animation = skottie::Animation::Builder()
Brian Osman849f4d62019-11-26 08:58:26 -050068 .setResourceProvider(skresources::FileResourceProvider::Make(assetPath))
Mike Reed5093e232019-05-30 10:10:50 -040069 .makeFromFile(FLAGS_input[0]);
70 if (!animation) {
71 SkDebugf("failed to load %s\n", FLAGS_input[0]);
72 return -1;
73 }
74
Mike Reedc0ee21f2019-05-28 16:11:03 -040075 SkISize dim = animation->size().toRound();
76 double duration = animation->duration();
Florin Malitadd5ba942019-11-24 15:42:24 -050077 int fps = SkTPin(FLAGS_fps, 1, 240);
78 double fps_scale = animation->fps() / fps;
Mike Reedc0ee21f2019-05-28 16:11:03 -040079
Mike Reed2d241f52019-09-16 15:14:21 -040080 float scale = 1;
81 if (FLAGS_set_dst_width > 0) {
82 scale = FLAGS_set_dst_width / (float)dim.width();
83 dim = { FLAGS_set_dst_width, SkScalarRoundToInt(scale * dim.height()) };
84 }
85
Mike Reed4b203ad2019-06-17 17:45:01 -040086 const int frames = SkScalarRoundToInt(duration * fps);
87 const double frame_duration = 1.0 / fps;
88
Mike Reedc0ee21f2019-05-28 16:11:03 -040089 if (FLAGS_verbose) {
Mike Reed2d241f52019-09-16 15:14:21 -040090 SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
Mike Reed4b203ad2019-06-17 17:45:01 -040091 dim.width(), dim.height(), duration, fps, frame_duration);
Mike Reedc0ee21f2019-05-28 16:11:03 -040092 }
93
Mike Reedc0ee21f2019-05-28 16:11:03 -040094 SkVideoEncoder encoder;
Mike Reedc0ee21f2019-05-28 16:11:03 -040095
Florin Malita2e4dc862021-09-09 10:35:33 -040096 GrDirectContext* grctx = nullptr;
Mike Reed5f12eaa2019-09-24 12:01:58 -040097 sk_sp<SkSurface> surf;
Mike Reed4b203ad2019-06-17 17:45:01 -040098 sk_sp<SkData> data;
99
Florin Malita10e52572019-10-09 16:03:35 -0400100 const auto info = SkImageInfo::MakeN32Premul(dim);
Mike Reed4b203ad2019-06-17 17:45:01 -0400101 do {
102 double loop_start = SkTime::GetSecs();
103
Florin Malita10e52572019-10-09 16:03:35 -0400104 if (!encoder.beginRecording(dim, fps)) {
105 SkDEBUGF("Invalid video stream configuration.\n");
106 return -1;
107 }
Mike Reed5f12eaa2019-09-24 12:01:58 -0400108
Mike Reed4b203ad2019-06-17 17:45:01 -0400109 // lazily allocate the surfaces
110 if (!surf) {
Mike Reed5f12eaa2019-09-24 12:01:58 -0400111 if (FLAGS_gpu) {
Florin Malita2e4dc862021-09-09 10:35:33 -0400112 grctx = factory.getContextInfo(contextType).directContext();
113 surf = SkSurface::MakeRenderTarget(grctx,
Mike Reed5f12eaa2019-09-24 12:01:58 -0400114 SkBudgeted::kNo,
115 info,
116 0,
117 GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
118 nullptr);
119 if (!surf) {
Florin Malita2e4dc862021-09-09 10:35:33 -0400120 grctx = nullptr;
Mike Reed5f12eaa2019-09-24 12:01:58 -0400121 }
122 }
123 if (!surf) {
124 surf = SkSurface::MakeRaster(info);
125 }
126 surf->getCanvas()->scale(scale, scale);
Mike Reed4b203ad2019-06-17 17:45:01 -0400127 }
128
Mike Reedc0ee21f2019-05-28 16:11:03 -0400129 for (int i = 0; i <= frames; ++i) {
Florin Malitadd5ba942019-11-24 15:42:24 -0500130 const double frame = i * fps_scale;
Mike Reed4b203ad2019-06-17 17:45:01 -0400131 if (FLAGS_verbose) {
Florin Malitadd5ba942019-11-24 15:42:24 -0500132 SkDebugf("rendering frame %g\n", frame);
Mike Reed4b203ad2019-06-17 17:45:01 -0400133 }
134
Florin Malitadd5ba942019-11-24 15:42:24 -0500135 produce_frame(surf.get(), animation.get(), frame);
Mike Reed4b203ad2019-06-17 17:45:01 -0400136
Mike Reed5f12eaa2019-09-24 12:01:58 -0400137 AsyncRec asyncRec = { info, &encoder };
Florin Malita2e4dc862021-09-09 10:35:33 -0400138 if (grctx) {
Florin Malitaf22dda92019-10-28 18:47:32 -0400139 auto read_pixels_cb = [](SkSurface::ReadPixelsContext ctx,
140 std::unique_ptr<const SkSurface::AsyncReadResult> result) {
141 if (result && result->count() == 1) {
142 AsyncRec* rec = reinterpret_cast<AsyncRec*>(ctx);
143 rec->encoder->addFrame({rec->info, result->data(0), result->rowBytes(0)});
144 }
145 };
Mike Reed5f12eaa2019-09-24 12:01:58 -0400146 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
Florin Malitaf22dda92019-10-28 18:47:32 -0400147 SkSurface::RescaleGamma::kSrc,
Florin Malita6356cb12021-01-05 12:56:34 -0500148 SkImage::RescaleMode::kNearest,
Florin Malitaf22dda92019-10-28 18:47:32 -0400149 read_pixels_cb, &asyncRec);
Florin Malita2e4dc862021-09-09 10:35:33 -0400150 grctx->submit();
Mike Reed5f12eaa2019-09-24 12:01:58 -0400151 } else {
152 SkPixmap pm;
153 SkAssertResult(surf->peekPixels(&pm));
154 encoder.addFrame(pm);
155 }
Mike Reedc0ee21f2019-05-28 16:11:03 -0400156 }
Florin Malita2e4dc862021-09-09 10:35:33 -0400157
158 if (grctx) {
159 // ensure all pending reads are completed
160 grctx->flushAndSubmit(true);
161 }
Mike Reed4b203ad2019-06-17 17:45:01 -0400162 data = encoder.endRecording();
163
164 if (FLAGS_loop) {
165 double loop_dur = SkTime::GetSecs() - loop_start;
166 SkDebugf("recording secs %g, frames %d, recording fps %d\n",
167 loop_dur, frames, (int)(frames / loop_dur));
Mike Reed5093e232019-05-30 10:10:50 -0400168 }
Mike Reed4b203ad2019-06-17 17:45:01 -0400169 } while (FLAGS_loop);
Mike Reedc0ee21f2019-05-28 16:11:03 -0400170
Mike Reed5093e232019-05-30 10:10:50 -0400171 if (FLAGS_output.count() == 0) {
172 SkDebugf("missing -o output_file.mp4 argument\n");
173 return 0;
174 }
175
Mike Reedc0ee21f2019-05-28 16:11:03 -0400176 SkFILEWStream ostream(FLAGS_output[0]);
177 if (!ostream.isValid()) {
178 SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
179 return -1;
180 }
181 ostream.write(data->data(), data->size());
Mike Reed5093e232019-05-30 10:10:50 -0400182 return 0;
Mike Reedc0ee21f2019-05-28 16:11:03 -0400183}