1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 16:10:06 +02:00
audacity/src/export/ExportCL.cpp
mchinen a9a0d51454 Timetrack fixes and refactoring.
Possibly fixes:

Bug 206 - Time Tracks: Ruler warp goes in wrong direction
Bug 205 - Time Tracks that slow down the audio result in truncated exports
2010-10-07 23:01:49 +00:00

480 lines
12 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ExportCL.cpp
Joshua Haberman
This code allows Audacity to export data by piping it to an external
program. Although it could in theory be made to work on Windows
(and perhaps even Mac), it's not worth it to invest the effort because
commandline encoders are rarely installed there.
**********************************************************************/
#include "../Audacity.h"
#include <wx/button.h>
#include <wx/log.h>
#include <wx/process.h>
#include <wx/textctrl.h>
#include "Export.h"
#include "../Project.h"
#include "../Mix.h"
#include "../Prefs.h"
#include "../Internat.h"
#include "../float_cast.h"
//----------------------------------------------------------------------------
// ExportCLOptions
//----------------------------------------------------------------------------
class ExportCLOptions : public wxDialog
{
public:
ExportCLOptions(wxWindow *parent);
void PopulateOrExchange(ShuttleGui & S);
void OnOK(wxCommandEvent & event);
private:
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(ExportCLOptions, wxDialog)
EVT_BUTTON(wxID_OK, ExportCLOptions::OnOK)
END_EVENT_TABLE()
///
///
ExportCLOptions::ExportCLOptions(wxWindow *parent)
: wxDialog(parent, wxID_ANY,
wxString(_("Specify Command Line Encoder")))
{
ShuttleGui S(this, eIsCreatingFromPrefs);
PopulateOrExchange(S);
}
///
///
void ExportCLOptions::PopulateOrExchange(ShuttleGui & S)
{
S.StartHorizontalLay(wxEXPAND, 0);
{
S.StartStatic(_("Command Line Export Setup"), true);
{
S.StartMultiColumn(2, wxEXPAND);
{
S.SetStretchyCol(1);
S.TieTextBox(_("Command:"),
wxT("/FileFormats/ExternalProgramExportCommand"),
wxT("lame - \"%f\""),
64);
S.AddFixedText(wxT(""));
S.TieCheckBox(_("Show output"),
wxT("/FileFormats/ExternalProgramShowOutput"),
false);
}
S.EndMultiColumn();
S.AddFixedText(_("Data will be piped to standard in. \"%f\" uses the file name in the export window."));
}
S.EndStatic();
}
S.EndHorizontalLay();
S.AddStandardButtons();
Layout();
Fit();
SetMinSize(GetSize());
Center();
return;
}
///
///
void ExportCLOptions::OnOK(wxCommandEvent& event)
{
ShuttleGui S(this, eIsSavingToPrefs);
PopulateOrExchange(S);
EndModal(wxID_OK);
return;
}
//----------------------------------------------------------------------------
// ExportCLProcess
//----------------------------------------------------------------------------
static void Drain(wxInputStream *s, wxString *o)
{
while (s->CanRead()) {
char buffer[4096];
s->Read(buffer, WXSIZEOF(buffer) - 1);
buffer[s->LastRead()] = wxT('\0');
*o += LAT1CTOWX(buffer);
}
}
class ExportCLProcess : public wxProcess
{
public:
ExportCLProcess(wxString *output)
{
mOutput = output;
mActive = true;
mStatus = -555;
Redirect();
}
bool IsActive()
{
return mActive;
}
void OnTerminate(int WXUNUSED( pid ), int status)
{
Drain(GetInputStream(), mOutput);
Drain(GetErrorStream(), mOutput);
mStatus = status;
mActive = false;
}
int GetStatus()
{
return mStatus;
}
private:
wxString *mOutput;
bool mActive;
int mStatus;
};
//----------------------------------------------------------------------------
// ExportCL
//----------------------------------------------------------------------------
/* this structure combines the RIFF header, the format chunk, and the data
* chunk header */
struct wav_header {
/* RIFF header */
char riffID[4]; /* "RIFF" */
wxUint32 lenAfterRiff; /* basically the file len - 8, or samples len + 32 */
char riffType[4]; /* "WAVE" */
/* format chunk */
char fmtID[4]; /* "fmt " */
wxUint32 formatChunkLen; /* (format chunk len - first two fields) 16 in our case */
wxUint16 formatTag; /* 1 for PCM */
wxUint16 channels;
wxUint32 sampleRate;
wxUint32 avgBytesPerSec; /* sampleRate * blockAlign */
wxUint16 blockAlign; /* bitsPerSample * channels (assume bps % 8 = 0) */
wxUint16 bitsPerSample;
/* data chunk header */
char dataID[4]; /* "data" */
wxUint32 dataLen; /* length of all samples in bytes */
};
class ExportCL : public ExportPlugin
{
public:
ExportCL();
void Destroy();
// Required
bool DisplayOptions(wxWindow *parent, int format = 0);
int Export(AudacityProject *project,
int channels,
wxString fName,
bool selectedOnly,
double t0,
double t1,
MixerSpec *mixerSpec = NULL,
Tags *metadata = NULL,
int subformat = 0);
};
ExportCL::ExportCL()
: ExportPlugin()
{
AddFormat();
SetFormat(wxT("CL"),0);
AddExtension(wxT(""),0);
SetMaxChannels(255,0);
SetCanMetaData(false,0);
SetDescription(_("(external program)"),0);
}
void ExportCL::Destroy()
{
delete this;
}
int ExportCL::Export(AudacityProject *project,
int channels,
wxString fName,
bool selectionOnly,
double t0,
double t1,
MixerSpec *mixerSpec,
Tags *metadata,
int subformat)
{
ExportCLProcess *p;
wxString output;
wxString cmd;
bool show;
long rc;
// Retrieve settings
gPrefs->Read(wxT("/FileFormats/ExternalProgramShowOutput"), &show, false);
cmd = gPrefs->Read(wxT("/FileFormats/ExternalProgramExportCommand"), wxT("lame - \"%f.mp3\""));
cmd.Replace(wxT("%f"), fName);
#if defined(__WXMSW__)
// Give Windows a chance at finding lame command in the default location.
wxString paths[] = {wxT("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity"),
wxT("HKEY_LOCAL_MACHINE\\Software\\FFmpeg for Audacity")};
wxString opath;
wxString npath;
wxRegKey reg;
wxGetEnv(wxT("PATH"), &opath);
npath = opath;
for (int i = 0; i < WXSIZEOF(paths); i++) {
reg.SetName(paths[i]);
if (reg.Exists()) {
wxString ipath;
reg.QueryValue(wxT("InstallPath"), ipath);
if (!ipath.IsEmpty()) {
npath += wxPATH_SEP + ipath;
}
}
}
wxSetEnv(wxT("PATH"),npath.c_str());
#endif
// Kick off the command
p = new ExportCLProcess(&output);
rc = wxExecute(cmd, wxEXEC_ASYNC, p);
#if defined(__WXMSW__)
if (!opath.IsEmpty()) {
wxSetEnv(wxT("PATH"),opath.c_str());
}
#endif
if (!rc) {
wxMessageBox(wxString::Format(_("Cannot export audio to %s"),
fName.c_str()));
p->Detach();
p->CloseOutput();
return false;
}
// Turn off logging to prevent broken pipe messages
wxLogNull nolog;
// establish parameters
int rate = lrint(project->GetRate());
sampleCount maxBlockLen = 44100 * 5;
unsigned long totalSamples = lrint((t1 - t0) * rate);
unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(int16Sample);
// fill up the wav header
wav_header header;
header.riffID[0] = 'R';
header.riffID[1] = 'I';
header.riffID[2] = 'F';
header.riffID[3] = 'F';
header.riffType[0] = 'W';
header.riffType[1] = 'A';
header.riffType[2] = 'V';
header.riffType[3] = 'E';
header.lenAfterRiff = wxUINT32_SWAP_ON_BE(sampleBytes + 32);
header.fmtID[0] = 'f';
header.fmtID[1] = 'm';
header.fmtID[2] = 't';
header.fmtID[3] = ' ';
header.formatChunkLen = wxUINT32_SWAP_ON_BE(16);
header.formatTag = wxUINT16_SWAP_ON_BE(1);
header.channels = wxUINT16_SWAP_ON_BE(channels);
header.sampleRate = wxUINT32_SWAP_ON_BE(rate);
header.bitsPerSample = wxUINT16_SWAP_ON_BE(SAMPLE_SIZE(int16Sample) * 8);
header.blockAlign = wxUINT16_SWAP_ON_BE(header.bitsPerSample * header.channels);
header.avgBytesPerSec = wxUINT32_SWAP_ON_BE(header.sampleRate * header.blockAlign);
header.dataID[0] = 'd';
header.dataID[1] = 'a';
header.dataID[2] = 't';
header.dataID[3] = 'a';
header.dataLen = wxUINT32_SWAP_ON_BE(sampleBytes);
// write the header
wxOutputStream *os = p->GetOutputStream();
os->Write(&header, sizeof(wav_header));
// Mix 'em up
int numWaveTracks;
WaveTrack **waveTracks;
TrackList *tracks = project->GetTracks();
tracks->GetWaveTracks(selectionOnly, &numWaveTracks, &waveTracks);
Mixer *mixer = CreateMixer(numWaveTracks,
waveTracks,
tracks->GetTimeTrack(),
t0,
t1,
channels,
maxBlockLen,
true,
rate,
int16Sample,
true,
mixerSpec);
size_t numBytes = 0;
samplePtr mixed;
int updateResult = eProgressSuccess;
// Prepare the progress display
ProgressDialog *progress = new ProgressDialog(_("Export"),
selectionOnly ?
_("Exporting the selected audio using command-line encoder") :
_("Exporting the entire project using command-line encoder"));
// Start piping the mixed data to the command
while (updateResult == eProgressSuccess && p->IsActive() && os->IsOk()) {
// Capture any stdout and stderr from the command
Drain(p->GetInputStream(), &output);
Drain(p->GetErrorStream(), &output);
// Need to mix another block
if (numBytes == 0) {
sampleCount numSamples = mixer->Process(maxBlockLen);
if (numSamples == 0) {
break;
}
mixed = mixer->GetBuffer();
numBytes = numSamples * channels;
// Byte-swapping is neccesary on big-endian machines, since
// WAV files are little-endian
#if wxBYTE_ORDER == wxBIG_ENDIAN
wxUint16 *buffer = (wxUint16 *) mixed;
for (int i = 0; i < numBytes; i++) {
buffer[i] = wxUINT16_SWAP_ON_BE(buffer[i]);
}
#endif
numBytes *= SAMPLE_SIZE(int16Sample);
}
// Don't write too much at once...pipes may not be able to handle it
size_t bytes = wxMin(numBytes, 4096);
numBytes -= bytes;
while (bytes > 0) {
os->Write(mixed, bytes);
if (!os->IsOk()) {
break;
}
bytes -= os->LastWrite();
mixed += os->LastWrite();
}
// Update the progress display
updateResult = progress->Update(mixer->MixGetCurrentTime()-t0, t1-t0);
}
// Done with the progress display
delete progress;
// Should make the process die
p->CloseOutput();
// Wait for process to terminate
while (p->IsActive()) {
wxMilliSleep(10);
wxTheApp->Yield();
}
// Display output on error or if the user wants to see it
if (p->GetStatus() != 0 || show) {
// TODO use ShowInfoDialog() instead.
wxDialog dlg(NULL,
wxID_ANY,
wxString(_("Command Output")),
wxDefaultPosition,
wxSize(600, 400),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
ShuttleGui S(&dlg, eIsCreating);
S.AddTextWindow(output);
S.StartHorizontalLay(wxALIGN_CENTER, false);
{
S.Id(wxID_OK).AddButton(_("&OK"))->SetDefault();
}
dlg.GetSizer()->AddSpacer(5);
dlg.Layout();
dlg.SetMinSize(dlg.GetSize());
dlg.Center();
dlg.ShowModal();
}
// Clean up
delete mixer;
delete[] waveTracks;
delete p;
return updateResult;
}
bool ExportCL::DisplayOptions(wxWindow *parent, int format)
{
ExportCLOptions od(parent);
od.ShowModal();
return true;
}
//----------------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------------
ExportPlugin *New_ExportCL()
{
return new ExportCL();
}
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
// version control system. Please do not modify past this point.
//
// Local Variables:
// c-basic-offset: 3
// indent-tabs-mode: nil
// End:
//
// vim: et sts=3 sw=3
// arch-tag: c1578868-82c5-4f4a-b61b-8d82536a3141