From: Carl Hetherington Date: Sun, 13 Jul 2014 19:44:45 +0000 (+0100) Subject: Very basic audio processing framework. X-Git-Tag: v2.0.48~728 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=3ddd928233130695d7f4eeee47a71409d8c04de7;hp=e8c5f14cb6736bdfa3610b2559c6c331c1c56984 Very basic audio processing framework. --- diff --git a/src/lib/audio_content.cc b/src/lib/audio_content.cc index 6317aa4cb..d02728b00 100644 --- a/src/lib/audio_content.cc +++ b/src/lib/audio_content.cc @@ -26,6 +26,7 @@ #include "exceptions.h" #include "config.h" #include "frame_rate_change.h" +#include "audio_processor.h" #include "i18n.h" @@ -42,11 +43,13 @@ int const AudioContentProperty::AUDIO_FRAME_RATE = 202; int const AudioContentProperty::AUDIO_GAIN = 203; int const AudioContentProperty::AUDIO_DELAY = 204; int const AudioContentProperty::AUDIO_MAPPING = 205; +int const AudioContentProperty::AUDIO_PROCESSOR = 206; AudioContent::AudioContent (shared_ptr f) : Content (f) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } @@ -55,6 +58,7 @@ AudioContent::AudioContent (shared_ptr f, DCPTime s) : Content (f, s) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } @@ -63,15 +67,20 @@ AudioContent::AudioContent (shared_ptr f, boost::filesystem::path p) : Content (f, p) , _audio_gain (0) , _audio_delay (Config::instance()->default_audio_delay ()) + , _audio_processor (0) { } AudioContent::AudioContent (shared_ptr f, cxml::ConstNodePtr node) : Content (f, node) + , _audio_processor (0) { _audio_gain = node->number_child ("AudioGain"); _audio_delay = node->number_child ("AudioDelay"); + if (node->optional_string_child ("AudioProcessor")) { + _audio_processor = AudioProcessor::from_id (node->string_child ("AudioProcessor")); + } } AudioContent::AudioContent (shared_ptr f, vector > c) @@ -94,6 +103,7 @@ AudioContent::AudioContent (shared_ptr f, vector _audio_gain = ref->audio_gain (); _audio_delay = ref->audio_delay (); + _audio_processor = ref->audio_processor (); } void @@ -102,6 +112,9 @@ AudioContent::as_xml (xmlpp::Node* node) const boost::mutex::scoped_lock lm (_mutex); node->add_child("AudioGain")->add_child_text (raw_convert (_audio_gain)); node->add_child("AudioDelay")->add_child_text (raw_convert (_audio_delay)); + if (_audio_processor) { + node->add_child("AudioProcessor")->add_child_text (_audio_processor->id ()); + } } @@ -127,6 +140,22 @@ AudioContent::set_audio_delay (int d) signal_changed (AudioContentProperty::AUDIO_DELAY); } +void +AudioContent::set_audio_processor (AudioProcessor const * p) +{ + { + boost::mutex::scoped_lock lm (_mutex); + _audio_processor = p; + } + + /* The channel count might have changed, so reset the mapping */ + AudioMapping m (processed_audio_channels ()); + m.make_default (); + set_audio_mapping (m); + + signal_changed (AudioContentProperty::AUDIO_PROCESSOR); +} + boost::signals2::connection AudioContent::analyse_audio (boost::function finished) { @@ -196,3 +225,14 @@ AudioContent::resampled_audio_frame_rate () const return rint (t); } + +int +AudioContent::processed_audio_channels () const +{ + if (!audio_processor ()) { + return audio_channels (); + } + + return audio_processor()->out_channels (audio_channels ()); +} + diff --git a/src/lib/audio_content.h b/src/lib/audio_content.h index 131ced61a..540839d69 100644 --- a/src/lib/audio_content.h +++ b/src/lib/audio_content.h @@ -31,6 +31,8 @@ namespace cxml { class Node; } +class AudioProcessor; + class AudioContentProperty { public: @@ -40,6 +42,7 @@ public: static int const AUDIO_GAIN; static int const AUDIO_DELAY; static int const AUDIO_MAPPING; + static int const AUDIO_PROCESSOR; }; /** @class AudioContent @@ -70,11 +73,13 @@ public: virtual boost::filesystem::path audio_analysis_path () const; int resampled_audio_frame_rate () const; + int processed_audio_channels () const; boost::signals2::connection analyse_audio (boost::function); void set_audio_gain (double); void set_audio_delay (int); + void set_audio_processor (AudioProcessor const *); double audio_gain () const { boost::mutex::scoped_lock lm (_mutex); @@ -86,11 +91,17 @@ public: return _audio_delay; } + AudioProcessor const * audio_processor () const { + boost::mutex::scoped_lock lm (_mutex); + return _audio_processor; + } + private: /** Gain to apply to audio in dB */ double _audio_gain; /** Delay to apply to audio (positive moves audio later) in milliseconds */ int _audio_delay; + AudioProcessor const * _audio_processor; }; #endif diff --git a/src/lib/audio_decoder.cc b/src/lib/audio_decoder.cc index 97a088791..19f31d30a 100644 --- a/src/lib/audio_decoder.cc +++ b/src/lib/audio_decoder.cc @@ -24,6 +24,7 @@ #include "resampler.h" #include "util.h" #include "film.h" +#include "audio_processor.h" #include "i18n.h" @@ -43,13 +44,17 @@ AudioDecoder::AudioDecoder (shared_ptr content) _resampler.reset (new Resampler (content->audio_frame_rate(), content->resampled_audio_frame_rate(), content->audio_channels ())); } + if (content->audio_processor ()) { + _processor = content->audio_processor()->clone (); + } + reset_decoded_audio (); } void AudioDecoder::reset_decoded_audio () { - _decoded_audio = ContentAudio (shared_ptr (new AudioBuffers (_audio_content->audio_channels(), 0)), 0); + _decoded_audio = ContentAudio (shared_ptr (new AudioBuffers (_audio_content->processed_audio_channels(), 0)), 0); } shared_ptr @@ -125,6 +130,10 @@ AudioDecoder::audio (shared_ptr data, ContentTime time) data = _resampler->run (data); } + if (_processor) { + data = _processor->run (data); + } + AudioFrame const frame_rate = _audio_content->resampled_audio_frame_rate (); if (_seek_reference) { diff --git a/src/lib/audio_decoder.h b/src/lib/audio_decoder.h index 0553d7c81..045efc002 100644 --- a/src/lib/audio_decoder.h +++ b/src/lib/audio_decoder.h @@ -61,6 +61,7 @@ protected: boost::shared_ptr _audio_content; boost::shared_ptr _resampler; + boost::shared_ptr _processor; boost::optional _audio_position; /** Currently-available decoded audio data */ ContentAudio _decoded_audio; diff --git a/src/lib/audio_mapping.cc b/src/lib/audio_mapping.cc index b3757c5f1..e86e2e2ac 100644 --- a/src/lib/audio_mapping.cc +++ b/src/lib/audio_mapping.cc @@ -40,7 +40,7 @@ AudioMapping::AudioMapping () } -/** Create a default AudioMapping for a given channel count. +/** Create an empty AudioMapping for a given channel count. * @param channels Number of channels. */ AudioMapping::AudioMapping (int channels) diff --git a/src/lib/audio_processor.cc b/src/lib/audio_processor.cc new file mode 100644 index 000000000..27146806b --- /dev/null +++ b/src/lib/audio_processor.cc @@ -0,0 +1,50 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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_processor.h" +#include "mid_side_decoder.h" + +using std::string; +using std::list; + +list AudioProcessor::_all; + +void +AudioProcessor::setup_audio_processors () +{ + _all.push_back (new MidSideDecoder ()); +} + +AudioProcessor const * +AudioProcessor::from_id (string id) +{ + for (list::const_iterator i = _all.begin(); i != _all.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + + return 0; +} + +list +AudioProcessor::all () +{ + return _all; +} diff --git a/src/lib/audio_processor.h b/src/lib/audio_processor.h new file mode 100644 index 000000000..7ff9f0ec6 --- /dev/null +++ b/src/lib/audio_processor.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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 DCPOMATIC_AUDIO_PROCESSOR_H +#define DCPOMATIC_AUDIO_PROCESSOR_H + +#include +#include +#include +#include "channel_count.h" + +class AudioBuffers; + +class AudioProcessor +{ +public: + virtual ~AudioProcessor () {} + + virtual std::string name () const = 0; + virtual std::string id () const = 0; + virtual ChannelCount in_channels () const = 0; + virtual int out_channels (int) const = 0; + virtual boost::shared_ptr clone () const = 0; + virtual boost::shared_ptr run (boost::shared_ptr) = 0; + + static std::list all (); + static void setup_audio_processors (); + static AudioProcessor const * from_id (std::string); + +private: + static std::list _all; +}; + +#endif diff --git a/src/lib/channel_count.h b/src/lib/channel_count.h new file mode 100644 index 000000000..4247fc063 --- /dev/null +++ b/src/lib/channel_count.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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 DCPOMATIC_CHANNEL_COUNT_H +#define DCPOMATIC_CHANNEL_COUNT_H + +class ChannelCount +{ +public: + ChannelCount () + : min (0) + , max (0) + {} + + ChannelCount (int n) + : min (n) + , max (n) + {} + + ChannelCount (int min_, int max_) + : min (min_) + , max (max_) + {} + + bool includes (int c) { + return min <= c && c <= max; + } + + int min; + int max; +}; + +#endif diff --git a/src/lib/mid_side_decoder.cc b/src/lib/mid_side_decoder.cc new file mode 100644 index 000000000..a518a4389 --- /dev/null +++ b/src/lib/mid_side_decoder.cc @@ -0,0 +1,72 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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 "mid_side_decoder.h" +#include "audio_buffers.h" + +#include "i18n.h" + +using std::string; +using boost::shared_ptr; + +string +MidSideDecoder::name () const +{ + return _("Mid-side decoder"); +} + +string +MidSideDecoder::id () const +{ + return N_("mid-side-decoder"); +} + +ChannelCount +MidSideDecoder::in_channels () const +{ + return ChannelCount (2); +} + +int +MidSideDecoder::out_channels (int) const +{ + return 3; +} + +shared_ptr +MidSideDecoder::clone () const +{ + return shared_ptr (new MidSideDecoder ()); +} + +shared_ptr +MidSideDecoder::run (shared_ptr in) +{ + shared_ptr out (new AudioBuffers (3, in->frames ())); + for (int i = 0; i < in->frames(); ++i) { + float const left = in->data()[0][i]; + float const right = in->data()[1][i]; + float const mid = (left + right) / 2; + out->data()[0][i] = left - mid; + out->data()[1][i] = right - mid; + out->data()[2][i] = mid; + } + + return out; +} diff --git a/src/lib/mid_side_decoder.h b/src/lib/mid_side_decoder.h new file mode 100644 index 000000000..a6d2721b6 --- /dev/null +++ b/src/lib/mid_side_decoder.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2014 Carl Hetherington + + 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_processor.h" + +class MidSideDecoder : public AudioProcessor +{ +public: + std::string name () const; + std::string id () const; + ChannelCount in_channels () const; + int out_channels (int) const; + boost::shared_ptr clone () const; + boost::shared_ptr run (boost::shared_ptr); +}; + + diff --git a/src/lib/util.cc b/src/lib/util.cc index 55df5cc83..dd39e286e 100644 --- a/src/lib/util.cc +++ b/src/lib/util.cc @@ -72,6 +72,7 @@ extern "C" { #include "video_content.h" #include "rect.h" #include "md5_digester.h" +#include "audio_processor.h" #ifdef DCPOMATIC_WINDOWS #include "stack.hpp" #endif @@ -336,6 +337,7 @@ dcpomatic_setup () Scaler::setup_scalers (); Filter::setup_filters (); SoundProcessor::setup_sound_processors (); + AudioProcessor::setup_audio_processors (); ui_thread = boost::this_thread::get_id (); } diff --git a/src/lib/wscript b/src/lib/wscript index d96bb7f96..60150f4da 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -8,6 +8,7 @@ sources = """ audio_content.cc audio_decoder.cc audio_mapping.cc + audio_processor.cc cinema.cc colour_conversion.cc config.cc @@ -55,6 +56,7 @@ sources = """ log.cc magick_image_proxy.cc md5_digester.cc + mid_side_decoder.cc player.cc player_video.cc playlist.cc diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc index 10b5a5f76..651ea93e1 100644 --- a/src/wx/audio_panel.cc +++ b/src/wx/audio_panel.cc @@ -23,6 +23,7 @@ #include "lib/sound_processor.h" #include "lib/ffmpeg_content.h" #include "lib/ffmpeg_audio_stream.h" +#include "lib/audio_processor.h" #include "audio_dialog.h" #include "audio_panel.h" #include "audio_mapping_view.h" @@ -33,6 +34,7 @@ using std::vector; using std::cout; using std::string; +using std::list; using boost::dynamic_pointer_cast; using boost::lexical_cast; using boost::shared_ptr; @@ -84,6 +86,12 @@ AudioPanel::AudioPanel (ContentPanel* p) grid->Add (_stream, wxGBPosition (r, 1)); _description = add_label_to_grid_bag_sizer (grid, this, "", false, wxGBPosition (r, 3)); ++r; + + add_label_to_grid_bag_sizer (grid, this, _("Process with"), true, wxGBPosition (r, 0)); + _processor = new wxChoice (this, wxID_ANY); + setup_processors (); + grid->Add (_processor, wxGBPosition (r, 1)); + ++r; _mapping = new AudioMappingView (this); _sizer->Add (_mapping, 1, wxEXPAND | wxALL, 6); @@ -93,9 +101,10 @@ AudioPanel::AudioPanel (ContentPanel* p) _gain->wrapped()->SetIncrement (0.5); _delay->wrapped()->SetRange (-1000, 1000); - _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::stream_changed, this)); - _show->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::show_clicked, this)); - _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::gain_calculate_button_clicked, this)); + _stream->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::stream_changed, this)); + _show->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::show_clicked, this)); + _gain_calculate_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&AudioPanel::gain_calculate_button_clicked, this)); + _processor->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&AudioPanel::processor_changed, this)); _mapping->Changed.connect (boost::bind (&AudioPanel::mapping_changed, this, _1)); } @@ -145,6 +154,12 @@ AudioPanel::film_content_changed (int property) setup_stream_description (); } } + } else if (property == AudioContentProperty::AUDIO_PROCESSOR) { + if (acs) { + checked_set (_processor, acs->audio_processor() ? acs->audio_processor()->id() : N_("none")); + } else { + checked_set (_processor, N_("none")); + } } } @@ -245,6 +260,21 @@ AudioPanel::setup_stream_description () } } +void +AudioPanel::processor_changed () +{ + string const s = string_client_data (_processor->GetClientObject (_processor->GetSelection ())); + AudioProcessor const * p = 0; + if (s != wx_to_std (N_("none"))) { + p = AudioProcessor::from_id (s); + } + + AudioContentList c = _parent->selected_audio (); + for (AudioContentList::const_iterator i = c.begin(); i != c.end(); ++i) { + (*i)->set_audio_processor (p); + } +} + void AudioPanel::mapping_changed (AudioMapping m) { @@ -266,11 +296,37 @@ AudioPanel::content_selection_changed () _gain->set_content (sel); _delay->set_content (sel); + _gain_calculate_button->Enable (sel.size() == 1); _show->Enable (sel.size() == 1); _stream->Enable (sel.size() == 1); + _processor->Enable (!sel.empty()); _mapping->Enable (sel.size() == 1); + setup_processors (); + film_content_changed (AudioContentProperty::AUDIO_MAPPING); + film_content_changed (AudioContentProperty::AUDIO_PROCESSOR); film_content_changed (FFmpegContentProperty::AUDIO_STREAM); film_content_changed (FFmpegContentProperty::AUDIO_STREAMS); } + +void +AudioPanel::setup_processors () +{ + AudioContentList sel = _parent->selected_audio (); + + _processor->Clear (); + list ap = AudioProcessor::all (); + _processor->Append (_("None"), new wxStringClientData (N_("none"))); + for (list::const_iterator i = ap.begin(); i != ap.end(); ++i) { + + AudioContentList::const_iterator j = sel.begin(); + while (j != sel.end() && (*i)->in_channels().includes ((*j)->audio_channels ())) { + ++j; + } + + if (j == sel.end ()) { + _processor->Append (std_to_wx ((*i)->name ()), new wxStringClientData (std_to_wx ((*i)->id ()))); + } + } +} diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h index 02faf6cd4..9a9d32698 100644 --- a/src/wx/audio_panel.h +++ b/src/wx/audio_panel.h @@ -43,6 +43,8 @@ private: void stream_changed (); void mapping_changed (AudioMapping); void setup_stream_description (); + void processor_changed (); + void setup_processors (); ContentSpinCtrlDouble* _gain; wxButton* _gain_calculate_button; @@ -50,6 +52,7 @@ private: ContentSpinCtrl* _delay; wxChoice* _stream; wxStaticText* _description; + wxChoice* _processor; AudioMappingView* _mapping; AudioDialog* _audio_dialog; };