File indexing completed on 2024-05-12 04:51:34

0001 /*
0002     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
0003     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "k3boggvorbisencoder.h"
0009 #include "k3boggvorbisencoderdefaults.h"
0010 #include "k3bcore.h"
0011 #include "k3bplugin_i18n.h"
0012 #include <config-k3b.h>
0013 
0014 #include <KConfig>
0015 #include <KConfigGroup>
0016 #include <KSharedConfig>
0017 
0018 #include <QDebug>
0019 
0020 #include <vorbis/vorbisenc.h>
0021 
0022 // for the random generator
0023 #include <stdlib.h>
0024 #include <time.h>
0025 
0026 
0027 K_PLUGIN_CLASS_WITH_JSON(K3bOggVorbisEncoder, "k3boggvorbisencoder.json")
0028 
0029 
0030 // quality levels -1 to 10 map to 0 to 11
0031 static const int s_rough_average_quality_level_bitrates[] = {
0032     45,
0033     64,
0034     80,
0035     96,
0036     112,
0037     128,
0038     160,
0039     192,
0040     224,
0041     256,
0042     320,
0043     400
0044 };
0045 
0046 // quality levels -1 to 10 map to 0 to 11
0047 // static const char* s_ogg_quality_level_strings[] = {
0048 //   I18N_NOOP("Low quality"),
0049 //   I18N_NOOP(""),
0050 //   I18N_NOOP(""),
0051 //   I18N_NOOP(""),
0052 //   I18N_NOOP(""),
0053 //   I18N_NOOP("targetted %1 kbps"),
0054 //   I18N_NOOP("targetted %1 kbps"),
0055 //   I18N_NOOP("targetted %1 kbps"),
0056 //   I18N_NOOP(""),
0057 //   I18N_NOOP(""),
0058 //   I18N_NOOP(""),
0059 //   I18N_NOOP(""),
0060 // };
0061 
0062 
0063 // THIS IS BASED ON THE OGG VORBIS LIB EXAMPLE
0064 // BECAUSE OF THE LACK OF DOCUMENTATION
0065 
0066 
0067 class K3bOggVorbisEncoder::Private
0068 {
0069 public:
0070     Private()
0071         : manualBitrate(false),
0072           qualityLevel(4),
0073           bitrateUpper(-1),
0074           bitrateNominal(-1),
0075           bitrateLower(-1),
0076           //      sampleRate(44100),
0077           oggStream(0),
0078           oggPage(0),
0079           oggPacket(0),
0080           vorbisInfo(0),
0081           vorbisComment(0),
0082           vorbisDspState(0),
0083           vorbisBlock(0),
0084           headersWritten(false) {
0085     }
0086 
0087     // encoding settings
0088     bool manualBitrate;
0089     // 0 to 10 -> 0.0 - 1.0
0090     int qualityLevel;
0091     int bitrateUpper;
0092     int bitrateNominal;
0093     int bitrateLower;
0094     //  int sampleRate;
0095 
0096     // encoding structures
0097     ogg_stream_state *oggStream;       // take physical pages, weld into a logical stream of packets
0098     ogg_page         *oggPage;         // one Ogg bitstream page.  Vorbis packets are inside
0099     ogg_packet       *oggPacket;       // one raw packet of data for decode
0100     vorbis_info      *vorbisInfo;      // struct that stores all the static vorbis bitstream settings
0101     vorbis_comment   *vorbisComment;   // struct that stores all the user comments
0102     vorbis_dsp_state *vorbisDspState;  // central working state for the packet->PCM decoder
0103     vorbis_block     *vorbisBlock;     // local working space for packet->PCM decode
0104 
0105     bool headersWritten;
0106 };
0107 
0108 
0109 K3bOggVorbisEncoder::K3bOggVorbisEncoder( QObject* parent,  const QVariantList& )
0110     : K3b::AudioEncoder( parent )
0111 {
0112     d = new Private();
0113 }
0114 
0115 
0116 K3bOggVorbisEncoder::~K3bOggVorbisEncoder()
0117 {
0118     cleanup();
0119     delete d;
0120 }
0121 
0122 
0123 bool K3bOggVorbisEncoder::initEncoderInternal( const QString&, const K3b::Msf& /*length*/, const MetaData& metaData )
0124 {
0125     cleanup();
0126 
0127     // load user settings
0128     loadConfig();
0129 
0130     d->oggPage = new ogg_page;
0131     d->oggPacket = new ogg_packet;
0132     d->vorbisInfo = new vorbis_info;
0133 
0134     vorbis_info_init( d->vorbisInfo );
0135 
0136     int ret = 0;
0137 
0138     if( d->manualBitrate ) {
0139         qDebug() << "(K3bOggVorbisEncoder) calling: "
0140                  << "vorbis_encode_init( d->vorbisInfo, 2, 44100, "
0141                  << (d->bitrateUpper != -1 ? d->bitrateUpper*1000 : -1) << ", "
0142                  << (d->bitrateNominal != -1 ? d->bitrateNominal*1000 : -1)  << ", "
0143                  << (d->bitrateLower != -1 ? d->bitrateLower*1000 : -1) << " );" << Qt::endl;
0144 
0145         ret = vorbis_encode_init( d->vorbisInfo,
0146                                   2, // 2 channels: stereo
0147                                   44100,
0148                                   d->bitrateUpper != -1 ? d->bitrateUpper*1000 : -1,
0149                                   d->bitrateNominal != -1 ? d->bitrateNominal*1000 : -1,
0150                                   d->bitrateLower != -1 ? d->bitrateLower*1000 : -1 );
0151     }
0152     else {
0153         if( d->qualityLevel < -1 )
0154             d->qualityLevel = -1;
0155         else if( d->qualityLevel > 10 )
0156             d->qualityLevel = 10;
0157 
0158         qDebug() << "(K3bOggVorbisEncoder) calling: "
0159                  << "vorbis_encode_init_vbr( d->vorbisInfo, 2, 44100, "
0160                  << (float)d->qualityLevel/10.0 << ");" << Qt::endl;
0161 
0162         ret = vorbis_encode_init_vbr( d->vorbisInfo,
0163                                       2, // 2 channels: stereo
0164                                       44100,
0165                                       (float)d->qualityLevel/10.0 );
0166     }
0167 
0168     if( ret ) {
0169         qDebug() << "(K3bOggVorbisEncoder) vorbis_encode_init failed: " << ret;
0170         cleanup();
0171         return false;
0172     }
0173 
0174     // init the comment stuff
0175     d->vorbisComment = new vorbis_comment;
0176     vorbis_comment_init( d->vorbisComment );
0177 
0178     // add the encoder tag (so everybody knows we did it! ;)
0179     vorbis_comment_add_tag( d->vorbisComment, QByteArray("ENCODER").data(), QByteArray("K3bOggVorbisEncoderPlugin").data() );
0180 
0181     // set up the analysis state and auxiliary encoding storage
0182     d->vorbisDspState = new vorbis_dsp_state;
0183     d->vorbisBlock = new vorbis_block;
0184     vorbis_analysis_init( d->vorbisDspState, d->vorbisInfo );
0185     vorbis_block_init( d->vorbisDspState, d->vorbisBlock );
0186 
0187     // set up our packet->stream encoder
0188     // pick a random serial number; that way we can more likely build
0189     // chained streams just by concatenation
0190     d->oggStream = new ogg_stream_state;
0191     srand( time(0) );
0192     ogg_stream_init( d->oggStream, rand() );
0193 
0194     // Set meta data
0195     for( MetaData::const_iterator it = metaData.constBegin(); it != metaData.constEnd(); ++it ) {
0196         QByteArray key;
0197 
0198         switch( it.key() ) {
0199         case META_TRACK_TITLE:
0200             key = "TITLE";
0201             break;
0202         case META_TRACK_ARTIST:
0203             key = "ARTIST";
0204             break;
0205         case META_ALBUM_TITLE:
0206             key = "ALBUM";
0207             break;
0208         case META_ALBUM_COMMENT:
0209             key = "DESCRIPTION";
0210             break;
0211         case META_YEAR:
0212             key = "DATE";
0213             break;
0214         case META_TRACK_NUMBER:
0215             key = "TRACKNUMBER";
0216             break;
0217         case META_GENRE:
0218             key = "GENRE";
0219             break;
0220         default:
0221             break;
0222         }
0223 
0224         if( !key.isEmpty() ) {
0225             vorbis_comment_add_tag( d->vorbisComment, key.data(), it.value().toString().toUtf8().data() );
0226         }
0227     }
0228 
0229     return true;
0230 }
0231 
0232 
0233 bool K3bOggVorbisEncoder::writeOggHeaders()
0234 {
0235     if( !d->oggStream ) {
0236         qDebug() << "(K3bOggVorbisEncoder) call to writeOggHeaders without init.";
0237         return false;
0238     }
0239     if( d->headersWritten ) {
0240         qDebug() << "(K3bOggVorbisEncoder) headers already written.";
0241         return true;
0242     }
0243 
0244     //
0245     // Vorbis streams begin with three headers; the initial header (with
0246     // most of the codec setup parameters) which is mandated by the Ogg
0247     // bitstream spec.  The second header holds any comment fields.  The
0248     // third header holds the bitstream codebook.  We merely need to
0249     // make the headers, then pass them to libvorbis one at a time;
0250     // libvorbis handles the additional Ogg bitstream constraints
0251     //
0252     ogg_packet header;
0253     ogg_packet header_comm;
0254     ogg_packet header_code;
0255 
0256     vorbis_analysis_headerout( d->vorbisDspState,
0257                                d->vorbisComment,
0258                                &header,
0259                                &header_comm,
0260                                &header_code);
0261 
0262     // automatically placed in its own page
0263     ogg_stream_packetin( d->oggStream, &header );
0264     ogg_stream_packetin( d->oggStream, &header_comm );
0265     ogg_stream_packetin( d->oggStream, &header_code );
0266 
0267     //
0268     // This ensures the actual
0269     // audio data will start on a new page, as per spec
0270     //
0271     QByteArray data;
0272     while( ogg_stream_flush( d->oggStream, d->oggPage ) ) {
0273         writeData( (char*)d->oggPage->header, d->oggPage->header_len );
0274         writeData( (char*)d->oggPage->body, d->oggPage->body_len );
0275     }
0276 
0277     d->headersWritten = true;
0278 
0279     return true;
0280 }
0281 
0282 
0283 qint64 K3bOggVorbisEncoder::encodeInternal( const char* data, qint64 len )
0284 {
0285     if( !d->headersWritten )
0286         if( !writeOggHeaders() )
0287             return -1;
0288 
0289     // expose the buffer to submit data
0290     float** buffer = vorbis_analysis_buffer( d->vorbisDspState, len/4 );
0291 
0292     // uninterleave samples
0293     qint64 i = 0;
0294     for( i = 0; i < len/4; ++i ) {
0295         buffer[0][i]=( (data[i*4+1]<<8) | (0x00ff&(int)data[i*4]) ) / 32768.f;
0296         buffer[1][i]=( (data[i*4+3]<<8) | (0x00ff&(int)data[i*4+2]) ) / 32768.f;
0297     }
0298 
0299     // tell the library how much we actually submitted
0300     vorbis_analysis_wrote( d->vorbisDspState, i );
0301 
0302     return flushVorbis();
0303 }
0304 
0305 
0306 long K3bOggVorbisEncoder::flushVorbis()
0307 {
0308     // vorbis does some data preanalysis, then divvies up blocks for
0309     // more involved (potentially parallel) processing.  Get a single
0310     // block for encoding now
0311     long writtenData = 0;
0312     while( vorbis_analysis_blockout( d->vorbisDspState, d->vorbisBlock ) == 1 ) {
0313 
0314         // analysis
0315         vorbis_analysis( d->vorbisBlock, 0 );
0316         vorbis_bitrate_addblock( d->vorbisBlock );
0317 
0318         while( vorbis_bitrate_flushpacket( d->vorbisDspState, d->oggPacket ) ) {
0319 
0320             // weld the packet into the bitstream
0321             ogg_stream_packetin( d->oggStream, d->oggPacket );
0322 
0323             // write out pages (if any)
0324             while( ogg_stream_pageout( d->oggStream, d->oggPage ) ) {
0325                 writeData( (char*)d->oggPage->header, d->oggPage->header_len );
0326                 writeData( (char*)d->oggPage->body, d->oggPage->body_len );
0327 
0328                 writtenData += ( d->oggPage->header_len + d->oggPage->body_len );
0329             }
0330         }
0331     }
0332 
0333     return writtenData;
0334 }
0335 
0336 
0337 void K3bOggVorbisEncoder::finishEncoderInternal()
0338 {
0339     if( d->vorbisDspState ) {
0340         vorbis_analysis_wrote( d->vorbisDspState, 0 );
0341         flushVorbis();
0342     }
0343     else
0344         qDebug() << "(K3bOggVorbisEncoder) call to finishEncoderInternal without init.";
0345 }
0346 
0347 
0348 void K3bOggVorbisEncoder::cleanup()
0349 {
0350     if( d->oggStream ) {
0351         ogg_stream_clear( d->oggStream );
0352         delete d->oggStream;
0353         d->oggStream = 0;
0354     }
0355     if( d->vorbisBlock ) {
0356         vorbis_block_clear( d->vorbisBlock );
0357         delete d->vorbisBlock;
0358         d->vorbisBlock = 0;
0359     }
0360     if( d->vorbisDspState ) {
0361         vorbis_dsp_clear( d->vorbisDspState );
0362         delete d->vorbisDspState;
0363         d->vorbisDspState = 0;
0364     }
0365     if( d->vorbisComment ) {
0366         vorbis_comment_clear( d->vorbisComment );
0367         delete d->vorbisComment;
0368         d->vorbisComment = 0;
0369     }
0370     if( d->vorbisInfo ) {
0371         vorbis_info_clear( d->vorbisInfo );
0372         delete d->vorbisInfo;
0373         d->vorbisInfo = 0;
0374     }
0375 
0376     // ogg_page and ogg_packet structs always point to storage in
0377     // libvorbis.  They're never freed or manipulated directly
0378     if( d->oggPage ) {
0379         delete d->oggPage;
0380         d->oggPage = 0;
0381     }
0382     if( d->oggPacket ) {
0383         delete d->oggPacket;
0384         d->oggPacket = 0;
0385     }
0386 
0387     d->headersWritten = false;
0388 }
0389 
0390 
0391 void K3bOggVorbisEncoder::loadConfig()
0392 {
0393     KSharedConfig::Ptr c = KSharedConfig::openConfig();
0394     KConfigGroup grp(c, QStringLiteral("K3bOggVorbisEncoderPlugin") );
0395 
0396     d->manualBitrate = grp.readEntry( "manual bitrate", DEFAULT_MANUAL_BITRATE );
0397     d->qualityLevel = grp.readEntry( "quality level", DEFAULT_QUALITY_LEVEL );
0398     d->bitrateUpper = grp.readEntry( "bitrate upper", DEFAULT_BITRATE_UPPER );
0399     d->bitrateNominal = grp.readEntry( "bitrate nominal", DEFAULT_BITRATE_NOMINAL );
0400     d->bitrateLower = grp.readEntry( "bitrate lower", DEFAULT_BITRATE_LOWER );
0401     //  d->sampleRate = c->readEntry( "samplerate", DEFAULT_SAMPLERATE );
0402 }
0403 
0404 
0405 QString K3bOggVorbisEncoder::fileTypeComment( const QString& ) const
0406 {
0407     return i18n("Ogg-Vorbis");
0408 }
0409 
0410 
0411 long long K3bOggVorbisEncoder::fileSize( const QString&, const K3b::Msf& msf ) const
0412 {
0413     KSharedConfig::Ptr c = KSharedConfig::openConfig();
0414     KConfigGroup grp(c, QStringLiteral("K3bOggVorbisEncoderPlugin") );
0415 
0416     // the following code is based on the size estimation from the audiocd KIO worker
0417     // TODO: reimplement.
0418 
0419     if( !grp.readEntry( "manual bitrate", DEFAULT_MANUAL_BITRATE ) ) {
0420         // Estimated numbers based on the Vorbis FAQ:
0421         // https://xiph.org/vorbis/faq/#quality
0422 
0423 //     static long vorbis_q_bitrate[] = { 45, 60,  74,  86,  106, 120, 152,
0424 //                     183, 207, 239, 309, 440 };
0425 
0426         int qualityLevel = grp.readEntry( "quality level", DEFAULT_QUALITY_LEVEL );
0427 
0428         if( qualityLevel < -1 )
0429             qualityLevel = -1;
0430         else if( qualityLevel > 10 )
0431             qualityLevel = 10;
0432         return ( (msf.totalFrames()/75) * s_rough_average_quality_level_bitrates[qualityLevel+1] * 1000 ) / 8;
0433     }
0434     else {
0435         return (msf.totalFrames()/75) * grp.readEntry( "bitrate nominal", 160 ) * 1000 / 8;
0436     }
0437 }
0438 
0439 #include "k3boggvorbisencoder.moc"
0440 
0441 #include "moc_k3boggvorbisencoder.cpp"