[Audacity-devel] [PATCH] Direct export to audio CD
A free multi-track audio editor and recorder
Brought to you by:
aosiniao
|
From: Jonathan W. <jw...@ph...> - 2008-06-25 05:32:11
|
Hi all
In the process of setting up a simple digital audio recordering system for
someone I had the need to directly export the recording to an audio CD. The
resulting CD didn't have to be mastered or have anything fancy done to it in
terms of track layout and so forth - it was really just to get the audio out
with only one or two clicks so it could be played back on a standard CD
player with little or no fuss.
In terms of the recording system, Audacity did everything I needed for this
application except for the audio CD export, so I added this functionality
myself. Since I think that others might find such functionality useful I
thought it might be helpful to submit it for discussion to see whether it
could make it into audacity. The patch (against 1.2.6) is at the end of
this post.
The patch utilises cdrdao to do the CD writing (tested with version 1.2.1).
The only temporary file created by the process is the small TOC file used to
tell cdrdao how to form the CD. Audio data itself is piped into cdrdao. As
it currently stands a new track is started every 5 minutes with no pre-gap,
so the audio plays continuously but you have the added convenience of 5
minute long tracks to facilitate quick "fast forwarding".
Admittedly the patch has some rough edges - for example, the length of the
tracks and the CD writer device is fixed in variable definitions at the top
of exports/ExportCDR.c. Ultimately these variables would be set via a
preferences pane, but I don't yet understand the audacity preferences system
enough to attempt to add this. Despite these limitations the patch is still
useful to generate a "first look" audio CD. I would be interested in any
feedback that you may have and whether we could work to get this into the
audacity tree.
Regards
jonathan
diff -ruN audacity-src-1.2.6/src/Makefile.in audacity-src-1.2.6-new/src/Makefile.in
--- audacity-src-1.2.6/src/Makefile.in 2006-11-15 14:42:11.000000000 +1030
+++ audacity-src-1.2.6-new/src/Makefile.in 2008-05-31 14:44:06.763567044 +0930
@@ -113,6 +113,7 @@
$(OBJDIR)/export/ExportOGG.o \
$(OBJDIR)/export/ExportPCM.o \
$(OBJDIR)/export/ExportCL.o \
+ $(OBJDIR)/export/ExportCDR.o \
$(OBJDIR)/import/Import.o \
$(OBJDIR)/import/ImportLOF.o \
$(OBJDIR)/import/ImportMIDI.o \
diff -ruN audacity-src-1.2.6/src/Menus.cpp audacity-src-1.2.6-new/src/Menus.cpp
--- audacity-src-1.2.6/src/Menus.cpp 2006-11-15 14:42:11.000000000 +1030
+++ audacity-src-1.2.6-new/src/Menus.cpp 2008-05-31 20:02:04.639614695 +0930
@@ -170,16 +170,19 @@
c->AddItem("ExportOgg", _("Export As Ogg Vorbis..."), FN(OnExportOggMix));
c->AddItem("ExportOggSel", _("Export Selection As Ogg Vorbis..."), FN(OnExportOggSelection));
c->AddSeparator();
+ c->AddItem("ExportCD", _("Export as audio CD"), FN(OnExportCD));
+ c->AddItem("ExportCDSel", _("Export Selection as audio CD"), FN(OnExportCDSel));
+ c->AddSeparator();
c->AddItem("ExportLabels", _("Export &Labels..."), FN(OnExportLabels));
c->AddItem("ExportMultiple", _("Export &Multiple..."), FN(OnExportMultiple));
// Enable Export commands only when there are tracks
c->SetCommandFlags(AudioIONotBusyFlag | TracksExistFlag,
AudioIONotBusyFlag | TracksExistFlag,
- "Export", "ExportMP3", "ExportOgg", NULL);
+ "Export", "ExportMP3", "ExportOgg", "ExportCD", NULL);
// Enable Export Selection commands only when there's a selection
c->SetCommandFlags(AudioIONotBusyFlag | TimeSelectedFlag | TracksSelectedFlag,
AudioIONotBusyFlag | TimeSelectedFlag | TracksSelectedFlag,
- "ExportSel", "ExportMP3Sel", "ExportOggSel", NULL);
+ "ExportSel", "ExportMP3Sel", "ExportOggSel", "ExportCDSel", NULL);
c->SetCommandFlags("ExportLabels",
AudioIONotBusyFlag | LabelTracksExistFlag,
@@ -1441,6 +1444,16 @@
::ExportLossy(this, true, mViewInfo.sel0, mViewInfo.sel1);
}
+void AudacityProject::OnExportCD()
+{
+ ::ExportCDR(this, true, "", false, 0.0, mTracks->GetEndTime());
+}
+
+void AudacityProject::OnExportCDSel()
+{
+ ::ExportCDR(this, true, "", true, mViewInfo.sel0, mViewInfo.sel1);
+}
+
void AudacityProject::OnExportMultiple()
{
::ExportMultiple(this);
diff -ruN audacity-src-1.2.6/src/Menus.h audacity-src-1.2.6-new/src/Menus.h
--- audacity-src-1.2.6/src/Menus.h 2006-11-15 14:42:11.000000000 +1030
+++ audacity-src-1.2.6-new/src/Menus.h 2008-05-31 20:03:00.462098625 +0930
@@ -90,6 +90,8 @@
void OnExportMP3Selection();
void OnExportOggMix();
void OnExportOggSelection();
+void OnExportCD();
+void OnExportCDSel();
void OnExportMultiple();
void OnExportLabels();
diff -ruN audacity-src-1.2.6/src/export/Export.h audacity-src-1.2.6-new/src/export/Export.h
--- audacity-src-1.2.6/src/export/Export.h 2006-11-15 14:42:10.000000000 +1030
+++ audacity-src-1.2.6-new/src/export/Export.h 2008-05-31 20:04:28.820618979 +0930
@@ -21,4 +21,7 @@
bool ExportLossy(AudacityProject *project,
bool selectionOnly, double t0, double t1);
+bool ExportCDR(AudacityProject *project, bool stereo, wxString fName,
+ bool selectionOnly, double t0, double t1);
+
#endif
diff -ruN audacity-src-1.2.6/src/export/ExportCDR.cpp audacity-src-1.2.6-new/src/export/ExportCDR.cpp
--- audacity-src-1.2.6/src/export/ExportCDR.cpp 1970-01-01 09:30:00.000000000 +0930
+++ audacity-src-1.2.6-new/src/export/ExportCDR.cpp 2008-06-09 21:26:45.022441456 +0930
@@ -0,0 +1,354 @@
+/**********************************************************************
+
+ Audacity: A Digital Audio Editor
+
+ ExportCDR.cpp
+
+ Jonathan Woithe
+ Based heavily on ExportCL.cpp by Joshua Haberman.
+
+ This code allows Audacity to export data directly onto an audio CD by
+ piping it to cdrdao. A suitable TOC file is created which instructs
+ cdrdao to take its input from stdout.
+
+**********************************************************************/
+
+#ifdef __WXGTK__
+
+/* _GNU_SOURCE is required for asprintf() but is already defined on the
+ * command line.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <wx/progdlg.h>
+#include <wx/msgdlg.h>
+
+#include "../Project.h"
+#include "../Mix.h"
+#include "../Prefs.h"
+
+/* FIXME: in time, parameters such as device, speed etc should be
+ * configurable at runtime.
+ */
+
+/* The maximum length of a track. The project's audio will be written to a
+ * series of tracks each of which is at most this length. The only
+ * exception is the final track - if it would otherwise be less than 4
+ * seconds it is merged with the previous track. The maximum length
+ * specified here is in seconds.
+ */
+signed int cdr_max_track_length = (5*60);
+
+/* The device to use for output */
+char *cdr_device = "/dev/cdrom";
+
+/* The speed to record the CD-R at */
+signed int cdr_speed = 16;
+
+static int msg_box(const char *msg, wxWindow *parent) {
+ return wxMessageBox(msg, wxMessageBoxCaptionStr, wxOK | wxCENTRE, parent);
+}
+
+
+signed int pipeopen(char *toc_name) {
+/*
+ * Open a writeable pipe to the CD writing command. The toc_name argument
+ * gives the name of the generated TOC file. The file descriptor of the
+ * write side of the pipe is returned, or -1 on error. The file descriptor
+ * is made non-blocking before returning.
+ */
+signed int fd;
+signed int pipefd[2];
+signed int child;
+signed int i;
+char speedstr[10];
+
+ if (pipe(pipefd) == -1)
+ return -1;
+
+ child = fork();
+ if (child == -1) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return -1;
+ }
+
+ if (child == 0) {
+ /* In the child process. Close write end of pipe. */
+ close(pipefd[1]);
+
+ /* Attach the read pipe to this process's stdin and call the desired command */
+ close(0);
+ dup(pipefd[0]);
+ close(pipefd[0]);
+ if (cdr_speed<1 || cdr_speed>128)
+ cdr_speed = 4;
+ snprintf(speedstr, 10, "%d", cdr_speed);
+ i = execlp("cdrdao", "cdrdao", "write", "-n", "--device", cdr_device,
+// "--simulate",
+ "--speed", speedstr, toc_name, (char *)NULL);
+ /* If execlp() returns there has been an error, so just exit the child */
+ exit(-1);
+ }
+
+ /* Close read end of pipe */
+ close(pipefd[0]);
+ fd = pipefd[1];
+ /* Make the pipe nonblocking so writes can't stop GUI redraws */
+ long fl = 0;
+ fl = fcntl(fd, F_GETFL, 0);
+ fl |= O_NONBLOCK;
+ fcntl(fd, F_SETFL, fl);
+ return fd;
+}
+
+
+bool ExportCDR(AudacityProject *project, bool stereo, wxString fName,
+ bool selectionOnly, double t0, double t1)
+{
+ struct sigaction sig, oldsig;
+ wxWindow *parent = project;
+ TrackList *tracks = project->GetTracks();
+ signed int write_error = 0;
+ signed int started = 0;
+ signed int rcode = 0;
+ signed errno_store = 0;
+
+ signed int idx;
+ signed int nw;
+
+ /* cdrdao expects 2-channel audio at 44100 Hz */
+ unsigned int channels = 2;
+ unsigned long totalOutSamples = (unsigned long)((t1 - t0) * 44100 + 0.5);
+
+ /* An audio CD must be at least 4 seconds long */
+ if (t1 - t0 < 4.0) {
+ msg_box("Project/selection is too short.\nMinimum length of audio CD is 4 seconds", parent);
+ return false;
+ }
+ /* Limit the audio CD to 79 minutes since that's what all commonly
+ * available blanks can do. Some "80 minute" discs are actually slightly
+ * less than that in reality.
+ */
+ if (t1 - t0 > 79.0*60) {
+ msg_box("Project/selection is too long.\nMaximum length of audio CD is 79 minutes", parent);
+ return false;
+ }
+
+ /* Create a cdrdao TOC file */
+ char toc_name[] = "/tmp/audacity_cd_toc-XXXXXX";
+ signed int fd = mkstemp(toc_name);
+ unsigned int i1, i2;
+ FILE *toc;
+ if (fd < 0) {
+ msg_box("Could not create TOC file", parent);
+ return false;
+ }
+ toc = fdopen(fd, "w");
+ if (toc == NULL) {
+ msg_box("Could not write to TOC file", parent);
+ close(fd);
+ remove(toc_name);
+ return false;
+ }
+ fprintf(toc, "CD_DA\n");
+ /* Create a track point once every cdr_max_track_length seconds */
+ i1 = 0;
+ i2 = cdr_max_track_length*44100;
+ do {
+ if (i1 + i2 > totalOutSamples) {
+ /* Size last track according to time remaining */
+ i2 = totalOutSamples-i1;
+ } else
+ if (totalOutSamples-i1-i2 < 4*44100) {
+ /* Make sure the last track isn't destined to be less than 4 seconds */
+ i2 = totalOutSamples-i1;
+ }
+ fprintf(toc, "TRACK AUDIO\n");
+ fprintf(toc, "FILE \"-\" %d %d\n", i1, i2);
+ i1 += i2;
+ } while (i1 < totalOutSamples);
+ if (fclose(toc) != 0) {
+ msg_box("Could not complete TOC file", parent);
+ remove(toc_name);
+ return false;
+ }
+
+ /* Put some explanatory phrases to stderr to help users interpret
+ * stdout/stderr.
+ */
+ fprintf(stderr, "cdrdao started\n");
+
+ fd = pipeopen(toc_name);
+ if (fd == -1) {
+ remove(toc_name);
+ msg_box("Could not start cdrdao to write CD", parent);
+ fprintf(stderr, "cdrdao complete\n");
+ return false;
+ }
+
+ /* Set up to ignore SIGPIPE so we can catch errors from the CD writer */
+ memset(&sig, 0, sizeof(sig));
+ sig.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sig, &oldsig);
+
+ sampleCount maxBlockLen = 44100 * 5;
+
+ wxProgressDialog *progress = NULL;
+ wxYield();
+ wxStartTimer();
+ wxBusyCursor busy;
+ bool cancelling = false;
+
+ int numWaveTracks;
+ WaveTrack **waveTracks;
+ tracks->GetWaveTracks(selectionOnly, &numWaveTracks, &waveTracks);
+ /* For an audio CD the sampling rate must be 44.1 kHz */
+ Mixer *mixer = new Mixer(numWaveTracks, waveTracks,
+ tracks->GetTimeTrack(),
+ t0, t1,
+ channels, maxBlockLen, true,
+ 44100, int16Sample);
+
+ if (!progress) {
+ wxString title;
+ wxString message;
+
+ if (selectionOnly)
+ title = _("Creating audio CD from selection");
+ else
+ title = _("Creating audio CD from project");
+ message = _("Initiating CD creation");
+
+ /* When aborting is implemented, add wxPD_CAN_ABORT. To auto-hide
+ * after reaching 100%, add wxPD_AUTO_HIDE.
+ */
+ progress =
+ new wxProgressDialog(title,
+ message,
+ 1000,
+ parent,
+ wxPD_REMAINING_TIME);
+ }
+
+ while(!cancelling) {
+ sampleCount numSamples = mixer->Process(maxBlockLen);
+
+ if (numSamples == 0)
+ break;
+
+ samplePtr mixed = mixer->GetBuffer();
+
+ // The output stream is expected to be big-endian by cdrdao.
+#if wxBYTE_ORDER == wxLITTLE_ENDIAN
+ {
+ short *buffer = (short*)mixed;
+ for( unsigned int i = 0; i < numSamples * channels; i++ )
+ buffer[i] = wxINT16_SWAP_ON_LE(buffer[i]);
+ }
+#endif
+
+ idx = 0;
+ nw = numSamples * channels * SAMPLE_SIZE(int16Sample);
+ do {
+ errno = 0;
+ rcode = write(fd, mixed+idx, nw);
+ errno_store = errno;
+ wxYield();
+ if (rcode > 0) {
+ nw -= rcode;
+ idx += rcode;
+ }
+ } while (nw>0 && (errno_store==0 || errno_store==EAGAIN || errno_store==EINTR));
+
+ if (nw != 0) {
+ write_error = -1;
+ break;
+ }
+ if (started == 0) {
+ /* Data has started to flow to the CD, so update the progress
+ * dialog's message.
+ */
+ progress->Update(0, _("Writing audio data to disc"));
+ started = 1;
+ }
+ /* Update the progress dialog's value. Don't let the value hit the
+ * specified maximum, thus preventing the dialog from concluding that
+ * the process is complete. This means we retain control of the
+ * progress dialog after all data has been written and can therefore
+ * change the displayed message while we wait for the disc to be
+ * finalised.
+ */
+ {
+ int progressvalue = int (999 * ((mixer->MixGetCurrentTime()-t0) /
+ (t1-t0)));
+ cancelling = !progress->Update(progressvalue);
+ }
+ }
+
+ delete mixer;
+
+ if (write_error == 0)
+ progress->Update(999, _("Finalising audio CD"));
+
+ // Wait for the child cdrdao process to exit before calling close(). This
+ // prevents the GUI from freezing up since the close() call may block until
+ // the process exits. There will be a significant delay between the sending
+ // of the last data and the ending of the CD writing process due to buffers in
+ // cdrdao and the disc finalisation process. This manual waiting at least allows
+ // the GUI to stay "alive" in this period.
+ //
+ // We use waitid() since it has has the WNOWAIT option which keeps the
+ // child in a "waitable" state after it returns. This means that the subsequent
+ // close() call can still work properly.
+ //
+ // Note that this was also a problem when popen()/pclose() was used to
+ // set up the IPC.
+ siginfo_t info;
+ int r;
+ do {
+ info.si_pid = 0;
+ r = waitid(P_ALL, -1, &info, WEXITED|WNOHANG|WNOWAIT);
+ wxYield();
+ } while (r==0 && info.si_pid==0);
+ fd = close( fd);
+
+ if (fd!=0 && write_error==0) {
+ write_error = fd;
+ }
+ remove(toc_name);
+
+ sigaction(SIGPIPE, &oldsig, NULL);
+ fprintf(stderr, "cdrdao complete\n");
+
+ if (write_error != 0) {
+ char *msg = NULL;
+ if (write_error != -1)
+ asprintf(&msg, "Error finalising CD: %d. Check stdout/stderr for more details.", write_error);
+ else
+ if (started == 0)
+ asprintf(&msg, "Error creating audio CD. Perhaps a blank disc isn't in the drive.\nCheck stdout/stderr for more details.");
+ else
+ asprintf(&msg, "Error writing audio data to CD. Check stdout/stderr for more details.");
+ msg_box(msg, parent);
+ free(msg);
+ }
+
+ if(progress)
+ delete progress;
+ if (write_error == 0)
+ msg_box("Audio CD created successfully", parent);
+
+ return write_error==0?true:false;
+}
+
+#endif /* __WXGTK__ */
+
|