[multimedia/kwave] /: Work/the/ogg vorbis more than 2 channels

Thomas Eschenbacher <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit c84134e9771eee05b1356b9bc1bf09d8564e8817 by Thomas Eschenbacher.
Committed on 18/08/2026 at 13:56.
Pushed by eschenbacher into branch 'master'.

Work/the/ogg vorbis more than 2 channels

* codec_ogg: support for up to 255 channels for ogg/opus and ogg/vorbis
* ogg/vorbis switch to VBR mode if more than 2 channels
*  file info: remember last ABR/VBR selection

M  +1    -1    .gitlab-ci.yml
M  +3    -1    CHANGES
M  +1    -79   plugins/codec_ogg/OpusEncoder.cpp
M  +1    -24   plugins/codec_ogg/OpusEncoder.h
M  +54   -9    plugins/codec_ogg/VorbisEncoder.cpp
M  +14   -5    plugins/fileinfo/FileInfoDialog.cpp

https://invent.kde.org/multimedia/kwave/-/commit/c84134e9771eee05b1356b9bc1bf09d8564e8817

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 47de347d..7be5fc0e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,7 +14,7 @@ include:
 
 codespell:
   stage: validate
-  image: python:3-slim
+  image: python:3.13-slim
   before_script:
     - pip install --no-cache-dir --break-system-packages codespell
   script:
diff --git a/CHANGES b/CHANGES
index 0e7512ba..d1ef8093 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,9 @@
-26.11.70 [2026-08-15]
+26.11.70 [2026-08-17]
  * codec_mp3: workaround for broken MBS conversion in ID3LIB
  * codec_mp3: do no longer explicitly link against libstdc++ and libz
  * codec_ogg: parallelized decoding of ogg/opus and ogg/vorbis
+ * codec_ogg: support for up to 255 channels for ogg/opus and ogg/vorbis
+ * file info: remember last ABR/VBR selection
  * integrated codespell into CI pipeline and fixed many codespell errors
  * OverviewCache: parallelized multi track cache validation
  * improved GUI responsiveness by pushing file load into a worker thread
diff --git a/plugins/codec_ogg/OpusEncoder.cpp b/plugins/codec_ogg/OpusEncoder.cpp
index 5154d23f..f4787ca7 100644
--- a/plugins/codec_ogg/OpusEncoder.cpp
+++ b/plugins/codec_ogg/OpusEncoder.cpp
@@ -73,7 +73,6 @@
 #include "libkwave/Sample.h"
 #include "libkwave/SampleArray.h"
 #include "libkwave/Utils.h"
-#include "libkwave/modules/ChannelMixer.h"
 #include "libkwave/modules/RateConverter.h"
 
 #include "OpusCommon.h"
@@ -98,11 +97,9 @@
 Kwave::OpusEncoder::OpusEncoder()
     :m_comments_map(),
      m_info(),
-     m_downmix(DOWNMIX_AUTO),
      m_bitrate(0),
      m_coding_rate(0),
      m_encoder_channels(0),
-     m_channel_mixer(nullptr),
      m_rate_converter(nullptr),
      m_frame_size(0),
      m_extra_out(0),
@@ -124,73 +121,6 @@ Kwave::OpusEncoder::~OpusEncoder()
 {
 }
 
-/***************************************************************************/
-bool Kwave::OpusEncoder::setupDownMix(QWidget *widget, unsigned int tracks,
-                                      int bitrate)
-{
-    // get "downmix" setting, default is "auto"
-    m_downmix = DOWNMIX_AUTO; // currently not user configurable
-
-    if ((m_downmix == DOWNMIX_AUTO) &&
-        (bitrate > 0) && (bitrate < (32000 * Kwave::toInt(tracks))))
-    {
-        if (tracks > 8) {
-            // downmix from more than 8 channels to mono
-            if (Kwave::MessageBox::warningContinueCancel(
-                widget,
-                i18n("Surround bitrate would be less than 32kBit/sec per "
-                      "channel, this file should be mixed down to mono."),
-                QString(), QString(), QString(),
-                _("opus_accept_down_mix_on_export")) != KMessageBox::Continue)
-            {
-                return false;
-            }
-            m_downmix = DOWNMIX_MONO;
-        } else if (tracks > 2) {
-            // downmix from more than stereo to stereo
-            if (Kwave::MessageBox::warningContinueCancel(
-                widget,
-                i18n("Surround bitrate would be less than 32kBit/sec per "
-                      "channel, this file should be mixed down to stereo."),
-                QString(), QString(), QString(),
-                _("opus_accept_down_mix_on_export")) != KMessageBox::Continue)
-            {
-                return false;
-            }
-            m_downmix = DOWNMIX_STEREO;
-        }
-    }
-    if (m_downmix == DOWNMIX_AUTO) // if still "auto"
-        m_downmix = DOWNMIX_OFF;   // then switch it off
-
-    switch (m_downmix) {
-        case DOWNMIX_MONO:   m_encoder_channels = 1;      break;
-        case DOWNMIX_STEREO: m_encoder_channels = 2;      break;
-        default:             m_encoder_channels = tracks; break;
-    }
-
-    if (m_encoder_channels != tracks) {
-        // create a channel mixer
-        m_channel_mixer = new(std::nothrow)
-            Kwave::ChannelMixer(tracks, m_encoder_channels);
-        Q_ASSERT(m_channel_mixer);
-        if (!m_channel_mixer || !m_channel_mixer->init()) {
-            qWarning("creating channel mixer failed");
-            return false;
-        }
-
-        // connect it to the end of the current preprocessing queue
-        // (normally this is the original sample source)
-        if (!Kwave::connect(*m_last_queue_element, *m_channel_mixer)) {
-            qWarning("connecting the channel mixer failed");
-            return false;
-        }
-        m_last_queue_element = m_channel_mixer;
-    }
-
-    return true;
-}
-
 /***************************************************************************/
 bool Kwave::OpusEncoder::setupBitrate(QWidget *widget, unsigned int tracks)
 {
@@ -505,7 +435,6 @@ bool Kwave::OpusEncoder::open(QWidget *widget, const Kwave::FileInfo &info,
     int err;
 
     // reset everything to defaults
-    m_downmix            = DOWNMIX_AUTO;
     m_bitrate            = -1;
     m_coding_rate        = 0;
     m_extra_out          = 0;
@@ -514,16 +443,12 @@ bool Kwave::OpusEncoder::open(QWidget *widget, const Kwave::FileInfo &info,
     memset(&m_opus_header.map, 0xFF, sizeof(m_opus_header.map));
     m_max_frame_bytes    = 0;
     m_last_queue_element = &src;
+    m_encoder_channels   = src_tracks;
 
     // get the desired bitrate
     if (!setupBitrate(widget, src_tracks))
         return false;
 
-    // determine the down mixing mode
-    // and set up the mixer if necessary
-    if (!setupDownMix(widget, src_tracks, m_bitrate))
-        return false;
-
     // determine the decoding sample rate
     // and set up the rate converter if necessary
     if (!setupCodingRate(widget, m_encoder_channels, sample_rate))
@@ -964,9 +889,6 @@ bool Kwave::OpusEncoder::encode(Kwave::MultiTrackReader &src,
 /***************************************************************************/
 void Kwave::OpusEncoder::close()
 {
-    delete m_channel_mixer;
-    m_channel_mixer = nullptr;
-
     delete m_rate_converter;
     m_rate_converter = nullptr;
 
diff --git a/plugins/codec_ogg/OpusEncoder.h b/plugins/codec_ogg/OpusEncoder.h
index fa146b80..44780318 100644
--- a/plugins/codec_ogg/OpusEncoder.h
+++ b/plugins/codec_ogg/OpusEncoder.h
@@ -39,7 +39,6 @@ class QWidget;
 namespace Kwave
 {
 
-    class ChannelMixer;
     class FileInfo;
     class MultiTrackReader;
     class StreamObject;
@@ -90,15 +89,6 @@ namespace Kwave
 
     private:
 
-        /**
-         * set up the downmixing mode
-         * @param widget a QWidget to be used as parent for error messages
-         * @param tracks number of tracks
-         * @param bitrate in bits/sec or -1 for "auto"
-         * @return true if succeeded or false if failed/canceled
-         */
-        bool setupDownMix(QWidget *widget, unsigned int tracks, int bitrate);
-
         /**
          * determine the bitrate to use for encoding
          * @param widget a QWidget to be used as parent for error messages
@@ -177,28 +167,15 @@ namespace Kwave
         /** one raw packet of data for decode */
         ogg_packet       m_op;
 
-        /**
-         * downmix mode: off, automatic, mono or stereo
-         */
-        enum {
-            DOWNMIX_OFF    = -1, /**< no downmixing               */
-            DOWNMIX_AUTO   =  0, /**< automatic, based on bitrate */
-            DOWNMIX_MONO   =  1, /**< downmix to mono             */
-            DOWNMIX_STEREO =  2  /**< downmix to stereo           */
-        } m_downmix;
-
         /** bitrate in bits/sec */
         int m_bitrate;
 
         /** encoding sample rate in bits/sec */
         int m_coding_rate;
 
-        /** number of tracks used for encoding, after downmixing */
+        /** number of tracks used for encoding */
         unsigned int m_encoder_channels;
 
-        /** channel mixer (if downmixing is required) */
-        Kwave::ChannelMixer *m_channel_mixer;
-
         /** sample rate converter (if needed) */
         Kwave::StreamObject *m_rate_converter;
 
diff --git a/plugins/codec_ogg/VorbisEncoder.cpp b/plugins/codec_ogg/VorbisEncoder.cpp
index b96b46d1..c1e8cf2e 100644
--- a/plugins/codec_ogg/VorbisEncoder.cpp
+++ b/plugins/codec_ogg/VorbisEncoder.cpp
@@ -25,7 +25,10 @@
 #include <QTime>
 #include <QtGlobal>
 
+#include <KConfig>
+#include <KConfigGroup>
 #include <KLocalizedString>
+#include <KSharedConfig>
 
 #include "libkwave/MessageBox.h"
 #include "libkwave/MultiTrackReader.h"
@@ -35,12 +38,17 @@
 
 #include "VorbisEncoder.h"
 
+using namespace Qt::StringLiterals;
+
 /** size of a buffer used for Vorbis encoding */
 #define BUFFER_SIZE 1024
 
 /** bitrate to be used when no bitrate has been selected */
 #define DEFAULT_BITRATE 64000
 
+/** section in the config file with default settings (see file info dialog)*/
+#define CONFIG_DEFAULT_SECTION u"plugin fileinfo - setup dialog"_s
+
 /***************************************************************************/
 Kwave::VorbisEncoder::VorbisEncoder()
     :m_comments_map(), m_info()
@@ -91,10 +99,11 @@ bool Kwave::VorbisEncoder::open(QWidget *widget, const Kwave::FileInfo &info,
     m_info = info;
     const unsigned int tracks = info.tracks();
     long int sample_rate = static_cast<long int>(info.rate());
+    int default_bitrate = (DEFAULT_BITRATE / 2) * tracks;
 
-    if (tracks > 2) {
+    if ((tracks ==0) || (tracks > 255)) {
         Kwave::MessageBox::sorry(widget,
-            i18n("This codec supports only mono or stereo files, "
+            i18n("This codec supports only 1 ... 255 channels, "
                  "%1 channels are not supported.", tracks));
         return false;
     }
@@ -111,26 +120,62 @@ bool Kwave::VorbisEncoder::open(QWidget *widget, const Kwave::FileInfo &info,
     int vbr_quality = info.contains(Kwave::INF_VBR_QUALITY) ?
         QVariant(info.get(Kwave::INF_VBR_QUALITY)).toInt() : -1;
 
+    // force VBR mode for multi-channel audio (> 2 tracks)
+    // libvorbis Bitrate Management (ABR/CBR) does NOT support more than 2 channels.
+    if (tracks > 2) {
+        if (vbr_quality < 0) {
+            KConfigGroup cfg = KSharedConfig::openConfig()->group(
+                CONFIG_DEFAULT_SECTION);
+            vbr_quality = cfg.readEntry("default_vbr_quality", -1);
+            qDebug("default VBR quality is: %d%%", vbr_quality);
+
+            // default to 50% quality if no quality setting is present
+            if (vbr_quality < 0)
+                vbr_quality = 50;
+            if (Kwave::MessageBox::warningContinueCancel(widget,
+                i18n("Ogg/Vorbis does not support ABR mode when using more "
+                     "than two channels. Switch to VBR mode with %1% "
+                     "and continue?",
+                     vbr_quality)) != KMessageBox::Continue)
+                return false; // <- canceled
+        }
+        // Reset ABR/CBR settings to enforce VBR mode
+        bitrate_nominal = -1;
+        bitrate_lower   = -1;
+        bitrate_upper   = -1;
+    }
+
     qDebug("OggEncoder: ABR=%d...%d...%d Bits/s, VBR=%d%%",
-           bitrate_lower,bitrate_nominal,bitrate_upper,vbr_quality);
+           bitrate_lower, bitrate_nominal, bitrate_upper,vbr_quality);
 
-    if ((vbr_quality < 0) && (bitrate_nominal <= 0)) {
+    // fallback check if no bitrate or VBR quality was specified at all
+    if ((vbr_quality < 0) && (bitrate_nominal <= 0) &&
+        (bitrate_lower <= 0) && (bitrate_upper <= 0))
+    {
         // no quality and no bitrate given -> complain !
         if (Kwave::MessageBox::warningContinueCancel(widget,
             i18n("You have not selected any bitrate for the encoding. "
                  "Do you want to continue and encode with %1 kBit/s "
                  "or cancel and choose a different bitrate?",
-                 DEFAULT_BITRATE / 1000)) != KMessageBox::Continue)
+                 default_bitrate / 1000)) != KMessageBox::Continue)
             return false; // <- canceled
 
-        bitrate_nominal = DEFAULT_BITRATE;
+        bitrate_nominal = default_bitrate;
         bitrate_lower = -1;
         bitrate_upper = -1;
     }
 
-    // some checks first
-    // Q_ASSERT(tracks < 255);
-    // if (tracks > 255) return false;
+    // enforce minimum bitrate thresholds for ABR/CBR (Mono/Stereo)
+    // libvorbis requires at least ~45 kbit/s per channel at standard sample rates
+    if ((vbr_quality < 0) && (bitrate_nominal > 0)) {
+        int min_bitrate_floor = 45000 * tracks;
+        if (bitrate_nominal < min_bitrate_floor) {
+            qWarning("VorbisEncoder: requested bitrate %d bps is too low."\
+                     " adjusting to floor: %d bps",
+                     bitrate_nominal, min_bitrate_floor);
+            bitrate_nominal = min_bitrate_floor;
+        }
+    }
 
     /********** Encode setup ************/
     vorbis_info_init(&m_vi);
diff --git a/plugins/fileinfo/FileInfoDialog.cpp b/plugins/fileinfo/FileInfoDialog.cpp
index 96a9dd69..25d05e96 100644
--- a/plugins/fileinfo/FileInfoDialog.cpp
+++ b/plugins/fileinfo/FileInfoDialog.cpp
@@ -312,10 +312,16 @@ void Kwave::FileInfoDialog::setupCompressionTab(KConfigGroup &cfg)
     updateAvailableCompressions();
     initInfo(lblCompression, cbCompression, Kwave::INF_COMPRESSION);
 
+    // ABR or VBR mode
     compressionWidget->init(m_info);
-    compressionWidget->setMode(m_info.contains(Kwave::INF_VBR_QUALITY) ?
-        Kwave::CompressionWidget::VBR_MODE :
-        Kwave::CompressionWidget::ABR_MODE);
+    Kwave::CompressionWidget::Mode abr_vbr =
+        static_cast<Kwave::CompressionWidget::Mode>(
+            cfg.readEntry("default_abr_vbr_mode",
+                static_cast<int>(Kwave::CompressionWidget::VBR_MODE)));
+    if (abr_vbr == Kwave::CompressionWidget::VBR_MODE)
+        compressionWidget->setMode(Kwave::CompressionWidget::VBR_MODE);
+    else
+        compressionWidget->setMode(Kwave::CompressionWidget::ABR_MODE);
 
     // ABR bitrate settings
     int abr_bitrate = m_info.contains(Kwave::INF_BITRATE_NOMINAL) ?
@@ -323,10 +329,10 @@ void Kwave::FileInfoDialog::setupCompressionTab(KConfigGroup &cfg)
                   cfg.readEntry("default_abr_nominal_bitrate", -1);
     int min_bitrate = m_info.contains(Kwave::INF_BITRATE_LOWER) ?
                   m_info.get(Kwave::INF_BITRATE_LOWER).toInt() :
-                  cfg.readEntry("default_abr_lower_bitrate",-1);
+                  cfg.readEntry("default_abr_lower_bitrate", -1);
     int max_bitrate = m_info.contains(Kwave::INF_BITRATE_UPPER) ?
                   m_info.get(Kwave::INF_BITRATE_UPPER).toInt() :
-                  cfg.readEntry("default_abr_upper_bitrate",-1);
+                  cfg.readEntry("default_abr_upper_bitrate", -1);
     compressionWidget->setBitrates(abr_bitrate, min_bitrate, max_bitrate);
 
     // VBR base quality
@@ -967,6 +973,9 @@ void Kwave::FileInfoDialog::accept()
 
         int quality = compressionWidget->baseQuality();
         cfg.writeEntry("default_vbr_quality", quality);
+
+        cfg.writeEntry("default_abr_vbr_mode",
+            static_cast<int>(compressionWidget->mode()));
     }
 
     qDebug("FileInfoDialog::accept()");
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.