Hopefully improve subtitle decoder seeking etc.
[dcpomatic.git] / src / lib / ffmpeg_decoder.cc
index 7a5bf8ba832f51a538ea57775147ba20296a3c06..668cab88530d1b385821056aeb837d4b2a011ff0 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
@@ -32,20 +32,27 @@ extern "C" {
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 }
 #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 "image_proxy.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)
-       , _pts_offset (0)
-       , _just_sought (false)
+       , _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,13 +82,11 @@ 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 + pts_offset;
-            video_pts_to_use = video_pts_from_ffmpeg + 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 = 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 */
 
 
        /* First, make one of them start at 0 */
 
@@ -102,24 +100,9 @@ FFmpegDecoder::FFmpegDecoder (shared_ptr<const Film> f, shared_ptr<const FFmpegC
 
        /* 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() + _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 ();
-               }
-
-               _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;
        }
 }
 
        }
 }
 
@@ -133,20 +116,15 @@ 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 ();
        }
        }
-
-       /* Stop us being asked for any more data */
-       _video_position = _ffmpeg_content->video_length_after_3d_combine ();
-       _audio_position = _ffmpeg_content->audio_length ();
 }
 
 }
 
-void
+bool
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
 FFmpegDecoder::pass ()
 {
        int r = av_read_frame (_format_context, &_packet);
@@ -156,29 +134,25 @@ 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 ();
                }
 
                flush ();
-               return;
+               return true;
        }
 
        }
 
-       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() && _ffmpeg_content->audio_stream()->uses_index (_format_context, si) && _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() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si) && film->with_subtitles ()) {
+       } else if (_ffmpeg_content->subtitle_stream() && _ffmpeg_content->subtitle_stream()->uses_index (_format_context, si)) {
                decode_subtitle_packet ();
        }
 
        av_free_packet (&_packet);
                decode_subtitle_packet ();
        }
 
        av_free_packet (&_packet);
+       return false;
 }
 
 /** @param data pointer to array of pointers to buffers.
 }
 
 /** @param data pointer to array of pointers to buffers.
@@ -311,76 +285,44 @@ FFmpegDecoder::bytes_per_audio_sample () const
 }
 
 void
 }
 
 void
-FFmpegDecoder::seek (VideoContent::Frame frame, bool accurate)
+FFmpegDecoder::seek (ContentTime time, bool accurate)
 {
 {
-       double const time_base = av_q2d (_format_context->streams[_video_stream]->time_base);
-
-       /* If we are doing an accurate seek, our initial shot will be 5 frames (5 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.
+       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.
        */
        */
-       int initial = frame;
-
-       if (accurate) {
-               initial -= 5;
-       }
 
 
-       if (initial < 0) {
-               initial = 0;
+       ContentTime pre_roll = accurate ? ContentTime::from_seconds (2) : ContentTime (0);
+       time -= pre_roll;
+       if (time < ContentTime (0)) {
+               time = ContentTime (0);
        }
 
        }
 
-       /* Initial seek time in the stream's timebase */
-       int64_t const initial_vt = ((initial / _ffmpeg_content->video_frame_rate()) - _pts_offset) / time_base;
+       ContentTime const u = time - _pts_offset;
+       int64_t s = u.seconds() / av_q2d (_format_context->streams[_video_stream]->time_base);
 
 
-       av_seek_frame (_format_context, _video_stream, initial_vt, AVSEEK_FLAG_BACKWARD);
-
-       avcodec_flush_buffers (video_codec_context());
-       if (_subtitle_codec_context) {
-               avcodec_flush_buffers (_subtitle_codec_context);
+       if (_ffmpeg_content->audio_stream ()) {
+               s = min (
+                       s, int64_t (u.seconds() / av_q2d (_ffmpeg_content->audio_stream()->stream(_format_context)->time_base))
+                       );
        }
 
        }
 
-       /* This !accurate is piling hack upon hack; setting _just_sought to true
-          even with accurate == true defeats our attempt to align the start
-          of the video and audio.  Here we disable that defeat when accurate == true
-          i.e. when we are making a DCP rather than just previewing one.
-          Ewww.  This should be gone in 2.0.
-       */
-       if (!accurate) {
-               _just_sought = true;
-       }
-       
-       _video_position = frame;
-       
-       if (frame == 0 || !accurate) {
-               /* We're already there, or we're as close as we need to be */
-               return;
+       /* Ridiculous empirical hack */
+       s--;
+       if (s < 0) {
+               s = 0;
        }
 
        }
 
-       while (1) {
-               int r = av_read_frame (_format_context, &_packet);
-               if (r < 0) {
-                       return;
-               }
-
-               if (_packet.stream_index != _video_stream) {
-                       av_free_packet (&_packet);
-                       continue;
-               }
-               
-               int finished = 0;
-               r = avcodec_decode_video2 (video_codec_context(), _frame, &finished, &_packet);
-               if (r >= 0 && finished) {
-                       _video_position = rint (
-                               (av_frame_get_best_effort_timestamp (_frame) * time_base + _pts_offset) * _ffmpeg_content->video_frame_rate()
-                               );
+       av_seek_frame (_format_context, _video_stream, s, 0);
 
 
-                       if (_video_position >= (frame - 1)) {
-                               av_free_packet (&_packet);
-                               break;
-                       }
-               }
-               
-               av_free_packet (&_packet);
+       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 ());
        }
 }
 
        }
 }
 
@@ -397,39 +339,23 @@ FFmpegDecoder::decode_audio_packet ()
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
 
                int frame_finished;
                int const decode_result = avcodec_decode_audio4 (audio_codec_context(), _frame, &frame_finished, &copy_packet);
+
                if (decode_result < 0) {
                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) {
-                       
-                       if (_audio_position == 0) {
-                               /* Where we are in the source, in seconds */
-                               double const pts = av_q2d (_format_context->streams[copy_packet.stream_index]->time_base)
-                                       * av_frame_get_best_effort_timestamp(_frame) + _pts_offset;
-
-                               if (pts > 0) {
-                                       /* Emit some silence */
-                                       shared_ptr<AudioBuffers> silence (
-                                               new AudioBuffers (
-                                                       _ffmpeg_content->audio_channels(),
-                                                       pts * _ffmpeg_content->content_audio_frame_rate()
-                                                       )
-                                               );
-                                       
-                                       silence->make_silent ();
-                                       audio (silence, _audio_position);
-                               }
-                       }
+                       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), _audio_position);
+
+                       audio (deinterleave_audio (_frame->data, data_size), ct);
                }
                        
                copy_packet.data += decode_result;
                }
                        
                copy_packet.data += decode_result;
@@ -450,18 +376,14 @@ 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;
        }
        } else {
                graph = *i;
        }
@@ -473,95 +395,25 @@ FFmpegDecoder::decode_video_packet ()
                shared_ptr<Image> image = i->first;
                
                if (i->second != AV_NOPTS_VALUE) {
                shared_ptr<Image> image = i->first;
                
                if (i->second != AV_NOPTS_VALUE) {
-
-                       double const pts = i->second * av_q2d (_format_context->streams[_video_stream]->time_base) + _pts_offset;
-
-                       if (_just_sought) {
-                               /* We just did a seek, so disable any attempts to correct for where we
-                                  are / should be.
-                               */
-                               _video_position = rint (pts * _ffmpeg_content->video_frame_rate ());
-                               _just_sought = false;
-                       }
-
-                       double const next = _video_position / _ffmpeg_content->video_frame_rate();
-                       double const one_frame = 1 / _ffmpeg_content->video_frame_rate ();
-                       double delta = pts - next;
-
-                       while (delta > one_frame) {
-                               /* This PTS is more than one frame forward in time of where we think we should be; emit
-                                  a black frame.
-                               */
-
-                               /* XXX: I think this should be a copy of the last frame... */
-                               boost::shared_ptr<Image> black (
-                                       new Image (
-                                               static_cast<AVPixelFormat> (_frame->format),
-                                               libdcp::Size (video_codec_context()->width, video_codec_context()->height),
-                                               true
-                                               )
-                                       );
-                               
-                               black->make_black ();
-                               video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
-                               delta -= one_frame;
-                       }
-
-                       if (delta > -one_frame) {
-                               /* This PTS is within a frame of being right; emit this (otherwise it will be dropped) */
-                               video (shared_ptr<ImageProxy> (new RawImageProxy (image)), false, _video_position);
-                       }
-                               
+                       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()) {
-               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) {
-               throw DecodeError (N_("could not find subtitle decoder"));
-       }
-       
-       if (avcodec_open2 (_subtitle_codec_context, _subtitle_codec, 0) < 0) {
-               throw DecodeError (N_("could not open subtitle decoder"));
-       }
-}
-
-bool
-FFmpegDecoder::done () const
-{
-       bool const vd = !_decode_video || (_video_position >= _ffmpeg_content->video_length());
-       bool const ad = !_decode_audio || !_ffmpeg_content->audio_stream() || (_audio_position >= _ffmpeg_content->audio_length());
-       return vd && ad;
-}
        
 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;
        }
 
@@ -569,31 +421,29 @@ 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) + _pts_offset;
-
-       /* hence start time for this sub */
-       Time const from = (packet_time + (double (sub.start_display_time) / 1e3)) * TIME_HZ;
-       Time 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;
        }
 
        /* Note RGBA is expressed little-endian, so the first byte in the word is R, second
           G, third B, fourth A.
        */
        }
 
        /* 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, libdcp::Size (rect->w, rect->h), true));
+       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];
@@ -615,20 +465,24 @@ FFmpegDecoder::decode_subtitle_packet ()
                out_p += image->stride()[0] / sizeof (uint32_t);
        }
 
                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);
 }
+
+list<ContentTimePeriod>
+FFmpegDecoder::subtitles_during (ContentTimePeriod p) const
+{
+       return _ffmpeg_content->subtitles_during (p);
+}