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

使用ReplayKit获取的CMSampleBuffer如何转换为m3u8格式并存储到指定路径?

实现CMSampleBuffer转本地M3U8存储的方案

要实现从ReplayKit采集的CMSampleBuffer生成M3U8,核心逻辑是先将音视频采样编码为H.264/AAC格式,再封装为TS切片,最后维护更新M3U8索引文件,具体步骤如下:


1. 导入依赖框架

需要用到系统的音视频编解码、文件处理相关框架:

import AVFoundation
import VideoToolbox
import AudioToolbox

2. 初始化全局配置参数

提前定义切片规则、存储路径、编码器等全局参数:

// 切片配置,单切片2秒适配低延迟实时播放
let kSegmentDuration: Double = 2
// 本地存储根路径,可替换为你指定的路径
let saveRootPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let m3u8FilePath = saveRootPath.appendingPathComponent("screen_record_live.m3u8")

// 运行时变量
var currentSegmentIndex = 0
var currentSegmentData: NSMutableData?
var currentSegmentDuration: Double = 0
// 自定义H264编码器、AAC编码器实例
var h264Encoder: H264Encoder?
var aacEncoder: AACEncoder?

3. 处理采集到的CMSampleBuffer

在系统回调的processSampleBuffer方法中完成编码、TS封装逻辑:

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
    switch sampleBufferType {
    case .video:
        // 视频帧送入H264编码器,得到NALU单元
        h264Encoder?.encode(sampleBuffer: sampleBuffer, completion: { [weak self] naluData, isKeyFrame in
            guard let self = self else { return }
            // 遇到关键帧且当前切片达到预设时长时,完成上一个切片存储
            if isKeyFrame && self.currentSegmentDuration >= kSegmentDuration {
                self.saveCurrentTSSegment()
                self.updateM3U8Index()
                // 重置当前切片状态
                self.currentSegmentIndex += 1
                self.currentSegmentData = NSMutableData()
                self.currentSegmentDuration = 0
            }
            // 将NALU单元封装为TS视频包写入当前切片
            if let videoTSPacket = TSPacketGenerator.generateVideoPacket(naluData: naluData, isKeyFrame: isKeyFrame) {
                self.currentSegmentData?.append(videoTSPacket)
            }
            // 累加切片时长
            self.currentSegmentDuration += CMSampleBufferGetDuration(sampleBuffer).seconds
        })
    case .audioApp, .audioMic:
        // 可先混合两路音频再送入AAC编码器,得到AAC帧
        aacEncoder?.encode(sampleBuffer: sampleBuffer, completion: { [weak self] aacFrameData in
            guard let self = self else { return }
            // 将AAC帧封装为TS音频包写入当前切片
            if let audioTSPacket = TSPacketGenerator.generateAudioPacket(aacData: aacFrameData) {
                self.currentSegmentData?.append(audioTSPacket)
            }
        })
    @unknown default:
        break
    }
}

4. 实现TS切片存储逻辑

将封装好的TS切片数据写入本地磁盘:

func saveCurrentTSSegment() {
    let segmentFilePath = saveRootPath.appendingPathComponent("seg_\(currentSegmentIndex).ts")
    try? currentSegmentData?.write(toFile: segmentFilePath, options: .atomic)
}

5. 实现M3U8索引更新逻辑

按照标准HLS协议格式维护M3U8文件:

func updateM3U8Index() {
    var m3u8Content = ""
    // 首次生成时写入M3U8头部
    if currentSegmentIndex == 0 {
        m3u8Content = """
        #EXTM3U
        #EXT-X-VERSION:3
        #EXT-X-TARGETDURATION:\(Int(kSegmentDuration + 1))
        #EXT-X-MEDIA-SEQUENCE:0
        
        """
    }
    // 追加新切片的索引信息
    m3u8Content += "#EXTINF:\(String(format: "%.3f", currentSegmentDuration)),\n"
    m3u8Content += "seg_\(currentSegmentIndex).ts\n"
    
    // 写入到本地路径
    if let fileHandle = FileHandle(forWritingAtPath: m3u8FilePath) {
        fileHandle.seekToEndOfFile()
        fileHandle.write(m3u8Content.data(using: .utf8)!)
    } else {
        try? m3u8Content.write(toFile: m3u8FilePath, atomically: true, encoding: .utf8)
    }
    
    // 录制结束时需要追加结束标记,点播场景必须添加:
    // let endTag = "\n#EXT-X-ENDLIST"
    // try? endTag.append(toFile: m3u8FilePath, encoding: .utf8)
}

注意事项

  • 所有IO操作、编码操作不要放在主线程执行,避免阻塞ReplayKit的采样回调导致录制中断
  • H.264编码生成的SPS、PPS数据要附加到每个关键帧的前面,否则单个TS切片解码时会出现花屏、无法播放的问题
  • 封装TS数据包时要对齐音视频的PTS/DTS时间戳,避免最终播放时出现音画不同步的问题
  • Broadcast Extension的内存上限为50MB,不要长时间缓存采样数据或切片数据,达到切片时长阈值后及时写入磁盘释放内存
  • 本地部署HTTP服务器后,要确保M3U8和TS切片放在同一目录下,保证相对路径引用正确

内容的提问来源于stack exchange,提问作者Sam KC

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:06:03