source: git/src/moviemaker.cc @ 763efe6

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 763efe6 was 98fd937, checked in by Olly Betts <olly@…>, 11 years ago

src/gfxcore.cc,src/moviemaker.cc,src/moviemaker.h: Improve reporting
of errors during the process of exporting a movie.

  • Property mode set to 100644
File size: 14.2 KB
Line 
1//
2//  moviemaker.cc
3//
4//  Class for writing movies from Aven.
5//
6//  Copyright (C) 2004,2011,2012,2013 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#ifdef HAVE_CONFIG_H
50#include <config.h>
51#endif
52
53#define __STDC_CONSTANT_MACROS
54
55#include <assert.h>
56#include <stdlib.h>
57#include <string.h>
58
59#include "moviemaker.h"
60
61#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
62extern "C" {
63# include <libavutil/mathematics.h>
64# include <libavformat/avformat.h>
65# include <libswscale/swscale.h>
66}
67# ifndef AV_PKT_FLAG_KEY
68#  define AV_PKT_FLAG_KEY PKT_FLAG_KEY
69# endif
70# ifndef HAVE_AV_GUESS_FORMAT
71#  define av_guess_format guess_format
72# endif
73# ifndef HAVE_AVIO_OPEN
74#  define avio_open url_fopen
75# endif
76# ifndef HAVE_AVIO_CLOSE
77#  define avio_close url_fclose
78# endif
79# ifndef HAVE_AVCODEC_OPEN2
80// We always pass NULL for OPTS below.
81#  define avcodec_open2(CTX, CODEC, OPTS) avcodec_open(CTX, CODEC)
82# endif
83# ifndef HAVE_AVFORMAT_NEW_STREAM
84// We always pass NULL for CODEC below.
85#  define avformat_new_stream(S, CODEC) av_new_stream(S, 0)
86# endif
87# if !HAVE_DECL_AVMEDIA_TYPE_VIDEO
88#  define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
89# endif
90# if !HAVE_DECL_AVCODEC_ID_NONE
91#  define AVCODEC_ID_NONE CODEC_ID_NONE
92# endif
93# if !HAVE_DECL_AV_PIX_FMT_YUV420P
94#  define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
95# endif
96# ifndef AVIO_FLAG_WRITE
97#  define AVIO_FLAG_WRITE URL_WRONLY
98# endif
99#endif
100
101enum {
102    MOVIE_NO_SUITABLE_FORMAT = 1,
103    MOVIE_AUDIO_ONLY,
104    MOVIE_FILENAME_TOO_LONG,
105    MOVIE_NOT_ENABLED
106};
107
108const int OUTBUF_SIZE = 200000;
109
110MovieMaker::MovieMaker()
111    : oc(0), video_st(0), frame(0), outbuf(0), pixels(0), sws_ctx(0), averrno(0)
112{
113#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
114    static bool initialised_ffmpeg = false;
115    if (initialised_ffmpeg) return;
116
117    // FIXME: register only the codec(s) we want to use...
118    avcodec_register_all();
119    av_register_all();
120
121    initialised_ffmpeg = true;
122#endif
123}
124
125bool MovieMaker::Open(const char *fnm, int width, int height)
126{
127#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
128    AVOutputFormat * fmt = av_guess_format(NULL, fnm, NULL);
129    if (!fmt) {
130        // We couldn't deduce the output format from file extension so default
131        // to MPEG.
132        fmt = av_guess_format("mpeg", NULL, NULL);
133        if (!fmt) {
134            averrno = MOVIE_NO_SUITABLE_FORMAT;
135            return false;
136        }
137    }
138    if (fmt->video_codec == AVCODEC_ID_NONE) {
139        averrno = MOVIE_AUDIO_ONLY;
140        return false;
141    }
142
143    /* Allocate the output media context. */
144    oc = avformat_alloc_context();
145    if (!oc) {
146        averrno = AVERROR(ENOMEM);
147        return false;
148    }
149    oc->oformat = fmt;
150    if (strlen(fnm) >= sizeof(oc->filename)) {
151        averrno = MOVIE_FILENAME_TOO_LONG;
152        return false;
153    }
154    strcpy(oc->filename, fnm);
155
156    /* find the video encoder */
157    AVCodec *codec = avcodec_find_encoder(fmt->video_codec);
158    if (!codec) {
159        // FIXME : Erm - internal ffmpeg library problem?
160        averrno = AVERROR(ENOMEM);
161        return false;
162    }
163
164    // Add the video stream.
165    video_st = avformat_new_stream(oc, codec);
166    if (!video_st) {
167        averrno = AVERROR(ENOMEM);
168        return false;
169    }
170
171    // Set sample parameters.
172    AVCodecContext *c = video_st->codec;
173    c->bit_rate = 400000;
174    /* Resolution must be a multiple of two. */
175    c->width = width;
176    c->height = height;
177    /* timebase: This is the fundamental unit of time (in seconds) in terms
178     * of which frame timestamps are represented. For fixed-fps content,
179     * timebase should be 1/framerate and timestamp increments should be
180     * identical to 1. */
181    c->time_base.den = 25; // Frames per second.
182    c->time_base.num = 1;
183    c->gop_size = 12; /* emit one intra frame every twelve frames at most */
184    c->pix_fmt = AV_PIX_FMT_YUV420P;
185    // B frames are backwards predicted - they can improve compression,
186    // but may slow encoding and decoding.
187    // if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
188    //     c->max_b_frames = 2;
189    // }
190
191    /* Some formats want stream headers to be separate. */
192    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
193        c->flags |= CODEC_FLAG_GLOBAL_HEADER;
194
195    int retval;
196#ifndef HAVE_AVFORMAT_WRITE_HEADER
197    // Set the output parameters (must be done even if no parameters).
198    retval = av_set_parameters(oc, NULL);
199    if (retval < 0) {
200        averrno = retval;
201        return false;
202    }
203#endif
204
205    retval = avcodec_open2(c, NULL, NULL);
206    if (retval < 0) {
207        averrno = retval;
208        return false;
209    }
210
211#ifndef HAVE_AVCODEC_ENCODE_VIDEO2
212    outbuf = NULL;
213    if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
214        outbuf = (unsigned char *)malloc(OUTBUF_SIZE);
215        if (!outbuf) {
216            averrno = AVERROR(ENOMEM);
217            return false;
218        }
219    }
220#endif
221
222    /* Allocate the encoded raw picture. */
223    frame = avcodec_alloc_frame();
224    if (!frame) {
225        averrno = AVERROR(ENOMEM);
226        return false;
227    }
228    retval = avpicture_alloc((AVPicture *)frame, c->pix_fmt, c->width, c->height);
229    if (retval < 0) {
230        averrno = retval;
231        return false;
232    }
233
234    if (c->pix_fmt != PIX_FMT_YUV420P) {
235        // FIXME need to allocate another frame for this case if we stop
236        // hardcoding PIX_FMT_YUV420P.
237        abort();
238    }
239
240    pixels = (unsigned char *)malloc(width * height * 6);
241    if (!pixels) {
242        averrno = AVERROR(ENOMEM);
243        return false;
244    }
245
246    // Show the format we've ended up with (for debug purposes).
247    // av_dump_format(oc, 0, fnm, 1);
248
249    av_free(sws_ctx);
250    sws_ctx = sws_getContext(width, height, PIX_FMT_RGB24,
251                             width, height, c->pix_fmt, SWS_BICUBIC,
252                             NULL, NULL, NULL);
253    if (sws_ctx == NULL) {
254        fprintf(stderr, "Cannot initialize the conversion context!\n");
255        averrno = AVERROR(ENOMEM);
256        return false;
257    }
258
259    if (!(fmt->flags & AVFMT_NOFILE)) {
260        retval = avio_open(&oc->pb, fnm, AVIO_FLAG_WRITE);
261        if (retval < 0) {
262            averrno = retval;
263            return false;
264        }
265    }
266
267    // Write the stream header, if any.
268#ifdef HAVE_AVFORMAT_WRITE_HEADER
269    retval = avformat_write_header(oc, NULL);
270#else
271    retval = av_write_header(oc);
272#endif
273    if (retval < 0) {
274        averrno = retval;
275        return false;
276    }
277
278    averrno = 0;
279    return true;
280#else
281    (void)fnm;
282    (void)width;
283    (void)height;
284    averrno = MOVIE_NOT_ENABLED;
285    return false;
286#endif
287}
288
289unsigned char * MovieMaker::GetBuffer() const {
290#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
291    AVCodecContext * c = video_st->codec;
292    return pixels + c->height * c->width * 3;
293#else
294    return NULL;
295#endif
296}
297
298int MovieMaker::GetWidth() const {
299    assert(video_st);
300#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
301    AVCodecContext *c = video_st->codec;
302    return c->width;
303#else
304    return 0;
305#endif
306}
307
308int MovieMaker::GetHeight() const {
309    assert(video_st);
310#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
311    AVCodecContext *c = video_st->codec;
312    return c->height;
313#else
314    return 0;
315#endif
316}
317
318bool MovieMaker::AddFrame()
319{
320#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
321    AVCodecContext * c = video_st->codec;
322
323    if (c->pix_fmt != PIX_FMT_YUV420P) {
324        // FIXME convert...
325        abort();
326    }
327
328    int len = 3 * c->width;
329    {
330        // Flip image vertically
331        int h = c->height;
332        unsigned char * src = pixels + h * len;
333        unsigned char * dest = src - len;
334        while (h--) {
335            memcpy(dest, src, len);
336            src += len;
337            dest -= len;
338        }
339    }
340    sws_scale(sws_ctx, &pixels, &len, 0, c->height, frame->data, frame->linesize);
341
342    if (oc->oformat->flags & AVFMT_RAWPICTURE) {
343        abort();
344    }
345
346    // Encode this frame.
347#ifdef HAVE_AVCODEC_ENCODE_VIDEO2
348    AVPacket pkt = { 0 };
349    int got_packet;
350    av_init_packet(&pkt);
351
352    int ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
353    if (ret < 0) {
354        averrno = ret;
355        return false;
356    }
357    if (got_packet && pkt.size) {
358        // Write the compressed frame to the media file.
359        if (pkt.pts != AV_NOPTS_VALUE) {
360            pkt.pts = av_rescale_q(pkt.pts,
361                                   c->time_base, video_st->time_base);
362        }
363        if (pkt.dts != AV_NOPTS_VALUE) {
364            pkt.dts = av_rescale_q(pkt.dts,
365                                   c->time_base, video_st->time_base);
366        }
367        pkt.stream_index = video_st->index;
368
369        /* Write the compressed frame to the media file. */
370        ret = av_interleaved_write_frame(oc, &pkt);
371        if (ret < 0) {
372            averrno = ret;
373            return false;
374        }
375    }
376#else
377    out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, frame);
378    // outsize == 0 means that this frame has been buffered, so there's nothing
379    // to write yet.
380    if (out_size) {
381        // Write the compressed frame to the media file.
382        AVPacket pkt;
383        av_init_packet(&pkt);
384
385        if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
386            pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
387        if (c->coded_frame->key_frame)
388            pkt.flags |= AV_PKT_FLAG_KEY;
389        pkt.stream_index = video_st->index;
390        pkt.data = outbuf;
391        pkt.size = out_size;
392
393        /* Write the compressed frame to the media file. */
394        int ret = av_interleaved_write_frame(oc, &pkt);
395        if (ret < 0) {
396            averrno = ret;
397            return false;
398        }
399    }
400#endif
401#endif
402    return true;
403}
404
405bool
406MovieMaker::Close()
407{
408#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
409    if (video_st && averrno == 0) {
410        // No more frames to compress.  The codec may have a few frames
411        // buffered if we're using B frames, so write those too.
412        AVCodecContext * c = video_st->codec;
413
414#ifdef HAVE_AVCODEC_ENCODE_VIDEO2
415        while (1) {
416            AVPacket pkt = { 0 };
417            int got_packet;
418            av_init_packet(&pkt);
419
420            int ret = avcodec_encode_video2(c, &pkt, NULL, &got_packet);
421            if (ret < 0) {
422                release();
423                averrno = ret;
424                return false;
425            }
426            if (!got_packet) break;
427            if (!pkt.size) continue;
428
429            // Write the compressed frame to the media file.
430            if (pkt.pts != AV_NOPTS_VALUE) {
431                pkt.pts = av_rescale_q(pkt.pts,
432                                       c->time_base, video_st->time_base);
433            }
434            if (pkt.dts != AV_NOPTS_VALUE) {
435                pkt.dts = av_rescale_q(pkt.dts,
436                                       c->time_base, video_st->time_base);
437            }
438            pkt.stream_index = video_st->index;
439
440            /* Write the compressed frame to the media file. */
441            ret = av_interleaved_write_frame(oc, &pkt);
442            if (ret < 0) {
443                release();
444                averrno = ret;
445                return false;
446            }
447        }
448#else
449        while (out_size) {
450            out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, NULL);
451            if (out_size) {
452                // Write the compressed frame to the media file.
453                AVPacket pkt;
454                av_init_packet(&pkt);
455
456                if (c->coded_frame->pts != (int64_t)AV_NOPTS_VALUE)
457                    pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base);
458                if (c->coded_frame->key_frame)
459                    pkt.flags |= AV_PKT_FLAG_KEY;
460                pkt.stream_index = video_st->index;
461                pkt.data = outbuf;
462                pkt.size = out_size;
463
464                /* write the compressed frame in the media file */
465                int ret = av_interleaved_write_frame(oc, &pkt);
466                if (ret < 0) {
467                    release();
468                    averrno = ret;
469                    return false;
470                }
471            }
472        }
473#endif
474
475        av_write_trailer(oc);
476    }
477
478    release();
479#endif
480    return true;
481}
482
483void
484MovieMaker::release()
485{
486#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
487    if (video_st) {
488        // Close codec.
489        avcodec_close(video_st->codec);
490        video_st = NULL;
491    }
492
493    if (frame) {
494        free(frame->data[0]);
495        free(frame);
496        frame = NULL;
497    }
498    free(pixels);
499    pixels = NULL;
500    free(outbuf);
501    outbuf = NULL;
502    av_free(sws_ctx);
503    sws_ctx = NULL;
504
505    if (oc) {
506        // Free the streams.
507        for (size_t i = 0; i < oc->nb_streams; ++i) {
508            av_freep(&oc->streams[i]->codec);
509            av_freep(&oc->streams[i]);
510        }
511
512        if (!(oc->oformat->flags & AVFMT_NOFILE)) {
513            // Close the output file.
514            avio_close(oc->pb);
515        }
516
517        // Free the stream.
518        av_free(oc);
519        oc = NULL;
520    }
521#endif
522}
523
524MovieMaker::~MovieMaker()
525{
526    release();
527}
528
529const char *
530MovieMaker::get_error_string() const
531{
532#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H
533    switch (averrno) {
534        case AVERROR(EIO):
535            return "I/O error";
536        case AVERROR(EDOM):
537            return "Number syntax expected in filename";
538        case AVERROR_INVALIDDATA:
539            /* same as AVERROR_UNKNOWN: return "unknown error"; */
540            return "invalid data found";
541        case AVERROR(ENOMEM):
542            return "not enough memory";
543        case AVERROR(EILSEQ):
544            return "unknown format";
545        case AVERROR(ENOSYS):
546            return "Operation not supported";
547        case AVERROR(ENOENT):
548            return "No such file or directory";
549        case AVERROR_EOF:
550            return "End of file";
551        case AVERROR_PATCHWELCOME:
552            return "Not implemented in FFmpeg";
553        case 0:
554            return "No error";
555        case MOVIE_NO_SUITABLE_FORMAT:
556            return "Couldn't find a suitable output format";
557        case MOVIE_AUDIO_ONLY:
558            return "Audio-only format specified";
559        case MOVIE_FILENAME_TOO_LONG:
560            return "Filename too long";
561        case MOVIE_NOT_ENABLED:
562            return "Movie export support not included";
563    }
564    return "Unknown error";
565#else
566    return "Movie generation support code not present";
567#endif
568}
Note: See TracBrowser for help on using the repository browser.