#include "i18n.h"
using std::string;
+using std::vector;
using std::max;
using std::min;
using std::cout;
, _done (0)
, _samples_per_point (1)
, _current (0)
- , _sample_peak (0)
- , _sample_peak_frame (0)
+ , _sample_peak (new float[film->audio_channels()])
+ , _sample_peak_frame (new Frame[film->audio_channels()])
#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
, _ebur128 (new AudioFilterGraph (film->audio_frame_rate(), film->audio_channels()))
#endif
delete const_cast<Filter*> (i);
}
delete[] _current;
+ delete[] _sample_peak;
+ delete[] _sample_peak_frame;
}
string
}
}
- _analysis->set_sample_peak (_sample_peak, DCPTime::from_frames (_sample_peak_frame, _film->audio_frame_rate ()));
+ vector<AudioAnalysis::PeakTime> sample_peak;
+ for (int i = 0; i < _film->audio_channels(); ++i) {
+ sample_peak.push_back (
+ AudioAnalysis::PeakTime (_sample_peak[i], DCPTime::from_frames (_sample_peak_frame[i], _film->audio_frame_rate ()))
+ );
+ }
+ _analysis->set_sample_peak (sample_peak);
#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
if (Config::instance()->analyse_ebur128 ()) {
void* eb = _ebur128->get("Parsed_ebur128_0")->priv;
- double true_peak = 0;
+ vector<float> true_peak;
for (int i = 0; i < _film->audio_channels(); ++i) {
- true_peak = max (true_peak, av_ebur128_get_true_peaks(eb)[i]);
+ true_peak.push_back (av_ebur128_get_true_peaks(eb)[i]);
}
_analysis->set_true_peak (true_peak);
_analysis->set_integrated_loudness (av_ebur128_get_integrated_loudness(eb));
_current[j][AudioPoint::RMS] += pow (s, 2);
_current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], as);
- if (as > _sample_peak) {
- _sample_peak = as;
- _sample_peak_frame = _done + i;
+ if (as > _sample_peak[j]) {
+ _sample_peak[j] = as;
+ _sample_peak_frame[j] = _done + i;
}
if (((_done + i) % _samples_per_point) == 0) {
int64_t _samples_per_point;
AudioPoint* _current;
- float _sample_peak;
- Frame _sample_peak_frame;
+ float* _sample_peak;
+ Frame* _sample_peak_frame;
boost::shared_ptr<AudioAnalysis> _analysis;
/*
- Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
using std::vector;
using std::cout;
using std::max;
+using std::pair;
+using std::make_pair;
using std::list;
using boost::shared_ptr;
+using boost::optional;
using boost::dynamic_pointer_cast;
using dcp::raw_convert;
+int const AudioAnalysis::_current_state_version = 2;
+
AudioAnalysis::AudioAnalysis (int channels)
{
_data.resize (channels);
cxml::Document f ("AudioAnalysis");
f.read_file (filename);
+ if (f.optional_number_child<int>("Version").get_value_or(1) < _current_state_version) {
+ /* Too old. Throw an exception so that this analysis is re-run. */
+ throw OldFormatError ("Audio analysis file is too old");
+ }
+
BOOST_FOREACH (cxml::NodePtr i, f.node_children ("Channel")) {
vector<AudioPoint> channel;
_data.push_back (channel);
}
- _sample_peak = f.optional_number_child<float> ("Peak");
- if (!_sample_peak) {
- /* New key */
- _sample_peak = f.optional_number_child<float> ("SamplePeak");
+ BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("SamplePeak")) {
+ _sample_peak.push_back (
+ PeakTime (
+ dcp::raw_convert<float>(i->content()), DCPTime(i->number_attribute<Frame>("Time"))
+ )
+ );
}
- if (f.optional_number_child<DCPTime::Type> ("PeakTime")) {
- _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("PeakTime"));
- } else if (f.optional_number_child<DCPTime::Type> ("SamplePeakTime")) {
- _sample_peak_time = DCPTime (f.number_child<DCPTime::Type> ("SamplePeakTime"));
+ BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children ("TruePeak")) {
+ _true_peak.push_back (dcp::raw_convert<float> (i->content ()));
}
- _true_peak = f.optional_number_child<float> ("TruePeak");
_integrated_loudness = f.optional_number_child<float> ("IntegratedLoudness");
_loudness_range = f.optional_number_child<float> ("LoudnessRange");
shared_ptr<xmlpp::Document> doc (new xmlpp::Document);
xmlpp::Element* root = doc->create_root_node ("AudioAnalysis");
+ root->add_child("Version")->add_child_text (raw_convert<string> (_current_state_version));
+
BOOST_FOREACH (vector<AudioPoint>& i, _data) {
xmlpp::Element* channel = root->add_child ("Channel");
BOOST_FOREACH (AudioPoint& j, i) {
}
}
- if (_sample_peak) {
- root->add_child("SamplePeak")->add_child_text (raw_convert<string> (_sample_peak.get ()));
- root->add_child("SamplePeakTime")->add_child_text (raw_convert<string> (_sample_peak_time.get().get ()));
+ for (size_t i = 0; i < _sample_peak.size(); ++i) {
+ xmlpp::Element* n = root->add_child("SamplePeak");
+ n->add_child_text (raw_convert<string> (_sample_peak[i].peak));
+ n->set_attribute ("Time", raw_convert<string> (_sample_peak[i].time.get()));
}
- if (_true_peak) {
- root->add_child("TruePeak")->add_child_text (raw_convert<string> (_true_peak.get ()));
+ BOOST_FOREACH (float i, _true_peak) {
+ root->add_child("TruePeak")->add_child_text (raw_convert<string> (i));
}
if (_integrated_loudness) {
return 0.0f;
}
+
+/** @return Peak across all channels, and the channel number it is on */
+pair<AudioAnalysis::PeakTime, int>
+AudioAnalysis::overall_sample_peak () const
+{
+ optional<PeakTime> pt;
+ int c;
+
+ for (size_t i = 0; i < _sample_peak.size(); ++i) {
+ if (!pt || _sample_peak[i].peak > pt->peak) {
+ pt = _sample_peak[i];
+ c = i;
+ }
+ }
+
+ return make_pair (pt.get(), c);
+}
+
+optional<float>
+AudioAnalysis::overall_true_peak () const
+{
+ optional<float> p;
+
+ BOOST_FOREACH (float i, _true_peak) {
+ if (!p || i > *p) {
+ p = i;
+ }
+ }
+
+ return p;
+}
void add_point (int c, AudioPoint const & p);
- void set_sample_peak (float peak, DCPTime time) {
+ struct PeakTime {
+ PeakTime (float p, DCPTime t)
+ : peak (p)
+ , time (t)
+ {}
+
+ float peak;
+ DCPTime time;
+ };
+
+ void set_sample_peak (std::vector<PeakTime> peak) {
_sample_peak = peak;
- _sample_peak_time = time;
}
- void set_true_peak (float peak) {
+ void set_true_peak (std::vector<float> peak) {
_true_peak = peak;
}
int points (int c) const;
int channels () const;
- boost::optional<float> sample_peak () const {
+ std::vector<PeakTime> sample_peak () const {
return _sample_peak;
}
- boost::optional<DCPTime> sample_peak_time () const {
- return _sample_peak_time;
- }
+ std::pair<PeakTime, int> overall_sample_peak () const;
- boost::optional<float> true_peak () const {
+ std::vector<float> true_peak () const {
return _true_peak;
}
+ boost::optional<float> overall_true_peak () const;
+
boost::optional<float> integrated_loudness () const {
return _integrated_loudness;
}
private:
std::vector<std::vector<AudioPoint> > _data;
- boost::optional<float> _sample_peak;
- boost::optional<DCPTime> _sample_peak_time;
- boost::optional<float> _true_peak;
+ std::vector<PeakTime> _sample_peak;
+ std::vector<float> _true_peak;
boost::optional<float> _integrated_loudness;
boost::optional<float> _loudness_range;
/** If this analysis was run on a single piece of
* happened.
*/
boost::optional<double> _analysis_gain;
+
+ static int const _current_state_version;
};
#endif
{}
};
+class OldFormatError : public std::runtime_error
+{
+public:
+ OldFormatError (std::string s)
+ : std::runtime_error (s)
+ {}
+};
+
#endif
DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
vector<string> n;
- n.push_back (_("L"));
- n.push_back (_("R"));
- n.push_back (_("C"));
- n.push_back (_("Lfe"));
- n.push_back (_("Ls"));
- n.push_back (_("Rs"));
- n.push_back (_("HI"));
- n.push_back (_("VI"));
- n.push_back (_("Lc"));
- n.push_back (_("Rc"));
- n.push_back (_("BsL"));
- n.push_back (_("BsR"));
- n.push_back (_("DBP"));
- n.push_back (_("DBS"));
- n.push_back ("");
- n.push_back ("");
-
- return vector<string> (n.begin(), n.begin() + audio_channels ());
+
+ for (int i = 0; i < audio_channels(); ++i) {
+ n.push_back (short_audio_channel_name (i));
+ }
+
+ return n;
}
void
#include "ratio.h"
#include "audio_analysis.h"
#include "compose.hpp"
+#include "util.h"
+#include <dcp/raw_convert.h>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
boost::filesystem::path path = film->audio_analysis_path (film->playlist ());
if (boost::filesystem::exists (path)) {
shared_ptr<AudioAnalysis> an (new AudioAnalysis (path));
- if (an->sample_peak() || an->true_peak()) {
- float const peak = max (an->sample_peak().get_value_or(0), an->true_peak().get_value_or(0));
+
+ string ch;
+
+ vector<AudioAnalysis::PeakTime> sample_peak = an->sample_peak ();
+ vector<float> true_peak = an->true_peak ();
+
+ for (size_t i = 0; i < sample_peak.size(); ++i) {
+ float const peak = max (sample_peak[i].peak, true_peak.empty() ? 0 : true_peak[i]);
float const peak_dB = 20 * log10 (peak) + an->gain_correction (film->playlist ());
- if (peak_dB > -3 && peak_dB < -0.5) {
- hints.push_back (_("Your audio level is very high. You should reduce the gain of your audio content."));
- } else if (peak_dB > -0.5) {
- hints.push_back (_("Your audio level is very close to clipping. You should reduce the gain of your audio content."));
+ if (peak_dB > -3) {
+ ch += dcp::raw_convert<string> (short_audio_channel_name (i)) + ", ";
}
}
+
+ ch = ch.substr (0, ch.length() - 2);
+
+ if (!ch.empty ()) {
+ hints.push_back (
+ String::compose (
+ _("Your audio level is very high (on %1). You should reduce the gain of your audio content."),
+ ch
+ )
+ );
+ }
}
return hints;
DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
/// TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
- /// enhancement channel (sub-woofer). HI is the hearing-impaired audio track and
- /// VI is the visually-impaired audio track (audio describe).
+ /// enhancement channel (sub-woofer).
string const channels[] = {
_("Left"),
_("Right"),
return channels[c];
}
+string
+short_audio_channel_name (int c)
+{
+ DCPOMATIC_ASSERT (MAX_DCP_AUDIO_CHANNELS == 16);
+
+ /// TRANSLATORS: these are short names of audio channels; Lfe is the low-frequency
+ /// enhancement channel (sub-woofer). HI is the hearing-impaired audio track and
+ /// VI is the visually-impaired audio track (audio describe).
+ string const channels[] = {
+ _("L"),
+ _("R"),
+ _("C"),
+ _("Lfe"),
+ _("Ls"),
+ _("Rs"),
+ _("HI"),
+ _("VI"),
+ _("Lc"),
+ _("Rc"),
+ _("BsL"),
+ _("BsR"),
+ _("DBP"),
+ _("DBPS"),
+ _(""),
+ _("")
+ };
+
+ return channels[c];
+}
+
+
bool
valid_image_file (boost::filesystem::path f)
{
/*
- Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
extern std::string digest_head_tail (std::vector<boost::filesystem::path>, boost::uintmax_t size);
extern void ensure_ui_thread ();
extern std::string audio_channel_name (int);
+extern std::string short_audio_channel_name (int);
extern bool valid_image_file (boost::filesystem::path);
extern bool valid_j2k_file (boost::filesystem::path);
#ifdef DCPOMATIC_WINDOWS
using std::cout;
using std::list;
+using std::vector;
+using std::pair;
using boost::shared_ptr;
using boost::bind;
using boost::optional;
try {
_analysis.reset (new AudioAnalysis (path));
+ } catch (OldFormatError& e) {
+ /* An old analysis file: recreate it */
+ JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
+ return;
} catch (xmlpp::exception& e) {
- /* Probably an old-style analysis file: recreate it */
+ /* Probably a (very) old-style analysis file: recreate it */
JobManager::instance()->analyse_audio (film, _playlist, _analysis_finished_connection, bind (&AudioDialog::analysis_finished, this));
return;
}
return;
}
- if (static_cast<bool>(_analysis->sample_peak ())) {
-
- float const peak_dB = 20 * log10 (_analysis->sample_peak().get()) + _analysis->gain_correction (_playlist);
-
- _sample_peak->SetLabel (
- wxString::Format (
- _("Sample peak is %.2fdB at %s"),
- peak_dB,
- time_to_timecode (_analysis->sample_peak_time().get(), film->video_frame_rate ()).data ()
- )
- );
-
- if (peak_dB > -3) {
- _sample_peak->SetForegroundColour (wxColour (255, 0, 0));
- } else {
- _sample_peak->SetForegroundColour (wxColour (0, 0, 0));
- }
+ pair<AudioAnalysis::PeakTime, int> const peak = _analysis->overall_sample_peak ();
+ float const peak_dB = 20 * log10 (peak.first.peak) + _analysis->gain_correction (_playlist);
+ _sample_peak->SetLabel (
+ wxString::Format (
+ _("Sample peak is %.2fdB at %s on %s"),
+ peak_dB,
+ time_to_timecode (peak.first.time, film->video_frame_rate ()).data (),
+ std_to_wx (short_audio_channel_name (peak.second)).data ()
+ )
+ );
+
+ if (peak_dB > -3) {
+ _sample_peak->SetForegroundColour (wxColour (255, 0, 0));
+ } else {
+ _sample_peak->SetForegroundColour (wxColour (0, 0, 0));
}
- if (static_cast<bool>(_analysis->true_peak ())) {
- float const peak_dB = 20 * log10 (_analysis->true_peak().get()) + _analysis->gain_correction (_playlist);
+ if (_analysis->overall_true_peak()) {
+ float const peak = _analysis->overall_true_peak().get();
+ float const peak_dB = 20 * log10 (peak) + _analysis->gain_correction (_playlist);
_true_peak->SetLabel (wxString::Format (_("True peak is %.2fdB"), peak_dB));
playlist->add (sel.front ());
try {
shared_ptr<AudioAnalysis> analysis (new AudioAnalysis (_parent->film()->audio_analysis_path (playlist)));
- if (analysis->sample_peak ()) {
- float const peak_dB = 20 * log10 (analysis->sample_peak().get()) + analysis->gain_correction (playlist);
- if (peak_dB > -3) {
- alert = true;
- }
- _peak->SetLabel (wxString::Format (_("Peak: %.2fdB"), peak_dB));
- } else {
- _peak->SetLabel (_("Peak: unknown"));
+ float const peak_dB = 20 * log10 (analysis->overall_sample_peak().first.peak) + analysis->gain_correction (playlist);
+ if (peak_dB > -3) {
+ alert = true;
}
+ _peak->SetLabel (wxString::Format (_("Peak: %.2fdB"), peak_dB));
} catch (...) {
_peak->SetLabel (_("Peak: unknown"));
}
#include "lib/playlist.h"
#include "test.h"
+using std::vector;
using boost::shared_ptr;
static float
}
}
- float const peak = random_float ();
- DCPTime const peak_time = DCPTime (rand ());
- a.set_sample_peak (peak, peak_time);
+ vector<AudioAnalysis::PeakTime> peak;
+ for (int i = 0; i < channels; ++i) {
+ peak.push_back (AudioAnalysis::PeakTime (random_float(), DCPTime (rand())));
+ }
+ a.set_sample_peak (peak);
a.write ("build/test/audio_analysis_serialisation_test");
}
}
- BOOST_CHECK (b.sample_peak ());
- BOOST_CHECK_CLOSE (b.sample_peak().get(), peak, 1);
- BOOST_CHECK (b.sample_peak_time ());
- BOOST_CHECK_EQUAL (b.sample_peak_time().get().get(), peak_time.get());
+ BOOST_REQUIRE_EQUAL (b.sample_peak().size(), 3);
+ for (int i = 0; i < channels; ++i) {
+ BOOST_CHECK_CLOSE (b.sample_peak()[i].peak, peak[i].peak, 1);
+ BOOST_CHECK_EQUAL (b.sample_peak()[i].time.get(), peak[i].time.get());
+ }
}
static void