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

Agora iOS SDK:AVSampleBufferDisplayLayer无法启用原生PiP及后台渲染问题

针对你在Agora iOS SDK中结合AVSampleBufferDisplayLayer使用原生画中画(PiP)的问题,以下是具体解决方案:

一、修正Agora初始化配置

首先清理冗余参数,确保后台视频输出配置正确,同时必须激活音频模块(iOS后台仅音频活跃进程能持续运行):

// 初始化Agora引擎
agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: "你的AppID", delegate: self)

// 配置后台视频输出
agoraKit.setParameters("{\"rtc.video.background_mode\": 1}") // 允许后台输出视频帧
agoraKit.setParameters("{\"rtc.video.enable_ios_background_mode\": true}")
agoraKit.setParameters("{\"che.video.force_renderer_background\": true}") // 兼容旧版本渲染逻辑

// 基础直播配置
agoraKit.setChannelProfile(.liveBroadcasting)
agoraKit.setClientRole(.broadcaster)
agoraKit.enableVideo()
agoraKit.enableLocalVideo(true)
agoraKit.setVideoFrameDelegate(self) // 必须在enableVideo后设置

// 激活音频模块(后台运行必需,可静音)
agoraKit.enableAudio()
agoraKit.muteLocalAudioStream(true)

// 配置系统音频会话
let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setCategory(.playback, mode: .default, options: .mixWithOthers)
    try audioSession.setActive(true)
} catch {
    print("音频会话配置失败:\(error)")
}

二、构建支持PiP的视频渲染管道

AVSampleBufferDisplayLayer不直接支持PiP,必须通过AVPlayerLayer配合AVPictureInPictureController实现。我们将在前台用AVSampleBufferDisplayLayer渲染,后台切换到AVPlayerLayer驱动PiP:

  1. 声明核心组件:
import AVKit

private var playerLayer: AVPlayerLayer!
private var pipController: AVPictureInPictureController?
private var videoLayerRemote: AVSampleBufferDisplayLayer?
  1. 初始化渲染组件与PiP控制器:
func setupRenderAndPiP() {
    // 初始化前台渲染层
    videoLayerRemote = AVSampleBufferDisplayLayer()
    videoLayerRemote?.frame = view.bounds
    view.layer.addSublayer(videoLayerRemote!)
    
    // 初始化AVPlayerLayer(用于PiP)
    playerLayer = AVPlayerLayer(player: AVPlayer())
    playerLayer.frame = view.bounds
    playerLayer.hidden = true // 前台隐藏,后台显示
    view.layer.addSublayer(playerLayer)
    
    // 初始化PiP控制器
    guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
    pipController = AVPictureInPictureController(playerLayer: playerLayer)
    pipController?.delegate = self
}
  1. 调整onRenderVideoFrame的帧处理逻辑,区分前后台渲染目标:
func onRenderVideoFrame(_ videoFrame: AgoraOutputVideoFrame, uid: UInt, channelId: String) -> Bool {
    guard uid != localUid else { return true }
    
    var sampleBuffer: CMSampleBuffer?
    
    // 保留你现有的I420/PixelBuffer转CMSampleBuffer逻辑
    if videoFrame.type == 1 { // I420格式
        guard let yBuffer = videoFrame.yBuffer,
              let uBuffer = videoFrame.uBuffer,
              let vBuffer = videoFrame.vBuffer else {
            return true
        }
        sampleBuffer = convertI420ToCMSampleBuffer(yPlane: yBuffer,
                                                   uPlane: uBuffer,
                                                   vPlane: vBuffer,
                                                   width: Int(videoFrame.width),
                                                   height: Int(videoFrame.height))
    } else {
        guard let pixelBuffer = videoFrame.pixelBuffer else {
            return true
        }
        sampleBuffer = createSampleBuffer(from: pixelBuffer)
    }
    
    guard let buffer = sampleBuffer else { return true }
    
    let appState = UIApplication.shared.applicationState
    if appState == .active {
        // 前台:用AVSampleBufferDisplayLayer渲染
        guard let videoLayer = videoLayerRemote else { return true }
        if videoLayer.status == .failed { videoLayer.flush() }
        if videoLayer.isReadyForMoreMediaData {
            videoLayer.enqueue(buffer)
        }
    } else {
        // 后台:将帧传入AVPlayerLayer(先把AVSampleBufferDisplayLayer移到playerLayer下)
        if videoLayerRemote?.superlayer !== playerLayer {
            videoLayerRemote?.removeFromSuperlayer()
            playerLayer.addSublayer(videoLayerRemote!)
            videoLayerRemote?.frame = playerLayer.bounds
        }
        
        guard let videoLayer = videoLayerRemote else { return true }
        if videoLayer.status == .failed { videoLayer.flush() }
        if videoLayer.isReadyForMoreMediaData {
            videoLayer.enqueue(buffer)
        }
    }
    
    return true
}

三、处理应用前后台切换

注册通知并实现切换逻辑,自动启动/停止PiP,切换渲染层:

override func viewDidLoad() {
    super.viewDidLoad()
    setupRenderAndPiP()
    
    // 注册前后台通知
    NotificationCenter.default.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(enterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}

@objc private func enterBackground() {
    // 显示playerLayer,隐藏前台渲染层
    playerLayer.hidden = false
    videoLayerRemote?.hidden = false
    
    // 启动PiP
    if let pipController = pipController, !pipController.isPictureInPictureActive {
        pipController.startPictureInPicture()
    }
}

@objc private func enterForeground() {
    // 停止PiP
    pipController?.stopPictureInPicture()
    
    // 切换回前台渲染层
    playerLayer.hidden = true
    if videoLayerRemote?.superlayer !== view.layer {
        videoLayerRemote?.removeFromSuperlayer()
        view.layer.addSublayer(videoLayerRemote!)
        videoLayerRemote?.frame = view.bounds
        videoLayerRemote?.hidden = false
    }
}

四、PiP控制器代理实现(可选)

处理PiP状态变化,比如用户手动关闭PiP的场景:

extension YourViewController: AVPictureInPictureControllerDelegate {
    func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
        // 用户关闭PiP后,可选回到前台或停止视频流
        if UIApplication.shared.applicationState == .background {
            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
        }
    }
}

关键注意事项

  1. 后台权限配置:在Xcode Capabilities中必须启用Audio, AirPlay, and Picture in Picture和Background Processing;Info.plist中添加NSCameraUsageDescription和NSMicrophoneUsageDescription(音频模块活跃必需)。
  2. 时间戳正确性:确保你的convertI420ToCMSampleBuffer和createSampleBuffer函数生成的CMSampleBuffer带有递增的、符合视频帧率的presentationTimeStamp,否则PiP会无法正常播放。
  3. Agora参数说明:rtc.video.pip仅适用于Agora自带渲染视图,自定义渲染时无效,可直接删除该参数。

内容的提问来源于stack exchange,提问作者Himanshu Fulwani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:40:54