Speculative fix for slow-downs and large memory consumption.
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <stdint.h>
21 #include "player.h"
22 #include "film.h"
23 #include "ffmpeg_decoder.h"
24 #include "ffmpeg_content.h"
25 #include "still_image_decoder.h"
26 #include "still_image_content.h"
27 #include "moving_image_decoder.h"
28 #include "moving_image_content.h"
29 #include "sndfile_decoder.h"
30 #include "sndfile_content.h"
31 #include "subtitle_content.h"
32 #include "playlist.h"
33 #include "job.h"
34 #include "image.h"
35 #include "ratio.h"
36 #include "resampler.h"
37 #include "log.h"
38 #include "scaler.h"
39
40 using std::list;
41 using std::cout;
42 using std::min;
43 using std::max;
44 using std::vector;
45 using std::pair;
46 using std::map;
47 using boost::shared_ptr;
48 using boost::weak_ptr;
49 using boost::dynamic_pointer_cast;
50
51 class Piece
52 {
53 public:
54         Piece (shared_ptr<Content> c)
55                 : content (c)
56                 , video_position (c->position ())
57                 , audio_position (c->position ())
58                 , repeat_to_do (0)
59                 , repeat_done (0)
60         {}
61         
62         Piece (shared_ptr<Content> c, shared_ptr<Decoder> d)
63                 : content (c)
64                 , decoder (d)
65                 , video_position (c->position ())
66                 , audio_position (c->position ())
67         {}
68
69         /** Set this piece to repeat a video frame a given number of times */
70         void set_repeat (IncomingVideo video, int num)
71         {
72                 repeat_video = video;
73                 repeat_to_do = num;
74                 repeat_done = 0;
75         }
76
77         void reset_repeat ()
78         {
79                 repeat_video.image.reset ();
80                 repeat_to_do = 0;
81                 repeat_done = 0;
82         }
83
84         bool repeating () const
85         {
86                 return repeat_done != repeat_to_do;
87         }
88
89         void repeat (Player* player)
90         {
91                 player->process_video (
92                         repeat_video.weak_piece,
93                         repeat_video.image,
94                         repeat_video.eyes,
95                         repeat_done > 0,
96                         repeat_video.frame,
97                         (repeat_done + 1) * (TIME_HZ / player->_film->video_frame_rate ())
98                         );
99
100                 ++repeat_done;
101         }
102         
103         shared_ptr<Content> content;
104         shared_ptr<Decoder> decoder;
105         /** Time of the last video we emitted relative to the start of the DCP */
106         Time video_position;
107         /** Time of the last audio we emitted relative to the start of the DCP */
108         Time audio_position;
109
110         IncomingVideo repeat_video;
111         int repeat_to_do;
112         int repeat_done;
113 };
114
115 Player::Player (shared_ptr<const Film> f, shared_ptr<const Playlist> p)
116         : _film (f)
117         , _playlist (p)
118         , _video (true)
119         , _audio (true)
120         , _have_valid_pieces (false)
121         , _video_position (0)
122         , _audio_position (0)
123         , _audio_merger (f->audio_channels(), bind (&Film::time_to_audio_frames, f.get(), _1), bind (&Film::audio_frames_to_time, f.get(), _1))
124         , _last_emit_was_black (false)
125 {
126         _playlist_changed_connection = _playlist->Changed.connect (bind (&Player::playlist_changed, this));
127         _playlist_content_changed_connection = _playlist->ContentChanged.connect (bind (&Player::content_changed, this, _1, _2, _3));
128         _film_changed_connection = _film->Changed.connect (bind (&Player::film_changed, this, _1));
129         set_video_container_size (fit_ratio_within (_film->container()->ratio (), _film->full_frame ()));
130 }
131
132 void
133 Player::disable_video ()
134 {
135         _video = false;
136 }
137
138 void
139 Player::disable_audio ()
140 {
141         _audio = false;
142 }
143
144 bool
145 Player::pass ()
146 {
147         if (!_have_valid_pieces) {
148                 setup_pieces ();
149                 _have_valid_pieces = true;
150         }
151
152         Time earliest_t = TIME_MAX;
153         shared_ptr<Piece> earliest;
154         enum {
155                 VIDEO,
156                 AUDIO
157         } type = VIDEO;
158
159         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
160                 if ((*i)->decoder->done ()) {
161                         continue;
162                 }
163
164                 shared_ptr<VideoDecoder> vd = dynamic_pointer_cast<VideoDecoder> ((*i)->decoder);
165                 shared_ptr<AudioDecoder> ad = dynamic_pointer_cast<AudioDecoder> ((*i)->decoder);
166
167                 if (_video && vd) {
168                         if ((*i)->video_position < earliest_t) {
169                                 earliest_t = (*i)->video_position;
170                                 earliest = *i;
171                                 type = VIDEO;
172                         }
173                 }
174
175                 if (_audio && ad && ad->has_audio ()) {
176                         if ((*i)->audio_position < earliest_t) {
177                                 earliest_t = (*i)->audio_position;
178                                 earliest = *i;
179                                 type = AUDIO;
180                         }
181                 }
182         }
183
184         if (!earliest) {
185                 flush ();
186                 return true;
187         }
188
189         switch (type) {
190         case VIDEO:
191                 if (earliest_t > _video_position) {
192                         emit_black ();
193                 } else {
194                         if (earliest->repeating ()) {
195                                 earliest->repeat (this);
196                         } else {
197                                 earliest->decoder->pass ();
198                         }
199                 }
200                 break;
201
202         case AUDIO:
203                 if (earliest_t > _audio_position) {
204                         emit_silence (_film->time_to_audio_frames (earliest_t - _audio_position));
205                 } else {
206                         earliest->decoder->pass ();
207
208                         if (earliest->decoder->done()) {
209                                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (earliest->content);
210                                 assert (ac);
211                                 shared_ptr<Resampler> re = resampler (ac, false);
212                                 if (re) {
213                                         shared_ptr<const AudioBuffers> b = re->flush ();
214                                         if (b->frames ()) {
215                                                 process_audio (earliest, b, ac->audio_length ());
216                                         }
217                                 }
218                         }
219                 }
220                 break;
221         }
222
223         if (_audio) {
224                 Time audio_done_up_to = TIME_MAX;
225                 for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
226                         if ((*i)->decoder->done ()) {
227                                 continue;
228                         }
229
230                         if (dynamic_pointer_cast<AudioDecoder> ((*i)->decoder)) {
231                                 audio_done_up_to = min (audio_done_up_to, (*i)->audio_position);
232                         }
233                 }
234
235                 TimedAudioBuffers<Time> tb = _audio_merger.pull (audio_done_up_to);
236                 Audio (tb.audio, tb.time);
237                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
238         }
239                 
240         return false;
241 }
242
243 /** @param extra Amount of extra time to add to the content frame's time (for repeat) */
244 void
245 Player::process_video (weak_ptr<Piece> weak_piece, shared_ptr<const Image> image, Eyes eyes, bool same, VideoContent::Frame frame, Time extra)
246 {
247         /* Keep a note of what came in so that we can repeat it if required */
248         _last_incoming_video.weak_piece = weak_piece;
249         _last_incoming_video.image = image;
250         _last_incoming_video.eyes = eyes;
251         _last_incoming_video.same = same;
252         _last_incoming_video.frame = frame;
253         _last_incoming_video.extra = extra;
254         
255         shared_ptr<Piece> piece = weak_piece.lock ();
256         if (!piece) {
257                 return;
258         }
259
260         shared_ptr<VideoContent> content = dynamic_pointer_cast<VideoContent> (piece->content);
261         assert (content);
262
263         FrameRateConversion frc (content->video_frame_rate(), _film->video_frame_rate());
264         if (frc.skip && (frame % 2) == 1) {
265                 return;
266         }
267
268         Time const relative_time = (frame * frc.factor() * TIME_HZ / _film->video_frame_rate());
269         if (content->trimmed (relative_time)) {
270                 return;
271         }
272
273         /* Convert to RGB first, as FFmpeg doesn't seem to like handling YUV images with odd widths */
274         shared_ptr<Image> work_image = image->scale (image->size (), _film->scaler(), PIX_FMT_RGB24, true);
275
276         work_image = work_image->crop (content->crop(), true);
277
278         float const ratio = content->ratio() ? content->ratio()->ratio() : content->video_size_after_crop().ratio();
279         libdcp::Size image_size = fit_ratio_within (ratio, _video_container_size);
280         
281         work_image = work_image->scale (image_size, _film->scaler(), PIX_FMT_RGB24, true);
282
283         Time time = content->position() + relative_time + extra - content->trim_start ();
284             
285         if (_film->with_subtitles () && _out_subtitle.image && time >= _out_subtitle.from && time <= _out_subtitle.to) {
286                 work_image->alpha_blend (_out_subtitle.image, _out_subtitle.position);
287         }
288
289         if (image_size != _video_container_size) {
290                 assert (image_size.width <= _video_container_size.width);
291                 assert (image_size.height <= _video_container_size.height);
292                 shared_ptr<Image> im (new Image (PIX_FMT_RGB24, _video_container_size, true));
293                 im->make_black ();
294                 im->copy (work_image, Position<int> ((_video_container_size.width - image_size.width) / 2, (_video_container_size.height - image_size.height) / 2));
295                 work_image = im;
296         }
297
298 #ifdef DCPOMATIC_DEBUG
299         _last_video = piece->content;
300 #endif
301
302         Video (work_image, eyes, content->colour_conversion(), same, time);
303
304         time += TIME_HZ / _film->video_frame_rate();
305         _last_emit_was_black = false;
306         _video_position = piece->video_position = time;
307
308         if (frc.repeat > 1 && !piece->repeating ()) {
309                 piece->set_repeat (_last_incoming_video, frc.repeat - 1);
310         }
311 }
312
313 void
314 Player::process_audio (weak_ptr<Piece> weak_piece, shared_ptr<const AudioBuffers> audio, AudioContent::Frame frame)
315 {
316         shared_ptr<Piece> piece = weak_piece.lock ();
317         if (!piece) {
318                 return;
319         }
320
321         shared_ptr<AudioContent> content = dynamic_pointer_cast<AudioContent> (piece->content);
322         assert (content);
323
324         /* Gain */
325         if (content->audio_gain() != 0) {
326                 shared_ptr<AudioBuffers> gain (new AudioBuffers (audio));
327                 gain->apply_gain (content->audio_gain ());
328                 audio = gain;
329         }
330
331         /* Resample */
332         if (content->content_audio_frame_rate() != content->output_audio_frame_rate()) {
333                 shared_ptr<Resampler> r = resampler (content, true);
334                 pair<shared_ptr<const AudioBuffers>, AudioContent::Frame> ro = r->run (audio, frame);
335                 audio = ro.first;
336                 frame = ro.second;
337         }
338         
339         Time const relative_time = _film->audio_frames_to_time (frame);
340
341         if (content->trimmed (relative_time)) {
342                 return;
343         }
344
345         Time time = content->position() + (content->audio_delay() * TIME_HZ / 1000) + relative_time - content->trim_start ();
346         
347         /* Remap channels */
348         shared_ptr<AudioBuffers> dcp_mapped (new AudioBuffers (_film->audio_channels(), audio->frames()));
349         dcp_mapped->make_silent ();
350         list<pair<int, libdcp::Channel> > map = content->audio_mapping().content_to_dcp ();
351         for (list<pair<int, libdcp::Channel> >::iterator i = map.begin(); i != map.end(); ++i) {
352                 if (i->first < audio->channels() && i->second < dcp_mapped->channels()) {
353                         dcp_mapped->accumulate_channel (audio.get(), i->first, i->second);
354                 }
355         }
356
357         audio = dcp_mapped;
358
359         /* We must cut off anything that comes before the start of all time */
360         if (time < 0) {
361                 int const frames = - time * _film->audio_frame_rate() / TIME_HZ;
362                 if (frames >= audio->frames ()) {
363                         return;
364                 }
365
366                 shared_ptr<AudioBuffers> trimmed (new AudioBuffers (audio->channels(), audio->frames() - frames));
367                 trimmed->copy_from (audio.get(), audio->frames() - frames, frames, 0);
368
369                 audio = trimmed;
370                 time = 0;
371         }
372
373         _audio_merger.push (audio, time);
374         piece->audio_position += _film->audio_frames_to_time (audio->frames ());
375 }
376
377 void
378 Player::flush ()
379 {
380         TimedAudioBuffers<Time> tb = _audio_merger.flush ();
381         if (tb.audio) {
382                 Audio (tb.audio, tb.time);
383                 _audio_position += _film->audio_frames_to_time (tb.audio->frames ());
384         }
385
386         while (_video_position < _audio_position) {
387                 emit_black ();
388         }
389
390         while (_audio_position < _video_position) {
391                 emit_silence (_film->time_to_audio_frames (_video_position - _audio_position));
392         }
393         
394 }
395
396 /** Seek so that the next pass() will yield (approximately) the requested frame.
397  *  Pass accurate = true to try harder to get close to the request.
398  *  @return true on error
399  */
400 void
401 Player::seek (Time t, bool accurate)
402 {
403         if (!_have_valid_pieces) {
404                 setup_pieces ();
405                 _have_valid_pieces = true;
406         }
407
408         if (_pieces.empty ()) {
409                 return;
410         }
411
412         for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
413                 shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> ((*i)->content);
414                 if (!vc) {
415                         continue;
416                 }
417
418                 /* s is the offset of t from the start position of this content */
419                 Time s = t - vc->position ();
420                 s = max (static_cast<Time> (0), s);
421                 s = min (vc->length_after_trim(), s);
422
423                 /* Hence set the piece positions to the `global' time */
424                 (*i)->video_position = (*i)->audio_position = vc->position() + s;
425
426                 /* And seek the decoder */
427                 dynamic_pointer_cast<VideoDecoder>((*i)->decoder)->seek (
428                         vc->time_to_content_video_frames (s + vc->trim_start ()), accurate
429                         );
430
431                 (*i)->reset_repeat ();
432         }
433
434         _video_position = _audio_position = t;
435
436         /* XXX: don't seek audio because we don't need to... */
437 }
438
439 void
440 Player::setup_pieces ()
441 {
442         list<shared_ptr<Piece> > old_pieces = _pieces;
443
444         _pieces.clear ();
445
446         ContentList content = _playlist->content ();
447         sort (content.begin(), content.end(), ContentSorter ());
448
449         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
450
451                 shared_ptr<Piece> piece (new Piece (*i));
452
453                 /* XXX: into content? */
454
455                 shared_ptr<const FFmpegContent> fc = dynamic_pointer_cast<const FFmpegContent> (*i);
456                 if (fc) {
457                         shared_ptr<FFmpegDecoder> fd (new FFmpegDecoder (_film, fc, _video, _audio));
458                         
459                         fd->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
460                         fd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
461                         fd->Subtitle.connect (bind (&Player::process_subtitle, this, weak_ptr<Piece> (piece), _1, _2, _3, _4));
462
463                         fd->seek (fc->time_to_content_video_frames (fc->trim_start ()), true);
464                         piece->decoder = fd;
465                 }
466                 
467                 shared_ptr<const StillImageContent> ic = dynamic_pointer_cast<const StillImageContent> (*i);
468                 if (ic) {
469                         shared_ptr<StillImageDecoder> id;
470                         
471                         /* See if we can re-use an old StillImageDecoder */
472                         for (list<shared_ptr<Piece> >::const_iterator j = old_pieces.begin(); j != old_pieces.end(); ++j) {
473                                 shared_ptr<StillImageDecoder> imd = dynamic_pointer_cast<StillImageDecoder> ((*j)->decoder);
474                                 if (imd && imd->content() == ic) {
475                                         id = imd;
476                                 }
477                         }
478
479                         if (!id) {
480                                 id.reset (new StillImageDecoder (_film, ic));
481                                 id->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
482                         }
483
484                         piece->decoder = id;
485                 }
486
487                 shared_ptr<const MovingImageContent> mc = dynamic_pointer_cast<const MovingImageContent> (*i);
488                 if (mc) {
489                         shared_ptr<MovingImageDecoder> md;
490
491                         if (!md) {
492                                 md.reset (new MovingImageDecoder (_film, mc));
493                                 md->Video.connect (bind (&Player::process_video, this, weak_ptr<Piece> (piece), _1, _2, _3, _4, 0));
494                         }
495
496                         piece->decoder = md;
497                 }
498
499                 shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
500                 if (sc) {
501                         shared_ptr<AudioDecoder> sd (new SndfileDecoder (_film, sc));
502                         sd->Audio.connect (bind (&Player::process_audio, this, weak_ptr<Piece> (piece), _1, _2));
503
504                         piece->decoder = sd;
505                 }
506
507                 _pieces.push_back (piece);
508         }
509 }
510
511 void
512 Player::content_changed (weak_ptr<Content> w, int property, bool frequent)
513 {
514         shared_ptr<Content> c = w.lock ();
515         if (!c) {
516                 return;
517         }
518
519         if (
520                 property == ContentProperty::POSITION || property == ContentProperty::LENGTH ||
521                 property == ContentProperty::TRIM_START || property == ContentProperty::TRIM_END
522                 ) {
523                 
524                 _have_valid_pieces = false;
525                 Changed (frequent);
526
527         } else if (property == SubtitleContentProperty::SUBTITLE_OFFSET || property == SubtitleContentProperty::SUBTITLE_SCALE) {
528
529                 update_subtitle ();
530                 Changed (frequent);
531
532         } else if (
533                 property == VideoContentProperty::VIDEO_FRAME_TYPE || property == VideoContentProperty::VIDEO_CROP ||
534                 property == VideoContentProperty::VIDEO_RATIO
535                 ) {
536                 
537                 Changed (frequent);
538
539         } else if (property == ContentProperty::PATH) {
540
541                 Changed (frequent);
542         }
543 }
544
545 void
546 Player::playlist_changed ()
547 {
548         _have_valid_pieces = false;
549         Changed (false);
550 }
551
552 void
553 Player::set_video_container_size (libdcp::Size s)
554 {
555         _video_container_size = s;
556         _black_frame.reset (new Image (PIX_FMT_RGB24, _video_container_size, true));
557         _black_frame->make_black ();
558 }
559
560 shared_ptr<Resampler>
561 Player::resampler (shared_ptr<AudioContent> c, bool create)
562 {
563         map<shared_ptr<AudioContent>, shared_ptr<Resampler> >::iterator i = _resamplers.find (c);
564         if (i != _resamplers.end ()) {
565                 return i->second;
566         }
567
568         if (!create) {
569                 return shared_ptr<Resampler> ();
570         }
571
572         _film->log()->log (
573                 String::compose (
574                         "Creating new resampler for %1 to %2 with %3 channels", c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()
575                         )
576                 );
577         
578         shared_ptr<Resampler> r (new Resampler (c->content_audio_frame_rate(), c->output_audio_frame_rate(), c->audio_channels()));
579         _resamplers[c] = r;
580         return r;
581 }
582
583 void
584 Player::emit_black ()
585 {
586 #ifdef DCPOMATIC_DEBUG
587         _last_video.reset ();
588 #endif
589
590         Video (_black_frame, EYES_BOTH, ColourConversion(), _last_emit_was_black, _video_position);
591         _video_position += _film->video_frames_to_time (1);
592         _last_emit_was_black = true;
593 }
594
595 void
596 Player::emit_silence (OutputAudioFrame most)
597 {
598         if (most == 0) {
599                 return;
600         }
601         
602         OutputAudioFrame N = min (most, _film->audio_frame_rate() / 2);
603         shared_ptr<AudioBuffers> silence (new AudioBuffers (_film->audio_channels(), N));
604         silence->make_silent ();
605         Audio (silence, _audio_position);
606         _audio_position += _film->audio_frames_to_time (N);
607 }
608
609 void
610 Player::film_changed (Film::Property p)
611 {
612         /* Here we should notice Film properties that affect our output, and
613            alert listeners that our output now would be different to how it was
614            last time we were run.
615         */
616
617         if (p == Film::SCALER || p == Film::WITH_SUBTITLES || p == Film::CONTAINER) {
618                 Changed (false);
619         }
620 }
621
622 void
623 Player::process_subtitle (weak_ptr<Piece> weak_piece, shared_ptr<Image> image, dcpomatic::Rect<double> rect, Time from, Time to)
624 {
625         _in_subtitle.piece = weak_piece;
626         _in_subtitle.image = image;
627         _in_subtitle.rect = rect;
628         _in_subtitle.from = from;
629         _in_subtitle.to = to;
630
631         update_subtitle ();
632 }
633
634 void
635 Player::update_subtitle ()
636 {
637         shared_ptr<Piece> piece = _in_subtitle.piece.lock ();
638         if (!piece) {
639                 return;
640         }
641
642         if (!_in_subtitle.image) {
643                 _out_subtitle.image.reset ();
644                 return;
645         }
646
647         shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (piece->content);
648         assert (sc);
649
650         dcpomatic::Rect<double> in_rect = _in_subtitle.rect;
651         libdcp::Size scaled_size;
652
653         in_rect.y += sc->subtitle_offset ();
654
655         /* We will scale the subtitle up to fit _video_container_size, and also by the additional subtitle_scale */
656         scaled_size.width = in_rect.width * _video_container_size.width * sc->subtitle_scale ();
657         scaled_size.height = in_rect.height * _video_container_size.height * sc->subtitle_scale ();
658
659         /* Then we need a corrective translation, consisting of two parts:
660          *
661          * 1.  that which is the result of the scaling of the subtitle by _video_container_size; this will be
662          *     rect.x * _video_container_size.width and rect.y * _video_container_size.height.
663          *
664          * 2.  that to shift the origin of the scale by subtitle_scale to the centre of the subtitle; this will be
665          *     (width_before_subtitle_scale * (1 - subtitle_scale) / 2) and
666          *     (height_before_subtitle_scale * (1 - subtitle_scale) / 2).
667          *
668          * Combining these two translations gives these expressions.
669          */
670         
671         _out_subtitle.position.x = rint (_video_container_size.width * (in_rect.x + (in_rect.width * (1 - sc->subtitle_scale ()) / 2)));
672         _out_subtitle.position.y = rint (_video_container_size.height * (in_rect.y + (in_rect.height * (1 - sc->subtitle_scale ()) / 2)));
673         
674         _out_subtitle.image = _in_subtitle.image->scale (
675                 scaled_size,
676                 Scaler::from_id ("bicubic"),
677                 _in_subtitle.image->pixel_format (),
678                 true
679                 );
680         _out_subtitle.from = _in_subtitle.from + piece->content->position ();
681         _out_subtitle.to = _in_subtitle.to + piece->content->position ();
682 }
683
684 /** Re-emit the last frame that was emitted, using current settings for crop, ratio, scaler and subtitles.
685  *  @return false if this could not be done.
686  */
687 bool
688 Player::repeat_last_video ()
689 {
690         if (!_last_incoming_video.image) {
691                 return false;
692         }
693
694         process_video (
695                 _last_incoming_video.weak_piece,
696                 _last_incoming_video.image,
697                 _last_incoming_video.eyes,
698                 _last_incoming_video.same,
699                 _last_incoming_video.frame,
700                 _last_incoming_video.extra
701                 );
702
703         return true;
704 }