0381ee37835708fa4ab0169179ad9bfa99c525b9
[dcpomatic.git] / src / lib / writer.cc
1 /*
2     Copyright (C) 2012 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 <libdcp/picture_asset.h>
21 #include "writer.h"
22 #include "compose.hpp"
23 #include "film.h"
24 #include "format.h"
25 #include "log.h"
26 #include "dcp_video_frame.h"
27
28 using std::make_pair;
29 using std::pair;
30 using boost::shared_ptr;
31
32 unsigned int const Writer::_maximum_frames_in_memory = 8;
33
34 Writer::Writer (shared_ptr<const Film> f)
35         : _film (f)
36         , _thread (0)
37         , _finish (false)
38         , _last_written_frame (-1)
39 {
40         _picture_asset.reset (
41                 new libdcp::MonoPictureAsset (
42                         _film->dir (_film->dcp_name()),
43                         String::compose ("video_%1.mxf", 0),
44                         DCPFrameRate (_film->frames_per_second()).frames_per_second,
45                         _film->format()->dcp_size()
46                         )
47                 );
48         
49         _picture_asset_writer = _picture_asset->start_write ();
50
51         _thread = new boost::thread (boost::bind (&Writer::thread, this));
52 }
53
54 void
55 Writer::write (shared_ptr<EncodedData> encoded, int frame)
56 {
57         boost::mutex::scoped_lock lock (_mutex);
58         _queue.push_back (make_pair (encoded, frame));
59         _condition.notify_all ();
60 }
61
62 struct QueueSorter
63 {
64         bool operator() (pair<shared_ptr<EncodedData>, int> const & a, pair<shared_ptr<EncodedData>, int> const & b) {
65                 return a.second < b.second;
66         }
67 };
68
69 void
70 Writer::thread ()
71 {
72         while (1)
73         {
74                 boost::mutex::scoped_lock lock (_mutex);
75
76                 while (1) {
77                         if (_finish ||
78                             _queue.size() > _maximum_frames_in_memory ||
79                             (!_queue.empty() && _queue.front().second == (_last_written_frame + 1))) {
80                                     
81                                     break;
82                             }
83
84                             TIMING ("writer sleeps with a queue of %1; %2 pending", _queue.size(), _pending.size());
85                             _condition.wait (lock);
86                             TIMING ("writer wakes with a queue of %1", _queue.size());
87
88                             _queue.sort (QueueSorter ());
89                 }
90
91                 if (_finish && _queue.empty() && _pending.empty()) {
92                         return;
93                 }
94
95                 /* Write any frames that we can write; i.e. those that are in sequence */
96                 while (!_queue.empty() && _queue.front().second == (_last_written_frame + 1)) {
97                         pair<boost::shared_ptr<EncodedData>, int> encoded = _queue.front ();
98                         _queue.pop_front ();
99
100                         lock.unlock ();
101                         _film->log()->log (String::compose ("Writer writes %1 to MXF", encoded.second));
102                         if (encoded.first) {
103                                 _picture_asset_writer->write (encoded.first->data(), encoded.first->size());
104                                 _last_written = encoded.first;
105                         } else {
106                                 _picture_asset_writer->write (_last_written->data(), _last_written->size());
107                         }
108                         lock.lock ();
109
110                         ++_last_written_frame;
111                 }
112
113                 while (_queue.size() > _maximum_frames_in_memory) {
114                         /* Too many frames in memory which can't yet be written to the stream.
115                            Put some to disk.
116                         */
117
118                         pair<boost::shared_ptr<EncodedData>, int> encoded = _queue.back ();
119                         _queue.pop_back ();
120                         if (!encoded.first) {
121                                 /* This is a `repeat-last' frame, so no need to write it to disk */
122                                 continue;
123                         }
124
125                         lock.unlock ();
126                         _film->log()->log (String::compose ("Writer full (awaiting %1); pushes %2 to disk", _last_written_frame + 1, encoded.second));
127                         encoded.first->write (_film, encoded.second);
128                         lock.lock ();
129
130                         _pending.push_back (encoded.second);
131                 }
132
133                 while (_queue.size() < _maximum_frames_in_memory && !_pending.empty()) {
134                         /* We have some space in memory.  Fetch some frames back off disk. */
135
136                         _pending.sort ();
137                         int const fetch = _pending.front ();
138
139                         lock.unlock ();
140                         _film->log()->log (String::compose ("Writer pulls %1 back from disk", fetch));
141                         shared_ptr<EncodedData> encoded;
142                         if (boost::filesystem::exists (_film->frame_out_path (fetch, false))) {
143                                 /* It's an actual frame (not a repeat-last); load it in */
144                                 encoded.reset (new EncodedData (_film->frame_out_path (fetch, false)));
145                         }
146                         lock.lock ();
147
148                         _queue.push_back (make_pair (encoded, fetch));
149                         _pending.remove (fetch);
150                 }
151         }
152
153 }
154
155 void
156 Writer::finish ()
157 {
158         if (!_thread) {
159                 return;
160         }
161         
162         boost::mutex::scoped_lock lock (_mutex);
163         _finish = true;
164         _condition.notify_all ();
165         lock.unlock ();
166
167         _thread->join ();
168         delete _thread;
169         _thread = 0;
170
171         _picture_asset_writer->finalize ();
172 }
173
174 /** Tell the writer that frame `f' should be a repeat of the frame before it */
175 void
176 Writer::repeat (int f)
177 {
178         boost::mutex::scoped_lock lock (_mutex);
179         _queue.push_back (make_pair (shared_ptr<EncodedData> (), f));
180 }