Basic stuff to analyse audio (job).
authorCarl Hetherington <cth@carlh.net>
Sun, 24 Feb 2013 22:15:50 +0000 (22:15 +0000)
committerCarl Hetherington <cth@carlh.net>
Sun, 24 Feb 2013 22:15:50 +0000 (22:15 +0000)
13 files changed:
src/lib/analyse_audio_job.cc [new file with mode: 0644]
src/lib/analyse_audio_job.h [new file with mode: 0644]
src/lib/audio_analysis.cc [new file with mode: 0644]
src/lib/audio_analysis.h [new file with mode: 0644]
src/lib/ffmpeg_decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/options.h
src/lib/wscript
src/tools/dvdomatic.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/wscript

diff --git a/src/lib/analyse_audio_job.cc b/src/lib/analyse_audio_job.cc
new file mode 100644 (file)
index 0000000..5623fdf
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "audio_analysis.h"
+#include "analyse_audio_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "options.h"
+#include "decoder_factory.h"
+#include "audio_decoder.h"
+
+using std::string;
+using std::max;
+using boost::shared_ptr;
+
+int const AnalyseAudioJob::_num_points = 1024;
+
+AnalyseAudioJob::AnalyseAudioJob (shared_ptr<Film> f)
+       : Job (f)
+       , _done_for_this_point (0)
+       , _done (0)
+       , _samples_per_point (1)
+{
+
+}
+
+string
+AnalyseAudioJob::name () const
+{
+       return String::compose ("Analyse audio of %1", _film->name());
+}
+
+void
+AnalyseAudioJob::run ()
+{
+       if (!_film->audio_stream () || !_film->length()) {
+               set_progress (1);
+               set_state (FINISHED_ERROR);
+               return;
+       }
+               
+       DecodeOptions options;
+       options.decode_video = false;
+
+       Decoders decoders = decoder_factory (_film, options);
+       assert (decoders.audio);
+       
+       decoders.audio->set_audio_stream (_film->audio_stream ());
+       decoders.audio->Audio.connect (bind (&AnalyseAudioJob::audio, this, _1));
+
+       int64_t total_audio_frames = video_frames_to_audio_frames (_film->length().get(), _film->audio_stream()->sample_rate(), _film->frames_per_second());
+       _samples_per_point = total_audio_frames / _num_points;
+
+       _current.resize (_film->audio_stream()->channels ());
+       _analysis.reset (new AudioAnalysis (_film->audio_stream()->channels()));
+                        
+       while (!decoders.audio->pass()) {
+               set_progress (float (_done) / total_audio_frames);
+       }
+
+       _analysis->write (_film->audio_analysis_path ());
+       
+       set_progress (1);
+       set_state (FINISHED_OK);
+}
+
+void
+AnalyseAudioJob::audio (shared_ptr<AudioBuffers> b)
+{
+       for (int i = 0; i < b->frames(); ++i) {
+               for (int j = 0; j < b->channels(); ++j) {
+                       float const s = b->data(j)[i];
+                       _current[j][AudioPoint::RMS] += pow (s, 2);
+                       _current[j][AudioPoint::PEAK] = max (_current[j][AudioPoint::PEAK], fabsf (s));
+
+                       if (_done_for_this_point == _samples_per_point) {
+                               _current[j][AudioPoint::RMS] = 20 * log10 (sqrt (_current[j][AudioPoint::RMS] / _samples_per_point));
+                               _current[j][AudioPoint::PEAK] = 20 * log10 (_current[j][AudioPoint::PEAK]);
+                               
+                               _analysis->add_point (j, _current[j]);
+                               
+                               _done_for_this_point = 0;
+                               _current[j] = AudioPoint ();
+                       }
+               }
+               
+               ++_done_for_this_point;
+       }
+
+       _done += b->frames ();
+}
+
diff --git a/src/lib/analyse_audio_job.h b/src/lib/analyse_audio_job.h
new file mode 100644 (file)
index 0000000..1e7229c
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "job.h"
+#include "audio_analysis.h"
+
+class AudioBuffers;
+
+class AnalyseAudioJob : public Job
+{
+public:
+       AnalyseAudioJob (boost::shared_ptr<Film> f);
+
+       std::string name () const;
+       void run ();
+
+private:
+       void audio (boost::shared_ptr<AudioBuffers>);
+
+       int64_t _done_for_this_point;
+       int64_t _done;
+       int64_t _samples_per_point;
+       std::vector<AudioPoint> _current;
+
+       boost::shared_ptr<AudioAnalysis> _analysis;
+
+       static const int _num_points;
+};
+
diff --git a/src/lib/audio_analysis.cc b/src/lib/audio_analysis.cc
new file mode 100644 (file)
index 0000000..4a710f4
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdint.h>
+#include <cassert>
+#include <fstream>
+#include "audio_analysis.h"
+
+using std::ostream;
+using std::string;
+using std::ofstream;
+using std::vector;
+
+AudioPoint::AudioPoint ()
+{
+       for (int i = 0; i < COUNT; ++i) {
+               _data[i] = 0;
+       }
+}
+
+void
+AudioPoint::write (ostream& s) const
+{
+       for (int i = 0; i < COUNT; ++i) {
+               s << _data[i] << "\n";
+       }
+}
+       
+
+AudioAnalysis::AudioAnalysis (int channels)
+{
+       _data.resize (channels);
+}
+
+void
+AudioAnalysis::add_point (int c, AudioPoint const & p)
+{
+       assert (c < int (_data.size ()));
+       _data[c].push_back (p);
+}
+
+void
+AudioAnalysis::write (string filename)
+{
+       ofstream f (filename.c_str ());
+       f << _data.size() << "\n";
+       for (vector<vector<AudioPoint> >::iterator i = _data.begin(); i != _data.end(); ++i) {
+               f << i->size () << "\n";
+               for (vector<AudioPoint>::iterator j = i->begin(); j != i->end(); ++j) {
+                       j->write (f);
+               }
+       }
+}
diff --git a/src/lib/audio_analysis.h b/src/lib/audio_analysis.h
new file mode 100644 (file)
index 0000000..1c668b9
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef DVDOMATIC_AUDIO_ANALYSIS_H
+#define DVDOMATIC_AUDIO_ANALYSIS_H
+
+#include <iostream>
+#include <vector>
+
+class AudioPoint
+{
+public:
+       enum Type {
+               PEAK,
+               RMS,
+               COUNT
+       };
+
+       AudioPoint ();
+
+       void write (std::ostream &) const;
+       
+       float& operator[] (Type t) {
+               return _data[t];
+       }
+
+private:
+       float _data[COUNT];
+};
+
+class AudioAnalysis
+{
+public:
+       AudioAnalysis (int c);
+
+       void add_point (int c, AudioPoint const & p);
+       void write (std::string);
+
+private:
+       std::vector<std::vector<AudioPoint> > _data;
+};
+
+#endif
index d4ed76e371ec414109bd36bc17a487ea6b9c8940..58c7317ac49700c1f792a52f97ddbe409e0e9606 100644 (file)
@@ -236,8 +236,10 @@ FFmpegDecoder::pass ()
 
                int frame_finished;
 
-               while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
-                       filter_and_emit_video (_frame);
+               if (_opt.decode_video) {
+                       while (avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet) >= 0 && frame_finished) {
+                               filter_and_emit_video (_frame);
+                       }
                }
 
                if (_audio_stream && _opt.decode_audio) {
@@ -258,7 +260,7 @@ FFmpegDecoder::pass ()
 
        shared_ptr<FFmpegAudioStream> ffa = dynamic_pointer_cast<FFmpegAudioStream> (_audio_stream);
 
-       if (_packet.stream_index == _video_stream) {
+       if (_packet.stream_index == _video_stream && _opt.decode_video) {
 
                int frame_finished;
                int const r = avcodec_decode_video2 (_video_codec_context, _frame, &frame_finished, &_packet);
@@ -288,9 +290,9 @@ FFmpegDecoder::pass ()
                           was before this packet.  Until then audio is thrown away.
                        */
                                
-                       if (_first_video && _first_video.get() <= source_pts_seconds) {
+                       if ((_first_video && _first_video.get() <= source_pts_seconds) || !_opt.decode_video) {
 
-                               if (!_first_audio) {
+                               if (!_first_audio && _opt.decode_video) {
                                        _first_audio = source_pts_seconds;
                                        
                                        /* This is our first audio frame, and if we've arrived here we must have had our
index 1cf161259a92e6f5e66b8f4128af396e2a7f9886..fb3fb4cde320a89dfca7bcf6bb991fbc307415c0 100644 (file)
@@ -51,6 +51,7 @@
 #include "video_decoder.h"
 #include "audio_decoder.h"
 #include "external_audio_decoder.h"
+#include "analyse_audio_job.h"
 
 using std::string;
 using std::stringstream;
@@ -237,6 +238,15 @@ Film::video_mxf_filename () const
        return video_state_identifier() + ".mxf";
 }
 
+string
+Film::audio_analysis_path () const
+{
+       boost::filesystem::path p;
+       p /= "analysis";
+       p /= content_digest();
+       return file (p.string ());
+}
+
 /** Add suitable Jobs to the JobManager to create a DCP for this Film */
 void
 Film::make_dcp ()
@@ -303,6 +313,19 @@ Film::make_dcp ()
        }
 }
 
+/** Start a job to analyse the audio of our content file */
+void
+Film::analyse_audio ()
+{
+       if (_analyse_audio_job) {
+               return;
+       }
+
+       _analyse_audio_job.reset (new AnalyseAudioJob (shared_from_this()));
+       _analyse_audio_job->Finished.connect (bind (&Film::analyse_audio_finished, this));
+       JobManager::instance()->add (_analyse_audio_job);
+}
+
 /** Start a job to examine our content file */
 void
 Film::examine_content ()
@@ -316,6 +339,12 @@ Film::examine_content ()
        JobManager::instance()->add (_examine_content_job);
 }
 
+void
+Film::analyse_audio_finished ()
+{
+       _analyse_audio_job.reset ();
+}
+
 void
 Film::examine_content_finished ()
 {
index 04a483998fe20eee2e26f3047d79471c07e7e7b4..c268d3eac9a01498505d50e6369089a19caeb279 100644 (file)
@@ -45,6 +45,7 @@ class Job;
 class Filter;
 class Log;
 class ExamineContentJob;
+class AnalyseAudioJob;
 class ExternalAudioStream;
 
 /** @class Film
@@ -65,8 +66,10 @@ public:
        std::string info_path (int f) const;
        std::string video_mxf_dir () const;
        std::string video_mxf_filename () const;
+       std::string audio_analysis_path () const;
 
        void examine_content ();
+       void analyse_audio ();
        void send_dcp_to_tms ();
 
        void make_dcp ();
@@ -374,9 +377,12 @@ private:
 
        /** Any running ExamineContentJob, or 0 */
        boost::shared_ptr<ExamineContentJob> _examine_content_job;
+       /** Any running AnalyseAudioJob, or 0 */
+       boost::shared_ptr<AnalyseAudioJob> _analyse_audio_job;
 
        void signal_changed (Property);
        void examine_content_finished ();
+       void analyse_audio_finished ();
        std::string video_state_identifier () const;
 
        /** Complete path to directory containing the film metadata;
index 2cd7dffdedaff57a9ba8bc107045dc710d0bbb03..0d2c07fd5674117d7afff91179b25885fbe9388a 100644 (file)
@@ -28,11 +28,13 @@ class DecodeOptions
 {
 public:
        DecodeOptions ()
-               : decode_audio (true)
+               : decode_video (true)
+               , decode_audio (true)
                , decode_subtitles (false)
                , video_sync (true)
        {}
-       
+
+       bool decode_video;
        bool decode_audio;
        bool decode_subtitles;
        bool video_sync;
index eee04190c1119c19510cdd6f551d712dbe4c0d6c..c2b46112cbe0de658ed5c9906661378bfcffd2fe 100644 (file)
@@ -16,6 +16,8 @@ def build(bld):
     obj.source = """
                 ab_transcode_job.cc
                 ab_transcoder.cc
+                 analyse_audio_job.cc
+                 audio_analysis.cc
                  audio_decoder.cc
                  audio_source.cc
                 config.cc
index 1b76132f604931f4bada50fc7c23d50eb74048ba..4f380b5ad87e6cc7e218415c03b91930c06691b7 100644 (file)
@@ -145,7 +145,7 @@ enum {
        ID_jobs_make_dcp,
        ID_jobs_send_dcp_to_tms,
        ID_jobs_show_dcp,
-       ID_jobs_examine_content,
+       ID_jobs_analyse_audio,
        ID_help_about
 };
 
@@ -170,7 +170,7 @@ setup_menu (wxMenuBar* m)
        add_item (jobs_menu, "&Send DCP to TMS", ID_jobs_send_dcp_to_tms, NEEDS_FILM);
        add_item (jobs_menu, "S&how DCP", ID_jobs_show_dcp, NEEDS_FILM);
        jobs_menu->AppendSeparator ();
-       add_item (jobs_menu, "&Examine content", ID_jobs_examine_content, NEEDS_FILM);
+       add_item (jobs_menu, "&Analyse audio", ID_jobs_analyse_audio, NEEDS_FILM);
 
        wxMenu* help = new wxMenu;
        add_item (help, "About", ID_help_about, ALWAYS);
@@ -207,7 +207,7 @@ public:
                Connect (ID_jobs_make_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_make_dcp));
                Connect (ID_jobs_send_dcp_to_tms, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_send_dcp_to_tms));
                Connect (ID_jobs_show_dcp, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_show_dcp));
-               Connect (ID_jobs_examine_content, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_examine_content));
+               Connect (ID_jobs_analyse_audio, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::jobs_analyse_audio));
                Connect (ID_help_about, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler (Frame::help_about));
 
                Connect (wxID_ANY, wxEVT_MENU_OPEN, wxMenuEventHandler (Frame::menu_opened));
@@ -386,10 +386,10 @@ private:
                }
 #endif         
        }
-       
-       void jobs_examine_content (wxCommandEvent &)
+
+       void jobs_analyse_audio (wxCommandEvent &)
        {
-               film->examine_content ();
+               film->analyse_audio ();
        }
        
        void help_about (wxCommandEvent &)
index 68291a812df03d5414d76dd258caae838a736940..4a67624dba15e72db0fbd635bec89ed47594f8f5 100644 (file)
@@ -45,6 +45,7 @@
 #include "sound_processor.h"
 #include "dci_metadata_dialog.h"
 #include "scaler.h"
+#include "audio_dialog.h"
 
 using std::string;
 using std::cout;
@@ -204,6 +205,7 @@ FilmEditor::connect_to_widgets ()
        _audio_gain_calculate_button->Connect (
                wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
                );
+       _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
        _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
        _use_content_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
        _use_external_audio->Connect (wxID_ANY, wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler (FilmEditor::use_audio_changed), 0, this);
@@ -315,6 +317,10 @@ FilmEditor::make_audio_panel ()
                grid->Add (s);
        }
 
+       _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
+       grid->AddSpacer (0);
+       grid->Add (_show_audio);
+
        {
                video_control (add_label_to_sizer (grid, _audio_panel, _("Audio Delay")));
                wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
@@ -1175,3 +1181,11 @@ FilmEditor::setup_dcp_name ()
                _dcp_name->SetLabel (std_to_wx (s));
        }
 }
+
+void
+FilmEditor::show_audio_clicked (wxCommandEvent &)
+{
+       AudioDialog* d = new AudioDialog (this, _film);
+       d->ShowModal ();
+       d->Destroy ();
+}
index 8cb90b481dd7523578f57731ecbc044ff3d96a85..bd0300aed6c5e9efd7fde7a35729b2008039ab58 100644 (file)
@@ -70,6 +70,7 @@ private:
        void scaler_changed (wxCommandEvent &);
        void audio_gain_changed (wxCommandEvent &);
        void audio_gain_calculate_button_clicked (wxCommandEvent &);
+       void show_audio_clicked (wxCommandEvent &);
        void audio_delay_changed (wxCommandEvent &);
        void with_subtitles_toggled (wxCommandEvent &);
        void subtitle_offset_changed (wxCommandEvent &);
@@ -145,6 +146,7 @@ private:
        wxSpinCtrl* _audio_gain;
        /** A button to open the gain calculation dialogue */
        wxButton* _audio_gain_calculate_button;
+       wxButton* _show_audio;
        /** The Film's audio delay */
        wxSpinCtrl* _audio_delay;
        wxCheckBox* _with_subtitles;
index d844b1f1b7a10e13e0810944295a4fc8898634c8..3fa40f55a7f811d74e1bf1186d99d3ee0a53288a 100644 (file)
@@ -13,6 +13,8 @@ def build(bld):
     obj.uselib = 'WXWIDGETS'
     obj.use = 'libdvdomatic'
     obj.source = """
+                 audio_dialog.cc
+                 audio_plot.cc
                  config_dialog.cc
                  dci_metadata_dialog.cc
                  dir_picker_ctrl.cc