0e6e908b3245c88fb8e9851e4e898a452c8d64bb
[dcpomatic.git] / src / wx / film_viewer.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  src/film_viewer.cc
23  *  @brief A wx widget to view a preview of a Film.
24  */
25
26
27 #include "closed_captions_dialog.h"
28 #include "film_viewer.h"
29 #include "gl_video_view.h"
30 #include "nag_dialog.h"
31 #include "playhead_to_frame_dialog.h"
32 #include "playhead_to_timecode_dialog.h"
33 #include "simple_video_view.h"
34 #include "wx_util.h"
35 #include "lib/butler.h"
36 #include "lib/compose.hpp"
37 #include "lib/config.h"
38 #include "lib/dcpomatic_log.h"
39 #include "lib/examine_content_job.h"
40 #include "lib/exceptions.h"
41 #include "lib/film.h"
42 #include "lib/filter.h"
43 #include "lib/image.h"
44 #include "lib/job_manager.h"
45 #include "lib/log.h"
46 #include "lib/player.h"
47 #include "lib/player_video.h"
48 #include "lib/ratio.h"
49 #include "lib/text_content.h"
50 #include "lib/timer.h"
51 #include "lib/util.h"
52 #include "lib/video_content.h"
53 #include "lib/video_decoder.h"
54 #include <dcp/exceptions.h>
55 #include <dcp/warnings.h>
56 extern "C" {
57 #include <libavutil/pixfmt.h>
58 }
59 LIBDCP_DISABLE_WARNINGS
60 #include <wx/tglbtn.h>
61 LIBDCP_ENABLE_WARNINGS
62 #include <iomanip>
63
64
65 using std::bad_alloc;
66 using std::dynamic_pointer_cast;
67 using std::make_shared;
68 using std::max;
69 using std::shared_ptr;
70 using std::string;
71 using std::vector;
72 using boost::optional;
73 #if BOOST_VERSION >= 106100
74 using namespace boost::placeholders;
75 #endif
76 using dcp::Size;
77 using namespace dcpomatic;
78
79
80 static
81 int
82 rtaudio_callback (void* out, void *, unsigned int frames, double, RtAudioStreamStatus, void* data)
83 {
84         return reinterpret_cast<FilmViewer*>(data)->audio_callback (out, frames);
85 }
86
87
88 FilmViewer::FilmViewer (wxWindow* p)
89         : _audio (DCPOMATIC_RTAUDIO_API)
90         , _closed_captions_dialog (new ClosedCaptionsDialog(p, this))
91 {
92 #if wxCHECK_VERSION(3, 1, 0)
93         switch (Config::instance()->video_view_type()) {
94         case Config::VIDEO_VIEW_OPENGL:
95                 _video_view = std::make_shared<GLVideoView>(this, p);
96                 break;
97         case Config::VIDEO_VIEW_SIMPLE:
98                 _video_view = std::make_shared<SimpleVideoView>(this, p);
99                 break;
100         }
101 #else
102         _video_view = std::make_shared<SimpleVideoView>(this, p);
103 #endif
104
105         _video_view->Sized.connect (boost::bind(&FilmViewer::video_view_sized, this));
106         _video_view->TooManyDropped.connect (boost::bind(boost::ref(TooManyDropped)));
107
108         set_film (shared_ptr<Film>());
109
110         _config_changed_connection = Config::instance()->Changed.connect(bind(&FilmViewer::config_changed, this, _1));
111         config_changed (Config::SOUND_OUTPUT);
112 }
113
114
115 FilmViewer::~FilmViewer ()
116 {
117         stop ();
118 }
119
120
121 /** Ask for ::idle_handler() to be called next time we are idle */
122 void
123 FilmViewer::request_idle_display_next_frame ()
124 {
125         if (_idle_get) {
126                 return;
127         }
128
129         _idle_get = true;
130         DCPOMATIC_ASSERT (signal_manager);
131         signal_manager->when_idle (boost::bind(&FilmViewer::idle_handler, this));
132 }
133
134
135 void
136 FilmViewer::idle_handler ()
137 {
138         if (!_idle_get) {
139                 return;
140         }
141
142         if (_video_view->display_next_frame(true) == VideoView::AGAIN) {
143                 /* get() could not complete quickly so we'll try again later */
144                 signal_manager->when_idle (boost::bind(&FilmViewer::idle_handler, this));
145         } else {
146                 _idle_get = false;
147         }
148 }
149
150
151 void
152 FilmViewer::set_film (shared_ptr<Film> film)
153 {
154         if (_film == film) {
155                 return;
156         }
157
158         _film = film;
159
160         _video_view->clear ();
161         _closed_captions_dialog->clear ();
162
163         if (!_film) {
164                 _player.reset ();
165                 recreate_butler ();
166                 _video_view->update ();
167                 return;
168         }
169
170         try {
171                 _player = make_shared<Player>(_film, _optimise_for_j2k ? Image::Alignment::COMPACT : Image::Alignment::PADDED);
172                 _player->set_fast ();
173                 if (_dcp_decode_reduction) {
174                         _player->set_dcp_decode_reduction (_dcp_decode_reduction);
175                 }
176         } catch (bad_alloc &) {
177                 error_dialog (_video_view->get(), _("There is not enough free memory to do that."));
178                 _film.reset ();
179                 return;
180         }
181
182         _player->set_always_burn_open_subtitles ();
183         _player->set_play_referenced ();
184
185         _film->Change.connect (boost::bind (&FilmViewer::film_change, this, _1, _2));
186         _film->LengthChange.connect (boost::bind(&FilmViewer::film_length_change, this));
187         _player->Change.connect (boost::bind (&FilmViewer::player_change, this, _1, _2, _3));
188
189         film_change (ChangeType::DONE, Film::Property::VIDEO_FRAME_RATE);
190         film_change (ChangeType::DONE, Film::Property::THREE_D);
191         film_length_change ();
192
193         /* Keep about 1 second's worth of history samples */
194         _latency_history_count = _film->audio_frame_rate() / _audio_block_size;
195
196         _closed_captions_dialog->update_tracks (_film);
197
198         recreate_butler ();
199
200         calculate_sizes ();
201         slow_refresh ();
202 }
203
204
205 void
206 FilmViewer::recreate_butler ()
207 {
208         suspend ();
209         _butler.reset ();
210
211         if (!_film) {
212                 resume ();
213                 return;
214         }
215
216 #if wxCHECK_VERSION(3, 1, 0)
217         auto const j2k_gl_optimised = dynamic_pointer_cast<GLVideoView>(_video_view) && _optimise_for_j2k;
218 #else
219         auto const j2k_gl_optimised = false;
220 #endif
221
222         _butler = std::make_shared<Butler>(
223                 _film,
224                 _player,
225                 Config::instance()->audio_mapping(_audio_channels),
226                 _audio_channels,
227                 boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24),
228                 VideoRange::FULL,
229                 j2k_gl_optimised ? Image::Alignment::COMPACT : Image::Alignment::PADDED,
230                 true,
231                 j2k_gl_optimised
232                 );
233
234         if (!Config::instance()->sound() && !_audio.isStreamOpen()) {
235                 _butler->disable_audio ();
236         }
237
238         _closed_captions_dialog->set_butler (_butler);
239
240         resume ();
241 }
242
243
244 void
245 FilmViewer::set_outline_content (bool o)
246 {
247         _outline_content = o;
248         _video_view->update ();
249 }
250
251
252 void
253 FilmViewer::set_outline_subtitles (optional<dcpomatic::Rect<double>> rect)
254 {
255         _outline_subtitles = rect;
256         _video_view->update ();
257 }
258
259
260 void
261 FilmViewer::set_eyes (Eyes e)
262 {
263         _video_view->set_eyes (e);
264         slow_refresh ();
265 }
266
267
268 void
269 FilmViewer::video_view_sized ()
270 {
271         calculate_sizes ();
272         if (!quick_refresh()) {
273                 slow_refresh ();
274         }
275 }
276
277
278 void
279 FilmViewer::calculate_sizes ()
280 {
281         if (!_film || !_player) {
282                 return;
283         }
284
285         auto const container = _film->container ();
286
287         auto const scale = dpi_scale_factor (_video_view->get());
288         int const video_view_width = std::round(_video_view->get()->GetSize().x * scale);
289         int const video_view_height = std::round(_video_view->get()->GetSize().y * scale);
290
291         auto const view_ratio = float(video_view_width) / video_view_height;
292         auto const film_ratio = container ? container->ratio () : 1.78;
293
294         dcp::Size out_size;
295         if (view_ratio < film_ratio) {
296                 /* panel is less widscreen than the film; clamp width */
297                 out_size.width = video_view_width;
298                 out_size.height = lrintf (out_size.width / film_ratio);
299         } else {
300                 /* panel is more widescreen than the film; clamp height */
301                 out_size.height = video_view_height;
302                 out_size.width = lrintf (out_size.height * film_ratio);
303         }
304
305         /* Catch silly values */
306         out_size.width = max (64, out_size.width);
307         out_size.height = max (64, out_size.height);
308
309         _player->set_video_container_size (out_size);
310 }
311
312
313 void
314 FilmViewer::suspend ()
315 {
316         ++_suspended;
317         if (_audio.isStreamRunning()) {
318                 _audio.abortStream();
319         }
320 }
321
322
323 void
324 FilmViewer::start_audio_stream_if_open ()
325 {
326         if (_audio.isStreamOpen()) {
327                 _audio.setStreamTime (_video_view->position().seconds());
328                 try {
329                         _audio.startStream ();
330                 } catch (RtAudioError& e) {
331                         _audio_channels = 0;
332                         error_dialog (
333                                 _video_view->get(),
334                                 _("There was a problem starting audio playback.  Please try another audio output device in Preferences."), std_to_wx(e.what())
335                                 );
336                 }
337         }
338 }
339
340
341 void
342 FilmViewer::resume ()
343 {
344         DCPOMATIC_ASSERT (_suspended > 0);
345         --_suspended;
346         if (_playing && !_suspended) {
347                 start_audio_stream_if_open ();
348                 _video_view->start ();
349         }
350 }
351
352
353 void
354 FilmViewer::start ()
355 {
356         if (!_film) {
357                 return;
358         }
359
360         auto v = PlaybackPermitted ();
361         if (v && !*v) {
362                 /* Computer says no */
363                 return;
364         }
365
366         /* We are about to set up the audio stream from the position of the video view.
367            If there is `lazy' seek in progress we need to wait for it to go through so that
368            _video_view->position() gives us a sensible answer.
369          */
370         while (_idle_get) {
371                 idle_handler ();
372         }
373
374         /* Take the video view's idea of position as our `playhead' and start the
375            audio stream (which is the timing reference) there.
376          */
377         start_audio_stream_if_open ();
378
379         _playing = true;
380         /* Calling start() below may directly result in Stopped being emitted, and if that
381          * happens we want it to come after the Started signal, so do that first.
382          */
383         Started ();
384         _video_view->start ();
385 }
386
387
388 bool
389 FilmViewer::stop ()
390 {
391         if (_audio.isStreamRunning()) {
392                 /* stop stream and discard any remaining queued samples */
393                 _audio.abortStream ();
394         }
395
396         if (!_playing) {
397                 return false;
398         }
399
400         _playing = false;
401         _video_view->stop ();
402         Stopped ();
403
404         _video_view->rethrow ();
405         return true;
406 }
407
408
409 void
410 FilmViewer::player_change (ChangeType type, int property, bool frequent)
411 {
412         if (type != ChangeType::DONE || frequent) {
413                 return;
414         }
415
416         if (_coalesce_player_changes) {
417                 _pending_player_changes.push_back (property);
418                 return;
419         }
420
421         player_change ({property});
422 }
423
424
425 void
426 FilmViewer::player_change (vector<int> properties)
427 {
428         calculate_sizes ();
429
430         bool try_quick_refresh = false;
431         bool update_ccap_tracks = false;
432
433         for (auto i: properties) {
434                 if (
435                         i == VideoContentProperty::CROP ||
436                         i == VideoContentProperty::CUSTOM_RATIO ||
437                         i == VideoContentProperty::CUSTOM_SIZE ||
438                         i == VideoContentProperty::FADE_IN ||
439                         i == VideoContentProperty::FADE_OUT ||
440                         i == VideoContentProperty::COLOUR_CONVERSION ||
441                         i == PlayerProperty::VIDEO_CONTAINER_SIZE ||
442                         i == PlayerProperty::FILM_CONTAINER
443                    ) {
444                         try_quick_refresh = true;
445                 }
446
447                 if (i == TextContentProperty::USE || i == TextContentProperty::TYPE || i == TextContentProperty::DCP_TRACK) {
448                         update_ccap_tracks = true;
449                 }
450         }
451
452         if (!try_quick_refresh || !quick_refresh()) {
453                 slow_refresh ();
454         }
455
456         if (update_ccap_tracks) {
457                 _closed_captions_dialog->update_tracks (_film);
458         }
459 }
460
461
462 void
463 FilmViewer::film_change (ChangeType type, Film::Property p)
464 {
465         if (type != ChangeType::DONE) {
466                 return;
467         }
468
469         if (p == Film::Property::AUDIO_CHANNELS) {
470                 recreate_butler ();
471         } else if (p == Film::Property::VIDEO_FRAME_RATE) {
472                 _video_view->set_video_frame_rate (_film->video_frame_rate());
473         } else if (p == Film::Property::THREE_D) {
474                 _video_view->set_three_d (_film->three_d());
475         } else if (p == Film::Property::CONTENT) {
476                 _closed_captions_dialog->update_tracks (_film);
477         }
478 }
479
480
481 void
482 FilmViewer::film_length_change ()
483 {
484         _video_view->set_length (_film->length());
485 }
486
487
488 /** Re-get the current frame slowly by seeking */
489 void
490 FilmViewer::slow_refresh ()
491 {
492         seek (_video_view->position(), true);
493 }
494
495
496 /** Try to re-get the current frame quickly by resetting the metadata
497  *  in the PlayerVideo that we used last time.
498  *  @return true if this was possible, false if not.
499  */
500 bool
501 FilmViewer::quick_refresh ()
502 {
503         if (!_video_view || !_film || !_player) {
504                 return true;
505         }
506         return _video_view->reset_metadata (_film, _player->video_container_size());
507 }
508
509
510 void
511 FilmViewer::seek (shared_ptr<Content> content, ContentTime t, bool accurate)
512 {
513         auto dt = _player->content_time_to_dcp (content, t);
514         if (dt) {
515                 seek (*dt, accurate);
516         }
517 }
518
519
520 void
521 FilmViewer::set_coalesce_player_changes (bool c)
522 {
523         _coalesce_player_changes = c;
524
525         if (!c) {
526                 player_change (_pending_player_changes);
527                 _pending_player_changes.clear ();
528         }
529 }
530
531
532 void
533 FilmViewer::seek (DCPTime t, bool accurate)
534 {
535         if (!_butler) {
536                 return;
537         }
538
539         if (t < DCPTime()) {
540                 t = DCPTime ();
541         }
542
543         if (t >= _film->length()) {
544                 t = _film->length() - one_video_frame();
545         }
546
547         suspend ();
548
549         _closed_captions_dialog->clear ();
550         _butler->seek (t, accurate);
551
552         if (!_playing) {
553                 /* We're not playing, so let the GUI thread get on and
554                    come back later to get the next frame after the seek.
555                 */
556                 request_idle_display_next_frame ();
557         } else {
558                 /* We're going to start playing again straight away
559                    so wait for the seek to finish.
560                 */
561                 while (_video_view->display_next_frame(false) == VideoView::AGAIN) {}
562         }
563
564         resume ();
565 }
566
567
568 void
569 FilmViewer::config_changed (Config::Property p)
570 {
571         if (p == Config::AUDIO_MAPPING) {
572                 recreate_butler ();
573                 return;
574         }
575
576         if (p != Config::SOUND && p != Config::SOUND_OUTPUT) {
577                 return;
578         }
579
580         if (_audio.isStreamOpen ()) {
581                 _audio.closeStream ();
582         }
583
584         if (Config::instance()->sound() && _audio.getDeviceCount() > 0) {
585                 unsigned int st = 0;
586                 if (Config::instance()->sound_output()) {
587                         while (st < _audio.getDeviceCount()) {
588                                 try {
589                                         if (_audio.getDeviceInfo(st).name == Config::instance()->sound_output().get()) {
590                                                 break;
591                                         }
592                                 } catch (RtAudioError&) {
593                                         /* Something went wrong with that device so we don't want to use it anyway */
594                                 }
595                                 ++st;
596                         }
597                         if (st == _audio.getDeviceCount()) {
598                                 st = _audio.getDefaultOutputDevice();
599                         }
600                 } else {
601                         st = _audio.getDefaultOutputDevice();
602                 }
603
604                 try {
605                         _audio_channels = _audio.getDeviceInfo(st).outputChannels;
606                         RtAudio::StreamParameters sp;
607                         sp.deviceId = st;
608                         sp.nChannels = _audio_channels;
609                         sp.firstChannel = 0;
610                         _audio.openStream (&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this);
611                 } catch (RtAudioError& e) {
612                         _audio_channels = 0;
613                         error_dialog (
614                                 _video_view->get(),
615                                 _("Could not set up audio output.  There will be no audio during the preview."), std_to_wx(e.what())
616                                 );
617                 }
618                 recreate_butler ();
619
620         } else {
621                 _audio_channels = 0;
622                 recreate_butler ();
623         }
624 }
625
626
627 DCPTime
628 FilmViewer::uncorrected_time () const
629 {
630         if (_audio.isStreamRunning()) {
631                 return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime());
632         }
633
634         return _video_view->position();
635 }
636
637
638 optional<DCPTime>
639 FilmViewer::audio_time () const
640 {
641         if (!_audio.isStreamRunning()) {
642                 return {};
643         }
644
645         return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime ()) -
646                 DCPTime::from_frames (average_latency(), _film->audio_frame_rate());
647 }
648
649
650 DCPTime
651 FilmViewer::time () const
652 {
653         return audio_time().get_value_or(_video_view->position());
654 }
655
656
657 int
658 FilmViewer::audio_callback (void* out_p, unsigned int frames)
659 {
660         while (true) {
661                 auto t = _butler->get_audio (Butler::Behaviour::NON_BLOCKING, reinterpret_cast<float*> (out_p), frames);
662                 if (!t || DCPTime(uncorrected_time() - *t) < one_video_frame()) {
663                         /* There was an underrun or this audio is on time; carry on */
664                         break;
665                 }
666                 /* The audio we just got was (very) late; drop it and get some more. */
667         }
668
669         boost::mutex::scoped_lock lm (_latency_history_mutex, boost::try_to_lock);
670         if (lm) {
671                 _latency_history.push_back (_audio.getStreamLatency ());
672                 if (_latency_history.size() > static_cast<size_t> (_latency_history_count)) {
673                         _latency_history.pop_front ();
674                 }
675         }
676
677         return 0;
678 }
679
680
681 Frame
682 FilmViewer::average_latency () const
683 {
684         boost::mutex::scoped_lock lm (_latency_history_mutex);
685         if (_latency_history.empty()) {
686                 return 0;
687         }
688
689         Frame total = 0;
690         for (auto i: _latency_history) {
691                 total += i;
692         }
693
694         return total / _latency_history.size();
695 }
696
697
698 void
699 FilmViewer::set_dcp_decode_reduction (optional<int> reduction)
700 {
701         _dcp_decode_reduction = reduction;
702         if (_player) {
703                 _player->set_dcp_decode_reduction (reduction);
704         }
705 }
706
707
708 optional<int>
709 FilmViewer::dcp_decode_reduction () const
710 {
711         return _dcp_decode_reduction;
712 }
713
714
715 optional<ContentTime>
716 FilmViewer::position_in_content (shared_ptr<const Content> content) const
717 {
718         return _player->dcp_to_content_time (content, position());
719 }
720
721
722 DCPTime
723 FilmViewer::one_video_frame () const
724 {
725         return DCPTime::from_frames (1, _film ? _film->video_frame_rate() : 24);
726 }
727
728
729 /** Open a dialog box showing our film's closed captions */
730 void
731 FilmViewer::show_closed_captions ()
732 {
733         _closed_captions_dialog->Show();
734 }
735
736
737 void
738 FilmViewer::seek_by (DCPTime by, bool accurate)
739 {
740         seek (_video_view->position() + by, accurate);
741 }
742
743
744 void
745 FilmViewer::set_pad_black (bool p)
746 {
747         _pad_black = p;
748 }
749
750
751 /** Called when a player has finished the current film.
752  *  May be called from a non-UI thread.
753  */
754 void
755 FilmViewer::finished ()
756 {
757         emit (boost::bind(&FilmViewer::ui_finished, this));
758 }
759
760
761 /** Called by finished() in the UI thread */
762 void
763 FilmViewer::ui_finished ()
764 {
765         stop ();
766         Finished ();
767 }
768
769
770 int
771 FilmViewer::dropped () const
772 {
773         return _video_view->dropped ();
774 }
775
776
777 int
778 FilmViewer::errored () const
779 {
780         return _video_view->errored ();
781 }
782
783
784 int
785 FilmViewer::gets () const
786 {
787         return _video_view->gets ();
788 }
789
790
791 void
792 FilmViewer::image_changed (shared_ptr<PlayerVideo> pv)
793 {
794         emit (boost::bind(boost::ref(ImageChanged), pv));
795 }
796
797
798 void
799 FilmViewer::set_optimise_for_j2k (bool o)
800 {
801         _optimise_for_j2k = o;
802         _video_view->set_optimise_for_j2k (o);
803 }
804
805
806 void
807 FilmViewer::set_crop_guess (dcpomatic::Rect<float> crop)
808 {
809         if (crop != _crop_guess) {
810                 _crop_guess = crop;
811                 _video_view->update ();
812         }
813 }
814
815
816 void
817 FilmViewer::unset_crop_guess ()
818 {
819         _crop_guess = {};
820         _video_view->update ();
821 }
822