1 /*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5 \*****************************************************************************/
6
7 /***********************************************************************************
8 SNES9X for Mac OS (c) Copyright John Stiles
9
10 Snes9x for Mac OS X
11
12 (c) Copyright 2001 - 2011 zones
13 (c) Copyright 2002 - 2005 107
14 (c) Copyright 2002 PB1400c
15 (c) Copyright 2004 Alexander and Sander
16 (c) Copyright 2004 - 2005 Steven Seeger
17 (c) Copyright 2005 Ryan Vogt
18 ***********************************************************************************/
19
20
21 #include "snes9x.h"
22 #include "memmap.h"
23 #include "apu.h"
24
25 #include <QuickTime/QuickTime.h>
26
27 #include "mac-prefix.h"
28 #include "mac-gworld.h"
29 #include "mac-os.h"
30 #include "mac-screenshot.h"
31 #include "mac-quicktime.h"
32
33 #define kMovDoubleSize (1 << 0)
34 #define kMovExtendedHeight (1 << 1)
35
36 static void CheckError (OSStatus, int);
37 static void MacQTOpenVideoComponent (ComponentInstance *);
38 static void MacQTCloseVideoComponent (ComponentInstance);
39 static OSStatus WriteFrameCallBack (void *, ICMCompressionSessionRef, OSStatus, ICMEncodedFrameRef, void *);
40
41 typedef struct
42 {
43 Movie movie;
44 Track vTrack, sTrack;
45 Media vMedia, sMedia;
46 ComponentInstance vci;
47 SoundDescriptionHandle soundDesc;
48 DataHandler dataHandler;
49 Handle soundBuffer;
50 Handle dataRef;
51 OSType dataRefType;
52 CVPixelBufferPoolRef pool;
53 ICMCompressionSessionRef session;
54 ICMCompressionSessionOptionsRef option;
55 CGImageRef srcImage;
56 TimeValue64 timeStamp;
57 long keyFrame, keyFrameCount;
58 long frameSkip, frameSkipCount;
59 int width, height;
60 int soundBufferSize;
61 int samplesPerSec;
62 } MacQTState;
63
64 static MacQTState sqt;
65
66
CheckError(OSStatus err,int n)67 static void CheckError (OSStatus err, int n)
68 {
69 if (err != noErr)
70 {
71 char mes[32];
72
73 sprintf(mes, "quicktime %02d", n);
74 QuitWithFatalError(err, mes);
75 }
76 }
77
MacQTOpenVideoComponent(ComponentInstance * rci)78 static void MacQTOpenVideoComponent (ComponentInstance *rci)
79 {
80 OSStatus err;
81 ComponentInstance ci;
82 CFDataRef data;
83
84 ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
85
86 data = (CFDataRef) CFPreferencesCopyAppValue(CFSTR("QTVideoSetting"), kCFPreferencesCurrentApplication);
87 if (data)
88 {
89 CFIndex len;
90 Handle hdl;
91
92 len = CFDataGetLength(data);
93 hdl = NewHandleClear((Size) len);
94 if (MemError() == noErr)
95 {
96 HLock(hdl);
97 CFDataGetBytes(data, CFRangeMake(0, len), (unsigned char *) *hdl);
98 err = SCSetInfo(ci, scSettingsStateType, &hdl);
99
100 DisposeHandle(hdl);
101 }
102
103 CFRelease(data);
104 }
105 else
106 {
107 SCSpatialSettings ss;
108 SCTemporalSettings ts;
109
110 ss.codecType = kAnimationCodecType;
111 ss.codec = 0;
112 ss.depth = 16;
113 ss.spatialQuality = codecMaxQuality;
114 err = SCSetInfo(ci, scSpatialSettingsType, &ss);
115
116 ts.frameRate = FixRatio(Memory.ROMFramesPerSecond, 1);
117 ts.keyFrameRate = Memory.ROMFramesPerSecond;
118 ts.temporalQuality = codecMaxQuality;
119 err = SCSetInfo(ci, scTemporalSettingsType, &ts);
120 }
121
122 *rci = ci;
123 }
124
MacQTCloseVideoComponent(ComponentInstance ci)125 static void MacQTCloseVideoComponent (ComponentInstance ci)
126 {
127 OSStatus err;
128
129 err = CloseComponent(ci);
130 }
131
MacQTVideoConfig(void)132 void MacQTVideoConfig (void)
133 {
134 OSStatus err;
135 ComponentInstance ci;
136
137 MacQTOpenVideoComponent(&ci);
138
139 long flag;
140 flag = scListEveryCodec | scAllowZeroKeyFrameRate | scDisableFrameRateItem | scAllowEncodingWithCompressionSession;
141 err = SCSetInfo(ci, scPreferenceFlagsType, &flag);
142
143 SCWindowSettings ws;
144 ws.size = sizeof(SCWindowSettings);
145 ws.windowRefKind = scWindowRefKindCarbon;
146 ws.parentWindow = NULL;
147 err = SCSetInfo(ci, scWindowOptionsType, &ws);
148
149 err = SCRequestSequenceSettings(ci);
150 if (err == noErr)
151 {
152 CFDataRef data;
153 Handle hdl;
154
155 err = SCGetInfo(ci, scSettingsStateType, &hdl);
156 if (err == noErr)
157 {
158 HLock(hdl);
159 data = CFDataCreate(kCFAllocatorDefault, (unsigned char *) *hdl, GetHandleSize(hdl));
160 if (data)
161 {
162 CFPreferencesSetAppValue(CFSTR("QTVideoSetting"), data, kCFPreferencesCurrentApplication);
163 CFRelease(data);
164 }
165
166 DisposeHandle(hdl);
167 }
168 }
169
170 MacQTCloseVideoComponent(ci);
171 }
172
MacQTStartRecording(char * path)173 void MacQTStartRecording (char *path)
174 {
175 OSStatus err;
176 CFStringRef str;
177 CFURLRef url;
178
179 memset(&sqt, 0, sizeof(sqt));
180
181 // storage
182
183 str = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
184 url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
185 err = QTNewDataReferenceFromCFURL(url, 0, &(sqt.dataRef), &(sqt.dataRefType));
186 CheckError(err, 21);
187 CFRelease(url);
188 CFRelease(str);
189
190 err = CreateMovieStorage(sqt.dataRef, sqt.dataRefType, 'TVOD', smSystemScript, createMovieFileDeleteCurFile | newMovieActive, &(sqt.dataHandler), &(sqt.movie));
191 CheckError(err, 22);
192
193 // video
194
195 MacQTOpenVideoComponent(&(sqt.vci));
196
197 long flag;
198 SCTemporalSettings ts;
199
200 flag = scAllowEncodingWithCompressionSession;
201 err = SCSetInfo(sqt.vci, scPreferenceFlagsType, &flag);
202
203 err = SCGetInfo(sqt.vci, scTemporalSettingsType, &ts);
204 ts.frameRate = FixRatio(Memory.ROMFramesPerSecond, 1);
205 if (ts.keyFrameRate < 1)
206 ts.keyFrameRate = Memory.ROMFramesPerSecond;
207 sqt.keyFrame = sqt.keyFrameCount = ts.keyFrameRate;
208 sqt.frameSkip = sqt.frameSkipCount = (macQTMovFlag & 0xFF00) >> 8;
209 err = SCSetInfo(sqt.vci, scTemporalSettingsType, &ts);
210
211 sqt.width = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * SNES_WIDTH;
212 sqt.height = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * ((macQTMovFlag & kMovExtendedHeight) ? SNES_HEIGHT_EXTENDED : SNES_HEIGHT);
213
214 sqt.srcImage = NULL;
215 sqt.timeStamp = 0;
216
217 SCSpatialSettings ss;
218 ICMEncodedFrameOutputRecord record;
219 ICMMultiPassStorageRef nullStorage = NULL;
220
221 err = SCCopyCompressionSessionOptions(sqt.vci, &(sqt.option));
222 CheckError(err, 61);
223 err = ICMCompressionSessionOptionsSetProperty(sqt.option, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_MultiPassStorage, sizeof(ICMMultiPassStorageRef), &nullStorage);
224
225 record.encodedFrameOutputCallback = WriteFrameCallBack;
226 record.encodedFrameOutputRefCon = NULL;
227 record.frameDataAllocator = NULL;
228 err = SCGetInfo(sqt.vci, scSpatialSettingsType, &ss);
229 err = ICMCompressionSessionCreate(kCFAllocatorDefault, sqt.width, sqt.height, ss.codecType, Memory.ROMFramesPerSecond, sqt.option, NULL, &record, &(sqt.session));
230 CheckError(err, 62);
231
232 CFMutableDictionaryRef dic;
233 CFNumberRef val;
234 OSType pix = k16BE555PixelFormat;
235 int row = sqt.width * 2;
236
237 dic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
238
239 val = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix);
240 CFDictionaryAddValue(dic, kCVPixelBufferPixelFormatTypeKey, val);
241 CFRelease(val);
242
243 val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.width));
244 CFDictionaryAddValue(dic, kCVPixelBufferWidthKey, val);
245 CFRelease(val);
246
247 val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.height));
248 CFDictionaryAddValue(dic, kCVPixelBufferHeightKey, val);
249 CFRelease(val);
250
251 val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &row);
252 CFDictionaryAddValue(dic, kCVPixelBufferBytesPerRowAlignmentKey, val);
253 CFRelease(val);
254
255 CFDictionaryAddValue(dic, kCVPixelBufferCGImageCompatibilityKey, kCFBooleanTrue);
256 CFDictionaryAddValue(dic, kCVPixelBufferCGBitmapContextCompatibilityKey, kCFBooleanTrue);
257
258 err = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, dic, &(sqt.pool));
259 CheckError(err, 63);
260
261 CFRelease(dic);
262
263 sqt.vTrack = NewMovieTrack(sqt.movie, FixRatio(sqt.width, 1), FixRatio(sqt.height, 1), kNoVolume);
264 CheckError(GetMoviesError(), 23);
265
266 sqt.vMedia = NewTrackMedia(sqt.vTrack, VideoMediaType, Memory.ROMFramesPerSecond, NULL, 0);
267 CheckError(GetMoviesError(), 24);
268
269 err = BeginMediaEdits(sqt.vMedia);
270 CheckError(err, 25);
271
272 // sound
273
274 sqt.soundDesc = (SoundDescriptionHandle) NewHandleClear(sizeof(SoundDescription));
275 CheckError(MemError(), 26);
276
277 (**sqt.soundDesc).descSize = sizeof(SoundDescription);
278 #ifdef __BIG_ENDIAN__
279 (**sqt.soundDesc).dataFormat = Settings.SixteenBitSound ? k16BitBigEndianFormat : k8BitOffsetBinaryFormat;
280 #else
281 (**sqt.soundDesc).dataFormat = Settings.SixteenBitSound ? k16BitLittleEndianFormat : k8BitOffsetBinaryFormat;
282 #endif
283 (**sqt.soundDesc).numChannels = Settings.Stereo ? 2 : 1;
284 (**sqt.soundDesc).sampleSize = Settings.SixteenBitSound ? 16 : 8;
285 (**sqt.soundDesc).sampleRate = (UnsignedFixed) FixRatio(Settings.SoundPlaybackRate, 1);
286
287 sqt.samplesPerSec = Settings.SoundPlaybackRate / Memory.ROMFramesPerSecond;
288
289 sqt.soundBufferSize = sqt.samplesPerSec;
290 if (Settings.SixteenBitSound)
291 sqt.soundBufferSize <<= 1;
292 if (Settings.Stereo)
293 sqt.soundBufferSize <<= 1;
294
295 sqt.soundBuffer = NewHandleClear(sqt.soundBufferSize);
296 CheckError(MemError(), 27);
297 HLock(sqt.soundBuffer);
298
299 sqt.sTrack = NewMovieTrack(sqt.movie, 0, 0, kFullVolume);
300 CheckError(GetMoviesError(), 28);
301
302 sqt.sMedia = NewTrackMedia(sqt.sTrack, SoundMediaType, Settings.SoundPlaybackRate, NULL, 0);
303 CheckError(GetMoviesError(), 29);
304
305 err = BeginMediaEdits(sqt.sMedia);
306 CheckError(err, 30);
307 }
308
MacQTRecordFrame(int width,int height)309 void MacQTRecordFrame (int width, int height)
310 {
311 OSStatus err;
312
313 // video
314
315 if (sqt.frameSkipCount == sqt.frameSkip)
316 {
317 CVPixelBufferRef buf;
318
319 err = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, sqt.pool, &buf);
320 if (err == noErr)
321 {
322 CGColorSpaceRef color;
323 CGContextRef ctx;
324 uint16 *p;
325
326 err = CVPixelBufferLockBaseAddress(buf, 0);
327 p = (uint16 *) CVPixelBufferGetBaseAddress(buf);
328
329 color = CGColorSpaceCreateDeviceRGB();
330 ctx = CGBitmapContextCreate((void *) p, sqt.width, sqt.height, 5, sqt.width * 2, color, kCGImageAlphaNoneSkipFirst | ((systemVersion >= 0x1040) ? kCGBitmapByteOrder16Host : 0));
331 CGContextSetShouldAntialias(ctx, false);
332
333 if (sqt.srcImage)
334 CGImageRelease(sqt.srcImage);
335 sqt.srcImage = CreateGameScreenCGImage();
336
337 CGRect dst = CGRectMake(0.0f, 0.0f, (float) sqt.width, (float) sqt.height);
338
339 if ((!(height % SNES_HEIGHT_EXTENDED)) && (!(macQTMovFlag & kMovExtendedHeight)))
340 {
341 CGRect src;
342
343 src.size.width = (float) width;
344 src.size.height = (float) ((height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
345 src.origin.x = (float) 0;
346 src.origin.y = (float) height - src.size.height;
347 DrawSubCGImage(ctx, sqt.srcImage, src, dst);
348 }
349 else
350 if ((sqt.height << 1) % height)
351 {
352 CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);
353 CGContextFillRect(ctx, dst);
354
355 float dh = (float) ((sqt.height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
356 float ofs = (float) ((int) ((drawoverscan ? 1.0 : 0.5) * ((float) sqt.height - dh) + 0.5));
357 dst = CGRectMake(0.0f, ofs, (float) sqt.width, dh);
358 CGContextDrawImage(ctx, dst, sqt.srcImage);
359 }
360 else
361 CGContextDrawImage(ctx, dst, sqt.srcImage);
362
363 CGContextRelease(ctx);
364 CGColorSpaceRelease(color);
365
366 #ifndef __BIG_ENDIAN__
367 for (int i = 0; i < sqt.width * sqt.height; i++)
368 SWAP_WORD(p[i]);
369 #endif
370
371 err = CVPixelBufferUnlockBaseAddress(buf, 0);
372
373 err = ICMCompressionSessionEncodeFrame(sqt.session, buf, sqt.timeStamp, 0, kICMValidTime_DisplayTimeStampIsValid, NULL, NULL, NULL);
374
375 CVPixelBufferRelease(buf);
376 }
377
378 sqt.keyFrameCount--;
379 if (sqt.keyFrameCount <= 0)
380 sqt.keyFrameCount = sqt.keyFrame;
381 }
382
383 sqt.frameSkipCount--;
384 if (sqt.frameSkipCount < 0)
385 sqt.frameSkipCount = sqt.frameSkip;
386
387 sqt.timeStamp++;
388
389 // sound
390
391 int sample_count = sqt.soundBufferSize;
392 if (Settings.SixteenBitSound)
393 sample_count >>= 1;
394
395 S9xMixSamples((uint8 *) *(sqt.soundBuffer), sample_count);
396
397 err = AddMediaSample(sqt.sMedia, sqt.soundBuffer, 0, sqt.soundBufferSize, 1, (SampleDescriptionHandle) sqt.soundDesc, sqt.samplesPerSec, mediaSampleNotSync, NULL);
398 }
399
WriteFrameCallBack(void * refCon,ICMCompressionSessionRef session,OSStatus r,ICMEncodedFrameRef frame,void * reserved)400 static OSStatus WriteFrameCallBack (void *refCon, ICMCompressionSessionRef session, OSStatus r, ICMEncodedFrameRef frame, void *reserved)
401 {
402 OSStatus err;
403
404 err = AddMediaSampleFromEncodedFrame(sqt.vMedia, frame, NULL);
405 return (err);
406 }
407
MacQTStopRecording(void)408 void MacQTStopRecording (void)
409 {
410 OSStatus err;
411
412 // video
413
414 err = ICMCompressionSessionCompleteFrames(sqt.session, true, 0, sqt.timeStamp);
415 err = ExtendMediaDecodeDurationToDisplayEndTime(sqt.vMedia, NULL);
416
417 err = EndMediaEdits(sqt.vMedia);
418 CheckError(err, 52);
419
420 err = InsertMediaIntoTrack(sqt.vTrack, 0, 0, (TimeValue) GetMediaDisplayDuration(sqt.vMedia), fixed1);
421 CheckError(err, 58);
422
423 CGImageRelease(sqt.srcImage);
424 CVPixelBufferPoolRelease(sqt.pool);
425 ICMCompressionSessionRelease(sqt.session);
426 ICMCompressionSessionOptionsRelease(sqt.option);
427
428 // sound
429
430 err = EndMediaEdits(sqt.sMedia);
431 CheckError(err, 54);
432
433 err = InsertMediaIntoTrack(sqt.sTrack, 0, 0, GetMediaDuration(sqt.sMedia), fixed1);
434 CheckError(err, 55);
435
436 DisposeHandle(sqt.soundBuffer);
437 DisposeHandle((Handle) sqt.soundDesc);
438
439 // storage
440
441 err = AddMovieToStorage(sqt.movie, sqt.dataHandler);
442 CheckError(err, 56);
443
444 MacQTCloseVideoComponent(sqt.vci);
445
446 err = CloseMovieStorage(sqt.dataHandler);
447 CheckError(err, 57);
448
449 DisposeHandle(sqt.dataRef);
450 DisposeMovie(sqt.movie);
451 }
452