[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()");