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