• 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 "modules/skottie/include/Skottie.h"
15 #include "modules/skresources/include/SkResources.h"
16 #include "src/utils/SkOSPath.h"
17 
18 #include "tools/flags/CommandLineFlags.h"
19 #include "tools/gpu/GrContextFactory.h"
20 
21 #include "include/gpu/GrContextOptions.h"
22 
23 static DEFINE_string2(input, i, "", "skottie animation to render");
24 static DEFINE_string2(output, o, "", "mp4 file to create");
25 static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
26 static DEFINE_int_2(fps, f, 25, "fps");
27 static DEFINE_bool2(verbose, v, false, "verbose mode");
28 static DEFINE_bool2(loop, l, false, "loop mode for profiling");
29 static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
30 static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
31 
produce_frame(SkSurface * surf,skottie::Animation * anim,double frame)32 static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame) {
33     anim->seekFrame(frame);
34     surf->getCanvas()->clear(SK_ColorWHITE);
35     anim->render(surf->getCanvas());
36 }
37 
38 struct AsyncRec {
39     SkImageInfo info;
40     SkVideoEncoder* encoder;
41 };
42 
main(int argc,char ** argv)43 int main(int argc, char** argv) {
44     SkGraphics::Init();
45 
46     CommandLineFlags::SetUsage("Converts skottie to a mp4");
47     CommandLineFlags::Parse(argc, argv);
48 
49     if (FLAGS_input.count() == 0) {
50         SkDebugf("-i input_file.json argument required\n");
51         return -1;
52     }
53 
54     auto contextType = sk_gpu_test::GrContextFactory::kGL_ContextType;
55     GrContextOptions grCtxOptions;
56     sk_gpu_test::GrContextFactory factory(grCtxOptions);
57 
58     SkString assetPath;
59     if (FLAGS_assetPath.count() > 0) {
60         assetPath.set(FLAGS_assetPath[0]);
61     } else {
62         assetPath = SkOSPath::Dirname(FLAGS_input[0]);
63     }
64     SkDebugf("assetPath %s\n", assetPath.c_str());
65 
66     auto animation = skottie::Animation::Builder()
67         .setResourceProvider(skresources::FileResourceProvider::Make(assetPath))
68         .makeFromFile(FLAGS_input[0]);
69     if (!animation) {
70         SkDebugf("failed to load %s\n", FLAGS_input[0]);
71         return -1;
72     }
73 
74     SkISize dim = animation->size().toRound();
75     double duration = animation->duration();
76     int fps = SkTPin(FLAGS_fps, 1, 240);
77     double fps_scale = animation->fps() / fps;
78 
79     float scale = 1;
80     if (FLAGS_set_dst_width > 0) {
81         scale = FLAGS_set_dst_width / (float)dim.width();
82         dim = { FLAGS_set_dst_width, SkScalarRoundToInt(scale * dim.height()) };
83     }
84 
85     const int frames = SkScalarRoundToInt(duration * fps);
86     const double frame_duration = 1.0 / fps;
87 
88     if (FLAGS_verbose) {
89         SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
90                  dim.width(), dim.height(), duration, fps, frame_duration);
91     }
92 
93     SkVideoEncoder encoder;
94 
95     GrContext* context = nullptr;
96     sk_sp<SkSurface> surf;
97     sk_sp<SkData> data;
98 
99     const auto info = SkImageInfo::MakeN32Premul(dim);
100     do {
101         double loop_start = SkTime::GetSecs();
102 
103         if (!encoder.beginRecording(dim, fps)) {
104             SkDEBUGF("Invalid video stream configuration.\n");
105             return -1;
106         }
107 
108         // lazily allocate the surfaces
109         if (!surf) {
110             if (FLAGS_gpu) {
111                 context = factory.getContextInfo(contextType).grContext();
112                 surf = SkSurface::MakeRenderTarget(context,
113                                                    SkBudgeted::kNo,
114                                                    info,
115                                                    0,
116                                                    GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
117                                                    nullptr);
118                 if (!surf) {
119                     context = nullptr;
120                 }
121             }
122             if (!surf) {
123                 surf = SkSurface::MakeRaster(info);
124             }
125             surf->getCanvas()->scale(scale, scale);
126         }
127 
128         for (int i = 0; i <= frames; ++i) {
129             const double frame = i * fps_scale;
130             if (FLAGS_verbose) {
131                 SkDebugf("rendering frame %g\n", frame);
132             }
133 
134             produce_frame(surf.get(), animation.get(), frame);
135 
136             AsyncRec asyncRec = { info, &encoder };
137             if (context) {
138                 auto read_pixels_cb = [](SkSurface::ReadPixelsContext ctx,
139                                          std::unique_ptr<const SkSurface::AsyncReadResult> result) {
140                     if (result && result->count() == 1) {
141                         AsyncRec* rec = reinterpret_cast<AsyncRec*>(ctx);
142                         rec->encoder->addFrame({rec->info, result->data(0), result->rowBytes(0)});
143                     }
144                 };
145                 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
146                                                 SkSurface::RescaleGamma::kSrc,
147                                                 kNone_SkFilterQuality,
148                                                 read_pixels_cb, &asyncRec);
149             } else {
150                 SkPixmap pm;
151                 SkAssertResult(surf->peekPixels(&pm));
152                 encoder.addFrame(pm);
153             }
154         }
155         data = encoder.endRecording();
156 
157         if (FLAGS_loop) {
158             double loop_dur = SkTime::GetSecs() - loop_start;
159             SkDebugf("recording secs %g, frames %d, recording fps %d\n",
160                      loop_dur, frames, (int)(frames / loop_dur));
161         }
162     } while (FLAGS_loop);
163 
164     if (FLAGS_output.count() == 0) {
165         SkDebugf("missing -o output_file.mp4 argument\n");
166         return 0;
167     }
168 
169     SkFILEWStream ostream(FLAGS_output[0]);
170     if (!ostream.isValid()) {
171         SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
172         return -1;
173     }
174     ostream.write(data->data(), data->size());
175     return 0;
176 }
177