--- /dev/null
+/*
+ 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 ();
+}
+
--- /dev/null
+/*
+ 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;
+};
+
--- /dev/null
+/*
+ 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);
+ }
+ }
+}
--- /dev/null
+/*
+ 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
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) {
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);
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
#include "video_decoder.h"
#include "audio_decoder.h"
#include "external_audio_decoder.h"
+#include "analyse_audio_job.h"
using std::string;
using std::stringstream;
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 ()
}
}
+/** 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 ()
JobManager::instance()->add (_examine_content_job);
}
+void
+Film::analyse_audio_finished ()
+{
+ _analyse_audio_job.reset ();
+}
+
void
Film::examine_content_finished ()
{
class Filter;
class Log;
class ExamineContentJob;
+class AnalyseAudioJob;
class ExternalAudioStream;
/** @class Film
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 ();
/** 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;
{
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;
obj.source = """
ab_transcode_job.cc
ab_transcoder.cc
+ analyse_audio_job.cc
+ audio_analysis.cc
audio_decoder.cc
audio_source.cc
config.cc
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
};
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);
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));
}
#endif
}
-
- void jobs_examine_content (wxCommandEvent &)
+
+ void jobs_analyse_audio (wxCommandEvent &)
{
- film->examine_content ();
+ film->analyse_audio ();
}
void help_about (wxCommandEvent &)
#include "sound_processor.h"
#include "dci_metadata_dialog.h"
#include "scaler.h"
+#include "audio_dialog.h"
using std::string;
using std::cout;
_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);
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);
_dcp_name->SetLabel (std_to_wx (s));
}
}
+
+void
+FilmEditor::show_audio_clicked (wxCommandEvent &)
+{
+ AudioDialog* d = new AudioDialog (this, _film);
+ d->ShowModal ();
+ d->Destroy ();
+}
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 &);
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;
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