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