Skip to content

Commit

Permalink
ffmpeg export: successfully pipe the exported WAV data
Browse files Browse the repository at this point in the history
  • Loading branch information
yohannd1 committed Dec 26, 2024
1 parent 6b6fc23 commit ef90209
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 103 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ src/subprocess.cpp
src/baseutils.cpp
src/fileutils.cpp
src/utfutils.cpp
src/stringutils.cpp

extern/itcompress/compression.c

Expand Down
9 changes: 4 additions & 5 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ class DivEngine {
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
DivAudioExportFormats exportFormat;
String exportFileExtNoDot;
double exportFadeOut;
bool isFadingOut;
int exportOutputs;
Expand Down Expand Up @@ -623,8 +624,8 @@ class DivEngine {
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
//sample banks

// sample banks
void loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
Expand All @@ -633,8 +634,6 @@ class DivEngine {
void loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
void loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);



int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);

bool initAudioBackend();
Expand Down Expand Up @@ -734,7 +733,7 @@ class DivEngine {
// export to text
SafeWriter* saveText(bool separatePatterns=true);
// export to an audio file
bool saveAudio(const char* path, DivAudioExportOptions options);
bool saveAudio(const char* path, DivAudioExportOptions options, const char *fileExt);
// wait for audio export to finish
void waitAudioFile();
// stop audio file export
Expand Down
26 changes: 24 additions & 2 deletions src/engine/sfWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,16 @@ int SFWrapper::doClose() {
return ret;
}

SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
void SFWrapper::initVio() {
vio.get_filelen=_vioGetSize;
vio.read=_vioRead;
vio.seek=_vioSeek;
vio.tell=_vioTell;
vio.write=_vioWrite;
}

SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
initVio();
logV("SFWrapper: opening %s",path);

const char* modeC="rb";
Expand All @@ -103,7 +107,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
f=NULL;
return NULL;
}

len=ftell(f);
if (len==(SIZE_MAX>>1)) {
logE("SFWrapper: failed to tell (%s)",strerror(errno));
Expand All @@ -128,3 +132,21 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
}
return sf;
}

SNDFILE* SFWrapper::doOpenFromWriteFd(int writeFd, SF_INFO* sfinfo) {
f=fdopen(writeFd,"w");
if (f==NULL) {
logE("SFWrapper: failed to open file descriptor %d (pipe) as file: %s",writeFd,strerror(errno));
return NULL;
}

initVio();
len=0; // I am hoping this is not used when writing
fileMode=SFM_WRITE;

sf=sf_open_virtual(&vio,SFM_WRITE,sfinfo,this);
if (sf==NULL) {
logE("SFWrapper: WHY IS IT NULL?!");
}
return sf;
}
2 changes: 2 additions & 0 deletions src/engine/sfWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ class SFWrapper {
sf_count_t ioWrite(const void* ptr, sf_count_t count);
sf_count_t ioTell();

void initVio();
int doClose();
SNDFILE* doOpen(const char* path, int mode, SF_INFO* sfinfo);
SNDFILE* doOpenFromWriteFd(int fd, SF_INFO* sfinfo);
SFWrapper():
f(NULL),
len(0),
Expand Down
177 changes: 95 additions & 82 deletions src/engine/wavOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "engine.h"
#include "../ta-log.h"
#include "../subprocess.h"
#include "../stringutils.h"
#ifdef HAVE_SNDFILE
#include "sfWrapper.h"
#endif
Expand Down Expand Up @@ -120,92 +121,106 @@ void DivEngine::runExportThread() {
si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
}

// TODO: receive format! and check if we really need to pipe it out
std::vector<String> args={"echo", "hello world!"};
Subprocess proc(args);
if (!proc.start()) {
logE("failed to start subprocess!\n");
// int writeFd=proc.pipeStdin(); // TODO
} else {
proc.wait();
}

sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
logE("could not open file for writing! (%s)",sf_strerror(NULL));
exporting=false;
return;
}
const auto doExport=[&](){
if (sf==NULL) {
logE("could not initialize export");
exporting=false;
return;
}

float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal;
for (int i=0; i<exportOutputs; i++) {
outBuf[i]=new float[EXPORT_BUFSIZE];
}
outBufFinal=new float[EXPORT_BUFSIZE*exportOutputs];
float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal;
for (int i=0; i<exportOutputs; i++) {
outBuf[i]=new float[EXPORT_BUFSIZE];
}
outBufFinal=new float[EXPORT_BUFSIZE*exportOutputs];

// take control of audio output
deinitAudioBackend();
playSub(false);
// take control of audio output
deinitAudioBackend();
playSub(false);

logI("rendering to file...");
logI("rendering to file...");

while (playing) {
size_t total=0;
nextBuf(NULL,outBuf,0,exportOutputs,EXPORT_BUFSIZE);
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
totalProcessed=EXPORT_BUFSIZE;
}
int fi=0;
for (int i=0; i<(int)totalProcessed; i++) {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]))*mul;
}
if (++curFadeOutSample>=fadeOutSamples) {
playing=false;
break;
}
} else {
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]));
}
if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) {
logD("start fading out...");
isFadingOut=true;
if (fadeOutSamples==0) break;
while (playing) {
size_t total=0;
nextBuf(NULL,outBuf,0,exportOutputs,EXPORT_BUFSIZE);
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
totalProcessed=EXPORT_BUFSIZE;
}
int fi=0;
for (int i=0; i<(int)totalProcessed; i++) {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]))*mul;
}
if (++curFadeOutSample>=fadeOutSamples) {
playing=false;
break;
}
} else {
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]));
}
if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) {
logD("start fading out...");
isFadingOut=true;
if (fadeOutSamples==0) break;
}
}
}
}

if (sf_writef_float(sf,outBufFinal,total)!=(int)total) {
logE("error: failed to write entire buffer!");
break;
if (sf_writef_float(sf,outBufFinal,total)!=(int)total) {
logE("error: failed to write entire buffer!");
break;
}
}
}

delete[] outBufFinal;
for (int i=0; i<exportOutputs; i++) {
delete[] outBuf[i];
}
delete[] outBufFinal;
for (int i=0; i<exportOutputs; i++) {
delete[] outBuf[i];
}

if (sfWrap.doClose()!=0) {
logE("could not close audio file!");
}
if (sfWrap.doClose()!=0) {
logE("could not close audio file!");
}

if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality,dcHiPass);
if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality,dcHiPass);
}
if (!output->setRun(true)) {
logE("error while activating audio!");
}
}
if (!output->setRun(true)) {
logE("error while activating audio!");
};

if (exportFileExtNoDot=="wav") {
sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si);
doExport();
} else {
std::vector<String> args={"tee", "test.txt"}; // TODO: build args
Subprocess proc(args);
int writeFd=proc.pipeStdin();
if (writeFd==-1) {
logE("failed to create stdin pipe for subprocess");
} if (proc.start()) {
sf=sfWrap.doOpenFromWriteFd(writeFd,&si);
doExport();
logI("waiting for ffmpeg to finish...");
int code = proc.wait();
if (code!=0) {
logE("ffmpeg failed to export successfully"); // TODO: actually show this in the UI? (it might be a good idea to read the output from ffmpeg, in that case)
}
} else {
logE("failed to start subprocess");
}
}
logI("done!");

logI("exporting done!");
exporting=false;
break;
}
Expand Down Expand Up @@ -481,7 +496,7 @@ bool DivEngine::shallSwitchCores() {
return true;
}

bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options, const char *fileExt) {
#ifndef HAVE_SNDFILE
logE("Furnace was not compiled with libsndfile. cannot export!");
return false;
Expand All @@ -490,17 +505,15 @@ bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
exportMode=options.mode;
exportFormat=options.format;
exportFadeOut=options.fadeOut;
if (fileExt[0]=='.') {
exportFileExtNoDot=&fileExt[1];
} else {
exportFileExtNoDot=fileExt;
}

memcpy(exportChannelMask,options.channelMask,DIV_MAX_CHANS*sizeof(bool));
if (exportMode!=DIV_EXPORT_MODE_ONE) {
// remove extension
String lowerCase=exportPath;
for (char& i: lowerCase) {
if (i>='A' && i<='Z') i+='a'-'A';
}
size_t extPos=lowerCase.rfind(".wav");
if (extPos!=String::npos) {
exportPath=exportPath.substr(0,extPos);
}
removeFileExt(exportPath,exportFileExtNoDot.c_str());
}
exporting=true;
stopExport=false;
Expand Down
6 changes: 2 additions & 4 deletions src/gui/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2583,9 +2583,6 @@ int FurnaceGUI::loadStream(String path) {
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
songOrdersLengths.clear();

const FurnaceGUIExportFormat *ef=&exportFormats[curAudioExportFormat];
const char *extNoDot=&ef->fileExt[1];

int loopOrder=0;
int loopRow=0;
int loopEnd=0;
Expand All @@ -2599,7 +2596,8 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
}
songLoopedSectionLength-=loopRow;

e->saveAudio(path.c_str(),audioExportOptions);
const FurnaceGUIExportFormat& ef=exportFormats[curAudioExportFormat];
e->saveAudio(path.c_str(),audioExportOptions,ef.fileExt);

totalFiles=0;
e->getTotalAudioFiles(totalFiles);
Expand Down
2 changes: 1 addition & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ int main(int argc, char** argv) {
}
if (outName!="") {
e.setConsoleMode(true);
e.saveAudio(outName.c_str(),exportOptions);
e.saveAudio(outName.c_str(),exportOptions,"wav");
e.waitAudioFile();
}
if (romOutName!="") {
Expand Down
Loading

0 comments on commit ef90209

Please sign in to comment.