Add an output audio matrix (#1482).
[dcpomatic.git] / src / wx / film_viewer.cc
1 /*
2     Copyright (C) 2012-2019 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 /** @file  src/film_viewer.cc
22  *  @brief A wx widget to view a preview of a Film.
23  */
24
25 #include "film_viewer.h"
26 #include "playhead_to_timecode_dialog.h"
27 #include "playhead_to_frame_dialog.h"
28 #include "wx_util.h"
29 #include "closed_captions_dialog.h"
30 #include "gl_video_view.h"
31 #include "simple_video_view.h"
32 #include "lib/film.h"
33 #include "lib/ratio.h"
34 #include "lib/util.h"
35 #include "lib/job_manager.h"
36 #include "lib/image.h"
37 #include "lib/exceptions.h"
38 #include "lib/examine_content_job.h"
39 #include "lib/filter.h"
40 #include "lib/player.h"
41 #include "lib/player_video.h"
42 #include "lib/video_content.h"
43 #include "lib/video_decoder.h"
44 #include "lib/timer.h"
45 #include "lib/butler.h"
46 #include "lib/log.h"
47 #include "lib/config.h"
48 #include "lib/compose.hpp"
49 #include "lib/dcpomatic_log.h"
50 extern "C" {
51 #include <libavutil/pixfmt.h>
52 }
53 #include <dcp/exceptions.h>
54 #include <wx/tglbtn.h>
55 #include <iostream>
56 #include <iomanip>
57
58 using std::string;
59 using std::pair;
60 using std::min;
61 using std::max;
62 using std::cout;
63 using std::list;
64 using std::bad_alloc;
65 using std::make_pair;
66 using std::exception;
67 using boost::shared_ptr;
68 using boost::dynamic_pointer_cast;
69 using boost::weak_ptr;
70 using boost::optional;
71 using dcp::Size;
72 using namespace dcpomatic;
73
74 static
75 int
76 rtaudio_callback (void* out, void *, unsigned int frames, double, RtAudioStreamStatus, void* data)
77 {
78         return reinterpret_cast<FilmViewer*>(data)->audio_callback (out, frames);
79 }
80
81 FilmViewer::FilmViewer (wxWindow* p)
82         : _coalesce_player_changes (false)
83         , _audio (DCPOMATIC_RTAUDIO_API)
84         , _audio_channels (0)
85         , _audio_block_size (1024)
86         , _playing (false)
87         , _suspended (0)
88         , _latency_history_count (0)
89         , _dropped (0)
90         , _closed_captions_dialog (new ClosedCaptionsDialog(p, this))
91         , _outline_content (false)
92         , _eyes (EYES_LEFT)
93         , _pad_black (false)
94 #ifdef DCPOMATIC_VARIANT_SWAROOP
95         , _background_image (false)
96 #endif
97         , _state_timer ("viewer")
98         , _gets (0)
99         , _idle_get (false)
100 {
101         switch (Config::instance()->video_view_type()) {
102         case Config::VIDEO_VIEW_OPENGL:
103                 _video_view = new GLVideoView (this, p);
104                 break;
105         case Config::VIDEO_VIEW_SIMPLE:
106                 _video_view = new SimpleVideoView (this, p);
107                 break;
108         }
109
110         _video_view->Sized.connect (boost::bind(&FilmViewer::video_view_sized, this));
111         _timer.Bind (wxEVT_TIMER, boost::bind(&FilmViewer::timer, this));
112
113         set_film (shared_ptr<Film> ());
114
115         _config_changed_connection = Config::instance()->Changed.connect (bind (&FilmViewer::config_changed, this, _1));
116         config_changed (Config::SOUND_OUTPUT);
117 }
118
119 FilmViewer::~FilmViewer ()
120 {
121         stop ();
122 }
123
124 /** Ask for ::get() to be called next time we are idle */
125 void
126 FilmViewer::request_idle_get ()
127 {
128         if (_idle_get) {
129                 return;
130         }
131
132         _idle_get = true;
133         DCPOMATIC_ASSERT (signal_manager);
134         signal_manager->when_idle (boost::bind(&FilmViewer::idle_handler, this));
135 }
136
137 void
138 FilmViewer::idle_handler ()
139 {
140         if (!_idle_get) {
141                 return;
142         }
143
144         if (get(true)) {
145                 _idle_get = false;
146         } else {
147                 /* get() could not complete quickly so we'll try again later */
148                 signal_manager->when_idle (boost::bind(&FilmViewer::idle_handler, this));
149         }
150 }
151
152 void
153 FilmViewer::set_film (shared_ptr<Film> film)
154 {
155         if (_film == film) {
156                 return;
157         }
158
159         _film = film;
160         _video_position = DCPTime ();
161         _player_video.first.reset ();
162         _player_video.second = DCPTime ();
163
164         _video_view->set_image (shared_ptr<Image>());
165         _closed_captions_dialog->clear ();
166
167         if (!_film) {
168                 _player.reset ();
169                 recreate_butler ();
170                 refresh_view ();
171                 return;
172         }
173
174         try {
175                 _player.reset (new Player (_film, _film->playlist ()));
176                 _player->set_fast ();
177                 if (_dcp_decode_reduction) {
178                         _player->set_dcp_decode_reduction (_dcp_decode_reduction);
179                 }
180         } catch (bad_alloc &) {
181                 error_dialog (_video_view->get(), _("There is not enough free memory to do that."));
182                 _film.reset ();
183                 return;
184         }
185
186         _player->set_always_burn_open_subtitles ();
187         _player->set_play_referenced ();
188
189         _film->Change.connect (boost::bind (&FilmViewer::film_change, this, _1, _2));
190         _player->Change.connect (boost::bind (&FilmViewer::player_change, this, _1, _2, _3));
191
192         /* Keep about 1 second's worth of history samples */
193         _latency_history_count = _film->audio_frame_rate() / _audio_block_size;
194
195         recreate_butler ();
196
197         calculate_sizes ();
198         slow_refresh ();
199 }
200
201 void
202 FilmViewer::recreate_butler ()
203 {
204         suspend ();
205         _butler.reset ();
206
207         if (!_film) {
208                 resume ();
209                 return;
210         }
211
212         _butler.reset(
213                 new Butler(
214                         _player,
215                         Config::instance()->audio_mapping(_audio_channels),
216                         _audio_channels,
217                         bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24),
218                         false,
219                         true
220                         )
221                 );
222
223         if (!Config::instance()->sound() && !_audio.isStreamOpen()) {
224                 _butler->disable_audio ();
225         }
226
227         _closed_captions_dialog->set_film_and_butler (_film, _butler);
228
229         resume ();
230 }
231
232 void
233 FilmViewer::refresh_view ()
234 {
235         _state_timer.set ("update-view");
236         _video_view->update ();
237         _state_timer.unset ();
238 }
239
240 /** Try to get a frame from the butler and display it.
241  *  @param lazy true to return false quickly if no video is available quickly (i.e. we are waiting for the butler).
242  *  false to ask the butler to block until it has video (unless it is suspended).
243  *  @return true on success, false if we did nothing because it would have taken too long.
244  */
245 bool
246 FilmViewer::get (bool lazy)
247 {
248         DCPOMATIC_ASSERT (_butler);
249         ++_gets;
250
251         do {
252                 Butler::Error e;
253                 _player_video = _butler->get_video (!lazy, &e);
254                 if (!_player_video.first && e == Butler::AGAIN) {
255                         if (lazy) {
256                                 /* No video available; return saying we failed */
257                                 return false;
258                         } else {
259                                 /* Player was suspended; come back later */
260                                 signal_manager->when_idle (boost::bind(&FilmViewer::get, this, false));
261                                 return false;
262                         }
263                 }
264         } while (
265                 _player_video.first &&
266                 _film->three_d() &&
267                 _eyes != _player_video.first->eyes() &&
268                 _player_video.first->eyes() != EYES_BOTH
269                 );
270
271         try {
272                 _butler->rethrow ();
273         } catch (DecodeError& e) {
274                 error_dialog (_video_view->get(), e.what());
275         }
276
277         display_player_video ();
278         PositionChanged ();
279
280         return true;
281 }
282
283 void
284 FilmViewer::display_player_video ()
285 {
286         if (!_player_video.first) {
287                 _video_view->set_image (shared_ptr<Image>());
288                 refresh_view ();
289                 return;
290         }
291
292         if (_playing && !_suspended && (time() - _player_video.second) > one_video_frame()) {
293                 /* Too late; just drop this frame before we try to get its image (which will be the time-consuming
294                    part if this frame is J2K).
295                 */
296                 _video_position = _player_video.second;
297                 ++_dropped;
298                 return;
299         }
300
301         /* In an ideal world, what we would do here is:
302          *
303          * 1. convert to XYZ exactly as we do in the DCP creation path.
304          * 2. convert back to RGB for the preview display, compensating
305          *    for the monitor etc. etc.
306          *
307          * but this is inefficient if the source is RGB.  Since we don't
308          * (currently) care too much about the precise accuracy of the preview's
309          * colour mapping (and we care more about its speed) we try to short-
310          * circuit this "ideal" situation in some cases.
311          *
312          * The content's specified colour conversion indicates the colourspace
313          * which the content is in (according to the user).
314          *
315          * PlayerVideo::image (bound to PlayerVideo::force) will take the source
316          * image and convert it (from whatever the user has said it is) to RGB.
317          */
318
319         _state_timer.set ("get image");
320
321         _video_view->set_image (
322                 _player_video.first->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true)
323                 );
324
325         _state_timer.set ("ImageChanged");
326         ImageChanged (_player_video.first);
327         _state_timer.unset ();
328
329         _video_position = _player_video.second;
330         _inter_position = _player_video.first->inter_position ();
331         _inter_size = _player_video.first->inter_size ();
332
333         refresh_view ();
334
335         _closed_captions_dialog->update (time());
336 }
337
338 void
339 FilmViewer::timer ()
340 {
341         if (!_film || !_playing || _suspended) {
342                 return;
343         }
344
345         get (false);
346         DCPTime const next = _video_position + one_video_frame();
347
348         if (next >= _film->length()) {
349                 stop ();
350                 Finished ();
351                 return;
352         }
353
354         LOG_DEBUG_PLAYER("%1 -> %2; delay %3", next.seconds(), time().seconds(), max((next.seconds() - time().seconds()) * 1000, 1.0));
355         _timer.Start (max ((next.seconds() - time().seconds()) * 1000, 1.0), wxTIMER_ONE_SHOT);
356
357         if (_butler) {
358                 _butler->rethrow ();
359         }
360 }
361
362 void
363 FilmViewer::set_outline_content (bool o)
364 {
365         _outline_content = o;
366         refresh_view ();
367 }
368
369 void
370 FilmViewer::set_eyes (Eyes e)
371 {
372         _eyes = e;
373         slow_refresh ();
374 }
375
376 void
377 FilmViewer::video_view_sized ()
378 {
379         calculate_sizes ();
380         if (!quick_refresh()) {
381                 slow_refresh ();
382         }
383         PositionChanged ();
384 }
385
386 void
387 FilmViewer::calculate_sizes ()
388 {
389         if (!_film || !_player) {
390                 return;
391         }
392
393         Ratio const * container = _film->container ();
394
395         float const view_ratio = float(_video_view->get()->GetSize().x) / _video_view->get()->GetSize().y;
396         float const film_ratio = container ? container->ratio () : 1.78;
397
398         if (view_ratio < film_ratio) {
399                 /* panel is less widscreen than the film; clamp width */
400                 _out_size.width = _video_view->get()->GetSize().x;
401                 _out_size.height = lrintf (_out_size.width / film_ratio);
402         } else {
403                 /* panel is more widescreen than the film; clamp height */
404                 _out_size.height = _video_view->get()->GetSize().y;
405                 _out_size.width = lrintf (_out_size.height * film_ratio);
406         }
407
408         /* Catch silly values */
409         _out_size.width = max (64, _out_size.width);
410         _out_size.height = max (64, _out_size.height);
411
412         _player->set_video_container_size (_out_size);
413 }
414
415 void
416 FilmViewer::suspend ()
417 {
418         ++_suspended;
419         if (_audio.isStreamRunning()) {
420                 _audio.abortStream();
421         }
422 }
423
424 void
425 FilmViewer::resume ()
426 {
427         --_suspended;
428         if (_playing && !_suspended) {
429                 if (_audio.isStreamOpen()) {
430                         _audio.setStreamTime (_video_position.seconds());
431                         _audio.startStream ();
432                 }
433                 timer ();
434         }
435 }
436
437 void
438 FilmViewer::start ()
439 {
440         if (!_film) {
441                 return;
442         }
443
444         optional<bool> v = PlaybackPermitted ();
445         if (v && !*v) {
446                 /* Computer says no */
447                 return;
448         }
449
450         if (_audio.isStreamOpen()) {
451                 _audio.setStreamTime (_video_position.seconds());
452                 _audio.startStream ();
453         }
454
455         _playing = true;
456         _dropped = 0;
457         timer ();
458         Started (position());
459 }
460
461 bool
462 FilmViewer::stop ()
463 {
464         if (_audio.isStreamRunning()) {
465                 /* stop stream and discard any remaining queued samples */
466                 _audio.abortStream ();
467         }
468
469         if (!_playing) {
470                 return false;
471         }
472
473         _playing = false;
474         Stopped (position());
475         return true;
476 }
477
478 void
479 FilmViewer::player_change (ChangeType type, int property, bool frequent)
480 {
481         if (type != CHANGE_TYPE_DONE || frequent) {
482                 return;
483         }
484
485         if (_coalesce_player_changes) {
486                 _pending_player_changes.push_back (property);
487                 return;
488         }
489
490         calculate_sizes ();
491         bool refreshed = false;
492         if (
493                 property == VideoContentProperty::CROP ||
494                 property == VideoContentProperty::SCALE ||
495                 property == VideoContentProperty::FADE_IN ||
496                 property == VideoContentProperty::FADE_OUT ||
497                 property == VideoContentProperty::COLOUR_CONVERSION ||
498                 property == PlayerProperty::VIDEO_CONTAINER_SIZE ||
499                 property == PlayerProperty::FILM_CONTAINER
500                 ) {
501                 refreshed = quick_refresh ();
502         }
503
504         if (!refreshed) {
505                 slow_refresh ();
506         }
507         PositionChanged ();
508 }
509
510 void
511 FilmViewer::film_change (ChangeType type, Film::Property p)
512 {
513         if (type == CHANGE_TYPE_DONE && p == Film::AUDIO_CHANNELS) {
514                 recreate_butler ();
515         }
516 }
517
518 /** Re-get the current frame slowly by seeking */
519 void
520 FilmViewer::slow_refresh ()
521 {
522         seek (_video_position, true);
523 }
524
525 /** Try to re-get the current frame quickly by resetting the metadata
526  *  in the PlayerVideo that we used last time.
527  *  @return true if this was possible, false if not.
528  */
529 bool
530 FilmViewer::quick_refresh ()
531 {
532         if (!_player_video.first) {
533                 return false;
534         }
535
536         if (!_player_video.first->reset_metadata (_film, _player->video_container_size(), _film->frame_size())) {
537                 return false;
538         }
539
540         display_player_video ();
541         return true;
542 }
543
544 void
545 FilmViewer::seek (shared_ptr<Content> content, ContentTime t, bool accurate)
546 {
547         optional<DCPTime> dt = _player->content_time_to_dcp (content, t);
548         if (dt) {
549                 seek (*dt, accurate);
550         }
551 }
552
553 void
554 FilmViewer::set_coalesce_player_changes (bool c)
555 {
556         _coalesce_player_changes = c;
557
558         if (!c) {
559                 BOOST_FOREACH (int i, _pending_player_changes) {
560                         player_change (CHANGE_TYPE_DONE, i, false);
561                 }
562                 _pending_player_changes.clear ();
563         }
564 }
565
566 void
567 FilmViewer::seek (DCPTime t, bool accurate)
568 {
569         if (!_butler) {
570                 return;
571         }
572
573         if (t < DCPTime ()) {
574                 t = DCPTime ();
575         }
576
577         if (t >= _film->length ()) {
578                 t = _film->length ();
579         }
580
581         suspend ();
582
583         _closed_captions_dialog->clear ();
584         _butler->seek (t, accurate);
585
586         if (!_playing) {
587                 request_idle_get ();
588         } else {
589                 /* Make sure we get a frame so that _video_position is set up before we resume */
590                 while (!get(true)) {}
591         }
592
593         resume ();
594 }
595
596 void
597 FilmViewer::config_changed (Config::Property p)
598 {
599 #ifdef DCPOMATIC_VARIANT_SWAROOP
600         if (p == Config::PLAYER_BACKGROUND_IMAGE) {
601                 refresh_view ();
602                 return;
603         }
604 #endif
605
606         if (p == Config::AUDIO_MAPPING) {
607                 recreate_butler ();
608                 return;
609         }
610
611         if (p != Config::SOUND && p != Config::SOUND_OUTPUT) {
612                 return;
613         }
614
615         if (_audio.isStreamOpen ()) {
616                 _audio.closeStream ();
617         }
618
619         if (Config::instance()->sound() && _audio.getDeviceCount() > 0) {
620                 unsigned int st = 0;
621                 if (Config::instance()->sound_output()) {
622                         while (st < _audio.getDeviceCount()) {
623                                 if (_audio.getDeviceInfo(st).name == Config::instance()->sound_output().get()) {
624                                         break;
625                                 }
626                                 ++st;
627                         }
628                         if (st == _audio.getDeviceCount()) {
629                                 st = _audio.getDefaultOutputDevice();
630                         }
631                 } else {
632                         st = _audio.getDefaultOutputDevice();
633                 }
634
635                 _audio_channels = _audio.getDeviceInfo(st).outputChannels;
636
637                 RtAudio::StreamParameters sp;
638                 sp.deviceId = st;
639                 sp.nChannels = _audio_channels;
640                 sp.firstChannel = 0;
641                 try {
642                         _audio.openStream (&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this);
643 #ifdef DCPOMATIC_USE_RTERROR
644                 } catch (RtError& e) {
645 #else
646                 } catch (RtAudioError& e) {
647 #endif
648                         error_dialog (
649                                 _video_view->get(),
650                                 _("Could not set up audio output.  There will be no audio during the preview."), std_to_wx(e.what())
651                                 );
652                 }
653                 recreate_butler ();
654
655         } else {
656                 _audio_channels = 0;
657                 recreate_butler ();
658         }
659 }
660
661 DCPTime
662 FilmViewer::uncorrected_time () const
663 {
664         if (_audio.isStreamRunning ()) {
665                 return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime());
666         }
667
668         return _video_position;
669 }
670
671 DCPTime
672 FilmViewer::time () const
673 {
674         if (_audio.isStreamRunning ()) {
675                 return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime ()) -
676                         DCPTime::from_frames (average_latency(), _film->audio_frame_rate());
677         }
678
679         return _video_position;
680 }
681
682 int
683 FilmViewer::audio_callback (void* out_p, unsigned int frames)
684 {
685         while (true) {
686                 optional<DCPTime> t = _butler->get_audio (reinterpret_cast<float*> (out_p), frames);
687                 if (!t || DCPTime(uncorrected_time() - *t) < one_video_frame()) {
688                         /* There was an underrun or this audio is on time; carry on */
689                         break;
690                 }
691                 /* The audio we just got was (very) late; drop it and get some more. */
692         }
693
694         boost::mutex::scoped_lock lm (_latency_history_mutex, boost::try_to_lock);
695         if (lm) {
696                 _latency_history.push_back (_audio.getStreamLatency ());
697                 if (_latency_history.size() > static_cast<size_t> (_latency_history_count)) {
698                         _latency_history.pop_front ();
699                 }
700         }
701
702         return 0;
703 }
704
705 Frame
706 FilmViewer::average_latency () const
707 {
708         boost::mutex::scoped_lock lm (_latency_history_mutex);
709         if (_latency_history.empty()) {
710                 return 0;
711         }
712
713         Frame total = 0;
714         BOOST_FOREACH (Frame i, _latency_history) {
715                 total += i;
716         }
717
718         return total / _latency_history.size();
719 }
720
721 void
722 FilmViewer::set_dcp_decode_reduction (optional<int> reduction)
723 {
724         _dcp_decode_reduction = reduction;
725         if (_player) {
726                 _player->set_dcp_decode_reduction (reduction);
727         }
728 }
729
730 optional<int>
731 FilmViewer::dcp_decode_reduction () const
732 {
733         return _dcp_decode_reduction;
734 }
735
736 DCPTime
737 FilmViewer::one_video_frame () const
738 {
739         return DCPTime::from_frames (1, _film ? _film->video_frame_rate() : 24);
740 }
741
742 /** Open a dialog box showing our film's closed captions */
743 void
744 FilmViewer::show_closed_captions ()
745 {
746         _closed_captions_dialog->Show();
747 }
748
749 void
750 FilmViewer::seek_by (DCPTime by, bool accurate)
751 {
752         seek (_video_position + by, accurate);
753 }
754
755 void
756 FilmViewer::set_pad_black (bool p)
757 {
758         _pad_black = p;
759 }