Sort out sphinx rules
[survex.git] / src / moviemaker-legacy.cc
blob0c4cd0d8e7b8815140f1eefa00a117b665a08de8
1 //
2 // moviemaker.cc
3 //
4 // Class for writing movies from Aven for old FFmpeg
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 #include <config.h>
51 #define __STDC_CONSTANT_MACROS
53 #include <assert.h>
54 #include <stdlib.h>
55 #include <string.h>
57 #include "moviemaker.h"
59 #ifdef WITH_FFMPEG
60 extern "C" {
61 # include <libavutil/imgutils.h>
62 # include <libavutil/mathematics.h>
63 # include <libavformat/avformat.h>
64 # include <libswscale/swscale.h>
66 # ifndef AV_PKT_FLAG_KEY
67 # define AV_PKT_FLAG_KEY PKT_FLAG_KEY
68 # endif
69 # ifndef HAVE_AV_GUESS_FORMAT
70 # define av_guess_format guess_format
71 # endif
72 # ifndef HAVE_AVIO_OPEN
73 # define avio_open url_fopen
74 # endif
75 # ifndef HAVE_AVIO_CLOSE
76 # define avio_close url_fclose
77 # endif
78 # ifndef HAVE_AV_FRAME_ALLOC
79 static inline AVFrame * av_frame_alloc() {
80 return avcodec_alloc_frame();
82 # endif
83 # ifndef HAVE_AV_FRAME_FREE
84 # ifdef HAVE_AVCODEC_FREE_FRAME
85 static inline void av_frame_free(AVFrame ** frame) {
86 avcodec_free_frame(frame);
88 # else
89 static inline void av_frame_free(AVFrame ** frame) {
90 free((*frame)->data[0]);
91 free(*frame);
92 *frame = NULL;
94 # endif
95 # endif
96 # ifndef HAVE_AVCODEC_OPEN2
97 // We always pass NULL for OPTS below.
98 # define avcodec_open2(CTX, CODEC, OPTS) avcodec_open(CTX, CODEC)
99 # endif
100 # ifndef HAVE_AVFORMAT_NEW_STREAM
101 // We always pass NULL for CODEC below.
102 # define avformat_new_stream(S, CODEC) av_new_stream(S, 0)
103 # endif
104 # if !HAVE_DECL_AVMEDIA_TYPE_VIDEO
105 # define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
106 # endif
107 # if !HAVE_DECL_AV_CODEC_ID_NONE
108 # define AV_CODEC_ID_NONE CODEC_ID_NONE
109 # endif
110 # if !HAVE_DECL_AV_PIX_FMT_RGB24
111 # define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
112 # endif
113 # if !HAVE_DECL_AV_PIX_FMT_YUV420P
114 # define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
115 # endif
116 # ifndef AVIO_FLAG_WRITE
117 # define AVIO_FLAG_WRITE URL_WRONLY
118 # endif
120 enum {
121 MOVIE_NO_SUITABLE_FORMAT = 1,
122 MOVIE_AUDIO_ONLY,
123 MOVIE_FILENAME_TOO_LONG
126 # ifndef HAVE_AVCODEC_ENCODE_VIDEO2
127 const int OUTBUF_SIZE = 200000;
128 # endif
129 #endif
131 MovieMaker::MovieMaker()
132 #ifdef WITH_FFMPEG
133 : oc(0), video_st(0), frame(0), outbuf(0), pixels(0), sws_ctx(0), averrno(0)
134 #endif
136 #ifdef WITH_FFMPEG
137 static bool initialised_ffmpeg = false;
138 if (initialised_ffmpeg) return;
140 // FIXME: register only the codec(s) we want to use...
141 avcodec_register_all();
142 av_register_all();
144 initialised_ffmpeg = true;
145 #endif
148 #ifdef WITH_FFMPEG
149 static int
150 write_packet(void *opaque, uint8_t *buf, int buf_size) {
151 FILE * fh = (FILE*)opaque;
152 size_t res = fwrite(buf, 1, buf_size, fh);
153 return res > 0 ? res : -1;
156 static int64_t
157 seek_stream(void *opaque, int64_t offset, int whence) {
158 FILE * fh = (FILE*)opaque;
159 return fseek(fh, offset, whence);
161 #endif
163 #define MAX_EXTENSION_LEN 8
165 bool MovieMaker::Open(FILE* fh, const char * ext, int width, int height)
167 #ifdef WITH_FFMPEG
168 fh_to_close = fh;
170 AVOutputFormat * fmt = NULL;
171 char dummy_filename[MAX_EXTENSION_LEN + 3] = "x.";
172 if (strlen(ext) <= MAX_EXTENSION_LEN) {
173 strcpy(dummy_filename + 2, ext);
174 // Pass "x." + extension to av_guess_format() to avoid having to deal
175 // with wide character filenames.
176 fmt = av_guess_format(NULL, dummy_filename, NULL);
178 if (!fmt) {
179 // We couldn't deduce the output format from file extension so default
180 // to MPEG.
181 fmt = av_guess_format("mpeg", NULL, NULL);
182 if (!fmt) {
183 averrno = MOVIE_NO_SUITABLE_FORMAT;
184 return false;
186 strcpy(dummy_filename + 2, "mpg");
188 if (fmt->video_codec == AV_CODEC_ID_NONE) {
189 averrno = MOVIE_AUDIO_ONLY;
190 return false;
193 /* Allocate the output media context. */
194 oc = avformat_alloc_context();
195 if (!oc) {
196 averrno = AVERROR(ENOMEM);
197 return false;
199 oc->oformat = fmt;
200 strcpy(oc->filename, dummy_filename);
202 /* find the video encoder */
203 AVCodec *codec = avcodec_find_encoder(fmt->video_codec);
204 if (!codec) {
205 // FIXME : Erm - internal ffmpeg library problem?
206 averrno = AVERROR(ENOMEM);
207 return false;
210 // Add the video stream.
211 video_st = avformat_new_stream(oc, codec);
212 if (!video_st) {
213 averrno = AVERROR(ENOMEM);
214 return false;
217 // Set sample parameters.
218 AVCodecContext *c = video_st->codec;
219 c->bit_rate = 400000;
220 /* Resolution must be a multiple of two. */
221 c->width = width;
222 c->height = height;
223 /* timebase: This is the fundamental unit of time (in seconds) in terms
224 * of which frame timestamps are represented. For fixed-fps content,
225 * timebase should be 1/framerate and timestamp increments should be
226 * identical to 1. */
227 #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55, 44, 0)
228 // Old way, which now causes deprecation warnings.
229 c->time_base.den = 25; // Frames per second.
230 c->time_base.num = 1;
231 #else
232 video_st->time_base.den = 25; // Frames per second.
233 video_st->time_base.num = 1;
234 c->time_base = video_st->time_base;
235 #endif
236 c->gop_size = 12; /* emit one intra frame every twelve frames at most */
237 c->pix_fmt = AV_PIX_FMT_YUV420P;
238 c->rc_buffer_size = c->bit_rate * 4; // Enough for 4 seconds
239 c->rc_max_rate = c->bit_rate * 2;
240 // B frames are backwards predicted - they can improve compression,
241 // but may slow encoding and decoding.
242 // if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
243 // c->max_b_frames = 2;
244 // }
246 /* Some formats want stream headers to be separate. */
247 if (oc->oformat->flags & AVFMT_GLOBALHEADER)
248 c->flags |= CODEC_FLAG_GLOBAL_HEADER;
250 int retval;
251 #ifndef HAVE_AVFORMAT_WRITE_HEADER
252 // Set the output parameters (must be done even if no parameters).
253 retval = av_set_parameters(oc, NULL);
254 if (retval < 0) {
255 averrno = retval;
256 return false;
258 #endif
260 retval = avcodec_open2(c, NULL, NULL);
261 if (retval < 0) {
262 averrno = retval;
263 return false;
266 #ifndef HAVE_AVCODEC_ENCODE_VIDEO2
267 outbuf = NULL;
268 if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
269 outbuf = (unsigned char *)av_malloc(OUTBUF_SIZE);
270 if (!outbuf) {
271 averrno = AVERROR(ENOMEM);
272 return false;
275 #endif
277 /* Allocate the encoded raw picture. */
278 frame = av_frame_alloc();
279 if (!frame) {
280 averrno = AVERROR(ENOMEM);
281 return false;
283 retval = av_image_alloc(frame->data, frame->linesize,
284 c->width, c->height, c->pix_fmt, 1);
285 if (retval < 0) {
286 averrno = retval;
287 return false;
290 if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
291 // FIXME need to allocate another frame for this case if we stop
292 // hardcoding AV_PIX_FMT_YUV420P.
293 abort();
296 frame->format = c->pix_fmt;
297 frame->width = c->width;
298 frame->height = c->height;
300 pixels = (unsigned char *)av_malloc(width * height * 6);
301 if (!pixels) {
302 averrno = AVERROR(ENOMEM);
303 return false;
306 // Show the format we've ended up with (for debug purposes).
307 // av_dump_format(oc, 0, fnm, 1);
309 av_free(sws_ctx);
310 sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGB24,
311 width, height, c->pix_fmt, SWS_BICUBIC,
312 NULL, NULL, NULL);
313 if (sws_ctx == NULL) {
314 fprintf(stderr, "Cannot initialize the conversion context!\n");
315 averrno = AVERROR(ENOMEM);
316 return false;
319 if (!(fmt->flags & AVFMT_NOFILE)) {
320 const int buf_size = 8192;
321 void * buf = av_malloc(buf_size);
322 oc->pb = avio_alloc_context(static_cast<uint8_t*>(buf), buf_size, 1,
323 fh, NULL, write_packet, seek_stream);
324 if (!oc->pb) {
325 averrno = AVERROR(ENOMEM);
326 return false;
330 // Write the stream header, if any.
331 #ifdef HAVE_AVFORMAT_WRITE_HEADER
332 retval = avformat_write_header(oc, NULL);
333 #else
334 retval = av_write_header(oc);
335 #endif
336 if (retval < 0) {
337 averrno = retval;
338 return false;
341 averrno = 0;
342 return true;
343 #else
344 (void)fh;
345 (void)ext;
346 (void)width;
347 (void)height;
348 return false;
349 #endif
352 unsigned char * MovieMaker::GetBuffer() const {
353 #ifdef WITH_FFMPEG
354 return pixels + GetWidth() * GetHeight() * 3;
355 #else
356 return NULL;
357 #endif
360 int MovieMaker::GetWidth() const {
361 #ifdef WITH_FFMPEG
362 assert(video_st);
363 AVCodecContext *c = video_st->codec;
364 return c->width;
365 #else
366 return 0;
367 #endif
370 int MovieMaker::GetHeight() const {
371 #ifdef WITH_FFMPEG
372 assert(video_st);
373 AVCodecContext *c = video_st->codec;
374 return c->height;
375 #else
376 return 0;
377 #endif
380 bool MovieMaker::AddFrame()
382 #ifdef WITH_FFMPEG
383 AVCodecContext * c = video_st->codec;
385 if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
386 // FIXME convert...
387 abort();
390 int len = 3 * c->width;
392 // Flip image vertically
393 int h = c->height;
394 unsigned char * src = pixels + h * len;
395 unsigned char * dest = src - len;
396 while (h--) {
397 memcpy(dest, src, len);
398 src += len;
399 dest -= len;
402 sws_scale(sws_ctx, &pixels, &len, 0, c->height, frame->data, frame->linesize);
404 if (oc->oformat->flags & AVFMT_RAWPICTURE) {
405 abort();
408 // Encode this frame.
409 #ifdef HAVE_AVCODEC_ENCODE_VIDEO2
410 AVPacket pkt;
411 int got_packet;
412 av_init_packet(&pkt);
413 pkt.data = NULL;
415 int ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
416 if (ret < 0) {
417 averrno = ret;
418 return false;
420 if (got_packet && pkt.size) {
421 // Write the compressed frame to the media file.
422 if (pkt.pts != int64_t(AV_NOPTS_VALUE)) {
423 pkt.pts = av_rescale_q(pkt.pts,
424 c->time_base, video_st->time_base);
426 if (pkt.dts != int64_t(AV_NOPTS_VALUE)) {
427 pkt.dts = av_rescale_q(pkt.dts,
428 c->time_base, video_st->time_base);
430 pkt.stream_index = video_st->index;
432 /* Write the compressed frame to the media file. */
433 ret = av_interleaved_write_frame(oc, &pkt);
434 if (ret < 0) {
435 averrno = ret;
436 return false;
439 #else
440 out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, frame);
441 // outsize == 0 means that this frame has been buffered, so there's nothing
442 // to write yet.
443 if (out_size) {
444 // Write the compressed frame to the media file.
445 AVPacket pkt;
446 av_init_packet(&pkt);
448 if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
449 pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
450 if (c->coded_frame->key_frame)
451 pkt.flags |= AV_PKT_FLAG_KEY;
452 pkt.stream_index = video_st->index;
453 pkt.data = outbuf;
454 pkt.size = out_size;
456 /* Write the compressed frame to the media file. */
457 int ret = av_interleaved_write_frame(oc, &pkt);
458 if (ret < 0) {
459 averrno = ret;
460 return false;
463 #endif
464 #endif
465 return true;
468 bool
469 MovieMaker::Close()
471 #ifdef WITH_FFMPEG
472 if (video_st && averrno == 0) {
473 // No more frames to compress. The codec may have a few frames
474 // buffered if we're using B frames, so write those too.
475 AVCodecContext * c = video_st->codec;
477 #ifdef HAVE_AVCODEC_ENCODE_VIDEO2
478 while (1) {
479 AVPacket pkt;
480 int got_packet;
481 av_init_packet(&pkt);
482 pkt.data = NULL;
483 pkt.size = 0;
485 int ret = avcodec_encode_video2(c, &pkt, NULL, &got_packet);
486 if (ret < 0) {
487 release();
488 averrno = ret;
489 return false;
491 if (!got_packet) break;
492 if (!pkt.size) continue;
494 // Write the compressed frame to the media file.
495 if (pkt.pts != int64_t(AV_NOPTS_VALUE)) {
496 pkt.pts = av_rescale_q(pkt.pts,
497 c->time_base, video_st->time_base);
499 if (pkt.dts != int64_t(AV_NOPTS_VALUE)) {
500 pkt.dts = av_rescale_q(pkt.dts,
501 c->time_base, video_st->time_base);
503 pkt.stream_index = video_st->index;
505 /* Write the compressed frame to the media file. */
506 ret = av_interleaved_write_frame(oc, &pkt);
507 if (ret < 0) {
508 release();
509 averrno = ret;
510 return false;
513 #else
514 while (out_size) {
515 out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, NULL);
516 if (out_size) {
517 // Write the compressed frame to the media file.
518 AVPacket pkt;
519 av_init_packet(&pkt);
521 if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
522 pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
523 if (c->coded_frame->key_frame)
524 pkt.flags |= AV_PKT_FLAG_KEY;
525 pkt.stream_index = video_st->index;
526 pkt.data = outbuf;
527 pkt.size = out_size;
529 /* write the compressed frame in the media file */
530 int ret = av_interleaved_write_frame(oc, &pkt);
531 if (ret < 0) {
532 release();
533 averrno = ret;
534 return false;
538 #endif
540 av_write_trailer(oc);
543 release();
544 #endif
545 return true;
548 #ifdef WITH_FFMPEG
549 void
550 MovieMaker::release()
552 if (video_st) {
553 // Close codec.
554 avcodec_close(video_st->codec);
555 video_st = NULL;
558 if (frame) {
559 av_frame_free(&frame);
561 av_free(pixels);
562 pixels = NULL;
563 av_free(outbuf);
564 outbuf = NULL;
565 av_free(sws_ctx);
566 sws_ctx = NULL;
568 if (oc) {
569 // Free the streams.
570 for (size_t i = 0; i < oc->nb_streams; ++i) {
571 av_freep(&oc->streams[i]->codec);
572 av_freep(&oc->streams[i]);
575 if (!(oc->oformat->flags & AVFMT_NOFILE)) {
576 // Release the AVIOContext.
577 av_free(oc->pb);
580 // Free the stream.
581 av_free(oc);
582 oc = NULL;
584 if (fh_to_close) {
585 fclose(fh_to_close);
586 fh_to_close = NULL;
589 #endif
591 MovieMaker::~MovieMaker()
593 #ifdef WITH_FFMPEG
594 release();
595 #endif
598 const char *
599 MovieMaker::get_error_string() const
601 #ifdef WITH_FFMPEG
602 switch (averrno) {
603 case AVERROR(EIO):
604 return "I/O error";
605 case AVERROR(EDOM):
606 return "Number syntax expected in filename";
607 case AVERROR_INVALIDDATA:
608 /* same as AVERROR_UNKNOWN: return "unknown error"; */
609 return "invalid data found";
610 case AVERROR(ENOMEM):
611 return "not enough memory";
612 case AVERROR(EILSEQ):
613 return "unknown format";
614 case AVERROR(ENOSYS):
615 return "Operation not supported";
616 case AVERROR(ENOENT):
617 return "No such file or directory";
618 case AVERROR_EOF:
619 return "End of file";
620 case AVERROR_PATCHWELCOME:
621 return "Not implemented in FFmpeg";
622 case 0:
623 return "No error";
624 case MOVIE_NO_SUITABLE_FORMAT:
625 return "Couldn't find a suitable output format";
626 case MOVIE_AUDIO_ONLY:
627 return "Audio-only format specified";
628 case MOVIE_FILENAME_TOO_LONG:
629 return "Filename too long";
631 #endif
632 return "Unknown error";