Add support for errors with a range of columns
[survex.git] / src / moviemaker.cc
bloba0d824667cd658d7055c1f9f50adca1c3b6d22b5
1 //
2 // moviemaker.cc
3 //
4 // Class for writing movies from Aven.
5 //
6 // Copyright (C) 2004,2011,2012,2013,2014,2015,2016 Olly Betts
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 /* Based on output-example.c:
25 * Libavformat API example: Output a media file in any supported
26 * libavformat format. The default codecs are used.
28 * Copyright (c) 2003 Fabrice Bellard
30 * Permission is hereby granted, free of charge, to any person obtaining a copy
31 * of this software and associated documentation files (the "Software"), to deal
32 * in the Software without restriction, including without limitation the rights
33 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34 * copies of the Software, and to permit persons to whom the Software is
35 * furnished to do so, subject to the following conditions:
37 * The above copyright notice and this permission notice shall be included in
38 * all copies or substantial portions of the Software.
40 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
43 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46 * THE SOFTWARE.
49 #ifdef HAVE_CONFIG_H
50 #include <config.h>
51 #endif
53 #define __STDC_CONSTANT_MACROS
55 #include <assert.h>
56 #include <stdlib.h>
57 #include <string.h>
59 #include "moviemaker.h"
61 #ifdef WITH_LIBAV
62 extern "C" {
63 # include <libavutil/imgutils.h>
64 # include <libavutil/mathematics.h>
65 # include <libavformat/avformat.h>
66 # include <libswscale/swscale.h>
68 # ifndef AV_PKT_FLAG_KEY
69 # define AV_PKT_FLAG_KEY PKT_FLAG_KEY
70 # endif
71 # ifndef HAVE_AV_GUESS_FORMAT
72 # define av_guess_format guess_format
73 # endif
74 # ifndef HAVE_AVIO_OPEN
75 # define avio_open url_fopen
76 # endif
77 # ifndef HAVE_AVIO_CLOSE
78 # define avio_close url_fclose
79 # endif
80 # ifndef HAVE_AV_FRAME_ALLOC
81 static inline AVFrame * av_frame_alloc() {
82 return avcodec_alloc_frame();
84 # endif
85 # ifndef HAVE_AV_FRAME_FREE
86 # ifdef HAVE_AVCODEC_FREE_FRAME
87 static inline void av_frame_free(AVFrame ** frame) {
88 avcodec_free_frame(frame);
90 # else
91 static inline void av_frame_free(AVFrame ** frame) {
92 free((*frame)->data[0]);
93 free(*frame);
94 *frame = NULL;
96 # endif
97 # endif
98 # ifndef HAVE_AVCODEC_OPEN2
99 // We always pass NULL for OPTS below.
100 # define avcodec_open2(CTX, CODEC, OPTS) avcodec_open(CTX, CODEC)
101 # endif
102 # ifndef HAVE_AVFORMAT_NEW_STREAM
103 // We always pass NULL for CODEC below.
104 # define avformat_new_stream(S, CODEC) av_new_stream(S, 0)
105 # endif
106 # if !HAVE_DECL_AVMEDIA_TYPE_VIDEO
107 # define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
108 # endif
109 # if !HAVE_DECL_AV_CODEC_ID_NONE
110 # define AV_CODEC_ID_NONE CODEC_ID_NONE
111 # endif
112 # if !HAVE_DECL_AV_PIX_FMT_RGB24
113 # define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
114 # endif
115 # if !HAVE_DECL_AV_PIX_FMT_YUV420P
116 # define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
117 # endif
118 # ifndef AVIO_FLAG_WRITE
119 # define AVIO_FLAG_WRITE URL_WRONLY
120 # endif
122 enum {
123 MOVIE_NO_SUITABLE_FORMAT = 1,
124 MOVIE_AUDIO_ONLY,
125 MOVIE_FILENAME_TOO_LONG
128 # ifndef HAVE_AVCODEC_ENCODE_VIDEO2
129 const int OUTBUF_SIZE = 200000;
130 # endif
131 #endif
133 MovieMaker::MovieMaker()
134 #ifdef WITH_LIBAV
135 : oc(0), video_st(0), frame(0), outbuf(0), pixels(0), sws_ctx(0), averrno(0)
136 #endif
138 #ifdef WITH_LIBAV
139 static bool initialised_ffmpeg = false;
140 if (initialised_ffmpeg) return;
142 // FIXME: register only the codec(s) we want to use...
143 avcodec_register_all();
144 av_register_all();
146 initialised_ffmpeg = true;
147 #endif
150 #ifdef WITH_LIBAV
151 static int
152 write_packet(void *opaque, uint8_t *buf, int buf_size) {
153 FILE * fh = (FILE*)opaque;
154 size_t res = fwrite(buf, 1, buf_size, fh);
155 return res > 0 ? res : -1;
157 #endif
159 #define MAX_EXTENSION_LEN 8
161 bool MovieMaker::Open(FILE* fh, const char * ext, int width, int height)
163 #ifdef WITH_LIBAV
164 fh_to_close = fh;
166 AVOutputFormat * fmt = NULL;
167 char dummy_filename[MAX_EXTENSION_LEN + 3] = "x.";
168 if (strlen(ext) <= MAX_EXTENSION_LEN) {
169 strcpy(dummy_filename + 2, ext);
170 // Pass "x." + extension to av_guess_format() to avoid having to deal
171 // with wide character filenames.
172 fmt = av_guess_format(NULL, dummy_filename, NULL);
174 if (!fmt) {
175 // We couldn't deduce the output format from file extension so default
176 // to MPEG.
177 fmt = av_guess_format("mpeg", NULL, NULL);
178 if (!fmt) {
179 averrno = MOVIE_NO_SUITABLE_FORMAT;
180 return false;
182 strcpy(dummy_filename + 2, "mpg");
184 if (fmt->video_codec == AV_CODEC_ID_NONE) {
185 averrno = MOVIE_AUDIO_ONLY;
186 return false;
189 /* Allocate the output media context. */
190 oc = avformat_alloc_context();
191 if (!oc) {
192 averrno = AVERROR(ENOMEM);
193 return false;
195 oc->oformat = fmt;
196 strcpy(oc->filename, dummy_filename);
198 /* find the video encoder */
199 AVCodec *codec = avcodec_find_encoder(fmt->video_codec);
200 if (!codec) {
201 // FIXME : Erm - internal ffmpeg library problem?
202 averrno = AVERROR(ENOMEM);
203 return false;
206 // Add the video stream.
207 video_st = avformat_new_stream(oc, codec);
208 if (!video_st) {
209 averrno = AVERROR(ENOMEM);
210 return false;
213 // Set sample parameters.
214 AVCodecContext *c = video_st->codec;
215 c->bit_rate = 400000;
216 /* Resolution must be a multiple of two. */
217 c->width = width;
218 c->height = height;
219 /* timebase: This is the fundamental unit of time (in seconds) in terms
220 * of which frame timestamps are represented. For fixed-fps content,
221 * timebase should be 1/framerate and timestamp increments should be
222 * identical to 1. */
223 #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55, 44, 0)
224 // Old way, which now causes deprecation warnings.
225 c->time_base.den = 25; // Frames per second.
226 c->time_base.num = 1;
227 #else
228 video_st->time_base.den = 25; // Frames per second.
229 video_st->time_base.num = 1;
230 c->time_base = video_st->time_base;
231 #endif
232 c->gop_size = 12; /* emit one intra frame every twelve frames at most */
233 c->pix_fmt = AV_PIX_FMT_YUV420P;
234 c->rc_buffer_size = c->bit_rate * 4; // Enough for 4 seconds
235 c->rc_max_rate = c->bit_rate * 2;
236 // B frames are backwards predicted - they can improve compression,
237 // but may slow encoding and decoding.
238 // if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
239 // c->max_b_frames = 2;
240 // }
242 /* Some formats want stream headers to be separate. */
243 if (oc->oformat->flags & AVFMT_GLOBALHEADER)
244 c->flags |= CODEC_FLAG_GLOBAL_HEADER;
246 int retval;
247 #ifndef HAVE_AVFORMAT_WRITE_HEADER
248 // Set the output parameters (must be done even if no parameters).
249 retval = av_set_parameters(oc, NULL);
250 if (retval < 0) {
251 averrno = retval;
252 return false;
254 #endif
256 retval = avcodec_open2(c, NULL, NULL);
257 if (retval < 0) {
258 averrno = retval;
259 return false;
262 #ifndef HAVE_AVCODEC_ENCODE_VIDEO2
263 outbuf = NULL;
264 if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
265 outbuf = (unsigned char *)av_malloc(OUTBUF_SIZE);
266 if (!outbuf) {
267 averrno = AVERROR(ENOMEM);
268 return false;
271 #endif
273 /* Allocate the encoded raw picture. */
274 frame = av_frame_alloc();
275 if (!frame) {
276 averrno = AVERROR(ENOMEM);
277 return false;
279 retval = av_image_alloc(frame->data, frame->linesize,
280 c->width, c->height, c->pix_fmt, 1);
281 if (retval < 0) {
282 averrno = retval;
283 return false;
286 if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
287 // FIXME need to allocate another frame for this case if we stop
288 // hardcoding AV_PIX_FMT_YUV420P.
289 abort();
292 frame->format = c->pix_fmt;
293 frame->width = c->width;
294 frame->height = c->height;
296 pixels = (unsigned char *)av_malloc(width * height * 6);
297 if (!pixels) {
298 averrno = AVERROR(ENOMEM);
299 return false;
302 // Show the format we've ended up with (for debug purposes).
303 // av_dump_format(oc, 0, fnm, 1);
305 av_free(sws_ctx);
306 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGB24,
307 width, height, c->pix_fmt, SWS_BICUBIC,
308 NULL, NULL, NULL);
309 if (sws_ctx == NULL) {
310 fprintf(stderr, "Cannot initialize the conversion context!\n");
311 averrno = AVERROR(ENOMEM);
312 return false;
315 if (!(fmt->flags & AVFMT_NOFILE)) {
316 const int buf_size = 8192;
317 void * buf = av_malloc(buf_size);
318 oc->pb = avio_alloc_context(static_cast<uint8_t*>(buf), buf_size, 1,
319 fh, NULL, write_packet, NULL);
320 if (!oc->pb) {
321 averrno = AVERROR(ENOMEM);
322 return false;
326 // Write the stream header, if any.
327 #ifdef HAVE_AVFORMAT_WRITE_HEADER
328 retval = avformat_write_header(oc, NULL);
329 #else
330 retval = av_write_header(oc);
331 #endif
332 if (retval < 0) {
333 averrno = retval;
334 return false;
337 averrno = 0;
338 return true;
339 #else
340 (void)fh;
341 (void)ext;
342 (void)width;
343 (void)height;
344 return false;
345 #endif
348 unsigned char * MovieMaker::GetBuffer() const {
349 #ifdef WITH_LIBAV
350 AVCodecContext * c = video_st->codec;
351 return pixels + c->height * c->width * 3;
352 #else
353 return NULL;
354 #endif
357 int MovieMaker::GetWidth() const {
358 #ifdef WITH_LIBAV
359 assert(video_st);
360 AVCodecContext *c = video_st->codec;
361 return c->width;
362 #else
363 return 0;
364 #endif
367 int MovieMaker::GetHeight() const {
368 #ifdef WITH_LIBAV
369 assert(video_st);
370 AVCodecContext *c = video_st->codec;
371 return c->height;
372 #else
373 return 0;
374 #endif
377 bool MovieMaker::AddFrame()
379 #ifdef WITH_LIBAV
380 AVCodecContext * c = video_st->codec;
382 if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
383 // FIXME convert...
384 abort();
387 int len = 3 * c->width;
389 // Flip image vertically
390 int h = c->height;
391 unsigned char * src = pixels + h * len;
392 unsigned char * dest = src - len;
393 while (h--) {
394 memcpy(dest, src, len);
395 src += len;
396 dest -= len;
399 sws_scale(sws_ctx, &pixels, &len, 0, c->height, frame->data, frame->linesize);
401 if (oc->oformat->flags & AVFMT_RAWPICTURE) {
402 abort();
405 // Encode this frame.
406 #ifdef HAVE_AVCODEC_ENCODE_VIDEO2
407 AVPacket pkt;
408 int got_packet;
409 av_init_packet(&pkt);
410 pkt.data = NULL;
412 int ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
413 if (ret < 0) {
414 averrno = ret;
415 return false;
417 if (got_packet && pkt.size) {
418 // Write the compressed frame to the media file.
419 if (pkt.pts != int64_t(AV_NOPTS_VALUE)) {
420 pkt.pts = av_rescale_q(pkt.pts,
421 c->time_base, video_st->time_base);
423 if (pkt.dts != int64_t(AV_NOPTS_VALUE)) {
424 pkt.dts = av_rescale_q(pkt.dts,
425 c->time_base, video_st->time_base);
427 pkt.stream_index = video_st->index;
429 /* Write the compressed frame to the media file. */
430 ret = av_interleaved_write_frame(oc, &pkt);
431 if (ret < 0) {
432 averrno = ret;
433 return false;
436 #else
437 out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, frame);
438 // outsize == 0 means that this frame has been buffered, so there's nothing
439 // to write yet.
440 if (out_size) {
441 // Write the compressed frame to the media file.
442 AVPacket pkt;
443 av_init_packet(&pkt);
445 if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
446 pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
447 if (c->coded_frame->key_frame)
448 pkt.flags |= AV_PKT_FLAG_KEY;
449 pkt.stream_index = video_st->index;
450 pkt.data = outbuf;
451 pkt.size = out_size;
453 /* Write the compressed frame to the media file. */
454 int ret = av_interleaved_write_frame(oc, &pkt);
455 if (ret < 0) {
456 averrno = ret;
457 return false;
460 #endif
461 #endif
462 return true;
465 bool
466 MovieMaker::Close()
468 #ifdef WITH_LIBAV
469 if (video_st && averrno == 0) {
470 // No more frames to compress. The codec may have a few frames
471 // buffered if we're using B frames, so write those too.
472 AVCodecContext * c = video_st->codec;
474 #ifdef HAVE_AVCODEC_ENCODE_VIDEO2
475 while (1) {
476 AVPacket pkt;
477 int got_packet;
478 av_init_packet(&pkt);
479 pkt.data = NULL;
480 pkt.size = 0;
482 int ret = avcodec_encode_video2(c, &pkt, NULL, &got_packet);
483 if (ret < 0) {
484 release();
485 averrno = ret;
486 return false;
488 if (!got_packet) break;
489 if (!pkt.size) continue;
491 // Write the compressed frame to the media file.
492 if (pkt.pts != int64_t(AV_NOPTS_VALUE)) {
493 pkt.pts = av_rescale_q(pkt.pts,
494 c->time_base, video_st->time_base);
496 if (pkt.dts != int64_t(AV_NOPTS_VALUE)) {
497 pkt.dts = av_rescale_q(pkt.dts,
498 c->time_base, video_st->time_base);
500 pkt.stream_index = video_st->index;
502 /* Write the compressed frame to the media file. */
503 ret = av_interleaved_write_frame(oc, &pkt);
504 if (ret < 0) {
505 release();
506 averrno = ret;
507 return false;
510 #else
511 while (out_size) {
512 out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, NULL);
513 if (out_size) {
514 // Write the compressed frame to the media file.
515 AVPacket pkt;
516 av_init_packet(&pkt);
518 if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
519 pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
520 if (c->coded_frame->key_frame)
521 pkt.flags |= AV_PKT_FLAG_KEY;
522 pkt.stream_index = video_st->index;
523 pkt.data = outbuf;
524 pkt.size = out_size;
526 /* write the compressed frame in the media file */
527 int ret = av_interleaved_write_frame(oc, &pkt);
528 if (ret < 0) {
529 release();
530 averrno = ret;
531 return false;
535 #endif
537 av_write_trailer(oc);
540 release();
541 #endif
542 return true;
545 #ifdef WITH_LIBAV
546 void
547 MovieMaker::release()
549 if (video_st) {
550 // Close codec.
551 avcodec_close(video_st->codec);
552 video_st = NULL;
555 if (frame) {
556 av_frame_free(&frame);
558 av_free(pixels);
559 pixels = NULL;
560 av_free(outbuf);
561 outbuf = NULL;
562 av_free(sws_ctx);
563 sws_ctx = NULL;
565 if (oc) {
566 // Free the streams.
567 for (size_t i = 0; i < oc->nb_streams; ++i) {
568 av_freep(&oc->streams[i]->codec);
569 av_freep(&oc->streams[i]);
572 if (!(oc->oformat->flags & AVFMT_NOFILE)) {
573 // Release the AVIOContext.
574 av_free(oc->pb);
577 // Free the stream.
578 av_free(oc);
579 oc = NULL;
581 if (fh_to_close) {
582 fclose(fh_to_close);
583 fh_to_close = NULL;
586 #endif
588 MovieMaker::~MovieMaker()
590 #ifdef WITH_LIBAV
591 release();
592 #endif
595 const char *
596 MovieMaker::get_error_string() const
598 #ifdef WITH_LIBAV
599 switch (averrno) {
600 case AVERROR(EIO):
601 return "I/O error";
602 case AVERROR(EDOM):
603 return "Number syntax expected in filename";
604 case AVERROR_INVALIDDATA:
605 /* same as AVERROR_UNKNOWN: return "unknown error"; */
606 return "invalid data found";
607 case AVERROR(ENOMEM):
608 return "not enough memory";
609 case AVERROR(EILSEQ):
610 return "unknown format";
611 case AVERROR(ENOSYS):
612 return "Operation not supported";
613 case AVERROR(ENOENT):
614 return "No such file or directory";
615 case AVERROR_EOF:
616 return "End of file";
617 case AVERROR_PATCHWELCOME:
618 return "Not implemented in FFmpeg";
619 case 0:
620 return "No error";
621 case MOVIE_NO_SUITABLE_FORMAT:
622 return "Couldn't find a suitable output format";
623 case MOVIE_AUDIO_ONLY:
624 return "Audio-only format specified";
625 case MOVIE_FILENAME_TOO_LONG:
626 return "Filename too long";
628 #endif
629 return "Unknown error";