普通视图

发现新文章,点击刷新页面。
昨天 — 2025年5月19日首页

iOS 截取和分割音视频

作者 90后晨仔
2025年5月19日 19:00

在 iOS 开发中,截取或分割音视频是常见需求,适用于短视频剪辑、语音消息裁剪、媒体内容编辑等场景。使用 AVFoundation 框架可以高效实现这一功能。下面将详细介绍如何在 iOS 中截取或分割音视频,并提供完整的代码示例和使用方法。


✅ 核心思路

截取或分割音视频的核心步骤如下:

  1. 加载原始音视频文件AVURLAsset
  2. 设置时间范围CMTimeRange)指定要截取的起始时间与持续时间
  3. 创建导出会话AVAssetExportSession
  4. 导出目标文件(支持 .mp4.m4a 等格式)
  5. 处理异步导出完成回调

🎬 视频截取示例(Objective-C)

- (void)trimVideoFromURL:(NSURL *)inputURL startTime:(NSTimeInterval)startTime duration:(NSTimeInterval)duration completion:(void (^)(NSURL *outputURL, NSError *error))completion {
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
    
    // 1. 创建导出会话
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
    
    // 2. 设置输出路径和文件格式
    NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"trimmedVideo.mp4"];
    exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
    exportSession.outputFileType = AVFileTypeMPEG4;
    
    // 3. 设置时间范围(start ~ start + duration)
    CMTime startCMTime = CMTimeMakeWithSeconds(startTime, 600);
    CMTime durationCMTime = CMTimeMakeWithSeconds(duration, 600);
    CMTimeRange timeRange = CMTimeRangeMake(startCMTime, durationCMTime);
    exportSession.timeRange = timeRange;
    
    // 4. 异步导出
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"视频截取成功: %@", outputPath);
            if (completion) completion([NSURL fileURLWithPath:outputPath], nil);
        } else {
            NSError *error = exportSession.error;
            NSLog(@"视频截取失败: %@", error.localizedDescription);
            if (completion) completion(nil, error);
        }
    }];
}

✅ 使用方法

NSURL *videoURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myVideo" ofType:@"mp4"]];
[self trimVideoFromURL:videoURL startTime:5.0 duration:10.0 completion:^(NSURL *outputURL, NSError *error) {
    if (outputURL) {
        NSLog(@"截取后的视频路径: %@", outputURL.path);
    }
}];

🎵 音频截取示例(Objective-C)

- (void)trimAudioFromURL:(NSURL *)inputURL startTime:(NSTimeInterval)startTime duration:(NSTimeInterval)duration completion:(void (^)(NSURL *outputURL, NSError *error))completion {
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
    
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetAppleM4A];
    
    NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"trimmedAudio.m4a"];
    exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
    exportSession.outputFileType = AVFileTypeAppleM4A;
    
    CMTime startCMTime = CMTimeMakeWithSeconds(startTime, 600);
    CMTime durationCMTime = CMTimeMakeWithSeconds(duration, 600);
    CMTimeRange timeRange = CMTimeRangeMake(startCMTime, durationCMTime);
    exportSession.timeRange = timeRange;
    
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"音频截取成功: %@", outputPath);
            if (completion) completion([NSURL fileURLWithPath:outputPath], nil);
        } else {
            NSError *error = exportSession.error;
            NSLog(@"音频截取失败: %@", error.localizedDescription);
            if (completion) completion(nil, error);
        }
    }];
}

✅ 使用方法

NSURL *audioURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myAudio" ofType:@"mp3"]];
[self trimAudioFromURL:audioURL startTime:3.0 duration:5.0 completion:^(NSURL *outputURL, NSError *error) {
    if (outputURL) {
        NSLog(@"截取后的音频路径: %@", outputURL.path);
    }
}];

📌 注意事项

项目 说明
时间单位 使用 CMTimeMakeWithSeconds 将秒数转换为 CMTime
输出路径 使用 NSTemporaryDirectory() 可避免存储问题
输出格式 视频推荐 .mp4,音频推荐 .m4a.caf
导出性能 使用 AVAssetExportPresetLowQuality 可提升处理速度
错误处理 检查 exportSession.statusexportSession.error

🚀 扩展建议

  • 多片段拼接:可结合 AVMutableComposition 实现多段裁剪后的内容拼接。
  • 后台导出:大文件建议在后台线程执行,避免阻塞主线程。
  • 第三方库:如需更复杂剪辑功能,可使用 FFmpeg-iOSGPUImage

✅ 总结

通过 AVAssetExportSessiontimeRange 属性,你可以轻松地从音视频文件中截取任意时间段的内容。这个方法既适用于音频也适用于视频,具有良好的兼容性和性能表现,是 iOS 音视频处理中的基础技能之一。

iOS 音视频格式

作者 90后晨仔
2025年5月19日 18:58

在 iOS 开发中,音频和视频的格式选择直接影响性能、兼容性和用户体验。以下是常见的音频和视频格式,以及实际开发中常用的场景:


一、音频格式

1. 常见音频格式

格式 特点 使用场景
AAC(Advanced Audio Codec) 高压缩率、音质好,iOS 原生支持(如 AVAudioRecorder 默认输出格式)。 音频录制、流媒体(如 Apple Music)、视频配音。
MP3(MPEG-1 Audio Layer III) 兼容性极广,压缩率中等,但音质略逊于 AAC。 老旧项目兼容性需求(如播放本地 MP3 文件)。
WAV(Waveform Audio File Format) 无损格式,文件体积大,保留原始音质。 音频处理工具(如波形分析)、录音后处理。
PCM(Pulse Code Modulation) 未压缩的原始音频数据,常用于实时处理。 音频采集(如麦克风输入)、音频算法开发(如 FFT 分析)。
Opus 低延迟、高压缩率,适合实时通信(如 VoIP)。 实时语音通话(如 WebRTC 集成)。

2. 实际开发中的使用

  • 录制音频
    使用 AVAudioRecorder 录制音频时,默认使用 AAC 格式(kAudioFormatMPEG4AAC),并通过 AVAudioSettings 设置采样率(如 44.1kHz)、位深度(16-bit)等参数。

    let settings: [String: Any] = [
        AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
        AVSampleRateKey: 44100,
        AVNumberOfChannelsKey: 1,
        AVEncoderBitRateKey: 128000,
        AVLinearPCMIsBigEndianKey: false
    ]
    do {
        audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
        audioRecorder.record()
    } catch {
        print("录音失败: $error)")
    }
    
  • 播放音频
    使用 AVAudioPlayer 播放本地或网络音频文件(支持 AAC、MP3、WAV 等格式)。

    do {
        audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
        audioPlayer.play()
    } catch {
        print("播放失败: $error)")
    }
    
  • 实时音频处理
    使用 AudioUnitAccelerate 框架处理 PCM 数据(如降噪、混响)。

    // 通过 AudioUnit 回调处理音频缓冲区
    func audioProcessingCallback(
        _ inRefCon: UnsafeMutableRawPointer,
        _ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
        _ inTimestamp: UnsafePointer<AudioTimeStamp>,
        _ inBusNumber: UInt32,
        _ inNumberFrames: UInt32,
        _ ioData: UnsafeMutablePointer<AudioBufferList>
    ) -> OSStatus {
        // 处理 PCM 数据(如 FFT 变换)
        return noErr
    }
    

二、视频格式

1. 常见视频格式

格式 特点 使用场景
H.264(MPEG-4 AVC) 广泛兼容,压缩率高,iOS 原生支持(AVAssetExportSession 默认输出格式)。 视频录制、播放、流媒体(如 HLS)。
H.265(HEVC) 比 H.264 压缩率更高,但兼容性稍差(需 iOS 10+)。 4K/8K 视频存储(如相机 App)。
ProRes 无损压缩,高质量但体积大,适合专业编辑。 视频剪辑工具(如 Final Cut Pro 导出)。
MOV(QuickTime Movie) 容器格式,可封装 H.264/AAC 等数据,iOS 原生支持。 视频预览、本地存储。
MP4(MPEG-4 Part 14) 容器格式,兼容性极广,适合网络传输。 视频上传、跨平台播放。

2. 实际开发中的使用

  • 视频录制
    使用 AVCaptureSession 捕获视频流,并通过 AVAssetWriter 将 H.264 编码的视频写入 MP4 文件。

    let videoOutput = AVCaptureMovieFileOutput()
    captureSession.addOutput(videoOutput)
    let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("output.mp4")
    videoOutput.startRecording(to: fileURL, recordingDelegate: self)
    
  • 视频播放
    使用 AVPlayer 播放本地或网络视频(支持 H.264、H.265、MP4、MOV 等格式)。

    let player = AVPlayer(url: videoURL)
    let playerViewController = AVPlayerViewController()
    playerViewController.player = player
    present(playerViewController, animated: true) {
        player.play()
    }
    
  • 视频编辑
    使用 AVMutableComposition 合并多个视频片段,并通过 AVAssetExportSession 导出为 H.264 编码的 MP4 文件。

    let composition = AVMutableComposition()
    let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    try? videoTrack.insertTimeRange(CMTimeRange(start: .zero, duration: asset.duration), of: asset.tracks(withMediaType: .video)[0], at: .zero)
    let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
    exporter.outputURL = outputURL
    exporter.outputFileType = .mp4
    exporter.exportAsynchronously {
        if exporter.status == .completed {
            print("视频导出成功")
        }
    }
    
  • 硬件编码
    使用 VideoToolbox 进行 H.264/H.265 的硬件编码(适用于高性能需求场景)。

    var compressionSession: VTCompressionSession?
    VTCompressionSessionCreate(
        allocator: kCFAllocatorDefault,
        width: width,
        height: height,
        codecType: kCMVideoCodecType_H264,
        encoderSpecification: nil,
        imageBufferAttributes: nil,
        compressedDataAllocator: nil,
        outputCallback: videoEncodeCallback,
        refcon: nil,
        compressionSessionOut: &compressionSession
    )
    

三、开发中的注意事项

  1. 兼容性

    • 使用 H.264 和 AAC 格式以确保最大兼容性(尤其是支持 iOS 9 及以下设备)。
    • 对于 HEVC(H.265),需检查设备系统版本(iOS 10+)和解码能力。
  2. 性能优化

    • 使用硬件编码(VideoToolbox/AudioToolbox)提升效率,减少 CPU 开销。
    • 对视频进行动态分辨率适配(如 1080p vs 720p)以平衡画质和流量。
  3. 容器格式选择

    • 本地存储优先使用 .mov(QuickTime 容器),网络传输优先使用 .mp4(兼容性更好)。
  4. 音视频同步

    • 在编辑或播放时,确保音频和视频的 PTS(显示时间戳)对齐,避免卡顿或音画不同步。

四、典型场景示例

场景 使用的格式 框架
实时视频通话 H.264 + AAC WebRTC/AVFoundation
视频剪辑 App H.264 + AAC(MP4 容器) AVFoundation
高清视频录制 H.265 + AAC(MOV 容器) AVCaptureSession + AVAssetWriter
语音备忘录 AAC(.m4a 容器) AVAudioRecorder
实时语音通信 Opus WebRTC

总结

在 iOS 开发中,H.264/AAC 是最常用的核心组合,兼顾兼容性和性能;MP4/MOV 是最常见的容器格式;Opus 在实时通信中表现优异。实际开发中,通过 AVFoundationVideoToolbox 等框架,开发者可以灵活处理这些格式,并结合硬件加速优化性能。

Flutter核心机制图解说明

作者 90后晨仔
2025年5月19日 18:19

一、三棵树协作流程详解

1. 架构关系示意图
[用户代码] → Widget树(声明式配置)
       ↓ 创建
Element树(生命周期管理)
       ↓ 绑定
RenderObject树(布局/绘制)
2. 协作流程步骤
  1. 初始化阶段

    • runApp() 触发根Widget创建
    • 生成对应的根Element(RenderObjectElement
    • Element创建关联的RenderObject
  2. 构建阶段

    • Widget树通过build()方法递归构建
    • Element树通过inflateWidget方法逐层创建子元素
    • RenderObject树执行createRenderObject初始化渲染对象
  3. 更新阶段

    • 当Widget发生变更时: a. Element树对比新旧Widget类型 b. 类型相同 → 更新现有Element配置(update()) c. 类型不同 → 销毁旧Element,创建新Element
  4. 布局阶段

    • RenderObject执行layout()方法
    • 父节点向子节点传递约束条件(Constraints)
    • 子节点返回布局尺寸(Size)
  5. 绘制阶段

    • 生成Layer树提交给Skia引擎
    • 通过OpenGL/Vulkan进行GPU渲染

二、Platform Channel架构解析

1. 通信层级结构
[Flutter层] - Dart代码
   │
   ├── MethodChannel (方法调用)
   ├── EventChannel (事件流)
   └── BasicMessageChannel (基础消息)
           │
           |
[Native层] - 平台原生代码
   │
   ├── Android (Java/Kotlin)
   └── iOS (Objective-C/Swift)
2. 数据流向示意图
Flutter → 序列化为二进制 → 平台通道 → 反序列化为原生类型 → Native处理
       ← 序列化返回数据 ←          ← 原生返回结果 ←
3. 核心组件说明表
组件 功能特点 典型使用场景
MethodChannel 支持异步方法调用与返回值 调用相机/获取地理位置
EventChannel 建立持续事件流(类似观察者模式) 传感器数据监听/实时定位更新
BasicMessageChannel 基础消息传递(支持自定义编解码器) 简单数据交换/二进制传输

三、Key机制工作原理图示

1. LocalKey复用逻辑
Widget树重建前:
Item1(Key:A) - Item2(Key:B) - Item3(Key:C)

Widget树重建后:
Item2(Key:B) - Item3(Key:C) - Item1(Key:A)

Element树保持:
ElementB ↔ ElementC ↔ ElementA(仅位置变化)
2. GlobalKey定位原理
           ┌───────────┐
           │ GlobalKey │
           └─────┬─────┘
                 │
           ┌─────▼─────┐
           │ Element树  │
           └─────┬─────┘
                 │
           ┌─────▼─────┐
           │ 获取RenderObject │
           └───────────┘

四、状态管理数据流模型

1. Provider架构模型
[ChangeNotifier] ← 数据更新
       │
       ├─── notifyListeners()
       │
[Consumer] → 局部刷新
       │
[Selector] → 精准刷新
2. GetX响应式流程
[Rx变量] → 数据变更
       │
       ├─── 自动触发更新
       │
[Obx组件] → 重建依赖部件
       │
[GetBuilder] → 手动控制刷新

五、混合开发通信时序图

1. MethodChannel调用流程
Flutter端               Native端
  │                        │
  │  invokeMethod('getInfo')│
  │───────────────────────>│
  │                        ├── 执行原生代码
  │                        │
  │     result(data)       │
  │<───────────────────────│
  │                        │
2. EventChannel事件流
Flutter端               Native端
  │                        │
  │   receiveBroadcast()   │
  │───────────────────────>│
  │                        ├── 注册监听器
  │                        │
  │     event(data)        │
  │<───────────────────────│(持续推送)
  │                        │

六、性能优化关键路径

1. 渲染优化路线
减少Widget重建 → 优化Element复用 → 降低RenderObject计算 → 精简Layer树
      ↑               ↑                  ↑
   const构造      Key精准控制        布局边界标记(RepaintBoundary)
2. 内存管理策略
图片缓存控制 → 及时销毁监听 → 避免闭包泄漏 → 使用Isolate计算
   ↑               ↑              ↑             ↑
LRU策略       dispose()清理    DevTools检测   compute()函数

通过以上文字图解,开发者可以建立清晰的架构认知:

  1. 三棵树机制:理解声明式UI的核心工作原理
  2. 平台交互:掌握混合开发的数据通信脉络
  3. 状态管理:构建可维护的响应式架构
  4. 性能优化:定位关键瓶颈实施精准优化

建议结合Flutter DevTools的以下功能进行验证:

  • Widget Inspector:实时查看三棵树状态
  • Timeline:分析渲染流水线性能
  • Memory:检测内存泄漏与溢出
昨天以前首页

Swift 中可选链(Optional Chaining)存在的意义是什么?

作者 90后晨仔
2025年5月18日 11:42

在 Swift 中,可选链(Optional Chaining) 的存在意义在于提供一种安全、简洁且优雅的方式,用于访问可能为 nil 的属性、方法或下标,同时避免运行时错误。它是 Swift 语言对空值安全(Null Safety)的核心设计之一,以下是其核心意义和优势的详细总结:


1. 安全访问空值(避免崩溃)

  • 核心意义
    在 Swift 中,如果直接访问一个可能为 nil 的属性或方法(如 john.residence!.numberOfRooms),若 residencenil,会触发运行时错误(fatal error: unexpectedly found nil)。
    可选链通过 ? 操作符替代 ! 强制解包,当链中的某个环节为 nil 时,整个表达式会安全地返回 nil,而不是崩溃。

  • 示例对比

    // 强制解包(危险):若 john.residence 为 nil,会崩溃
    let roomCount = john.residence!.numberOfRooms
    
    // 可选链(安全):若 john.residence 为 nil,roomCount 为 nil
    let roomCount = john.residence?.numberOfRooms
    

2. 简化嵌套访问的复杂性

  • 核心意义
    在复杂的嵌套对象链中(如 person.address?.building?.name),可选链允许开发者用单行代码安全地访问多层属性,而无需多次嵌套 if letguard let

  • 示例

    class Person {
        var address: Address?
    }
    
    class Address {
        var building: Building?
    }
    
    class Building {
        var name: String?
    }
    
    let person = Person()
    if let name = person.address?.building?.name {
        print("Building name: $name)")
    } else {
        print("无法获取建筑名称")
    }
    

    如果没有可选链,需要多层嵌套解包,代码会变得冗长且难以阅读:

    if let address = person.address {
        if let building = address.building {
            if let name = building.name {
                print("Building name: $name)")
            } else {
                print("无法获取建筑名称")
            }
        } else {
            print("无法获取建筑名称")
        }
    } else {
        print("无法获取建筑名称")
    }
    

3. 统一处理可选值的返回类型

  • 核心意义
    无论目标属性是否为可选类型,可选链的返回值始终是可选类型(例如,原本返回 Int 的属性会变为 Int?)。这种设计使得开发者可以统一处理结果(如使用 if letguard let 解包),无需额外判断原始属性是否为可选。

  • 示例

    class Residence {
        var numberOfRooms = 1 // 非可选属性
    }
    
    class Person {
        var residence: Residence?
    }
    
    let john = Person()
    let roomCount: Int? = john.residence?.numberOfRooms // 返回 Int?
    

4. 替代 Objective-C 中的 nil 消息传递

  • 核心意义
    在 Objective-C 中,向 nil 发送消息是合法的,但不会有任何效果(返回 nil0)。Swift 的可选链扩展了这一特性,使其适用于所有类型(包括值类型),并明确通过返回 nil 表示失败。

  • 示例

    // Swift 可选链
    let result = someOptionalValue?.doSomething() // 若 someOptionalValue 为 nil,result 为 nil
    
    // Objective-C 中类似行为
    [someOptionalValue doSomething]; // 若 someOptionalValue 为 nil,不会崩溃但无效果
    

5. 支持方法调用和下标访问

  • 核心意义
    可选链不仅适用于属性访问,还支持方法调用下标访问,进一步扩展了其适用范围。

  • 示例

    class Toy {
        func flying() { print("飞盘在飞---") }
    }
    
    class Dog {
        var toy: Toy?
    }
    
    class Person {
        var dog: Dog?
    }
    
    let person = Person()
    person.dog?.toy?.flying() // 安全调用方法
    
    let array: [Int]? = [1, 2, 3]
    let value = array?[1] // 安全访问下标
    

6. 提高代码可读性和可维护性

  • 核心意义
    通过减少冗余的 if letguard let 嵌套,可选链使代码更简洁、直观,开发者可以快速定位潜在的 nil 风险点。

  • 对比示例

    // 使用可选链
    let name = user?.profile?.address?.city
    
    // 不使用可选链
    if let user = user, let profile = user.profile, let address = profile.address {
        let name = address.city
    }
    

7. 与 if let/guard let 的协同使用

  • 核心意义
    可选链通常与 if letguard let 结合使用,实现对结果的进一步解包和处理。

  • 示例

    if let roomCount = john.residence?.numberOfRooms {
        print("房间数: $roomCount)")
    } else {
        print("无法获取房间数")
    }
    

总结

Swift 的可选链通过以下方式显著提升了开发体验:

  1. 安全性:避免因 nil 导致的崩溃。
  2. 简洁性:减少嵌套代码,提升可读性。
  3. 灵活性:支持属性、方法、下标的统一访问。
  4. 一致性:所有类型均适用,无需区分类或值类型。

核心意义
可选链是 Swift 对空值安全的优雅解决方案,它让开发者在面对复杂对象链时,既能保持代码的简洁性,又能确保程序的健壮性。

❌
❌