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