一. 引言

视频变速(Speed Ramp)是视频编辑中最常见的特效之一:

  • 慢动作(Slow Motion):强调细节,让观众捕捉到肉眼难以察觉的瞬间;
  • 快动作(Fast Motion):压缩时长,强化节奏,常用于 vlog、综艺片段。

在 AVFoundation 中,视频变速的本质是 对音视频轨道的时间线进行重新映射。通过调整时间范围(CMTimeRange)与目标时长(toDuration),即可让视频和音频实现同步的快进或慢放效果。

本文将通过一个完整的 Demo 实战,展示如何在 iOS 中使用 AVFoundation 实现视频的变速处理,涵盖从模型设计到合成器构建的完整流程。

二. 核心思路回顾

要实现变速,必须同时处理 视频轨道 与 音频轨道,以保证二者的同步:

1. 视频轨道

  • 借助 AVMutableVideoCompositionInstruction 和 AVMutableVideoCompositionLayerInstruction,保证变速后的视频能够正常渲染。
  • 关键在于:

  1. videoTrack.scaleTimeRange():调整视频的时间范围到新的时长;
  2. instruction.timeRange:指定变速后的可见区间;
  3. videoComposition:控制整体渲染(帧率、尺寸等)。

2. 音频轨道

  • 音频不涉及渲染和图层,处理方式更简单。
  • 只需调用 audioTrack.scaleTimeRange(),将某一时间段的音频拉伸或压缩到新的时长,从而实现变速效果。

三. Demo 架构设计

在本次 Demo 中,我们延续前文的 时间线(TimeLine)驱动合成 的设计思路。通过定义模型与 Builder,将变速逻辑解耦,使其可以灵活扩展。

1. PHSpeedItem —— 变速模型

class PHSpeedItem: PHMediaItem {/// 变速的倍速var scaleSpeed: Float = 2.0
}

PHSpeedItem 继承自 PHMediaItem,用于描述一段变速操作。它包含了:

  • startTime:变速的起始时间点;
  • timeRange:变速的持续时长;
  • scaleSpeed:变速的倍速(例如 2.0 表示慢两倍,0.5 表示快两倍)。

这样,我们就能精确地定义:从视频的某个时间点开始,持续多少秒,需要以什么速度播放

2. PHTimeLine —— 时间线模型

class PHTimeLine: NSObject {var videoItmes = [PHVideoItem]()var audioItems = [PHAudioItem]()var musicItems = [PHMusicItem]()var maskItem = PHMaskItem(text: "PHVideoExample",image: UIImage(named: "mask1"),bounds: CGRect(x: 0, y: 0, width: 1280, height: 720))var seepItems = [PHSpeedItem]()
}

PHTimeLine 是整个编辑流程的核心。它聚合了:

  • 视频轨道(videoItems)
  • 音频轨道(audioItems)
  • 背景音乐(musicItems)
  • 水印(maskItem)
  • 变速效果(seepItems)

通过在 buildTimeLine 时一次性构建这些属性,后续的 CompositionBuilder 只需要解析 timeLine 即可。

3. PHSpeedCompositionBuilder —— 合成器

class PHSpeedCompositionBuilder: PHComositionBuilder {private let composition = AVMutableComposition()private let timeLine: PHTimeLineprivate var videoComposition: AVMutableVideoCompositionprivate var audioMix: AVAudioMix?init(timeLine: PHTimeLine) {self.timeLine = timeLineself.videoComposition = AVMutableVideoComposition()videoComposition.frameDuration = CMTime(value: 1, timescale: 30)videoComposition.renderSize = CGSize(width: 1280, height: 720)}func buildComposition() -> (any PHComposition)? {// 添加视频 & 音频轨道guard let videoTrack = self.addTrack(with: .video, mediaItems: self.timeLine.videoItmes),let audioTrack = self.addTrack(with: .audio, mediaItems: self.timeLine.audioItems) else {return nil}// 遍历变速片段for seepItem in timeLine.seepItems {self.applySpeed(to: videoTrack,audioTrack: audioTrack,startTime: seepItem.startTime,duration: seepItem.timeRange.duration,scaleSpeed: seepItem.scaleSpeed)}return PHSpeedComposition(composition: composition,videoComposition: videoComposition,audioMix: audioMix)}
}

在 PHSpeedCompositionBuilder 中,我们做了三件核心的事:

  • 初始化渲染配置:帧率(30fps)、渲染尺寸(1280x720)。
  • 加载轨道:将视频、音频素材插入到 AVMutableComposition。
  • 应用变速:遍历 seepItems,对每个变速区间调用 applySpeed,从而实现快动作/慢动作。

四. 变速核心实现

视频变速的核心逻辑集中在 applySpeed 方法中。它的作用是:

  • 调整 视频轨道 的播放时长,实现快动作或慢动作;
  • 调整 音频轨道 的播放时长,保持与视频同步;
  • 更新 视频合成指令,确保渲染时长正确。

来看代码:

/// 应用变速效果
/// - Parameters:
///   - videoTrack: 视频轨道
///   - audioTrack: 音频轨道
///   - startTime: 变速开始时间
///   - duration: 变速持续时间
///   - scaleSpeed: 变速比例,例如 2.0 表示慢动作,0.5 表示快动作
private func applySpeed(to videoTrack: AVMutableCompositionTrack,audioTrack: AVMutableCompositionTrack,startTime: CMTime,duration: CMTime,scaleSpeed: Float) {let instruction = AVMutableVideoCompositionInstruction()// 计算变速后的时长let scaledDuration = CMTimeMultiplyByFloat64(duration, multiplier: Float64(scaleSpeed))let totalDuration = CMTimeSubtract(videoTrack.timeRange.duration, duration) + scaledDuration// 设置渲染时长instruction.timeRange = CMTimeRange(start: .zero, duration: totalDuration)let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)instruction.layerInstructions = [layerInstruction]videoComposition.instructions.append(instruction)// 视频变速:修改时间区间videoTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: scaledDuration)// 音频变速:保持同步let audioDuration = CMTimeMultiplyByFloat64(duration, multiplier: Float64(scaleSpeed))audioTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: audioDuration)print("视频轨道总时长: \(CMTimeGetSeconds(videoTrack.timeRange.duration)) 秒")
}

1. 核心计算公式

let scaledDuration = duration * scaleSpeed
let totalDuration = (原始总时长 - duration) + scaledDuration
  • scaledDuration:表示 变速区间在新速度下的时长
  • totalDuration:表示 整条视频在变速后新的总时长

👉 这一步非常关键。如果直接把 instruction.timeRange 设置为 scaledDuration,就会出现画面丢失的问题。必须用 totalDuration 来覆盖渲染范围,确保整个视频能正常播放。

2. 视频轨道处理

videoTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: scaledDuration)

这一步会将 startTime ~ duration 的视频片段 拉伸/压缩 到新的时长,实现快/慢动作。

  • scaleSpeed = 2.0 → 时长 *2 → 慢动作;
  • scaleSpeed = 0.5 → 时长 *0.5 → 快动作。

3. 音频轨道处理

audioTrack.scaleTimeRange(CMTimeRange(start: startTime, duration: duration),toDuration: audioDuration)

音频的逻辑与视频相同,只是 不需要渲染指令。通过调整 timeRange,即可保证音视频同步。

4. 视频 vs 音频 的区别

  • 视频轨道:必须结合 AVMutableVideoCompositionInstruction 来更新渲染时长,否则会丢画面。
  • 音频轨道:只需调整 scaleTimeRange 即可,不需要合成指令。

五. 实战效果验证

在前面,我们已经完成了 PHSpeedCompositionBuilder 的核心实现。现在,只需要在 Demo 中构建一个 PHTimeLine,并添加 PHSpeedItem,就能快速验证效果。

1. 构建 TimeLine

// 构建带变速的时间线
let timeLine = PHTimeLine.buildTimeLine(with: items)// 假设我们在第 2 秒开始,持续 3 秒,并让视频变慢 2 倍
let speedItem = PHSpeedItem()
speedItem.startTime = CMTime(seconds: 2, preferredTimescale: 600)
speedItem.timeRange = CMTimeRange(start: speedItem.startTime,duration: CMTime(seconds: 3, preferredTimescale: 600))
speedItem.scaleSpeed = 2.0timeLine.seepItems = [speedItem]

2. 构建 Composition

let builder = PHSpeedCompositionBuilder(timeLine: timeLine)
guard let composition = builder.buildComposition() else {print("❌ 构建 Composition 失败")return
}

3. 播放或导出

如果原始视频是 15 秒

  • 在第 2 ~ 5 秒的区间变慢 2 倍 → 该区间时长变为 6 秒;
  • 总时长 = 15 - 3 + 6 = 18 秒;
  • 播放时可以清晰看到 2s → 5s 片段被拉长

六. 结语

在本文中,我们完整实现了 视频变速处理,并通过 Demo 验证了其效果。核心思想是:

  • 视频轨道:通过 scaleTimeRange 拉伸或压缩片段时长,并结合 AVMutableVideoCompositionInstruction 和 AVMutableVideoCompositionLayerInstruction 确保画面渲染正确。
  • 音频轨道:相比视频更为简单,仅需调整 timeRange 即可保证与视频保持同步。

在实战中,我们实现了 局部变速 的支持,例如在第 2 秒 ~ 5 秒区间执行 2 倍慢动作,总时长相应调整为 18 秒。通过这种方式,我们不仅能够处理 整体变速,也能灵活地在指定区间内应用变速效果。

不过,本篇案例依然有一个简化假设:从一个起点到一个终点单段变速。而在实际开发中,用户往往希望在多个不同的区间应用不同的变速效果(例如“先快 → 再慢 → 再恢复正常”)。这会带来更复杂的轨道管理、区间拼接和指令叠加问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96875.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96875.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96875.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MCP零基础学习(7)|实战指南:构建论文分析智能体

在之前的教程中,我们已经介绍了 MCP(Model Context Protocol)的基本概念及其核心组件。在本篇教程中,我们将通过一个实际案例,演示如何运用 MCP 构建一个能够分析学术论文的智能体。这个智能体将具备读取 PDF 文件、提…

Unity URP半透明物体自身交叠解决方案

前言 在 Unity 的通用渲染管线(URP)中,处理半透明物体的自身交叠是一个常见挑战。当半透明物体(如玻璃、水或透明材质)的某些部分相互重叠时,可能会出现渲染顺序问题,导致视觉瑕疵。 对惹&…

哈希算法入门:深入浅出讲明白HASH哈希算法

一、先搞懂:哈希算法到底是 “啥玩意儿”?咱们先别碰复杂概念,从你每天都会遇到的事说起 —— 你会发现,“哈希思维” 其实早就藏在生活里了。(一)生活中的 “哈希例子”:给东西 “贴标签、找位…

Vuex 和 Pinia 各自的优点

核心总结(一句话概括) Vuex:Vue 官方曾经的状态管理标准解决方案,成熟稳定,概念清晰,但语法稍显冗长。Pinia:Vue 官方推荐的新一代状态管理库,API 设计极其简洁,完美支持…

几种方式实现文件自动上传到服务器共享文件夹

文章目录一、方案核心逻辑二、详细实现步骤(以Windows系统为例)1. 确认服务器共享文件夹的“访问权限”(前提)2. 选择“传输触发方式”(按需求选实时/周期)(1)周期传输(如…

Milvus介绍及多模态检索实践

1、核心组件 1.1 Collection (集合) 可以用一个图书馆的比喻来理解 Collection: Collection (集合): 相当于一个图书馆,是所有数据的顶层容器。一个 Collection 可以包含多个 Partition,每个 Partition 可以包含多个 Entity。 Partition (分区…

第二十三天-LCD液晶显示实验

一、LCD结构体定义LCD为LCD_TypeDef类型的指针,指向0x6C000000的地址空间(bank1分区4的地址范围)。为什么需要并上0x000007FE呢?因为虽然驱动SRAM的时序和16位8080接口时序(驱动LCD时序)很像,但…

SQL性能调优

MySQL出现性能差的原因有哪些? 可能是 SOL查询使用了全表扫描,也可能是查询语句过于复杂,如多表 IOIN 或嵌套子查询。 也有可能是单表数据量过大。 通常情况下,添加索引就能解决大部分性能问题。对于一些热点数据,还可以通过增加…

dapo:开源大规模llm强化学习系统的突破与实现

本文由「大千AI助手」原创发布,专注用真话讲AI,回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我,一起撕掉过度包装,学习真实的AI技术! ✨ 1. dapo概述:开源llm强化学习系统的重要突破 dapo&…

【车载开发系列】ParaSoft集成测试环境配置(五)

【车载开发系列】ParaSoft集成测试环境配置(五) 【车载开发系列】ParaSoft集成测试环境配置(五) 【车载开发系列】ParaSoft集成测试环境配置(五) 一. 剥离硬件环境的设置 二. 灵活使用编译开关 三. 导入修改后的bdf文件 四. 自动生成底层桩函数 五. 开始跑集成测试用例 六…

大模型(一)什么是 MCP?如何使用 Charry Studio 集成 MCP?

目录一、什么是 MCP?1.1 🤔 开始之前的思考1.2 MCP 的定义1.3 MCP 结构二、MCP 的使用2.1 uv 的安装2.2 MCP 广场2.3 MCP 的配置2.4 MCP 的依赖安装2.5 Charry Studio2.6 测试结果背景: MCP 这个概念大概是 2025 年上半年火起来的&#xff0c…

源码导航页

一、Python捕捉动作发送到Unity驱动模型跟着动(获取源码) 二、AI输入法源码(获取源码) 三、Java企业级后台管理系统-登录授权角色菜单(获取源码) 四、Jetson实现纯视觉导航(获取源码&#xff09…

HTTP/2 性能提升的核心原因

一、协议架构优化‌‌二进制分帧(Binary Framing)‌HTTP/2 将传统文本格式的报文(如请求头、数据体)拆分为独立的二进制帧(Frame),每个帧包含流标识符(Stream ID)&#x…

vulnhub-billu_b0x靶机渗透

一、靶场详情 Billu_b0x 是 Vulnhub 上的经典中等难度靶机,主要考察从信息收集到提权的完整渗透流程:先通过端口和目录扫描发现网站入口,利用 SQL 注入或文件包含进入后台并上传 WebShell,再通过反弹 Shell 获取低权限用户&#…

C# 相机内存复用(减少图像采集耗时)以及行数复用

背景我们在做图像处理时,都会对一些相机的SDK进行开发完成图像采集的操作,为后续图像处理做准备。本文主要的目的是降低图像采集的耗时,应用在一些高速检测的场景下。利用循环队列内存复用的方式,去掉或者减少新建内存的时间。线扫…

MTK Linux DRM分析(十三)- Mediatek KMS实现mtk_drm_drv.c(Part.1)

一、简介 MediaTek (MTK) 的DRM驱动(基于mtk_drm_drv.c)是为MediaTek SoC(如MT6985、MT6895等)设计的显示子系统(Display Subsystem)驱动程序。它实现了Linux DRM/KMS框架,支持多CRTC、多平面(plane)、连接器(connector)和编码器(encoder)的显示管道。驱动处理硬…

Wireshark笔记-DHCP流程与数据包解析

背景DHCP从大学上网络课时就开始知道了,当时只知道,能让计算机上网,要不就静态配IP,要不就DHCP获取,就能上网。2021年时,毕业好几年了,想学习下网络知识,就准备考一个软考网工。按要…

Coze用户账号设置修改用户头像-前端源码

概述 Coze Studio的用户头像修改功能是用户账号设置中的重要组成部分,允许用户上传和更新个人头像。本文将深入分析该功能的前端实现,包括组件架构、文件上传处理、API设计和用户体验优化等方面。 技术架构 整体架构设计 Coze Studio采用现代化的前端架构…

新手Github提交PR(Pull requests)详细教程

一、什么是Pull requests? Pull Requests(PR)是代码协作平台(如 GitHub、GitLab 等)中的一种功能,用于提议将某分支的代码变更合并到另一个分支(通常是主分支)。它允许开发者在合并…

本地通过跳板机连接无公网IP的内网服务器

本地环境:SSH client 堡垒机:有公网IP,有连接内网服务器的秘钥 SSH配置: Host jmsHostName [堡垒机的公网IP]Port 22User rootIdentityFile ~/.ssh/id_rsaHost appHostName 10.0.0.14Port 22User rootIdentityFile ~/.ssh/svc-p…