I have a movie with two video tracks. I need to read frames from the two tracks interleaved - that is, one frame from track 1, then one frame from track 2, then from track 1 and so on. I'm using an AVAsset with two AVAssetReaders to read the frames and it works; however, if I deallocate everything and try a second time, copyNextSampleBuffer returns null on the first frame, and the error is -11829 (Cannot Open). This doesn't happen if I try a single-track movie, or if I try reading just a single track from the same movie.
This is my stripped-down code:
NSURL *url = /* url to movie */;
NSError *localError = nil;
AVAssetReader *assetReader[2];
AVAssetReaderOutput *assetReaderOutput[2];
NSDictionary *assetOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
NSDictionary *decompressionVideoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey,
[NSDictionary dictionary], (id)kCVPixelBufferIOSurfacePropertiesKey,
nil];
for (int loop = 0; loop < 2; ++loop)
{
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:assetOptions];
float durationInSeconds = CMTimeGetSeconds(asset.duration);
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
NSUInteger numTracks = [videoTracks count];
float framesPerSecond = [videoTracks[0] nominalFrameRate];
int numFrames = (int)(durationInSeconds * framesPerSecond);
for (NSUInteger trackNum = 0; trackNum < numTracks; ++trackNum)
{
assetReader[trackNum] = [[AVAssetReader alloc] initWithAsset:asset error:&localError];
assetReaderOutput[trackNum] = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTracks[trackNum] outputSettings:decompressionVideoSettings];
[assetReader[trackNum] addOutput:assetReaderOutput[trackNum]];
[assetReader[trackNum] startReading];
}
for (int frameNum = 0; frameNum < numFrames; ++frameNum)
{
for (int trackNum = 0; trackNum < numTracks; ++trackNum)
{
CMSampleBufferRef sampleBuffer = [assetReaderOutput[trackNum] copyNextSampleBuffer];
if (sampleBuffer == NULL)
{
NSError *error = [assetReader[trackNum] error];
/* Handle error */
}
else
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (imageBuffer && (CFGetTypeID(imageBuffer) == CVPixelBufferGetTypeID()))
{
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)imageBuffer;
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
void* ptr = CVPixelBufferGetBaseAddress(pixelBuffer);
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
/* Do something with ptr */
}
CFRelease(sampleBuffer);
sampleBuffer = NULL;
}
}
}
[asset release];
asset = nil;
for (int trackNum = 0; trackNum < numTracks; ++trackNum)
{
[assetReader[trackNum] release];
assetReader[trackNum] = nil;
[assetReaderOutput[trackNum] release];
assetReaderOutput[trackNum] = nil;
}
int userInput;
scanf("%i", &userInput);
}
So on the second iteration of the outer loop, I get to the Handle error code on the first frame of the first track. Just to be clear, this does not happen if, for example, I replace the line
NSUInteger numTracks = [videoTracks count];
with
NSUInteger numTracks = 1;
Any ideas? This might be connected to this (yet unanswered) question.