倩倩之美~

导航

ios虚拟摄像头插件,iPhone苹果替换相机软件,通过xposed框架实现

下载地址:https://www.pan38.com/share.php?code=DuNzA
技术原理
通过MobileSubstrate注入系统相机进程
Hook AVFoundation框架的AVCaptureSession相关方法
替换原始视频流数据
核心实现代码
`
%hook AVCaptureVideoDataOutput

  • (void)setSampleBufferDelegate:(id)delegate queue:(dispatch_queue_t)queue {
    %orig; // 调用原始方法
    [VideoFaker shared].realDelegate = delegate; // 保存原始代理
    [VideoFaker setupVirtualCamera]; // 初始化虚拟摄像头
    }
    %end

@interface VideoFaker : NSObject

  • (instancetype)shared;
  • (void)replaceVideoBuffer:(CMSampleBufferRef)buffer;
    @end`

` VideoFaker {
id _realDelegate;
}

  • (void)captureOutput:(AVCaptureOutput *)output
    didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
    fromConnection:(AVCaptureConnection *)connection {

    // 替换为自定义视频帧
    CMSampleBufferRef fakeBuffer = [self generateFakeFrame];
    [_realDelegate captureOutput:output didOutputSampleBuffer:fakeBuffer fromConnection:connection];
    CFRelease(fakeBuffer);
    }

  • (CMSampleBufferRef)generateFakeFrame {
    // 生成虚拟图像逻辑
    CVPixelBufferRef pixelBuffer = [FrameGenerator createTestPattern];
    return [BufferConverter sampleBufferFromPixelBuffer:pixelBuffer];
    }
    @end`

基于SwiftUI框架设计,包含摄像头切换、滤镜选择和视频源配置功能:

`
import SwiftUI
import AVKit

struct CameraView: View {
@StateObject private var model = CameraModel()
@State private var showSourcePicker = false
@State private var selectedFilter = "None"

var body: some View {
    VStack {
        // 视频预览区域
        ZStack {
            if let frame = model.currentFrame {
                Image(uiImage: frame)
                    .resizable()
                    .scaledToFit()
                    .colorMultiply(selectedFilter == "None" ? .white : .accentColor)
            } else {
                Color.black
            }
            
            VStack {
                HStack {
                    Button(action: { model.switchCamera() }) {
                        Image(systemName: "arrow.triangle.2.circlepath")
                            .padding(8)
                            .background(Circle().fill(.ultraThinMaterial))
                    }
                    
                    Spacer()
                    
                    Menu(selectedFilter) {
                        ForEach(["None", "Sepia", "Mono", "Vintage"], id: \.self) { filter in
                            Button(filter) { 
                                selectedFilter = filter
                                model.applyFilter(filter)
                            }
                        }
                    }
                }
                .padding()
                
                Spacer()
                
                // 视频源选择按钮
                Button(action: { showSourcePicker.toggle() }) {
                    Label("Video Source", systemImage: "film")
                        .padding()
                        .background(Capsule().fill(.blue))
                }
            }
        }
        
        // 控制按钮区域
        HStack {
            Button(action: model.toggleRecording) {
                Circle()
                    .fill(model.isRecording ? .red : .gray)
                    .frame(width: 60, height: 60)
                    .overlay(
                        Circle()
                            .stroke(.white, lineWidth: 3)
                    )
            }
            
            Spacer()
            
            Button(action: model.capturePhoto) {
                Circle()
                    .fill(.white)
                    .frame(width: 70, height: 70)
                    .overlay(
                        Circle()
                            .stroke(.blue, lineWidth: 3)
                    )
            }
        }
        .padding()
    }
    .sheet(isPresented: $showSourcePicker) {
        SourcePickerView(selectedSource: $model.videoSource)
    }
}

}`

视频选择器(PHPickerViewController)、时长滑块控制(UISlider)和视频裁剪导出功能

`
import UIKit
import PhotosUI
import AVKit

class VideoPickerViewController: UIViewController {

// MARK: - UI Components
private let selectButton: UIButton = {
    let btn = UIButton(type: .system)
    btn.setTitle("选择本地视频", for: .normal)
    btn.backgroundColor = .systemBlue
    btn.tintColor = .white
    btn.layer.cornerRadius = 8
    return btn
}()

private let timeLabel: UILabel = {
    let label = UILabel()
    label.text = "选择时长: 0秒"
    label.textAlignment = .center
    return label
}()

private let slider: UISlider = {
    let slider = UISlider()
    slider.minimumValue = 0
    slider.maximumValue = 60
    return slider
}()

private var selectedAsset: AVAsset?

// MARK: - Lifecycle
override func viewDidLoad() {
    super.viewDidLoad()
    setupUI()
    setupActions()
}

// MARK: - Setup
private func setupUI() {
    view.backgroundColor = .white
    let stack = UIStackView(arrangedSubviews: [selectButton, timeLabel, slider])
    stack.axis = .vertical
    stack.spacing = 20
    stack.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(stack)
    
    NSLayoutConstraint.activate([
        stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
        stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40)
    ])
}

private func setupActions() {
    selectButton.addTarget(self, action: #selector(selectVideo), for: .touchUpInside)
    slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
}

// MARK: - Actions
@objc private func selectVideo() {
    var config = PHPickerConfiguration()
    config.filter = .videos
    config.selectionLimit = 1
    
    let picker = PHPickerViewController(configuration: config)
    picker.delegate = self
    present(picker, animated: true)
}

@objc private func sliderValueChanged() {
    let selectedTime = Int(slider.value)
    timeLabel.text = "选择时长: \(selectedTime)秒"
}

private func processVideo(with duration: CMTime) {
    guard let asset = selectedAsset else { return }
    
    let startTime = CMTime(seconds: 0, preferredTimescale: 600)
    let endTime = CMTime(seconds: Double(slider.value), preferredTimescale: 600)
    let timeRange = CMTimeRange(start: startTime, end: endTime)
    
    let exportSession = AVAssetExportSession(asset: asset, 
                                           presetName: AVAssetExportPresetHighestQuality)
    exportSession?.outputURL = FileManager.default.temporaryDirectory
        .appendingPathComponent("trimmed_video.mp4")
    exportSession?.outputFileType = .mp4
    exportSession?.timeRange = timeRange
    
    exportSession?.exportAsynchronously {
        DispatchQueue.main.async {
            if exportSession?.status == .completed {
                self.playVideo(at: exportSession!.outputURL!)
            }
        }
    }
}

private func playVideo(at url: URL) {
    let player = AVPlayer(url: url)
    let vc = AVPlayerViewController()
    vc.player = player
    present(vc, animated: true) {
        player.play()
    }
}

}

// MARK: - PHPickerViewControllerDelegate
extension VideoPickerViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController,
didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)

    guard let result = results.first else { return }
    result.itemProvider.loadObject(ofClass: AVAsset.self) { [weak self] (asset, error) in
        guard let self = self, let asset = asset as? AVAsset else { return }
        
        DispatchQueue.main.async {
            self.selectedAsset = asset
            let duration = asset.duration.seconds
            self.slider.maximumValue = Float(duration)
            self.timeLabel.text = "选择时长: 0秒/\(Int(duration))秒"
        }
    }
}

}`

posted on 2025-06-17 09:15  爱开发的倩倩  阅读(169)  评论(0)    收藏  举报