kwave  18.07.70
MP3Encoder.cpp
Go to the documentation of this file.
1 /*************************************************************************
2  MP3Encoder.cpp - export of MP3 data via "lame"
3  -------------------
4  begin : Sat May 19 2012
5  copyright : (C) 2012 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <math.h>
21 
22 #include <id3/globals.h>
23 #include <id3/misc_support.h>
24 #include <id3/tag.h>
25 
26 #include <QByteArray>
27 #include <QDate>
28 #include <QDateTime>
29 #include <QLatin1Char>
30 #include <QList>
31 #include <QMap>
32 
33 #include <KLocalizedString>
34 
35 #include "libkwave/FileInfo.h"
36 #include "libkwave/GenreType.h"
37 #include "libkwave/MessageBox.h"
38 #include "libkwave/MetaDataList.h"
39 #include "libkwave/MixerMatrix.h"
41 #include "libkwave/Sample.h"
42 #include "libkwave/SampleReader.h"
43 #include "libkwave/String.h"
44 #include "libkwave/Utils.h"
45 
46 #include "ID3_QIODeviceWriter.h"
47 #include "MP3CodecPlugin.h"
48 #include "MP3Encoder.h"
49 #include "MP3EncoderSettings.h"
50 
51 /***************************************************************************/
53  :Kwave::Encoder(),
54  m_property_map(),
55  m_lock(),
56  m_dst(Q_NULLPTR),
57  m_process(this),
58  m_program(),
59  m_params()
60 {
63 
64  connect(&m_process, SIGNAL(readyReadStandardOutput()),
65  this, SLOT(dataAvailable()));
66 }
67 
68 /***************************************************************************/
70 {
71 }
72 
73 /***************************************************************************/
75 {
76  return new MP3Encoder();
77 }
78 
79 /***************************************************************************/
80 QList<Kwave::FileProperty> Kwave::MP3Encoder::supportedProperties()
81 {
82  return m_property_map.properties();
83 }
84 
85 /***************************************************************************/
87  ID3_Tag &tag)
88 {
89  const Kwave::FileInfo info(meta_data);
90  ID3_FrameInfo frameInfo;
91 
92  const QMap<Kwave::FileProperty, QVariant> properties(info.properties());
93  QMap<Kwave::FileProperty, QVariant>::const_iterator it;
94  for (it = properties.begin(); it != properties.end(); ++it) {
95  const Kwave::FileProperty &property = it.key();
96  const QVariant &value = it.value();
97 
98  ID3_FrameID id = m_property_map.findProperty(property);
99  if (id == ID3FID_NOFRAME) continue;
100 
101  if (info.contains(Kwave::INF_CD) && (property == Kwave::INF_CDS))
102  continue; /* INF_CDS has already been handled by INF_CD */
103  if (info.contains(Kwave::INF_TRACK) && (property == Kwave::INF_TRACKS))
104  continue; /* INF_TRACKS has already been handled by INF_TRACK */
105 
106  ID3_Frame *frame = new ID3_Frame;
107  Q_ASSERT(frame);
108  if (!frame) break;
109 
110  QString str_val = value.toString();
111 // qDebug("encoding ID3 tag #%02d, property='%s', value='%s'",
112 // static_cast<int>(id),
113 // DBG(info.name(property)),
114 // DBG(str_val)
115 // );
116 
117  // encode in UCS16
118  frame->SetID(id);
119  ID3_Field *field = frame->GetField(ID3FN_TEXT);
120  if (!field) {
121  qWarning("no field, frame id=%d", static_cast<int>(id));
122  delete frame;
123  continue;
124  }
125 
127  switch (encoding) {
129  {
130  field->SetEncoding(ID3TE_UTF16);
131 
132  // if "number of CDs is available: append with "/"
133  int cds = info.get(Kwave::INF_CDS).toInt();
134  if (cds > 0)
135  str_val += _("/%1").arg(cds);
136 
137  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
138  break;
139  }
141  {
142  // if "number of tracks is available: append with "/"
143  int tracks = info.get(Kwave::INF_TRACKS).toInt();
144  if (tracks > 0)
145  str_val += _("/%1").arg(tracks);
146 
147  field->SetEncoding(ID3TE_UTF16);
148  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
149  break;
150  }
152  // the same as ENC_COMMENT, but without "Description"
153  /* FALLTHROUGH */
155  {
156  // detect language at the start "[xxx] "
157  QString lang;
158  if (str_val.startsWith(QLatin1Char('[')) &&
159  (str_val.at(4) == QLatin1Char(']'))) {
160  lang = str_val.mid(1,3);
161  str_val = str_val.mid(5);
162  frame->GetField(ID3FN_DESCRIPTION)->Set("");
163  frame->GetField(ID3FN_LANGUAGE)->Set(
164  static_cast<const char *>(lang.toLatin1().data()));
165  }
166  /* frame->GetField(ID3FN_DESCRIPTION)->Set(""); */
167  field->SetEncoding(ID3TE_UTF16);
168  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
169  break;
170  }
172  {
173  int genre = Kwave::GenreType::fromID3(str_val);
174  if (genre >= 0)
175  str_val = Kwave::GenreType::name(genre, false);
176  // else: user defined genre type, take it as it is
177 
178  field->SetEncoding(ID3TE_UTF16);
179  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
180  break;
181  }
183  {
184  // length in milliseconds
185  const double rate = info.rate();
186  const sample_index_t samples = info.length();
187  if ((rate > 0) && samples) {
188  const sample_index_t ms = static_cast<sample_index_t>(
189  (static_cast<double>(samples) * 1E3) / rate);
190 
191  str_val = QString::number(ms);
192 
193  field->SetEncoding(ID3TE_UTF16);
194  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
195  } else {
196  delete frame;
197  frame = Q_NULLPTR;
198  }
199  break;
200  }
202  {
203  // ISO 8601 timestamp: "yyyy-MM-ddTHH:mm:ss"
204  QString s = Kwave::string2date(str_val);
205 
206  // if failed, try "yyyy" format (year only)
207  if (!s.length()) {
208  int year = str_val.toInt();
209  if ((year > 0) && (year < 9999)) {
210  frame->SetID(ID3FID_YEAR);
211  // -> re-get the field !
212  // it has become invalid through "SetID()"
213  field = frame->GetField(ID3FN_TEXT);
214  if (!field) {
215  qWarning("no field, frame id=%d",
216  static_cast<int>(id));
217  break;
218  }
219  s = _("%1").arg(year, 4, 10, QLatin1Char('0'));
220  }
221  }
222 
223  if (s.length()) {
224  field->SetEncoding(ID3TE_UTF16);
225  field->Set(static_cast<const unicode_t *>(s.utf16()));
226  } else {
227  // date is invalid, unknown format
228  qWarning("MP3Encoder::encodeID3Tags(): invalid date: '%s'",
229  DBG(str_val));
230  delete frame;
231  frame = Q_NULLPTR;
232  }
233  break;
234  }
235  case ID3_PropertyMap::ENC_TEXT_SLASH: /* FALLTHROUGH */
236  case ID3_PropertyMap::ENC_TEXT_URL: /* FALLTHROUGH */
238  field->SetEncoding(ID3TE_UTF16);
239  field->Set(static_cast<const unicode_t *>(str_val.utf16()));
240  break;
241  case ID3_PropertyMap::ENC_NONE: /* FALLTHROUGH */
242  default:
243  // ignore
244  delete frame;
245  frame = Q_NULLPTR;
246  break;
247  }
248 
249  if (frame) tag.AttachFrame(frame);
250  }
251 
252  tag.Strip();
253  tag.Update();
254 }
255 
256 #define OPTION(__field__) \
257  if (settings.__field__.length()) m_params.append(settings.__field__)
258 
259 #define OPTION_P(__field__, __value__) \
260  if (settings.__field__.length()) \
261  m_params.append( \
262  QString(settings.__field__.arg(__value__)).split(QLatin1Char(' ')))
263 
264 /***************************************************************************/
266  QIODevice &dst,
267  const Kwave::MetaDataList &meta_data)
268 {
269  bool result = true;
270  ID3_Tag id3_tag;
271  Kwave::MP3EncoderSettings settings;
272 
273  settings.load();
274 
275  ID3_TagType id3_tag_type = ID3TT_ID3V2;
276  id3_tag.SetSpec(ID3V2_LATEST);
277 
278  const Kwave::FileInfo info(meta_data);
279 
280  // get info: tracks, sample rate
281  const unsigned int tracks = src.tracks();
282  const sample_index_t length = src.last() - src.first() + 1;
283  unsigned int bits = qBound(8U, ((info.bits() + 7) & ~0x7), 32U);
284  const double rate = info.rate();
285  const unsigned int out_tracks = qMin(tracks, 2U);
286 
287  // when encoding track count > 2, show a warning that we will mix down
288  // to stereo
289  if (tracks > 2) {
291  widget,
292  i18n("The file format you have chosen supports only mono or "
293  "stereo. This file will be mixed down to stereo when "
294  "saving."),
295  QString(), QString(), QString(),
296  _("mp3_accept_down_mix_on_export")) != KMessageBox::Continue)
297  {
298  return false;
299  }
300  }
301 
302  // open the output device
303  if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
305  i18n("Unable to open the file for saving!"));
306  return false;
307  }
308 
309  m_dst = &dst;
310  m_params.clear();
311 
312  // encode meta data into with id3lib
313  ID3_QIODeviceWriter id3_writer(dst);
314  encodeID3Tags(meta_data, id3_tag);
315 
316  OPTION(m_flags.m_prepend); // optional parameters at the very start
317 
318  // mandantory audio input format and encoding options
319  OPTION(m_input.m_raw_format); // input is raw audio
320  OPTION(m_input.m_byte_order); // byte swapping
321  OPTION(m_input.m_signed); // signed sample format
322 
323  // supported sample rates [kHz]
324  // 8 / 11.025 / 12 / 16 / 22.05 / 24 /32 / 44.1 / 48
325  // if our rate is not supported, lame automatically resamples with the
326  // next higher supported rate
327  if (settings.m_format.m_sample_rate.length()) {
328  QString str = settings.m_format.m_sample_rate;
329  if (str.contains(_("[%khz]"))) {
330  str = str.replace(_("[%khz]"),
331  _("%1")).arg(rate / 1000.0, 1, 'f', 2);
332  m_params.append(str.split(QLatin1Char(' ')));
333  } else {
334  m_params.append(str.arg(rate).split(QLatin1Char(' ')));
335  }
336  }
337 
338  // bits per sample, supported by Kwave are: 8 / 16 / 24 / 32
339  if (!settings.m_format.m_bits_per_sample.contains(QLatin1Char('%'))) {
340  // bits/sample are not selectable => use default=16bit
341  bits = 16;
342  OPTION(m_format.m_bits_per_sample);
343  } else {
344  OPTION_P(m_format.m_bits_per_sample, bits);
345  }
346 
347  // encode one track as "mono" and two tracks as "joint-stereo"
348  if (tracks == 1) {
349  OPTION(m_format.m_channels.m_mono);
350  } else {
351  OPTION(m_format.m_channels.m_stereo);
352  }
353 
354  // nominal / lower / upper bitrate
355  int bitrate_min = 8;
356  int bitrate_max = 320;
357  int bitrate_nom = 128;
359  // nominal bitrate => use ABR mode
360  bitrate_nom = info.get(Kwave::INF_BITRATE_NOMINAL).toInt() / 1000;
361  bitrate_nom = qBound(bitrate_min, bitrate_nom, bitrate_max);
362  OPTION_P(m_quality.m_bitrate.m_avg, bitrate_nom);
363  }
365  int bitrate = info.get(Kwave::INF_BITRATE_LOWER).toInt() / 1000;
366  bitrate_min = qBound(bitrate_min, bitrate, bitrate_nom);
367  OPTION_P(m_quality.m_bitrate.m_min, bitrate_min);
368  }
370  int bitrate = info.get(Kwave::INF_BITRATE_UPPER).toInt() / 1000;
371  bitrate_max = qBound(bitrate_nom, bitrate, bitrate_max);
372  OPTION_P(m_quality.m_bitrate.m_max, bitrate_max);
373  }
374  // Kwave::INF_MPEG_LAYER, /**< MPEG Layer, I/II/III */
375  // Kwave::INF_MPEG_MODEEXT, /**< MPEG mode extension */
376  // Kwave::INF_MPEG_VERSION, /**< MPEG version */
377 
378  /* MPEG emphasis mode */
380  int emphasis = info.get(Kwave::INF_MPEG_EMPHASIS).toInt();
381  switch (emphasis) {
382  case 1:
383  OPTION(m_encoding.m_emphasis.m_50_15ms); // 1 = 50/15ms
384  break;
385  case 3:
386  OPTION(m_encoding.m_emphasis.m_ccit_j17); // 3 = CCIT J.17
387  break;
388  case 0: /* FALLTHROUGH */
389  default:
390  OPTION(m_encoding.m_emphasis.m_none); // 0 = none
391  break;
392  }
393  }
394 
395  OPTION(m_encoding.m_noise_shaping); // noise shaping settings
396  OPTION(m_encoding.m_compatibility); // compatibility options
397 
398  if (info.contains(Kwave::INF_COPYRIGHTED) && info.get(Kwave::INF_COPYRIGHTED).toBool()) {
399  OPTION(m_flags.m_copyright); // copyrighted
400  }
401 
402  if (info.contains(Kwave::INF_ORIGINAL) && !info.get(Kwave::INF_ORIGINAL).toBool()) {
403  OPTION(m_flags.m_original); // original
404  }
405 
406  OPTION(m_flags.m_protect); // CRC protection
407  OPTION(m_flags.m_append); // optional parameters at the end
408 
409  m_params.append(_("-")); // infile = stdin
410  m_params.append(_("-")); // outfile = stdout
411 
412  m_program = settings.m_path;
413 
414  qDebug("MP3Encoder::encode(): %s %s",
415  DBG(m_program), DBG(m_params.join(_(" ")))
416  );
417 
418  m_process.setReadChannel(QProcess::StandardOutput);
419 
420  m_process.start(m_program, m_params);
421  QString stdError;
422  if (!m_process.waitForStarted()) {
423  qWarning("cannot start program '%s'", DBG(m_program));
424  m_process.waitForFinished();
425  result = false;
426  }
427 
428  // if a ID3v2 tag is requested, the tag comes at the start
429  if (id3_tag_type == ID3TT_ID3V2)
430  id3_tag.Render(id3_writer, id3_tag_type);
431 
432  // MP3 supports only mono and stereo, prepare a mixer matrix
433  // (not used in case of tracks <= 2)
434  Kwave::MixerMatrix mixer(tracks, out_tracks);
435 
436  // read in from the sample readers
437  const unsigned int buf_len = sizeof(m_write_buffer);
438  const int bytes_per_sample = bits / 8;
439 
440  sample_index_t rest = length;
441  Kwave::SampleArray in_samples(tracks);
442  Kwave::SampleArray out_samples(tracks);
443 
444  while (result && rest && (m_process.state() != QProcess::NotRunning)) {
445  unsigned int x;
446  unsigned int y;
447 
448  // merge the tracks into the sample buffer
449  quint8 *dst_buffer = &(m_write_buffer[0]);
450  unsigned int count = buf_len / (bytes_per_sample * tracks);
451  if (rest < count) count = Kwave::toUint(rest);
452 
453  unsigned int written = 0;
454  for (written = 0; written < count; written++) {
455  const sample_t *src_buf = Q_NULLPTR;
456 
457  // fill input buffer with samples
458  for (x = 0; x < tracks; ++x) {
459  in_samples[x] = 0;
460  Kwave::SampleReader *stream = src[x];
461  Q_ASSERT(stream);
462  if (!stream) continue;
463 
464  if (!stream->eof()) (*stream) >> in_samples[x];
465  }
466 
467  if (tracks > 2) {
468  // multiply matrix with input to get output
469  const Kwave::SampleArray &in = in_samples;
470  for (y = 0; y < out_tracks; ++y) {
471  double sum = 0;
472  for (x = 0; x < tracks; ++x)
473  sum += static_cast<double>(in[x]) * mixer[x][y];
474  out_samples[y] = static_cast<sample_t>(sum);
475  }
476 
477  // use output of the matrix
478  src_buf = out_samples.constData();
479  } else {
480  // use input buffer directly
481  src_buf = in_samples.constData();
482  }
483 
484  // sample conversion from 24bit to raw PCM, native endian
485  for (y = 0; y < out_tracks; ++y) {
486  sample_t s = *(src_buf++);
487 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
488  // big endian
489  if (bits >= 8)
490  *(dst_buffer++) = static_cast<quint8>(s >> 16);
491  if (bits > 8)
492  *(dst_buffer++) = static_cast<quint8>(s >> 8);
493  if (bits > 16)
494  *(dst_buffer++) = static_cast<quint8>(s & 0xFF);
495  if (bits > 24)
496  *(dst_buffer++) = 0x00;
497 #else
498  // little endian
499  if (bits > 24)
500  *(dst_buffer++) = 0x00;
501  if (bits > 16)
502  *(dst_buffer++) = static_cast<quint8>(s & 0xFF);
503  if (bits > 8)
504  *(dst_buffer++) = static_cast<quint8>(s >> 8);
505  if (bits >= 8)
506  *(dst_buffer++) = static_cast<quint8>(s >> 16);
507 #endif
508  }
509  }
510 
511  // write out to the stdin of the external process
512  qint64 bytes_written = m_process.write(
513  reinterpret_cast<char *>(&(m_write_buffer[0])),
514  written * (bytes_per_sample * tracks)
515  );
516 
517  // break if eof reached or disk full
518  if (!bytes_written) break;
519 
520  // wait for write to take all data...
521  m_process.waitForBytesWritten();
522 
523  // abort if the user pressed cancel
524  // --> this would leave a corrupted file !!!
525  if (src.isCanceled()) break;
526 
527  Q_ASSERT(rest >= written);
528  rest -= written;
529  }
530 
531  // flush and close the write channel
532  m_process.closeWriteChannel();
533 
534  // wait until the process has finished
535  qDebug("wait for finish of the process");
536  while (m_process.state() != QProcess::NotRunning) {
537  m_process.waitForFinished(100);
538  if (src.isCanceled()) break;
539  }
540 
541  int exit_code = m_process.exitCode();
542  qDebug("exit code=%d", exit_code);
543  if (!result || (exit_code != 0)) {
544  result = false;
545  stdError = QString::fromLocal8Bit(m_process.readAllStandardError());
546  qWarning("stderr output: %s", DBG(stdError));
547 
549  i18nc("%1=name of the external program, %2=stderr of the program",
550  "An error occurred while calling the external encoder '%1':\n\n%2",
551  m_program, stdError
552  ));
553  }
554 
555  // if a ID3v1 tag is requested, the tag comes at the end
556  if (id3_tag_type != ID3TT_ID3V2)
557  id3_tag.Render(id3_writer, id3_tag_type);
558 
559  {
560  QMutexLocker _lock(&m_lock);
561  m_dst = Q_NULLPTR;
562  dst.close();
563  }
564 
565  return result;
566 }
567 
568 /***************************************************************************/
570 {
571  while (m_process.bytesAvailable()) {
572  qint64 len = m_process.read(&(m_read_buffer[0]), sizeof(m_read_buffer));
573  if (len) {
574  QMutexLocker _lock(&m_lock);
575  if (m_dst) m_dst->write(&(m_read_buffer[0]), len);
576  }
577  }
578 }
579 
580 /***************************************************************************/
581 /***************************************************************************/
bool contains(const FileProperty property) const
Definition: FileInfo.cpp:354
QProcess m_process
Definition: MP3Encoder.h:98
virtual QList< Kwave::FileProperty > supportedProperties()
Definition: MP3Encoder.cpp:80
virtual sample_index_t first() const
Definition: App.h:33
static QString name(int id, bool localized)
Definition: GenreType.cpp:34
virtual Kwave::Encoder * instance()
Definition: MP3Encoder.cpp:74
struct Kwave::MP3EncoderSettings::@4 m_format
double rate() const
Definition: FileInfo.cpp:415
ID3_PropertyMap m_property_map
Definition: MP3Encoder.h:89
virtual unsigned int tracks() const Q_DECL_OVERRIDE
QVariant get(FileProperty key) const
Definition: FileInfo.cpp:372
quint64 sample_index_t
Definition: Sample.h:28
QList< Kwave::FileProperty > properties() const
void encodeID3Tags(const Kwave::MetaDataList &meta_data, ID3_Tag &tag)
Definition: MP3Encoder.cpp:86
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
bool eof() const
Definition: SampleReader.h:66
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
QString Q_DECL_EXPORT string2date(const QString &s)
Definition: Utils.cpp:126
ID3_FrameID findProperty(const Kwave::FileProperty property) const
sample_index_t length() const
Definition: FileInfo.cpp:400
virtual sample_index_t last() const
const QMap< FileProperty, QVariant > properties() const
Definition: FileInfo.cpp:389
#define REGISTER_MIME_TYPES
static int warningContinueCancel(QWidget *widget, QString message, QString caption=QString(), const QString buttonContinue=QString(), const QString buttonCancel=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:115
const sample_t * constData() const
Definition: SampleArray.h:54
#define OPTION_P(__field__, __value__)
Definition: MP3Encoder.cpp:259
QIODevice * m_dst
Definition: MP3Encoder.h:95
char m_read_buffer[PIPE_BUF]
Definition: MP3Encoder.h:119
#define REGISTER_COMPRESSION_TYPES
Encoding encoding(const ID3_FrameID id) const
#define _(m)
Definition: memcpy.c:66
quint8 m_write_buffer[PIPE_BUF]
Definition: MP3Encoder.h:116
#define DBG(qs)
Definition: String.h:55
virtual ~MP3Encoder()
Definition: MP3Encoder.cpp:69
unsigned int toUint(T x)
Definition: Utils.h:109
virtual bool encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data)
Definition: MP3Encoder.cpp:265
unsigned int bits() const
Definition: FileInfo.cpp:430
static int fromID3(const QString &tag)
Definition: GenreType.cpp:45
FileProperty
Definition: FileInfo.h:45
#define OPTION(__field__)
Definition: MP3Encoder.cpp:256
QStringList m_params
Definition: MP3Encoder.h:104
qint32 sample_t
Definition: Sample.h:37