mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #28173 from satyam102006:fix/issue-28165-videoio-ios-crash
videoio(ios): fix NSInvalidArgumentException in VideoWriter release #28173 Summary Fixes a crash on iOS when calling `VideoWriter::release()` in OpenCV 4.12.0. Issue Fixes #28165 Detailed Description This PR resolves a regression where an `NSInvalidArgumentException` was thrown with the message ` -[NSAutoreleasePool retain]: Cannot retain an autorelease pool`. The crash was caused by the manual usage of `NSAutoreleasePool` in the `~CvVideoWriter_AVFoundation` destructor. The local pool variable was being captured by the completion handler block passed to `[mMovieWriter finishWritingWithCompletionHandler:]`. Since `NSAutoreleasePool` instances cannot be retained, capturing them in a block causes a crash. Changes: - Replaced manual `NSAutoreleasePool` allocation and draining with modern `@autoreleasepool` blocks in `CvVideoWriter_AVFoundation`. - applied this modernization to the Constructor, Destructor, and `write` methods. - Specifically in the destructor, an inner `@autoreleasepool` is now used inside the completion block, ensuring no pool object needs to be captured from the outer scope.
This commit is contained in:
@@ -1155,228 +1155,198 @@ CvVideoWriter_AVFoundation::CvVideoWriter_AVFoundation(const char* filename, int
|
||||
double fps, const cv::Size& frame_size,
|
||||
int is_color) {
|
||||
|
||||
NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init];
|
||||
@autoreleasepool {
|
||||
frameCount = 0;
|
||||
movieFPS = fps;
|
||||
movieSize = frame_size;
|
||||
movieColor = is_color;
|
||||
argbimage = cv::Mat(movieSize, CV_8UC4);
|
||||
path = [[[NSString stringWithCString:filename encoding:NSASCIIStringEncoding] stringByExpandingTildeInPath] retain];
|
||||
|
||||
/*
|
||||
AVFileTypeQuickTimeMovie
|
||||
UTI for the QuickTime movie file format.
|
||||
The value of this UTI is com.apple.quicktime-movie. Files are identified with the .mov and .qt extensions.
|
||||
|
||||
frameCount = 0;
|
||||
movieFPS = fps;
|
||||
movieSize = frame_size;
|
||||
movieColor = is_color;
|
||||
argbimage = cv::Mat(movieSize, CV_8UC4);
|
||||
path = [[[NSString stringWithCString:filename encoding:NSASCIIStringEncoding] stringByExpandingTildeInPath] retain];
|
||||
AVFileTypeMPEG4
|
||||
UTI for the MPEG-4 file format.
|
||||
The value of this UTI is public.mpeg-4. Files are identified with the .mp4 extension.
|
||||
|
||||
AVFileTypeAppleM4V
|
||||
UTI for the iTunes video file format.
|
||||
The value of this UTI is com.apple.mpeg-4-video. Files are identified with the .m4v extension.
|
||||
|
||||
/*
|
||||
AVFileTypeQuickTimeMovie
|
||||
UTI for the QuickTime movie file format.
|
||||
The value of this UTI is com.apple.quicktime-movie. Files are identified with the .mov and .qt extensions.
|
||||
AVFileType3GPP
|
||||
UTI for the 3GPP file format.
|
||||
The value of this UTI is public.3gpp. Files are identified with the .3gp, .3gpp, and .sdv extensions.
|
||||
*/
|
||||
|
||||
AVFileTypeMPEG4
|
||||
UTI for the MPEG-4 file format.
|
||||
The value of this UTI is public.mpeg-4. Files are identified with the .mp4 extension.
|
||||
|
||||
AVFileTypeAppleM4V
|
||||
UTI for the iTunes video file format.
|
||||
The value of this UTI is com.apple.mpeg-4-video. Files are identified with the .m4v extension.
|
||||
|
||||
AVFileType3GPP
|
||||
UTI for the 3GPP file format.
|
||||
The value of this UTI is public.3gpp. Files are identified with the .3gp, .3gpp, and .sdv extensions.
|
||||
*/
|
||||
|
||||
NSString *fileExt =[[[path pathExtension] lowercaseString] copy];
|
||||
if ([fileExt isEqualToString:@"mov"] || [fileExt isEqualToString:@"qt"]){
|
||||
fileType = [AVFileTypeQuickTimeMovie copy];
|
||||
}else if ([fileExt isEqualToString:@"mp4"]){
|
||||
fileType = [AVFileTypeMPEG4 copy];
|
||||
}else if ([fileExt isEqualToString:@"m4v"]){
|
||||
fileType = [AVFileTypeAppleM4V copy];
|
||||
NSString *fileExt = [[[path pathExtension] lowercaseString] copy];
|
||||
if ([fileExt isEqualToString:@"mov"] || [fileExt isEqualToString:@"qt"]) {
|
||||
fileType = [AVFileTypeQuickTimeMovie copy];
|
||||
} else if ([fileExt isEqualToString:@"mp4"]) {
|
||||
fileType = [AVFileTypeMPEG4 copy];
|
||||
} else if ([fileExt isEqualToString:@"m4v"]) {
|
||||
fileType = [AVFileTypeAppleM4V copy];
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
}else if ([fileExt isEqualToString:@"3gp"] || [fileExt isEqualToString:@"3gpp"] || [fileExt isEqualToString:@"sdv"] ){
|
||||
fileType = [AVFileType3GPP copy];
|
||||
} else if ([fileExt isEqualToString:@"3gp"] || [fileExt isEqualToString:@"3gpp"] || [fileExt isEqualToString:@"sdv"]) {
|
||||
fileType = [AVFileType3GPP copy];
|
||||
#endif
|
||||
} else{
|
||||
fileType = [AVFileTypeMPEG4 copy]; //default mp4
|
||||
}
|
||||
[fileExt release];
|
||||
} else {
|
||||
fileType = [AVFileTypeMPEG4 copy]; //default mp4
|
||||
}
|
||||
[fileExt release];
|
||||
|
||||
char cc[5];
|
||||
cc[0] = fourcc & 255;
|
||||
cc[1] = (fourcc >> 8) & 255;
|
||||
cc[2] = (fourcc >> 16) & 255;
|
||||
cc[3] = (fourcc >> 24) & 255;
|
||||
cc[4] = 0;
|
||||
int cc2 = CV_FOURCC(cc[0], cc[1], cc[2], cc[3]);
|
||||
if (cc2!=fourcc) {
|
||||
std::cout << "WARNING: Didn't properly encode FourCC. Expected " << fourcc
|
||||
<< " but got " << cc2 << "." << std::endl;
|
||||
//exception;
|
||||
}
|
||||
char cc[5];
|
||||
cc[0] = fourcc & 255;
|
||||
cc[1] = (fourcc >> 8) & 255;
|
||||
cc[2] = (fourcc >> 16) & 255;
|
||||
cc[3] = (fourcc >> 24) & 255;
|
||||
cc[4] = 0;
|
||||
int cc2 = CV_FOURCC(cc[0], cc[1], cc[2], cc[3]);
|
||||
if (cc2 != fourcc) {
|
||||
std::cout << "WARNING: Didn't properly encode FourCC. Expected " << fourcc
|
||||
<< " but got " << cc2 << "." << std::endl;
|
||||
}
|
||||
|
||||
// Three codec supported AVVideoCodecTypeH264 AVVideoCodecTypeJPEG AVVideoCodecTypeHEVC
|
||||
// On iPhone 3G H264 is not supported.
|
||||
if (fourcc == CV_FOURCC('J','P','E','G') || fourcc == CV_FOURCC('j','p','e','g') ||
|
||||
fourcc == CV_FOURCC('M','J','P','G') || fourcc == CV_FOURCC('m','j','p','g')){
|
||||
codec = [AVVideoCodecTypeJPEG copy]; // Use JPEG codec if specified, otherwise H264
|
||||
}else if(fourcc == CV_FOURCC('H','2','6','4') || fourcc == CV_FOURCC('a','v','c','1')){
|
||||
codec = [AVVideoCodecTypeH264 copy];
|
||||
// Three codec supported AVVideoCodecTypeH264 AVVideoCodecTypeJPEG AVVideoCodecTypeHEVC
|
||||
// On iPhone 3G H264 is not supported.
|
||||
if (fourcc == CV_FOURCC('J','P','E','G') || fourcc == CV_FOURCC('j','p','e','g') ||
|
||||
fourcc == CV_FOURCC('M','J','P','G') || fourcc == CV_FOURCC('m','j','p','g')) {
|
||||
codec = [AVVideoCodecTypeJPEG copy]; // Use JPEG codec if specified, otherwise H264
|
||||
} else if (fourcc == CV_FOURCC('H','2','6','4') || fourcc == CV_FOURCC('a','v','c','1')) {
|
||||
codec = [AVVideoCodecTypeH264 copy];
|
||||
// Available since iOS 11
|
||||
#if TARGET_OS_VISION || (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
|
||||
}else if(fourcc == CV_FOURCC('H','2','6','5') || fourcc == CV_FOURCC('h','v','c','1') ||
|
||||
fourcc == CV_FOURCC('H','E','V','C') || fourcc == CV_FOURCC('h','e','v','c')){
|
||||
if (@available(iOS 11, *)) {
|
||||
codec = [AVVideoCodecTypeHEVC copy];
|
||||
} else {
|
||||
codec = [AVVideoCodecTypeH264 copy];
|
||||
}
|
||||
} else if (fourcc == CV_FOURCC('H','2','6','5') || fourcc == CV_FOURCC('h','v','c','1') ||
|
||||
fourcc == CV_FOURCC('H','E','V','C') || fourcc == CV_FOURCC('h','e','v','c')) {
|
||||
if (@available(iOS 11, *)) {
|
||||
codec = [AVVideoCodecTypeHEVC copy];
|
||||
} else {
|
||||
codec = [AVVideoCodecTypeH264 copy];
|
||||
}
|
||||
#endif
|
||||
}else{
|
||||
codec = [AVVideoCodecTypeH264 copy]; // default canonical H264.
|
||||
} else {
|
||||
codec = [AVVideoCodecTypeH264 copy]; // default canonical H264.
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
|
||||
// Wire the writer:
|
||||
// Supported file types:
|
||||
// AVFileTypeQuickTimeMovie AVFileTypeMPEG4 AVFileTypeAppleM4V AVFileType3GPP
|
||||
|
||||
mMovieWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path]
|
||||
fileType:fileType
|
||||
error:&error];
|
||||
|
||||
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
codec, AVVideoCodecKey,
|
||||
[NSNumber numberWithInt:movieSize.width], AVVideoWidthKey,
|
||||
[NSNumber numberWithInt:movieSize.height], AVVideoHeightKey,
|
||||
nil];
|
||||
|
||||
mMovieWriterInput = [[AVAssetWriterInput
|
||||
assetWriterInputWithMediaType:AVMediaTypeVideo
|
||||
outputSettings:videoSettings] retain];
|
||||
|
||||
[mMovieWriter addInput:mMovieWriterInput];
|
||||
|
||||
mMovieWriterAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:mMovieWriterInput sourcePixelBufferAttributes:nil];
|
||||
|
||||
//Start a session:
|
||||
[mMovieWriter startWriting];
|
||||
[mMovieWriter startSessionAtSourceTime:kCMTimeZero];
|
||||
|
||||
if (mMovieWriter.status == AVAssetWriterStatusFailed) {
|
||||
NSLog(@"%@", [mMovieWriter.error localizedDescription]);
|
||||
// TODO: error handling, cleanup. Throw exception?
|
||||
}
|
||||
}
|
||||
|
||||
//NSLog(@"Path: %@", path);
|
||||
|
||||
NSError *error = nil;
|
||||
|
||||
|
||||
// Make sure the file does not already exist. Necessary to overwrite??
|
||||
/*
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
if ([fileManager fileExistsAtPath:path]){
|
||||
[fileManager removeItemAtPath:path error:&error];
|
||||
}
|
||||
*/
|
||||
|
||||
// Wire the writer:
|
||||
// Supported file types:
|
||||
// AVFileTypeQuickTimeMovie AVFileTypeMPEG4 AVFileTypeAppleM4V AVFileType3GPP
|
||||
|
||||
mMovieWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path]
|
||||
fileType:fileType
|
||||
error:&error];
|
||||
//NSParameterAssert(mMovieWriter);
|
||||
|
||||
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
codec, AVVideoCodecKey,
|
||||
[NSNumber numberWithInt:movieSize.width], AVVideoWidthKey,
|
||||
[NSNumber numberWithInt:movieSize.height], AVVideoHeightKey,
|
||||
nil];
|
||||
|
||||
mMovieWriterInput = [[AVAssetWriterInput
|
||||
assetWriterInputWithMediaType:AVMediaTypeVideo
|
||||
outputSettings:videoSettings] retain];
|
||||
|
||||
//NSParameterAssert(mMovieWriterInput);
|
||||
//NSParameterAssert([mMovieWriter canAddInput:mMovieWriterInput]);
|
||||
|
||||
[mMovieWriter addInput:mMovieWriterInput];
|
||||
|
||||
mMovieWriterAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:mMovieWriterInput sourcePixelBufferAttributes:nil];
|
||||
|
||||
|
||||
//Start a session:
|
||||
[mMovieWriter startWriting];
|
||||
[mMovieWriter startSessionAtSourceTime:kCMTimeZero];
|
||||
|
||||
|
||||
if(mMovieWriter.status == AVAssetWriterStatusFailed){
|
||||
NSLog(@"%@", [mMovieWriter.error localizedDescription]);
|
||||
// TODO: error handling, cleanup. Throw exception?
|
||||
// return;
|
||||
}
|
||||
|
||||
[localpool drain];
|
||||
}
|
||||
|
||||
|
||||
CvVideoWriter_AVFoundation::~CvVideoWriter_AVFoundation() {
|
||||
NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
[mMovieWriterInput markAsFinished];
|
||||
[mMovieWriter finishWritingWithCompletionHandler:^() {
|
||||
[mMovieWriter release];
|
||||
[mMovieWriterInput release];
|
||||
[mMovieWriterAdaptor release];
|
||||
[path release];
|
||||
[codec release];
|
||||
[fileType release];
|
||||
argbimage.release();
|
||||
|
||||
[localpool drain];
|
||||
}];
|
||||
@autoreleasepool {
|
||||
[mMovieWriterInput markAsFinished];
|
||||
[mMovieWriter finishWritingWithCompletionHandler:^() {
|
||||
@autoreleasepool {
|
||||
[mMovieWriter release];
|
||||
[mMovieWriterInput release];
|
||||
[mMovieWriterAdaptor release];
|
||||
[path release];
|
||||
[codec release];
|
||||
[fileType release];
|
||||
argbimage.release();
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
void CvVideoWriter_AVFoundation::write(cv::InputArray image) {
|
||||
NSAutoreleasePool* localpool = [[NSAutoreleasePool alloc] init];
|
||||
@autoreleasepool {
|
||||
// writer status check
|
||||
if (![mMovieWriterInput isReadyForMoreMediaData] || mMovieWriter.status != AVAssetWriterStatusWriting) {
|
||||
NSLog(@"[mMovieWriterInput isReadyForMoreMediaData] Not ready for media data or ...");
|
||||
NSLog(@"mMovieWriter.status: %d. Error: %@", (int)mMovieWriter.status, [mMovieWriter.error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
// writer status check
|
||||
if (![mMovieWriterInput isReadyForMoreMediaData] || mMovieWriter.status != AVAssetWriterStatusWriting ) {
|
||||
NSLog(@"[mMovieWriterInput isReadyForMoreMediaData] Not ready for media data or ...");
|
||||
NSLog(@"mMovieWriter.status: %d. Error: %@", (int)mMovieWriter.status, [mMovieWriter.error localizedDescription]);
|
||||
[localpool drain];
|
||||
return;
|
||||
BOOL success = FALSE;
|
||||
|
||||
if (image.size().height != movieSize.height || image.size().width != movieSize.width) {
|
||||
std::cout << "Frame size does not match video size." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (movieColor) {
|
||||
cv::cvtColor(image, argbimage, cv::COLOR_BGR2BGRA);
|
||||
} else {
|
||||
cv::cvtColor(image, argbimage, cv::COLOR_GRAY2BGRA);
|
||||
}
|
||||
|
||||
//IplImage -> CGImage conversion
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
NSData *nsData = [NSData dataWithBytes:argbimage.data length:argbimage.total() * argbimage.elemSize()];
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)nsData);
|
||||
CGImageRef cgImage = CGImageCreate(argbimage.size().width, argbimage.size().height,
|
||||
8, 32, argbimage.step[0],
|
||||
colorSpace, kCGImageAlphaLast|kCGBitmapByteOrderDefault,
|
||||
provider, NULL, false, kCGRenderingIntentDefault);
|
||||
|
||||
//CGImage -> CVPixelBufferRef conversion
|
||||
CVPixelBufferRef pixelBuffer = NULL;
|
||||
CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
|
||||
int status = CVPixelBufferCreateWithBytes(NULL,
|
||||
movieSize.width,
|
||||
movieSize.height,
|
||||
kCVPixelFormatType_32BGRA,
|
||||
(void*)CFDataGetBytePtr(cfData),
|
||||
CGImageGetBytesPerRow(cgImage),
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
&pixelBuffer);
|
||||
if (status == kCVReturnSuccess) {
|
||||
success = [mMovieWriterAdaptor appendPixelBuffer:pixelBuffer
|
||||
withPresentationTime:CMTimeMake(frameCount, movieFPS)];
|
||||
}
|
||||
|
||||
//cleanup
|
||||
CFRelease(cfData);
|
||||
CVPixelBufferRelease(pixelBuffer);
|
||||
CGImageRelease(cgImage);
|
||||
CGDataProviderRelease(provider);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
if (success) {
|
||||
frameCount++;
|
||||
return;
|
||||
} else {
|
||||
NSLog(@"Frame appendPixelBuffer failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL success = FALSE;
|
||||
|
||||
if (image.size().height!=movieSize.height || image.size().width!=movieSize.width){
|
||||
std::cout<<"Frame size does not match video size."<<std::endl;
|
||||
[localpool drain];
|
||||
return;
|
||||
}
|
||||
|
||||
if (movieColor) {
|
||||
//assert(image->nChannels == 3);
|
||||
cv::cvtColor(image, argbimage, cv::COLOR_BGR2BGRA);
|
||||
}else{
|
||||
//assert(image->nChannels == 1);
|
||||
cv::cvtColor(image, argbimage, cv::COLOR_GRAY2BGRA);
|
||||
}
|
||||
//IplImage -> CGImage conversion
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
NSData *nsData = [NSData dataWithBytes:argbimage.data length:argbimage.total() * argbimage.elemSize()];
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)nsData);
|
||||
CGImageRef cgImage = CGImageCreate(argbimage.size().width, argbimage.size().height,
|
||||
8, 32, argbimage.step[0],
|
||||
colorSpace, kCGImageAlphaLast|kCGBitmapByteOrderDefault,
|
||||
provider, NULL, false, kCGRenderingIntentDefault);
|
||||
|
||||
//CGImage -> CVPixelBufferRef conversion
|
||||
CVPixelBufferRef pixelBuffer = NULL;
|
||||
CFDataRef cfData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
|
||||
int status = CVPixelBufferCreateWithBytes(NULL,
|
||||
movieSize.width,
|
||||
movieSize.height,
|
||||
kCVPixelFormatType_32BGRA,
|
||||
(void*)CFDataGetBytePtr(cfData),
|
||||
CGImageGetBytesPerRow(cgImage),
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
&pixelBuffer);
|
||||
if(status == kCVReturnSuccess){
|
||||
success = [mMovieWriterAdaptor appendPixelBuffer:pixelBuffer
|
||||
withPresentationTime:CMTimeMake(frameCount, movieFPS)];
|
||||
}
|
||||
|
||||
//cleanup
|
||||
CFRelease(cfData);
|
||||
CVPixelBufferRelease(pixelBuffer);
|
||||
CGImageRelease(cgImage);
|
||||
CGDataProviderRelease(provider);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
[localpool drain];
|
||||
|
||||
if (success) {
|
||||
frameCount ++;
|
||||
//NSLog(@"Frame #%d", frameCount);
|
||||
return;
|
||||
}else{
|
||||
NSLog(@"Frame appendPixelBuffer failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
#pragma clang diagnostic pop
|
||||
Reference in New Issue
Block a user