Handle multiple audio streams in a single piece of content
[dcpomatic.git] / src / lib / audio_content.cc
1 /*
2     Copyright (C) 2013-2015 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 "audio_content.h"
21 #include "analyse_audio_job.h"
22 #include "job_manager.h"
23 #include "film.h"
24 #include "exceptions.h"
25 #include "config.h"
26 #include "frame_rate_change.h"
27 #include "raw_convert.h"
28 #include <libcxml/cxml.h>
29 #include <boost/foreach.hpp>
30
31 #include "i18n.h"
32
33 using std::string;
34 using std::cout;
35 using std::vector;
36 using std::stringstream;
37 using std::fixed;
38 using std::setprecision;
39 using boost::shared_ptr;
40 using boost::dynamic_pointer_cast;
41 using boost::optional;
42
43 /** Something stream-related has changed */
44 int const AudioContentProperty::AUDIO_STREAMS = 200;
45 int const AudioContentProperty::AUDIO_GAIN = 201;
46 int const AudioContentProperty::AUDIO_DELAY = 202;
47
48 AudioContent::AudioContent (shared_ptr<const Film> f)
49         : Content (f)
50         , _audio_gain (0)
51         , _audio_delay (Config::instance()->default_audio_delay ())
52 {
53
54 }
55
56 AudioContent::AudioContent (shared_ptr<const Film> f, DCPTime s)
57         : Content (f, s)
58         , _audio_gain (0)
59         , _audio_delay (Config::instance()->default_audio_delay ())
60 {
61
62 }
63
64 AudioContent::AudioContent (shared_ptr<const Film> f, boost::filesystem::path p)
65         : Content (f, p)
66         , _audio_gain (0)
67         , _audio_delay (Config::instance()->default_audio_delay ())
68 {
69
70 }
71
72 AudioContent::AudioContent (shared_ptr<const Film> f, cxml::ConstNodePtr node)
73         : Content (f, node)
74 {
75         _audio_gain = node->number_child<float> ("AudioGain");
76         _audio_delay = node->number_child<int> ("AudioDelay");
77 }
78
79 AudioContent::AudioContent (shared_ptr<const Film> f, vector<shared_ptr<Content> > c)
80         : Content (f, c)
81 {
82         shared_ptr<AudioContent> ref = dynamic_pointer_cast<AudioContent> (c[0]);
83         DCPOMATIC_ASSERT (ref);
84         
85         for (size_t i = 0; i < c.size(); ++i) {
86                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c[i]);
87
88                 if (ac->audio_gain() != ref->audio_gain()) {
89                         throw JoinError (_("Content to be joined must have the same audio gain."));
90                 }
91
92                 if (ac->audio_delay() != ref->audio_delay()) {
93                         throw JoinError (_("Content to be joined must have the same audio delay."));
94                 }
95         }
96
97         _audio_gain = ref->audio_gain ();
98         _audio_delay = ref->audio_delay ();
99 }
100
101 void
102 AudioContent::as_xml (xmlpp::Node* node) const
103 {
104         boost::mutex::scoped_lock lm (_mutex);
105         node->add_child("AudioGain")->add_child_text (raw_convert<string> (_audio_gain));
106         node->add_child("AudioDelay")->add_child_text (raw_convert<string> (_audio_delay));
107 }
108
109
110 void
111 AudioContent::set_audio_gain (double g)
112 {
113         {
114                 boost::mutex::scoped_lock lm (_mutex);
115                 _audio_gain = g;
116         }
117         
118         signal_changed (AudioContentProperty::AUDIO_GAIN);
119 }
120
121 void
122 AudioContent::set_audio_delay (int d)
123 {
124         {
125                 boost::mutex::scoped_lock lm (_mutex);
126                 _audio_delay = d;
127         }
128         
129         signal_changed (AudioContentProperty::AUDIO_DELAY);
130 }
131
132 boost::signals2::connection
133 AudioContent::analyse_audio (boost::function<void()> finished)
134 {
135         shared_ptr<const Film> film = _film.lock ();
136         DCPOMATIC_ASSERT (film);
137         
138         shared_ptr<AnalyseAudioJob> job (new AnalyseAudioJob (film, dynamic_pointer_cast<AudioContent> (shared_from_this())));
139         boost::signals2::connection c = job->Finished.connect (finished);
140         JobManager::instance()->add (job);
141
142         return c;
143 }
144
145 boost::filesystem::path
146 AudioContent::audio_analysis_path () const
147 {
148         shared_ptr<const Film> film = _film.lock ();
149         if (!film) {
150                 return boost::filesystem::path ();
151         }
152
153         boost::filesystem::path p = film->audio_analysis_dir ();
154         p /= digest() + "_" + audio_mapping().digest();
155         return p;
156 }
157
158 string
159 AudioContent::technical_summary () const
160 {
161         string s = "audio :";
162         BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
163                 s += String::compose ("stream channels %1 rate %2", i->channels(), i->frame_rate());
164         }
165
166         return s;
167 }
168
169 void
170 AudioContent::set_audio_mapping (AudioMapping mapping)
171 {
172         int c = 0;
173         BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
174                 AudioMapping stream_mapping (i->channels ());
175                 for (int j = 0; j < i->channels(); ++j) {
176                         for (int k = 0; k < MAX_DCP_AUDIO_CHANNELS; ++k) {
177                                 stream_mapping.set (j, static_cast<dcp::Channel> (k), mapping.get (c, static_cast<dcp::Channel> (k)));
178                         }
179                         ++c;
180                 }
181                 i->set_mapping (stream_mapping);
182         }
183                 
184         signal_changed (AudioContentProperty::AUDIO_STREAMS);
185 }
186
187 AudioMapping
188 AudioContent::audio_mapping () const
189 {
190         int channels = 0;
191         BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
192                 channels += i->channels ();
193         }
194         
195         AudioMapping merged (channels);
196         
197         int c = 0;
198         int s = 0;
199         BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
200                 AudioMapping mapping = i->mapping ();
201                 for (int j = 0; j < mapping.content_channels(); ++j) {
202                         merged.set_name (c, String::compose ("%1:%2", s + 1, j + 1));
203                         for (int k = 0; k < MAX_DCP_AUDIO_CHANNELS; ++k) {
204                                 merged.set (c, static_cast<dcp::Channel> (k), mapping.get (j, static_cast<dcp::Channel> (k)));
205                         }
206                         ++c;
207                 }
208                 ++s;
209         }
210
211         return merged;
212 }
213
214 /** @return the frame rate that this content should be resampled to in order
215  *  that it is in sync with the active video content at its start time.
216  */
217 int
218 AudioContent::resampled_audio_frame_rate () const
219 {
220         shared_ptr<const Film> film = _film.lock ();
221         DCPOMATIC_ASSERT (film);
222         
223         /* Resample to a DCI-approved sample rate */
224         double t = has_rate_above_48k() ? 96000 : 48000;
225
226         FrameRateChange frc = film->active_frame_rate_change (position ());
227
228         /* Compensate if the DCP is being run at a different frame rate
229            to the source; that is, if the video is run such that it will
230            look different in the DCP compared to the source (slower or faster).
231         */
232
233         if (frc.change_speed) {
234                 t /= frc.speed_up;
235         }
236
237         return rint (t);
238 }
239
240 string
241 AudioContent::processing_description () const
242 {
243         vector<AudioStreamPtr> streams = audio_streams ();
244         if (streams.empty ()) {
245                 return "";
246         }
247
248         /* Possible answers are:
249            1. all audio will be resampled from x to y.
250            2. all audio will be resampled to y (from a variety of rates)
251            3. some audio will be resampled to y (from a variety of rates)
252            4. nothing will be resampled.
253         */
254
255         bool not_resampled = false;
256         bool resampled = false;
257         bool same = true;
258
259         optional<int> common_frame_rate;
260         BOOST_FOREACH (AudioStreamPtr i, streams) {
261                 if (i->frame_rate() != resampled_audio_frame_rate()) {
262                         resampled = true;
263                 } else {
264                         not_resampled = true;
265                 }
266
267                 if (common_frame_rate && common_frame_rate != i->frame_rate ()) {
268                         same = false;
269                 }
270                 common_frame_rate = i->frame_rate ();
271         }
272
273         if (not_resampled && !resampled) {
274                 return _("Audio will not be resampled");
275         }
276
277         if (not_resampled && resampled) {
278                 return String::compose (_("Some audio will be resampled to %1kHz"), resampled_audio_frame_rate ());
279         }
280
281         if (!not_resampled && resampled) {
282                 if (same) {
283                         return String::compose (_("Audio will be resampled from %1kHz to %2kHz"), common_frame_rate.get(), resampled_audio_frame_rate ());
284                 } else {
285                         return String::compose (_("Audio will be resampled to %1kHz"), resampled_audio_frame_rate ());
286                 }
287         }
288
289         return "";
290 }
291
292 bool
293 AudioContent::has_rate_above_48k () const
294 {
295         BOOST_FOREACH (AudioStreamPtr i, audio_streams ()) {
296                 if (i->frame_rate() > 48000) {
297                         return true;
298                 }
299         }
300
301         return false;
302 }