tomwalters@397: // Copyright 2006, Willem van Engen tomwalters@397: // tomwalters@397: // AIM-C: A C++ implementation of the Auditory Image Model tomwalters@397: // http://www.acousticscale.org/AIMC tomwalters@397: // tomwalters@397: // Licensed under the Apache License, Version 2.0 (the "License"); tomwalters@397: // you may not use this file except in compliance with the License. tomwalters@397: // You may obtain a copy of the License at tomwalters@397: // tomwalters@397: // http://www.apache.org/licenses/LICENSE-2.0 tomwalters@397: // tomwalters@397: // Unless required by applicable law or agreed to in writing, software tomwalters@397: // distributed under the License is distributed on an "AS IS" BASIS, tomwalters@397: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. tomwalters@397: // See the License for the specific language governing permissions and tomwalters@397: // limitations under the License. tomwalters@397: tomwalters@397: /*! tomwalters@397: * \file tomwalters@397: * \brief Output device for output to a movie tomwalters@397: * tomwalters@397: * \author Willem van Engen tomwalters@397: * \date created 2006/10/16 tomwalters@397: * \version \$Id: GraphicsOutputDeviceMovie.cpp 633 2008-09-11 04:20:16Z tom $ tomwalters@397: */ tomwalters@397: tomwalters@397: /*! \todo tomwalters@397: * A recent experiment showed that the video was about an audioframe tomwalters@397: * (something like 30 ms) behind the audio in rdct.wav. It seems odd tomwalters@397: * to me, since I already output a frame at the beginning to compensate tomwalters@397: * for the missed buffer. tomwalters@397: * A solution that would solve this and be a broader improvement, is to tomwalters@397: * include the source's time when Fire()ing. The outputdevice can then tomwalters@397: * do audio/video synchronization. In the case of the movie, the very tomwalters@397: * first gGrab() looks at the time and emits as much empty output frames tomwalters@397: * as needed until the correct signal time is reached. tomwalters@397: */ tomwalters@397: #include "Support/Common.h" tomwalters@397: tomwalters@397: #ifdef _WINDOWS tomwalters@398: # include // for _mkdir&_rmdir tomwalters@397: #else tomwalters@398: # include tomwalters@398: # include // for opendir&friends tomwalters@397: #endif tomwalters@397: #include tomwalters@397: #include tomwalters@397: tom@400: #include "Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.h" tomwalters@397: tom@400: namespace aimc { tomwalters@397: tomwalters@411: GraphicsOutputDeviceMovie::GraphicsOutputDeviceMovie(Parameters *parameters) tomwalters@411: : GraphicsOutputDeviceCairo(parameters) { tomwalters@411: sound_filename_.clear(); tomwalters@411: movie_filename_.clear(); tomwalters@397: } tomwalters@397: tomwalters@411: bool GraphicsOutputDeviceMovie::Initialize(Parameters *global_parameters) { tomwalters@411: global_parameters_ = global_parameters; tomwalters@411: sound_filename_ = global_parameters->GetString("input_filename"); tomwalters@411: string file_suffix = parameters_->DefaultString("filename_suffix", ".mov"); tomwalters@411: movie_filename_ = global_parameters->GetString("output_filename_base") tomwalters@411: + file_suffix; tomwalters@411: tomwalters@398: FILE *f; tomwalters@397: tomwalters@398: // Check sound file exists tomwalters@411: if ((f = fopen(sound_filename_.c_str(), "r")) == NULL) { tom@400: LOG_ERROR(_T("Couldn't open sound file '%s' for movie creation."), tomwalters@411: sound_filename_.c_str()); tomwalters@411: sound_filename_.clear(); tomwalters@398: return false; tomwalters@398: } tomwalters@398: fclose(f); tomwalters@397: tomwalters@398: // Check movie output file can be made tomwalters@411: if ((f = fopen(movie_filename_.c_str(), "w")) == NULL) { tom@400: LOG_ERROR(_T("Couldn't open movie file '%s' to write to."), tomwalters@411: movie_filename_.c_str()); tomwalters@411: movie_filename_.clear(); tomwalters@398: return false; tomwalters@398: } tomwalters@398: fclose(f); tomwalters@397: tomwalters@398: // Get a temporary image output directory tomwalters@398: //! \warning Not really safe ... but windows has no mkdtemp() tomwalters@398: //! \todo Make build system check for mkdtemp() to use it when available. See TODO.txt. tomwalters@397: #ifdef _WINDOWS tomwalters@411: char *temp_dir = NULL; tomwalters@411: if ((temp_dir = _tempnam(NULL, AIM_NAME)) tomwalters@411: && _mkdir(temp_dir) >= 0) { tomwalters@411: directory_ = temp_dir; tomwalters@411: directory_ += "\\"; // Make sure to end with trailing slash tomwalters@411: } else { tom@400: LOG_ERROR(_T("Couldn't create a temporary directory for movie output.")); tomwalters@411: if (temp_dir) { tomwalters@411: free(temp_dir); tomwalters@411: } tomwalters@398: return false; tomwalters@398: } tomwalters@411: if (temp_dir) { tomwalters@411: free(temp_dir); tomwalters@397: } tomwalters@411: #else tomwalters@411: char temp_dir[PATH_MAX]; tomwalters@411: strcpy(temp_dir, "/tmp/"AIM_NAME"-movie.XXXXXX"); tomwalters@411: if (mkdtemp(temp_dir)) { tomwalters@411: directory_ = temp_dir; tomwalters@411: directory_ += "/"; // Make sure to end with trailing slash tomwalters@411: } else { tomwalters@411: LOG_ERROR(_T("Couldn't create a temporary directory for movie output.")); tomwalters@398: return false; tomwalters@397: } tomwalters@411: #endif tomwalters@411: tomwalters@411: // We want png for movie conversion tomwalters@411: parameters_->SetString("output.img.format", "png"); tomwalters@411: if ( !GraphicsOutputDeviceCairo::Initialize(directory_) ) { tomwalters@411: return false; tomwalters@411: } tomwalters@398: return true; tomwalters@397: } tomwalters@397: tomwalters@397: void GraphicsOutputDeviceMovie::Start() { tomwalters@398: GraphicsOutputDeviceCairo::Start(); tomwalters@411: // Output a couple of frames to get audio/video in sync, put params in there. tomwalters@411: gGrab(); tomwalters@411: PlotParameterScreen(); tomwalters@411: gRelease(); tomwalters@398: gGrab(); tomwalters@398: PlotParameterScreen(); tomwalters@398: gRelease(); tomwalters@397: } tomwalters@397: tomwalters@397: void GraphicsOutputDeviceMovie::Stop() { tomwalters@398: GraphicsOutputDeviceCairo::Stop(); tomwalters@398: CloseFile(); tomwalters@397: tomwalters@397: #ifdef __WX__ tomwalters@398: // GUI only: popup dialog tomwalters@397: #else tomwalters@398: printf("Generating movie ... \n"); tomwalters@397: #endif tomwalters@411: AIM_ASSERT(parameters_); tomwalters@411: // Convert images and sound file to a movie. tomwalters@411: //! \warning Movie files are overwritten without warning. tomwalters@398: char sffmpegPath[1024]; tomwalters@411: if (!parameters_->IsSet("output.ffmpeg_path")) { tomwalters@397: strcpy(sffmpegPath,"ffmpeg"); tomwalters@397: } else { tomwalters@411: strcpy(sffmpegPath, parameters_->GetString("output.ffmpeg_path")); tomwalters@397: } tomwalters@397: char sCodecOptions[1024]; tomwalters@411: if (!parameters_->IsSet("output.ffmpeg_codec_options")) { tomwalters@397: strcpy(sCodecOptions,""); tomwalters@397: } else { tomwalters@411: strcpy(sCodecOptions, parameters_->GetString("output.ffmpeg_codec_options")); tomwalters@397: } tomwalters@411: float frame_rate = global_parameters_->DefaultFloat("frame_rate", -1.0); tomwalters@398: char sCmdLine[1024]; //!\todo check that snprintf does not want a larger buffer tomwalters@398: snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]), tomwalters@398: "%s -r %.2f -y -i \"%s\" -i \"%s%%06d.png\" " tomwalters@398: "-sameq -r %.2f -ar 44100 -acodec pcm_s16le %s \"%s\"", tomwalters@411: sffmpegPath, frame_rate, sound_filename_.c_str(), directory_.c_str(), tomwalters@411: frame_rate, sCodecOptions, movie_filename_.c_str()); tomwalters@397: printf(sCmdLine); tomwalters@397: printf("\n"); tomwalters@398: if (system(sCmdLine)) { tom@400: LOG_ERROR(_T("Couldn't create movie output.")); tomwalters@398: } tomwalters@397: tomwalters@397: #ifdef __WX__ tomwalters@398: // GUI only: close dialog again tomwalters@397: #endif tomwalters@398: // Remove files in temporary directory and the dir itself tomwalters@398: //! \todo make portable function, possibly decided on by build system tomwalters@397: #ifdef _WINDOWS tomwalters@398: HANDLE hList; tomwalters@398: WIN32_FIND_DATA FileData; tomwalters@398: snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]), "%s/*.*", m_sDir); tomwalters@398: if ((hList = FindFirstFile(sCmdLine, &FileData)) == INVALID_HANDLE_VALUE) { tom@400: LOG_ERROR(_T("Couldn't remove files from temporary directory.")); tomwalters@398: return; tomwalters@398: } tomwalters@398: bool bRMfinished = false; tomwalters@398: while (!bRMfinished) { tomwalters@398: snprintf(sCmdLine, tomwalters@397: sizeof(sCmdLine)/sizeof(sCmdLine[0]), tomwalters@398: "%s%s", tomwalters@397: m_sDir, tomwalters@397: FileData.cFileName); tomwalters@398: remove(sCmdLine); tomwalters@398: if (!FindNextFile(hList, &FileData) && GetLastError() == ERROR_NO_MORE_FILES) { tomwalters@398: bRMfinished = true; tomwalters@397: } tomwalters@398: } tomwalters@398: FindClose(hList); tomwalters@398: _rmdir(m_sDir); tomwalters@397: #else tomwalters@398: DIR *dir; tomwalters@398: struct dirent *dirent; tomwalters@411: if (!(dir = opendir(directory_.c_str()))) { tom@400: LOG_ERROR(_T("Couldn't remove files in temporary directory.")); tomwalters@398: return; tomwalters@398: } tomwalters@402: while ((dirent = readdir(dir))) { tomwalters@398: snprintf(sCmdLine, tomwalters@397: sizeof(sCmdLine)/sizeof(sCmdLine[0]), tomwalters@398: "%s%s", tomwalters@411: directory_.c_str(), tomwalters@397: dirent->d_name); tomwalters@398: unlink(sCmdLine); tomwalters@398: } tomwalters@398: closedir(dir); tomwalters@411: rmdir(directory_.c_str()); tomwalters@397: #endif tomwalters@397: } tomwalters@397: tomwalters@397: void GraphicsOutputDeviceMovie::PlotParameterScreen() { tomwalters@411: AIM_ASSERT(parameters_); tomwalters@398: char sStr[50]; tomwalters@398: int lineno = 1; tomwalters@397: tomwalters@411: float fMarL = parameters_->GetFloat(_S("graph.margin.left")); tomwalters@411: float fMarT = parameters_->GetFloat(_S("graph.margin.top")); tomwalters@398: float fTextHeight = 1.0f / 50.0f * 1.2; // change this when fontsizing is there! tomwalters@397: tomwalters@398: gText2f(fMarL, 1-(fMarT+fTextHeight*lineno++), tomwalters@397: _S("AIM-C")); tomwalters@398: gText2f(fMarL, tomwalters@397: 1-(fMarT+fTextHeight*lineno++), tomwalters@397: _S("(c) 2006-2010, Thomas Walters, Willem van Engen")); tomwalters@398: gText2f(fMarL, tomwalters@397: 1-(fMarT+fTextHeight*lineno++), tomwalters@397: _S("http://aimc.acousticscale.org/")); tomwalters@398: lineno++; tomwalters@397: tomwalters@398: static const char *pPlotParams[] = { tomwalters@398: _S("input.buffersize"), tomwalters@398: _S("input.samplerate"), tomwalters@398: _S("bmm.freqstart"), tomwalters@398: _S("bmm.freqend"), tomwalters@398: _S("bmm.numchannels"), tomwalters@398: _S("preset.name"), tomwalters@398: _S("preset.title"), tomwalters@398: NULL tomwalters@398: }; tomwalters@398: for (int i = 0; pPlotParams[i]; i++) { tomwalters@398: snprintf(sStr, tomwalters@397: sizeof(sStr)/sizeof(sStr[0]), _S("%s=%s"), tomwalters@398: pPlotParams[i], tomwalters@411: parameters_->GetString(pPlotParams[i])); tomwalters@398: gText2f(fMarL, tomwalters@397: 1-(fMarT+fTextHeight*lineno++), tomwalters@397: sStr); tomwalters@398: } tomwalters@397: } tom@400: } // namespace aimc