zoul
zoul

Reputation: 104065

Memory problems with [AVAssetWriterInput requestMediaDataWhenReadyOnQueue:usingBlock:]

I’m writing a library to export assets to a file using AVFoundation. I create a reader, a writer, connect the inputs and outputs to these and then call the requestMediaDataWhenReadyOnQueue method on the inputs to start pulling the data. The block callback supplied to this method looks a bit like this:

[input requestMediaDataWhenReadyOnQueue:queue usingBlock:^{
    while ([input isReadyForMoreMediaData]) {
        CMSampleBufferRef buffer;
        // The track has some more data for us
        if ([reader status] == AVAssetReaderStatusReading
               && (buffer = [output copyNextSampleBuffer])) {
            BOOL result = [input appendSampleBuffer:buffer];
            CFRelease(buffer);
            if (!result) {
                // handle error
                break;
            }
        // The track is finished, for whatever reason
        } else {
            [input markAsFinished]; ⬅
            switch ([reader status]) {
                // inspect the status and act accordingly
            }
        }
    }
}];

This works perfectly on iOS 5, but on iOS 4 the code dies from EXC_BAD_ACCESS after the line marked with the ⬅ arrow. After some poking around I feel like the block was somehow destroyed immediately after marking the input as finished. The self pointer that’s perfectly valid before executing the bad line somehow turns into 0xfff… or some garbage value as reported by the debugger. But the object pointed to it before is fine, as confirmed by the zombies tool, it does not get deallocated.

What am I missing?

Upvotes: 3

Views: 3645

Answers (2)

nh32rg
nh32rg

Reputation: 1482

Try [self retain] as the first line of the block and [self release] as the last line.

Another critical issue is that if the App is suspended (enters background) using requestMediaDataWhenReadyOnQueue you need to explicitly cover all of the [reader status] values as it will fail when the app restarts. In some cases I found the block ran more than once with a fail status flag. In other posts with similar code there's a lot of [retain]ing of the AV variables, which are then released at the end of the block. Because the block can run more than once this approach doesn't work in cases when the app enters the background state.

I found the following to work well in the "switch" (above):

                case AVAssetReaderStatusReading:
                    break;

                case AVAssetReaderStatusCompleted:
                    [videoWriterInput markAsFinished];
                    //do something else, like add an audio stream
                    [videoWriter finishWriting];
                    break;

                case AVAssetReaderStatusFailed:
                    [videoWriterInput markAsFinished];
                    [videoWriter finishWriting];
                    break;

                case AVAssetReaderStatusCancelled:
                case AVAssetReaderStatusUnknown:
                    [videoWriterInput markAsFinished];
                    [videoWriter cancelWriting];
                    break;
            }

            dispatch_sync(dispatch_get_main_queue(), ^{
              //hide any progress indicators
            });

            break;

other than "self", nothing is retained. The block should retain the variables automatically if they are required.

Upvotes: 0

Ray Fix
Ray Fix

Reputation: 5645

Seeing the same (similar) issue. iOS5 happy, iOS4.3.5, not happy. Interested to learn what you ultimately find.

Got around it by explicitly retaining writer, writer input, reader, reader output before the requestMedatWhenReadyOnQueue block and explicitly releasing all four at the very end of the else clause.

The doc does say that after marking finished, "The block should then exit." Maybe they are not kidding. If you do anything other than exit, it is an error. The above workaround seems to work though.

UPDATE: I still found that it occasionally crashed even after retaining and releasing all of the asset objects. As your question observes, it crashes shortly after you mark the writer input as finished it is as if the block itself is being deallocated. Rather than just pass the block as part of the function. I create a copied block property that is part of a long lived object. I initialize it with Block_copy and only release it in the destructor of the long lived object. This seems to do the trick. I haven't seen any 4.3.5 crashes since.

Upvotes: 1

Related Questions