I am trying to use the AVAssetWriter to encode video. I am using ARC. What I am trying to do is stream video over a socket.
Now here is the problem. The setup I have works the first time around. I am unable to use the AVAssetWriter again, without restarting my application first. If I don't restart my application, when I call [m_writer startWriting] I receive the following error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x14d06fe0 {Error Domain=NSOSStatusErrorDomain Code=-12983 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12983)}
I made sure that file was deleted when the cleaup method was called.
I used iExplorer to navigate to the temp folder where the file was
being created. I also made sure that the status of the AVAssetWriter
after calling finshWriting is AVAssetWriterStatusFinished.
Edit: The error persists even If I used a different file name.
I create an instance of the VideoEncoder class ( code below) and then I feed it raw images. Then I wait for something to be written to the output file by the AVAssetWriter using a dispatch_source_set_event_handler(), i.e.:
m_inputFile = [NSFileHandle fileHandleForReadingAtPath: m_writer.path];
m_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [m_inputFile fileDescriptor], 0, m_readQueue);
dispatch_source_set_event_handler(m_readSource, ^{
// Read bytes from file and send over socket
}
dispatch_source_set_cancel_handler(m_readSource, ^{
[self cleanup];
}
dispatch_resume(m_readSource);
I have created a wrapper for the AVAssetWriter called VideoEncoder. Here is how I initialize the AVAssetWriter:
#implementation VideoEncoder : NSObject
{
AVAssetWriter* m_writer;
AVAssetWriterInput* m_writerInput;
AVAssetWriterInputPixelBufferAdaptor* m_pixelBufferAdaptor;
}
-(id) initWithPath:(NSString*)path width:(int)width height:(int)height parameters:(NSDictionary*) parameters
{
if (self = [super init])
{
self.path = path;
[[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
NSURL* url = [NSURL fileURLWithPath: self.path];
m_writer = [[AVAssetWriter alloc] initWithURL:url fileType:AVFileTypeQuickTimeMovie error: nil];
NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithInt: width], AVVideoWidthKey,
[NSNumber numberWithInt:height], AVVideoHeightKey,
parameters, AVVideoCompressionPropertiesKey,
nil];
m_writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
m_writerInput.expectsMediaDataInRealTime = YES;
[m_writer addInput:m_writerInput];
NSDictionary* pixelBufferOptions = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt: kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
[NSNumber numberWithInt: width*4], kCVPixelBufferBytesPerRowAllignmentKey,
[NSNumber numberWithInt; width], kCVPixelBufferWidthKey,
[NSNumber numberWithInt; height], kCVPixelBufferHeightKey,
nil];
m_pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
initWithAssetWriterInput: m_writerInput
sourcePixelBufferAttributes: pixelBufferOptions];
[m_writer startWriting];
[m_writer startSessionAtSourceTime: kCMTimeZero];
}
}
This is how I feed it raw images:
-(BOOL) encodeFrame:(CVPixelBufferRead)pixelBuffer time(CMTime)time;
{
if (m_writer.status == AVAssetWriterStatusFailed)
{
return NO;
}
if (m_writerInput.readyForMoreMediaData == YES)
{
if ([m_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:time])
{
return YES:
}
}
return NO;
}
This is how the pixel buffers are generated:
-(bool) encodeImage:(const char*) frameBuffer width:(int)width height:(int)height
{
CVPixelBufferRef pixelBuffer = NULL;
CVReturn ret;
ret = CVPixelBufferCraeteWithBytes(
kCFAllocatorDefault,
with,
height,
kCVPixelFormatType_32BGRA,
(void*) frameBuffer,
width * 4,
NULL, NULL, NULL, NULL, &pixelBuffer);
if (ret == kCVReturnSuccess && pixelBuffer)
{
CFTimeInterval currTime = CACurrentMediaTime();
if (m_frameIndex > 0 )
{
int ticks = (int)((currTime - m_prevTime) * 1000) / 30;
CMTime addTime;
ticks = ticks <= 0 ? 1 : ticks;
addTime = CMTimeMake(ticks, 30);
m_timestamp = CMTimeAdd(m_timestamp, addTime);
}
m_prevTime = currTime;
[m_impl encodeFrame: pixelBuffer time: m_timestamp];
CVBufferRelease(pixelBuffer);
m_frameIndex ++;
}
}
This is how I terminate the AVAssetWriter:
-(void) finishWritingWithCompletionHandler(void (^)(void))handler
{
[m_writerInput markAsFinished];
[m_writer finishWithCompletionHandler: ^{
handler();
}
}
And here is how I use the wrapper. I use this wrapper form a C++ class. Instantiation:
-(void) makeFilename
{
NSString* filename = [NSString* stringWithFormat: #"temp%d.tmp", 1];
NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: filename];
return path;
}
bool CFrameEncoder::StartEncoding()
{
m_impl = CFRetain((__bridge*void)[[VideoEncoder alloc] initWithPath:[self makeFilepath] width:800 height:480 parameters: params]);
}
Dealloc:
bool CFrameEncoder::StopDecoding()
{
[(__bridge VideoEncoder*) m_impl terminate];
CFRelease(m_impl);
}
The terminate function (of the VideoEncoder class):
-(void) terminate
{
if (m_readSource)
{
dispatch_source_cancel(m_readSource);
}
}
// The dispatch source cancel handler
-(void) cleanup
{
m_readQueue = nil;
m_readSource = nil;
[m_inputFile closeFile];
NSString* path = m_writer.path;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[m_writer finishWithCompletionHandler:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
m_writer = nil;
[[NSFileManager defaultManager] removeItemAtPath: path error: nil];
}
The AVAssetWriter class can have only a limited number of instances. If your application goes over this limit, then the AVAssetWriter begins to fail with AVAssetWriterStatusFailed when you try to startWriting.
This means that there is something in my code that is preventing the AVAssetWriter from being deallocated. Does anybody know where?
I am attempting to store std::vector to NSData and back directly. My first attempt I converted each point to an NSValue and stored them with NSKeyedUnarchiver which seems terribly inefficient. My test dataset required 64MB of human readable text (with NSKeyedUnarchiver), versus converting each std:vector to NSData the resulting stored files is a much more reasonable 896kb. I am doing as follows to store the data:
typedef std::vector<CGPoint> CGContour;
typedef std::vector<std::vector<CGPoint>> CGContours;
static CGContours contoursVector;
contoursVector = CGContours(1024); //Populated with CGContours that are populated with CGPoints datatypes above
//doing the following in a for loop, just showing record 0 for brevity
NSData *contourData([[NSData alloc]
initWithBytesNoCopy: contoursVector[0].data()
length: contoursVector[0].size()
freeWhenDone:false]);
I am able to retrieve the buffer:
const void *buffer = [contourData bytes];
size_t len = [contourData length];
However, I am unable to figure out how to populate a std::vector with the const void buffer pointer. I have tried using every possible pointer and dereference combination I can think of - the only thing that I can get to compile is this:
contoursVector[0] = *(CGContour *)[contourData bytes];
If I inspect the vector of CGPoints they are 0,0, so clearly something is not right.
EDIT: after implementing the suggested answer, sometimes it works, other times I get EXC_BAD_ACCESS. Here is the relevant back trace:
* thread #17: tid = 0x11bf7d4, 0x0000000111607551 libsystem_platform.dylib`_platform_memmove$VARIANT$Ivybridge + 49, queue = 'NSOperationQueue 0x7fa298f51000 :: NSOperation 0x7fa29f3251f0 (QOS: UTILITY)', stop reason = EXC_BAD_ACCESS (code=1, address=0x126e27000)
frame #0: 0x0000000111607551 libsystem_platform.dylib`_platform_memmove$VARIANT$Ivybridge + 49
frame #1: 0x000000010d01890f Foundation`NSCopyMemoryPages + 57
frame #2: 0x000000010cf9b737 Foundation`_NSDataCreateVMDispatchData + 103
frame #3: 0x000000010cf99cf2 Foundation`-[_NSPlaceholderData initWithBytes:length:copy:deallocator:] + 230
frame #4: 0x000000010cfa5902 Foundation`-[NSData(NSData) initWithBytes:length:] + 37
* frame #5: 0x000000010cfeabfb Foundation`+[NSData(NSData) dataWithBytes:length:] + 54
frame #6: 0x000000010c5c998a TDTPhotoLib`storePointData() + 682 at TDTContourImage.mm:562
Odd thing is both the contours and the contour being converted to data both look valid in the debugger and the issue seems to be intermittent (sometimes it works other times it doesn't but can't tell what if anything may be different)
EDIT 2:
I am able to iterate over every point, but it crashes on the NSData line.
NSMutableArray<NSData *> *groupedPointsArrayMain = [NSMutableArray new];
for(const CGContour &contour : contoursVector)
{
if (contour.size() > 0) {
// I am able to iterate over every point and store them this way
NSMutableArray *contourPoints = [NSMutableArray arrayWithCapacity:contour.size()];
for(const CGPoint &point : contour)
{
[contourPoints addObject:[NSValue valueWithCGPoint:point]];
}
//When it crashes, it will crash on this line
//despite it successfully walking over each point
//in the code directly above
NSData *data = [NSData dataWithBytes: contour.data()
length: (contour.size() * cgContourSize)];
[groupedPointsArrayMain addObject:data];
}
}
Something like this should do the trick. Please note I didn't try to compile this code since I'm on Linux atm.
typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;
const size_t contourSize = sizeof(CGContour);
NSMutableArray<NSData *> *datas = [NSMutableArray new];
{ // store
CGContours contours(1024);
for(const CGContour &contour : contours)
{
NSData *data = [NSData dataWithBytes: contour.data()
length: contour.size() * contourSize];
[datas addObject:data]
}
}
{ // restore
CGContours contours;
for(NSData *data in datas)
{
const size_t count = [data length] / contourSize;
CGPoint *first = (CGPoint *)[data bytes];
CGPoint *last = first + count;
contours.emplace_back(first, last);
}
}
I have made a soft synthesizer in Visual Studio 2012 with C++, MFC and DirectX. Despite having added code to rapidly fade out the sound I am experiencing popping / clicking when stopping playback (also when starting).
I copied the DirectX code from this project: http://www.codeproject.com/Articles/7474/Sound-Generator-How-to-create-alien-sounds-using-m
I'm not sure if I'm allowed to cut and paste all the code from the Code Project. Basically I use the Player class from that project as is, the instance of this class is called m_player in my code. The Stop member function in that class calls the Stop function of LPDIRECTSOUNDBUFFER:
void Player::Stop()
{
DWORD status;
if (m_lpDSBuffer == NULL)
return;
HRESULT hres = m_lpDSBuffer->GetStatus(&status);
if (FAILED(hres))
EXCEP(DirectSoundErr::GetErrDesc(hres), "Player::Stop GetStatus");
if ((status & DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
{
hres = m_lpDSBuffer->Stop();
if (FAILED(hres))
EXCEP(DirectSoundErr::GetErrDesc(hres), "Player::Stop Stop");
}
}
Here is the notification code (with some supporting code) in my project that fills the sound buffer. Note that the rend function always returns a double between -1 to 1, m_ev_smps = 441, m_n_evs = 3 and m_ev_sz = 882. subInit is called from OnInitDialog:
#define FD_STEP 0.0005
#define SC_NOT_PLYD 0
#define SC_PLYNG 1
#define SC_FD_OUT 2
#define SC_FD_IN 3
#define SC_STPNG 4
#define SC_STPD 5
bool CMainDlg::subInit()
// initialises various variables and the sound player
{
Player *pPlayer;
SOUNDFORMAT format;
std::vector<DWORD> events;
int t, buf_sz;
try
{
pPlayer = new Player();
pPlayer->SetHWnd(m_hWnd);
m_player = pPlayer;
m_player->Init();
format.NbBitsPerSample = 16;
format.NbChannels = 1;
format.SamplingRate = 44100;
m_ev_smps = 441;
m_n_evs = 3;
m_smps = new short[m_ev_smps];
m_smp_scale = (int)pow(2, format.NbBitsPerSample - 1);
m_max_tm = (int)((double)m_ev_smps / (double)(format.SamplingRate * 1000));
m_ev_sz = m_ev_smps * format.NbBitsPerSample/8;
buf_sz = m_ev_sz * m_n_evs;
m_player->CreateSoundBuffer(format, buf_sz, 0);
m_player->SetSoundEventListener(this);
for(t = 0; t < m_n_evs; t++)
events.push_back((int)((t + 1)*m_ev_sz - m_ev_sz * 0.95));
m_player->CreateEventReadNotification(events);
m_status = SC_NOT_PLYD;
}
catch(MATExceptions &e)
{
MessageBox(e.getAllExceptionStr().c_str(), "Error initializing the sound player");
EndDialog(IDCANCEL);
return FALSE;
}
return TRUE;
}
void CMainDlg::Stop()
// stop playing
{
m_player->Stop();
m_status = SC_STPD;
}
void CMainDlg::OnBnClickedStop()
// causes fade out
{
m_status = SC_FD_OUT;
}
void CMainDlg::OnSoundPlayerNotify(int ev_num)
// render some sound samples and check for errors
{
ScopeGuardMutex guard(&m_mutex);
int s, end, begin, elapsed;
if (m_status != SC_STPNG)
{
begin = GetTickCount();
try
{
for(s = 0; s < m_ev_smps; s++)
{
m_smps[s] = (int)(m_synth->rend() * 32768 * m_fade);
if (m_status == SC_FD_IN)
{
m_fade += FD_STEP;
if (m_fade > 1)
{
m_fade = 1;
m_status = SC_PLYNG;
}
}
else if (m_status == SC_FD_OUT)
{
m_fade -= FD_STEP;
if (m_fade < 0)
{
m_fade = 0;
m_status = SC_STPNG;
}
}
}
}
catch(MATExceptions &e)
{
OutputDebugString(e.getAllExceptionStr().c_str());
}
try
{
m_player->Write(((ev_num + 1) % m_n_evs)*m_ev_sz, (unsigned char*)m_smps, m_ev_sz);
}
catch(MATExceptions &e)
{
OutputDebugString(e.getAllExceptionStr().c_str());
}
end = GetTickCount();
elapsed = end - begin;
if(elapsed > m_max_tm)
m_warn_msg.Format(_T("Warning! compute time: %dms"), elapsed);
else
m_warn_msg.Format(_T("compute time: %dms"), elapsed);
}
if (m_status == SC_STPNG)
Stop();
}
It seems like the buffer is not always sounding out when the stop button is clicked. I don't have any specific code for waiting for the sound buffer to finish playing before the DirectX Stop is called. Other than that the sound playback is working just fine, so at least I am initialising the player correctly and notification code is working in that respect.
Try replacing 32768 with 32767. Not by any means sure this is your issue, but it could overflow the positive short int range (assuming your audio is 16-bit) and cause a "pop".
I got rid of the pops / clicks when stopping playback, by filling the buffer with zeros after the fade out. However I still get pops when re-starting playback, despite filling with zeros and then fading back in (it is frustrating).
I uses ARC
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
Not much information is given. It goes straight to main
int main(int argc, char *argv[])
{
#autoreleasepool {
//int retVal = UIApplicationMain(argc, argv, nil, nil);
int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([newIsiKotaAppDelegate class]));
return retVal;
}
}
I manage to spot that the last code executed is this:
self.lblTimeLeft.attributedText = arString;
I provided additional code to test things out
NSAttributedString * arString = [self asSchedule:arTimes];
self.lblTimeLeft.attributedText = arString;
PO1(#"Hello World");
while(false);// Error happen after this code is executed
}
The code is part of the routine that provides UITableViewCell to display. So error happen after
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = indexPath.row;
NSDictionary * rowDictionary = _marManagedObjectArray [row];
BGCinemaScheduleCell * BCS = [[BGCinemaScheduleCell alloc]init];
BCS.dicCinemaDictionary =rowDictionary;
return BCS; //This is where the error happen
}
It seems that the NSAttributedString works fine till it's actually displayed or something.
The error seems to happen on
TAttributes::TAttributes(__CFDictionary const *)
The content of asSchedule is usual
-(NSAttributedString *) asSchedule:(NSArray *) arTimes
{
NSMutableArray * timeBefore = [NSMutableArray array];
NSMutableArray * timeAfter = [NSMutableArray array];
for (NSString * strTime in arTimes) {
NSDate * date = [NSDate dtWithCurrentDateAndProvidedTime: strTime];
NSTimeInterval dblInterval =[date timeIntervalSinceNow];
if (dblInterval>0)
{
[timeAfter addObject:strTime];
}
else{
[timeBefore addObject:strTime];
}
}
NSString * strTimeBefore = [timeBefore componentsJoinedByString:#" "];
NSString * strTimeAfter = [timeAfter componentsJoinedByString:#" "];
NSString *yourString = [NSString stringWithFormat:#"%# %#", strTimeBefore, strTimeAfter];
// start at the end of strTimeBefore and go the length of strTimeAfter
NSRange boldedRange = NSMakeRange([strTimeBefore length] + 1, [strTimeAfter length]);
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:yourString];
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
PO1(NSStringFromRange(boldedRange));
PO1(#(attrString.length));
return attrString;
}
Update:
I changed self.lblTimeLeft.attributedText = arString; to self.lblTimeLeft.attributedText = arString.copy; to no avail.
I wonder if this could be the problem:
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
I think the original sample use KCFont something instead of NSFontAttributeName
You were almost there:
I wonder if this could be the problem: [attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
Your code says:
NSString *boldFontName = [[UIFont boldSystemFontOfSize:12] fontName];
...
[attrString addAttribute:NSFontAttributeName value:boldFontName range:boldedRange];
It should say:
UIFont *boldFont = [UIFont boldSystemFontOfSize:12];
...
[attrString addAttribute:NSFontAttributeName value:boldFont range:boldedRange];
According to Table 1 of the docs (link below) and my own app code, you should be passing an NSFont not a font name.
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/AttributedStrings/Articles/standardAttributes.html#//apple_ref/doc/uid/TP40004903-SW2
Can't tell from the code whether your project uses ARC or not, but it looks like your asSchedule method returns a string with a refcount of 0. It's up to you to retain a reference to that string, either old-school using the retain keyword (probably a bad idea) or by assigning it to a property declared with the strong keyword (or retain, if pre-ARC).
The best tool for debugging this stuff is Instruments, using the Zombies inspection. If I am right that you are trying to use the string after its refcount hits 0 and the memory has been released, you'll see the history of refcount increments and decrements pretty clearly.
Try alloc and initing the Attributedstring and then assign it to the self.lblTimeLeft.attributedText
I have just seen the announcment of iPhone 5 and it says that the pixel resolution has changed to 1136*640, affecting in this way the ASPECT RATIO of the app.
How should I deal with this in my Cocos2d game? I got all the graphics done for the "old" 960*640 retina display screen and I guess that those will be distorted on the iPhone 5 screen.
Am I right? Or will there be the "old resolution" images shown without modifying the aspect ratio and leaving some screen black?
EDIT: Is there a way to get Cocos2d to detect if it is iPhone 5 and in that case draw the background files in the top part of the screen (top 960 pixels) and get some other custom background files to be drawn in the remaining pixels (e.g. those could be some custom ad banners or some extra buttons available in our Game only for iPhone 5).
I have just added 4 inch support to my app this morning. Cocos2d runs fine (in the simulator) with no modifications. All of my scenes have resized correctly, I just had to make a few modifications to some positions as they were fixed coordinates not relative.
There is currently no way to load different images easily, I suspect there will be a new naming convention similar to -hd in the next few days.
As for your edit question, you will probably find that once you enable the 4 inch mode your layout will have a large black space at the top already. Of course you can put whatever you want there.
You can detect if it is a tall screen using
[[UIScreen mainScreen] bounds].size.height
iOS will automatically place thin black bars on either side of the app
so it remains consistent with the way the apps were originally
designed for previous versions of the iPhone
Several sources report this, this is from here.
As of yet, with no devices nor iOS 6 being available, we don't even know if or which kind of modifications would need to be done to cocos2d to support iPhone 5. Stop worrying. Don't assume. Wait and see.
As with all other devices, there will be ways to detect the exact device type. Again, this would have to wait until we get at least iOS 6. You could join the beta program in hopes of finding out, but as long as its in beta such information is under NDA and you can only find out perhaps via the private Apple developer forums.
COCOS2d - v2.x
replace CCFileUtils.h and .m with following codes and add line inside delegate: [sharedFileUtils setiPhone5DisplaySuffix:#"-568h"]; // >>>>>> iPhone 5 -568h
.h
/*
* cocos2d for iPhone: http://www.cocos2d-iphone.org
*
* Copyright (c) 2008-2010 Ricardo Quesada
* Copyright (c) 2011 Zynga Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#import <Foundation/Foundation.h>
#import "../ccTypes.h"
/** Helper class to handle file operations */
#interface CCFileUtils : NSObject
{
NSFileManager *fileManager_;
NSBundle *bundle_;
NSMutableDictionary *fullPathCache_;
NSMutableDictionary *removeSuffixCache_;
#ifdef __CC_PLATFORM_IOS
BOOL enableFallbackSuffixes_;
NSString *iPhoneRetinaDisplaySuffix_;
NSString *iPhone5DisplaySuffix_; // <<<<<<<<<<<<< ADDED
NSString *iPadSuffix_;
NSString *iPadRetinaDisplaySuffix_;
#endif // __CC_PLATFORM_IOS
}
/** NSBundle used by CCFileUtils. By default it uses [NSBundle mainBundle].
#since v2.0
*/
#property (nonatomic, readwrite, retain) NSBundle *bundle;
/** NSFileManager used by CCFileUtils. By default it uses its own intance.
#since v2.0
*/
#property (nonatomic, readwrite, retain) NSFileManager *fileManager;
#ifdef __CC_PLATFORM_IOS
/** The iPhone RetinaDisplay suffixes to load resources.
By default it is "-hd" and "" in that order.
Only valid on iOS. Not valid for OS X.
#since v1.1
*/
#property (nonatomic,readwrite, copy, setter = setiPhoneRetinaDisplaySuffix:) NSString *iPhoneRetinaDisplaySuffix;
/** The iPhone 5 suffixes to load resources.
By default it is "-hd" and "" in that order.
Only valid on iOS. Not valid for OS X.
#since v1.1
*/
#property (nonatomic,readwrite, copy, setter = setiPhone5DisplaySuffix:) NSString *iPhone5DisplaySuffix;
/** The iPad suffixes to load resources.
By default it is "-ipad", "-hd", "", in that order.
Only valid on iOS. Not valid for OS X.
#since v1.1
*/
#property (nonatomic,readwrite, copy, setter = setiPadSuffix:) NSString *iPadSuffix;
/** Sets the iPad Retina Display suffixes to load resources.
By default it is "-ipadhd", "-ipad", "-hd", "", in that order.
Only valid on iOS. Not valid for OS X.
#since v2.0
*/
#property (nonatomic,readwrite, copy, setter = setiPadRetinaDisplaySuffix:) NSString *iPadRetinaDisplaySuffix;
/** Whether of not the fallback sufixes is enabled.
When enabled it will try to search for the following suffixes in the following order until one is found:
* On iPad HD : iPad HD suffix, iPad suffix, iPhone HD suffix, Without suffix
* On iPad : iPad suffix, iPhone HD suffix, Without suffix
* On iPhone HD: iPhone HD suffix, Without suffix
By default this functionality is off;
*/
#property (nonatomic, readwrite) BOOL enableFallbackSuffixes;
#endif // __CC_PLATFORM_IOS
/** returns the shared file utils instance */
+(CCFileUtils*) sharedFileUtils;
/** Purge cached entries.
Will be called automatically by the Director when a memory warning is received
*/
-(void) purgeCachedEntries;
/** Returns the fullpath of an filename.
If in iPhoneRetinaDisplay mode, and a RetinaDisplay file is found, it will return that path.
If in iPad mode, and an iPad file is found, it will return that path.
Examples:
* In iPad mode: "image.png" -> "/full/path/image-ipad.png" (in case the -ipad file exists)
* In iPhone RetinaDisplay mode: "image.png" -> "/full/path/image-hd.png" (in case the -hd file exists)
* In iPad RetinaDisplay mode: "image.png" -> "/full/path/image-ipadhd.png" (in case the -ipadhd file exists)
*/
-(NSString*) fullPathFromRelativePath:(NSString*) relPath;
/** Returns the fullpath of an filename including the resolution of the image.
If in RetinaDisplay mode, and a RetinaDisplay file is found, it will return that path.
If in iPad mode, and an iPad file is found, it will return that path.
Examples:
* In iPad mode: "image.png" -> "/full/path/image-ipad.png" (in case the -ipad file exists)
* In iPhone RetinaDisplay mode: "image.png" -> "/full/path/image-hd.png" (in case the -hd file exists)
* In iPad RetinaDisplay mode: "image.png" -> "/full/path/image-ipadhd.png" (in case the -ipadhd file exists)
If an iPad file is found, it will set resolution type to kCCResolutioniPad
If a RetinaDisplay file is found, it will set resolution type to kCCResolutionRetinaDisplay
*/
-(NSString*) fullPathFromRelativePath:(NSString*)relPath resolutionType:(ccResolutionType*)resolutionType;
#ifdef __CC_PLATFORM_IOS
/** removes the suffix from a path
* On iPhone RetinaDisplay it will remove the -hd suffix
* On iPad it will remove the -ipad suffix
* On iPad RetinaDisplay it will remove the -ipadhd suffix
Only valid on iOS. Not valid for OS X.
#since v0.99.5
*/
-(NSString *)removeSuffixFromFile:(NSString*) path;
/** Returns whether or not a given path exists with the iPhone RetinaDisplay suffix.
Only available on iOS. Not supported on OS X.
#since v1.1
*/
-(BOOL) iPhoneRetinaDisplayFileExistsAtPath:(NSString*)filename;
/** Returns whether or not a given path exists with the iPhone 5 suffix.
Only available on iOS. Not supported on OS X.
#since v1.1
*/
-(BOOL) iPhone5DisplayFileExistsAtPath:(NSString*)filename;
/** Returns whether or not a given filename exists with the iPad suffix.
Only available on iOS. Not supported on OS X.
#since v1.1
*/
-(BOOL) iPadFileExistsAtPath:(NSString*)filename;
/** Returns whether or not a given filename exists with the iPad RetinaDisplay suffix.
Only available on iOS. Not supported on OS X.
#since v2.0
*/
-(BOOL) iPadRetinaDisplayFileExistsAtPath:(NSString*)filename;
#endif // __CC_PLATFORM_IOS
#end
#ifdef __cplusplus
extern "C" {
#endif
/** loads a file into memory.
the caller should release the allocated buffer.
#returns the size of the allocated buffer
#since v0.99.5
*/
NSInteger ccLoadFileIntoMemory(const char *filename, unsigned char **out);
#ifdef __cplusplus
}
#endif
.m
/*
* cocos2d for iPhone: http://www.cocos2d-iphone.org
*
* Copyright (c) 2008-2010 Ricardo Quesada
* Copyright (c) 2011 Zynga Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#import "CCFileUtils.h"
#import "../CCConfiguration.h"
#import "../ccMacros.h"
#import "../ccConfig.h"
#import "../ccTypes.h"
enum {
kCCiPhone,
kCCiPhoneRetinaDisplay,
kCCiPhone5Display,
kCCiPad,
kCCiPadRetinaDisplay,
};
#pragma mark - Helper free functions
NSInteger ccLoadFileIntoMemory(const char *filename, unsigned char **out)
{
NSCAssert( out, #"ccLoadFileIntoMemory: invalid 'out' parameter");
NSCAssert( &*out, #"ccLoadFileIntoMemory: invalid 'out' parameter");
size_t size = 0;
FILE *f = fopen(filename, "rb");
if( !f ) {
*out = NULL;
return -1;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
*out = malloc(size);
size_t read = fread(*out, 1, size, f);
if( read != size ) {
free(*out);
*out = NULL;
return -1;
}
fclose(f);
return size;
}
#pragma mark - CCCacheValue
#interface CCCacheValue : NSObject
{
NSString *fullpath_;
ccResolutionType resolutionType_;
}
#property (nonatomic, readwrite, retain) NSString *fullpath;
#property (nonatomic, readwrite ) ccResolutionType resolutionType;
#end
#implementation CCCacheValue
#synthesize fullpath = fullpath_, resolutionType = resolutionType_;
-(id) initWithFullPath:(NSString*)path resolutionType:(ccResolutionType)resolutionType
{
if( (self=[super init]) )
{
self.fullpath = path;
self.resolutionType = resolutionType;
}
return self;
}
- (void)dealloc
{
[fullpath_ release];
[super dealloc];
}
#end
#pragma mark - CCFileUtils
#ifdef __CC_PLATFORM_IOS
#interface CCFileUtils()
-(NSString *) removeSuffix:(NSString*)suffix fromPath:(NSString*)path;
-(BOOL) fileExistsAtPath:(NSString*)string withSuffix:(NSString*)suffix;
-(NSInteger) runningDevice;
#end
#endif // __CC_PLATFORM_IOS
#implementation CCFileUtils
#synthesize fileManager=fileManager_, bundle=bundle_;
#ifdef __CC_PLATFORM_IOS
#synthesize iPhoneRetinaDisplaySuffix = iPhoneRetinaDisplaySuffix_;
#synthesize iPhone5DisplaySuffix = iPhone5DisplaySuffix_;
#synthesize iPadSuffix = iPadSuffix_;
#synthesize iPadRetinaDisplaySuffix = iPadRetinaDisplaySuffix_;
#synthesize enableFallbackSuffixes = enableFallbackSuffixes_;
#endif // __CC_PLATFORM_IOS
+ (id)sharedFileUtils
{
static dispatch_once_t pred;
static CCFileUtils *fileUtils = nil;
dispatch_once(&pred, ^{
fileUtils = [[self alloc] init];
});
return fileUtils;
}
-(id) init
{
if( (self=[super init])) {
fileManager_ = [[NSFileManager alloc] init];
fullPathCache_ = [[NSMutableDictionary alloc] initWithCapacity:30];
removeSuffixCache_ = [[NSMutableDictionary alloc] initWithCapacity:30];
bundle_ = [[NSBundle mainBundle] retain];
#ifdef __CC_PLATFORM_IOS
iPhoneRetinaDisplaySuffix_ = #"-hd";
iPhone5DisplaySuffix_ = #"-568h";
iPadSuffix_ = #"-ipad";
iPadRetinaDisplaySuffix_ = #"-ipadhd";
enableFallbackSuffixes_ = NO;
#endif // __CC_PLATFORM_IOS
}
return self;
}
-(void) purgeCachedEntries
{
[fullPathCache_ removeAllObjects];
[removeSuffixCache_ removeAllObjects];
}
- (void)dealloc
{
[fileManager_ release];
[bundle_ release];
[fullPathCache_ release];
[removeSuffixCache_ release];
#ifdef __CC_PLATFORM_IOS
[iPhoneRetinaDisplaySuffix_ release];
[iPhone5DisplaySuffix_ release];
[iPadSuffix_ release];
[iPadRetinaDisplaySuffix_ release];
#endif // __CC_PLATFORM_IOS
[super dealloc];
}
-(NSString*) pathForResource:(NSString*)resource ofType:(NSString *)ext inDirectory:(NSString *)subpath
{
return [bundle_ pathForResource:resource
ofType:ext
inDirectory:subpath];
}
-(NSString*) getPath:(NSString*)path forSuffix:(NSString*)suffix
{
NSString *newName = path;
// only recreate filename if suffix is valid
if( suffix && [suffix length] > 0)
{
NSString *pathWithoutExtension = [path stringByDeletingPathExtension];
NSString *name = [pathWithoutExtension lastPathComponent];
// check if path already has the suffix.
if( [name rangeOfString:suffix].location == NSNotFound ) {
NSString *extension = [path pathExtension];
if( [extension isEqualToString:#"ccz"] || [extension isEqualToString:#"gz"] )
{
// All ccz / gz files should be in the format filename.xxx.ccz
// so we need to pull off the .xxx part of the extension as well
extension = [NSString stringWithFormat:#"%#.%#", [pathWithoutExtension pathExtension], extension];
pathWithoutExtension = [pathWithoutExtension stringByDeletingPathExtension];
}
newName = [pathWithoutExtension stringByAppendingString:suffix];
newName = [newName stringByAppendingPathExtension:extension];
} else
CCLOGWARN(#"cocos2d: WARNING Filename(%#) already has the suffix %#. Using it.", name, suffix);
}
NSString *ret = nil;
// only if it is not an absolute path
if( ! [path isAbsolutePath] ) {
// pathForResource also searches in .lproj directories. issue #1230
NSString *imageDirectory = [path stringByDeletingLastPathComponent];
// If the file does not exist it will return nil.
ret = [self pathForResource:[newName lastPathComponent]
ofType:nil
inDirectory:imageDirectory];
}
else if( [fileManager_ fileExistsAtPath:newName] )
ret = newName;
if( ! ret )
CCLOGINFO(#"cocos2d: CCFileUtils: file not found: %#", [newName lastPathComponent] );
return ret;
}
-(NSString*) fullPathFromRelativePath:(NSString*)relPath resolutionType:(ccResolutionType*)resolutionType
{
NSAssert(relPath != nil, #"CCFileUtils: Invalid path");
CCCacheValue *value = [fullPathCache_ objectForKey:relPath];
if( value ) {
*resolutionType = value.resolutionType;
return value.fullpath;
}
// Initialize to non-nil
NSString *ret = #"";
#ifdef __CC_PLATFORM_IOS
NSInteger device = [self runningDevice];
// iPad HD ?
if( device == kCCiPadRetinaDisplay ) {
ret = [self getPath:relPath forSuffix:iPadRetinaDisplaySuffix_];
*resolutionType = kCCResolutioniPadRetinaDisplay;
}
// iPad ?
if( device == kCCiPad || (enableFallbackSuffixes_ && !ret) ) {
ret = [self getPath:relPath forSuffix:iPadSuffix_];
*resolutionType = kCCResolutioniPad;
}
// iPhone HD ?
if( device == kCCiPhoneRetinaDisplay || (enableFallbackSuffixes_ && !ret) ) {
ret = [self getPath:relPath forSuffix:iPhoneRetinaDisplaySuffix_];
*resolutionType = kCCResolutioniPhoneRetinaDisplay;
}
// If it is not Phone HD, or if the previous "getPath" failed, then use iPhone images.
if( device == kCCiPhone || !ret )
{
ret = [self getPath:relPath forSuffix:#""];
*resolutionType = kCCResolutioniPhone;
}
#elif defined(__CC_PLATFORM_MAC)
*resolutionType = kCCResolutionMac;
ret = [self getPath:relPath forSuffix:#""];
#endif // __CC_PLATFORM_MAC
if( ! ret ) {
CCLOGWARN(#"cocos2d: Warning: File not found: %#", relPath);
ret = relPath;
}
value = [[CCCacheValue alloc] initWithFullPath:ret resolutionType:*resolutionType];
[fullPathCache_ setObject:value forKey:relPath];
[value release];
return ret;
}
-(NSString*) fullPathFromRelativePath:(NSString*) relPath
{
ccResolutionType ignore;
return [self fullPathFromRelativePath:relPath resolutionType:&ignore];
}
#pragma mark CCFileUtils - Suffix (iOS only)
#ifdef __CC_PLATFORM_IOS
// XXX: Optimization: This should be called only once
-(NSInteger) runningDevice
{
NSInteger ret=-1;
if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
if( CC_CONTENT_SCALE_FACTOR() == 2 )
ret = kCCiPadRetinaDisplay;
else
ret = kCCiPad;
}
else
{
if( CC_CONTENT_SCALE_FACTOR() == 2 )
if (([[UIScreen mainScreen] bounds].size.width == 640) || ([[UIScreen mainScreen] bounds].size.width == 1136))
{
ret = kCCiPhone5Display;
} else {
ret = kCCiPhoneRetinaDisplay;
}
else
ret = kCCiPhone;
}
return ret;
}
-(NSString *) removeSuffix:(NSString*)suffix fromPath:(NSString*)path
{
// quick return
if( ! suffix || [suffix length] == 0 )
return path;
NSString *name = [path lastPathComponent];
// check if path already has the suffix.
if( [name rangeOfString:suffix].location != NSNotFound ) {
CCLOGINFO(#"cocos2d: Filename(%#) contains %# suffix. Removing it. See cocos2d issue #1040", path, suffix);
NSString *newLastname = [name stringByReplacingOccurrencesOfString:suffix withString:#""];
NSString *pathWithoutLastname = [path stringByDeletingLastPathComponent];
return [pathWithoutLastname stringByAppendingPathComponent:newLastname];
}
// suffix was not removed
return nil;
}
-(NSString*) removeSuffixFromFile:(NSString*) path
{
NSString *withoutSuffix = [removeSuffixCache_ objectForKey:path];
if( withoutSuffix )
return withoutSuffix;
// Initial value should be non-nil
NSString *ret = #"";
NSInteger device = [self runningDevice];
if( device == kCCiPadRetinaDisplay )
ret = [self removeSuffix:iPadRetinaDisplaySuffix_ fromPath:path];
if( device == kCCiPad || (enableFallbackSuffixes_ && !ret) )
ret = [self removeSuffix:iPadSuffix_ fromPath:path];
if( device == kCCiPhoneRetinaDisplay || (enableFallbackSuffixes_ && !ret) )
ret = [self removeSuffix:iPhoneRetinaDisplaySuffix_ fromPath:path];
if( device == kCCiPhone5Display || (enableFallbackSuffixes_ && !ret) )
ret = [self removeSuffix:iPhone5DisplaySuffix_ fromPath:path];
if( device == kCCiPhone || !ret )
ret = path;
if( ret )
[removeSuffixCache_ setObject:ret forKey:path];
return ret;
}
-(BOOL) fileExistsAtPath:(NSString*)relPath withSuffix:(NSString*)suffix
{
NSString *fullpath = nil;
// only if it is not an absolute path
if( ! [relPath isAbsolutePath] ) {
// pathForResource also searches in .lproj directories. issue #1230
NSString *file = [relPath lastPathComponent];
NSString *imageDirectory = [relPath stringByDeletingLastPathComponent];
fullpath = [bundle_ pathForResource:file
ofType:nil
inDirectory:imageDirectory];
}
if (fullpath == nil)
fullpath = relPath;
NSString *path = [self getPath:fullpath forSuffix:suffix];
return ( path != nil );
}
-(BOOL) iPhoneRetinaDisplayFileExistsAtPath:(NSString*)path
{
return [self fileExistsAtPath:path withSuffix:iPhoneRetinaDisplaySuffix_];
}
-(BOOL) iPhone5DisplayFileExistsAtPath:(NSString*)path
{
return [self fileExistsAtPath:path withSuffix:iPhoneRetinaDisplaySuffix_];
}
-(BOOL) iPadFileExistsAtPath:(NSString*)path
{
return [self fileExistsAtPath:path withSuffix:iPadSuffix_];
}
-(BOOL) iPadRetinaDisplayFileExistsAtPath:(NSString*)path
{
return [self fileExistsAtPath:path withSuffix:iPadRetinaDisplaySuffix_];
}
#endif // __CC_PLATFORM_IOS
#end
All of you suggested good solutions but there will some sort of naming convention like #2x and h#2x for ratina display and iphone 5 launch images respectively.
Thanks
Mano