Main Page · Modules · All Classes · Class Hierarchy
MALossyConverter.cpp
1 /*
2  * This file is part of the AiBO+ project
3  *
4  * Copyright (C) 2005-2016 Csaba Kertész (csaba.kertesz@gmail.com)
5  *
6  * AiBO+ is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * AiBO+ is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
19  *
20  */
21 
22 #include "MALossyConverter.hpp"
23 
24 #include "core/MANum.hpp"
25 
26 #include <MCBinaryData.hpp>
27 #include <MCLog.hpp>
28 #include <MCThreadLocalData.hpp>
29 
30 #include <vorbis/codec.h>
31 #include <vorbis/vorbisenc.h>
32 #if defined(__unix__) || defined(__MINGW32__) || defined(__MINGW64__)
33 #define OV_EXCLUDE_STATIC_CALLBACKS 1
34 #endif
35 #include <vorbis/vorbisfile.h>
36 #include <ivorbisfile.h>
37 
38 #include <lame.h>
39 
40 #include <boost/scoped_ptr.hpp>
41 
42 namespace
43 {
44 MCThreadLocalData<MCBinaryData> CodingBuffer(true);
45 MCThreadLocalData<MCBinaryData> DecodingBuffer(true);
46 
47 void CheckStaticAudioCodingVariables()
48 {
49  if (unlikely(!CodingBuffer.get()))
50  {
51  CodingBuffer.reset(new MCBinaryData(10000));
52  }
53  if (unlikely(!DecodingBuffer.get()))
54  {
55  DecodingBuffer.reset(new MCBinaryData(10000));
56  }
57 }
58 
59 
60 size_t ReadOgg(void* dst, size_t size1, size_t size2, void* fh)
61 {
62  MCBinaryData* Data = reinterpret_cast<MCBinaryData*>(fh);
63  size_t Len = Data->GetRemainingCapacity(size1*size2);
64 
65  memcpy(dst, &Data->GetData()[Data->GetPosition()], Len);
66  Data->IncrementPosition(Len);
67  return Len;
68 }
69 
70 
71 int SeekOgg(void* fh, ogg_int64_t to, int type)
72 {
73  MCBinaryData* Data = reinterpret_cast<MCBinaryData*>(fh);
74 
75  if (to < 0)
76  {
77  MC_WARNING("Can't go back in Ogg Vorbis decoding... (%d)", (int)to);
78  return -1;
79  }
80  switch (type)
81  {
82  case SEEK_CUR:
83  if (Data->GetRemainingCapacity((unsigned int)to) < (unsigned int)to)
84  {
85  Data->SetPosition(Data->GetSize());
86  return -1;
87  } else {
88  Data->IncrementPosition(to);
89  }
90  break;
91 
92  case SEEK_END:
93  if (Data->GetSize() < (int)to)
94  {
95  Data->SetPosition(0);
96  return -1;
97  }
98  Data->SetPosition(Data->GetSize()-(int)to);
99  break;
100 
101  case SEEK_SET:
102  if (Data->GetSize() < (int)to)
103  {
104  Data->SetPosition(Data->GetSize());
105  return -1;
106  }
107  Data->SetPosition((int)to);
108  break;
109 
110  default:
111  return -1;
112  }
113  return 0;
114 }
115 
116 
117 int CloseOgg(void*)
118 {
119  return 0;
120 }
121 
122 
123 long TellOgg(void* fh)
124 {
125  MCBinaryData* Data = reinterpret_cast<MCBinaryData*>(fh);
126 
127  return (long)Data->GetPosition();
128 }
129 }
130 
131 MCBinaryData* MALossyConverter::EncodeToOggVorbis(const MCBinaryData& raw_data, const int sample_rate,
132  const int channels, const int quality)
133 {
134  if (raw_data.GetSize() <= 0)
135  return nullptr;
136 
137  boost::scoped_ptr<ogg_stream_state> OggStream(new ogg_stream_state);
138  boost::scoped_ptr<ogg_packet> OggPacket(new ogg_packet);
139  boost::scoped_ptr<ogg_page> OggPage(new ogg_page);
140  boost::scoped_ptr<vorbis_info> VorbisInfo(new vorbis_info);
141  boost::scoped_ptr<vorbis_comment> VorbisComment(new vorbis_comment);
142  boost::scoped_ptr<vorbis_dsp_state> VorbisDsp(new vorbis_dsp_state);
143  boost::scoped_ptr<vorbis_block> VorbisBlock(new vorbis_block);
144  boost::scoped_ptr<ogg_packet> Header(new ogg_packet);
145  boost::scoped_ptr<ogg_packet> HeaderComment(new ogg_packet);
146  boost::scoped_ptr<ogg_packet> HeaderCode(new ogg_packet);
147  int Quality = (int)MANum<int>(quality, -2, 320);
148 
149  CheckStaticAudioCodingVariables();
150  CodingBuffer->Allocate(raw_data.GetSize()*2);
151  vorbis_info_init(&*VorbisInfo);
152  if (Quality <= 10)
153  {
154  // VBR encoding
155  if (vorbis_encode_init_vbr(&*VorbisInfo, channels, sample_rate, (float)Quality / 10))
156  {
157  MC_WARNING("Ogg Vorbis initialization failed.");
158  return nullptr;
159  }
160  } else {
161  Quality = MCMax(Quality, 32);
162  // CBR encoding
163  if (vorbis_encode_init(&*VorbisInfo, channels, sample_rate, -1, Quality*1000, -1))
164  {
165  MC_WARNING("Ogg Vorbis initialization failed.");
166  return nullptr;
167  }
168  }
169  vorbis_comment_init(&*VorbisComment);
170  vorbis_analysis_init(&*VorbisDsp, &*VorbisInfo);
171  vorbis_block_init(&*VorbisDsp, &*VorbisBlock);
172  ogg_stream_init(&*OggStream, MCRand(0, 1000000));
173  // Write the header
174  vorbis_analysis_headerout(&*VorbisDsp, &*VorbisComment, &*Header, &*HeaderComment, &*HeaderCode);
175  ogg_stream_packetin(&*OggStream, &*Header);
176  ogg_stream_packetin(&*OggStream, &*HeaderComment);
177  ogg_stream_packetin(&*OggStream, &*HeaderCode);
178  while (true)
179  {
180  int Result = ogg_stream_flush(&*OggStream, &*OggPage);
181 
182  if (Result == 0)
183  break;
184 
185  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->header, OggPage->header_len);
186  CodingBuffer->IncrementPosition(OggPage->header_len);
187  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->body, OggPage->body_len);
188  CodingBuffer->IncrementPosition(OggPage->body_len);
189  }
190  // Encode the audio data to Ogg Vorbis
191  int Position = 0;
192  const int SliceSize = 1024;
193 
194  while (Position < raw_data.GetSize())
195  {
196  int Size = ((raw_data.GetSize()-Position < SliceSize) ? raw_data.GetSize()-Position : SliceSize);
197  float** Buffer = vorbis_analysis_buffer(&*VorbisDsp, Size);
198  char* RawDataPtr = (char*)&raw_data.GetData()[Position];
199 
200  // Uninterleave samples
201  if (channels == 2)
202  {
203  for (int i = 0; i < Size / 4 ; ++i)
204  {
205  Buffer[0][i] = ((RawDataPtr[i*4+1] << 8) | (0x00ff & (int)RawDataPtr[i*4])) / 32768.f;
206  Buffer[1][i] = ((RawDataPtr[i*4+3] << 8) | (0x00ff & (int)RawDataPtr[i*4+2])) / 32768.f;
207  }
208  vorbis_analysis_wrote(&*VorbisDsp, Size / 4);
209  } else {
210  for (int i = 0; i < Size / 2 ; ++i)
211  {
212  Buffer[0][i] = ((RawDataPtr[i*2+1] << 8) | (0x00ff & (int)RawDataPtr[i*2])) / 32768.f;
213  }
214  vorbis_analysis_wrote(&*VorbisDsp, Size / 2);
215  }
216 
217  while (vorbis_analysis_blockout(&*VorbisDsp, &*VorbisBlock) == 1)
218  {
219  vorbis_analysis(&*VorbisBlock, nullptr);
220  vorbis_bitrate_addblock(&*VorbisBlock);
221 
222  while (vorbis_bitrate_flushpacket(&*VorbisDsp, &*OggPacket))
223  {
224  ogg_stream_packetin(&*OggStream, &*OggPacket);
225 
226  while (true)
227  {
228  int Result = ogg_stream_pageout(&*OggStream, &*OggPage);
229 
230  if (Result == 0)
231  break;
232 
233  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->header, OggPage->header_len);
234  CodingBuffer->IncrementPosition(OggPage->header_len);
235  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->body, OggPage->body_len);
236  CodingBuffer->IncrementPosition(OggPage->body_len);
237  }
238  }
239  }
240  Position += Size;
241  }
242  // Finalize and close the file
243  vorbis_analysis_wrote(&*VorbisDsp, 0);
244  while (vorbis_analysis_blockout(&*VorbisDsp, &*VorbisBlock) == 1)
245  {
246  vorbis_analysis(&*VorbisBlock, nullptr);
247  vorbis_bitrate_addblock(&*VorbisBlock);
248 
249  while (vorbis_bitrate_flushpacket(&*VorbisDsp, &*OggPacket))
250  {
251  ogg_stream_packetin(&*OggStream, &*OggPacket);
252 
253  while (true)
254  {
255  int Result = ogg_stream_pageout(&*OggStream, &*OggPage);
256 
257  if (Result == 0)
258  break;
259 
260  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->header, OggPage->header_len);
261  CodingBuffer->IncrementPosition(OggPage->header_len);
262  memcpy(&CodingBuffer->GetData()[CodingBuffer->GetPosition()], (const char*)OggPage->body, OggPage->body_len);
263  CodingBuffer->IncrementPosition(OggPage->body_len);
264 
265  if (ogg_page_eos(&*OggPage) > 0)
266  break;
267  }
268  }
269  }
270  // Close the stream
271  ogg_stream_clear(&*OggStream);
272  vorbis_block_clear(&*VorbisBlock);
273  vorbis_dsp_clear(&*VorbisDsp);
274  vorbis_comment_clear(&*VorbisComment);
275  vorbis_info_clear(&*VorbisInfo);
276  // Return the encoded data
277  MCBinaryData* Data = new MCBinaryData(CodingBuffer->GetPosition());
278 
279  memcpy(Data->GetData(), CodingBuffer->GetData(), CodingBuffer->GetPosition());
280  return Data;
281 }
282 
283 
284 MCBinaryData* MALossyConverter::DecodeFromOggVorbis(MCBinaryData& ogg_data, int& sample_rate, int& channels)
285 {
286  if (ogg_data.GetSize() <= 0)
287  return nullptr;
288 
289  int OldPosition = ogg_data.GetPosition();
290  boost::scoped_ptr<OggVorbis_File> VorbisFile(new OggVorbis_File);
291  ov_callbacks Callbacks;
292 
293  memset(&*VorbisFile, 0, sizeof(OggVorbis_File));
294  ogg_data.SetPosition(0);
295  Callbacks.read_func = ReadOgg;
296  Callbacks.seek_func = SeekOgg;
297  Callbacks.close_func = CloseOgg;
298  Callbacks.tell_func = TellOgg;
299  // Check the Ogg Vorbis data integrity
300  if (ov_open_callbacks(&ogg_data, &*VorbisFile, nullptr, -1, Callbacks) < 0)
301  {
302  MC_WARNING("Corrupt Ogg Vorbis data.");
303  ov_clear(&*VorbisFile);
304  ogg_data.SetPosition(OldPosition);
305  return nullptr;
306  }
307  // Extract the stream properties
308  channels = ov_info(&*VorbisFile, -1)->channels;
309  sample_rate = ov_info(&*VorbisFile, -1)->rate;
310  // Decode the Ogg Vorbis data
311  int Position = 0;
312  int DesiredSize = ov_pcm_total(&*VorbisFile, -1)*2*channels;
313  MCBinaryData* DecodedData = new MCBinaryData(DesiredSize);
314 
315  // Fill the buffer
316  while (DesiredSize != 0)
317  {
318  int Section = 0;
319  long Ret = 0;
320 
321  Ret = ov_read(&*VorbisFile, (char*)&DecodedData->GetData()[Position], DesiredSize, 0, 2, 1, &Section);
322  if (Ret == 0 || DesiredSize < 10)
323  {
324  return DecodedData;
325  } else
326  if (Ret < 0)
327  {
328  delete DecodedData;
329  MC_WARNING("Corrupt vorbis file");
330  ov_clear(&*VorbisFile);
331  ogg_data.SetPosition(OldPosition);
332  return nullptr;
333  } else {
334  Position += Ret;
335  DesiredSize -= Ret;
336  }
337  }
338 
339  // Clean up
340  ov_clear(&*VorbisFile);
341  ogg_data.SetPosition(OldPosition);
342  return DecodedData;
343 }
344 
345 
346 MCBinaryData* MALossyConverter::QuickDecodeFromOggVorbis(MCBinaryData& ogg_data, int& sample_rate, int& channels)
347 {
348  if (ogg_data.GetSize() <= 0)
349  return nullptr;
350 
351  int OldPosition = ogg_data.GetPosition();
352  boost::scoped_ptr<vorbisidec::OggVorbis_File> VorbisFile(new vorbisidec::OggVorbis_File);
353  vorbisidec::ov_callbacks Callbacks;
354 
355  memset(&*VorbisFile, 0, sizeof(vorbisidec::OggVorbis_File));
356  ogg_data.SetPosition(0);
357  Callbacks.read_func = ReadOgg;
358  Callbacks.seek_func = SeekOgg;
359  Callbacks.close_func = CloseOgg;
360  Callbacks.tell_func = TellOgg;
361  // Check the Ogg Vorbis data integrity
362  if (vorbisidec::ov_open_callbacks(&ogg_data, &*VorbisFile, nullptr, -1, Callbacks) < 0)
363  {
364  MC_WARNING("Corrupt Ogg Vorbis data.");
365  vorbisidec::ov_clear(&*VorbisFile);
366  ogg_data.SetPosition(OldPosition);
367  return nullptr;
368  }
369  // Extract the stream properties
370  channels = vorbisidec::ov_info(&*VorbisFile, -1)->channels;
371  sample_rate = vorbisidec::ov_info(&*VorbisFile, -1)->rate;
372  // Decode the Ogg Vorbis data
373  int Position = 0;
374  int DesiredSize = vorbisidec::ov_pcm_total(&*VorbisFile, -1)*2*channels;
375  MCBinaryData* DecodedData = new MCBinaryData(DesiredSize);
376 
377  // Fill the buffer
378  while (DesiredSize != 0)
379  {
380  int Section = 0;
381  long Ret = 0;
382 
383  Ret = vorbisidec::ov_read(&*VorbisFile, (char*)&DecodedData->GetData()[Position], DesiredSize, &Section);
384  if (Ret == 0 || DesiredSize < 10)
385  {
386  return DecodedData;
387  } else
388  if (Ret < 0)
389  {
390  delete DecodedData;
391  MC_WARNING("Corrupt vorbis file");
392  vorbisidec::ov_clear(&*VorbisFile);
393  ogg_data.SetPosition(OldPosition);
394  return nullptr;
395  } else {
396  Position += Ret;
397  DesiredSize -= Ret;
398  }
399  }
400 
401  // Clean up
402  vorbisidec::ov_clear(&*VorbisFile);
403  ogg_data.SetPosition(OldPosition);
404  return DecodedData;
405 }
406 
407 
408 MCBinaryData* MALossyConverter::EncodeToMp3(const MCBinaryData& raw_data, const int sample_rate,
409  const int channels, const int quality)
410 {
411  if (raw_data.GetSize() <= 0)
412  return nullptr;
413 
414  lame::lame_t lame = lame::lame_init();
415  int Quality = (int)MANum<int>(quality, 0, 320);
416 
417  CheckStaticAudioCodingVariables();
418  CodingBuffer->Allocate(raw_data.GetSize()*2);
419  // Set mp3 encoding parameters
420  lame::lame_set_in_samplerate(lame, sample_rate);
421  if (channels == 1)
422  {
423  lame::lame_set_num_channels(lame, 1);
424  lame::lame_set_mode(lame, lame::MONO);
425  } else {
426  lame::lame_set_num_channels(lame, 2);
427  lame::lame_set_mode(lame, lame::JOINT_STEREO);
428  }
429  lame::lame_set_findReplayGain(lame, 0);
430  if (Quality <= 9)
431  {
432  // VBR encoding
433  lame::lame_set_VBR(lame, lame::vbr_mtrh);
434  lame::lame_set_VBR_q(lame, Quality);
435  lame::lame_set_quality(lame, 0);
436  } else {
437  // CBR encoding
438  Quality = MCMax(Quality, 48);
439 
440  lame::lame_set_VBR(lame, lame::vbr_off);
441  lame::lame_set_brate(lame, Quality);
442  }
443  lame::lame_init_params(lame);
444 
445  int Count = 0;
446 
447  if (channels == 2)
448  {
449  Count += lame_encode_buffer_interleaved(lame, (short*)(void*)raw_data.GetData(), raw_data.GetSize() / 4,
450  CodingBuffer->GetData(), CodingBuffer->GetSize());
451  } else {
452  Count += lame_encode_buffer(lame, (short*)(void*)raw_data.GetData(), nullptr, raw_data.GetSize() / 2,
453  CodingBuffer->GetData(), CodingBuffer->GetSize());
454  }
455  Count += lame_encode_flush(lame, &CodingBuffer->GetData()[Count], CodingBuffer->GetSize()-Count);
456  // Return the encoded data
457  MCBinaryData* Data = new MCBinaryData(Count);
458 
459  memcpy(Data->GetData(), CodingBuffer->GetData(), Count);
460  lame_close(lame);
461  return Data;
462 }
463 
464 
465 MCBinaryData* MALossyConverter::DecodeFromMp3(MCBinaryData& mp3_data, unsigned int orig_size,
466  int& sample_rate, int& channels)
467 {
468  if (mp3_data.GetSize() <= 0)
469  return nullptr;
470 
471  void* handler = lame::hip_decode_init();
472  lame::mp3data_struct Mp3Header;
473  MC::BinaryDataSPtr RightChannel(new MCBinaryData(mp3_data.GetSize()*50));
474 
475  memset(&Mp3Header, 0, sizeof(Mp3Header));
476  CheckStaticAudioCodingVariables();
477  DecodingBuffer->Allocate(mp3_data.GetSize()*50);
478  int Count = 0;
479 
480  for (int i = 0; i < mp3_data.GetSize(); i += 128)
481  {
482  Count += lame::hip_decode1_headers(handler, &mp3_data.GetData()[i],
483  mp3_data.GetSize()-i < 128 ? mp3_data.GetSize()-i : 128,
484  (short*)(void*)&DecodingBuffer->GetData()[Count],
485  (short*)(void*)&RightChannel->GetData()[Count],
486  &Mp3Header)*2;
487  }
488  channels = Mp3Header.stereo;
489  sample_rate = Mp3Header.samplerate;
490  lame::hip_decode_exit(handler);
491  // Return the encoded data (skip the delay in the beginning)
492  // Unfortunately, the mp3 encoding adds some non-deterministic delays before/after the waveform.
493  // The bigger silence is the starting part, therefore, half of the whole addition is cut from
494  // the beginning.
495  int DropCount = 0;
496  MCBinaryData* Data = new MCBinaryData(Count*channels-DropCount*channels);
497 
498  if (orig_size > 0 && Count > (int)orig_size && Count-(int)orig_size / channels > 0)
499  {
500  DropCount = (Count-orig_size / channels) / 2;
501  }
502  if (channels == 1)
503  {
504  short* MonoBuffer = (short*)(void*)&DecodingBuffer->GetData()[DropCount*2];
505  unsigned char* TargetBuffer = Data->GetData();
506 
507  for (int i = DropCount; i < Count / 2; ++i)
508  {
509  TargetBuffer[0] = (unsigned char)(MonoBuffer[0] & 0xff);
510  TargetBuffer[1] = (unsigned char)((MonoBuffer[0] >> 8) & 0xff);
511  MonoBuffer++;
512  TargetBuffer += 2;
513  }
514  } else {
515  short* LeftBuffer = (short*)(void*)&DecodingBuffer->GetData()[DropCount*2];
516  short* RightBuffer = (short*)(void*)&RightChannel->GetData()[DropCount*2];
517  unsigned char* TargetBuffer = Data->GetData();
518 
519  for (int i = DropCount; i < Count / 2; ++i)
520  {
521  TargetBuffer[0] = (unsigned char)(LeftBuffer[0] & 0xff);
522  TargetBuffer[1] = (unsigned char)((LeftBuffer[0] >> 8) & 0xff);
523  TargetBuffer[2] = (unsigned char)(RightBuffer[0] & 0xff);
524  TargetBuffer[3] = (unsigned char)((RightBuffer[0] >> 8) & 0xff);
525  LeftBuffer++;
526  RightBuffer++;
527  TargetBuffer += 4;
528  }
529  }
530  return Data;
531 }
static MCBinaryData * EncodeToMp3(const MCBinaryData &raw_data, const int sample_rate, const int channels, const int quality)
Encode raw audio data to mp3.
Binary data class.
#define MC_WARNING(...)
Warning macro.
Definition: MCLog.hpp:43
static MCBinaryData * DecodeFromOggVorbis(MCBinaryData &ogg_data, int &sample_rate, int &channels)
Decode Ogg Vorbis data to raw audio.
T MCRand(const T &min, const T &max)
Get a random number generated with standard calls.
Definition: MCDefs.hpp:248
unsigned int GetRemainingCapacity(unsigned int capacity) const
Check if the binary data has enough remaining capacity.
void SetPosition(unsigned int position)
Set the cursor position.
void IncrementPosition(unsigned int position=1)
Increment the cursor position.
static MCBinaryData * DecodeFromMp3(MCBinaryData &mp3_data, unsigned int orig_size, int &sample_rate, int &channels)
Decode mp3 data to raw audio.
unsigned char * GetData() const
Get direct access to the binary data.
A wrapper class to cover boost::thread_specific_ptr/folly::ThreadLocal API on certain targets...
const T MCMax(const U &container)
Get the maximal value of a container.
static MCBinaryData * EncodeToOggVorbis(const MCBinaryData &raw_data, const int sample_rate, const int channels, const int quality)
Encode raw audio data to Ogg Vorbis.
static MCBinaryData * QuickDecodeFromOggVorbis(MCBinaryData &ogg_data, int &sample_rate, int &channels)
Decode Ogg Vorbis data with integer decoder to raw audio.
int GetPosition() const
Get the current position in the binary data.
int GetSize() const
Get binary data size.