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