source: git/src/moviemaker.cc @ f02f043

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereostereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since f02f043 was c54d6fc, checked in by Olly Betts <olly@…>, 9 years ago

Handle the "no movie export" case in moviemaker.cc

This way we know that dropping the legacy version of the code is
safe once those versions are sufficiently old that they aren't
worth trying to support at all.

  • Property mode set to 100644
File size: 11.0 KB
Line 
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.
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 WITH_LIBAV
62extern "C" {
63# include <libavutil/imgutils.h>
64# include <libavutil/mathematics.h>
65# include <libavformat/avformat.h>
66# include <libswscale/swscale.h>
67}
68#endif
69
70// Handle the "no libav/FFmpeg" case in this file.
71#if !defined WITH_LIBAV || LIBAVCODEC_VERSION_MAJOR >= 57
72
73#ifdef WITH_LIBAV
74enum {
75    MOVIE_NO_SUITABLE_FORMAT = 1,
76    MOVIE_AUDIO_ONLY,
77    MOVIE_FILENAME_TOO_LONG
78};
79#endif
80
81MovieMaker::MovieMaker()
82#ifdef WITH_LIBAV
83    : oc(0), video_st(0), context(0), frame(0), pixels(0), sws_ctx(0), averrno(0)
84#endif
85{
86#ifdef WITH_LIBAV
87    static bool initialised_ffmpeg = false;
88    if (initialised_ffmpeg) return;
89
90    // FIXME: register only the codec(s) we want to use...
91    avcodec_register_all();
92    av_register_all();
93
94    initialised_ffmpeg = true;
95#endif
96}
97
98#ifdef WITH_LIBAV
99static int
100write_packet(void *opaque, uint8_t *buf, int buf_size) {
101    FILE * fh = (FILE*)opaque;
102    size_t res = fwrite(buf, 1, buf_size, fh);
103    return res > 0 ? res : -1;
104}
105
106static int64_t
107seek_stream(void *opaque, int64_t offset, int whence) {
108    FILE * fh = (FILE*)opaque;
109    return fseek(fh, offset, whence);
110}
111#endif
112
113#define MAX_EXTENSION_LEN 8
114
115bool MovieMaker::Open(FILE* fh, const char * ext, int width, int height)
116{
117#ifdef WITH_LIBAV
118    fh_to_close = fh;
119
120    /* Allocate the output media context. */
121    char dummy_filename[MAX_EXTENSION_LEN + 3] = "x.";
122    oc = NULL;
123    if (strlen(ext) <= MAX_EXTENSION_LEN) {
124        // Use "x." + extension for format detection to avoid having to deal
125        // with wide character filenames.
126        strcpy(dummy_filename + 2, ext);
127        avformat_alloc_output_context2(&oc, NULL, NULL, dummy_filename);
128    }
129    if (!oc) {
130        averrno = MOVIE_NO_SUITABLE_FORMAT;
131        return false;
132    }
133
134    AVOutputFormat * fmt = oc->oformat;
135    if (fmt->video_codec == AV_CODEC_ID_NONE) {
136        averrno = MOVIE_AUDIO_ONLY;
137        return false;
138    }
139
140    /* find the video encoder */
141    AVCodec *codec = avcodec_find_encoder(fmt->video_codec);
142    if (!codec) {
143        // FIXME : Erm - internal ffmpeg library problem?
144        averrno = AVERROR(ENOMEM);
145        return false;
146    }
147
148    // Add the video stream.
149    video_st = avformat_new_stream(oc, NULL);
150    if (!video_st) {
151        averrno = AVERROR(ENOMEM);
152        return false;
153    }
154
155    context = avcodec_alloc_context3(codec);
156    context->codec_id = fmt->video_codec;
157    context->width = width;
158    context->height = height;
159    video_st->time_base.den = 25; // Frames per second.
160    video_st->time_base.num = 1;
161    context->time_base = video_st->time_base;
162    context->bit_rate = width * height * (4 * 0.07) * context->time_base.den / context->time_base.num;
163    context->bit_rate_tolerance = context->bit_rate;
164    context->global_quality = 4;
165    context->rc_buffer_size = 2 * 1024 * 1024;
166    context->rc_max_rate = context->bit_rate * 8;
167    context->gop_size = 50; /* Twice the framerate */
168    context->pix_fmt = AV_PIX_FMT_YUV420P;
169    if (context->has_b_frames) {
170        // B frames are backwards predicted - they can improve compression,
171        // but may slow encoding and decoding.
172        context->max_b_frames = 4;
173    }
174
175    /* Some formats want stream headers to be separate. */
176    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
177        context->flags |= CODEC_FLAG_GLOBAL_HEADER;
178
179    int retval;
180    retval = avcodec_open2(context, codec, NULL);
181    if (retval < 0) {
182        averrno = retval;
183        return false;
184    }
185
186    /* Allocate the encoded raw picture. */
187    frame = av_frame_alloc();
188    if (!frame) {
189        averrno = AVERROR(ENOMEM);
190        return false;
191    }
192
193    frame->format = context->pix_fmt;
194    frame->width = width;
195    frame->height = height;
196    frame->pts = 0;
197
198    retval = av_frame_get_buffer(frame, 32);
199    if (retval < 0) {
200        averrno = retval;
201        return false;
202    }
203
204    if (frame->format != AV_PIX_FMT_YUV420P) {
205        // FIXME need to allocate another frame for this case if we stop
206        // hardcoding AV_PIX_FMT_YUV420P.
207        abort();
208    }
209
210    /* copy the stream parameters to the muxer */
211    retval = avcodec_parameters_from_context(video_st->codecpar, context);
212    if (retval < 0) {
213        averrno = retval;
214        return false;
215    }
216
217    pixels = (unsigned char *)av_malloc(width * height * 6);
218    if (!pixels) {
219        averrno = AVERROR(ENOMEM);
220        return false;
221    }
222
223    // Show the format we've ended up with (for debug purposes).
224    // av_dump_format(oc, 0, dummy_filename, 1);
225
226    av_free(sws_ctx);
227    sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGB24,
228                             width, height, context->pix_fmt, SWS_BICUBIC,
229                             NULL, NULL, NULL);
230    if (sws_ctx == NULL) {
231        fprintf(stderr, "Cannot initialize the conversion context!\n");
232        averrno = AVERROR(ENOMEM);
233        return false;
234    }
235
236    if (!(fmt->flags & AVFMT_NOFILE)) {
237        const int buf_size = 8192;
238        void * buf = av_malloc(buf_size);
239        oc->pb = avio_alloc_context(static_cast<uint8_t*>(buf), buf_size, 1,
240                                    fh, NULL, write_packet, seek_stream);
241        if (!oc->pb) {
242            averrno = AVERROR(ENOMEM);
243            return false;
244        }
245    }
246
247    // Write the stream header, if any.
248    retval = avformat_write_header(oc, NULL);
249    if (retval < 0) {
250        averrno = retval;
251        return false;
252    }
253
254    averrno = 0;
255    return true;
256#else
257    (void)fh;
258    (void)ext;
259    (void)width;
260    (void)height;
261    return false;
262#endif
263}
264
265unsigned char * MovieMaker::GetBuffer() const {
266#ifdef WITH_LIBAV
267    return pixels + GetWidth() * GetHeight() * 3;
268#else
269    return NULL;
270#endif
271}
272
273int MovieMaker::GetWidth() const {
274#ifdef WITH_LIBAV
275    assert(video_st);
276    return video_st->codecpar->width;
277#else
278    return 0;
279#endif
280}
281
282int MovieMaker::GetHeight() const {
283#ifdef WITH_LIBAV
284    assert(video_st);
285    return video_st->codecpar->height;
286#else
287    return 0;
288#endif
289}
290
291#ifdef WITH_LIBAV
292// Call with frame=NULL when done.
293int
294MovieMaker::encode_frame(AVFrame* frame_or_null)
295{
296    int ret = avcodec_send_frame(context, frame_or_null);
297    if (ret < 0) return ret;
298
299    AVPacket *pkt = av_packet_alloc();
300    pkt->size = 0;
301    while ((ret = avcodec_receive_packet(context, pkt)) == 0) {
302        // Rescale output packet timestamp values from codec to stream timebase.
303        av_packet_rescale_ts(pkt, context->time_base, video_st->time_base);
304        pkt->stream_index = video_st->index;
305
306        // Write the compressed frame to the media file.
307        ret = av_interleaved_write_frame(oc, pkt);
308        if (ret < 0) {
309            av_packet_free(&pkt);
310            release();
311            return ret;
312        }
313    }
314    av_packet_free(&pkt);
315    return 0;
316}
317#endif
318
319bool MovieMaker::AddFrame()
320{
321#ifdef WITH_LIBAV
322    int ret = av_frame_make_writable(frame);
323    if (ret < 0) {
324        averrno = ret;
325        return false;
326    }
327
328    enum AVPixelFormat pix_fmt = context->pix_fmt;
329
330    if (pix_fmt != AV_PIX_FMT_YUV420P) {
331        // FIXME convert...
332        abort();
333    }
334
335    int len = 3 * GetWidth();
336    {
337        // Flip image vertically
338        int h = GetHeight();
339        unsigned char * src = pixels + h * len;
340        unsigned char * dest = src - len;
341        while (h--) {
342            memcpy(dest, src, len);
343            src += len;
344            dest -= len;
345        }
346    }
347    sws_scale(sws_ctx, &pixels, &len, 0, GetHeight(),
348              frame->data, frame->linesize);
349
350    if (oc->oformat->flags & AVFMT_RAWPICTURE) {
351        abort();
352    }
353
354    ++frame->pts;
355
356    // Encode this frame.
357    ret = encode_frame(frame);
358    if (ret < 0) {
359        averrno = ret;
360        return false;
361    }
362#endif
363    return true;
364}
365
366bool
367MovieMaker::Close()
368{
369#ifdef WITH_LIBAV
370    if (video_st && averrno == 0) {
371        // Flush out any remaining data.
372        int ret = encode_frame(NULL);
373        if (ret < 0) {
374            averrno = ret;
375            return false;
376        }
377        av_write_trailer(oc);
378    }
379
380    release();
381#endif
382    return true;
383}
384
385#ifdef WITH_LIBAV
386void
387MovieMaker::release()
388{
389    // Close codec.
390    avcodec_free_context(&context);
391    av_frame_free(&frame);
392    av_free(pixels);
393    pixels = NULL;
394    sws_freeContext(sws_ctx);
395    sws_ctx = NULL;
396
397    // Free the stream.
398    avformat_free_context(oc);
399    oc = NULL;
400
401    if (fh_to_close) {
402        fclose(fh_to_close);
403        fh_to_close = NULL;
404    }
405}
406#endif
407
408MovieMaker::~MovieMaker()
409{
410#ifdef WITH_LIBAV
411    release();
412#endif
413}
414
415const char *
416MovieMaker::get_error_string() const
417{
418#ifdef WITH_LIBAV
419    switch (averrno) {
420        case AVERROR(EIO):
421            return "I/O error";
422        case AVERROR(EDOM):
423            return "Number syntax expected in filename";
424        case AVERROR_INVALIDDATA:
425            /* same as AVERROR_UNKNOWN: return "unknown error"; */
426            return "invalid data found";
427        case AVERROR(ENOMEM):
428            return "not enough memory";
429        case AVERROR(EILSEQ):
430            return "unknown format";
431        case AVERROR(ENOSYS):
432            return "Operation not supported";
433        case AVERROR(ENOENT):
434            return "No such file or directory";
435        case AVERROR_EOF:
436            return "End of file";
437        case AVERROR_PATCHWELCOME:
438            return "Not implemented in FFmpeg";
439        case 0:
440            return "No error";
441        case MOVIE_NO_SUITABLE_FORMAT:
442            return "Couldn't find a suitable output format";
443        case MOVIE_AUDIO_ONLY:
444            return "Audio-only format specified";
445        case MOVIE_FILENAME_TOO_LONG:
446            return "Filename too long";
447    }
448#endif
449    return "Unknown error";
450}
451
452#else
453
454#include "moviemaker-legacy.cc"
455
456#endif
Note: See TracBrowser for help on using the repository browser.