Initial backend support for external export encoder
authorRobin Gareus <robin@gareus.org>
Mon, 19 Nov 2018 04:21:17 +0000 (05:21 +0100)
committerRobin Gareus <robin@gareus.org>
Mon, 19 Nov 2018 04:21:17 +0000 (05:21 +0100)
This adds an experimental pipe to ffmpeg to encode mp3. Currently
quality is hardcoded and various aspects remain to be implemented.
However, it is sufficient for initial testing.

libs/ardour/ardour/export_format_base.h
libs/ardour/ardour/export_formats.h
libs/ardour/ardour/export_graph_builder.h
libs/ardour/enums.cc
libs/ardour/export_format_manager.cc
libs/ardour/export_formats.cc
libs/ardour/export_graph_builder.cc
libs/ardour/export_profile_manager.cc

index ee2739eee1f576ab8c33115e9eb970596691a619..04566f081ee2d67e77d1d2ec238b265dbddd0cbe 100644 (file)
@@ -43,7 +43,8 @@ class LIBARDOUR_API ExportFormatBase {
 
        enum Type {
                T_None = 0,
-               T_Sndfile
+               T_Sndfile,
+               T_FFMPEG
        };
 
        enum FormatId {
@@ -56,7 +57,8 @@ class LIBARDOUR_API ExportFormatBase {
                F_IRCAM = SF_FORMAT_IRCAM,
                F_RAW = SF_FORMAT_RAW,
                F_FLAC = SF_FORMAT_FLAC,
-               F_Ogg = SF_FORMAT_OGG
+               F_Ogg = SF_FORMAT_OGG,
+               F_FFMPEG = 0xF10000
        };
 
        enum Endianness {
index 4c9c2057256da84ff586088e2d8564205bc259c9..8e51827b89be2f04028cc0ce1608463515d22f44 100644 (file)
@@ -210,6 +210,17 @@ class LIBARDOUR_API ExportFormatBWF : public ExportFormat, public HasSampleForma
        virtual bool has_broadcast_info () const { return true; }
 };
 
+
+class LIBARDOUR_API ExportFormatFFMPEG : public ExportFormat {
+  public:
+       ExportFormatFFMPEG (std::string const& name, std::string const& ext);
+       ~ExportFormatFFMPEG () {};
+
+       bool set_compatibility_state (ExportFormatCompatibility const & compatibility);
+       Type get_type () const { return T_FFMPEG; }
+       SampleFormat get_explicit_sample_format () const { return SF_Float; }
+};
+
 } // namespace ARDOUR
 
 #endif /* __ardour_export_formats__ */
index ebc3fdadfc2f1b9acb238c395512ddcceef2ecb5..00cf8226be184aef9cf8d8bfc2d60bbc85dcea92 100644 (file)
@@ -39,6 +39,7 @@ namespace AudioGrapher {
        template <typename T> class SampleFormatConverter;
        template <typename T> class Interleaver;
        template <typename T> class SndfileWriter;
+       template <typename T> class CmdPipeWriter;
        template <typename T> class SilenceTrimmer;
        template <typename T> class TmpFile;
        template <typename T> class Threader;
@@ -102,7 +103,11 @@ class LIBARDOUR_API ExportGraphBuilder
                typedef boost::shared_ptr<AudioGrapher::SndfileWriter<int> >    IntWriterPtr;
                typedef boost::shared_ptr<AudioGrapher::SndfileWriter<short> >  ShortWriterPtr;
 
+               typedef boost::shared_ptr<AudioGrapher::CmdPipeWriter<Sample> > FloatPipePtr;
+
                template<typename T> void init_writer (boost::shared_ptr<AudioGrapher::SndfileWriter<T> > & writer);
+               template<typename T> void init_writer (boost::shared_ptr<AudioGrapher::CmdPipeWriter<T> > & writer);
+
                void copy_files (std::string orig_path);
 
                FileSpec               config;
@@ -115,6 +120,7 @@ class LIBARDOUR_API ExportGraphBuilder
                FloatWriterPtr float_writer;
                IntWriterPtr   int_writer;
                ShortWriterPtr short_writer;
+               FloatPipePtr   pipe_writer;
        };
 
        // sample format converter
index 853391c3d9d6fe55ec840d41983ecc0efa28e03d..dd7c00f25f670b358b6bd7e3b7ff0faa351aed89 100644 (file)
@@ -577,6 +577,7 @@ setup_enum_writer ()
 
        REGISTER_CLASS_ENUM (ExportFormatBase, T_None);
        REGISTER_CLASS_ENUM (ExportFormatBase, T_Sndfile);
+       REGISTER_CLASS_ENUM (ExportFormatBase, T_FFMPEG);
        REGISTER (_ExportFormatBase_Type);
 
        REGISTER_CLASS_ENUM (ExportFormatBase, F_None);
@@ -589,6 +590,7 @@ setup_enum_writer ()
        REGISTER_CLASS_ENUM (ExportFormatBase, F_FLAC);
        REGISTER_CLASS_ENUM (ExportFormatBase, F_Ogg);
        REGISTER_CLASS_ENUM (ExportFormatBase, F_CAF);
+       REGISTER_CLASS_ENUM (ExportFormatBase, F_FFMPEG);
        REGISTER (_ExportFormatBase_FormatId);
 
        REGISTER_CLASS_ENUM (ExportFormatBase, E_FileDefault);
index 2b5fc1868ba500ea10a5e7dc687517d8cb5b2cef..3581fb890e28821692ef11180eef4981a7ead6a6 100644 (file)
@@ -19,6 +19,7 @@
 */
 
 #include "ardour/export_format_manager.h"
+#include "ardour/filesystem_paths.h"
 
 #include "ardour/export_format_specification.h"
 #include "ardour/export_format_compatibility.h"
@@ -210,6 +211,12 @@ ExportFormatManager::init_formats ()
                f_ptr.reset (new ExportFormatFLAC ());
                add_format (f_ptr);
        } catch (ExportFormatIncompatible & e) {}
+
+       std::string unused;
+       if (ArdourVideoToolPaths::transcoder_exe (unused, unused)) {
+               f_ptr.reset (new ExportFormatFFMPEG ("MP3", "mp3"));
+               add_format (f_ptr);
+       }
 }
 
 void
index 7fa30c6b51aac0760cc815701cd3d819a79cbad6..0ab02046c3f89b6495dacca73adb584ad92ae871 100644 (file)
@@ -365,4 +365,37 @@ ExportFormatBWF::set_compatibility_state (ExportFormatCompatibility const & comp
        return compatible;
 }
 
+
+/*** FFMPEG Pipe ***/
+
+ExportFormatFFMPEG::ExportFormatFFMPEG (std::string const& name, std::string const& ext)
+{
+       set_name (name);
+       set_format_id (F_FFMPEG);
+       sample_formats.insert (SF_Float);
+
+       add_sample_rate (SR_22_05);
+       add_sample_rate (SR_44_1);
+       add_sample_rate (SR_48);
+       add_sample_rate (SR_88_2);
+       add_sample_rate (SR_96);
+       add_sample_rate (SR_176_4);
+       add_sample_rate (SR_192);
+       add_sample_rate (SR_Session);
+
+       add_endianness (E_Little);
+
+       set_extension (ext);
+       set_quality (Q_LossyCompression);
+}
+
+bool
+ExportFormatFFMPEG::set_compatibility_state (ExportFormatCompatibility const & compatibility)
+{
+       bool compatible = compatibility.has_format (F_FFMPEG);
+       set_compatible (compatible);
+       return compatible;
+}
+
+
 }; // namespace ARDOUR
index b89e73d0eda270a28c8303bef1954f45e0d10888..b2eaafce21dc7aa3004110772c57ffa4af6f9702 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "audiographer/process_context.h"
 #include "audiographer/general/chunker.h"
+#include "audiographer/general/cmdpipe_writer.h"
 #include "audiographer/general/interleaver.h"
 #include "audiographer/general/normalizer.h"
 #include "audiographer/general/analyser.h"
 
 #include "ardour/audioengine.h"
 #include "ardour/export_channel_configuration.h"
+#include "ardour/export_failed.h"
 #include "ardour/export_filename.h"
 #include "ardour/export_format_specification.h"
 #include "ardour/export_timespan.h"
+#include "ardour/filesystem_paths.h"
 #include "ardour/session_directory.h"
 #include "ardour/sndfile_helpers.h"
+#include "ardour/system_exec.h"
 
 #include "pbd/file_utils.h"
 #include "pbd/cpus.h"
@@ -212,8 +216,13 @@ boost::shared_ptr<AudioGrapher::Sink<Sample> >
 ExportGraphBuilder::Encoder::init (FileSpec const & new_config)
 {
        config = new_config;
-       init_writer (float_writer);
-       return float_writer;
+       if (config.format->format_id() == ExportFormatBase::F_FFMPEG) {
+               init_writer (pipe_writer);
+               return pipe_writer;
+       } else {
+               init_writer (float_writer);
+               return float_writer;
+       }
 }
 
 template <>
@@ -257,6 +266,10 @@ ExportGraphBuilder::Encoder::destroy_writer (bool delete_out_file)
                        short_writer->close ();
                }
 
+               if (pipe_writer) {
+                       pipe_writer->close ();
+               }
+
                if (std::remove(writer_filename.c_str() ) != 0) {
                        std::cout << "Encoder::destroy_writer () : Error removing file: " << strerror(errno) << std::endl;
                }
@@ -265,6 +278,7 @@ ExportGraphBuilder::Encoder::destroy_writer (bool delete_out_file)
        float_writer.reset ();
        int_writer.reset ();
        short_writer.reset ();
+       pipe_writer.reset ();
 }
 
 bool
@@ -293,6 +307,69 @@ ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::Sndfil
        writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
 }
 
+template<typename T>
+void
+ExportGraphBuilder::Encoder::init_writer (boost::shared_ptr<AudioGrapher::CmdPipeWriter<T> > & writer)
+{
+       unsigned channels = config.channel_config->get_n_chans();
+       config.filename->set_channel_config(config.channel_config);
+       writer_filename = config.filename->get_path (config.format);
+
+       std::string ffmpeg_exe;
+       std::string unused;
+
+       if (!ArdourVideoToolPaths::transcoder_exe (ffmpeg_exe, unused)) {
+               throw ExportFailed ("External encoder (ffmpeg) is not available.");
+       }
+
+       int quality = 3; // TODO get from config.format
+
+       int a=0;
+       char **argp = (char**) calloc (100, sizeof(char*));
+       char tmp[64];
+       argp[a++] = strdup(ffmpeg_exe.c_str());
+       argp[a++] = strdup ("-f");
+       argp[a++] = strdup ("f32le");
+       argp[a++] = strdup ("-acodec");
+       argp[a++] = strdup ("pcm_f32le");
+       argp[a++] = strdup ("-ac");
+       snprintf (tmp, sizeof(tmp), "%d", channels);
+       argp[a++] = strdup (tmp);
+       argp[a++] = strdup ("-ar");
+       snprintf (tmp, sizeof(tmp), "%d", config.format->sample_rate());
+       argp[a++] = strdup (tmp);
+       argp[a++] = strdup ("-i");
+       argp[a++] = strdup ("pipe:0");
+
+       argp[a++] = strdup ("-y");
+       if (quality < 10) {
+               /* variable rate, lower is better */
+               snprintf (tmp, sizeof(tmp), "%d", quality);
+               argp[a++] = strdup ("-q:a"); argp[a++] = strdup (tmp);
+       } else {
+               /* fixed bitrate, higher is better */
+               snprintf (tmp, sizeof(tmp), "%dk", quality); // eg. "192k"
+               argp[a++] = strdup ("-b:a"); argp[a++] = strdup (tmp);
+       }
+
+       /* TODO: add SessionMetadata::Metadata()
+        * see gtk2_ardour/export_video_dialog.cc
+        * and gtk2_ardour/transcode_ffmpeg.cc
+        */
+
+       argp[a++] = strdup (writer_filename.c_str());
+       argp[a] = (char *)0;
+
+       /* argp is free()d in ~SystemExec,
+        * SystemExec is deleted when writer is destroyed */
+       ARDOUR::SystemExec* exec = new ARDOUR::SystemExec (ffmpeg_exe, argp);
+       if (exec->start(0)) {
+               throw ExportFailed ("External encoder (ffmpeg) cannot be started.");
+       }
+       writer.reset (new AudioGrapher::CmdPipeWriter<T> (exec, writer_filename));
+       writer->FileWritten.connect_same_thread (copy_files_connection, boost::bind (&ExportGraphBuilder::Encoder::copy_files, this, _1));
+}
+
 void
 ExportGraphBuilder::Encoder::copy_files (std::string orig_path)
 {
index f99755e1580677cada72fe75f9052af56659a5b8..28cccde3cdf74c2f26b4a89e3d479e71904ee0d6 100644 (file)
@@ -954,6 +954,8 @@ ExportProfileManager::check_format (ExportFormatSpecPtr format, uint32_t channel
        switch (format->type()) {
          case ExportFormatBase::T_Sndfile:
                return check_sndfile_format (format, channels);
+         case ExportFormatBase::T_FFMPEG:
+               return true;
 
          default:
                throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));