You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iPhone端任意视频转码为720P 30fps H264标准参数的实现问题

问题解决完整方案

你现有代码的两个核心问题可通过系统内置API直接修复,无需引入第三方库,完整实现如下:

1. 旋转问题修复逻辑

你之前直接将原视频的preferredTransform赋值给写入器输入,导致导出视频仍携带旋转元数据,播放时触发旋转才会出现内容出框、黑屏。正确处理方式是:

  • 先计算原视频应用旋转后的实际自然展示尺寸
  • 写入器输入直接使用.identity变换,导出视频无旋转元数据
  • 视频缩放裁剪逻辑直接作用于已旋转的正方向帧,避免适配错误

2. 帧率统一逻辑

系统内置AVMutableVideoComposition原生支持自动帧率转换,只需指定目标帧率的frameDuration,系统会自动处理高帧率抽帧、低帧率补帧,全程不改变视频时长、播放速度无异常。

完整修正代码

import AVFoundation

func transcodeVideo(videoUrl: URL, completion: @escaping (Result<URL, Error>) -> Void) {
    let asset = AVURLAsset(url: videoUrl)
    
    guard let videoTrack = asset.tracks(withMediaType: .video).first else {
        completion(.failure(VideoHelperError.unknown))
        return
    }
    
    // 计算原视频应用旋转后的自然尺寸
    let naturalSize = videoTrack.naturalSize.applying(videoTrack.preferredTransform)
    let correctedSize = CGSize(width: abs(naturalSize.width), height: abs(naturalSize.height))
    
    // 配置视频合成规则,自动处理帧率
    let videoComposition = AVMutableVideoComposition()
    videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) // 固定30fps
    videoComposition.renderSize = CGSize(width: 720, height: 1280) // 目标720P竖屏尺寸
    
    // 配置合成层,应用原视频变换保证内容方向正确
    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRangeMake(start: .zero, duration: asset.duration)
    
    let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    // 计算变换矩阵:先应用原视频旋转,再适配目标尺寸aspect fill
    let scaleFactor = max(videoComposition.renderSize.width / correctedSize.width,
                          videoComposition.renderSize.height / correctedSize.height)
    let scaleTransform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
    let finalTransform = videoTrack.preferredTransform.concatenating(scaleTransform)
    layerInstruction.setTransform(finalTransform, at: .zero)
    
    instruction.layerInstructions = [layerInstruction]
    videoComposition.instructions = [instruction]
    
    // 导出配置
    guard let outputUrl = try? FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).mp4") else {
        completion(.failure(VideoHelperError.fileSystemFailure))
        return
    }
    
    guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset1280x720) else {
        completion(.failure(VideoHelperError.unknown))
        return
    }
    
    exportSession.outputURL = outputUrl
    exportSession.outputFileType = .mp4
    exportSession.videoComposition = videoComposition
    exportSession.shouldOptimizeForNetworkUse = true
    exportSession.videoSettings = [
        AVVideoCodecKey: AVVideoCodecType.h264,
        AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill
    ] as [String : Any]
    
    exportSession.exportAsynchronously {
        DispatchQueue.main.async {
            switch exportSession.status {
            case .completed:
                completion(.success(outputUrl))
            case .failed, .cancelled:
                completion(.failure(exportSession.error ?? VideoHelperError.unknown))
            default:
                break
            }
        }
    }
}

// 自定义错误类型,和你原有代码对齐
enum VideoHelperError: Error {
    case unknown
    case fileSystemFailure
}

对应问题解答

  1. 帧率转换的系统内置方案:直接使用AVMutableVideoComposition的frameDuration属性指定目标帧率,系统自动完成抽帧/补帧逻辑,无需手动处理样本时间戳,适配所有原帧率场景,保证视频时长、播放速度完全正常。
  2. 旋转信息处理最优方案:通过AVMutableVideoCompositionLayerInstruction的setTransform方法,在合成阶段就将原视频的旋转元数据直接应用到帧内容上,导出的视频使用默认无变换属性,完全去除旋转元数据,aspect fill适配直接作用于正方向的内容,不会出现内容出框、黑屏问题。

内容的提问来源于stack exchange,提问作者Paulo C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 08:24:03