You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k3b/plugins/encoder/ogg/k3boggvorbisencoder.cpp

556 lines
16 KiB

/*
*
* $Id: k3boggvorbisencoder.cpp 619556 2007-01-03 17:38:12Z trueg $
* Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
*
* This file is part of the K3b project.
* Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
*
* 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.
* See the file "COPYING" for the exact licensing terms.
*/
#include <config.h>
#include "k3boggvorbisencoder.h"
#include "base_k3boggvorbisencodersettingswidget.h"
#include <k3bcore.h>
#include <k3bpluginfactory.h>
#include <klocale.h>
#include <kconfig.h>
#include <kdebug.h>
#include <knuminput.h>
#include <tqlayout.h>
#include <tqradiobutton.h>
#include <tqslider.h>
#include <tqlcdnumber.h>
#include <tqcheckbox.h>
#include <tqcstring.h>
#include <tqtooltip.h>
#include <tqwhatsthis.h>
#include <tqlabel.h>
#include <vorbis/vorbisenc.h>
// for the random generator
#include <stdlib.h>
#include <time.h>
K_EXPORT_COMPONENT_FACTORY( libk3boggvorbisencoder, K3bPluginFactory<K3bOggVorbisEncoder>( "libk3boggvorbisencoder" ) )
// quality levels -1 to 10 map to 0 to 11
static const int s_rough_average_quality_level_bitrates[] = {
45,
64,
80,
96,
112,
128,
160,
192,
224,
256,
320,
400
};
// quality levels -1 to 10 map to 0 to 11
// static const char* s_ogg_quality_level_strings[] = {
// I18N_NOOP("Low quality"),
// I18N_NOOP(""),
// I18N_NOOP(""),
// I18N_NOOP(""),
// I18N_NOOP(""),
// I18N_NOOP("targetted %1 kbps"),
// I18N_NOOP("targetted %1 kbps"),
// I18N_NOOP("targetted %1 kbps"),
// I18N_NOOP(""),
// I18N_NOOP(""),
// I18N_NOOP(""),
// I18N_NOOP(""),
// };
// THIS IS BASED ON THE OGG VORBIS LIB EXAMPLE
// BECAUSE OF THE LACK OF DOCUMENTATION
class K3bOggVorbisEncoder::Private
{
public:
Private()
: manualBitrate(false),
qualityLevel(4),
bitrateUpper(-1),
bitrateNominal(-1),
bitrateLower(-1),
// sampleRate(44100),
oggStream(0),
oggPage(0),
oggPacket(0),
vorbisInfo(0),
vorbisComment(0),
vorbisDspState(0),
vorbisBlock(0),
headersWritten(false) {
}
// encoding settings
bool manualBitrate;
// 0 to 10 -> 0.0 - 1.0
int qualityLevel;
int bitrateUpper;
int bitrateNominal;
int bitrateLower;
// int sampleRate;
// encoding structures
ogg_stream_state *oggStream; // take physical pages, weld into a logical stream of packets
ogg_page *oggPage; // one Ogg bitstream page. Vorbis packets are inside
ogg_packet *oggPacket; // one raw packet of data for decode
vorbis_info *vorbisInfo; // struct that stores all the static vorbis bitstream settings
vorbis_comment *vorbisComment; // struct that stores all the user comments
vorbis_dsp_state *vorbisDspState; // central working state for the packet->PCM decoder
vorbis_block *vorbisBlock; // local working space for packet->PCM decode
bool headersWritten;
};
K3bOggVorbisEncoder::K3bOggVorbisEncoder( TQObject* parent, const char* name )
: K3bAudioEncoder( parent, name )
{
d = new Private();
}
K3bOggVorbisEncoder::~K3bOggVorbisEncoder()
{
cleanup();
delete d;
}
bool K3bOggVorbisEncoder::initEncoderInternal( const TQString&, const K3b::Msf& )
{
cleanup();
// load user settings
loadConfig();
d->oggPage = new ogg_page;
d->oggPacket = new ogg_packet;
d->vorbisInfo = new vorbis_info;
vorbis_info_init( d->vorbisInfo );
int ret = 0;
if( d->manualBitrate ) {
kdDebug() << "(K3bOggVorbisEncoder) calling: "
<< "vorbis_encode_init( d->vorbisInfo, 2, 44100, "
<< (d->bitrateUpper != -1 ? d->bitrateUpper*1000 : -1) << ", "
<< (d->bitrateNominal != -1 ? d->bitrateNominal*1000 : -1) << ", "
<< (d->bitrateLower != -1 ? d->bitrateLower*1000 : -1) << " );" << endl;
ret = vorbis_encode_init( d->vorbisInfo,
2, // 2 channels: stereo
44100,
d->bitrateUpper != -1 ? d->bitrateUpper*1000 : -1,
d->bitrateNominal != -1 ? d->bitrateNominal*1000 : -1,
d->bitrateLower != -1 ? d->bitrateLower*1000 : -1 );
}
else {
if( d->qualityLevel < -1 )
d->qualityLevel = -1;
else if( d->qualityLevel > 10 )
d->qualityLevel = 10;
kdDebug() << "(K3bOggVorbisEncoder) calling: "
<< "vorbis_encode_init_vbr( d->vorbisInfo, 2, 44100, "
<< (float)d->qualityLevel/10.0 << ");" << endl;
ret = vorbis_encode_init_vbr( d->vorbisInfo,
2, // 2 channels: stereo
44100,
(float)d->qualityLevel/10.0 );
}
if( ret ) {
kdDebug() << "(K3bOggVorbisEncoder) vorbis_encode_init failed: " << ret << endl;
cleanup();
return false;
}
// init the comment stuff
d->vorbisComment = new vorbis_comment;
vorbis_comment_init( d->vorbisComment );
// add the encoder tag (so everybody knows we did it! ;)
vorbis_comment_add_tag( d->vorbisComment, TQCString("ENCODER").data(), TQCString("K3bOggVorbisEncoderPlugin").data() );
// set up the analysis state and auxiliary encoding storage
d->vorbisDspState = new vorbis_dsp_state;
d->vorbisBlock = new vorbis_block;
vorbis_analysis_init( d->vorbisDspState, d->vorbisInfo );
vorbis_block_init( d->vorbisDspState, d->vorbisBlock );
// set up our packet->stream encoder
// pick a random serial number; that way we can more likely build
// chained streams just by concatenation
d->oggStream = new ogg_stream_state;
srand( time(0) );
ogg_stream_init( d->oggStream, rand() );
return true;
}
bool K3bOggVorbisEncoder::writeOggHeaders()
{
if( !d->oggStream ) {
kdDebug() << "(K3bOggVorbisEncoder) call to writeOggHeaders without init." << endl;
return false;
}
if( d->headersWritten ) {
kdDebug() << "(K3bOggVorbisEncoder) headers already written." << endl;
return true;
}
//
// Vorbis streams begin with three headers; the initial header (with
// most of the codec setup parameters) which is mandated by the Ogg
// bitstream spec. The second header holds any comment fields. The
// third header holds the bitstream codebook. We merely need to
// make the headers, then pass them to libvorbis one at a time;
// libvorbis handles the additional Ogg bitstream constraints
//
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout( d->vorbisDspState,
d->vorbisComment,
&header,
&header_comm,
&header_code);
// automatically placed in its own page
ogg_stream_packetin( d->oggStream, &header );
ogg_stream_packetin( d->oggStream, &header_comm );
ogg_stream_packetin( d->oggStream, &header_code );
//
// This ensures the actual
// audio data will start on a new page, as per spec
//
TQByteArray data;
while( ogg_stream_flush( d->oggStream, d->oggPage ) ) {
writeData( (char*)d->oggPage->header, d->oggPage->header_len );
writeData( (char*)d->oggPage->body, d->oggPage->body_len );
}
d->headersWritten = true;
return true;
}
long K3bOggVorbisEncoder::encodeInternal( const char* data, TQ_ULONG len )
{
if( !d->headersWritten )
if( !writeOggHeaders() )
return -1;
// expose the buffer to submit data
float** buffer = vorbis_analysis_buffer( d->vorbisDspState, len/4 );
// uninterleave samples
unsigned long i = 0;
for( i = 0; i < len/4; i++ ) {
buffer[0][i]=( (data[i*4+1]<<8) | (0x00ff&(int)data[i*4]) ) / 32768.f;
buffer[1][i]=( (data[i*4+3]<<8) | (0x00ff&(int)data[i*4+2]) ) / 32768.f;
}
// tell the library how much we actually submitted
vorbis_analysis_wrote( d->vorbisDspState, i );
return flushVorbis();
}
long K3bOggVorbisEncoder::flushVorbis()
{
// vorbis does some data preanalysis, then divvies up blocks for
// more involved (potentially parallel) processing. Get a single
// block for encoding now
long writtenData = 0;
while( vorbis_analysis_blockout( d->vorbisDspState, d->vorbisBlock ) == 1 ) {
// analysis
vorbis_analysis( d->vorbisBlock, 0 );
vorbis_bitrate_addblock( d->vorbisBlock );
while( vorbis_bitrate_flushpacket( d->vorbisDspState, d->oggPacket ) ) {
// weld the packet into the bitstream
ogg_stream_packetin( d->oggStream, d->oggPacket );
// write out pages (if any)
while( ogg_stream_pageout( d->oggStream, d->oggPage ) ) {
writeData( (char*)d->oggPage->header, d->oggPage->header_len );
writeData( (char*)d->oggPage->body, d->oggPage->body_len );
writtenData += ( d->oggPage->header_len + d->oggPage->body_len );
}
}
}
return writtenData;
}
void K3bOggVorbisEncoder::finishEncoderInternal()
{
if( d->vorbisDspState ) {
vorbis_analysis_wrote( d->vorbisDspState, 0 );
flushVorbis();
}
else
kdDebug() << "(K3bOggVorbisEncoder) call to finishEncoderInternal without init." << endl;
}
void K3bOggVorbisEncoder::setMetaDataInternal( K3bAudioEncoder::MetaDataField f, const TQString& value )
{
if( d->vorbisComment ) {
TQCString key;
switch( f ) {
case META_TRACK_TITLE:
key = "TITLE";
break;
case META_TRACK_ARTIST:
key = "ARTIST";
break;
case META_ALBUM_TITLE:
key = "ALBUM";
break;
case META_ALBUM_COMMENT:
key = "DESCRIPTION";
break;
case META_YEAR:
key = "DATE";
break;
case META_TRACK_NUMBER:
key = "TRACKNUMBER";
break;
case META_GENRE:
key = "GENRE";
break;
default:
return;
}
vorbis_comment_add_tag( d->vorbisComment, key.data(), value.utf8().data() );
}
else
kdDebug() << "(K3bOggVorbisEncoder) call to setMetaDataInternal without init." << endl;
}
void K3bOggVorbisEncoder::cleanup()
{
if( d->oggStream ) {
ogg_stream_clear( d->oggStream );
delete d->oggStream;
d->oggStream = 0;
}
if( d->vorbisBlock ) {
vorbis_block_clear( d->vorbisBlock );
delete d->vorbisBlock;
d->vorbisBlock = 0;
}
if( d->vorbisDspState ) {
vorbis_dsp_clear( d->vorbisDspState );
delete d->vorbisDspState;
d->vorbisDspState = 0;
}
if( d->vorbisComment ) {
vorbis_comment_clear( d->vorbisComment );
delete d->vorbisComment;
d->vorbisComment = 0;
}
if( d->vorbisInfo ) {
vorbis_info_clear( d->vorbisInfo );
delete d->vorbisInfo;
d->vorbisInfo = 0;
}
// ogg_page and ogg_packet structs always point to storage in
// libvorbis. They're never freed or manipulated directly
if( d->oggPage ) {
delete d->oggPage;
d->oggPage = 0;
}
if( d->oggPacket ) {
delete d->oggPacket;
d->oggPacket = 0;
}
d->headersWritten = false;
}
void K3bOggVorbisEncoder::loadConfig()
{
TDEConfig* c = k3bcore->config();
c->setGroup( "K3bOggVorbisEncoderPlugin" );
d->manualBitrate = c->readBoolEntry( "manual bitrate", false );
d->qualityLevel = c->readNumEntry( "quality level", 4 );
d->bitrateUpper = c->readNumEntry( "bitrate upper", -1 );
d->bitrateNominal = c->readNumEntry( "bitrate nominal", -1 );
d->bitrateLower = c->readNumEntry( "bitrate lower", -1 );
// d->sampleRate = c->readNumEntry( "samplerate", 44100 );
}
K3bOggVorbisEncoderSettingsWidget::K3bOggVorbisEncoderSettingsWidget( TQWidget* parent, const char* name )
: K3bPluginConfigWidget( parent, name )
{
w = new base_K3bOggVorbisEncoderSettingsWidget( this );
TQString ttQuality = i18n("Controls the quality of the encoded files.");
TQString wsQuality = i18n("<p>Vorbis' audio quality is not best measured in kilobits per second, "
"but on a scale from -1 to 10 called <em>quality</em>."
"<p>For now, quality -1 is roughly equivalent to 45kbps average, "
"5 is roughly 160kbps, and 10 gives about 400kbps. "
"Most people seeking very-near-CD-quality audio encode at a quality of 5 or, "
"for lossless stereo coupling, 6. The quality 3 gives, at "
"approximately 110kbps a smaller filesize and significantly better fidelity "
"than .mp3 compression at 128kbps."
"<p><em>This explanation is based on the one from the www.vorbis.com FAQ.</em>");
TQToolTip::add( w->m_radioQualityLevel, ttQuality );
TQToolTip::add( w->m_labelQualityLevel, ttQuality );
TQToolTip::add( w->m_slideQualityLevel, ttQuality );
TQWhatsThis::add( w->m_radioQualityLevel, wsQuality );
TQWhatsThis::add( w->m_labelQualityLevel, wsQuality );
TQWhatsThis::add( w->m_slideQualityLevel, wsQuality );
TQHBoxLayout* lay = new TQHBoxLayout( this );
lay->setMargin( 0 );
lay->addWidget( w );
connect( w->m_slideQualityLevel, TQT_SIGNAL(valueChanged(int)),
this, TQT_SLOT(slotQualityLevelChanged(int)) );
slotQualityLevelChanged( 4 );
}
K3bOggVorbisEncoderSettingsWidget::~K3bOggVorbisEncoderSettingsWidget()
{
}
void K3bOggVorbisEncoderSettingsWidget::slotQualityLevelChanged( int val )
{
w->m_labelQualityLevel->setText( TQString::number(val) + " "
+ i18n("(targetted VBR of %1)").arg(s_rough_average_quality_level_bitrates[val+1]) );
}
void K3bOggVorbisEncoderSettingsWidget::loadConfig()
{
TDEConfig* c = k3bcore->config();
c->setGroup( "K3bOggVorbisEncoderPlugin" );
if( c->readBoolEntry( "manual bitrate", false ) )
w->m_radioManual->setChecked(true);
else
w->m_radioQualityLevel->setChecked(true);
w->m_slideQualityLevel->setValue( c->readNumEntry( "quality level", 4 ) );
w->m_inputBitrateUpper->setValue( c->readNumEntry( "bitrate upper", -1 ) );
w->m_checkBitrateUpper->setChecked( c->readNumEntry( "bitrate upper", -1 ) != -1 );
w->m_inputBitrateNominal->setValue( c->readNumEntry( "bitrate nominal", -1 ) );
w->m_checkBitrateNominal->setChecked( c->readNumEntry( "bitrate nominal", -1 ) != -1 );
w->m_inputBitrateLower->setValue( c->readNumEntry( "bitrate lower", -1 ) );
w->m_checkBitrateLower->setChecked( c->readNumEntry( "bitrate lower", -1 ) != -1 );
// w->m_inputSamplerate->setValue( c->readNumEntry( "samplerate", 44100 ) );
}
void K3bOggVorbisEncoderSettingsWidget::saveConfig()
{
TDEConfig* c = k3bcore->config();
c->setGroup( "K3bOggVorbisEncoderPlugin" );
c->writeEntry( "manual bitrate", w->m_radioManual->isChecked() );
c->writeEntry( "quality level", w->m_slideQualityLevel->value() );
c->writeEntry( "bitrate upper", w->m_checkBitrateUpper->isChecked() ? w->m_inputBitrateUpper->value() : -1 );
c->writeEntry( "bitrate nominal", w->m_checkBitrateNominal->isChecked() ? w->m_inputBitrateNominal->value() : -1 );
c->writeEntry( "bitrate lower", w->m_checkBitrateLower->isChecked() ? w->m_inputBitrateLower->value() : -1 );
// c->writeEntry( "samplerate", w->m_inputSamplerate->value() );
}
TQString K3bOggVorbisEncoder::fileTypeComment( const TQString& ) const
{
return i18n("Ogg-Vorbis");
}
long long K3bOggVorbisEncoder::fileSize( const TQString&, const K3b::Msf& msf ) const
{
TDEConfig* c = k3bcore->config();
c->setGroup( "K3bOggVorbisEncoderPlugin" );
// the following code is based on the size estimation from the audiocd kioslave
// TODO: reimplement.
if( !c->readBoolEntry( "manual bitrate", false ) ) {
// Estimated numbers based on the Vorbis FAQ:
// http://www.xiph.org/archives/vorbis-faq/200203/0030.html
// static long vorbis_q_bitrate[] = { 45, 60, 74, 86, 106, 120, 152,
// 183, 207, 239, 309, 440 };
int qualityLevel = c->readNumEntry( "quality level", 4 );
if( qualityLevel < -1 )
qualityLevel = -1;
else if( qualityLevel > 10 )
qualityLevel = 10;
return ( (msf.totalFrames()/75) * s_rough_average_quality_level_bitrates[qualityLevel+1] * 1000 ) / 8;
}
else {
return (msf.totalFrames()/75) * c->readNumEntry( "bitrate nominal", 160 ) * 1000 / 8;
}
}
K3bPluginConfigWidget* K3bOggVorbisEncoder::createConfigWidget( TQWidget* parent,
const char* name ) const
{
return new K3bOggVorbisEncoderSettingsWidget( parent, name );
}
#include "k3boggvorbisencoder.moc"