我正在使用
AVAssetExportSession
在 iOS 应用程序中导出视频。为了以正确的方向渲染视频,我使用 AVAssetTrack
的 preferredTransform
。对于某些源视频,此属性似乎具有错误的值,并且视频在结果中显示为偏移或全黑。我该如何解决这个问题?
preferredTransform
是CGAffineTransform
。属性 a
、b
、c
、d
是反射矩阵和旋转矩阵的串联,属性 tx
和 ty
描述平移。在我观察到的所有不正确的 preferredTransform
情况下,反射/旋转部分似乎是正确的,只有平移部分包含错误的值。一个可靠的修复似乎是检查 a
、b
、c
、d
(总共八个案例,每个案例对应于 UIImageOrientation
中的一个案例)并相应地更新 tx
和 ty
:
extension AVAssetTrack {
var fixedPreferredTransform: CGAffineTransform {
var t = preferredTransform
switch(t.a, t.b, t.c, t.d) {
case (1, 0, 0, 1):
t.tx = 0
t.ty = 0
case (1, 0, 0, -1):
t.tx = 0
t.ty = naturalSize.height
case (-1, 0, 0, 1):
t.tx = naturalSize.width
t.ty = 0
case (-1, 0, 0, -1):
t.tx = naturalSize.width
t.ty = naturalSize.height
case (0, -1, 1, 0):
t.tx = 0
t.ty = naturalSize.width
case (0, 1, -1, 0):
t.tx = naturalSize.height
t.ty = 0
case (0, 1, 1, 0):
t.tx = 0
t.ty = 0
case (0, -1, -1, 0):
t.tx = naturalSize.height
t.ty = naturalSize.width
default:
break
}
return t
}
}
我最终做了一些稍微更稳健的事情,我认为,我根据最终的结果取消了转换:
auto naturalFrame = CGRectMake(0, 0, naturalSize.width, naturalSize.height);
auto preferredFrame = CGRectApplyAffineTransform(naturalFrame, preferredTransform);
preferredTransform.tx -= preferredFrame.origin.x;
preferredTransform.ty -= preferredFrame.origin.y;
请注意,您不能只对
(0, 0)
应用变换,因为 CGRect.origin
会考虑翻转等因素。
在 Theo 的出色回答的基础上,我将分享一些其他有用的“固定”变量,这些变量是我以与他的示例相同的风格创建的,我相信您将需要这些变量才能正确处理数据。
import AVFoundation
import UIKit
extension AVAssetTrack {
// This is the current orientation of the buffer which sits in positive X and Y space. eg: For a leftMirrored representation of a portrait 1080w by 1920h image, the buffer has had its X-axis reversed and is rotated to the left, but is still sitting in the top right quadrant in the space of X(0->1920) and Y(0->1080). This variable is used to calculate the required fixedPreferredTransform and fixedNaturalSize required to handle the buffer appropriately.
var fixedOrientation: UIImage.Orientation {
/*
// Here is a list of debug transforms showing what each of these terms means. Useful to check the logic below.
let id = CGAffineTransform.identity
let mirror = CGAffineTransform(scaleX: -1.0, y: 1.0)
let up = id
let left = id.concatenating(.init(rotationAngle: -1.0 * .pi / 2.0))
let right = id.concatenating(.init(rotationAngle: 1.0 * .pi / 2.0))
let down = id.concatenating(.init(rotationAngle: .pi))
let upMirror = up.concatenating(mirror)
let leftMirror = left.concatenating(mirror)
let rightMirror = right.concatenating(mirror)
let downMirror = down.concatenating(mirror)
*/
switch (preferredTransform.a, preferredTransform.b, preferredTransform.c, preferredTransform.d) {
case (0.0, 1.0, -1.0, 0.0): return .right
case (0.0, -1.0, 1.0, 0.0): return .left
case (1.0, 0.0, 0.0, 1.0): return .up
case (-1.0, 0.0, 0.0, -1.0): return .down
case (0.0, 1.0, 1.0, 0.0): return .rightMirrored
case (0.0, -1.0, -1.0, 0.0): return .leftMirrored
case (-1.0, 0.0, 0.0, 1.0): return .upMirrored
case (1.0, 0.0, 0.0, -1.0): return .downMirrored
default: return .up
}
}
// When handling the buffer, after it has been unmirrored and set upright using the fixedPreferredTransform variable, it is important to note that the size of the upright buffer might be the transpose (swap X and Y) than the original unmodified buffer, so that subsequent code knows the proper size to deal with.
var fixedNaturalSize: CGSize {
let naturalSize = naturalSize
switch fixedOrientation {
case .up: return naturalSize
case .down: return naturalSize
case .left: return .init(width: naturalSize.height, height: naturalSize.width)
case .right: return .init(width: naturalSize.height, height: naturalSize.width)
case .upMirrored: return naturalSize
case .downMirrored: return naturalSize
case .leftMirrored: return .init(width: naturalSize.height, height: naturalSize.width)
case .rightMirrored: return .init(width: naturalSize.height, height: naturalSize.width)
default: return naturalSize
}
}
// This is the required transform to change the stored buffer (located as defined by the fixedOrientation variable) such that it is upright and un-mirrored, with origin at X,Y = 0,0 and extending into the positive X and Y space (ie: in the upper quadrant).
// Note that the built-in preferredTransform variable does not include shifting of the buffer into the top right quadrant, which is required for subsequent processing.
var fixedPreferredTransform: CGAffineTransform {
var t = preferredTransform
switch fixedOrientation {
case .up: // .up
t.tx = 0
t.ty = 0
case .downMirrored: // .downMirrored
t.tx = 0
t.ty = naturalSize.height
case .upMirrored: // .upMirrored
t.tx = naturalSize.width
t.ty = 0
case .down: // .down
t.tx = naturalSize.width
t.ty = naturalSize.height
case .left: // .left
t.tx = 0
t.ty = naturalSize.width
case .right: // .right
t.tx = naturalSize.height
t.ty = 0
case .rightMirrored: // .rightMirrored
t.tx = 0
t.ty = 0
case .leftMirrored: // .leftMirrored
t.tx = naturalSize.height
t.ty = naturalSize.width
default:
break
}
return t
}
}