tomwalters@397
|
1 // Copyright 2006, Willem van Engen
|
tomwalters@397
|
2 //
|
tomwalters@397
|
3 // AIM-C: A C++ implementation of the Auditory Image Model
|
tomwalters@397
|
4 // http://www.acousticscale.org/AIMC
|
tomwalters@397
|
5 //
|
tomwalters@397
|
6 // Licensed under the Apache License, Version 2.0 (the "License");
|
tomwalters@397
|
7 // you may not use this file except in compliance with the License.
|
tomwalters@397
|
8 // You may obtain a copy of the License at
|
tomwalters@397
|
9 //
|
tomwalters@397
|
10 // http://www.apache.org/licenses/LICENSE-2.0
|
tomwalters@397
|
11 //
|
tomwalters@397
|
12 // Unless required by applicable law or agreed to in writing, software
|
tomwalters@397
|
13 // distributed under the License is distributed on an "AS IS" BASIS,
|
tomwalters@397
|
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
tomwalters@397
|
15 // See the License for the specific language governing permissions and
|
tomwalters@397
|
16 // limitations under the License.
|
tomwalters@397
|
17
|
tomwalters@397
|
18 /*!
|
tomwalters@397
|
19 * \file
|
tomwalters@397
|
20 * \brief Output device for output to a movie
|
tomwalters@397
|
21 *
|
tomwalters@397
|
22 * \author Willem van Engen <cnbh@willem.engen.nl>
|
tomwalters@397
|
23 * \date created 2006/10/16
|
tomwalters@397
|
24 * \version \$Id: GraphicsOutputDeviceMovie.cpp 633 2008-09-11 04:20:16Z tom $
|
tomwalters@397
|
25 */
|
tomwalters@397
|
26
|
tomwalters@397
|
27 /*! \todo
|
tomwalters@397
|
28 * A recent experiment showed that the video was about an audioframe
|
tomwalters@397
|
29 * (something like 30 ms) behind the audio in rdct.wav. It seems odd
|
tomwalters@397
|
30 * to me, since I already output a frame at the beginning to compensate
|
tomwalters@397
|
31 * for the missed buffer.
|
tomwalters@397
|
32 * A solution that would solve this and be a broader improvement, is to
|
tomwalters@397
|
33 * include the source's time when Fire()ing. The outputdevice can then
|
tomwalters@397
|
34 * do audio/video synchronization. In the case of the movie, the very
|
tomwalters@397
|
35 * first gGrab() looks at the time and emits as much empty output frames
|
tomwalters@397
|
36 * as needed until the correct signal time is reached.
|
tomwalters@397
|
37 */
|
tomwalters@397
|
38 #include "Support/Common.h"
|
tomwalters@397
|
39
|
tomwalters@397
|
40 #ifdef _WINDOWS
|
tomwalters@398
|
41 # include <direct.h> // for _mkdir&_rmdir
|
tomwalters@397
|
42 #else
|
tomwalters@398
|
43 # include <sys/types.h>
|
tomwalters@398
|
44 # include <dirent.h> // for opendir&friends
|
tomwalters@397
|
45 #endif
|
tomwalters@397
|
46 #include <stdio.h>
|
tomwalters@397
|
47 #include <string.h>
|
tomwalters@397
|
48
|
tomwalters@397
|
49
|
tomwalters@397
|
50 #include "Output/GraphicsOutputDeviceMovie.h"
|
tomwalters@397
|
51
|
tomwalters@397
|
52 GraphicsOutputDeviceMovie::GraphicsOutputDeviceMovie(Parameters *pParam)
|
tomwalters@398
|
53 : GraphicsOutputDeviceCairo(pParam) { // or GraphicsOutputDevicePlotutils
|
tomwalters@398
|
54 m_sMovieFile[0] = '\0';
|
tomwalters@398
|
55 m_sSoundFile[0] = '\0';
|
tomwalters@397
|
56 }
|
tomwalters@397
|
57
|
tomwalters@397
|
58 bool GraphicsOutputDeviceMovie::Initialize(const char *sSoundFile,
|
tomwalters@397
|
59 const char *sMovieFile) {
|
tomwalters@398
|
60 FILE *f;
|
tomwalters@398
|
61 AIM_ASSERT(sSoundFile);
|
tomwalters@398
|
62 AIM_ASSERT(sMovieFile);
|
tomwalters@397
|
63
|
tomwalters@398
|
64 // Check sound file exists
|
tomwalters@398
|
65 if ((f = fopen(sSoundFile, "r")) == NULL) {
|
tomwalters@398
|
66 AIM_ERROR(_T("Couldn't open sound file '%s' for movie creation."),
|
tomwalters@397
|
67 sSoundFile);
|
tomwalters@398
|
68 return false;
|
tomwalters@398
|
69 }
|
tomwalters@398
|
70 fclose(f);
|
tomwalters@398
|
71 strcpy(m_sSoundFile, sSoundFile);
|
tomwalters@397
|
72
|
tomwalters@398
|
73 // Check movie output file can be made
|
tomwalters@398
|
74 if ( (f=fopen(sMovieFile, "w"))==NULL ) {
|
tomwalters@398
|
75 aimERROR(_T("Couldn't open movie file '%s' to write to."),
|
tomwalters@397
|
76 sMovieFile);
|
tomwalters@398
|
77 return false;
|
tomwalters@398
|
78 }
|
tomwalters@398
|
79 fclose(f);
|
tomwalters@398
|
80 strcpy(m_sMovieFile, sMovieFile);
|
tomwalters@397
|
81
|
tomwalters@398
|
82 // Get a temporary image output directory
|
tomwalters@398
|
83 //! \warning Not really safe ... but windows has no mkdtemp()
|
tomwalters@398
|
84 //! \todo Make build system check for mkdtemp() to use it when available. See TODO.txt.
|
tomwalters@398
|
85 char *sTmpDir = NULL;
|
tomwalters@397
|
86 #ifdef _WINDOWS
|
tomwalters@398
|
87 if ((sTmpDir = _tempnam(NULL, AIM_NAME))
|
tomwalters@397
|
88 && _mkdir(sTmpDir) >= 0) {
|
tomwalters@398
|
89 strcpy(m_sDir, sTmpDir);
|
tomwalters@398
|
90 strcat(m_sDir, "\\"); // Make sure to end with trailing slash
|
tomwalters@398
|
91 } else
|
tomwalters@397
|
92 #else
|
tomwalters@398
|
93 strcpy(m_sDir, "/tmp/"AIM_NAME"-movie.XXXXXX");
|
tomwalters@398
|
94 if (mkdtemp(m_sDir)) {
|
tomwalters@398
|
95 strcat(m_sDir, "/"); // Make sure to end with trailing slash
|
tomwalters@398
|
96 } else
|
tomwalters@397
|
97 #endif
|
tomwalters@398
|
98 {
|
tomwalters@398
|
99 AIM_ERROR(_T("Couldn't create a temporary directory for movie output."));
|
tomwalters@398
|
100 if (sTmpDir) free(sTmpDir);
|
tomwalters@398
|
101 return false;
|
tomwalters@398
|
102 }
|
tomwalters@398
|
103 if (sTmpDir) {
|
tomwalters@397
|
104 free(sTmpDir);
|
tomwalters@397
|
105 }
|
tomwalters@397
|
106
|
tomwalters@398
|
107 // We want png for movie conversion
|
tomwalters@398
|
108 //! \bug This may change the user preference in GUI, hmm what to do? See TODO.txt
|
tomwalters@398
|
109 m_pParam->SetString("output.img.format", "png");
|
tomwalters@398
|
110 //if ( !GraphicsOutputDevicePlotutils::Initialize(m_sDir) ) {
|
tomwalters@398
|
111 if ( !GraphicsOutputDeviceCairo::Initialize(m_sDir) ) {
|
tomwalters@398
|
112 return false;
|
tomwalters@397
|
113 }
|
tomwalters@397
|
114
|
tomwalters@398
|
115 return true;
|
tomwalters@397
|
116 }
|
tomwalters@397
|
117
|
tomwalters@397
|
118 void GraphicsOutputDeviceMovie::Start() {
|
tomwalters@398
|
119 //GraphicsOutputDevicePlotutils::Start();
|
tomwalters@398
|
120 GraphicsOutputDeviceCairo::Start();
|
tomwalters@398
|
121 // Just output a single frame to get audio/video in sync, put params in there
|
tomwalters@398
|
122 gGrab();
|
tomwalters@398
|
123 PlotParameterScreen();
|
tomwalters@398
|
124 gRelease();
|
tomwalters@397
|
125 }
|
tomwalters@397
|
126
|
tomwalters@397
|
127 void GraphicsOutputDeviceMovie::Stop() {
|
tomwalters@398
|
128 // Make sure Plotutils is really done writing.
|
tomwalters@398
|
129 //GraphicsOutputDevicePlotutils::Stop();
|
tomwalters@398
|
130 GraphicsOutputDeviceCairo::Stop();
|
tomwalters@398
|
131 CloseFile();
|
tomwalters@397
|
132
|
tomwalters@397
|
133 #ifdef __WX__
|
tomwalters@398
|
134 // GUI only: popup dialog
|
tomwalters@397
|
135 #else
|
tomwalters@398
|
136 printf("Generating movie ... \n");
|
tomwalters@397
|
137 #endif
|
tomwalters@398
|
138 AIM_ASSERT(m_pParam);
|
tomwalters@398
|
139 // Convert images and sound file to a movie
|
tomwalters@398
|
140 //! \warning Movie files are overwritten without warning
|
tomwalters@398
|
141 //! \bug ffmpeg only works with colour images, not with bw. So make sure to not use bw only in drawing..
|
tomwalters@398
|
142 // Always convert to audio stream of 44.1kHz or problems may occur in playing or conversio.
|
tomwalters@398
|
143 float fFps = 1000.0 / m_pParam->GetFloat("output.frameperiod");
|
tomwalters@398
|
144 char sffmpegPath[1024];
|
tomwalters@398
|
145 if (!m_pParam->IsSet("output.ffmpeg_path")) {
|
tomwalters@397
|
146 strcpy(sffmpegPath,"ffmpeg");
|
tomwalters@397
|
147 } else {
|
tomwalters@398
|
148 strcpy(sffmpegPath, m_pParam->GetString("output.ffmpeg_path"));
|
tomwalters@397
|
149 }
|
tomwalters@397
|
150 char sCodecOptions[1024];
|
tomwalters@398
|
151 if (!m_pParam->IsSet("output.ffmpeg_codec_options")) {
|
tomwalters@397
|
152 strcpy(sCodecOptions,"");
|
tomwalters@397
|
153 } else {
|
tomwalters@398
|
154 strcpy(sCodecOptions, m_pParam->GetString("output.ffmpeg_codec_options"));
|
tomwalters@397
|
155 }
|
tomwalters@397
|
156
|
tomwalters@398
|
157 char sCmdLine[1024]; //!\todo check that snprintf does not want a larger buffer
|
tomwalters@398
|
158 snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]),
|
tomwalters@398
|
159 "%s -r %.2f -y -i \"%s\" -i \"%s%%06d.png\" "
|
tomwalters@398
|
160 "-title \"%s\" -comment \"Generated by "AIM_NAME" "AIM_VERSION_STRING"\" "
|
tomwalters@398
|
161 "-sameq -r %.2f -ar 44100 -acodec pcm_s16le %s \"%s\"",
|
tomwalters@398
|
162 sffmpegPath, fFps, m_sSoundFile, m_sDir,
|
tomwalters@398
|
163 m_pParam->GetString("output.movie.title"),
|
tomwalters@398
|
164 fFps, sCodecOptions, m_sMovieFile);
|
tomwalters@397
|
165 printf(sCmdLine);
|
tomwalters@397
|
166 printf("\n");
|
tomwalters@398
|
167 if (system(sCmdLine)) {
|
tomwalters@398
|
168 AIM_ERROR(_T("Couldn't create movie output."));
|
tomwalters@398
|
169 }
|
tomwalters@397
|
170
|
tomwalters@397
|
171 #ifdef __WX__
|
tomwalters@398
|
172 // GUI only: close dialog again
|
tomwalters@397
|
173 #endif
|
tomwalters@398
|
174 // Remove files in temporary directory and the dir itself
|
tomwalters@398
|
175 //! \todo make portable function, possibly decided on by build system
|
tomwalters@397
|
176 #ifdef _WINDOWS
|
tomwalters@398
|
177 HANDLE hList;
|
tomwalters@398
|
178 WIN32_FIND_DATA FileData;
|
tomwalters@398
|
179 snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]), "%s/*.*", m_sDir);
|
tomwalters@398
|
180 if ((hList = FindFirstFile(sCmdLine, &FileData)) == INVALID_HANDLE_VALUE) {
|
tomwalters@398
|
181 AIM_ERROR(_T("Couldn't remove files from temporary directory."));
|
tomwalters@398
|
182 return;
|
tomwalters@398
|
183 }
|
tomwalters@398
|
184 bool bRMfinished = false;
|
tomwalters@398
|
185 while (!bRMfinished) {
|
tomwalters@398
|
186 snprintf(sCmdLine,
|
tomwalters@397
|
187 sizeof(sCmdLine)/sizeof(sCmdLine[0]),
|
tomwalters@398
|
188 "%s%s",
|
tomwalters@397
|
189 m_sDir,
|
tomwalters@397
|
190 FileData.cFileName);
|
tomwalters@398
|
191 remove(sCmdLine);
|
tomwalters@398
|
192 if (!FindNextFile(hList, &FileData) && GetLastError() == ERROR_NO_MORE_FILES) {
|
tomwalters@398
|
193 bRMfinished = true;
|
tomwalters@397
|
194 }
|
tomwalters@398
|
195 }
|
tomwalters@398
|
196 FindClose(hList);
|
tomwalters@398
|
197 _rmdir(m_sDir);
|
tomwalters@397
|
198 #else
|
tomwalters@398
|
199 DIR *dir;
|
tomwalters@398
|
200 struct dirent *dirent;
|
tomwalters@398
|
201 if (!(dir = opendir(m_sDir))) {
|
tomwalters@398
|
202 AIM_ERROR(_T("Couldn't remove files in temporary directory."));
|
tomwalters@398
|
203 return;
|
tomwalters@398
|
204 }
|
tomwalters@398
|
205 while (dirent = readdir(dir)) {
|
tomwalters@398
|
206 snprintf(sCmdLine,
|
tomwalters@397
|
207 sizeof(sCmdLine)/sizeof(sCmdLine[0]),
|
tomwalters@398
|
208 "%s%s",
|
tomwalters@397
|
209 m_sDir,
|
tomwalters@397
|
210 dirent->d_name);
|
tomwalters@398
|
211 unlink(sCmdLine);
|
tomwalters@398
|
212 }
|
tomwalters@398
|
213 closedir(dir);
|
tomwalters@398
|
214 rmdir(m_sDir);
|
tomwalters@397
|
215 #endif
|
tomwalters@397
|
216 }
|
tomwalters@397
|
217
|
tomwalters@397
|
218 void GraphicsOutputDeviceMovie::PlotParameterScreen() {
|
tomwalters@398
|
219 AIM_ASSERT(m_pParam);
|
tomwalters@398
|
220 char sStr[50];
|
tomwalters@398
|
221 int lineno = 1;
|
tomwalters@397
|
222
|
tomwalters@398
|
223 float fMarL = m_pParam->GetFloat(_S("graph.margin.left"));
|
tomwalters@398
|
224 float fMarT = m_pParam->GetFloat(_S("graph.margin.top"));
|
tomwalters@398
|
225 float fTextHeight = 1.0f / 50.0f * 1.2; // change this when fontsizing is there!
|
tomwalters@397
|
226
|
tomwalters@398
|
227 gText2f(fMarL, 1-(fMarT+fTextHeight*lineno++),
|
tomwalters@397
|
228 _S("AIM-C"));
|
tomwalters@398
|
229 gText2f(fMarL,
|
tomwalters@397
|
230 1-(fMarT+fTextHeight*lineno++),
|
tomwalters@397
|
231 _S("(c) 2006-2010, Thomas Walters, Willem van Engen"));
|
tomwalters@398
|
232 gText2f(fMarL,
|
tomwalters@397
|
233 1-(fMarT+fTextHeight*lineno++),
|
tomwalters@397
|
234 _S("http://aimc.acousticscale.org/"));
|
tomwalters@398
|
235 lineno++;
|
tomwalters@397
|
236
|
tomwalters@398
|
237 static const char *pPlotParams[] = {
|
tomwalters@398
|
238 _S("input.buffersize"),
|
tomwalters@398
|
239 _S("input.samplerate"),
|
tomwalters@398
|
240 _S("bmm.freqstart"),
|
tomwalters@398
|
241 _S("bmm.freqend"),
|
tomwalters@398
|
242 _S("bmm.numchannels"),
|
tomwalters@398
|
243 _S("preset.name"),
|
tomwalters@398
|
244 _S("preset.title"),
|
tomwalters@398
|
245 NULL
|
tomwalters@398
|
246 };
|
tomwalters@398
|
247 for (int i = 0; pPlotParams[i]; i++) {
|
tomwalters@398
|
248 snprintf(sStr,
|
tomwalters@397
|
249 sizeof(sStr)/sizeof(sStr[0]), _S("%s=%s"),
|
tomwalters@398
|
250 pPlotParams[i],
|
tomwalters@397
|
251 m_pParam->GetString(pPlotParams[i]));
|
tomwalters@398
|
252 gText2f(fMarL,
|
tomwalters@397
|
253 1-(fMarT+fTextHeight*lineno++),
|
tomwalters@397
|
254 sStr);
|
tomwalters@398
|
255 }
|
tomwalters@397
|
256 }
|