1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include "NstIoFile.hpp"
26 #include "NstIoStream.hpp"
27 #include "NstObjectPod.hpp"
28 #include "NstWindowUser.hpp"
29 #include "NstManager.hpp"
30 #include "NstManagerAviConverter.hpp"
31 #include "NstManagerMovie.hpp"
32 
33 #if NST_MSVC
34 #pragma comment(lib,"vfw32")
35 #endif
36 
37 #if NST_MSVC >= 1200
38 #pragma warning( push )
39 #pragma warning( disable : 4201 )
40 #endif
41 
42 #include <vfw.h>
43 
44 #if NST_MSVC >= 1200
45 #pragma warning( pop )
46 #endif
47 
48 namespace Nestopia
49 {
50 	namespace Managers
51 	{
AviConverter(Emulator & e)52 		AviConverter::AviConverter(Emulator& e)
53 		: emulator(e), on(e.IsOn())
54 		{
55 			Nes::Video::Output::lockCallback.Get( nesVideoLockFunc, nesVideoLockData );
56 			Nes::Video::Output::unlockCallback.Get( nesVideoUnlockFunc, nesVideoUnlockData );
57 			Nes::Sound::Output::lockCallback.Get( nesSoundLockFunc, nesSoundLockData );
58 			Nes::Sound::Output::unlockCallback.Get( nesSoundUnlockFunc, nesSoundUnlockData );
59 			Nes::Movie::eventCallback.Get( nesMovieEventFunc, nesMovieEventData );
60 
61 			Nes::Video::Output::lockCallback.Unset();
62 			Nes::Video::Output::unlockCallback.Unset();
63 			Nes::Sound::Output::lockCallback.Unset();
64 			Nes::Sound::Output::unlockCallback.Unset();
65 			Nes::Movie::eventCallback.Unset();
66 
67 			Nes::Video(emulator).GetRenderState( renderState );
68 
69 			if (!on)
70 				emulator.Power( true );
71 
72 			if (emulator.IsOn())
73 				emulator.SaveState( saveState, false, Emulator::QUIETLY );
74 		}
75 
76 		#ifdef NST_MSVC_OPTIMIZE
77 		#pragma optimize("t", on)
78 		#endif
79 
Record(const Path & moviePath,const Path & aviPath) const80 		uint AviConverter::Record(const Path& moviePath,const Path& aviPath) const
81 		{
82 			if (moviePath.Empty() || aviPath.Empty() || !emulator.IsOn())
83 				return 0;
84 
85 			try
86 			{
87 				class Movie
88 				{
89 					Emulator& emulator;
90 					Io::Stream::In stream;
91 
92 				public:
93 
94 					explicit Movie(Emulator& e,const Path& path)
95 					: emulator(e), stream(path) {}
96 
97 					uint Play()
98 					{
99 						const Nes::Result result = Nes::Movie(emulator).Play( stream );
100 						return NES_FAILED(result) ? Emulator::ResultToString( result ) : 0;
101 					}
102 
103 					~Movie()
104 					{
105 						Nes::Movie(emulator).Stop();
106 					}
107 				};
108 
109 				struct BitmapInfo : Object::Pod<BITMAPINFOHEADER>
110 				{
111 					BitmapInfo(const uint width,const uint height)
112 					{
113 						NST_ASSERT( width && height );
114 
115 						biSize        = sizeof(BITMAPINFOHEADER);
116 						biWidth       = width;
117 						biHeight      = height << uint(width / height >= 2);
118 						biPlanes      = 1;
119 						biBitCount    = VIDEO_BPP;
120 						biCompression = BI_RGB;
121 						biSizeImage   = VIDEO_BPP/8 * biWidth * biHeight;
122 					}
123 				};
124 
125 				struct CompressVars : Object::Pod<COMPVARS>
126 				{
127 					CompressVars()
128 					{
129 						cbSize = sizeof(COMPVARS);
130 					}
131 
132 					~CompressVars()
133 					{
134 						::ICCompressorFree( this );
135 					}
136 
137 					BOOL Choose(BitmapInfo& bitmapInfo)
138 					{
139 						char title[] = "Choose Video Codec";
140 
141 						return ::ICCompressorChoose
142 						(
143 							Application::Instance::GetMainWindow(),
144 							ICMF_CHOOSE_DATARATE|ICMF_CHOOSE_KEYFRAME,
145 							&bitmapInfo,
146 							NULL,
147 							this,
148 							title
149 						);
150 					}
151 				};
152 
153 				class File
154 				{
155 					PAVIFILE file;
156 					mutable wcstring name;
157 
158 				public:
159 
160 					explicit File(wcstring n)
161 					: name(n)
162 					{
163 						NST_ASSERT( name && *name );
164 
165 						::AVIFileInit();
166 
167 						if (::AVIFileOpen( &file, name, OF_WRITE|OF_CREATE, NULL ) != AVIERR_OK)
168 							file = NULL;
169 					}
170 
171 					operator PAVIFILE() const
172 					{
173 						return file;
174 					}
175 
176 					void SetSuccess() const
177 					{
178 						name = NULL;
179 					}
180 
181 					~File()
182 					{
183 						if (file)
184 						{
185 							::AVIFileRelease( file );
186 
187 							if (name)
188 								::DeleteFile( name );
189 						}
190 
191 						::AVIFileExit();
192 					}
193 				};
194 
195 				class Stream
196 				{
197 					PAVISTREAM stream;
198 
199 				public:
200 
201 					Stream()
202 					: stream(NULL) {}
203 
204 					Stream(const File* avi,AVISTREAMINFO& info)
205 					{
206 						NST_ASSERT( !avi || bool(*avi) );
207 
208 						if (!avi || ::AVIFileCreateStream( *avi, &stream, &info ) != AVIERR_OK)
209 							stream = NULL;
210 					}
211 
212 					Stream(const File& avi,AVISTREAMINFO& info)
213 					{
214 						NST_ASSERT( bool(avi) );
215 
216 						if (::AVIFileCreateStream( avi, &stream, &info ) != AVIERR_OK)
217 							stream = NULL;
218 					}
219 
220 					Stream(const Stream& base,AVICOMPRESSOPTIONS& options)
221 					{
222 						if (::AVIMakeCompressedStream( &stream, base, &options, NULL ) != AVIERR_OK)
223 							stream = NULL;
224 					}
225 
226 					operator PAVISTREAM() const
227 					{
228 						return stream;
229 					}
230 
231 					bool SetFormat(void* info,uint size) const
232 					{
233 						NST_ASSERT( stream && info && size );
234 
235 						return ::AVIStreamSetFormat( stream, 0, info, size ) == AVIERR_OK;
236 					}
237 
238 					~Stream()
239 					{
240 						if (stream)
241 							::AVIStreamRelease( stream );
242 					}
243 				};
244 
245 				struct CompressOptions : Object::Pod<AVICOMPRESSOPTIONS>
246 				{
247 					CompressOptions(const CompressVars& compressVars)
248 					{
249 						fccType          = streamtypeVIDEO;
250 						fccHandler       = compressVars.fccHandler;
251 						dwKeyFrameEvery  = compressVars.lKey;
252 						dwQuality        = compressVars.lQ;
253 						dwBytesPerSecond = compressVars.lDataRate;
254 
255 						if (compressVars.lDataRate > 0)
256 							dwFlags |= AVICOMPRESSF_DATARATE;
257 
258 						if (compressVars.lKey > 0)
259 							dwFlags |= AVICOMPRESSF_KEYFRAMES;
260 					}
261 				};
262 
263 				struct VideoInfo : Object::Pod<AVISTREAMINFO>
264 				{
265 					VideoInfo(Emulator& emulator,const BitmapInfo& bitmapInfo,const CompressVars& compressVars)
266 					{
267 						fccType               = streamtypeVIDEO;
268 						fccHandler            = compressVars.fccHandler;
269 						dwScale               = 1;
270 						dwRate                = emulator.GetDefaultSpeed();
271 						dwQuality             = compressVars.lQ;
272 						dwSuggestedBufferSize = bitmapInfo.biSizeImage;
273 						rcFrame.right         = bitmapInfo.biWidth;
274 						rcFrame.bottom        = bitmapInfo.biHeight;
275 					}
276 				};
277 
278 				struct WaveFormat : Object::Pod<WAVEFORMATEX>
279 				{
280 					WaveFormat(const Nes::Sound nes)
281 					{
282 						wFormatTag      = WAVE_FORMAT_PCM;
283 						nSamplesPerSec  = nes.GetSampleRate();
284 						wBitsPerSample  = nes.GetSampleBits();
285 						nChannels       = 1 + (nes.GetSpeaker() == Nes::Sound::SPEAKER_STEREO);
286 						nBlockAlign     = wBitsPerSample / 8 * nChannels;
287 						nAvgBytesPerSec = nSamplesPerSec * nBlockAlign;
288 					}
289 				};
290 
291 				struct SoundInfo : Object::Pod<AVISTREAMINFO>
292 				{
293 					SoundInfo(const WaveFormat& waveFormat)
294 					{
295 						fccType         = streamtypeAUDIO;
296 						fccHandler      = 1;
297 						dwScale         = waveFormat.nBlockAlign;
298 						dwInitialFrames = 1;
299 						dwRate          = waveFormat.nAvgBytesPerSec;
300 						dwQuality       = DWORD(-1);
301 						dwSampleSize    = waveFormat.nBlockAlign;
302 					}
303 				};
304 
305 				struct Buffer
306 				{
307 					uchar* const ptr;
308 					const uint size;
309 
310 					Buffer(uint n) : ptr(n ? new uchar [n] : NULL), size(n) {}
311 					~Buffer() { delete [] ptr; }
312 				};
313 
314 				Movie movie( emulator, moviePath );
315 
316 				if (const uint ids = movie.Play())
317 					return ids;
318 
319 				BitmapInfo bitmapInfo( renderState.width, renderState.height );
320 				CompressVars compressVars;
321 
322 				if (!compressVars.Choose( bitmapInfo ))
323 					return 0;
324 
325 				Application::Instance::Waiter wait;
326 
327 				const File avi( aviPath.Ptr() );
328 
329 				if (!avi)
330 					return IDS_AVI_WRITE_ERR;
331 
332 				VideoInfo videoInfo( emulator, bitmapInfo, compressVars );
333 				const Stream video( avi, videoInfo );
334 
335 				if (!video)
336 					return IDS_AVI_WRITE_ERR;
337 
338 				CompressOptions compressOptions( compressVars );
339 				const Stream compressor( video, compressOptions );
340 
341 				if (!compressor)
342 					return IDS_AVI_WRITE_ERR;
343 
344 				if (!compressor.SetFormat( &bitmapInfo, bitmapInfo.biSize + bitmapInfo.biClrUsed * sizeof(RGBQUAD) ))
345 					return IDS_AVI_WRITE_ERR;
346 
347 				Window::User::Inform( IDS_AVI_WRITE_INFO );
348 				Application::Instance::Locker locker;
349 
350 				WaveFormat waveFormat( emulator );
351 				SoundInfo soundInfo( waveFormat );
352 				const Stream sound( Nes::Sound(emulator).IsAudible() ? &avi : NULL, soundInfo );
353 
354 				if (Nes::Sound(emulator).IsAudible())
355 				{
356 					if (!sound)
357 						return IDS_AVI_WRITE_ERR;
358 
359 					if (!sound.SetFormat( &waveFormat, sizeof(WAVEFORMATEX) ))
360 						return IDS_AVI_WRITE_ERR;
361 				}
362 
363 				Buffer pixels( bitmapInfo.biSizeImage );
364 				Buffer samples( waveFormat.nAvgBytesPerSec / videoInfo.dwRate );
365 
366 				// picture will be stored upside down, adjust the pitch
367 				Nes::Video::Output videoOutput( pixels.ptr + (VIDEO_BPP/8 * bitmapInfo.biWidth * (renderState.height-1)), -int(VIDEO_BPP/8 * bitmapInfo.biWidth) );
368 				Nes::Sound::Output soundOutput( samples.ptr, samples.size / waveFormat.nBlockAlign );
369 
370 				{
371 					Nes::Video::RenderState tmp;
372 					Nes::Video(emulator).GetRenderState( tmp );
373 
374 					tmp.bits.count = VIDEO_BPP;
375 					tmp.bits.mask.r = VIDEO_R_MASK;
376 					tmp.bits.mask.g = VIDEO_G_MASK;
377 					tmp.bits.mask.b = VIDEO_B_MASK;
378 
379 					Nes::Video(emulator).SetRenderState( tmp );
380 				}
381 
382 				for (uint frame=0, sample=0, size=0; Nes::Movie(emulator).IsPlaying() && size < MAX_FILE_SIZE; ++frame, sample += soundOutput.length[0])
383 				{
384 					::Sleep( 0 );
385 
386 					if (locker.CheckInput( VK_ESCAPE ))
387 						return IDS_AVI_WRITE_ABORT;
388 
389 					if (NES_FAILED(emulator.Nes::Emulator::Execute( &videoOutput, sound ? &soundOutput : NULL, NULL )))
390 						return IDS_AVI_WRITE_ERR;
391 
392 					if (bitmapInfo.biHeight == renderState.height * 2)
393 					{
394 						const uint pitch = -videoOutput.pitch;
395 
396 						for (uchar *src=pixels.ptr + (pitch * (renderState.height-1)), *dst = pixels.ptr + (bitmapInfo.biSizeImage - pitch); ; src -= pitch)
397 						{
398 							std::memcpy( dst, src, pitch );
399 							dst -= pitch;
400 
401 							if (dst == pixels.ptr)
402 								break;
403 
404 							std::memcpy( dst, src, pitch );
405 							dst -= pitch;
406 						}
407 					}
408 
409 					long written[2] = {0,0};
410 
411 					if
412 					(
413 						(::AVIStreamWrite( compressor, frame, 1, pixels.ptr, pixels.size, AVIIF_KEYFRAME, NULL, written+0 ) != AVIERR_OK) ||
414 						(sound && ::AVIStreamWrite( sound, sample, soundOutput.length[0], samples.ptr, samples.size, 0, NULL, written+1 ) != AVIERR_OK)
415 					)
416 						return IDS_AVI_WRITE_ERR;
417 
418 					size += (written[0] > 0 ? written[0] : 0) + (written[1] > 0 ? written[1] : 0);
419 				}
420 
421 				avi.SetSuccess();
422 
423 				return IDS_AVI_WRITE_FINISHED;
424 			}
425 			catch (Io::File::Exception id)
426 			{
427 				return id;
428 			}
429 			catch (...)
430 			{
431 				return IDS_ERR_GENERIC;
432 			}
433 		}
434 
435 		#ifdef NST_MSVC_OPTIMIZE
436 		#pragma optimize("", on)
437 		#endif
438 
~AviConverter()439 		AviConverter::~AviConverter()
440 		{
441 			if (saveState.Size())
442 				emulator.LoadState( saveState, Emulator::QUIETLY );
443 
444 			if (!on)
445 				emulator.Power( false );
446 
447 			Nes::Video::Output::lockCallback.Set( nesVideoLockFunc, nesVideoLockData );
448 			Nes::Video::Output::unlockCallback.Set( nesVideoUnlockFunc, nesVideoUnlockData );
449 			Nes::Sound::Output::lockCallback.Set( nesSoundLockFunc, nesSoundLockData );
450 			Nes::Sound::Output::unlockCallback.Set( nesSoundUnlockFunc, nesSoundUnlockData );
451 			Nes::Movie::eventCallback.Set( nesMovieEventFunc, nesMovieEventData );
452 
453 			Nes::Video(emulator).SetRenderState( renderState );
454 
455 			Application::Instance::GetMainWindow().Redraw();
456 		}
457 	}
458 }
459