source: git/src/moviemaker-legacy.cc

walls-data
Last change on this file was 4c83f84, checked in by Olly Betts <olly@…>, 5 days ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

  • Property mode set to 100644
File size: 15.8 KB
Line 
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.
12//
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.
17//
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
21//
22
23/* Based on output-example.c:
24 *
25 * Libavformat API example: Output a media file in any supported
26 * libavformat format. The default codecs are used.
27 *
28 * Copyright (c) 2003 Fabrice Bellard
29 *
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:
36 *
37 * The above copyright notice and this permission notice shall be included in
38 * all copies or substantial portions of the Software.
39 *
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.
47 */
48
49#include <config.h>
50
51#define __STDC_CONSTANT_MACROS
52
53#include <assert.h>
54#include <stdlib.h>
55#include <string.h>
56
57#include "moviemaker.h"
58
59#ifdef WITH_FFMPEG
60extern "C" {
61# include <libavutil/imgutils.h>
62# include <libavutil/mathematics.h>
63# include <libavformat/avformat.h>
64# include <libswscale/swscale.h>
65}
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
79static inline AVFrame * av_frame_alloc() {
80    return avcodec_alloc_frame();
81}
82# endif
83# ifndef HAVE_AV_FRAME_FREE
84#  ifdef HAVE_AVCODEC_FREE_FRAME
85static inline void av_frame_free(AVFrame ** frame) {
86    avcodec_free_frame(frame);
87}
88#  else
89static inline void av_frame_free(AVFrame ** frame) {
90    free((*frame)->data[0]);
91    free(*frame);
92    *frame = NULL;
93}
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
119
120enum {
121    MOVIE_NO_SUITABLE_FORMAT = 1,
122    MOVIE_AUDIO_ONLY,
123    MOVIE_FILENAME_TOO_LONG
124};
125
126# ifndef HAVE_AVCODEC_ENCODE_VIDEO2
127const int OUTBUF_SIZE = 200000;
128# endif
129#endif
130
131MovieMaker::MovieMaker()
132#ifdef WITH_FFMPEG
133    : oc(0), video_st(0), frame(0), outbuf(0), pixels(0), sws_ctx(0), averrno(0)
134#endif
135{
136#ifdef WITH_FFMPEG
137    static bool initialised_ffmpeg = false;
138    if (initialised_ffmpeg) return;
139
140    // FIXME: register only the codec(s) we want to use...
141    avcodec_register_all();
142    av_register_all();
143
144    initialised_ffmpeg = true;
145#endif
146}
147
148#ifdef WITH_FFMPEG
149static int
150write_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;
154}
155
156static int64_t
157seek_stream(void *opaque, int64_t offset, int whence) {
158    FILE * fh = (FILE*)opaque;
159    return fseek(fh, offset, whence);
160}
161#endif
162
163#define MAX_EXTENSION_LEN 8
164
165bool MovieMaker::Open(FILE* fh, const char * ext, int width, int height)
166{
167#ifdef WITH_FFMPEG
168    fh_to_close = fh;
169
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);
177    }
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;
185        }
186        strcpy(dummy_filename + 2, "mpg");
187    }
188    if (fmt->video_codec == AV_CODEC_ID_NONE) {
189        averrno = MOVIE_AUDIO_ONLY;
190        return false;
191    }
192
193    /* Allocate the output media context. */
194    oc = avformat_alloc_context();
195    if (!oc) {
196        averrno = AVERROR(ENOMEM);
197        return false;
198    }
199    oc->oformat = fmt;
200    strcpy(oc->filename, dummy_filename);
201
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;
208    }
209
210    // Add the video stream.
211    video_st = avformat_new_stream(oc, codec);
212    if (!video_st) {
213        averrno = AVERROR(ENOMEM);
214        return false;
215    }
216
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    // }
245
246    /* Some formats want stream headers to be separate. */
247    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
248        c->flags |= CODEC_FLAG_GLOBAL_HEADER;
249
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;
257    }
258#endif
259
260    retval = avcodec_open2(c, NULL, NULL);
261    if (retval < 0) {
262        averrno = retval;
263        return false;
264    }
265
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;
273        }
274    }
275#endif
276
277    /* Allocate the encoded raw picture. */
278    frame = av_frame_alloc();
279    if (!frame) {
280        averrno = AVERROR(ENOMEM);
281        return false;
282    }
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;
288    }
289
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();
294    }
295
296    frame->format = c->pix_fmt;
297    frame->width = c->width;
298    frame->height = c->height;
299
300    pixels = (unsigned char *)av_malloc(width * height * 6);
301    if (!pixels) {
302        averrno = AVERROR(ENOMEM);
303        return false;
304    }
305
306    // Show the format we've ended up with (for debug purposes).
307    // av_dump_format(oc, 0, fnm, 1);
308
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;
317    }
318
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;
327        }
328    }
329
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;
339    }
340
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
350}
351
352unsigned char * MovieMaker::GetBuffer() const {
353#ifdef WITH_FFMPEG
354    return pixels + GetWidth() * GetHeight() * 3;
355#else
356    return NULL;
357#endif
358}
359
360int 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
368}
369
370int 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
378}
379
380bool MovieMaker::AddFrame()
381{
382#ifdef WITH_FFMPEG
383    AVCodecContext * c = video_st->codec;
384
385    if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
386        // FIXME convert...
387        abort();
388    }
389
390    int len = 3 * c->width;
391    {
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;
400        }
401    }
402    sws_scale(sws_ctx, &pixels, &len, 0, c->height, frame->data, frame->linesize);
403
404    if (oc->oformat->flags & AVFMT_RAWPICTURE) {
405        abort();
406    }
407
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;
414
415    int ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
416    if (ret < 0) {
417        averrno = ret;
418        return false;
419    }
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);
425        }
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);
429        }
430        pkt.stream_index = video_st->index;
431
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;
437        }
438    }
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);
447
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;
455
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;
461        }
462    }
463#endif
464#endif
465    return true;
466}
467
468bool
469MovieMaker::Close()
470{
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;
476
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;
484
485            int ret = avcodec_encode_video2(c, &pkt, NULL, &got_packet);
486            if (ret < 0) {
487                release();
488                averrno = ret;
489                return false;
490            }
491            if (!got_packet) break;
492            if (!pkt.size) continue;
493
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);
498            }
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);
502            }
503            pkt.stream_index = video_st->index;
504
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;
511            }
512        }
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);
520
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;
528
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;
535                }
536            }
537        }
538#endif
539
540        av_write_trailer(oc);
541    }
542
543    release();
544#endif
545    return true;
546}
547
548#ifdef WITH_FFMPEG
549void
550MovieMaker::release()
551{
552    if (video_st) {
553        // Close codec.
554        avcodec_close(video_st->codec);
555        video_st = NULL;
556    }
557
558    if (frame) {
559        av_frame_free(&frame);
560    }
561    av_free(pixels);
562    pixels = NULL;
563    av_free(outbuf);
564    outbuf = NULL;
565    av_free(sws_ctx);
566    sws_ctx = NULL;
567
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]);
573        }
574
575        if (!(oc->oformat->flags & AVFMT_NOFILE)) {
576            // Release the AVIOContext.
577            av_free(oc->pb);
578        }
579
580        // Free the stream.
581        av_free(oc);
582        oc = NULL;
583    }
584    if (fh_to_close) {
585        fclose(fh_to_close);
586        fh_to_close = NULL;
587    }
588}
589#endif
590
591MovieMaker::~MovieMaker()
592{
593#ifdef WITH_FFMPEG
594    release();
595#endif
596}
597
598const char *
599MovieMaker::get_error_string() const
600{
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";
630    }
631#endif
632    return "Unknown error";
633}
Note: See TracBrowser for help on using the repository browser.