• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
8 #include "experimental/ffmpeg/SkVideoEncoder.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkGraphics.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkSurface.h"
13 #include "include/core/SkTime.h"
14 #include "include/private/base/SkTPin.h"
15 #include "modules/skottie/include/Skottie.h"
16 #include "modules/skresources/include/SkResources.h"
17 #include "src/utils/SkOSPath.h"
18 
19 #include "tools/flags/CommandLineFlags.h"
20 #include "tools/gpu/GrContextFactory.h"
21 
22 #include "include/gpu/GrContextOptions.h"
23 
24 static DEFINE_string2(input, i, "", "skottie animation to render");
25 static DEFINE_string2(output, o, "", "mp4 file to create");
26 static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
27 static DEFINE_int_2(fps, f, 25, "fps");
28 static DEFINE_bool2(verbose, v, false, "verbose mode");
29 static DEFINE_bool2(loop, l, false, "loop mode for profiling");
30 static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
31 static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
32 
produce_frame(SkSurface * surf,skottie::Animation * anim,double frame)33 static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame) {
34     anim->seekFrame(frame);
35     surf->getCanvas()->clear(SK_ColorWHITE);
36     anim->render(surf->getCanvas());
37 }
38 
39 struct AsyncRec {
40     SkImageInfo info;
41     SkVideoEncoder* encoder;
42 };
43 
main(int argc,char ** argv)44 int main(int argc, char** argv) {
45     SkGraphics::Init();
46 
47     CommandLineFlags::SetUsage("Converts skottie to a mp4");
48     CommandLineFlags::Parse(argc, argv);
49 
50     if (FLAGS_input.size() == 0) {
51         SkDebugf("-i input_file.json argument required\n");
52         return -1;
53     }
54 
55     auto contextType = sk_gpu_test::GrContextFactory::kGL_ContextType;
56     GrContextOptions grCtxOptions;
57     sk_gpu_test::GrContextFactory factory(grCtxOptions);
58 
59     SkString assetPath;
60     if (FLAGS_assetPath.size() > 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()
68         .setResourceProvider(skresources::FileResourceProvider::Make(assetPath))
69         .makeFromFile(FLAGS_input[0]);
70     if (!animation) {
71         SkDebugf("failed to load %s\n", FLAGS_input[0]);
72         return -1;
73     }
74 
75     SkISize dim = animation->size().toRound();
76     double duration = animation->duration();
77     int fps = SkTPin(FLAGS_fps, 1, 240);
78     double fps_scale = animation->fps() / fps;
79 
80     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 
86     const int frames = SkScalarRoundToInt(duration * fps);
87     const double frame_duration = 1.0 / fps;
88 
89     if (FLAGS_verbose) {
90         SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
91                  dim.width(), dim.height(), duration, fps, frame_duration);
92     }
93 
94     SkVideoEncoder encoder;
95 
96     GrDirectContext* grctx = nullptr;
97     sk_sp<SkSurface> surf;
98     sk_sp<SkData> data;
99 
100     const auto info = SkImageInfo::MakeN32Premul(dim);
101     do {
102         double loop_start = SkTime::GetSecs();
103 
104         if (!encoder.beginRecording(dim, fps)) {
105             SkDEBUGF("Invalid video stream configuration.\n");
106             return -1;
107         }
108 
109         // lazily allocate the surfaces
110         if (!surf) {
111             if (FLAGS_gpu) {
112                 grctx = factory.getContextInfo(contextType).directContext();
113                 surf = SkSurface::MakeRenderTarget(grctx,
114                                                    skgpu::Budgeted::kNo,
115                                                    info,
116                                                    0,
117                                                    GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
118                                                    nullptr);
119                 if (!surf) {
120                     grctx = nullptr;
121                 }
122             }
123             if (!surf) {
124                 surf = SkSurface::MakeRaster(info);
125             }
126             surf->getCanvas()->scale(scale, scale);
127         }
128 
129         for (int i = 0; i <= frames; ++i) {
130             const double frame = i * fps_scale;
131             if (FLAGS_verbose) {
132                 SkDebugf("rendering frame %g\n", frame);
133             }
134 
135             produce_frame(surf.get(), animation.get(), frame);
136 
137             AsyncRec asyncRec = { info, &encoder };
138             if (grctx) {
139                 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                 };
146                 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
147                                                 SkSurface::RescaleGamma::kSrc,
148                                                 SkImage::RescaleMode::kNearest,
149                                                 read_pixels_cb, &asyncRec);
150                 grctx->submit();
151             } else {
152                 SkPixmap pm;
153                 SkAssertResult(surf->peekPixels(&pm));
154                 encoder.addFrame(pm);
155             }
156         }
157 
158         if (grctx) {
159             // ensure all pending reads are completed
160             grctx->flushAndSubmit(true);
161         }
162         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));
168         }
169     } while (FLAGS_loop);
170 
171     if (FLAGS_output.size() == 0) {
172         SkDebugf("missing -o output_file.mp4 argument\n");
173         return 0;
174     }
175 
176     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());
182     return 0;
183 }
184