AVAssetWriter finishWriting failure when CTCallStateIncoming - recording

I use AVAssetWriter AVCaptureSession to recording video.it work well.I use UIApplicationDidEnterBackgroundNotification and CTCallCenter.callEventHandler to stop record when Application goes background or a call come in.UIApplicationDidEnterBackgroundNotification works well.But in CTCallCenter.callEventHandler,[AVAssetWriter finishWriting] return NO . here is AVAssetWriter.error:
Error Domain=AVFoundationErrorDomain Code=-11800 "这项操作无法完成" UserInfo=0x6c0bc20 {NSLocalizedFailureReason=发生未知错误(-12785), NSUnderlyingError=0x6c0fc80 "The operation couldn’t be completed. (OSStatus error -12785.)", NSLocalizedDescription=这项操作无法完成}
It seems AVAssetWriter failed immediately when a call coming.The recorded file not finished and can't be played.Can someone tell me how to do with it?
CTCallCenter code:
m_callCenter = [[CTCallCenter alloc] init];
m_callCenter.callEventHandler= ^(CTCall* call)
{
if (call.callState == CTCallStateDialing || call.callState == CTCallStateIncoming){
[self stopRecording];
//[self performSelectorOnMainThread:#selector(stopRecording) withObject:nil waitUntilDone:NO];
}
};
stopRecording work fine in other case.

Phone calls cause AudioSession interruptions, so you might find out sooner if you use the AudioSession callback. Although I suspect your AVAssetWriter may already be fried at this point.
Setting AVAssetWriter.movieFragmentInterval should help minimize your loss - from AVAssetWriter.h:
When movie fragments are used, a partially written asset whose writing
is unexpectedly interrupted can be successfully opened and played up
to multiples of the specified time interval.

Related

Why do I sometimes get mangled replies with concurrent NSURLSession requests

I am working on an OS X (Yosemite) app which downloads two types of csv data (call them type A and type B) from the internet asynchronously using the NSURLSession API .There are multiple requests for each type of csv. Each request is it's own dedicated session wrapped in a custom class. There is one base request class with a subclass for each type. (In hindsight maybe not an ideal design but irrelevant for my issue I think).
The app is constructed such that each type of csv data is downloaded in a sequential queue. Only one request of each type can be active at a time but both types can occur simultaneously and both use the main thread for delegate callbacks. All of this works fine usually.
The issue I am seeing is that sometimes with heavy traffic I get "cross hearing", i.e. I sometimes get a response back to a type B request that is reported as completed successfully but it contains a number of type B cvs lines and then some type A lines tagged on after - so I sometimes (rarely) get type A data in my type B requests. (or the other way around).
Basically it look like the "switching" logic in Apples API gets confused about which incoming packet belongs to what request/session. The two different request types goes to different URLs but they are related and it may be that they both in the end resolve to the same IP, I am not sure about that. I wonder if there may be something related to the packet headers if they come from the same server that makes it difficult to determine what request they belong to (I'm not good enough at the internet protocols to know if this is a sensible guess). If that is the case then the solution must be to ensure all requests are in one queue so that they cannot be active simultaneously, but I do not want to do that large architecture change before I am confident there is no other workaround.
I looked for similar questions and found this old question (Why is my data getting corrupted when I send requests asynchronously in objective c for iOS?) which appears to describe the exact same issue but unfortunately it has no answer. Other than that I found nothing similar so I guess I am doing something stupid here but it would be good to know why this issue occurs before I start changing the architecture to fix it.
Has anyone seen this before and know what the cause and workaround is?
I did not include any code as I felt there was no point given it appears to be an architecture issue and if I added code it would need to be a lot. However I will be happy to add whatever you suggest if that helps understand the question.
Edit:
The relevant (I hope) code added below. Note objects are one shot only. The parameters for the request are injected by the init method and the NSURLSession is used for a single task only. Hence the session is invalidated after launch and the NSMutableData array released after parsing of the data.
-(BOOL)executeRequest {
NSURLSessionConfiguration *theConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *theSession = [NSURLSession sessionWithConfiguration:theConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:self.queryURL cachePolicy: NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:BSTTIMEOUT];
NSURLSessionDataTask *theTask = [theSession dataTaskWithRequest:theRequest];
if(!theTask) {
return NO;
}
[theTask resume];
[theSession finishTasksAndInvalidate];
self.internetData = [NSMutableData dataWithCapacity:0];
return YES;
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.internetData appendData:data];
return;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if((error)||(![self parseData]))
{
self.internetData = nil;
if(!error) {
NSDictionary *errorDictionary = #{ NSLocalizedDescriptionKey : #"Parsing of internet data failed", NSLocalizedFailureReasonErrorKey : #"Bad data was found in received buffer"};
error = [NSError errorWithDomain:NSCocoaErrorDomain code:EIO userInfo:errorDictionary];
}
NSDictionary* ui = [NSDictionary dictionaryWithObject:error forKey:#"Error"];
[[NSNotificationCenter defaultCenter] postNotificationName:[self failNotification] object:self userInfo:ui];
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:[self successNotification] object:self];
return;
}
First of all: You should not create a new session for each request. This are no sessions anymore. From the docs:
With the NSURLSession API, your app creates one or more sessions, each of which coordinates a group of related data transfer tasks. For example, if you are writing a web browser, your app might create one session per tab or window, or one session for interactive use and another session for background downloads. Within each session, your app adds a series of tasks, each of which represents a request for a specific URL (following HTTP redirects if necessary).
Second: Where do you store the session et al., so it is not deallocated?
Your main problem: Obviously you start new requests while requests are potentially running. But you have only one NSMutableData instance that receives the data in -URLSession:task:didReceiveData:: Many requests, one storage … Of course that mixes up.
I finally managed to track down my (stupid) error. For future reference the issue was caused by a failure to realise that the data coming back was not zero terminated.
Most of the data requested in my case is XML and the NSXMLParserclass wants a NSDatawithout extra trailing zeros so that works well.
But the requests which occasionally failed uses a CSV format where the data passes over a NSStringwhich is created by [NSString stringWithUTF8String] which expects a zero terminated c style string as input. This was the main culprit. Often it worked as it should. Sometimes it failed outright and sometimes it just did a buffer overrun and got some of the previous request data that was in the same memory area. These were the cases I noticed when posting the question.
Thus the solution is to switch to the use of [[NSString alloc] initWithData: encoding:NSUTF8StringEncoding] which works with non null-terminated NSDatabuffers.

GCDAsyncSocket didReadData only gets called once

I am trying to set up a Java server talking to a iPhone client using GCDAsyncSocket. For some reason my client code on the iPhone is not reading back all of the data.
I see didReadData gets called the first time, but never again. Ideally, I need to mimic the functionality of the HTTP protocol where it sends a header and then the payload. The size of the payload would be in the header. But that wasn't working, so I simplified my code even further in hopes of finding the issue. Below is the code, and below that the output.
client:
- (BOOL) sendString:(NSString *) string
{
[asyncSocket writeData:[string dataUsingEncoding:NSUTF8StringEncoding]
withTimeout:-1 tag:TAG_PAYLOAD];
[asyncSocket readDataToLength:1 withTimeout:(-1) tag:TAG_HEADER];
}
- (void) socket:(GCDAsyncSocket *) sock didReadData:(NSData *)data
withTag:(long)tag
{
NSString *str = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(#"Read data %# with tag: %ld", str, tag);
if(tag == TAG_HEADER)
{
//TODO - parse the header, get the fields
[sock readDataToLength:3 withTimeout:-1 tag:TAG_PAYLOAD];
//[sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1
tag:TAG_PAYLOAD];
}
else
{
NSLog(#"Payload... %#", str);
NSLog(#"Tag: %ld", tag);
}
}
Java server:
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
String clientCommand = in.readLine();
System.out.println("Client Says :" + clientCommand);
out.println("Andrew\r\nAUSTIN\r\n");
out.flush();
However, on the client, the only thing I get back is A. The exact output is:
Read data A with tag: 10
My question is:
How come the didReadData method is never called again? There should be more of "Andrew\r\nAustin" recv'd back on the client. But it just hangs. The readDataToData and readDataToLength both seem to never get the full string.
I noticed the CRLF defined in GCDAsyncSocket.h is not \r\n but instead the hex values. Does this matter? Thats why I tried the readDataToLength method but that still failed. But I would like to know if this matters cross-platform or not.
Thanks.
OK - so I figured it out after pulling out what little hair I have left.
What is happening is that I have client code above in a separate class outside of the view. Practically all of the examples I came across had the GCDAsyncSocket stuff handled inside the view. It works great in there! I really didn't want to do this because on each view I need to send/read data and didn't want to duplicate my work. By placing an NSLog() line in the dealloc method of this helper class, called SocketComm, I was able to see it was getting deallocated before it was firing. So I needed to change the way I was calling my helper class. I declare SocketComm* sockComm a strong property in the viewController.h file and allocated it in the viewDidLoad() method. This means that it stays in scope the whole time. Of course, this means I need to deallocate it manually and do some other housekeeping things.
I still am not sure if this is the best way to handle this situation either, as far as memory management goes. Because now I will have to alloc this on every viewDidLoad method. It seems like it should be simpler than this, but here we are. And I still don't know why it never read the data the first time (my only guess is that the GCDAsyncSocket library or the iphone software detected a dead thread when the parent that spawned it got deallocated and decided to terminate it - but this is only a guess as I have just started objective-c).
This would also explain why sometimes it would work and sometimes it wouldn't. It seemed like it was in a race condition. Not sure if the above code I originally posted resulted in a race condition exactly, but some things I would try would work, and then the next time fail. It never read more than the first time though, and only about half the time would it even read that. Sometimes it wouldn't even send the data out over the socket!
In summation (and for whoever else comes looking for an answer):
Always check your memory management. I had to place an NSLog in dealloc() of the SocketComm helper class to fully see what was happening, and as soon as I did that I knew what the culprit was.
If you get weird results where sometimes it works and sometimes it doesn't, check your memory management. For me, sometimes it would do the first read and sometimes it wouldn't. This lead me to believe the thread was getting terminated.
If I find a better way to do this I will come back and update this answer.
Memory management. Let me repeat: memory management.

OSX-Parse - How to get images in background

I have this code going to get Parse images into my OSX app.
// give our representation to the image browser
- (id)imageRepresentation {
NSLog(#"%s", __FUNCTION__);
return [_file getData];
}
This works but results in :
Warning: A long-running Parse operation is being executed on the main thread.
Break on warnParseOperationOnMainThread() to debug.
I need to move the [_file getData] method to a background thread, and could use some help.
The error message you are getting -
Warning: A long-running Parse operation is being executed on the main thread.
Break on warnParseOperationOnMainThread() to debug.
Is simply a warning when running queries that are asynchronous. More information can be found here.
If you want to run a query in the background, use the findObjectsInBackgroundWithBlock: method of the PFQuery class.

Should didReceiveResponse always be called for NSURLSessionUploadTasks with custom delegates?

I'm investigating using NSURLSessionUploadTasks to manage the background uploading of a few files. The session is created using:
_urlsession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration backgroundSessionConfiguration:identifier] delegate:self delegateQueue:nil];
This is created within a class that conforms to URLSessionDataTaskDelegate, and specifically defines:
– URLSession:dataTask:didReceiveResponse:completionHandler:
– URLSession:dataTask:didBecomeDownloadTask:
– URLSession:dataTask:didReceiveData:
And logs to the console each time one of these delegates is called.
Then, an upload task is created with the following code:
NSString *urlString = [NSString stringWithFormat:#"%#%#?filename=%#", HOST, UPLOAD_PATH, filename];
NSMutableURLRequest *attachmentUploadRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
attachmentUploadRequest.HTTPMethod = #"POST";
[attachmentUploadRequest addValue:#"application/binary" forHTTPHeaderField:#"Content-Type"];
NSURLSessionTask* task = [_urlsession uploadTaskWithRequest:attachmentUploadRequest fromFile:filePath];
task.taskDescription = 'upload';
However, the sequence of delegate callbacks that I get is not as expected:
URLSession:didReceiveChallenge:completionHandler:]:196: Respond with <NSURLCredential: 0x1cf4fe00>:
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]:282: Task 'upload' sent 32768 bytes
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:]:282: Task 'upload' sent 48150 bytes
URLSession:dataTask:didReceiveData:]:222: Task 'upload' got some data:
Notably, the body data is sent, as expected, but then it switches immediately to didReceiveData delegate callbacks, with no didReceiveResponse callback beforehand. This is an unexpected behavior for me: I'd expected to receive information about the response so that I can properly set up data, or better yet, convert the task to a download task to save the response to a file.
If the upload task is submitted in a default URL session, then didReceiveResponse is called, and I can successfully convert the task to a background download task.
I can't find any indications in Apple's documentation for whether or not didReceiveResponse should be called for NSURLSessionUploadTasks that are in the background. It seems that they should: the documentation for NSURLSessionUploadTask indicates that it is a subclass of NSURLSessionDataTask with small modifications in behavior, but neither of the listed differences involves not sending the didReceiveResponse callback. None of the background-session-specific docs mention this limitation.
Is this a bug, or have a missed/misinterpreted some piece of the documentation that explain that upload tasks in the background do not call didReceiveResponse?
I asked Apple engineers about this during recent Tech Talks. They followed up and gave the following response - not entirely satisfactory, and I feel like they should document this behavior if it is different than any other HTTP handling flow. Especially since the foreground behavior does get the didReceiveData, but doesn't get the didReceiveResponse. At the very least they need to document this non-obvious behavior.
"The way things work today is that we don’t send the didReceiveResponse callback for background uploads to avoid waking the app if it’s not already running. The drawback is that the app cannot choose to convert the background upload into a download task when the response is received. Our decision was based on expecting the response data for a file upload would be small and therefore delivering the response data to the client as NSData instead of a downloaded file would be fine."

How can I make multiple calls to initWithContentsOfURL without it eventually returning the wrong stuff?

I'm doing multiple levels of parsing of web pages where I use information from one page to drill down and grab a "lower" page to parse. When I get to the lowest level of my hierarchy, I no longer hit a new page, I basically hit the same one (with different parameters) and make SQL database entries.
If I don't slow things down (by putting a sleep(1)) before that inner loop, initWithContentsOfURL eventually returns a kind of stub piece of HTML. Here's the code I use to get my HTML nodes:
NSError *err = nil;
NSString* webStringURL = [sURL stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
NSData *contentData = [[[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: webStringURL]
options: 0
error: &err] autorelease];
NSString *dataString = [[[NSString alloc] initWithData: contentData
encoding: NSISOLatin1StringEncoding] autorelease];
NSData *data = [dataString dataUsingEncoding: NSUTF8StringEncoding];
TFHpple *xPathDoc = [[[TFHpple alloc] initWithHTMLData: data] autorelease];
It works fine with 4 levels of looping. In faxt, it can run 24/7 with no real memory leak problem. It only dies when I have a connection issue. That is as long as I put in the sleep(1) before the inner-most loop.
It's like it's too fast and initWithContentsOfURL can't keep up. I suppose I could try to do something asynchronous but this is not for user-consumption and the direct synchronous looping works just fine... almost. I've tried different ways of slowing things down. Pausing for one second on a regular basis works but if I take that out, it starts getting bogus data after about 10 times through the inner loop. Is there a way to handle this properly?
I don't think it's a problem of initWithContentsOfURL; rather, I suspect it's the server or network that is unable to respond that quickly.
The following assumes that's the case.
If you want to receive network errors and/or server response errors, you need to use NSURLConnection. There's no way to get notified about the error from initWithContentsOfURL. If you know what is the stub page, or if you know a magic string in the successful response, you can check the returned NSData against those.

Resources