• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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