1 /*
2 * Copyright 2019 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/SkColorSpace.h"
10 #include "include/core/SkImage.h"
11 #include "include/core/SkYUVAIndex.h"
12 #include "include/private/SkTDArray.h"
13
14 extern "C" {
15 #include "libswscale/swscale.h"
16 }
17
18 class SkRandomAccessWStream {
19 SkTDArray<char> fStorage;
20 size_t fPos = 0;
21
22 public:
SkRandomAccessWStream()23 SkRandomAccessWStream() {}
24
pos() const25 size_t pos() const { return fPos; }
26
size() const27 size_t size() const { return fStorage.size(); }
28
write(const void * src,size_t bytes)29 void write(const void* src, size_t bytes) {
30 size_t len = fStorage.size();
31 SkASSERT(fPos <= len);
32
33 size_t overwrite = std::min(len - fPos, bytes);
34 if (overwrite) {
35 SkDebugf("overwrite %zu bytes at %zu offset with %zu remaining\n", overwrite, fPos, bytes - overwrite);
36 memcpy(&fStorage[fPos], src, overwrite);
37 fPos += overwrite;
38 src = (const char*)src + overwrite;
39 bytes -= overwrite;
40 }
41 // bytes now represents the amount to append
42 if (bytes) {
43 fStorage.append(bytes, (const char*)src);
44 fPos += bytes;
45 }
46 SkASSERT(fPos <= fStorage.size());
47 }
48
seek(size_t pos)49 void seek(size_t pos) {
50 SkASSERT(pos <= fStorage.size());
51 fPos = pos;
52 }
53
detachAsData()54 sk_sp<SkData> detachAsData() {
55 // TODO: could add an efficient detach to SkTDArray if we wanted, w/o copy
56 return SkData::MakeWithCopy(fStorage.begin(), fStorage.size());
57 }
58 };
59
60 ///////////////////////////////////////////////////////////////////////////////////////////////////
61
62 // returns true on error (and may dump the particular error message)
check_err(int err,const int silentList[]=nullptr)63 static bool check_err(int err, const int silentList[] = nullptr) {
64 if (err >= 0) {
65 return false;
66 }
67
68 if (silentList) {
69 for (; *silentList; ++silentList) {
70 if (*silentList == err) {
71 return true; // we still report the error, but we don't printf
72 }
73 }
74 }
75
76 char errbuf[128];
77 const char *errbuf_ptr = errbuf;
78
79 if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) {
80 errbuf_ptr = strerror(AVUNERROR(err));
81 }
82 SkDebugf("%s\n", errbuf_ptr);
83 return true;
84 }
85
sk_write_packet(void * ctx,uint8_t * buffer,int size)86 static int sk_write_packet(void* ctx, uint8_t* buffer, int size) {
87 SkRandomAccessWStream* stream = (SkRandomAccessWStream*)ctx;
88 stream->write(buffer, size);
89 return size;
90 }
91
sk_seek_packet(void * ctx,int64_t pos,int whence)92 static int64_t sk_seek_packet(void* ctx, int64_t pos, int whence) {
93 SkRandomAccessWStream* stream = (SkRandomAccessWStream*)ctx;
94 switch (whence) {
95 case SEEK_SET:
96 break;
97 case SEEK_CUR:
98 pos = (int64_t)stream->pos() + pos;
99 break;
100 case SEEK_END:
101 pos = (int64_t)stream->size() + pos;
102 break;
103 default:
104 return -1;
105 }
106 if (pos < 0 || pos > (int64_t)stream->size()) {
107 return -1;
108 }
109 stream->seek(SkToSizeT(pos));
110 return pos;
111 }
112
SkVideoEncoder()113 SkVideoEncoder::SkVideoEncoder() {
114 fInfo = SkImageInfo::MakeUnknown();
115 }
116
~SkVideoEncoder()117 SkVideoEncoder::~SkVideoEncoder() {
118 this->reset();
119
120 if (fSWScaleCtx) {
121 sws_freeContext(fSWScaleCtx);
122 }
123 }
124
reset()125 void SkVideoEncoder::reset() {
126 if (fFrame) {
127 av_frame_free(&fFrame);
128 fFrame = nullptr;
129 }
130 if (fEncoderCtx) {
131 avcodec_free_context(&fEncoderCtx);
132 fEncoderCtx = nullptr;
133 }
134 if (fFormatCtx) {
135 avformat_free_context(fFormatCtx);
136 fFormatCtx = nullptr;
137 }
138
139 av_packet_free(&fPacket);
140 fPacket = nullptr;
141
142 fSurface.reset();
143 fWStream.reset();
144 }
145
init(int fps)146 bool SkVideoEncoder::init(int fps) {
147 // only support this for now
148 AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P;
149
150 this->reset();
151
152 fWStream.reset(new SkRandomAccessWStream);
153
154 int bufferSize = 4 * 1024;
155 uint8_t* buffer = (uint8_t*)av_malloc(bufferSize);
156 if (!buffer) {
157 return false;
158 }
159 fStreamCtx = avio_alloc_context(buffer, bufferSize, AVIO_FLAG_WRITE, fWStream.get(),
160 nullptr, sk_write_packet, sk_seek_packet);
161 SkASSERT(fStreamCtx);
162
163 avformat_alloc_output_context2(&fFormatCtx, nullptr, "mp4", nullptr);
164 SkASSERT(fFormatCtx);
165 fFormatCtx->pb = fStreamCtx;
166
167 AVOutputFormat *output_format = fFormatCtx->oformat;
168
169 if (output_format->video_codec == AV_CODEC_ID_NONE) {
170 return false;
171 }
172 AVCodec* codec = avcodec_find_encoder(output_format->video_codec);
173 SkASSERT(codec);
174
175 fStream = avformat_new_stream(fFormatCtx, codec);
176 SkASSERT(fStream);
177 fStream->id = fFormatCtx->nb_streams-1;
178 fStream->time_base = (AVRational){ 1, fps };
179
180 fEncoderCtx = avcodec_alloc_context3(codec);
181 SkASSERT(fEncoderCtx);
182
183 fEncoderCtx->codec_id = output_format->video_codec;
184 fEncoderCtx->width = fInfo.width();
185 fEncoderCtx->height = fInfo.height();
186 fEncoderCtx->time_base = fStream->time_base;
187 fEncoderCtx->pix_fmt = pix_fmt;
188
189 /* Some formats want stream headers to be separate. */
190 if (output_format->flags & AVFMT_GLOBALHEADER) {
191 fEncoderCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
192 }
193
194 if (check_err(avcodec_open2(fEncoderCtx, codec, nullptr))) {
195 return false;
196 }
197 fFrame = av_frame_alloc();
198 SkASSERT(fFrame);
199 fFrame->format = pix_fmt;
200 fFrame->width = fEncoderCtx->width;
201 fFrame->height = fEncoderCtx->height;
202 if (check_err(av_frame_get_buffer(fFrame, 32))) {
203 return false;
204 }
205
206 if (check_err(avcodec_parameters_from_context(fStream->codecpar, fEncoderCtx))) {
207 return false;
208 }
209 if (check_err(avformat_write_header(fFormatCtx, nullptr))) {
210 return false;
211 }
212 fPacket = av_packet_alloc();
213 return true;
214 }
215
216 #include "include/core/SkCanvas.h"
217 #include "include/core/SkColorFilter.h"
218 #include "include/core/SkSurface.h"
219 #include "src/core/SkYUVMath.h"
220
is_valid(SkISize dim)221 static bool is_valid(SkISize dim) {
222 if (dim.width() <= 0 || dim.height() <= 0) {
223 return false;
224 }
225 // need the dimensions to be even for YUV 420
226 return ((dim.width() | dim.height()) & 1) == 0;
227 }
228
beginRecording(SkISize dim,int fps)229 bool SkVideoEncoder::beginRecording(SkISize dim, int fps) {
230 if (!is_valid(dim)) {
231 return false;
232 }
233
234 // need opaque and bgra to efficiently use libyuv / convert-to-yuv-420
235 SkAlphaType alphaType = kOpaque_SkAlphaType;
236 sk_sp<SkColorSpace> cs = nullptr; // should we use this?
237 fInfo = SkImageInfo::Make(dim.width(), dim.height(), kRGBA_8888_SkColorType, alphaType, cs);
238 if (!this->init(fps)) {
239 return false;
240 }
241
242 fCurrentPTS = 0;
243 fDeltaPTS = 1;
244
245 SkASSERT(sws_isSupportedInput(AV_PIX_FMT_RGBA) > 0);
246 SkASSERT(sws_isSupportedOutput(AV_PIX_FMT_YUV420P) > 0);
247 // sws_getCachedContext takes in either null or a previous ctx. It returns either a new ctx,
248 // or the same as the input if it is compatible with the inputs. Thus we never have to
249 // explicitly release our ctx until the destructor, since sws_getCachedContext takes care
250 // of freeing the old as needed if/when it returns a new one.
251 fSWScaleCtx = sws_getCachedContext(fSWScaleCtx,
252 dim.width(), dim.height(), AV_PIX_FMT_RGBA,
253 dim.width(), dim.height(), AV_PIX_FMT_YUV420P,
254 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
255 return fSWScaleCtx != nullptr;
256 }
257
addFrame(const SkPixmap & pm)258 bool SkVideoEncoder::addFrame(const SkPixmap& pm) {
259 if (!is_valid(pm.dimensions())) {
260 return false;
261 }
262 /* make sure the frame data is writable */
263 if (check_err(av_frame_make_writable(fFrame))) {
264 return false;
265 }
266
267 fFrame->pts = fCurrentPTS;
268 fCurrentPTS += fDeltaPTS;
269
270 const uint8_t* src[] = { (const uint8_t*)pm.addr() };
271 const int strides[] = { SkToInt(pm.rowBytes()) };
272 sws_scale(fSWScaleCtx, src, strides, 0, fInfo.height(), fFrame->data, fFrame->linesize);
273
274 return this->sendFrame(fFrame);
275 }
276
sendFrame(AVFrame * frame)277 bool SkVideoEncoder::sendFrame(AVFrame* frame) {
278 if (check_err(avcodec_send_frame(fEncoderCtx, frame))) {
279 return false;
280 }
281
282 int ret = 0;
283 while (ret >= 0) {
284 ret = avcodec_receive_packet(fEncoderCtx, fPacket);
285 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
286 break;
287 }
288 if (check_err(ret)) {
289 return false;
290 }
291
292 av_packet_rescale_ts(fPacket, fEncoderCtx->time_base, fStream->time_base);
293 SkASSERT(fPacket->stream_index == fStream->index);
294
295 if (check_err(av_interleaved_write_frame(fFormatCtx, fPacket))) {
296 return false;
297 }
298 }
299 return true;
300 }
301
beginFrame()302 SkCanvas* SkVideoEncoder::beginFrame() {
303 if (!fSurface) {
304 fSurface = SkSurface::MakeRaster(fInfo);
305 if (!fSurface) {
306 return nullptr;
307 }
308 }
309 SkCanvas* canvas = fSurface->getCanvas();
310 canvas->restoreToCount(1);
311 canvas->clear(0);
312 return canvas;
313 }
314
endFrame()315 bool SkVideoEncoder::endFrame() {
316 if (!fSurface) {
317 return false;
318 }
319 SkPixmap pm;
320 return fSurface->peekPixels(&pm) && this->addFrame(pm);
321 }
322
endRecording()323 sk_sp<SkData> SkVideoEncoder::endRecording() {
324 if (!fFormatCtx) {
325 return nullptr;
326 }
327
328 this->sendFrame(nullptr);
329 av_write_trailer(fFormatCtx);
330
331 sk_sp<SkData> data = fWStream->detachAsData();
332 this->reset();
333 return data;
334 }
335