Typo fix.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index 298284512371b87c53ba21edee018434f218d009..885bd0e85e03ad11a79e1ff6da34f2173e05b184 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #include <iomanip>
 #include <iostream>
 #include <stdint.h>
 #include <iomanip>
 #include <iostream>
 #include <stdint.h>
-#include <boost/lexical_cast.hpp>
 #include <sndfile.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 }
 #include <sndfile.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 }
-#include "film.h"
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
 #include "util.h"
 #include "log.h"
 #include "ffmpeg_decoder.h"
 #include "filter.h"
 #include "exceptions.h"
 #include "image.h"
 #include "util.h"
 #include "log.h"
 #include "ffmpeg_decoder.h"
+#include "ffmpeg_audio_stream.h"
+#include "ffmpeg_subtitle_stream.h"
 #include "filter_graph.h"
 #include "audio_buffers.h"
 #include "ffmpeg_content.h"
 #include "filter_graph.h"
 #include "audio_buffers.h"
 #include "ffmpeg_content.h"
+#include "image_proxy.h"
+#include "film.h"
+#include "timer.h"
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
+#define LOG_GENERAL(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_GENERAL);
+#define LOG_ERROR(...) _video_content->film()->log()->log (String::compose (__VA_ARGS__), Log::TYPE_ERROR);
+#define LOG_WARNING(...) _video_content->film()->log()->log (__VA_ARGS__, Log::TYPE_WARNING);
+
 using std::cout;
 using std::string;
 using std::vector;
 using std::cout;
 using std::string;
 using std::vector;
@@ -53,26 +60,19 @@ using std::stringstream;
 using std::list;
 using std::min;
 using std::pair;
 using std::list;
 using std::min;
 using std::pair;
+using std::make_pair;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
 using boost::shared_ptr;
 using boost::optional;
 using boost::dynamic_pointer_cast;
-using libdcp::Size;
+using dcp::Size;
 
 
-FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegContent> c, bool video, bool audio)
-       : Decoder (f)
-       , VideoDecoder (f, c)
-       , AudioDecoder (f, c)
-       , SubtitleDecoder (f)
+FFmpegDecoder::FFmpegDecoder (shared_ptr<const FFmpegContent> c, shared_ptr<Log> log)
+       : VideoDecoder (c)
+       , AudioDecoder (c)
+       , SubtitleDecoder (c)
        , FFmpeg (c)
        , FFmpeg (c)
-       , _subtitle_codec_context (0)
-       , _subtitle_codec (0)
-       , _decode_video (video)
-       , _decode_audio (audio)
-       , _video_pts_offset (0)
-       , _audio_pts_offset (0)
+       , _log (log)
 {
 {
-       setup_subtitle ();
-
        /* Audio and video frame PTS values may not start with 0.  We want
           to fiddle them so that:
 
        /* Audio and video frame PTS values may not start with 0.  We want
           to fiddle them so that:
 
@@ -82,45 +82,27 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
           Then we remove big initial gaps in PTS and we allow our
           insertion of black frames to work.
 
-          We will do:
-            audio_pts_to_use = audio_pts_from_ffmpeg + audio_pts_offset;
-            video_pts_to_use = video_pts_from_ffmpeg + video_pts_offset;
+          We will do pts_to_use = pts_from_ffmpeg + pts_offset;
        */
 
        */
 
-       bool const have_video = video && c->first_video();
-       bool const have_audio = _decode_audio && c->audio_stream () && c->audio_stream()->first_audio;
+       bool const have_video = c->first_video();
+       bool const have_audio = c->audio_stream () && c->audio_stream()->first_audio;
 
        /* First, make one of them start at 0 */
 
        if (have_audio && have_video) {
 
        /* First, make one of them start at 0 */
 
        if (have_audio && have_video) {
-               _video_pts_offset = _audio_pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
+               _pts_offset = - min (c->first_video().get(), c->audio_stream()->first_audio.get());
        } else if (have_video) {
        } else if (have_video) {
-               _video_pts_offset = - c->first_video().get();
+               _pts_offset = - c->first_video().get();
        } else if (have_audio) {
        } else if (have_audio) {
-               _audio_pts_offset = - c->audio_stream()->first_audio.get();
+               _pts_offset = - c->audio_stream()->first_audio.get();
        }
 
        /* Now adjust both so that the video pts starts on a frame */
        if (have_video && have_audio) {
        }
 
        /* Now adjust both so that the video pts starts on a frame */
        if (have_video && have_audio) {
-               double first_video = c->first_video().get() + _video_pts_offset;
-               double const old_first_video = first_video;
-               
-               /* Round the first video up to a frame boundary */
-               if (fabs (rint (first_video * c->video_frame_rate()) - first_video * c->video_frame_rate()) > 1e-6) {
-                       first_video = ceil (first_video * c->video_frame_rate()) / c->video_frame_rate ();
-               }
-
-               _video_pts_offset += first_video - old_first_video;
-               _audio_pts_offset += first_video - old_first_video;
-       }
-}
-
-FFmpegDecoder::~FFmpegDecoder ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
-
-       if (_subtitle_codec_context) {
-               avcodec_close (_subtitle_codec_context);
+               ContentTime first_video = c->first_video().get() + _pts_offset;
+               ContentTime const old_first_video = first_video;
+               _pts_offset += first_video.round_up (c->video_frame_rate ()) - old_first_video;
        }
 }
 
        }
 }
 
@@ -134,15 +116,12 @@ FFmpegDecoder::flush ()
        
        /* XXX: should we reset _packet.data and size after each *_decode_* call? */
        
        
        /* XXX: should we reset _packet.data and size after each *_decode_* call? */
        
-       if (_decode_video) {
-               while (decode_video_packet ()) {}
-       }
+       while (decode_video_packet ()) {}
        
        
-       if (_ffmpeg_content->audio_stream() && _decode_audio) {
+       if (_ffmpeg_content->audio_stream()) {
                decode_audio_packet ();
                decode_audio_packet ();
+               AudioDecoder::flush ();
        }
        }
-
-       AudioDecoder::flush ();
 }
 
 bool
 }
 
 bool
@@ -155,27 +134,20 @@ FFmpegDecoder::pass ()
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
                        /* Maybe we should fail here, but for now we'll just finish off instead */
                        char buf[256];
                        av_strerror (r, buf, sizeof(buf));
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
-                       film->log()->log (String::compose (N_("error on av_read_frame (%1) (%2)"), buf, r));
+                       LOG_ERROR (N_("error on av_read_frame (%1) (%2)"), buf, r);
                }
 
                flush ();
                return true;
        }
 
                }
 
                flush ();
                return true;
        }
 
-       avcodec_get_frame_defaults (_frame);
-
-       shared_ptr<const Film> film = _film.lock ();
-       assert (film);
-
        int const si = _packet.stream_index;
        int const si = _packet.stream_index;
-       
-       if (si == _video_stream && _decode_video) {
+
+       if (si == _video_stream) {
                decode_video_packet ();
                decode_video_packet ();
-       } else if (_ffmpeg_content->audio_stream() && si == _ffmpeg_content->audio_stream()->index (_format_context) && _decode_audio) {
+       } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si)) {
                decode_audio_packet ();
                decode_audio_packet ();
-       } else if (_ffmpeg_content->subtitle_stream() && si == _ffmpeg_content->subtitle_stream()->index (_format_context) && film->with_subtitles ()) {
+       } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
        }
 
                decode_subtitle_packet ();
        }
 
@@ -201,6 +173,23 @@ FFmpegDecoder::deinterleave_audio (uint8_t** data, int size)
        shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
 
        switch (audio_sample_format()) {
        shared_ptr<AudioBuffers> audio (new AudioBuffers (_ffmpeg_content->audio_channels(), frames));
 
        switch (audio_sample_format()) {
+       case AV_SAMPLE_FMT_U8:
+       {
+               uint8_t* p = reinterpret_cast<uint8_t *> (data[0]);
+               int sample = 0;
+               int channel = 0;
+               for (int i = 0; i < total_samples; ++i) {
+                       audio->data(channel)[sample] = float(*p++) / (1 << 23);
+
+                       ++channel;
+                       if (channel == _ffmpeg_content->audio_channels()) {
+                               channel = 0;
+                               ++sample;
+                       }
+               }
+       }
+       break;
+       
        case AV_SAMPLE_FMT_S16:
        {
                int16_t* p = reinterpret_cast<int16_t *> (data[0]);
        case AV_SAMPLE_FMT_S16:
        {
                int16_t* p = reinterpret_cast<int16_t *> (data[0]);
@@ -295,131 +284,45 @@ FFmpegDecoder::bytes_per_audio_sample () const
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
        return av_get_bytes_per_sample (audio_sample_format ());
 }
 
-int
-FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
+void
+FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
 {
-       int frames_read = 0;
-       optional<ContentTime> last_video;
-       optional<ContentTime> last_audio;
-
-       while (!finished (last_video, last_audio, frames_read)) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       /* We should flush our decoders here, possibly yielding a few more frames,
-                          but the consequence of having to do that is too hideous to contemplate.
-                          Instead we give up and say that you can't seek too close to the end
-                          of a file.
-                       */
-                       return frames_read;
-               }
-
-               ++frames_read;
-
-               double const time_base = av_q2d (_format_context->streams[_packet.stream_index]->time_base);
-
-               if (_packet.stream_index == _video_stream) {
+       VideoDecoder::seek (time, accurate);
+       AudioDecoder::seek (time, accurate);
+       
+       /* If we are doing an `accurate' seek, we need to use pre-roll, as
+          we don't really know what the seek will give us.
+       */
 
 
-                       avcodec_get_frame_defaults (_frame);
-                       
-                       int finished = 0;
-                       r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
-                       if (r >= 0 && finished) {
-                               last_video = rint (
-                                       (av_frame_get_best_effort_timestamp (_frame) * time_base + _video_pts_offset) * TIME_HZ
-                                       );
-                       }
-                               
-               } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
-                       AVPacket copy_packet = _packet;
-                       while (copy_packet.size > 0) {
-
-                               int finished;
-                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &finished, &_packet);
-                               if (r >= 0 && finished) {
-                                       last_audio = rint (
-                                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _audio_pts_offset) * TIME_HZ
-                                               );
-                               }
-                                       
-                               copy_packet.data += r;
-                               copy_packet.size -= r;
-                       }
-               }
-               
-               av_free_packet (&_packet);
+       ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+       time -= pre_roll;
+       if (time < ContentTime (0)) {
+               time = ContentTime (0);
        }
 
        }
 
-       return frames_read;
-}
-
-bool
-FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
-{
-       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
-}
-
-bool
-FFmpegDecoder::seek_final_finished (int n, int done) const
-{
-       return n == done;
-}
+       ContentTime const u = time - _pts_offset;
+       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
 
 
-void
-FFmpegDecoder::seek_and_flush (ContentTime t)
-{
-       int64_t s = ((double (t) / TIME_HZ) - _video_pts_offset) /
-               av_q2d (_format_context->streams[_video_stream]->time_base);
-       
        if (_ffmpeg_content->audio_stream ()) {
                s = min (
        if (_ffmpeg_content->audio_stream ()) {
                s = min (
-                       s, int64_t (
-                               ((double (t) / TIME_HZ) - _audio_pts_offset) /
-                               av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base)
-                               )
+                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
                        );
        }
 
                        );
        }
 
-       av_seek_frame (_format_context, _video_stream, s, AVSEEK_FLAG_BACKWARD);
+       /* Ridiculous empirical hack */
+       s--;
+       if (s < 0) {
+               s = 0;
+       }
+
+       av_seek_frame (_format_context, _video_stream, s, 0);
 
        avcodec_flush_buffers (video_codec_context());
        if (audio_codec_context ()) {
                avcodec_flush_buffers (audio_codec_context ());
        }
 
        avcodec_flush_buffers (video_codec_context());
        if (audio_codec_context ()) {
                avcodec_flush_buffers (audio_codec_context ());
        }
-       if (_subtitle_codec_context) {
-               avcodec_flush_buffers (_subtitle_codec_context);
-       }
-}
-
-void
-FFmpegDecoder::seek (ContentTime time, bool accurate)
-{
-       Decoder::seek (time, accurate);
-       
-       /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
-          a number plucked from the air) earlier than we want to end up.  The loop below
-          will hopefully then step through to where we want to be.
-       */
-
-       ContentTime pre_roll = accurate ? (0.2 * TIME_HZ) : 0;
-       ContentTime initial_seek = time - pre_roll;
-       if (initial_seek < 0) {
-               initial_seek = 0;
-       }
-       
-       /* Initial seek time in the video stream's timebase */
-
-       seek_and_flush (initial_seek);
-
-       if (!accurate) {
-               /* That'll do */
-               return;
-       }
-
-       int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
-       
-       seek_and_flush (initial_seek);
-       if (N > 0) {
-               minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
+       if (subtitle_codec_context ()) {
+               avcodec_flush_buffers (subtitle_codec_context ());
        }
 }
 
        }
 }
 
@@ -438,23 +341,21 @@ FFmpegDecoder::decode_audio_packet ()
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
 
                if (decode_result < 0) {
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
 
                if (decode_result < 0) {
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
-                       film->log()->log (String::compose ("avcodec_decode_audio4 failed (%1)", decode_result));
+                       LOG_ERROR ("avcodec_decode_audio4 failed (%1)", decode_result);
                        return;
                }
 
                if (frame_finished) {
                        return;
                }
 
                if (frame_finished) {
-                       ContentTime const t = rint (
-                               (av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                * av_frame_get_best_effort_timestamp(_frame) + _audio_pts_offset) * TIME_HZ
-                               );
-
+                       ContentTime const ct = ContentTime::from_seconds (
+                               av_frame_get_best_effort_timestamp (_frame) *
+                               av_q2d (_ffmpeg_content->audio_stream()->stream (_format_context)->time_base))
+                               + _pts_offset;
+                       
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
                        int const data_size = av_samples_get_buffer_size (
                                0, audio_codec_context()->channels, _frame->nb_samples, audio_sample_format (), 1
                                );
-                       
-                       audio (deinterleave_audio (_frame->data, data_size), t);
+
+                       audio (deinterleave_audio (_frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
                }
                        
                copy_packet.data += decode_result;
@@ -475,74 +376,44 @@ FFmpegDecoder::decode_video_packet ()
        shared_ptr<FilterGraph> graph;
        
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
        shared_ptr<FilterGraph> graph;
        
        list<shared_ptr<FilterGraph> >::iterator i = _filter_graphs.begin();
-       while (i != _filter_graphs.end() && !(*i)->can_process (libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
+       while (i != _filter_graphs.end() && !(*i)->can_process (dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format)) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
                ++i;
        }
 
        if (i == _filter_graphs.end ()) {
-               shared_ptr<const Film> film = _film.lock ();
-               assert (film);
-
-               graph.reset (new FilterGraph (_ffmpeg_content, libdcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
+               graph.reset (new FilterGraph (_ffmpeg_content, dcp::Size (_frame->width, _frame->height), (AVPixelFormat) _frame->format));
                _filter_graphs.push_back (graph);
                _filter_graphs.push_back (graph);
-
-               film->log()->log (String::compose (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format));
+               LOG_GENERAL (N_("New graph for %1x%2, pixel format %3"), _frame->width, _frame->height, _frame->format);
        } else {
                graph = *i;
        }
 
        list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
 
        } else {
                graph = *i;
        }
 
        list<pair<shared_ptr<Image>, int64_t> > images = graph->process (_frame);
 
-       string post_process = Filter::ffmpeg_strings (_ffmpeg_content->filters()).second;
-       
        for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
 
                shared_ptr<Image> image = i->first;
        for (list<pair<shared_ptr<Image>, int64_t> >::iterator i = images.begin(); i != images.end(); ++i) {
 
                shared_ptr<Image> image = i->first;
-               if (!post_process.empty ()) {
-                       image = image->post_process (post_process, true);
-               }
                
                if (i->second != AV_NOPTS_VALUE) {
                
                if (i->second != AV_NOPTS_VALUE) {
-                       ContentTime const t = rint ((i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _video_pts_offset) * TIME_HZ);
-                       video (image, false, t);
+                       double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset.seconds ();
+                       video (
+                               shared_ptr<ImageProxy> (new RawImageProxy (image, _video_content->film()->log())),
+                               rint (pts * _ffmpeg_content->video_frame_rate ())
+                               );
                } else {
                } else {
-                       shared_ptr<const Film> film = _film.lock ();
-                       assert (film);
-                       film->log()->log ("Dropping frame without PTS");
+                       LOG_WARNING ("Dropping frame without PTS");
                }
        }
 
        return true;
 }
                }
        }
 
        return true;
 }
-
-       
-void
-FFmpegDecoder::setup_subtitle ()
-{
-       boost::mutex::scoped_lock lm (_mutex);
        
        
-       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->index (_format_context) >= int (_format_context->nb_streams)) {
-               return;
-       }
-
-       _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
-       _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
-
-       if (_subtitle_codec == 0) {
-               throw DecodeError (_("could not find subtitle decoder"));
-       }
-       
-       if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
-               throw DecodeError (N_("could not open subtitle decoder"));
-       }
-}
-
 void
 FFmpegDecoder::decode_subtitle_packet ()
 {
        int got_subtitle;
        AVSubtitle sub;
 void
 FFmpegDecoder::decode_subtitle_packet ()
 {
        int got_subtitle;
        AVSubtitle sub;
-       if (avcodec_decode_subtitle2 (_subtitle_codec_context, &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
+       if (avcodec_decode_subtitle2 (subtitle_codec_context(), &sub, &got_subtitle, &_packet) < 0 || !got_subtitle) {
                return;
        }
 
                return;
        }
 
@@ -550,60 +421,68 @@ FFmpegDecoder::decode_subtitle_packet ()
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
           indicate that the previous subtitle should stop.
        */
        if (sub.num_rects <= 0) {
-               subtitle (shared_ptr<Image> (), dcpomatic::Rect<double> (), 0, 0);
+               image_subtitle (ContentTimePeriod (), shared_ptr<Image> (), dcpomatic::Rect<double> ());
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
                
                return;
        } else if (sub.num_rects > 1) {
                throw DecodeError (_("multi-part subtitles not yet supported"));
        }
                
-       /* Subtitle PTS in seconds (within the source, not taking into account any of the
+       /* Subtitle PTS (within the source, not taking into account any of the
           source that we may have chopped off for the DCP)
        */
           source that we may have chopped off for the DCP)
        */
-       double const packet_time = static_cast<double> (sub.pts) / AV_TIME_BASE;
-       
-       /* hence start time for this sub */
-       ContentTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       ContentTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+       ContentTimePeriod period = subtitle_period (sub) + _pts_offset;
 
        AVSubtitleRect const * rect = sub.rects[0];
 
        if (rect->type != SUBTITLE_BITMAP) {
 
        AVSubtitleRect const * rect = sub.rects[0];
 
        if (rect->type != SUBTITLE_BITMAP) {
-               throw DecodeError (_("non-bitmap subtitles not yet supported"));
+               /* XXX */
+               // throw DecodeError (_("non-bitmap subtitles not yet supported"));
+               return;
        }
        }
-       
-       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, libdcp::Size (rect->w, rect->h), true));
+
+       /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
+          G, third B, fourth A.
+       */
+       shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (rect->w, rect->h), true));
 
        /* Start of the first line in the subtitle */
        uint8_t* sub_p = rect->pict.data[0];
 
        /* Start of the first line in the subtitle */
        uint8_t* sub_p = rect->pict.data[0];
-       /* sub_p looks up into a RGB palette which is here */
+       /* sub_p looks up into a BGRA palette which is here
+          (i.e. first byte B, second G, third R, fourth A)
+       */
        uint32_t const * palette = (uint32_t *) rect->pict.data[1];
        /* Start of the output data */
        uint32_t* out_p = (uint32_t *) image->data()[0];
        uint32_t const * palette = (uint32_t *) rect->pict.data[1];
        /* Start of the output data */
        uint32_t* out_p = (uint32_t *) image->data()[0];
-       
+
        for (int y = 0; y < rect->h; ++y) {
                uint8_t* sub_line_p = sub_p;
                uint32_t* out_line_p = out_p;
                for (int x = 0; x < rect->w; ++x) {
        for (int y = 0; y < rect->h; ++y) {
                uint8_t* sub_line_p = sub_p;
                uint32_t* out_line_p = out_p;
                for (int x = 0; x < rect->w; ++x) {
-                       *out_line_p++ = palette[*sub_line_p++];
+                       uint32_t const p = palette[*sub_line_p++];
+                       *out_line_p++ = ((p & 0xff) << 16) | (p & 0xff00) | ((p & 0xff0000) >> 16) | (p & 0xff000000);
                }
                sub_p += rect->pict.linesize[0];
                out_p += image->stride()[0] / sizeof (uint32_t);
        }
 
                }
                sub_p += rect->pict.linesize[0];
                out_p += image->stride()[0] / sizeof (uint32_t);
        }
 
-       libdcp::Size const vs = _ffmpeg_content->video_size ();
+       dcp::Size const vs = _ffmpeg_content->video_size ();
 
 
-       subtitle (
+       image_subtitle (
+               period,
                image,
                dcpomatic::Rect<double> (
                        static_cast<double> (rect->x) / vs.width,
                        static_cast<double> (rect->y) / vs.height,
                        static_cast<double> (rect->w) / vs.width,
                        static_cast<double> (rect->h) / vs.height
                image,
                dcpomatic::Rect<double> (
                        static_cast<double> (rect->x) / vs.width,
                        static_cast<double> (rect->y) / vs.height,
                        static_cast<double> (rect->w) / vs.width,
                        static_cast<double> (rect->h) / vs.height
-                       ),
-               from,
-               to
+                       )
                );
                );
-                         
        
        avsubtitle_free (&sub);
 }
        
        avsubtitle_free (&sub);
 }
+
+bool
+FFmpegDecoder::has_subtitle_during (ContentTimePeriod p) const
+{
+       return _ffmpeg_content->has_subtitle_during (p);
+}