mirror of https://github.com/snes9xgit/snes9x.git
452 lines
13 KiB
Plaintext
452 lines
13 KiB
Plaintext
/*****************************************************************************\
|
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
|
This file is licensed under the Snes9x License.
|
|
For further information, consult the LICENSE file in the root directory.
|
|
\*****************************************************************************/
|
|
|
|
/***********************************************************************************
|
|
SNES9X for Mac OS (c) Copyright John Stiles
|
|
|
|
Snes9x for Mac OS X
|
|
|
|
(c) Copyright 2001 - 2011 zones
|
|
(c) Copyright 2002 - 2005 107
|
|
(c) Copyright 2002 PB1400c
|
|
(c) Copyright 2004 Alexander and Sander
|
|
(c) Copyright 2004 - 2005 Steven Seeger
|
|
(c) Copyright 2005 Ryan Vogt
|
|
***********************************************************************************/
|
|
|
|
|
|
#include "snes9x.h"
|
|
#include "memmap.h"
|
|
#include "apu.h"
|
|
|
|
#include <QuickTime/QuickTime.h>
|
|
|
|
#include "mac-prefix.h"
|
|
#include "mac-gworld.h"
|
|
#include "mac-os.h"
|
|
#include "mac-screenshot.h"
|
|
#include "mac-quicktime.h"
|
|
|
|
#define kMovDoubleSize (1 << 0)
|
|
#define kMovExtendedHeight (1 << 1)
|
|
|
|
static void CheckError (OSStatus, int);
|
|
static void MacQTOpenVideoComponent (ComponentInstance *);
|
|
static void MacQTCloseVideoComponent (ComponentInstance);
|
|
static OSStatus WriteFrameCallBack (void *, ICMCompressionSessionRef, OSStatus, ICMEncodedFrameRef, void *);
|
|
|
|
typedef struct
|
|
{
|
|
Movie movie;
|
|
Track vTrack, sTrack;
|
|
Media vMedia, sMedia;
|
|
ComponentInstance vci;
|
|
SoundDescriptionHandle soundDesc;
|
|
DataHandler dataHandler;
|
|
Handle soundBuffer;
|
|
Handle dataRef;
|
|
OSType dataRefType;
|
|
CVPixelBufferPoolRef pool;
|
|
ICMCompressionSessionRef session;
|
|
ICMCompressionSessionOptionsRef option;
|
|
CGImageRef srcImage;
|
|
TimeValue64 timeStamp;
|
|
long keyFrame, keyFrameCount;
|
|
long frameSkip, frameSkipCount;
|
|
int width, height;
|
|
int soundBufferSize;
|
|
int samplesPerSec;
|
|
} MacQTState;
|
|
|
|
static MacQTState sqt;
|
|
|
|
|
|
static void CheckError (OSStatus err, int n)
|
|
{
|
|
if (err != noErr)
|
|
{
|
|
char mes[32];
|
|
|
|
sprintf(mes, "quicktime %02d", n);
|
|
QuitWithFatalError(err, mes);
|
|
}
|
|
}
|
|
|
|
static void MacQTOpenVideoComponent (ComponentInstance *rci)
|
|
{
|
|
OSStatus err;
|
|
ComponentInstance ci;
|
|
CFDataRef data;
|
|
|
|
ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
|
|
|
|
data = (CFDataRef) CFPreferencesCopyAppValue(CFSTR("QTVideoSetting"), kCFPreferencesCurrentApplication);
|
|
if (data)
|
|
{
|
|
CFIndex len;
|
|
Handle hdl;
|
|
|
|
len = CFDataGetLength(data);
|
|
hdl = NewHandleClear((Size) len);
|
|
if (MemError() == noErr)
|
|
{
|
|
HLock(hdl);
|
|
CFDataGetBytes(data, CFRangeMake(0, len), (unsigned char *) *hdl);
|
|
err = SCSetInfo(ci, scSettingsStateType, &hdl);
|
|
|
|
DisposeHandle(hdl);
|
|
}
|
|
|
|
CFRelease(data);
|
|
}
|
|
else
|
|
{
|
|
SCSpatialSettings ss;
|
|
SCTemporalSettings ts;
|
|
|
|
ss.codecType = kAnimationCodecType;
|
|
ss.codec = 0;
|
|
ss.depth = 16;
|
|
ss.spatialQuality = codecMaxQuality;
|
|
err = SCSetInfo(ci, scSpatialSettingsType, &ss);
|
|
|
|
ts.frameRate = FixRatio(Memory.ROMFramesPerSecond, 1);
|
|
ts.keyFrameRate = Memory.ROMFramesPerSecond;
|
|
ts.temporalQuality = codecMaxQuality;
|
|
err = SCSetInfo(ci, scTemporalSettingsType, &ts);
|
|
}
|
|
|
|
*rci = ci;
|
|
}
|
|
|
|
static void MacQTCloseVideoComponent (ComponentInstance ci)
|
|
{
|
|
OSStatus err;
|
|
|
|
err = CloseComponent(ci);
|
|
}
|
|
|
|
void MacQTVideoConfig (void)
|
|
{
|
|
OSStatus err;
|
|
ComponentInstance ci;
|
|
|
|
MacQTOpenVideoComponent(&ci);
|
|
|
|
long flag;
|
|
flag = scListEveryCodec | scAllowZeroKeyFrameRate | scDisableFrameRateItem | scAllowEncodingWithCompressionSession;
|
|
err = SCSetInfo(ci, scPreferenceFlagsType, &flag);
|
|
|
|
SCWindowSettings ws;
|
|
ws.size = sizeof(SCWindowSettings);
|
|
ws.windowRefKind = scWindowRefKindCarbon;
|
|
ws.parentWindow = NULL;
|
|
err = SCSetInfo(ci, scWindowOptionsType, &ws);
|
|
|
|
err = SCRequestSequenceSettings(ci);
|
|
if (err == noErr)
|
|
{
|
|
CFDataRef data;
|
|
Handle hdl;
|
|
|
|
err = SCGetInfo(ci, scSettingsStateType, &hdl);
|
|
if (err == noErr)
|
|
{
|
|
HLock(hdl);
|
|
data = CFDataCreate(kCFAllocatorDefault, (unsigned char *) *hdl, GetHandleSize(hdl));
|
|
if (data)
|
|
{
|
|
CFPreferencesSetAppValue(CFSTR("QTVideoSetting"), data, kCFPreferencesCurrentApplication);
|
|
CFRelease(data);
|
|
}
|
|
|
|
DisposeHandle(hdl);
|
|
}
|
|
}
|
|
|
|
MacQTCloseVideoComponent(ci);
|
|
}
|
|
|
|
void MacQTStartRecording (char *path)
|
|
{
|
|
OSStatus err;
|
|
CFStringRef str;
|
|
CFURLRef url;
|
|
|
|
memset(&sqt, 0, sizeof(sqt));
|
|
|
|
// storage
|
|
|
|
str = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
|
|
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, str, kCFURLPOSIXPathStyle, false);
|
|
err = QTNewDataReferenceFromCFURL(url, 0, &(sqt.dataRef), &(sqt.dataRefType));
|
|
CheckError(err, 21);
|
|
CFRelease(url);
|
|
CFRelease(str);
|
|
|
|
err = CreateMovieStorage(sqt.dataRef, sqt.dataRefType, 'TVOD', smSystemScript, createMovieFileDeleteCurFile | newMovieActive, &(sqt.dataHandler), &(sqt.movie));
|
|
CheckError(err, 22);
|
|
|
|
// video
|
|
|
|
MacQTOpenVideoComponent(&(sqt.vci));
|
|
|
|
long flag;
|
|
SCTemporalSettings ts;
|
|
|
|
flag = scAllowEncodingWithCompressionSession;
|
|
err = SCSetInfo(sqt.vci, scPreferenceFlagsType, &flag);
|
|
|
|
err = SCGetInfo(sqt.vci, scTemporalSettingsType, &ts);
|
|
ts.frameRate = FixRatio(Memory.ROMFramesPerSecond, 1);
|
|
if (ts.keyFrameRate < 1)
|
|
ts.keyFrameRate = Memory.ROMFramesPerSecond;
|
|
sqt.keyFrame = sqt.keyFrameCount = ts.keyFrameRate;
|
|
sqt.frameSkip = sqt.frameSkipCount = (macQTMovFlag & 0xFF00) >> 8;
|
|
err = SCSetInfo(sqt.vci, scTemporalSettingsType, &ts);
|
|
|
|
sqt.width = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * SNES_WIDTH;
|
|
sqt.height = ((macQTMovFlag & kMovDoubleSize) ? 2 : 1) * ((macQTMovFlag & kMovExtendedHeight) ? SNES_HEIGHT_EXTENDED : SNES_HEIGHT);
|
|
|
|
sqt.srcImage = NULL;
|
|
sqt.timeStamp = 0;
|
|
|
|
SCSpatialSettings ss;
|
|
ICMEncodedFrameOutputRecord record;
|
|
ICMMultiPassStorageRef nullStorage = NULL;
|
|
|
|
err = SCCopyCompressionSessionOptions(sqt.vci, &(sqt.option));
|
|
CheckError(err, 61);
|
|
err = ICMCompressionSessionOptionsSetProperty(sqt.option, kQTPropertyClass_ICMCompressionSessionOptions, kICMCompressionSessionOptionsPropertyID_MultiPassStorage, sizeof(ICMMultiPassStorageRef), &nullStorage);
|
|
|
|
record.encodedFrameOutputCallback = WriteFrameCallBack;
|
|
record.encodedFrameOutputRefCon = NULL;
|
|
record.frameDataAllocator = NULL;
|
|
err = SCGetInfo(sqt.vci, scSpatialSettingsType, &ss);
|
|
err = ICMCompressionSessionCreate(kCFAllocatorDefault, sqt.width, sqt.height, ss.codecType, Memory.ROMFramesPerSecond, sqt.option, NULL, &record, &(sqt.session));
|
|
CheckError(err, 62);
|
|
|
|
CFMutableDictionaryRef dic;
|
|
CFNumberRef val;
|
|
OSType pix = k16BE555PixelFormat;
|
|
int row = sqt.width * 2;
|
|
|
|
dic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
|
|
val = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix);
|
|
CFDictionaryAddValue(dic, kCVPixelBufferPixelFormatTypeKey, val);
|
|
CFRelease(val);
|
|
|
|
val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.width));
|
|
CFDictionaryAddValue(dic, kCVPixelBufferWidthKey, val);
|
|
CFRelease(val);
|
|
|
|
val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &(sqt.height));
|
|
CFDictionaryAddValue(dic, kCVPixelBufferHeightKey, val);
|
|
CFRelease(val);
|
|
|
|
val = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &row);
|
|
CFDictionaryAddValue(dic, kCVPixelBufferBytesPerRowAlignmentKey, val);
|
|
CFRelease(val);
|
|
|
|
CFDictionaryAddValue(dic, kCVPixelBufferCGImageCompatibilityKey, kCFBooleanTrue);
|
|
CFDictionaryAddValue(dic, kCVPixelBufferCGBitmapContextCompatibilityKey, kCFBooleanTrue);
|
|
|
|
err = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, dic, &(sqt.pool));
|
|
CheckError(err, 63);
|
|
|
|
CFRelease(dic);
|
|
|
|
sqt.vTrack = NewMovieTrack(sqt.movie, FixRatio(sqt.width, 1), FixRatio(sqt.height, 1), kNoVolume);
|
|
CheckError(GetMoviesError(), 23);
|
|
|
|
sqt.vMedia = NewTrackMedia(sqt.vTrack, VideoMediaType, Memory.ROMFramesPerSecond, NULL, 0);
|
|
CheckError(GetMoviesError(), 24);
|
|
|
|
err = BeginMediaEdits(sqt.vMedia);
|
|
CheckError(err, 25);
|
|
|
|
// sound
|
|
|
|
sqt.soundDesc = (SoundDescriptionHandle) NewHandleClear(sizeof(SoundDescription));
|
|
CheckError(MemError(), 26);
|
|
|
|
(**sqt.soundDesc).descSize = sizeof(SoundDescription);
|
|
#ifdef __BIG_ENDIAN__
|
|
(**sqt.soundDesc).dataFormat = Settings.SixteenBitSound ? k16BitBigEndianFormat : k8BitOffsetBinaryFormat;
|
|
#else
|
|
(**sqt.soundDesc).dataFormat = Settings.SixteenBitSound ? k16BitLittleEndianFormat : k8BitOffsetBinaryFormat;
|
|
#endif
|
|
(**sqt.soundDesc).numChannels = Settings.Stereo ? 2 : 1;
|
|
(**sqt.soundDesc).sampleSize = Settings.SixteenBitSound ? 16 : 8;
|
|
(**sqt.soundDesc).sampleRate = (UnsignedFixed) FixRatio(Settings.SoundPlaybackRate, 1);
|
|
|
|
sqt.samplesPerSec = Settings.SoundPlaybackRate / Memory.ROMFramesPerSecond;
|
|
|
|
sqt.soundBufferSize = sqt.samplesPerSec;
|
|
if (Settings.SixteenBitSound)
|
|
sqt.soundBufferSize <<= 1;
|
|
if (Settings.Stereo)
|
|
sqt.soundBufferSize <<= 1;
|
|
|
|
sqt.soundBuffer = NewHandleClear(sqt.soundBufferSize);
|
|
CheckError(MemError(), 27);
|
|
HLock(sqt.soundBuffer);
|
|
|
|
sqt.sTrack = NewMovieTrack(sqt.movie, 0, 0, kFullVolume);
|
|
CheckError(GetMoviesError(), 28);
|
|
|
|
sqt.sMedia = NewTrackMedia(sqt.sTrack, SoundMediaType, Settings.SoundPlaybackRate, NULL, 0);
|
|
CheckError(GetMoviesError(), 29);
|
|
|
|
err = BeginMediaEdits(sqt.sMedia);
|
|
CheckError(err, 30);
|
|
}
|
|
|
|
void MacQTRecordFrame (int width, int height)
|
|
{
|
|
OSStatus err;
|
|
|
|
// video
|
|
|
|
if (sqt.frameSkipCount == sqt.frameSkip)
|
|
{
|
|
CVPixelBufferRef buf;
|
|
|
|
err = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, sqt.pool, &buf);
|
|
if (err == noErr)
|
|
{
|
|
CGColorSpaceRef color;
|
|
CGContextRef ctx;
|
|
uint16 *p;
|
|
|
|
err = CVPixelBufferLockBaseAddress(buf, 0);
|
|
p = (uint16 *) CVPixelBufferGetBaseAddress(buf);
|
|
|
|
color = CGColorSpaceCreateDeviceRGB();
|
|
ctx = CGBitmapContextCreate((void *) p, sqt.width, sqt.height, 5, sqt.width * 2, color, kCGImageAlphaNoneSkipFirst | ((systemVersion >= 0x1040) ? kCGBitmapByteOrder16Host : 0));
|
|
CGContextSetShouldAntialias(ctx, false);
|
|
|
|
if (sqt.srcImage)
|
|
CGImageRelease(sqt.srcImage);
|
|
sqt.srcImage = CreateGameScreenCGImage();
|
|
|
|
CGRect dst = CGRectMake(0.0f, 0.0f, (float) sqt.width, (float) sqt.height);
|
|
|
|
if ((!(height % SNES_HEIGHT_EXTENDED)) && (!(macQTMovFlag & kMovExtendedHeight)))
|
|
{
|
|
CGRect src;
|
|
|
|
src.size.width = (float) width;
|
|
src.size.height = (float) ((height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
|
|
src.origin.x = (float) 0;
|
|
src.origin.y = (float) height - src.size.height;
|
|
DrawSubCGImage(ctx, sqt.srcImage, src, dst);
|
|
}
|
|
else
|
|
if ((sqt.height << 1) % height)
|
|
{
|
|
CGContextSetRGBFillColor(ctx, 0.0f, 0.0f, 0.0f, 1.0f);
|
|
CGContextFillRect(ctx, dst);
|
|
|
|
float dh = (float) ((sqt.height > 256) ? (SNES_HEIGHT << 1) : SNES_HEIGHT);
|
|
float ofs = (float) ((int) ((drawoverscan ? 1.0 : 0.5) * ((float) sqt.height - dh) + 0.5));
|
|
dst = CGRectMake(0.0f, ofs, (float) sqt.width, dh);
|
|
CGContextDrawImage(ctx, dst, sqt.srcImage);
|
|
}
|
|
else
|
|
CGContextDrawImage(ctx, dst, sqt.srcImage);
|
|
|
|
CGContextRelease(ctx);
|
|
CGColorSpaceRelease(color);
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
for (int i = 0; i < sqt.width * sqt.height; i++)
|
|
SWAP_WORD(p[i]);
|
|
#endif
|
|
|
|
err = CVPixelBufferUnlockBaseAddress(buf, 0);
|
|
|
|
err = ICMCompressionSessionEncodeFrame(sqt.session, buf, sqt.timeStamp, 0, kICMValidTime_DisplayTimeStampIsValid, NULL, NULL, NULL);
|
|
|
|
CVPixelBufferRelease(buf);
|
|
}
|
|
|
|
sqt.keyFrameCount--;
|
|
if (sqt.keyFrameCount <= 0)
|
|
sqt.keyFrameCount = sqt.keyFrame;
|
|
}
|
|
|
|
sqt.frameSkipCount--;
|
|
if (sqt.frameSkipCount < 0)
|
|
sqt.frameSkipCount = sqt.frameSkip;
|
|
|
|
sqt.timeStamp++;
|
|
|
|
// sound
|
|
|
|
int sample_count = sqt.soundBufferSize;
|
|
if (Settings.SixteenBitSound)
|
|
sample_count >>= 1;
|
|
|
|
S9xMixSamples((uint8 *) *(sqt.soundBuffer), sample_count);
|
|
|
|
err = AddMediaSample(sqt.sMedia, sqt.soundBuffer, 0, sqt.soundBufferSize, 1, (SampleDescriptionHandle) sqt.soundDesc, sqt.samplesPerSec, mediaSampleNotSync, NULL);
|
|
}
|
|
|
|
static OSStatus WriteFrameCallBack (void *refCon, ICMCompressionSessionRef session, OSStatus r, ICMEncodedFrameRef frame, void *reserved)
|
|
{
|
|
OSStatus err;
|
|
|
|
err = AddMediaSampleFromEncodedFrame(sqt.vMedia, frame, NULL);
|
|
return (err);
|
|
}
|
|
|
|
void MacQTStopRecording (void)
|
|
{
|
|
OSStatus err;
|
|
|
|
// video
|
|
|
|
err = ICMCompressionSessionCompleteFrames(sqt.session, true, 0, sqt.timeStamp);
|
|
err = ExtendMediaDecodeDurationToDisplayEndTime(sqt.vMedia, NULL);
|
|
|
|
err = EndMediaEdits(sqt.vMedia);
|
|
CheckError(err, 52);
|
|
|
|
err = InsertMediaIntoTrack(sqt.vTrack, 0, 0, (TimeValue) GetMediaDisplayDuration(sqt.vMedia), fixed1);
|
|
CheckError(err, 58);
|
|
|
|
CGImageRelease(sqt.srcImage);
|
|
CVPixelBufferPoolRelease(sqt.pool);
|
|
ICMCompressionSessionRelease(sqt.session);
|
|
ICMCompressionSessionOptionsRelease(sqt.option);
|
|
|
|
// sound
|
|
|
|
err = EndMediaEdits(sqt.sMedia);
|
|
CheckError(err, 54);
|
|
|
|
err = InsertMediaIntoTrack(sqt.sTrack, 0, 0, GetMediaDuration(sqt.sMedia), fixed1);
|
|
CheckError(err, 55);
|
|
|
|
DisposeHandle(sqt.soundBuffer);
|
|
DisposeHandle((Handle) sqt.soundDesc);
|
|
|
|
// storage
|
|
|
|
err = AddMovieToStorage(sqt.movie, sqt.dataHandler);
|
|
CheckError(err, 56);
|
|
|
|
MacQTCloseVideoComponent(sqt.vci);
|
|
|
|
err = CloseMovieStorage(sqt.dataHandler);
|
|
CheckError(err, 57);
|
|
|
|
DisposeHandle(sqt.dataRef);
|
|
DisposeMovie(sqt.movie);
|
|
}
|