• 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/private/base/SkTPin.h"
14 #include "modules/skottie/include/Skottie.h"
15 #include "modules/skresources/include/SkResources.h"
16 #include "src/base/SkTime.h"
17 #include "src/utils/SkOSPath.h"
18 
19 #include "tools/CodecUtils.h"
20 #include "tools/flags/CommandLineFlags.h"
21 #include "tools/gpu/GrContextFactory.h"
22 
23 #include "include/gpu/GrContextOptions.h"
24 #include "include/gpu/GrTypes.h"
25 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
26 #include "modules/skshaper/utils/FactoryHelpers.h"
27 
28 #if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
29 #include "include/ports/SkFontMgr_mac_ct.h"
30 #elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
31 #include "include/ports/SkFontMgr_fontconfig.h"
32 #else
33 #include "include/ports/SkFontMgr_empty.h"
34 #endif
35 
36 static DEFINE_string2(input, i, "", "skottie animation to render");
37 static DEFINE_string2(output, o, "", "mp4 file to create");
38 static DEFINE_string2(assetPath, a, "", "path to assets needed for json file");
39 static DEFINE_int_2(fps, f, 25, "fps");
40 static DEFINE_bool2(verbose, v, false, "verbose mode");
41 static DEFINE_bool2(loop, l, false, "loop mode for profiling");
42 static DEFINE_int(set_dst_width, 0, "set destination width (height will be computed)");
43 static DEFINE_bool2(gpu, g, false, "use GPU for rendering");
44 
produce_frame(SkSurface * surf,skottie::Animation * anim,double frame)45 static void produce_frame(SkSurface* surf, skottie::Animation* anim, double frame) {
46     anim->seekFrame(frame);
47     surf->getCanvas()->clear(SK_ColorWHITE);
48     anim->render(surf->getCanvas());
49 }
50 
51 struct AsyncRec {
52     SkImageInfo info;
53     SkVideoEncoder* encoder;
54 };
55 
main(int argc,char ** argv)56 int main(int argc, char** argv) {
57     SkGraphics::Init();
58 
59     CommandLineFlags::SetUsage("Converts skottie to a mp4");
60     CommandLineFlags::Parse(argc, argv);
61 
62     if (FLAGS_input.size() == 0) {
63         SkDebugf("-i input_file.json argument required\n");
64         return -1;
65     }
66 
67     auto contextType = skgpu::ContextType::kGL;
68     GrContextOptions grCtxOptions;
69     sk_gpu_test::GrContextFactory factory(grCtxOptions);
70 
71     SkString assetPath;
72     if (FLAGS_assetPath.size() > 0) {
73         assetPath.set(FLAGS_assetPath[0]);
74     } else {
75         assetPath = SkOSPath::Dirname(FLAGS_input[0]);
76     }
77     SkDebugf("assetPath %s\n", assetPath.c_str());
78 
79     CodecUtils::RegisterAllAvailable();
80 
81     // If necessary, clients should use a font manager that would load fonts from the system.
82 #if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
83     sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_CoreText(nullptr);
84 #elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
85     sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_Android(nullptr, std::make_unique<SkFontScanner_FreeType>());
86 #elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
87     sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_FontConfig(nullptr);
88 #else
89     sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_Custom_Empty();
90 #endif
91 
92     auto animation = skottie::Animation::Builder()
93         .setResourceProvider(skresources::FileResourceProvider::Make(assetPath))
94         .setTextShapingFactory(SkShapers::BestAvailable())
95         .setFontManager(fontMgr)
96         .makeFromFile(FLAGS_input[0]);
97     if (!animation) {
98         SkDebugf("failed to load %s\n", FLAGS_input[0]);
99         return -1;
100     }
101 
102     SkISize dim = animation->size().toRound();
103     double duration = animation->duration();
104     int fps = SkTPin(FLAGS_fps, 1, 240);
105     double fps_scale = animation->fps() / fps;
106 
107     float scale = 1;
108     if (FLAGS_set_dst_width > 0) {
109         scale = FLAGS_set_dst_width / (float)dim.width();
110         dim = { FLAGS_set_dst_width, SkScalarRoundToInt(scale * dim.height()) };
111     }
112 
113     const int frames = SkScalarRoundToInt(duration * fps);
114     const double frame_duration = 1.0 / fps;
115 
116     if (FLAGS_verbose) {
117         SkDebugf("Size %dx%d duration %g, fps %d, frame_duration %g\n",
118                  dim.width(), dim.height(), duration, fps, frame_duration);
119     }
120 
121     SkVideoEncoder encoder;
122 
123     GrDirectContext* grctx = nullptr;
124     sk_sp<SkSurface> surf;
125     sk_sp<SkData> data;
126 
127     const auto info = SkImageInfo::MakeN32Premul(dim);
128     do {
129         double loop_start = SkTime::GetSecs();
130 
131         if (!encoder.beginRecording(dim, fps)) {
132             SkDEBUGF("Invalid video stream configuration.\n");
133             return -1;
134         }
135 
136         // lazily allocate the surfaces
137         if (!surf) {
138             if (FLAGS_gpu) {
139                 grctx = factory.getContextInfo(contextType).directContext();
140                 surf = SkSurfaces::RenderTarget(grctx,
141                                                 skgpu::Budgeted::kNo,
142                                                 info,
143                                                 0,
144                                                 GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
145                                                 nullptr);
146                 if (!surf) {
147                     grctx = nullptr;
148                 }
149             }
150             if (!surf) {
151                 surf = SkSurfaces::Raster(info);
152             }
153             surf->getCanvas()->scale(scale, scale);
154         }
155 
156         for (int i = 0; i <= frames; ++i) {
157             const double frame = i * fps_scale;
158             if (FLAGS_verbose) {
159                 SkDebugf("rendering frame %g\n", frame);
160             }
161 
162             produce_frame(surf.get(), animation.get(), frame);
163 
164             AsyncRec asyncRec = { info, &encoder };
165             if (grctx) {
166                 auto read_pixels_cb = [](SkSurface::ReadPixelsContext ctx,
167                                          std::unique_ptr<const SkSurface::AsyncReadResult> result) {
168                     if (result && result->count() == 1) {
169                         AsyncRec* rec = reinterpret_cast<AsyncRec*>(ctx);
170                         rec->encoder->addFrame({rec->info, result->data(0), result->rowBytes(0)});
171                     }
172                 };
173                 surf->asyncRescaleAndReadPixels(info, {0, 0, info.width(), info.height()},
174                                                 SkSurface::RescaleGamma::kSrc,
175                                                 SkImage::RescaleMode::kNearest,
176                                                 read_pixels_cb, &asyncRec);
177                 grctx->submit();
178             } else {
179                 SkPixmap pm;
180                 SkAssertResult(surf->peekPixels(&pm));
181                 encoder.addFrame(pm);
182             }
183         }
184 
185         if (grctx) {
186             // ensure all pending reads are completed
187             grctx->flushAndSubmit(GrSyncCpu::kYes);
188         }
189         data = encoder.endRecording();
190 
191         if (FLAGS_loop) {
192             double loop_dur = SkTime::GetSecs() - loop_start;
193             SkDebugf("recording secs %g, frames %d, recording fps %d\n",
194                      loop_dur, frames, (int)(frames / loop_dur));
195         }
196     } while (FLAGS_loop);
197 
198     if (FLAGS_output.size() == 0) {
199         SkDebugf("missing -o output_file.mp4 argument\n");
200         return 0;
201     }
202 
203     SkFILEWStream ostream(FLAGS_output[0]);
204     if (!ostream.isValid()) {
205         SkDebugf("Can't create output file %s\n", FLAGS_output[0]);
206         return -1;
207     }
208     ostream.write(data->data(), data->size());
209     return 0;
210 }
211