减少 AVAssetWriter 的内存使用
Reduce memory usage of AVAssetWriter
正如标题所说,我在 AVAssetWriter 和内存方面遇到了一些麻烦。
关于我的一些笔记 environment/requirements:
- 我没有使用 ARC,但如果有一种方法可以简单地使用它并使其全部正常工作,我会全力以赴。不过,我的尝试没有任何区别。我将使用它的环境需要尽快最小化/释放内存。
- Objective-C 是一个要求
- 内存使用必须尽可能低,现在占用的300mb在我的设备上测试时不稳定(iPhone X)。
密码
这是下面截图时使用的代码https://gist.github.com/jontelang/8f01b895321d761cbb8cda9d7a5be3bd
记忆中的问题/项目
在整个处理过程中,大部分看起来占用大量内存的东西似乎都是在开始时分配的。
所以在我看来,问题不在于我的代码。我个人控制的代码 似乎 不是问题,即加载图像,创建缓冲区,释放它似乎不是内存有问题的地方。例如,如果我在 Instruments 中标记大部分时间 在 之后,内存是稳定的并且内存的 none 保持不变。
持续 5mb 的唯一原因是它在标记期结束后立即被释放。
现在怎么办?
实际上我开始写这个问题的重点是我的代码是否正确发布了东西,但现在看来这很好。那么我现在有什么选择?
- 我可以在当前代码中配置什么东西来减少内存需求吗?
- 我的 writer/input 设置有问题吗?
- 我是否需要使用完全不同的方式制作视频才能完成这项工作?
使用 CVPixelBufferPool 的注意事项
在 CVPixelBufferCreate Apple 的文档中指出:
If you need to create and release a number of pixel buffers, you should instead use a pixel buffer pool (see CVPixelBufferPool) for efficient reuse of pixel buffer memory.
我也试过这个,但我发现内存使用没有变化。更改池的属性似乎也没有任何效果,所以我实际上并没有 100% 正确地使用它的可能性很小,尽管与在线代码相比 似乎 至少像我一样。并且输出文件有效。
代码在这里 https://gist.github.com/jontelang/41a702d831afd9f9ceeb0f9f5365de03
这是一个稍微不同的版本,我以稍微不同的方式设置池 https://gist.github.com/jontelang/c0351337bd496a6c7e0c94293adf881f。
更新 1
所以我更深入地查看了跟踪,以找出 when/where 大部分分配的来源。这是一张带注释的图片:
外卖是:
- space 没有分配给 AVAssetWriter
- 保留到最后的500mb在处理开始后500ms内分配
- 好像是在AVAssetWriter内部完成的
我在此处上传了 .trace 文件:https://www.dropbox.com/sh/f3tf0gw8gamu924/AAACrAbleYzbyeoCbC9FQLR6a?dl=0
创建 Dispatch Queue 时,请确保使用 Autorlease Pool 创建队列。将 DISPATCH_QUEUE_SERIAL
替换为 DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL
.
将 for
循环的每次迭代也包装到自动释放池中
像这样:
[assetWriterInput requestMediaDataWhenReadyOnQueue:recordingQueue usingBlock:^{
for (int i = 1; i < 200; ++i) {
@autoreleasepool {
while (![assetWriterInput isReadyForMoreMediaData]) {
[NSThread sleepForTimeInterval:0.01];
}
NSString *path = [NSString stringWithFormat:@"/Users/jontelang/Desktop/SnapperVideoDump/frames/frame_%i.jpg", i];
UIImage *image = [UIImage imageWithContentsOfFile:path];
CGImageRef ref = [image CGImage];
CVPixelBufferRef buffer = [self pixelBufferFromCGImage:ref pool:writerAdaptor.pixelBufferPool];
CMTime presentTime = CMTimeAdd(CMTimeMake(i, 60), CMTimeMake(1, 60));
[writerAdaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
CVPixelBufferRelease(buffer);
}
}
[assetWriterInput markAsFinished];
[assetWriter finishWritingWithCompletionHandler:^{}];
}];
不,我看到它在应用程序中的峰值约为 240 MB。这是我第一次使用这种分配 - 很有趣。
我正在使用 AssetWriter 通过流式传输 cmSampleBuffer 来编写视频文件:CMSampleBuffer。它通过 Camera CaptureOutput Realtime 从 AVCaptureVideoDataOutputSampleBufferDelegate 获取。
虽然我还没有找到实际的问题,但我在这个问题中描述的内存问题通过在实际设备上而不是模拟器上简单地解决了。
@Eugene_Dudnyk 答案就在现场,for 或 while 循环内的自动释放池是关键,这是我如何让它在 Swift 上工作的,另外,请使用 AVAssetWriterInputPixelBufferAdaptor
用于像素缓冲池:
videoInput.requestMediaDataWhenReady(on: videoInputQueue) { [weak self] in
while videoInput.isReadyForMoreMediaData {
autoreleasepool {
guard let sample = assetReaderVideoOutput.copyNextSampleBuffer(),
let buffer = CMSampleBufferGetImageBuffer(sample) else {
print("Error while processing video frames")
videoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
closeWriter()
}
return
}
// Process image and render back to buffer (in place operation, where ciProcessedImage is your processed new image)
self?.getCIContext().render(ciProcessedImage, to: buffer)
let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample)
self?.adapter?.append(buffer, withPresentationTime: timeStamp)
}
}
}
我的内存使用率停止上升。
正如标题所说,我在 AVAssetWriter 和内存方面遇到了一些麻烦。
关于我的一些笔记 environment/requirements:
- 我没有使用 ARC,但如果有一种方法可以简单地使用它并使其全部正常工作,我会全力以赴。不过,我的尝试没有任何区别。我将使用它的环境需要尽快最小化/释放内存。
- Objective-C 是一个要求
- 内存使用必须尽可能低,现在占用的300mb在我的设备上测试时不稳定(iPhone X)。
密码
这是下面截图时使用的代码https://gist.github.com/jontelang/8f01b895321d761cbb8cda9d7a5be3bd
记忆中的问题/项目
在整个处理过程中,大部分看起来占用大量内存的东西似乎都是在开始时分配的。
所以在我看来,问题不在于我的代码。我个人控制的代码 似乎 不是问题,即加载图像,创建缓冲区,释放它似乎不是内存有问题的地方。例如,如果我在 Instruments 中标记大部分时间 在 之后,内存是稳定的并且内存的 none 保持不变。
持续 5mb 的唯一原因是它在标记期结束后立即被释放。
现在怎么办?
实际上我开始写这个问题的重点是我的代码是否正确发布了东西,但现在看来这很好。那么我现在有什么选择?
- 我可以在当前代码中配置什么东西来减少内存需求吗?
- 我的 writer/input 设置有问题吗?
- 我是否需要使用完全不同的方式制作视频才能完成这项工作?
使用 CVPixelBufferPool 的注意事项
在 CVPixelBufferCreate Apple 的文档中指出:
If you need to create and release a number of pixel buffers, you should instead use a pixel buffer pool (see CVPixelBufferPool) for efficient reuse of pixel buffer memory.
我也试过这个,但我发现内存使用没有变化。更改池的属性似乎也没有任何效果,所以我实际上并没有 100% 正确地使用它的可能性很小,尽管与在线代码相比 似乎 至少像我一样。并且输出文件有效。
代码在这里 https://gist.github.com/jontelang/41a702d831afd9f9ceeb0f9f5365de03
这是一个稍微不同的版本,我以稍微不同的方式设置池 https://gist.github.com/jontelang/c0351337bd496a6c7e0c94293adf881f。
更新 1
所以我更深入地查看了跟踪,以找出 when/where 大部分分配的来源。这是一张带注释的图片:
外卖是:
- space 没有分配给 AVAssetWriter
- 保留到最后的500mb在处理开始后500ms内分配
- 好像是在AVAssetWriter内部完成的
我在此处上传了 .trace 文件:https://www.dropbox.com/sh/f3tf0gw8gamu924/AAACrAbleYzbyeoCbC9FQLR6a?dl=0
创建 Dispatch Queue 时,请确保使用 Autorlease Pool 创建队列。将
DISPATCH_QUEUE_SERIAL
替换为DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL
.将
for
循环的每次迭代也包装到自动释放池中
像这样:
[assetWriterInput requestMediaDataWhenReadyOnQueue:recordingQueue usingBlock:^{
for (int i = 1; i < 200; ++i) {
@autoreleasepool {
while (![assetWriterInput isReadyForMoreMediaData]) {
[NSThread sleepForTimeInterval:0.01];
}
NSString *path = [NSString stringWithFormat:@"/Users/jontelang/Desktop/SnapperVideoDump/frames/frame_%i.jpg", i];
UIImage *image = [UIImage imageWithContentsOfFile:path];
CGImageRef ref = [image CGImage];
CVPixelBufferRef buffer = [self pixelBufferFromCGImage:ref pool:writerAdaptor.pixelBufferPool];
CMTime presentTime = CMTimeAdd(CMTimeMake(i, 60), CMTimeMake(1, 60));
[writerAdaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
CVPixelBufferRelease(buffer);
}
}
[assetWriterInput markAsFinished];
[assetWriter finishWritingWithCompletionHandler:^{}];
}];
不,我看到它在应用程序中的峰值约为 240 MB。这是我第一次使用这种分配 - 很有趣。
我正在使用 AssetWriter 通过流式传输 cmSampleBuffer 来编写视频文件:CMSampleBuffer。它通过 Camera CaptureOutput Realtime 从 AVCaptureVideoDataOutputSampleBufferDelegate 获取。
虽然我还没有找到实际的问题,但我在这个问题中描述的内存问题通过在实际设备上而不是模拟器上简单地解决了。
@Eugene_Dudnyk 答案就在现场,for 或 while 循环内的自动释放池是关键,这是我如何让它在 Swift 上工作的,另外,请使用 AVAssetWriterInputPixelBufferAdaptor
用于像素缓冲池:
videoInput.requestMediaDataWhenReady(on: videoInputQueue) { [weak self] in
while videoInput.isReadyForMoreMediaData {
autoreleasepool {
guard let sample = assetReaderVideoOutput.copyNextSampleBuffer(),
let buffer = CMSampleBufferGetImageBuffer(sample) else {
print("Error while processing video frames")
videoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
closeWriter()
}
return
}
// Process image and render back to buffer (in place operation, where ciProcessedImage is your processed new image)
self?.getCIContext().render(ciProcessedImage, to: buffer)
let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample)
self?.adapter?.append(buffer, withPresentationTime: timeStamp)
}
}
}
我的内存使用率停止上升。