IOS sometimes records a blank video file
I have a class Movie
that has an array UIImage
which I am writing to an H264 file. It works, but sometimes it records a video file that does not contain any content, but the file size is still set to a non-zero size.
Here is the code where I am writing. I am new to iOS development, so it was copied from the internet, so I cannot fully understand what it does. Hopefully someone can suggest a better way to do this.
frame
is an instance UIImage
in a loop
func writeAnimationToMovie() {
var error: NSError?
writer.startWriting()
writer.startSessionAtSourceTime(kCMTimeZero)
var buffer: CVPixelBufferRef
var frameCount = 0
for frame in self.photos {
buffer = createPixelBufferFromCGImage(frame.CGImage)
var appendOk = false
var j = 0
while (!appendOk && j < 30) {
if pixelBufferAdaptor.assetWriterInput.readyForMoreMediaData {
let frameTime = CMTimeMake(Int64(frameCount), Int32(fps))
appendOk = pixelBufferAdaptor.appendPixelBuffer(buffer, withPresentationTime: frameTime)
// appendOk will always be false
NSThread.sleepForTimeInterval(0.05)
} else {
NSThread.sleepForTimeInterval(0.1)
}
j++
}
if (!appendOk) {
println("Doh, frame \(frame) at offset \(frameCount) failed to append")
}
frameCount++
}
input.markAsFinished()
writer.finishWritingWithCompletionHandler({
if self.writer.status == AVAssetWriterStatus.Failed {
println("oh noes, an error: \(self.writer.error.description)")
} else {
let content = NSFileManager.defaultManager().contentsAtPath(self.fileURL.path!)
println("wrote video: \(self.fileURL.path) at size: \(content?.length)")
}
})
}
func createPixelBufferFromCGImage(image: CGImageRef) -> CVPixelBufferRef {
let options = [
"kCVPixelBufferCGImageCompatibilityKey": true,
"kCVPixelBufferCGBitmapContextCompatibilityKey": true
]
let frameSize = CGSizeMake(CGFloat(CGImageGetWidth(image)), CGFloat(CGImageGetHeight(image)))
var pixelBufferPointer = UnsafeMutablePointer<Unmanaged<CVPixelBuffer>?>.alloc(1)
var status:CVReturn = CVPixelBufferCreate(
kCFAllocatorDefault,
UInt(frameSize.width),
UInt(frameSize.height),
OSType(kCVPixelFormatType_32ARGB),
options,
pixelBufferPointer
)
var lockStatus:CVReturn = CVPixelBufferLockBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue(), 0)
var pxData:UnsafeMutablePointer<(Void)> = CVPixelBufferGetBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue())
let bitmapinfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue)
let rgbColorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB()
var context:CGContextRef = CGBitmapContextCreate(
pxData,
UInt(frameSize.width),
UInt(frameSize.height),
8,
4 * CGImageGetWidth(image),
rgbColorSpace,
bitmapinfo
)
CGContextDrawImage(context, CGRectMake(0, 0, frameSize.width, frameSize.height), image)
CVPixelBufferUnlockBaseAddress(pixelBufferPointer.memory?.takeUnretainedValue(), 0)
return pixelBufferPointer.memory!.takeUnretainedValue()
}
EDIT Files that don't seem to contain any content won't play like other movies that do. If I view a working file with exiftool
, this is what I get
$ exiftool 20242697651186-o.mp4
ExifTool Version Number : 9.76
File Name : 20242697651186-o.mp4
Directory : .
File Size : 74 kB
File Modification Date/Time : 2014:12:05 11:07:29-05:00
File Access Date/Time : 2014:12:08 10:12:29-05:00
File Inode Change Date/Time : 2014:12:05 11:07:29-05:00
File Permissions : rw-r--r--
File Type : MP4
MIME Type : video/mp4
Major Brand : MP4 v2 [ISO 14496-14]
Minor Version : 0.0.1
Compatible Brands : mp41, mp42, isom
Movie Data Size : 74741
Movie Data Offset : 44
Movie Header Version : 0
Create Date : 2014:12:05 16:07:32
Modify Date : 2014:12:05 16:07:32
Time Scale : 600
Duration : 0.50 s
Preferred Rate : 1
Preferred Volume : 100.00%
Preview Time : 0 s
Preview Duration : 0 s
Poster Time : 0 s
Selection Time : 0 s
Selection Duration : 0 s
Current Time : 0 s
Next Track ID : 2
Track Header Version : 0
Track Create Date : 2014:12:05 16:07:32
Track Modify Date : 2014:12:05 16:07:32
Track ID : 1
Track Duration : 0.50 s
Track Layer : 0
Track Volume : 0.00%
Matrix Structure : 1 0 0 0 1 0 0 0 1
Image Width : 640
Image Height : 480
Media Header Version : 0
Media Create Date : 2014:12:05 16:07:32
Media Modify Date : 2014:12:05 16:07:32
Media Time Scale : 600
Media Duration : 0.50 s
Media Language Code : und
Handler Type : Video Track
Handler Description : Core Media Video
Graphics Mode : srcCopy
Op Color : 0 0 0
Compressor ID : avc1
Source Image Width : 640
Source Image Height : 480
X Resolution : 72
Y Resolution : 72
Bit Depth : 24
Video Frame Rate : 8
Avg Bitrate : 1.2 Mbps
Image Size : 640x480
Rotation : 0
And here is the file that doesn't work. It doesn't have all the metadata.
$ exiftool 20242891987099-o.mp4
ExifTool Version Number : 9.76
File Name : 20242891987099-o.mp4
Directory : .
File Size : 75 kB
File Modification Date/Time : 2014:12:05 11:07:37-05:00
File Access Date/Time : 2014:12:08 10:12:36-05:00
File Inode Change Date/Time : 2014:12:05 11:07:37-05:00
File Permissions : rw-r--r--
File Type : MP4
MIME Type : video/mp4
Major Brand : MP4 v2 [ISO 14496-14]
Minor Version : 0.0.1
Compatible Brands : mp41, mp42, isom
Movie Data Size : 76856
Movie Data Offset : 44
source to share
You have a few questions. I'm not sure what exactly you mean by "does not contain any content", but hopefully one of these will help (and if not, you should implement them anyway):
-
You are blocking the thread with your calls before
sleepForTimeInterval()
, which may cause the problem. This answer suggests moving the run loop instead of sleeping, which is slightly better, but there is an even better suggestion in the documentationreadyForMoreMediaData
:This property can be monitored using key observation (see the Key-Value Observing Programming Guide ). Observers should not assume that they will be notified of changes to a particular stream.
Instead of starting a loop and asking if it's available, just ask the object to tell you when it's ready for more KVO use.
-
In your method,
createPixelBufferFromCGImage
you are not checking for a failure at any point. For example, you should take advantage of the opportunitypixelBufferPointer.memory?
nil
.
Basically, I'm guessing one of these things is happening:
-
j
is shown30
and nothing has been written because the thread is blocked. In this case, you are writing an empty file with the correct file size. -
createPixelBufferFromCGImage
returns unexpected data that you write to disk
source to share