Use cxml::ConstNodePtr.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index e8489f5f6cbd1ea9e2fcbbabaf58faa279e51c13..9ae5f0485246d2f88b42432adcfcf479a1eb711a 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 "filter.h"
 #include "exceptions.h"
 #include "image.h"
@@ -56,20 +54,15 @@ using std::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)
        , FFmpeg (c)
        , FFmpeg (c)
+       , _log (log)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
        , _subtitle_codec_context (0)
        , _subtitle_codec (0)
-       , _decode_video (video)
-       , _decode_audio (audio)
-       , _video_pts_offset (0)
-       , _audio_pts_offset (0)
 {
        setup_subtitle ();
 
 {
        setup_subtitle ();
 
@@ -82,36 +75,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;
+               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 +118,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 +136,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->log (String::compose (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 +175,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]);
@@ -296,12 +287,11 @@ FFmpegDecoder::bytes_per_audio_sample () const
 }
 
 int
 }
 
 int
-FFmpegDecoder::minimal_run (boost::function<bool (ContentTime, ContentTime, int)> finished)
+FFmpegDecoder::minimal_run (boost::function<bool (optional<ContentTime>, optional<ContentTime>, int)> finished)
 {
        int frames_read = 0;
 {
        int frames_read = 0;
-       ContentTime last_video = 0;
-       ContentTime last_audio = 0;
-       bool flushing = false;
+       optional<ContentTime> last_video;
+       optional<ContentTime> last_audio;
 
        while (!finished (last_video, last_audio, frames_read)) {
                int r = av_read_frame (_format_context, &_packet);
 
        while (!finished (last_video, last_audio, frames_read)) {
                int r = av_read_frame (_format_context, &_packet);
@@ -322,24 +312,20 @@ FFmpegDecoder::minimal_run (boost::function<bool (ContentTime, ContentTime, int)
 
                        avcodec_get_frame_defaults (_frame);
                        
 
                        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
-                                       );
+                       int got_picture = 0;
+                       r = avcodec_decode_video2 (video_codec_context(), _frame, &got_picture, &_packet);
+                       if (r >= 0 && got_picture) {
+                               last_video = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
                        }
                        }
-                               
-               } else if (_ffmpeg_content->audio_stream() && _packet.stream_index == _ffmpeg_content->audio_stream()->index (_format_context)) {
+
+               } else if (_ffmpeg_content->audio_stream() && _ffmpeg_content->audio_stream()->uses_index (_format_context, _packet.stream_index)) {
                        AVPacket copy_packet = _packet;
                        while (copy_packet.size > 0) {
 
                        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
-                                               );
+                               int got_frame;
+                               r = avcodec_decode_audio4 (audio_codec_context(), _frame, &got_frame, &_packet);
+                               if (r >= 0 && got_frame) {
+                                       last_audio = ContentTime::from_seconds (av_frame_get_best_effort_timestamp (_frame) * time_base) + _pts_offset;
                                }
                                        
                                copy_packet.data += r;
                                }
                                        
                                copy_packet.data += r;
@@ -354,9 +340,9 @@ FFmpegDecoder::minimal_run (boost::function<bool (ContentTime, ContentTime, int)
 }
 
 bool
 }
 
 bool
-FFmpegDecoder::seek_overrun_finished (ContentTime seek, ContentTime last_video, ContentTime last_audio) const
+FFmpegDecoder::seek_overrun_finished (ContentTime seek, optional<ContentTime> last_video, optional<ContentTime> last_audio) const
 {
 {
-       return last_video >= seek || last_audio >= seek;
+       return (last_video && last_video.get() >= seek) || (last_audio && last_audio.get() >= seek);
 }
 
 bool
 }
 
 bool
@@ -368,19 +354,23 @@ FFmpegDecoder::seek_final_finished (int n, int done) const
 void
 FFmpegDecoder::seek_and_flush (ContentTime t)
 {
 void
 FFmpegDecoder::seek_and_flush (ContentTime t)
 {
-       int64_t const initial_v = ((double (t) / TIME_HZ) - _video_pts_offset) /
-               av_q2d (_format_context->streams[_video_stream]->time_base);
+       ContentTime const u = t - _pts_offset;
+       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
 
 
-       av_seek_frame (_format_context, _video_stream, initial_v, AVSEEK_FLAG_BACKWARD);
-
-       shared_ptr<FFmpegAudioStream> as = _ffmpeg_content->audio_stream ();
-       if (as) {
-               int64_t initial_a = ((double (t) / TIME_HZ) - _audio_pts_offset) /
-                       av_q2d (as->stream(_format_context)->time_base);
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+                       );
+       }
 
 
-               av_seek_frame (_format_context, as->index (_format_context), initial_a, 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 ());
@@ -393,30 +383,31 @@ FFmpegDecoder::seek_and_flush (ContentTime t)
 void
 FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
 void
 FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
-       Decoder::seek (time, accurate);
+       VideoDecoder::seek (time, accurate);
+       AudioDecoder::seek (time, accurate);
        
        
-       /* If we are doing an accurate seek, our initial shot will be 200ms (200 being
+       /* If we are doing an accurate seek, our initial shot will be 2s (2 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.
        */
 
           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 pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
        ContentTime initial_seek = time - pre_roll;
        ContentTime initial_seek = time - pre_roll;
-       if (initial_seek < 0) {
-               initial_seek = 0;
+       if (initial_seek < ContentTime (0)) {
+               initial_seek = ContentTime (0);
        }
        }
-       
+
        /* Initial seek time in the video stream's timebase */
 
        seek_and_flush (initial_seek);
 
        /* Initial seek time in the video stream's timebase */
 
        seek_and_flush (initial_seek);
 
-       if (time == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
+       if (!accurate) {
+               /* That'll do */
                return;
        }
 
        int const N = minimal_run (boost::bind (&FFmpegDecoder::seek_overrun_finished, this, time, _1, _2));
                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));
        seek_and_flush (initial_seek);
        if (N > 0) {
                minimal_run (boost::bind (&FFmpegDecoder::seek_final_finished, this, N - 1, _3));
@@ -438,23 +429,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->log (String::compose ("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,40 +464,29 @@ 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->log (String::compose (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 (image, 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->log ("Dropping frame without PTS");
                }
        }
 
                }
        }
 
@@ -521,15 +499,19 @@ FFmpegDecoder::setup_subtitle ()
 {
        boost::mutex::scoped_lock lm (_mutex);
        
 {
        boost::mutex::scoped_lock lm (_mutex);
        
-       if (!_ffmpeg_content->subtitle_stream() || _ffmpeg_content->subtitle_stream()->index (_format_context) >= int (_format_context->nb_streams)) {
+       if (!_ffmpeg_content->subtitle_stream()) {
                return;
        }
 
        _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
                return;
        }
 
        _subtitle_codec_context = _ffmpeg_content->subtitle_stream()->stream(_format_context)->codec;
+       if (_subtitle_codec_context == 0) {
+               throw DecodeError (N_("could not find subtitle stream"));
+       }
+
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
        if (_subtitle_codec == 0) {
        _subtitle_codec = avcodec_find_decoder (_subtitle_codec_context->codec_id);
 
        if (_subtitle_codec == 0) {
-               throw DecodeError (_("could not find subtitle decoder"));
+               throw DecodeError (N_("could not find subtitle decoder"));
        }
        
        if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
        }
        
        if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
@@ -550,60 +532,67 @@ 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 (ContentTime (), ContentTime (), 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;
+       ContentTime packet_time = ContentTime::from_seconds (static_cast<double> (sub.pts) / AV_TIME_BASE) + _pts_offset;
        
        /* hence start time for this sub */
        
        /* hence start time for this sub */
-       DCPTime const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       DCPTime const to = (packet_time + (double (sub.end_display_time) / 1e3)) * TIME_HZ;
+       ContentTime const from = packet_time + ContentTime::from_seconds (sub.start_display_time / 1e3);
+       ContentTime const to = packet_time + ContentTime::from_seconds (sub.end_display_time / 1e3);
 
        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 (
+               from,
+               to,
                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);
 }