kwave  18.07.70
WavEncoder.cpp
Go to the documentation of this file.
1 /*************************************************************************
2  WavEncoder.cpp - encoder for wav data
3  -------------------
4  begin : Sun Mar 10 2002
5  copyright : (C) 2002 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 #include <stdlib.h>
22 
23 #include <KLocalizedString>
24 
25 #include <QByteArray>
26 #include <QtEndian>
27 #include <QtGlobal>
28 
29 #include "libkwave/Compression.h"
30 #include "libkwave/FileInfo.h"
31 #include "libkwave/LabelList.h"
32 #include "libkwave/MessageBox.h"
34 #include "libkwave/Sample.h"
35 #include "libkwave/SampleFormat.h"
36 #include "libkwave/SampleReader.h"
37 #include "libkwave/Utils.h"
39 
40 #include "WavEncoder.h"
41 #include "WavFileFormat.h"
42 
43 /***************************************************************************/
45  :Kwave::Encoder(), m_property_map()
46 {
49 }
50 
51 /***************************************************************************/
53 {
54 }
55 
56 /***************************************************************************/
58 {
59  return new Kwave::WavEncoder();
60 }
61 
62 /***************************************************************************/
63 QList<Kwave::FileProperty> Kwave::WavEncoder::supportedProperties()
64 {
65  return m_property_map.properties();
66 }
67 
68 /***************************************************************************/
70  Kwave::FileInfo &info,
71  unsigned int frame_size)
72 {
73  const unsigned int length = Kwave::toUint(info.length());
74  quint32 correct_size = length * frame_size;
75  const int compression = info.contains(Kwave::INF_COMPRESSION) ?
76  info.get(Kwave::INF_COMPRESSION).toInt() :
78  if (compression != Kwave::Compression::NONE) {
79  qWarning("WARNING: libaudiofile might have produced a broken header!");
80  return;
81  }
82 
83  // just to be sure: at offset 36 we expect the chunk name "data"
84  dst.seek(36);
85  char chunk_name[5];
86  memset(chunk_name, 0x00, sizeof(chunk_name));
87  dst.read(&chunk_name[0], 4);
88  if (strncmp("data", chunk_name, sizeof(chunk_name))) {
89  qWarning("WARNING: unexpected wav header format, check disabled");
90  return;
91  }
92 
93  // read the data chunk size that libaudiofile has written
94  quint32 data_size;
95  dst.seek(40);
96  dst.read(reinterpret_cast<char *>(&data_size), 4);
97  data_size = qFromLittleEndian<quint32>(data_size);
98  if (data_size == length * frame_size) {
99 // qDebug("(data size written by libaudiofile is correct)");
100  return;
101  }
102 
103  qWarning("WARNING: libaudiofile wrote a wrong 'data' chunk size!");
104  qWarning(" current=%u, correct=%u", data_size, correct_size);
105 
106  // write the fixed size of the "data" chunk
107  dst.seek(40);
108  data_size = qToLittleEndian<quint32>(correct_size);
109  dst.write(reinterpret_cast<char *>(&data_size), 4);
110 
111  // also fix the "RIFF" size
112  dst.seek(4);
113  quint32 riff_size = static_cast<quint32>(dst.size()) - 4 - 4;
114  riff_size = qToLittleEndian<quint32>(riff_size);
115  dst.write(reinterpret_cast<char *>(&riff_size), 4);
116 
117 }
118 
119 /***************************************************************************/
121 {
122  // create a list of chunk names and properties for the INFO chunk
123  QMap<Kwave::FileProperty, QVariant> properties(info.properties());
124  QMap<QByteArray, QByteArray> info_chunks;
125  unsigned int info_size = 0;
126 
127  for (QMap<Kwave::FileProperty, QVariant>::Iterator it = properties.begin();
128  it != properties.end(); ++it)
129  {
130  Kwave::FileProperty property = it.key();
131  if (!m_property_map.containsProperty(property)) continue;
132 
133  QByteArray chunk_id = m_property_map.findProperty(property);
134  if (info_chunks.contains(chunk_id)) continue; // already encoded
135 
136  QByteArray value = QVariant(properties[property]).toString().toUtf8();
137  info_chunks.insert(chunk_id, value);
138  info_size += 4 + 4 + value.length();
139  if (value.length() & 0x01) info_size++;
140  }
141 
142  // if there are properties to save, create a LIST chunk
143  if (!info_chunks.isEmpty()) {
144  quint32 size;
145 
146  // enlarge the main RIFF chunk by the size of the LIST chunk
147  info_size += 4 + 4 + 4; // add the size of LIST(INFO)
148  dst.seek(4);
149  dst.read(reinterpret_cast<char *>(&size), 4);
150  size = qToLittleEndian<quint32>(
151  qFromLittleEndian<quint32>(size) + info_size);
152  dst.seek(4);
153  dst.write(reinterpret_cast<char *>(&size), 4);
154 
155  // add the LIST(INFO) chunk itself
156  dst.seek(dst.size());
157  if (dst.pos() & 1) dst.write("\000", 1); // padding
158  dst.write("LIST", 4);
159  size = qToLittleEndian<quint32>(info_size - 8);
160  dst.write(reinterpret_cast<char *>(&size), 4);
161  dst.write("INFO", 4);
162 
163  // append the chunks to the end of the file
164  for (QMap<QByteArray, QByteArray>::Iterator it = info_chunks.begin();
165  it != info_chunks.end(); ++it)
166  {
167  QByteArray name = it.key();
168  QByteArray value = it.value();
169 
170  dst.write(name.data(), 4); // chunk name
171  size = value.length(); // length of the chunk
172  if (size & 0x01) size++;
173  size = qToLittleEndian<quint32>(size);
174  dst.write(reinterpret_cast<char *>(&size), 4);
175  dst.write(value.data(), value.length());
176  if (value.length() & 0x01) {
177  const char zero = 0;
178  dst.write(&zero, 1);
179  }
180  }
181  }
182 }
183 
184 /***************************************************************************/
185 void Kwave::WavEncoder::writeLabels(QIODevice &dst,
186  const Kwave::LabelList &labels)
187 {
188  const unsigned int labels_count = labels.count();
189  quint32 size, additional_size = 0, index, data;
190 
191  // shortcut: nothing to do if no labels present
192  if (!labels_count) return;
193 
194  // easy things first: size of the cue list (has fixed record size)
195  // without chunk name and chunk size
196  const unsigned int size_of_cue_list =
197  4 + /* number of entries */
198  labels_count * (6 * 4); /* cue list entry: 6 x 32 bit */
199 
200  // now the size of the labels
201  unsigned int size_of_labels = 0;
202  foreach (const Kwave::Label &label, labels) {
203  if (label.isNull()) continue;
204  unsigned int name_len = label.name().toUtf8().size();
205  if (!name_len) continue; // skip zero-length names
206  size_of_labels += (3 * 4); // 3 * 4 byte
207  size_of_labels += name_len;
208  // padding if size is unaligned
209  if (size_of_labels & 1) size_of_labels++;
210  }
211  if (size_of_labels) {
212  size_of_labels += 4; /* header entry: 'adtl' */
213  // enlarge the main RIFF chunk by the size of the LIST chunk
214  additional_size += 4 + 4 + size_of_labels; // add size of LIST(adtl)
215  }
216 
217  // enlarge the main RIFF chunk by the size of the cue chunks
218  additional_size += 4 + 4 + size_of_cue_list; // add size of 'cue '
219 
220  dst.seek(4);
221  dst.read(reinterpret_cast<char *>(&size), 4);
222  size = qToLittleEndian<quint32>(
223  qFromLittleEndian<quint32>(size) + additional_size);
224  dst.seek(4);
225  dst.write(reinterpret_cast<char *>(&size), 4);
226 
227  // seek to the end of the file
228  dst.seek(dst.size());
229  if (dst.pos() & 1) dst.write("\000", 1); // padding
230 
231  // add the 'cue ' list
232  dst.write("cue ", 4);
233  size = qToLittleEndian<quint32>(size_of_cue_list);
234  dst.write(reinterpret_cast<char *>(&size), 4);
235 
236  // number of entries
237  size = qToLittleEndian<quint32>(labels_count);
238  dst.write(reinterpret_cast<char *>(&size), 4);
239 
240  index = 0;
241  foreach (const Kwave::Label &label, labels) {
242  if (label.isNull()) continue;
243  /*
244  * typedef struct {
245  * quint32 dwIdentifier; <- index
246  * quint32 dwPosition; <- 0
247  * quint32 fccChunk; <- 'data'
248  * quint32 dwChunkStart; <- 0
249  * quint32 dwBlockStart; <- 0
250  * quint32 dwSampleOffset; <- label.pos()
251  * } cue_list_entry_t;
252  */
253  data = qToLittleEndian<quint32>(index);
254  dst.write(reinterpret_cast<char *>(&data), 4); // dwIdentifier
255  data = 0;
256  dst.write(reinterpret_cast<char *>(&data), 4); // dwPosition
257  dst.write("data", 4); // fccChunk
258  dst.write(reinterpret_cast<char *>(&data), 4); // dwChunkStart
259  dst.write(reinterpret_cast<char *>(&data), 4); // dwBlockStart
260  data = qToLittleEndian<quint32>(Kwave::toUint(label.pos()));
261  dst.write(reinterpret_cast<char *>(&data), 4); // dwSampleOffset
262  index++;
263  }
264 
265  // add the LIST(adtl) chunk
266  if (size_of_labels) {
267  dst.write("LIST", 4);
268  size = qToLittleEndian<quint32>(size_of_labels);
269  dst.write(reinterpret_cast<char *>(&size), 4);
270  dst.write("adtl", 4);
271  index = 0;
272  foreach (const Kwave::Label &label, labels) {
273  if (label.isNull()) continue;
274  QByteArray name = label.name().toUtf8();
275 
276  /*
277  * typedef struct {
278  * quint32 dwChunkID; <- 'labl'
279  * quint32 dwChunkSize; (without padding !)
280  * quint32 dwIdentifier; <- index
281  * char dwText[]; <- label->name()
282  * } label_list_entry_t;
283  */
284  if (name.size()) {
285  dst.write("labl", 4); // dwChunkID
286  data = qToLittleEndian<quint32>(name.size() + 4);
287 
288  // dwChunkSize
289  dst.write(reinterpret_cast<char *>(&data), 4);
290  data = qToLittleEndian<quint32>(index);
291 
292  // dwIdentifier
293  dst.write(reinterpret_cast<char *>(&data), 4);
294  dst.write(name.data(), name.size()); // dwText
295  if (name.size() & 1) {
296  // padding if necessary
297  data = 0;
298  dst.write(reinterpret_cast<char *>(&data), 1);
299  }
300  }
301  index++;
302  }
303  }
304 }
305 
306 /***************************************************************************/
308  QIODevice &dst,
309  const Kwave::MetaDataList &meta_data)
310 {
311  Kwave::FileInfo info(meta_data);
312 
313  /* first get and check some header information */
314  const unsigned int tracks = info.tracks();
315  const sample_index_t length = info.length();
316  unsigned int bits = info.bits();
317  const double rate = info.rate();
318 
321  format.fromInt(info.get(Kwave::INF_SAMPLE_FORMAT).toInt());
322 
323  Kwave::Compression::Type compression =
327 
328  // use default bit resolution if missing
329  Q_ASSERT(bits);
330  if (!bits) bits = 16;
331 
332  // check for a valid source
333  if ((!tracks) || (!length)) return false;
334  Q_ASSERT(src.tracks() == tracks);
335  if (src.tracks() != tracks) return false;
336 
337  // check if the chosen compression mode is supported for saving
338  if ((compression != Kwave::Compression::NONE) &&
339  (compression != Kwave::Compression::G711_ULAW) &&
340  (compression != Kwave::Compression::G711_ALAW))
341  {
342  qWarning("compression mode %d not supported!",
343  Kwave::Compression(compression).toInt());
344  int what_now = Kwave::MessageBox::warningYesNoCancel(widget,
345  i18n("Sorry, the currently selected compression type cannot "
346  "be used for saving. Do you want to use "
347  "G711 ULAW compression instead?"), QString(),
348  i18n("&Yes, use G711"),
349  i18n("&No, store uncompressed")
350  );
351  switch (what_now) {
352  case (KMessageBox::Yes):
355  compression = Kwave::Compression::G711_ULAW;
356  break;
357  case (KMessageBox::No):
360  compression = Kwave::Compression::NONE;
361  break;
362  default:
363  return false; // bye bye, save later...
364  }
365  }
366 
367  // check for unsupported compression/bits/sample format combinations
368  // G.711 and MSADPCM support only 16 bit signed as input format!
369  if ((compression == Kwave::Compression::G711_ULAW) ||
370  (compression == Kwave::Compression::G711_ALAW))
371  {
372  if ((format != Kwave::SampleFormat::Signed) || (bits != 16)) {
374  bits = 16;
375  info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt()));
376  info.setBits(16);
377  qDebug("auto-switching to 16 bit signed format");
378  }
379  } else if ((bits <= 8) && (format != Kwave::SampleFormat::Unsigned)) {
381  info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt()));
382  qDebug("auto-switching to unsigned format");
383  } else if ((bits > 8) && (format != Kwave::SampleFormat::Signed)) {
385  info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt()));
386  qDebug("auto-switching to signed format");
387  }
388 
389  // open the output device
390  if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
392  i18n("Unable to open the file for saving!"));
393  return false;
394  }
395 
396  // check for proper size: WAV supports only 32bit addressing
397  if (length * ((bits + 7) / 8) >= UINT_MAX) {
398  Kwave::MessageBox::error(widget, i18n("File or selection too large"));
399  return false;
400  }
401 
402  int af_sample_format = AF_SAMPFMT_TWOSCOMP;
403  Kwave::SampleFormat fmt(format);
404  switch (fmt)
405  {
407  af_sample_format = AF_SAMPFMT_UNSIGNED;
408  break;
410  af_sample_format = AF_SAMPFMT_FLOAT;
411  break;
413  af_sample_format = AF_SAMPFMT_DOUBLE;
414  break;
415  case Kwave::SampleFormat::Signed: /* FALLTHROUGH */
416  default:
417  af_sample_format = AF_SAMPFMT_TWOSCOMP;
418  break;
419  }
420 
421  AFfilesetup setup;
422  setup = afNewFileSetup();
423  afInitFileFormat(setup, AF_FILE_WAVE);
424  afInitChannels(setup, AF_DEFAULT_TRACK, tracks);
425  afInitSampleFormat(setup, AF_DEFAULT_TRACK, af_sample_format, bits);
426  afInitCompression(setup, AF_DEFAULT_TRACK,
427  Kwave::Compression::toAudiofile(compression));
428  afInitRate(setup, AF_DEFAULT_TRACK, rate);
429 
430  Kwave::VirtualAudioFile outfile(dst);
431  outfile.open(&outfile, setup);
432 
433  AFfilehandle fh = outfile.handle();
434  if (!fh || (outfile.lastError() >= 0)) {
435  QString reason;
436 
437  switch (outfile.lastError()) {
438  case AF_BAD_NOT_IMPLEMENTED:
439  reason = i18n("Format or function is not implemented") /*+
440  "\n("+format_name+")"*/;
441  break;
442  case AF_BAD_MALLOC:
443  reason = i18n("Out of memory");
444  break;
445  case AF_BAD_HEADER:
446  reason = i18n("File header is damaged");
447  break;
448  case AF_BAD_CODEC_TYPE:
449  reason = i18n("Invalid codec type")/* +
450  "\n("+format_name+")"*/;
451  break;
452  case AF_BAD_OPEN:
453  reason = i18n("Opening the file failed");
454  break;
455  case AF_BAD_READ:
456  reason = i18n("Read access failed");
457  break;
458  case AF_BAD_SAMPFMT:
459  reason = i18n("Invalid sample format");
460  break;
461  default:
462  reason = reason.number(outfile.lastError());
463  }
464 
465  QString text= i18n("An error occurred while opening the "\
466  "file:\n'%1'", reason);
467  Kwave::MessageBox::error(widget, text);
468 
469  return false;
470  }
471 
472  // set up libaudiofile to produce Kwave's internal sample format
473 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
474  afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_BIGENDIAN);
475 #else
476  afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN);
477 #endif
478  afSetVirtualSampleFormat(fh, AF_DEFAULT_TRACK,
479  AF_SAMPFMT_TWOSCOMP, SAMPLE_STORAGE_BITS);
480 
481  // allocate a buffer for input data
482  const unsigned int virtual_frame_size = Kwave::toUint(
483  afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, 1));
484  const unsigned int buffer_frames = (8 * 1024);
485  sample_storage_t *buffer = static_cast<sample_storage_t *>(
486  malloc(buffer_frames * virtual_frame_size));
487  if (!buffer) return false;
488 
489  // read in from the sample readers
490  sample_index_t rest = length;
491  while (rest) {
492  // merge the tracks into the sample buffer
493  sample_storage_t *p = buffer;
494  unsigned int count = buffer_frames;
495  if (rest < count) count = Kwave::toUint(rest);
496 
497  for (unsigned int pos = 0; pos < count; pos++) {
498  for (unsigned int track = 0; track < tracks; track++) {
499  Kwave::SampleReader *stream = src[track];
500  sample_t sample = 0;
501  if (!stream->eof()) (*stream) >> sample;
502 
503  // the following cast is only necessary if
504  // sample_t is not equal to sample_storage_t
505  sample_storage_t act = static_cast<sample_storage_t>(sample);
506  act *= (1 << (SAMPLE_STORAGE_BITS - SAMPLE_BITS));
507  *p = act;
508  p++;
509  }
510  }
511 
512  // write out through libaudiofile
513  count = afWriteFrames(fh, AF_DEFAULT_TRACK, buffer, count);
514 
515  // break if eof reached or disk full
516  Q_ASSERT(count);
517  if (!count) break;
518 
519  Q_ASSERT(rest >= count);
520  rest -= count;
521 
522  // abort if the user pressed cancel
523  // --> this would leave a corrupted file !!!
524  if (src.isCanceled()) break;
525  }
526 
527  // close the audiofile stuff, we need control over the
528  // fixed-up file on our own
529  outfile.close();
530 
531  // clean up the sample buffer
532  free(buffer);
533  afFreeFileSetup(setup);
534 
535  // due to a buggy implementation of libaudiofile
536  // we have to fix up the length of the "data" and the "RIFF" chunk
537  fixAudiofileBrokenHeaderBug(dst, info, (bits * tracks) >> 3);
538 
539  // put the properties into the INFO chunk
540  writeInfoChunk(dst, info);
541 
542  // write the labels list
543  writeLabels(dst, Kwave::LabelList(meta_data));
544 
545  return true;
546 }
547 
548 /***************************************************************************/
549 /***************************************************************************/
AFfilehandle & handle()
bool contains(const FileProperty property) const
Definition: FileInfo.cpp:354
void writeLabels(QIODevice &dst, const Kwave::LabelList &labels)
Definition: WavEncoder.cpp:185
Definition: App.h:33
QByteArray findProperty(const Kwave::FileProperty property) const
double rate() const
Definition: FileInfo.cpp:415
virtual unsigned int tracks() const Q_DECL_OVERRIDE
Kwave::WavPropertyMap m_property_map
Definition: WavEncoder.h:101
QVariant get(FileProperty key) const
Definition: FileInfo.cpp:372
void fixAudiofileBrokenHeaderBug(QIODevice &dst, Kwave::FileInfo &info, unsigned int frame_size)
Definition: WavEncoder.cpp:69
virtual sample_index_t pos() const
Definition: Label.cpp:56
static Kwave::Compression::Type fromInt(int i)
Definition: Compression.cpp:78
quint64 sample_index_t
Definition: Sample.h:28
virtual void open(Kwave::VirtualAudioFile *x, AFfilesetup setup)
virtual bool encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) Q_DECL_OVERRIDE
Definition: WavEncoder.cpp:307
bool eof() const
Definition: SampleReader.h:66
bool containsProperty(const Kwave::FileProperty property) const
virtual ~WavEncoder() Q_DECL_OVERRIDE
Definition: WavEncoder.cpp:52
void set(FileProperty key, const QVariant &value)
Definition: FileInfo.cpp:363
const char name[16]
Definition: memcpy.c:510
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
sample_index_t length() const
Definition: FileInfo.cpp:400
const QMap< FileProperty, QVariant > properties() const
Definition: FileInfo.cpp:389
int toInt(T x)
Definition: Utils.h:127
void writeInfoChunk(QIODevice &dst, Kwave::FileInfo &info)
Definition: WavEncoder.cpp:120
virtual Encoder * instance() Q_DECL_OVERRIDE
Definition: WavEncoder.cpp:57
QList< Kwave::FileProperty > properties() const
virtual bool isNull() const
Definition: MetaData.cpp:69
virtual QString name() const
Definition: Label.cpp:74
qint32 sample_storage_t
Definition: Sample.h:40
static int warningYesNoCancel(QWidget *widget, QString message, QString caption=QString(), const QString buttonYes=QString(), const QString buttonNo=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:104
static int toAudiofile(Kwave::Compression::Type compression)
#define REGISTER_MIME_TYPES
virtual QList< Kwave::FileProperty > supportedProperties() Q_DECL_OVERRIDE
Definition: WavEncoder.cpp:63
unsigned int tracks() const
Definition: FileInfo.cpp:445
void setBits(unsigned int bits)
Definition: FileInfo.cpp:439
#define REGISTER_COMPRESSION_TYPES
#define SAMPLE_STORAGE_BITS
Definition: Sample.h:46
unsigned int toUint(T x)
Definition: Utils.h:109
unsigned int bits() const
Definition: FileInfo.cpp:430
void assign(Format f)
Definition: SampleFormat.h:62
int toInt() const
Definition: SampleFormat.h:70
FileProperty
Definition: FileInfo.h:45
static double zero(double)
Definition: Functions.cpp:83
#define SAMPLE_BITS
Definition: Sample.h:43
qint32 sample_t
Definition: Sample.h:37