mirror of
https://github.com/ElvishArtisan/rivendell.git
synced 2025-04-07 01:13:50 +02:00
* Added the name of the Rivendell library component to 'Unknown Error' messages. Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
2092 lines
53 KiB
C++
2092 lines
53 KiB
C++
// rdaudioconvert.cpp
|
|
//
|
|
// Convert Audio File Formats
|
|
//
|
|
// (C) Copyright 2010-2019 Fred Gleason <fredg@paravelsystems.com>
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2 as
|
|
// published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public
|
|
// License along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
//
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <math.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include <rdapplication.h>
|
|
#include <rdaudioconvert.h>
|
|
#include <rdcart.h>
|
|
#include <rdconf.h>
|
|
#include <rd.h>
|
|
#include <rdtempdirectory.h>
|
|
|
|
#include <sndfile.h>
|
|
#include <samplerate.h>
|
|
#include <soundtouch/SoundTouch.h>
|
|
#ifdef HAVE_VORBIS
|
|
#include <ogg/ogg.h>
|
|
#include <vorbis/vorbisenc.h>
|
|
#endif // HAVE_VORBIS
|
|
#ifdef HAVE_FLAC
|
|
#include <FLAC++/encoder.h>
|
|
#include <rdflacdecode.h>
|
|
#endif // HAVE_FLAC
|
|
#ifdef HAVE_MP4_LIBS
|
|
#include <mp4v2/mp4v2.h>
|
|
#include <neaacdec.h>
|
|
#endif // HAVE_MP4_LIBS
|
|
//#include <id3/tag.h>
|
|
#include <id3v2tag.h>
|
|
#include <textidentificationframe.h>
|
|
#include <mpegfile.h>
|
|
#include <qfile.h>
|
|
|
|
#define STAGE2_XFER_SIZE 2048
|
|
#define STAGE2_BUFFER_SIZE 49152
|
|
|
|
RDAudioConvert::RDAudioConvert(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
conv_start_point=-1;
|
|
conv_end_point=-1;
|
|
conv_speed_ratio=1.0;
|
|
conv_peak_sample=0.0;
|
|
conv_settings=NULL;
|
|
conv_src_wavedata=new RDWaveData();
|
|
conv_dst_wavedata=NULL;
|
|
conv_src_converter=rda->libraryConf()->srcConverter();
|
|
conv_transcoding_delay=rda->config()->transcodingDelay();
|
|
|
|
//
|
|
// Load MPEG Libraries
|
|
//
|
|
conv_mad_handle=dlopen("libmad.so.0",RTLD_LAZY);
|
|
conv_lame_handle=dlopen("libmp3lame.so.0",RTLD_LAZY);
|
|
conv_twolame_handle=dlopen("libtwolame.so.0",RTLD_LAZY);
|
|
}
|
|
|
|
|
|
RDAudioConvert::~RDAudioConvert()
|
|
{
|
|
delete conv_src_wavedata;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setSourceFile(const QString &filename)
|
|
{
|
|
conv_src_filename=filename;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setDestinationFile(const QString &filename)
|
|
{
|
|
conv_dst_filename=filename;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setDestinationSettings(RDSettings *settings)
|
|
{
|
|
conv_settings=settings;
|
|
}
|
|
|
|
|
|
RDWaveData *RDAudioConvert::sourceWaveData() const
|
|
{
|
|
return conv_src_wavedata;
|
|
}
|
|
|
|
|
|
QString RDAudioConvert::sourceRdxl() const
|
|
{
|
|
return conv_src_rdxl;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setDestinationWaveData(RDWaveData *wavedata)
|
|
{
|
|
conv_dst_wavedata=wavedata;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setDestinationRdxl(const QString &xml)
|
|
{
|
|
conv_dst_rdxl=xml;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setRange(int start_pt,int end_pt)
|
|
{
|
|
conv_start_point=start_pt;
|
|
conv_end_point=end_pt;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::setSpeedRatio(float ratio)
|
|
{
|
|
conv_speed_ratio=ratio;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::convert()
|
|
{
|
|
RDAudioConvert::ErrorCode err;
|
|
QString tmpfile1;
|
|
QString tmpfile2;
|
|
RDTempDirectory *temp_dir=NULL;
|
|
|
|
//
|
|
// Make sure we're all set to go...
|
|
//
|
|
if(conv_settings==NULL) {
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
if(!RDAudioConvert::settingsValid(conv_settings)) {
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
struct stat stats;
|
|
memset(&stats,0,sizeof(stats));
|
|
if(stat((const char *)conv_src_filename.toUtf8(),&stats)!=0) {
|
|
return RDAudioConvert::ErrorNoSource;
|
|
}
|
|
if(conv_dst_filename.isEmpty()) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
if((conv_speed_ratio<RD_TIMESCALE_MIN)||(conv_speed_ratio>RD_TIMESCALE_MAX)) {
|
|
return RDAudioConvert::ErrorInvalidSpeed;
|
|
}
|
|
|
|
//
|
|
// Generate Temporary Filenames
|
|
//
|
|
temp_dir=new RDTempDirectory("rdaudioconvert");
|
|
QString err_msg;
|
|
if(!temp_dir->create(&err_msg)) {
|
|
delete temp_dir;
|
|
rda->syslog(LOG_WARNING,"Could not create %s",
|
|
(const char *)err_msg.toUtf8());
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
tmpfile1=QString(temp_dir->path())+"/signed32_1.wav";
|
|
tmpfile2=QString(temp_dir->path())+"/signed32_2.wav";
|
|
|
|
//
|
|
// Stage One -- Convert Source Format to Signed 32 Bit Integer
|
|
//
|
|
if((err=Stage1Convert(conv_src_filename,tmpfile1))!=
|
|
RDAudioConvert::ErrorOk) {
|
|
delete temp_dir;
|
|
return err;
|
|
}
|
|
|
|
//
|
|
// Stage Two -- Convert Levels, Sample Rate, Channelization, Speed
|
|
//
|
|
if((err=Stage2Convert(tmpfile1,tmpfile2))!=
|
|
RDAudioConvert::ErrorOk) {
|
|
delete temp_dir;
|
|
return err;
|
|
}
|
|
|
|
//
|
|
// Stage Three -- Write Out Destination Format
|
|
//
|
|
if((err=Stage3Convert(tmpfile2,conv_dst_filename))!=
|
|
RDAudioConvert::ErrorOk) {
|
|
delete temp_dir;
|
|
return err;
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
delete temp_dir;
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
|
|
bool RDAudioConvert::settingsValid(RDSettings *settings)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
QString RDAudioConvert::errorText(RDAudioConvert::ErrorCode err)
|
|
{
|
|
QString ret=QString().sprintf("Unknown RDAudioConvert Error [%u]",err);
|
|
|
|
switch(err) {
|
|
case RDAudioConvert::ErrorOk:
|
|
ret=tr("OK");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorInvalidSettings:
|
|
ret=tr("Invalid/Unsupported Settings");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorNoSource:
|
|
ret=tr("Unable to access source file");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorNoDestination:
|
|
ret=tr("Unable to create destination file");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorInvalidSource:
|
|
ret=tr("Unrecognized source format");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorInternal:
|
|
ret=tr("Internal Error");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorFormatNotSupported:
|
|
ret=tr("Unsupported Format");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorNoDisc:
|
|
ret=tr("No CD found in drive");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorNoTrack:
|
|
ret=tr("No such track on CD");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorInvalidSpeed:
|
|
ret=tr("Invalid speed ratio");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorFormatError:
|
|
ret=tr("Source format error");
|
|
break;
|
|
|
|
case RDAudioConvert::ErrorNoSpace:
|
|
ret=tr("No space left on device");
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1Convert(const QString &srcfile,
|
|
const QString &dstfile)
|
|
{
|
|
SNDFILE *sf_src=NULL;
|
|
SF_INFO sf_src_info;
|
|
RDWaveFile *wave=NULL;
|
|
RDAudioConvert::ErrorCode err=RDAudioConvert::ErrorInvalidSource;
|
|
|
|
//
|
|
// Try RDWaveFile
|
|
//
|
|
wave=new RDWaveFile(srcfile);
|
|
if(wave->openWave(conv_src_wavedata)) {
|
|
switch(wave->type()) {
|
|
case RDWaveFile::Wave:
|
|
if(wave->getFormatTag()==WAVE_FORMAT_MPEG) {
|
|
err=Stage1Mpeg(dstfile,wave);
|
|
delete wave;
|
|
return err;
|
|
}
|
|
break;
|
|
|
|
case RDWaveFile::Mpeg:
|
|
case RDWaveFile::Atx:
|
|
case RDWaveFile::Tmc:
|
|
case RDWaveFile::Ambos:
|
|
err=Stage1Mpeg(dstfile,wave);
|
|
delete wave;
|
|
return err;
|
|
|
|
case RDWaveFile::Ogg:
|
|
err=Stage1Vorbis(dstfile,wave);
|
|
delete wave;
|
|
return err;
|
|
|
|
case RDWaveFile::Flac:
|
|
err=Stage1Flac(dstfile,wave);
|
|
delete wave;
|
|
return err;
|
|
|
|
case RDWaveFile::M4A:
|
|
err=Stage1M4A(dstfile,wave);
|
|
delete wave;
|
|
return err;
|
|
|
|
case RDWaveFile::Aiff:
|
|
case RDWaveFile::Unknown:
|
|
break;
|
|
}
|
|
}
|
|
delete wave;
|
|
|
|
//
|
|
// Try Libsndfile
|
|
//
|
|
memset(&sf_src_info,0,sizeof(sf_src_info));
|
|
if((sf_src=sf_open(srcfile.toUtf8(),SFM_READ,&sf_src_info))!=NULL) {
|
|
err=Stage1SndFile(dstfile,sf_src,&sf_src_info);
|
|
sf_close(sf_src);
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1Flac(const QString &dstfile,
|
|
RDWaveFile *wave)
|
|
{
|
|
#ifdef HAVE_FLAC
|
|
SNDFILE *sf_dst=NULL;
|
|
SF_INFO sf_dst_info;
|
|
RDFlacDecode *flac=NULL;
|
|
|
|
//
|
|
// Open Destination
|
|
//
|
|
memset(&sf_dst_info,0,sizeof(sf_dst_info));
|
|
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
|
sf_dst_info.channels=wave->getChannels();
|
|
sf_dst_info.samplerate=wave->getSamplesPerSec();
|
|
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Decode
|
|
//
|
|
flac=new RDFlacDecode(sf_dst);
|
|
flac->setRange(conv_start_point,conv_end_point);
|
|
flac->decode(wave,&conv_peak_sample);
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
delete flac;
|
|
sf_close(sf_dst);
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_FLAC
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1Vorbis(const QString &dstfile,
|
|
RDWaveFile *wave)
|
|
{
|
|
#ifdef HAVE_VORBIS
|
|
SNDFILE *sf_dst=NULL;
|
|
SF_INFO sf_dst_info;
|
|
ogg_sync_state ogg_sync;
|
|
ogg_stream_state ogg_stream;
|
|
ogg_packet ogg_packet;
|
|
ogg_page ogg_page;
|
|
vorbis_info vorbis_info;
|
|
vorbis_comment vorbis_comment;
|
|
vorbis_dsp_state vorbis_dsp;
|
|
vorbis_block vorbis_block;
|
|
int fd;
|
|
ssize_t n;
|
|
long serialno=-1;
|
|
bool vorbis_ready=false;
|
|
int frames;
|
|
float **pcm;
|
|
float pcmbuf[32768];
|
|
sf_count_t start=0;
|
|
sf_count_t end=wave->getSampleLength();
|
|
sf_count_t total_frames=0;
|
|
|
|
//
|
|
// Open Destination
|
|
//
|
|
memset(&sf_dst_info,0,sizeof(sf_dst_info));
|
|
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
|
sf_dst_info.channels=wave->getChannels();
|
|
sf_dst_info.samplerate=wave->getSamplesPerSec();
|
|
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Initialize Decoder
|
|
//
|
|
if((fd=open(wave->getName().toUtf8(),O_RDONLY))<0) {
|
|
sf_close(sf_dst);
|
|
return RDAudioConvert::ErrorNoSource;
|
|
}
|
|
ogg_sync_init(&ogg_sync);
|
|
vorbis_info_init(&vorbis_info);
|
|
vorbis_comment_init(&vorbis_comment);
|
|
|
|
//
|
|
// Decode
|
|
//
|
|
if(conv_start_point>0) {
|
|
start=(double)conv_start_point*(double)wave->getSamplesPerSec()/1000.0;
|
|
}
|
|
if(conv_end_point>=0) {
|
|
end=(double)conv_end_point*(double)wave->getSamplesPerSec()/1000.0;
|
|
}
|
|
while((n=read(fd,ogg_sync_buffer(&ogg_sync,4096),4096))>0) {
|
|
ogg_sync_wrote(&ogg_sync,n);
|
|
while(ogg_sync_pageout(&ogg_sync,&ogg_page)==1) {
|
|
if(serialno<0) {
|
|
serialno=ogg_page_serialno(&ogg_page);
|
|
ogg_stream_init(&ogg_stream,serialno);
|
|
}
|
|
if(ogg_stream_pagein(&ogg_stream,&ogg_page)==0) {
|
|
while(ogg_stream_packetout(&ogg_stream,&ogg_packet)==1) {
|
|
switch(ogg_packet.packetno) {
|
|
case 0: // Start Packet
|
|
case 1: // Comment Packet
|
|
vorbis_synthesis_headerin(&vorbis_info,&vorbis_comment,&ogg_packet);
|
|
break;
|
|
|
|
case 2: // Codebook Packet
|
|
vorbis_synthesis_headerin(&vorbis_info,&vorbis_comment,&ogg_packet);
|
|
vorbis_synthesis_init(&vorbis_dsp,&vorbis_info);
|
|
vorbis_block_init(&vorbis_dsp,&vorbis_block);
|
|
vorbis_ready=true;
|
|
break;
|
|
|
|
default: // Audio Packets
|
|
if(vorbis_synthesis(&vorbis_block,&ogg_packet)==0) {
|
|
vorbis_synthesis_blockin(&vorbis_dsp,&vorbis_block);
|
|
}
|
|
while((frames=vorbis_synthesis_pcmout(&vorbis_dsp,&pcm))>0) {
|
|
for(int i=0;i<frames;i++) {
|
|
for(int j=0;j<wave->getChannels();j++) {
|
|
pcmbuf[wave->getChannels()*i+j]=pcm[j][i];
|
|
}
|
|
}
|
|
if(total_frames>=start) {
|
|
if((total_frames+frames)<end) { // Write entire buffer
|
|
UpdatePeak(pcmbuf,frames*wave->getChannels());
|
|
sf_writef_float(sf_dst,pcmbuf,frames);
|
|
}
|
|
else {
|
|
if(total_frames<(total_frames+frames)) { // Write start of buffer
|
|
UpdatePeak(pcmbuf,
|
|
(total_frames+frames-end)*wave->getChannels());
|
|
sf_writef_float(sf_dst,pcmbuf,total_frames+frames-end);
|
|
//
|
|
// Done -- no need to decode the rest
|
|
//
|
|
if(vorbis_ready) {
|
|
vorbis_block_clear(&vorbis_block);
|
|
vorbis_dsp_clear(&vorbis_dsp);
|
|
}
|
|
vorbis_info_clear(&vorbis_info);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
ogg_stream_clear(&ogg_stream);
|
|
ogg_sync_clear(&ogg_sync);
|
|
::close(fd);
|
|
sf_close(sf_dst);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
int diff=total_frames+frames-start;
|
|
if(diff>0) { // Write end of buffer
|
|
UpdatePeak(pcmbuf+diff,(frames-diff)*wave->getChannels());
|
|
sf_writef_float(sf_dst,pcmbuf+diff,frames-diff);
|
|
}
|
|
}
|
|
total_frames+=frames;
|
|
vorbis_synthesis_read(&vorbis_dsp,frames);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
if(vorbis_ready) {
|
|
vorbis_block_clear(&vorbis_block);
|
|
vorbis_dsp_clear(&vorbis_dsp);
|
|
}
|
|
vorbis_info_clear(&vorbis_info);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
ogg_stream_clear(&ogg_stream);
|
|
ogg_sync_clear(&ogg_sync);
|
|
::close(fd);
|
|
sf_close(sf_dst);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_VORBIS
|
|
}
|
|
|
|
#define STAGE1BUFSIZE 16384
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1Mpeg(const QString &dstfile,
|
|
RDWaveFile *wave)
|
|
{
|
|
#ifdef HAVE_MAD
|
|
SNDFILE *sf_dst=NULL;
|
|
SF_INFO sf_dst_info;
|
|
struct mad_stream mad_stream;
|
|
struct mad_frame mad_frame;
|
|
struct mad_synth mad_synth;
|
|
int left_over=0;
|
|
int fsize;
|
|
int n;
|
|
unsigned char buffer[STAGE1BUFSIZE];
|
|
float sf_buffer[1152*2];
|
|
sf_count_t start=0;
|
|
int64_t end=-1;
|
|
sf_count_t frames=0;
|
|
|
|
//
|
|
// Load MAD
|
|
//
|
|
if(!LoadMad()) {
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
}
|
|
|
|
//
|
|
// Open Destination
|
|
//
|
|
memset(&sf_dst_info,0,sizeof(sf_dst_info));
|
|
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
|
sf_dst_info.channels=wave->getChannels();
|
|
sf_dst_info.samplerate=wave->getSamplesPerSec();
|
|
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE);
|
|
|
|
//
|
|
// Initialize Decoder
|
|
//
|
|
mad_stream_init(&mad_stream);
|
|
mad_frame_init(&mad_frame);
|
|
mad_synth_init(&mad_synth);
|
|
fsize=144*wave->getHeadBitRate()/wave->getSamplesPerSec();
|
|
|
|
//
|
|
// Decode
|
|
//
|
|
if(conv_start_point>0) {
|
|
start=(double)conv_start_point*(double)wave->getSamplesPerSec()/1000.0;
|
|
}
|
|
if(conv_end_point>=0) {
|
|
end=(double)conv_end_point*(double)wave->getSamplesPerSec()/1000.0;
|
|
}
|
|
while((n=wave->readWave(buffer+left_over,fsize))>0) {
|
|
if((buffer[left_over]==0xff)&&(buffer[2+left_over]&0x02)!=0) {
|
|
n+=wave->readWave(buffer+left_over+n,1); // Padding slot
|
|
}
|
|
mad_stream_buffer(&mad_stream,buffer,n+left_over);
|
|
//printf("mad err: %d\n",mad_stream.error);
|
|
while(1) {
|
|
|
|
int thiserr=mad_frame_decode(&mad_frame,&mad_stream);
|
|
if(thiserr!=0) {
|
|
if(!MAD_RECOVERABLE(mad_stream.error)) {
|
|
break;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
mad_synth_frame(&mad_synth,&mad_frame);
|
|
for(int i=0;i<mad_synth.pcm.length;i++) {
|
|
for(int j=0;j<mad_synth.pcm.channels;j++) {
|
|
sf_buffer[i*mad_synth.pcm.channels+j]=
|
|
(float)mad_f_todouble(mad_synth.pcm.samples[j][i]);
|
|
}
|
|
}
|
|
if(frames>=start) {
|
|
if((end<0)||((frames+mad_synth.pcm.length)<end)) { // Write full buffer
|
|
UpdatePeak(sf_buffer,mad_synth.pcm.length*wave->getChannels());
|
|
sf_writef_float(sf_dst,sf_buffer,mad_synth.pcm.length);
|
|
}
|
|
else {
|
|
if(frames<(frames+mad_synth.pcm.length)) { // Write start of buffer
|
|
UpdatePeak(sf_buffer,
|
|
(frames+mad_synth.pcm.length-end)*wave->getChannels());
|
|
sf_writef_float(sf_dst,sf_buffer,frames+mad_synth.pcm.length-end);
|
|
//
|
|
// Done -- no need to decode the rest
|
|
//
|
|
mad_synth_finish(&mad_synth);
|
|
mad_frame_finish(&mad_frame);
|
|
mad_stream_finish(&mad_stream);
|
|
wave->closeWave();
|
|
sf_close(sf_dst);
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
int diff=frames+mad_synth.pcm.length-start;
|
|
if(diff>0) { // Write end of buffer
|
|
UpdatePeak(sf_buffer+diff,
|
|
(mad_synth.pcm.length-diff)*wave->getChannels());
|
|
sf_writef_float(sf_dst,sf_buffer+diff,mad_synth.pcm.length-diff);
|
|
}
|
|
}
|
|
frames+=mad_synth.pcm.length;
|
|
|
|
}
|
|
left_over=mad_stream.bufend-mad_stream.next_frame;
|
|
|
|
// Prevent buffer overflow on malformed files.
|
|
// The amount checked for should match the maximum amount that may be read
|
|
// by the next top-of-loop wave->readWave call.
|
|
if(left_over + fsize + 1 > STAGE1BUFSIZE) {
|
|
return RDAudioConvert::ErrorFormatError;
|
|
}
|
|
memmove(buffer,mad_stream.next_frame,left_over);
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
memset(buffer+left_over,0,MAD_BUFFER_GUARD);
|
|
mad_stream_buffer(&mad_stream,buffer,MAD_BUFFER_GUARD+left_over);
|
|
if(mad_frame_decode(&mad_frame,&mad_stream)==0) {
|
|
mad_synth_frame(&mad_synth,&mad_frame);
|
|
for(int i=0;i<mad_synth.pcm.length;i++) {
|
|
for(int j=0;j<mad_synth.pcm.channels;j++) {
|
|
sf_buffer[i*mad_synth.pcm.channels+j]=
|
|
(float)mad_f_todouble(mad_synth.pcm.samples[j][i]);
|
|
}
|
|
}
|
|
UpdatePeak(sf_buffer,mad_synth.pcm.length*wave->getChannels());
|
|
sf_writef_float(sf_dst,sf_buffer,mad_synth.pcm.length);
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
mad_synth_finish(&mad_synth);
|
|
mad_frame_finish(&mad_frame);
|
|
mad_stream_finish(&mad_stream);
|
|
wave->closeWave();
|
|
|
|
sf_close(sf_dst);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_MAD
|
|
}
|
|
|
|
// Based on libfaad's frontend/main.c, but using libmp4v2 for MP4 access.
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1M4A(const QString &dstfile,
|
|
RDWaveFile *wave)
|
|
{
|
|
#ifdef HAVE_MP4_LIBS
|
|
SNDFILE *sf_dst=NULL;
|
|
SF_INFO sf_dst_info;
|
|
MP4FileHandle f;
|
|
MP4TrackId audioTrack;
|
|
MP4SampleId firstSample, lastSample;
|
|
uint32_t aacBufSize, aacConfigSize;
|
|
uint8_t *aacBuf, *aacConfigBuffer;
|
|
NeAACDecHandle hDecoder;
|
|
NeAACDecConfigurationPtr config;
|
|
unsigned long foundSampleRate;
|
|
unsigned char foundChannels;
|
|
RDAudioConvert::ErrorCode ret = RDAudioConvert::ErrorOk;
|
|
|
|
if(!dlmp4.load()) {
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
}
|
|
|
|
//
|
|
// Open source
|
|
//
|
|
f = dlmp4.MP4Read(wave->getName().toUtf8());
|
|
if(f == MP4_INVALID_FILE_HANDLE)
|
|
return RDAudioConvert::ErrorNoSource;
|
|
|
|
audioTrack = dlmp4.getMP4AACTrack(f);
|
|
firstSample = 1;
|
|
lastSample = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack);
|
|
if(conv_start_point > 0) {
|
|
|
|
double startsecs = ((double)conv_start_point) / 1000;
|
|
MP4Timestamp startts = (MP4Timestamp)(startsecs * wave->getSamplesPerSec());
|
|
firstSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, startts, /*need_sync=*/false);
|
|
if(firstSample == MP4_INVALID_SAMPLE_ID) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_mp4;
|
|
}
|
|
|
|
}
|
|
|
|
if(conv_end_point > 0) {
|
|
|
|
double stopsecs = ((double)conv_end_point) / 1000;
|
|
MP4Timestamp stopts = (MP4Timestamp)(stopsecs * wave->getSamplesPerSec());
|
|
lastSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, stopts, /*need_sync=*/false);
|
|
if(lastSample == MP4_INVALID_SAMPLE_ID) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_mp4;
|
|
}
|
|
|
|
}
|
|
|
|
aacBufSize = dlmp4.MP4GetTrackMaxSampleSize(f, audioTrack);
|
|
aacBuf = (uint8_t*)malloc(aacBufSize);
|
|
if(!aacBufSize || !aacBuf) {
|
|
// Probably the source's fault for specifying a massive buffer.
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_mp4;
|
|
}
|
|
|
|
dlmp4.MP4GetTrackESConfiguration(f, audioTrack, &aacConfigBuffer, &aacConfigSize);
|
|
if(!aacConfigBuffer) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_mp4_buf;
|
|
}
|
|
|
|
//
|
|
// Open Destination
|
|
//
|
|
|
|
memset(&sf_dst_info,0,sizeof(sf_dst_info));
|
|
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
|
sf_dst_info.channels=wave->getChannels();
|
|
sf_dst_info.samplerate=wave->getSamplesPerSec();
|
|
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
|
|
ret = RDAudioConvert::ErrorNoDestination;
|
|
goto out_mp4_configbuf;
|
|
}
|
|
sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE);
|
|
|
|
//
|
|
// Initialize Decoder
|
|
//
|
|
hDecoder = dlmp4.NeAACDecOpen();
|
|
|
|
config = dlmp4.NeAACDecGetCurrentConfiguration(hDecoder);
|
|
config->outputFormat = FAAD_FMT_FLOAT;
|
|
config->downMatrix = 1; // Downmix >2 channels to stereo.
|
|
if(!dlmp4.NeAACDecSetConfiguration(hDecoder, config)) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_decoder;
|
|
}
|
|
|
|
if(dlmp4.NeAACDecInit2(hDecoder, aacConfigBuffer, aacConfigSize, &foundSampleRate, &foundChannels) < 0) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_decoder;
|
|
}
|
|
|
|
if(foundSampleRate != wave->getSamplesPerSec() || foundChannels != wave->getChannels()) {
|
|
fprintf(stderr, "M4A header information inconsistent with actual file? Header: %u/%u; file: %lu/%u\n",
|
|
wave->getSamplesPerSec(), (unsigned)wave->getChannels(), foundSampleRate, (unsigned)foundChannels);
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
goto out_decoder;
|
|
}
|
|
|
|
//
|
|
// Decode
|
|
//
|
|
for(MP4SampleId i = firstSample; i <= lastSample; ++i) {
|
|
|
|
uint32_t aacBytes = aacBufSize;
|
|
if(!dlmp4.MP4ReadSample(f, audioTrack, i, &aacBuf, &aacBytes, 0, 0, 0, 0)) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
break;
|
|
}
|
|
|
|
NeAACDecFrameInfo frameInfo;
|
|
// The library docs are not clear about the lifetime or cleanup of sample_buffer.
|
|
// I hope it lives until the next NeAACDecDecode call, and is cleaned up by NeAACDecClose
|
|
void* sample_buffer = dlmp4.NeAACDecDecode(hDecoder, &frameInfo, aacBuf, aacBytes);
|
|
if(!sample_buffer) {
|
|
ret = RDAudioConvert::ErrorInvalidSource;
|
|
break;
|
|
}
|
|
|
|
UpdatePeak((const float*)sample_buffer, frameInfo.samples);
|
|
|
|
if(sf_write_float(sf_dst, (const float*)sample_buffer, frameInfo.samples) != (sf_count_t)frameInfo.samples) {
|
|
rda->syslog(LOG_WARNING,"%s",sf_strerror(sf_dst));
|
|
ret = RDAudioConvert::ErrorInternal;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
|
|
out_decoder:
|
|
dlmp4.NeAACDecClose(hDecoder);
|
|
// out_sf:
|
|
sf_close(sf_dst);
|
|
out_mp4_configbuf:
|
|
free(aacConfigBuffer);
|
|
out_mp4_buf:
|
|
free(aacBuf);
|
|
out_mp4:
|
|
dlmp4.MP4Close(f, 0);
|
|
|
|
return ret;
|
|
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif
|
|
}
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage1SndFile(const QString &dstfile,
|
|
SNDFILE *sf_src,
|
|
SF_INFO *sf_src_info)
|
|
{
|
|
SNDFILE *sf_dst=NULL;
|
|
SF_INFO sf_dst_info;
|
|
sf_count_t start=0;
|
|
sf_count_t end=sf_src_info->frames;
|
|
|
|
//
|
|
// Open Destination
|
|
//
|
|
sf_dst_info=*sf_src_info;
|
|
sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
|
if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Transfer Data
|
|
//
|
|
sf_count_t buffer_size=2048/sf_src_info->channels;
|
|
float *buffer=new float[2048];
|
|
sf_count_t n=0;
|
|
if(conv_start_point>0) {
|
|
start=sf_seek(sf_src,(double)conv_start_point*
|
|
(double)sf_src_info->samplerate/1000.0,SEEK_SET);
|
|
}
|
|
if(conv_end_point>=0) {
|
|
end=(double)conv_end_point*(double)sf_src_info->samplerate/1000.0;
|
|
}
|
|
while((n=sf_readf_float(sf_src,buffer,buffer_size))>0) {
|
|
UpdatePeak(buffer,n*sf_src_info->channels);
|
|
sf_writef_float(sf_dst,buffer,n);
|
|
start+=n;
|
|
if((end-start)<buffer_size) {
|
|
buffer_size=end-start;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
delete buffer;
|
|
sf_close(sf_dst);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage2Convert(const QString &srcfile,
|
|
const QString &dstfile)
|
|
{
|
|
soundtouch::SoundTouch *st_conv=NULL;
|
|
SNDFILE *src_sf=NULL;
|
|
SNDFILE *dst_sf=NULL;
|
|
SF_INFO src_info;
|
|
SF_INFO dst_info;
|
|
SRC_STATE *src_state=NULL;
|
|
SRC_DATA src_data;
|
|
float *pcm[3]={NULL,NULL,NULL};
|
|
bool free_pcm[3]={false,false,false};
|
|
int err;
|
|
sf_count_t n;
|
|
float ratio=1.0;
|
|
|
|
//
|
|
// Open Files
|
|
//
|
|
memset(&src_info,0,sizeof(src_info));
|
|
if((src_sf=sf_open(srcfile,SFM_READ,&src_info))==NULL) {
|
|
rda->syslog(LOG_WARNING,"Could not open %s",(const char *)srcfile.toUtf8());
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
sf_command(src_sf,SFC_SET_NORM_FLOAT,NULL,SF_FALSE);
|
|
sf_command(dst_sf,SFC_SET_CLIPPING,NULL,SF_TRUE);
|
|
memset(&dst_info,0,sizeof(dst_info));
|
|
dst_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_32;
|
|
dst_info.channels=conv_settings->channels();
|
|
dst_info.samplerate=conv_settings->sampleRate();
|
|
if((dst_sf=sf_open(dstfile,SFM_WRITE,&dst_info))==NULL) {
|
|
sf_close(src_sf);
|
|
rda->syslog(LOG_WARNING,"Could not open %s",(const char *)dstfile.toUtf8());
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
|
|
//
|
|
// Allocate Buffers
|
|
//
|
|
pcm[0]=new float[STAGE2_BUFFER_SIZE];
|
|
free_pcm[0]=true;
|
|
if(dst_info.samplerate!=src_info.samplerate) {
|
|
pcm[1]=new float[STAGE2_BUFFER_SIZE];
|
|
free_pcm[1]=true;
|
|
if(dst_info.channels!=src_info.channels) {
|
|
pcm[2]=new float[STAGE2_BUFFER_SIZE];
|
|
free_pcm[2]=true;
|
|
}
|
|
else {
|
|
pcm[2]=pcm[1];
|
|
}
|
|
}
|
|
else {
|
|
pcm[1]=pcm[0];
|
|
if(dst_info.channels!=src_info.channels) {
|
|
pcm[2]=new float[STAGE2_BUFFER_SIZE];
|
|
free_pcm[2]=true;
|
|
}
|
|
else {
|
|
pcm[2]=pcm[0];
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize Rate Converter
|
|
//
|
|
if(dst_info.samplerate!=src_info.samplerate) {
|
|
if((src_state=src_new(conv_src_converter,src_info.channels,&err))==NULL) {
|
|
sf_close(src_sf);
|
|
sf_close(dst_sf);
|
|
rda->syslog(LOG_WARNING,"%s",src_strerror(err));
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
memset(&src_data,0,sizeof(src_data));
|
|
src_data.src_ratio=(double)dst_info.samplerate/(double)src_info.samplerate;
|
|
src_data.data_in=pcm[0];
|
|
src_data.data_out=pcm[1];
|
|
src_data.output_frames=STAGE2_XFER_SIZE*dst_info.samplerate/
|
|
src_info.samplerate+src_info.channels;
|
|
}
|
|
|
|
//
|
|
// Initialize Speed Converter
|
|
//
|
|
if(conv_speed_ratio!=1.0) {
|
|
st_conv=new soundtouch::SoundTouch();
|
|
st_conv->setTempo(conv_speed_ratio);
|
|
st_conv->setSampleRate(dst_info.samplerate);
|
|
st_conv->setChannels(dst_info.channels);
|
|
}
|
|
|
|
//
|
|
// Calculate Gain Ratio
|
|
//
|
|
if(conv_settings->normalizationLevel()!=0) {
|
|
float gain=
|
|
(float)conv_settings->normalizationLevel()-20.0*log10f(conv_peak_sample);
|
|
ratio=exp10f(gain/20.0);
|
|
}
|
|
|
|
//
|
|
// Convert
|
|
//
|
|
while((n=sf_readf_float(src_sf,pcm[0],STAGE2_XFER_SIZE))>0) {
|
|
|
|
//
|
|
// Levels
|
|
//
|
|
if(ratio!=1.0) {
|
|
for(unsigned i=0;i<(n*src_info.channels);i++) {
|
|
pcm[0][i]=ratio*pcm[0][i];
|
|
}
|
|
}
|
|
|
|
//
|
|
// Sample Rate
|
|
//
|
|
if(src_state!=NULL) {
|
|
src_data.input_frames=n;
|
|
if((err=src_process(src_state,&src_data))!=0) {
|
|
fprintf(stderr,"SRC Error: %s\n",src_strerror(err));
|
|
rda->syslog(LOG_WARNING,"%s",src_strerror(err));
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
n=src_data.output_frames_gen;
|
|
}
|
|
|
|
//
|
|
// Channelization
|
|
//
|
|
switch(src_info.channels) {
|
|
case 1:
|
|
switch(dst_info.channels) {
|
|
case 1: // Nothing to do
|
|
break;
|
|
|
|
case 2:
|
|
for(unsigned i=0;i<n;i++) {
|
|
pcm[2][2*i]=pcm[1][i];
|
|
pcm[2][2*i+1]=pcm[1][i];
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
switch(dst_info.channels) {
|
|
case 1:
|
|
for(unsigned i=0;i<n;i++) {
|
|
pcm[2][i]=(pcm[1][2*i]+pcm[1][2*i+1])/2;
|
|
}
|
|
break;
|
|
|
|
case 2: // Nothing to do
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Speed
|
|
//
|
|
if(st_conv!=NULL) {
|
|
st_conv->putSamples((soundtouch::SAMPLETYPE *)pcm[2],n);
|
|
n=st_conv->receiveSamples((soundtouch::SAMPLETYPE *)pcm[2],STAGE2_BUFFER_SIZE/dst_info.channels);
|
|
}
|
|
|
|
//
|
|
// Write Output
|
|
//
|
|
if(sf_writef_float(dst_sf,pcm[2],n)!=n) {
|
|
for(unsigned i=0;i<3;i++) {
|
|
if(free_pcm[i]) {
|
|
delete pcm[i];
|
|
}
|
|
}
|
|
if(src_state!=NULL) {
|
|
src_delete(src_state);
|
|
}
|
|
sf_close(src_sf);
|
|
sf_close(dst_sf);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
|
|
//
|
|
// Finish Up Speed Conversion
|
|
//
|
|
if(st_conv!=NULL) {
|
|
st_conv->flush();
|
|
while((n=st_conv->
|
|
receiveSamples((soundtouch::SAMPLETYPE *)pcm[2],
|
|
STAGE2_BUFFER_SIZE/dst_info.channels))>0) {
|
|
if(sf_writef_float(dst_sf,pcm[2],n)!=n) {
|
|
for(unsigned i=0;i<3;i++) {
|
|
if(free_pcm[i]) {
|
|
delete pcm[i];
|
|
}
|
|
}
|
|
if(src_state!=NULL) {
|
|
src_delete(src_state);
|
|
}
|
|
sf_close(src_sf);
|
|
sf_close(dst_sf);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
delete st_conv;
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
for(unsigned i=0;i<3;i++) {
|
|
if(free_pcm[i]) {
|
|
delete pcm[i];
|
|
}
|
|
}
|
|
if(src_state!=NULL) {
|
|
src_delete(src_state);
|
|
}
|
|
sf_close(src_sf);
|
|
sf_close(dst_sf);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Convert(const QString &srcfile,
|
|
const QString &dstfile)
|
|
{
|
|
SNDFILE *src_sf=NULL;
|
|
SF_INFO src_sf_info;
|
|
RDAudioConvert::ErrorCode ret;
|
|
|
|
//
|
|
// Open Source File
|
|
//
|
|
if((src_sf=sf_open(srcfile,SFM_READ,&src_sf_info))==NULL) {
|
|
rda->syslog(LOG_WARNING,"%s",sf_strerror(NULL));
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
|
|
switch(conv_settings->format()) {
|
|
case RDSettings::Pcm16:
|
|
ret=Stage3Pcm16(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::Pcm24:
|
|
ret=Stage3Pcm24(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::MpegL2:
|
|
ret=Stage3Layer2(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::MpegL2Wav:
|
|
ret=Stage3Layer2Wav(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::MpegL3:
|
|
ret=Stage3Layer3(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::Flac:
|
|
ret=Stage3Flac(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::OggVorbis:
|
|
ret=Stage3Vorbis(src_sf,&src_sf_info,dstfile);
|
|
break;
|
|
|
|
case RDSettings::MpegL1:
|
|
default:
|
|
ret=RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
sf_close(src_sf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Flac(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
#ifdef HAVE_FLAC
|
|
sf_count_t n;
|
|
int32_t *pcm;
|
|
|
|
//
|
|
// Initialize Encoder
|
|
//
|
|
FLAC::Encoder::File *flac=new FLAC::Encoder::File();
|
|
flac->set_channels(src_sf_info->channels);
|
|
flac->set_bits_per_sample(16); // FIXME: Should vary by input file
|
|
flac->set_sample_rate(src_sf_info->samplerate);
|
|
//flac->set_compression_level(8);
|
|
flac->set_blocksize(0);
|
|
unlink(dstfile);
|
|
/*
|
|
* FLAC <1.2.x
|
|
*
|
|
flac->set_filename(dstfile.ascii());
|
|
switch(flac->init()) {
|
|
case 0:
|
|
break;
|
|
|
|
default:
|
|
delete flac;
|
|
rda->log(RDConfig::LogWarning,QString("flac->init() failure"));
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
*/
|
|
/*
|
|
* FLAC 1.2.x
|
|
*/
|
|
switch(flac->init(dstfile.ascii())) {
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_OK:
|
|
break;
|
|
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_NUMBER_OF_CHANNELS:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BITS_PER_SAMPLE:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE:
|
|
delete flac;
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_ENCODER_ERROR:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_UNSUPPORTED_CONTAINER:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_MAX_LPC_ORDER:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_QLP_COEFF_PRECISION:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_BLOCK_SIZE_TOO_SMALL_FOR_LPC_ORDER:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE:
|
|
case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_METADATA:
|
|
default:
|
|
delete flac;
|
|
rda->syslog(LOG_WARNING,"flac->init() failure");
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
|
|
pcm=new int32_t[2048*src_sf_info->channels];
|
|
|
|
//
|
|
// Encode
|
|
//
|
|
while((n=sf_readf_int(src_sf,pcm,2048))>0) {
|
|
for(unsigned i=0;i<(n*src_sf_info->channels);i++) {
|
|
pcm[i]=pcm[i]>>16;
|
|
}
|
|
flac->process_interleaved(pcm,n);
|
|
}
|
|
flac->finish();
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
delete pcm;
|
|
delete flac;
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_FLAC
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Vorbis(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
#ifdef HAVE_VORBIS
|
|
ogg_stream_state ogg_stream;
|
|
ogg_page ogg_page;
|
|
ogg_packet header;
|
|
ogg_packet comment;
|
|
ogg_packet codebook;
|
|
ogg_packet ogg_packet;
|
|
vorbis_info vorbis_info;
|
|
vorbis_comment vorbis_comment;
|
|
vorbis_dsp_state vorbis_dsp;
|
|
vorbis_block vorbis_block;
|
|
float *pcm=NULL;
|
|
float **vorbis;
|
|
sf_count_t n;
|
|
int dst_fd=-1;
|
|
|
|
//
|
|
// Open Destination File
|
|
//
|
|
unlink(dstfile);
|
|
if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Initialize the Encoder
|
|
//
|
|
vorbis_info_init(&vorbis_info);
|
|
switch(vorbis_encode_init_vbr(&vorbis_info,src_sf_info->channels,
|
|
src_sf_info->samplerate,
|
|
conv_settings->quality())) {
|
|
case OV_EFAULT:
|
|
default:
|
|
rda->syslog(LOG_WARNING,"vorbis_encode_init_vbr() failure");
|
|
return RDAudioConvert::ErrorInternal;
|
|
|
|
case OV_EINVAL:
|
|
case OV_EIMPL:
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
|
|
case 0:
|
|
break;
|
|
}
|
|
vorbis_comment_init(&vorbis_comment);
|
|
// Metadata stuff goes here...
|
|
vorbis_analysis_init(&vorbis_dsp,&vorbis_info);
|
|
vorbis_block_init(&vorbis_dsp,&vorbis_block);
|
|
vorbis_analysis_headerout(&vorbis_dsp,&vorbis_comment,
|
|
&header,&comment,&codebook);
|
|
ogg_stream_init(&ogg_stream,rand());
|
|
ogg_stream_packetin(&ogg_stream,&header);
|
|
ogg_stream_packetin(&ogg_stream,&comment);
|
|
ogg_stream_packetin(&ogg_stream,&codebook);
|
|
pcm=new float[2048*src_sf_info->channels];
|
|
|
|
//
|
|
// Encode
|
|
//
|
|
while((n=sf_readf_float(src_sf,pcm,2048))>0) {
|
|
vorbis=vorbis_analysis_buffer(&vorbis_dsp,n);
|
|
for(unsigned i=0;i<n;i++) {
|
|
for(int j=0;j<src_sf_info->channels;j++) {
|
|
vorbis[j][i]=pcm[src_sf_info->channels*i+j];
|
|
}
|
|
}
|
|
vorbis_analysis_wrote(&vorbis_dsp,n);
|
|
while(vorbis_analysis_blockout(&vorbis_dsp,&vorbis_block)>0) {
|
|
vorbis_analysis(&vorbis_block,&ogg_packet);
|
|
ogg_stream_packetin(&ogg_stream,&ogg_packet);
|
|
while(ogg_stream_pageout(&ogg_stream,&ogg_page)!=0) {
|
|
if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
|
|
ogg_page.header_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
|
|
ogg_page.body_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
}
|
|
while(ogg_stream_flush(&ogg_stream,&ogg_page)!=0) {
|
|
if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
|
|
ogg_page.header_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
|
|
ogg_page.body_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
vorbis=vorbis_analysis_buffer(&vorbis_dsp,0);
|
|
vorbis_analysis_wrote(&vorbis_dsp,0);
|
|
while(vorbis_analysis_blockout(&vorbis_dsp,&vorbis_block)>0) {
|
|
vorbis_analysis(&vorbis_block,&ogg_packet);
|
|
ogg_stream_packetin(&ogg_stream,&ogg_packet);
|
|
while(ogg_stream_pageout(&ogg_stream,&ogg_page)!=0) {
|
|
if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
|
|
ogg_page.header_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
|
|
ogg_page.body_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
}
|
|
while(ogg_stream_flush(&ogg_stream,&ogg_page)!=0) {
|
|
if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
|
|
ogg_page.header_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
|
|
ogg_page.body_len) {
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
::close(dst_fd);
|
|
delete pcm;
|
|
ogg_stream_clear(&ogg_stream);
|
|
vorbis_comment_clear(&vorbis_comment);
|
|
vorbis_info_clear(&vorbis_info);
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_VORBIS
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer3(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
#ifdef HAVE_LAME
|
|
MPEG_mode mpeg_mode=STEREO;
|
|
lame_global_flags *lameopts=NULL;
|
|
int dst_fd=-1;
|
|
int16_t pcm[2304];
|
|
unsigned char mpeg[2048];
|
|
sf_count_t n;
|
|
sf_count_t s;
|
|
|
|
//
|
|
// Load LAME
|
|
//
|
|
if(!LoadLame()) {
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
}
|
|
|
|
//
|
|
// Determine MPEG Mode
|
|
//
|
|
switch(src_sf_info->channels) {
|
|
case 1:
|
|
mpeg_mode=MONO;
|
|
break;
|
|
|
|
case 2:
|
|
mpeg_mode=STEREO;
|
|
break;
|
|
|
|
default:
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Open Destination File
|
|
//
|
|
unlink(dstfile);
|
|
if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Initialize Encoder
|
|
//
|
|
if((lameopts=lame_init())==NULL) {
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
rda->syslog(LOG_WARNING,"lame_init() failure");
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
lame_set_mode(lameopts,mpeg_mode);
|
|
lame_set_num_channels(lameopts,src_sf_info->channels);
|
|
lame_set_in_samplerate(lameopts,src_sf_info->samplerate);
|
|
lame_set_out_samplerate(lameopts,src_sf_info->samplerate);
|
|
lame_set_brate(lameopts,conv_settings->bitRate()/1000);
|
|
lame_set_bWriteVbrTag(lameopts,0);
|
|
if(lame_init_params(lameopts)!=0) {
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Encode
|
|
//
|
|
if(src_sf_info->channels==2) {
|
|
while((n=sf_readf_short(src_sf,pcm,1152))>0) {
|
|
if((s=lame_encode_buffer_interleaved(lameopts,pcm,n,mpeg,2048))>=0) {
|
|
if(write(dst_fd,mpeg,s)!=s) {
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
}
|
|
else {
|
|
while((n=sf_readf_short(src_sf,pcm,1152))>0) {
|
|
if((s=lame_encode_buffer(lameopts,pcm,NULL,n,mpeg,2048))>=0) {
|
|
if(write(dst_fd,mpeg,s)!=s) {
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
}
|
|
}
|
|
if((s=lame_encode_flush(lameopts,mpeg,2048))>=0) {
|
|
if(write(dst_fd,mpeg,s)!=s) {
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
lame_close(lameopts);
|
|
::close(dst_fd);
|
|
|
|
//
|
|
// Apply Metadata
|
|
//
|
|
if(conv_dst_wavedata!=NULL) {
|
|
ApplyId3Tag(dstfile,conv_dst_wavedata);
|
|
}
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_LAME
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer2Wav(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
#ifdef HAVE_TWOLAME
|
|
sf_count_t n;
|
|
ssize_t s;
|
|
RDWaveFile *wave=NULL;
|
|
TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;
|
|
twolame_options *lameopts=NULL;
|
|
float pcm[2304];
|
|
unsigned char mpeg[2048];
|
|
|
|
//
|
|
// Load TwoLAME
|
|
//
|
|
if(!LoadTwoLame()) {
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
}
|
|
|
|
//
|
|
// Determine MPEG Mode
|
|
//
|
|
switch(src_sf_info->channels) {
|
|
case 1:
|
|
mpeg_mode=TWOLAME_MONO;
|
|
break;
|
|
|
|
case 2:
|
|
mpeg_mode=TWOLAME_STEREO;
|
|
break;
|
|
|
|
default:
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Open Destination File
|
|
//
|
|
wave=new RDWaveFile(dstfile);
|
|
wave->setFormatTag(WAVE_FORMAT_MPEG);
|
|
wave->setChannels(src_sf_info->channels);
|
|
switch(src_sf_info->channels) {
|
|
case 1:
|
|
wave->setHeadMode(ACM_MPEG_SINGLECHANNEL);
|
|
break;
|
|
|
|
case 2:
|
|
wave->setHeadMode(ACM_MPEG_STEREO);
|
|
break;
|
|
}
|
|
wave->setSamplesPerSec(src_sf_info->samplerate);
|
|
wave->setHeadLayer(2);
|
|
wave->setHeadBitRate(conv_settings->bitRate());
|
|
wave->setBextChunk(true);
|
|
wave->setMextChunk(true);
|
|
wave->setCartChunk(conv_dst_wavedata!=NULL);
|
|
wave->setLevlChunk(true);
|
|
wave->setRdxlContents(conv_dst_rdxl);
|
|
unlink(dstfile);
|
|
if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Initialize Encoder
|
|
//
|
|
if((lameopts=twolame_init())==NULL) {
|
|
wave->closeWave();
|
|
rda->syslog(LOG_WARNING,"twolame_init() failure");
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
twolame_set_mode(lameopts,mpeg_mode);
|
|
twolame_set_num_channels(lameopts,src_sf_info->channels);
|
|
twolame_set_in_samplerate(lameopts,src_sf_info->samplerate);
|
|
twolame_set_out_samplerate(lameopts,src_sf_info->samplerate);
|
|
twolame_set_bitrate(lameopts,conv_settings->bitRate()/1000);
|
|
twolame_set_energy_levels(lameopts,1);
|
|
if(twolame_init_params(lameopts)!=0) {
|
|
twolame_close(&lameopts);
|
|
wave->closeWave();
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Encode
|
|
//
|
|
while((n=sf_readf_float(src_sf,pcm,1152))>0) {
|
|
if((s=twolame_encode_buffer_float32_interleaved(lameopts,
|
|
pcm,n,mpeg,2048))>=0) {
|
|
if(wave->writeWave(mpeg,s)!=s) {
|
|
twolame_close(&lameopts);
|
|
wave->closeWave(src_sf_info->frames);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr,"TwoLAME encode error\n");
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
if((s=twolame_encode_flush(lameopts,mpeg,2048))>=0) {
|
|
if(wave->writeWave(mpeg,s)!=s) {
|
|
twolame_close(&lameopts);
|
|
wave->closeWave(src_sf_info->frames);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr,"TwoLAME encode error\n");
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
twolame_close(&lameopts);
|
|
wave->closeWave(src_sf_info->frames);
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_TWOLAME
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer2(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
#ifdef HAVE_TWOLAME
|
|
sf_count_t n;
|
|
ssize_t s;
|
|
int dst_fd=-1;
|
|
TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;
|
|
twolame_options *lameopts=NULL;
|
|
float pcm[2304];
|
|
unsigned char mpeg[2048];
|
|
|
|
if(!LoadTwoLame()) {
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
}
|
|
if((conv_settings->bitRate()>192000)&&(src_sf_info->channels<2)) {
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Determine MPEG Mode
|
|
//
|
|
switch(src_sf_info->channels) {
|
|
case 1:
|
|
mpeg_mode=TWOLAME_MONO;
|
|
break;
|
|
|
|
case 2:
|
|
mpeg_mode=TWOLAME_STEREO;
|
|
break;
|
|
|
|
default:
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Open Destination File
|
|
//
|
|
unlink(dstfile);
|
|
if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
|
|
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
|
|
//
|
|
// Initialize Encoder
|
|
//
|
|
if((lameopts=twolame_init())==NULL) {
|
|
::close(dst_fd);
|
|
rda->syslog(LOG_WARNING,"twolame_init() failure");
|
|
return RDAudioConvert::ErrorInternal;
|
|
}
|
|
twolame_set_mode(lameopts,mpeg_mode);
|
|
twolame_set_num_channels(lameopts,src_sf_info->channels);
|
|
twolame_set_in_samplerate(lameopts,src_sf_info->samplerate);
|
|
twolame_set_out_samplerate(lameopts,src_sf_info->samplerate);
|
|
twolame_set_bitrate(lameopts,conv_settings->bitRate()/1000);
|
|
if(twolame_init_params(lameopts)!=0) {
|
|
twolame_close(&lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorInvalidSettings;
|
|
}
|
|
|
|
//
|
|
// Encode
|
|
//
|
|
while((n=sf_readf_float(src_sf,pcm,1152))>0) {
|
|
if((s=twolame_encode_buffer_float32_interleaved(lameopts,
|
|
pcm,n,mpeg,2048))>=0) {
|
|
if(write(dst_fd,mpeg,s)!=s) {
|
|
twolame_close(&lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr,"TwoLAME encode error\n");
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
if((s=twolame_encode_flush(lameopts,mpeg,2048))>=0) {
|
|
if(write(dst_fd,mpeg,s)!=s) {
|
|
twolame_close(&lameopts);
|
|
::close(dst_fd);
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr,"TwoLAME encode error\n");
|
|
}
|
|
|
|
//
|
|
// Clean Up
|
|
//
|
|
twolame_close(&lameopts);
|
|
::close(dst_fd);
|
|
|
|
//
|
|
// Apply Metadata
|
|
//
|
|
if(conv_dst_wavedata!=NULL) {
|
|
ApplyId3Tag(dstfile,conv_dst_wavedata);
|
|
}
|
|
|
|
return RDAudioConvert::ErrorOk;
|
|
#else
|
|
return RDAudioConvert::ErrorFormatNotSupported;
|
|
#endif // HAVE_TWOLAME
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Pcm16(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
short *sf_buffer=NULL;
|
|
ssize_t n;
|
|
|
|
RDWaveFile *wave=new RDWaveFile(dstfile);
|
|
wave->setFormatTag(WAVE_FORMAT_PCM);
|
|
wave->setChannels(src_sf_info->channels);
|
|
wave->setSamplesPerSec(src_sf_info->samplerate);
|
|
wave->setBitsPerSample(16);
|
|
wave->setBextChunk(true);
|
|
wave->setCartChunk(conv_dst_wavedata!=NULL);
|
|
wave->setRdxlContents(conv_dst_rdxl);
|
|
if((conv_dst_wavedata!=NULL)&&(conv_settings->normalizationLevel()!=0)) {
|
|
wave->setCartLevelRef(32768*
|
|
exp10((double)conv_settings->normalizationLevel()/20.0));
|
|
}
|
|
wave->setLevlChunk(true);
|
|
sf_buffer=new int16_t[2048*src_sf_info->channels];
|
|
unlink(dstfile);
|
|
if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
while((n=sf_readf_short(src_sf,sf_buffer,2048))>0) {
|
|
if((unsigned)wave->
|
|
writeWave(sf_buffer,n*sizeof(short)*src_sf_info->channels)!=
|
|
(n*sizeof(short)*src_sf_info->channels)) {
|
|
delete sf_buffer;
|
|
wave->closeWave();
|
|
delete wave;
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
delete sf_buffer;
|
|
wave->closeWave();
|
|
delete wave;
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
|
|
RDAudioConvert::ErrorCode RDAudioConvert::Stage3Pcm24(SNDFILE *src_sf,
|
|
SF_INFO *src_sf_info,
|
|
const QString &dstfile)
|
|
{
|
|
int *sf_buffer=NULL;
|
|
uint8_t *pcm24=NULL;
|
|
ssize_t n;
|
|
|
|
RDWaveFile *wave=new RDWaveFile(dstfile);
|
|
wave->setFormatTag(WAVE_FORMAT_PCM);
|
|
wave->setChannels(src_sf_info->channels);
|
|
wave->setSamplesPerSec(src_sf_info->samplerate);
|
|
wave->setBitsPerSample(24);
|
|
wave->setBextChunk(true);
|
|
wave->setCartChunk(conv_dst_wavedata!=NULL);
|
|
wave->setRdxlContents(conv_dst_rdxl);
|
|
if((conv_dst_wavedata!=NULL)&&(conv_settings->normalizationLevel()!=0)) {
|
|
wave->setCartLevelRef(32768*
|
|
exp10((double)conv_settings->normalizationLevel()/20.0));
|
|
}
|
|
wave->setLevlChunk(true);
|
|
sf_buffer=new int[2048*src_sf_info->channels];
|
|
pcm24=new uint8_t[2048*src_sf_info->channels*sizeof(int)];
|
|
unlink(dstfile);
|
|
if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
|
|
return RDAudioConvert::ErrorNoDestination;
|
|
}
|
|
while((n=sf_readf_int(src_sf,sf_buffer,2048))>0) {
|
|
for(ssize_t i=0;i<(n*src_sf_info->channels);i++) {
|
|
pcm24[3*i]=0xFF&(sf_buffer[i]>>8);
|
|
pcm24[3*i+1]=0xFF&(sf_buffer[i]>>16);
|
|
pcm24[3*i+2]=0xFF&(sf_buffer[i]>>24);
|
|
}
|
|
if((unsigned)wave->writeWave(pcm24,n*3*src_sf_info->channels)!=
|
|
(n*3*src_sf_info->channels)) {
|
|
delete sf_buffer;
|
|
delete pcm24;
|
|
wave->closeWave();
|
|
delete wave;
|
|
return RDAudioConvert::ErrorNoSpace;
|
|
}
|
|
usleep(conv_transcoding_delay);
|
|
}
|
|
delete sf_buffer;
|
|
delete pcm24;
|
|
wave->closeWave();
|
|
delete wave;
|
|
return RDAudioConvert::ErrorOk;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::ApplyId3Tag(const QString &filename,RDWaveData *wavedata)
|
|
{
|
|
TagLib::MPEG::File *file=new TagLib::MPEG::File(filename.toUtf8(),false);
|
|
TagLib::PropertyMap *map=new TagLib::PropertyMap();
|
|
TagLib::ID3v2::Tag *tag=file->ID3v2Tag();
|
|
|
|
AddId3Property(map,"TITLE",wavedata->title());
|
|
if(!wavedata->artist().isEmpty()) {
|
|
AddId3Property(map,"ARTIST",wavedata->artist());
|
|
}
|
|
if(!wavedata->album().isEmpty()) {
|
|
AddId3Property(map,"ALBUM",wavedata->album());
|
|
}
|
|
if(!wavedata->label().isEmpty()) {
|
|
AddId3Property(map,"LABEL",wavedata->label());
|
|
}
|
|
if(!wavedata->conductor().isEmpty()) {
|
|
AddId3Property(map,"CONDUCTOR",wavedata->conductor());
|
|
}
|
|
if(!wavedata->composer().isEmpty()) {
|
|
AddId3Property(map,"COMPOSER",wavedata->composer());
|
|
}
|
|
if(!wavedata->publisher().isEmpty()) {
|
|
AddId3Property(map,"PUBLISHER",wavedata->publisher());
|
|
}
|
|
if(!wavedata->copyrightNotice().isEmpty()) {
|
|
AddId3Property(map,"COPYRIGHT",wavedata->copyrightNotice());
|
|
}
|
|
if(!wavedata->isrc().isEmpty()) {
|
|
AddId3Property(map,"ISRC",wavedata->isrc());
|
|
}
|
|
if(wavedata->releaseYear()>0) {
|
|
AddId3Property(map,"YEAR",QString().sprintf("%d",wavedata->releaseYear()));
|
|
}
|
|
if(wavedata->beatsPerMinute()>0) {
|
|
AddId3Property(map,"BPM",
|
|
QString().sprintf("%d",wavedata->beatsPerMinute()));
|
|
}
|
|
tag->setProperties(*map);
|
|
|
|
RDCart *cart=new RDCart(wavedata->cartNumber());
|
|
if(cart->exists()) {
|
|
QString xml=
|
|
cart->xml(true,conv_start_point<0,conv_settings,wavedata->cutNumber());
|
|
TagLib::ID3v2::UserTextIdentificationFrame *frame=
|
|
new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
|
|
frame->setDescription("rdxl");
|
|
frame->setText(TagLib::String((const char *)xml.toUtf8(),
|
|
TagLib::String::UTF8));
|
|
tag->addFrame(frame);
|
|
}
|
|
delete cart;
|
|
|
|
file->save();
|
|
delete map;
|
|
delete file;
|
|
}
|
|
|
|
|
|
void RDAudioConvert::AddId3Property(TagLib::PropertyMap *map,const QString &key,
|
|
const QString &value) const
|
|
{
|
|
TagLib::StringList args;
|
|
|
|
args.
|
|
append(TagLib::String((const char *)value.toUtf8(),TagLib::String::UTF8));
|
|
map->insert((const char *)key.toUtf8(),args);
|
|
}
|
|
|
|
|
|
void RDAudioConvert::UpdatePeak(const float data[],ssize_t len)
|
|
{
|
|
float peak;
|
|
|
|
for(ssize_t i=0;i<len;i++) {
|
|
if((peak=fabsf(data[i]))>conv_peak_sample) {
|
|
conv_peak_sample=peak;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RDAudioConvert::UpdatePeak(const double data[],ssize_t len)
|
|
{
|
|
float peak;
|
|
|
|
for(ssize_t i=0;i<len;i++) {
|
|
if((peak=(float)fabsf(data[i]))>conv_peak_sample) {
|
|
conv_peak_sample=peak;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool RDAudioConvert::LoadMad()
|
|
{
|
|
#ifdef HAVE_MAD
|
|
if(conv_mad_handle==NULL) {
|
|
return false;
|
|
}
|
|
*(void **)(&mad_stream_init)=
|
|
dlsym(conv_mad_handle,"mad_stream_init");
|
|
*(void **)(&mad_frame_init)=
|
|
dlsym(conv_mad_handle,"mad_frame_init");
|
|
*(void **)(&mad_synth_init)=
|
|
dlsym(conv_mad_handle,"mad_synth_init");
|
|
*(void **)(&mad_stream_buffer)=
|
|
dlsym(conv_mad_handle,"mad_stream_buffer");
|
|
*(void **)(&mad_frame_decode)=
|
|
dlsym(conv_mad_handle,"mad_frame_decode");
|
|
*(void **)(&mad_synth_frame)=
|
|
dlsym(conv_mad_handle,"mad_synth_frame");
|
|
*(void **)(&mad_frame_finish)=
|
|
dlsym(conv_mad_handle,"mad_frame_finish");
|
|
*(void **)(&mad_stream_finish)=
|
|
dlsym(conv_mad_handle,"mad_stream_finish");
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif // HAVE_MAD
|
|
}
|
|
|
|
|
|
bool RDAudioConvert::LoadTwoLame()
|
|
{
|
|
#ifdef HAVE_TWOLAME
|
|
if(conv_twolame_handle==NULL) {
|
|
return false;
|
|
}
|
|
*(void **)(&twolame_init)=dlsym(conv_twolame_handle,"twolame_init");
|
|
*(void **)(&twolame_set_mode)=dlsym(conv_twolame_handle,"twolame_set_mode");
|
|
*(void **)(&twolame_set_num_channels)=
|
|
dlsym(conv_twolame_handle,"twolame_set_num_channels");
|
|
*(void **)(&twolame_set_in_samplerate)=
|
|
dlsym(conv_twolame_handle,"twolame_set_in_samplerate");
|
|
*(void **)(&twolame_set_out_samplerate)=
|
|
dlsym(conv_twolame_handle,"twolame_set_out_samplerate");
|
|
*(void **)(&twolame_set_bitrate)=
|
|
dlsym(conv_twolame_handle,"twolame_set_bitrate");
|
|
*(void **)(&twolame_init_params)=
|
|
dlsym(conv_twolame_handle,"twolame_init_params");
|
|
*(void **)(&twolame_close)=dlsym(conv_twolame_handle,"twolame_close");
|
|
*(void **)(&twolame_encode_buffer_float32_interleaved)=
|
|
dlsym(conv_twolame_handle,"twolame_encode_buffer_float32_interleaved");
|
|
*(void **)(&twolame_encode_flush)=
|
|
dlsym(conv_twolame_handle,"twolame_encode_flush");
|
|
*(void **)(&twolame_set_energy_levels)=
|
|
dlsym(conv_twolame_handle,"twolame_set_energy_levels");
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif // HAVE_TWOLAME
|
|
}
|
|
|
|
|
|
bool RDAudioConvert::LoadLame()
|
|
{
|
|
#ifdef HAVE_LAME
|
|
if(conv_lame_handle==NULL) {
|
|
return false;
|
|
}
|
|
*(void **)(&lame_init)=dlsym(conv_lame_handle,"lame_init");
|
|
*(void **)(&lame_set_mode)=
|
|
dlsym(conv_lame_handle,"lame_set_mode");
|
|
*(void **)(&lame_set_num_channels)=
|
|
dlsym(conv_lame_handle,"lame_set_num_channels");
|
|
*(void **)(&lame_set_in_samplerate)=
|
|
dlsym(conv_lame_handle,"lame_set_in_samplerate");
|
|
*(void **)(&lame_set_out_samplerate)=
|
|
dlsym(conv_lame_handle,"lame_set_out_samplerate");
|
|
*(void **)(&lame_set_brate)=dlsym(conv_lame_handle,"lame_set_brate");
|
|
*(void **)(&lame_init_params)=dlsym(conv_lame_handle,"lame_init_params");
|
|
*(void **)(&lame_close)=dlsym(conv_lame_handle,"lame_close");
|
|
*(void **)(&lame_encode_buffer_interleaved)=
|
|
dlsym(conv_lame_handle,"lame_encode_buffer_interleaved");
|
|
*(void **)(&lame_encode_buffer)=
|
|
dlsym(conv_lame_handle,"lame_encode_buffer");
|
|
*(void **)(&lame_encode_flush)=dlsym(conv_lame_handle,"lame_encode_flush");
|
|
*(void **)(&lame_set_bWriteVbrTag)=
|
|
dlsym(conv_lame_handle,"lame_set_bWriteVbrTag");
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif // HAVE_LAME
|
|
}
|