415 lines
12 KiB
C++
415 lines
12 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
ExportFLAC.cpp
|
|
|
|
Frederik M.J.V
|
|
|
|
This program is distributed under the GNU General Public License, version 2.
|
|
A copy of this license is included with this source.
|
|
|
|
Based on ExportOGG.cpp by:
|
|
Joshua Haberman
|
|
|
|
Portions from vorbis-tools, copyright 2000-2002 Michael Smith
|
|
<msmith@labyrinth.net.au>; Vorbize, Kenneth Arnold <kcarnold@yahoo.com>;
|
|
and libvorbis examples, Monty <monty@xiph.org>
|
|
|
|
**********************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
|
|
#ifdef USE_LIBFLAC
|
|
|
|
#include "ExportFLAC.h"
|
|
#include "Export.h"
|
|
|
|
#include <wx/progdlg.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/log.h>
|
|
#include <wx/msgdlg.h>
|
|
|
|
#include "FLAC++/encoder.h"
|
|
|
|
#include "../float_cast.h"
|
|
#include "../Project.h"
|
|
#include "../Mix.h"
|
|
#include "../Prefs.h"
|
|
#include "../ShuttleGui.h"
|
|
|
|
#include "../Internat.h"
|
|
#include "../Tags.h"
|
|
|
|
#include "../Track.h"
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportFLACOptions Class
|
|
//----------------------------------------------------------------------------
|
|
|
|
class ExportFLACOptions : public wxPanel
|
|
{
|
|
public:
|
|
|
|
ExportFLACOptions(wxWindow *parent, int format);
|
|
virtual ~ExportFLACOptions();
|
|
|
|
void PopulateOrExchange(ShuttleGui & S);
|
|
bool TransferDataToWindow();
|
|
bool TransferDataFromWindow();
|
|
};
|
|
|
|
///
|
|
///
|
|
ExportFLACOptions::ExportFLACOptions(wxWindow *parent, int WXUNUSED(format))
|
|
: wxPanel(parent, wxID_ANY)
|
|
{
|
|
ShuttleGui S(this, eIsCreatingFromPrefs);
|
|
PopulateOrExchange(S);
|
|
|
|
TransferDataToWindow();
|
|
}
|
|
|
|
///
|
|
///
|
|
ExportFLACOptions::~ExportFLACOptions()
|
|
{
|
|
TransferDataFromWindow();
|
|
}
|
|
|
|
///
|
|
///
|
|
void ExportFLACOptions::PopulateOrExchange(ShuttleGui & S)
|
|
{
|
|
wxArrayString flacLevelNames, flacLevelLabels;
|
|
flacLevelLabels.Add(wxT("0")); flacLevelNames.Add(_("0 (fastest)"));
|
|
flacLevelLabels.Add(wxT("1")); flacLevelNames.Add(_("1"));
|
|
flacLevelLabels.Add(wxT("2")); flacLevelNames.Add(_("2"));
|
|
flacLevelLabels.Add(wxT("3")); flacLevelNames.Add(_("3"));
|
|
flacLevelLabels.Add(wxT("4")); flacLevelNames.Add(_("4"));
|
|
flacLevelLabels.Add(wxT("5")); flacLevelNames.Add(_("5"));
|
|
flacLevelLabels.Add(wxT("6")); flacLevelNames.Add(_("6"));
|
|
flacLevelLabels.Add(wxT("7")); flacLevelNames.Add(_("7"));
|
|
flacLevelLabels.Add(wxT("8")); flacLevelNames.Add(_("8 (best)"));
|
|
|
|
wxArrayString flacBitDepthNames, flacBitDepthLabels;
|
|
flacBitDepthLabels.Add(wxT("16")); flacBitDepthNames.Add(_("16 bit"));
|
|
flacBitDepthLabels.Add(wxT("24")); flacBitDepthNames.Add(_("24 bit"));
|
|
|
|
S.StartVerticalLay();
|
|
{
|
|
S.StartHorizontalLay(wxCENTER);
|
|
{
|
|
S.StartMultiColumn(2, wxCENTER);
|
|
{
|
|
S.TieChoice(_("Level:"), wxT("/FileFormats/FLACLevel"),
|
|
wxT("5"), flacLevelNames, flacLevelLabels);
|
|
S.TieChoice(_("Bit depth:"), wxT("/FileFormats/FLACBitDepth"),
|
|
wxT("16"), flacBitDepthNames, flacBitDepthLabels);
|
|
}
|
|
S.EndMultiColumn();
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
return;
|
|
}
|
|
|
|
///
|
|
///
|
|
bool ExportFLACOptions::TransferDataToWindow()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
///
|
|
///
|
|
bool ExportFLACOptions::TransferDataFromWindow()
|
|
{
|
|
ShuttleGui S(this, eIsSavingToPrefs);
|
|
PopulateOrExchange(S);
|
|
|
|
gPrefs->Flush();
|
|
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportFLAC Class
|
|
//----------------------------------------------------------------------------
|
|
|
|
#define SAMPLES_PER_RUN 8192
|
|
|
|
/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
|
|
#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
|
|
#define LEGACY_FLAC
|
|
#else
|
|
#undef LEGACY_FLAC
|
|
#endif
|
|
|
|
static struct
|
|
{
|
|
bool do_exhaustive_model_search;
|
|
bool do_escape_coding;
|
|
bool do_mid_side_stereo;
|
|
bool loose_mid_side_stereo;
|
|
unsigned qlp_coeff_precision;
|
|
unsigned min_residual_partition_order;
|
|
unsigned max_residual_partition_order;
|
|
unsigned rice_parameter_search_dist;
|
|
unsigned max_lpc_order;
|
|
} flacLevels[] = {
|
|
{ false, false, false, false, 0, 2, 2, 0, 0 },
|
|
{ false, false, true, true, 0, 2, 2, 0, 0 },
|
|
{ false, false, true, false, 0, 0, 3, 0, 0 },
|
|
{ false, false, false, false, 0, 3, 3, 0, 6 },
|
|
{ false, false, true, true, 0, 3, 3, 0, 8 },
|
|
{ false, false, true, false, 0, 3, 3, 0, 8 },
|
|
{ false, false, true, false, 0, 0, 4, 0, 8 },
|
|
{ true, false, true, false, 0, 0, 6, 0, 8 },
|
|
{ true, false, true, false, 0, 0, 6, 0, 12 },
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class ExportFLAC : public ExportPlugin
|
|
{
|
|
public:
|
|
|
|
ExportFLAC();
|
|
void Destroy();
|
|
|
|
// Required
|
|
|
|
wxWindow *OptionsCreate(wxWindow *parent, int format);
|
|
int Export(AudacityProject *project,
|
|
int channels,
|
|
const wxString &fName,
|
|
bool selectedOnly,
|
|
double t0,
|
|
double t1,
|
|
MixerSpec *mixerSpec = NULL,
|
|
Tags *metadata = NULL,
|
|
int subformat = 0) override;
|
|
|
|
private:
|
|
|
|
bool GetMetadata(AudacityProject *project, Tags *tags);
|
|
|
|
FLAC__StreamMetadata *mMetadata;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ExportFLAC::ExportFLAC()
|
|
: ExportPlugin()
|
|
{
|
|
AddFormat();
|
|
SetFormat(wxT("FLAC"),0);
|
|
AddExtension(wxT("flac"),0);
|
|
SetMaxChannels(FLAC__MAX_CHANNELS,0);
|
|
SetCanMetaData(true,0);
|
|
SetDescription(_("FLAC Files"),0);
|
|
}
|
|
|
|
void ExportFLAC::Destroy()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
int ExportFLAC::Export(AudacityProject *project,
|
|
int numChannels,
|
|
const wxString &fName,
|
|
bool selectionOnly,
|
|
double t0,
|
|
double t1,
|
|
MixerSpec *mixerSpec,
|
|
Tags *metadata,
|
|
int WXUNUSED(subformat))
|
|
{
|
|
double rate = project->GetRate();
|
|
TrackList *tracks = project->GetTracks();
|
|
|
|
wxLogNull logNo; // temporarily disable wxWidgets error messages
|
|
int updateResult = eProgressSuccess;
|
|
|
|
int levelPref;
|
|
gPrefs->Read(wxT("/FileFormats/FLACLevel"), &levelPref, 5);
|
|
|
|
wxString bitDepthPref =
|
|
gPrefs->Read(wxT("/FileFormats/FLACBitDepth"), wxT("16"));
|
|
|
|
FLAC::Encoder::File encoder;
|
|
|
|
#ifdef LEGACY_FLAC
|
|
encoder.set_filename(OSOUTPUT(fName));
|
|
#endif
|
|
encoder.set_channels(numChannels);
|
|
encoder.set_sample_rate(lrint(rate));
|
|
|
|
// See note in GetMetadata() about a bug in libflac++ 1.1.2
|
|
if (!GetMetadata(project, metadata)) {
|
|
return false;
|
|
}
|
|
|
|
if (mMetadata) {
|
|
encoder.set_metadata(&mMetadata, 1);
|
|
}
|
|
|
|
sampleFormat format;
|
|
if (bitDepthPref == wxT("24")) {
|
|
format = int24Sample;
|
|
encoder.set_bits_per_sample(24);
|
|
} else { //convert float to 16 bits
|
|
format = int16Sample;
|
|
encoder.set_bits_per_sample(16);
|
|
}
|
|
|
|
// Duplicate the flac command line compression levels
|
|
if (levelPref < 0 || levelPref > 8) {
|
|
levelPref = 5;
|
|
}
|
|
encoder.set_do_exhaustive_model_search(flacLevels[levelPref].do_exhaustive_model_search);
|
|
encoder.set_do_escape_coding(flacLevels[levelPref].do_escape_coding);
|
|
if (numChannels != 2) {
|
|
encoder.set_do_mid_side_stereo(false);
|
|
encoder.set_loose_mid_side_stereo(false);
|
|
}
|
|
else {
|
|
encoder.set_do_mid_side_stereo(flacLevels[levelPref].do_mid_side_stereo);
|
|
encoder.set_loose_mid_side_stereo(flacLevels[levelPref].loose_mid_side_stereo);
|
|
}
|
|
encoder.set_qlp_coeff_precision(flacLevels[levelPref].qlp_coeff_precision);
|
|
encoder.set_min_residual_partition_order(flacLevels[levelPref].min_residual_partition_order);
|
|
encoder.set_max_residual_partition_order(flacLevels[levelPref].max_residual_partition_order);
|
|
encoder.set_rice_parameter_search_dist(flacLevels[levelPref].rice_parameter_search_dist);
|
|
encoder.set_max_lpc_order(flacLevels[levelPref].max_lpc_order);
|
|
|
|
#ifdef LEGACY_FLAC
|
|
encoder.init();
|
|
#else
|
|
wxFFile f; // will be closed when it goes out of scope
|
|
if (!f.Open(fName, wxT("w+b"))) {
|
|
wxMessageBox(wxString::Format(_("FLAC export couldn't open %s"), fName.c_str()));
|
|
return false;
|
|
}
|
|
|
|
// Even though there is an init() method that takes a filename, use the one that
|
|
// takes a file handle because wxWidgets can open a file with a Unicode name and
|
|
// libflac can't (under Windows).
|
|
int status = encoder.init(f.fp());
|
|
if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
|
|
wxMessageBox(wxString::Format(_("FLAC encoder failed to initialize\nStatus: %d"), status));
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (mMetadata) {
|
|
::FLAC__metadata_object_delete(mMetadata);
|
|
}
|
|
|
|
int numWaveTracks;
|
|
WaveTrack **waveTracks;
|
|
tracks->GetWaveTracks(selectionOnly, &numWaveTracks, &waveTracks);
|
|
Mixer *mixer = CreateMixer(numWaveTracks, waveTracks,
|
|
tracks->GetTimeTrack(),
|
|
t0, t1,
|
|
numChannels, SAMPLES_PER_RUN, false,
|
|
rate, format, true, mixerSpec);
|
|
delete [] waveTracks;
|
|
|
|
int i, j;
|
|
FLAC__int32 **tmpsmplbuf = new FLAC__int32*[numChannels];
|
|
for (i = 0; i < numChannels; i++) {
|
|
tmpsmplbuf[i] = (FLAC__int32 *) calloc(SAMPLES_PER_RUN, sizeof(FLAC__int32));
|
|
}
|
|
|
|
{
|
|
ProgressDialog progress(wxFileName(fName).GetName(),
|
|
selectionOnly ?
|
|
_("Exporting the selected audio as FLAC") :
|
|
_("Exporting the entire project as FLAC"));
|
|
|
|
while (updateResult == eProgressSuccess) {
|
|
sampleCount samplesThisRun = mixer->Process(SAMPLES_PER_RUN);
|
|
if (samplesThisRun == 0) { //stop encoding
|
|
break;
|
|
}
|
|
else {
|
|
for (i = 0; i < numChannels; i++) {
|
|
samplePtr mixed = mixer->GetBuffer(i);
|
|
if (format == int24Sample) {
|
|
for (j = 0; j < samplesThisRun; j++) {
|
|
tmpsmplbuf[i][j] = ((int *)mixed)[j];
|
|
}
|
|
}
|
|
else {
|
|
for (j = 0; j < samplesThisRun; j++) {
|
|
tmpsmplbuf[i][j] = ((short *)mixed)[j];
|
|
}
|
|
}
|
|
}
|
|
encoder.process(tmpsmplbuf, samplesThisRun);
|
|
}
|
|
updateResult = progress.Update(mixer->MixGetCurrentTime() - t0, t1 - t0);
|
|
}
|
|
f.Detach(); // libflac closes the file
|
|
encoder.finish();
|
|
}
|
|
|
|
for (i = 0; i < numChannels; i++) {
|
|
free(tmpsmplbuf[i]);
|
|
}
|
|
delete mixer;
|
|
|
|
delete[] tmpsmplbuf;
|
|
|
|
return updateResult;
|
|
}
|
|
|
|
wxWindow *ExportFLAC::OptionsCreate(wxWindow *parent, int format)
|
|
{
|
|
wxASSERT(parent); // to justify safenew
|
|
return safenew ExportFLACOptions(parent, format);
|
|
}
|
|
|
|
// LL: There's a bug in libflac++ 1.1.2 that prevents us from using
|
|
// FLAC::Metadata::VorbisComment directly. The set_metadata()
|
|
// function allocates an array on the stack, but the base library
|
|
// expects that array to be valid until the stream is initialized.
|
|
//
|
|
// This has been fixed in 1.1.4.
|
|
bool ExportFLAC::GetMetadata(AudacityProject *project, Tags *tags)
|
|
{
|
|
// Retrieve tags if needed
|
|
if (tags == NULL)
|
|
tags = project->GetTags();
|
|
|
|
mMetadata = ::FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
|
|
|
wxString n;
|
|
for (const auto &pair : tags->GetRange()) {
|
|
n = pair.first;
|
|
const auto &v = pair.second;
|
|
if (n == TAG_YEAR) {
|
|
n = wxT("DATE");
|
|
}
|
|
FLAC::Metadata::VorbisComment::Entry entry(n.mb_str(wxConvUTF8),
|
|
v.mb_str(wxConvUTF8));
|
|
::FLAC__metadata_object_vorbiscomment_append_comment(mMetadata,
|
|
entry.get_entry(),
|
|
true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ExportPlugin *New_ExportFLAC()
|
|
{
|
|
return new ExportFLAC();
|
|
}
|
|
|
|
#endif // USE_LIBFLAC
|
|
|