
Camera Sun 这个项目在 Hacker News 上出现时定位非常清楚它想修的不是 iPhone 的摄像头硬件而是 iPhone 自带相机 App 里那些“软件替用户做了决定”的部分。原生相机拍完的照片不少人会碰到这样一个感觉照片里的场景和眼睛看到的对不上。具体来说高光被压得厉害、皮肤纹理被抹得很平、夜景照片边缘像水彩画想手动锁定曝光和白平衡又找不到入口。如果你只是看不惯原生相机的默认效果或者正在考虑自己做一个第三方相机应用这篇内容可以帮你把问题落到具体步骤上。我更想按“搞清楚要修什么 - 搭一个最小可运行工程 - 调整核心参数 - 真机验证和排查”的顺序拆一遍 Camera Sun 这类项目会走过的路。文章里会包含环境准备、权限、关键代码、参数判断标准和常见报错排查适合做过基本 iOS 开发、但还没深入 AVFoundation 的人参考。1. 先理解 Camera Sun 想修的是哪一类问题1.1 原生相机的“自动处理”不是用户可控的iPhone 原生相机的问题往往不是“拍不出来”而是“拍出来的东西被处理成了系统认为好的样子”。智能 HDR、深度融合、夜间模式这些能力默认都是自动开启的。系统会在你按下快门的瞬间把多张不同曝光的照片合成到一起再通过图像信号处理器做降噪、锐化和色彩映射。这个流程对大多数普通用户是友好的但对有明确拍摄意图的人来说问题就出现了。你想拍一张暗部细节保留、高光不过曝的照片系统可能为了保住高光把整个人物拍得很平你想拍一张皮肤纹理清晰、有颗粒感的照片系统可能直接判断为“噪点”然后抹掉。很多第三方相机项目包括 Camera Sun 这一类想做的一件事就是让用户重新拿回拍摄参数的控制权。不是把界面做得花哨而是把自动处理关掉、降低系统干预、允许用户锁定关键参数让最终照片更接近拍摄时看到的样子。1.2 常见被吐槽的场景和判断标准要判断原生相机到底哪里不好用不能只靠感觉。我一般会先拿同一个场景用原生相机和一个可手动控制参数的相机 App 各拍一张再按几个标准对比对比项原生相机常见表现需要关注的判断方式人脸肤色低光下偏黄或偏灰放大看肤色和背景过渡高光阴影高光经常被压到发灰看云层、灯光区域是否丢失层次锐化程度边缘容易出现白边放大到 200% 看头发和物体轮廓夜景效果涂抹感明显看墙面纹理和文字边缘手动控制只能点按对焦曝光看是否支持曝光补偿、锁定白平衡、RAW连续拍摄稳定性连拍后处理时间较长记录每张回调时间是否一致判断标准不要只看“看起来清不清楚”而是看照片有没有保留细节、有没有出现明显处理痕迹、参数是否可控。如果你拍的是 JPEG 或 HEIC那么原始信息已经经过一次系统处理后面再怎么修都相当于“二次加工”。所以 Camera Sun 这类项目如果真想修复问题往往会把输出格式、图像管线、色彩空间这些底层选项一起暴露出来而不是只加一个滤镜按钮。1.3 自定义相机和修图流程的边界这里要泼一盆冷水不是所有“原生相机的问题”都能靠一个自定义相机 App 解决。有些问题是硬件限制比如镜头物理光圈、传感器尺寸、防抖能力软件只能做补偿不能改变基础画质。有些问题是系统限制AVFoundation 虽然开放了很多接口但并不允许第三方 App 完全接管所有拍照硬件处理。系统层的多帧合成、深度融合、夜间模式第三方应用可以选择不使用但很难彻底绕开底层的传感器控制。关键是明确自己能修什么。能修的是界面控制权、输出格式、参数锁定、保存流程不能修的是系统在传感器层已经做过的处理。如果拍摄时输出的是 HEIC 或 JPEG系统 ISP 已经跑过一轮了拍出来的文件本身就是处理后的结果。想保留更多原始信息就需要考虑输出 ProRAW 或 RAW 格式并且在外部流程里做后期。这个边界在项目起步时就要想清楚否则写了半天功能最后发现色彩还是被系统改了一轮。2. 搭一个能跑真机的最小自定义相机工程2.1 环境与前置条件我建议把第一次测试拆成三步先启动相机预览再拍一张照片最后确认照片能保存。不要一上来就堆功能。环境准备首先需要一台 Mac装了 Xcode能新建 UIKit 工程。模拟器没有真实摄像头AVCaptureSession 在里面跑不了所以必须准备一台 iPhone 真机用数据线连接。不需要付费开发者账号个人 Apple ID 就能跑真机只是免费签名有有效期过一阵需要重新安装。系统版本方面不用追求最新。你在 Xcode 里新建工程时把 Deployment Target 设置成你手上那台测试机支持的最低系统版本就行。项目越往底层走越依赖设备能力所以先把“能跑起来”做通再想着适配更多机型。2.2 权限配置别漏iOS 应用访问摄像头和相册必须在 Info.plist 里声明用途描述。漏掉任意一个 key可能在启动或者保存照片时直接闪退或者在系统弹窗里看不到任何文字测试到一半断掉。最基础的需要这几个keyNSCameraUsageDescription/key string需要使用相机来拍摄照片/string keyNSPhotoLibraryAddUsageDescription/key string需要保存照片到相册/string keyNSPhotoLibraryUsageDescription/key string需要访问相册中的照片/stringNSPhotoLibraryAddUsageDescription 负责“只往里写”NSPhotoLibraryUsageDescription 负责“读写相册”。如果只是保存照片通常 Add 这个就够。但为了测试时能读取生成的图片我一般会把两个都写上省得权限弹窗来回跳。2.3 最小预览链路Session、Input、Output、PreviewAVFoundation 的相机开发核心链路其实就是四个对象AVCaptureSession所有采集工作的“会话容器”。AVCaptureDeviceInput把摄像头硬件包装成输入源。AVCapturePhotoOutput负责拍摄静态照片的输出端。AVCaptureVideoPreviewLayer把实时画面渲染到屏幕上。下面是一个最简的相机页面结构先不管 UI 和异常处理只验证链路是否通import UIKit import AVFoundation class CameraViewController: UIViewController { var captureSession: AVCaptureSession! var previewLayer: AVCaptureVideoPreviewLayer! var photoOutput: AVCapturePhotoOutput! override func viewDidLoad() { super.viewDidLoad() checkPermission() } func checkPermission() { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: setupCamera() case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.async { if granted { self.setupCamera() } } } default: break } } func setupCamera() { captureSession AVCaptureSession() captureSession.sessionPreset .photo guard let backCamera AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: .back ), let input try? AVCaptureDeviceInput(device: backCamera) else { print(找不到后置摄像头) return } if captureSession.canAddInput(input) { captureSession.addInput(input) } photoOutput AVCapturePhotoOutput() if captureSession.canAddOutput(photoOutput) { captureSession.addOutput(photoOutput) } previewLayer AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.videoGravity .resizeAspectFill previewLayer.frame view.bounds view.layer.insertSublayer(previewLayer, at: 0) captureSession.startRunning() } IBAction func takePhoto(_ sender: Any) { let settings AVCapturePhotoSettings() photoOutput.capturePhoto(with: settings, delegate: self) } } extension CameraViewController: AVCapturePhotoCaptureDelegate { func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let data photo.fileDataRepresentation(), let image UIImage(data: data) else { return } UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) } }这段代码跑通之后你会在手机屏幕上看到实时预览按一下拍照按钮照片进系统相册。注意 AVCapturePhotoCaptureDelegate 在旧版本里需要手动标志协议是否遵守新 Xcode 一般不需要额外处理但如果编译报“does not conform”之类的错优先检查 import 和类声明位置。2.4 按下快门后发生了什么很多人跑通上面代码后就急着调滤镜、加手势、改分辨率。我的建议是先把“按下快门后”这条链路想清楚。capturePhoto 方法调用之后系统会经历对焦、测光、曝光、传感器采集、图像处理、编码等一系列步骤最后通过 delegate 回调把 AVCapturePhoto 对象交给你。这个对象上可以拿到 JPEG/HEIF 数据、原始数据、元数据、缩略图等。photo.fileDataRepresentation() 返回的是经过系统管线处理过的图片数据。如果你不在这里做任何额外处理保存到相册的照片就已经带上了系统的自动优化。Camera Sun 这类项目如果要“修复”原生相机通常就是在这个环节做文章要么改输出格式要么关掉某些自动处理要么额外保存一份 RAW。先不要想这么多。第一阶段只要确认相册里能出现照片并且控制台没有权限报错就说明整条链路已经通了。剩下的都是在这个链路基础之上做替换。3. 关键参数和拍摄选项怎么选3.1 会话预设、分辨率与格式AVCaptureSession 的 sessionPreset 控制着采集质量。常用预设包括 .photo、.high、.medium、.hd1920x1080 等。我的建议是日常先使用 .photo它会给当前的设备一个较合适的照片分辨率。等后面需要控制文件大小或传输速度时再降级到 .hd1920x1080 或更低。预设适用场景注意点.photo静态照片优先占用内存较大.high拍照和视频兼顾画质中等通用性强.medium网络传输、快速预览文件小细节少.hd1920x1080视频或低内存场景不是所有设备都支持还有一个容易忽略的 AVCapturePhotoSettings 参数photoQualityPrioritization。它能让你在“更快的处理速度”和“更高的画质”之间取一个值。默认通常是 .balanced。如果连拍时每张照片都要等很久可以尝试 .speed如果追求单张画质可以选 .quality。但不要一上来就拉满质量因为照片体积和处理时间会同时上升。输出格式方面简单理解HEIC体积小但已经过系统处理后期空间有限。JPEG兼容性好适合直接分享。RAW / ProRAW保留传感器原始信息适合后期调色但文件非常大。如果你发现“拍出来的照片还是被自动优化了”很可能不是因为相机 App 代码有问题而是输出格式本身就是处理后的结果。想测试系统处理边界可以单独加一个开关同一场景分别输出 HEIC 和 RAW 对比。只要你能看到 RAW 文件没有被锐化涂抹就说明问题出在图像管线不是出在传感器。3.2 对焦、曝光、白平衡的调节顺序Camera Sun 这类项目最核心的竞争力就是让用户能够锁定参数。在 AVFoundation 里手动控制主要落在 AVCaptureDevice 上。我一般按这个顺序调节先设置对焦兴趣点再设置曝光兴趣点最后才考虑白平衡。原因是曝光和白平衡都依赖场景亮度先确定画面焦点后面的测光才更稳定。下面是一个锁定对焦和曝光的示意guard let device AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: .back ) else { return } do { try device.lockForConfiguration() // 点按对焦 if device.isFocusPointOfInterestSupported { device.focusPointOfInterest CGPoint(x: 0.5, y: 0.5) device.focusMode .autoFocus } // 点按测光 if device.isExposurePointOfInterestSupported { device.exposurePointOfInterest CGPoint(x: 0.5, y: 0.5) device.exposureMode .autoExpose } // 手动白平衡固定 if device.isWhiteBalanceModeSupported(.locked) { device.setWhiteBalanceModeLocked(with: AVCaptureDevice.WhiteBalanceGains( redGain: 1.0, greenGain: 1.0, blueGain: 1.0 )) } device.unlockForConfiguration() } catch { print(配置设备参数失败\(error)) }这段代码里的红绿蓝增益我用了 1.0只是一个示例。真实使用中你通常要先用 AVCaptureDevice.whiteBalanceGains(for:) 从一个色温值算出设备支持的增益值不能直接写死。过早锁定白平衡反而会让场景色偏更明显所以刚开始做项目时先只调试对焦和曝光白平衡保持 AWB 就够了。3.3 试拍、连拍、低光场景的稳定性验证功能能跑不代表稳定。我建议把测试分成三档第一档单拍。同一场景拍 10 张每张之间间隔 1 秒看是否有照片丢失、保存失败或预览卡顿。第二档连续拍摄。不要调大并发先按住快门连拍 10 到 20 张看每张照片回调的间隔是否均匀。如果越拍越慢甚至出现回调丢失说明 photoOutput 的配置或内存管理还有问题。第三档低光场景。关掉部分灯光在较暗环境下拍几张观察系统是否自动启用长曝光。长曝光意味着手持拍摄的成片率下降也意味着单张处理时间变长。如果你的相机应用允许用户手动调节曝光时间这里就要特别注意快门时间太长照片容易糊快门时间太短噪点会明显。不要以为“图上看起来亮”就代表画质好。真正的判断标准是放大 100% 后看暗部有没有明显断层亮部有没有过曝边缘有没有伪色。这些只有实拍才知道。3.4 用设备信息表辅助判断兼容性一个很常见的坑是在 iPhone 15 Pro 上跑得好好的功能放到旧机型上预览黑屏或者拍照崩溃。原因多半是你用了设备不支持的 API。不要通过机型名称去猜功能而是要用 AVFoundation 提供的能力查询接口。比如isFocusPointOfInterestSupportedisExposurePointOfInterestSupportedisLowLightBoostSupportedsupportsSessionPresetisNightModeSupportedavailableRawPhotoPixelFormatTypes如果你在日志里看到类似 iPhone xx,x 这样的标识那是产品型号标识符和功能兼容性没有直接关系。判断兼容性要看当前设备是否支持某个特性而不是看型号字符串。我一般会在设置页放一个“当前设备能力”面板把常用支持项列出来。这样后续测试某一台新机器时不用反复看文档直接跑一下就知道哪些按钮该隐藏、哪些不该显示。4. 真机调试、常见报错和生产化建议4.1 先跑通一条完整链路再扩展功能做相机应用最大的挫败感不是 API 不会用而是功能堆太多了出问题根本不知道是哪一环导致的。比如你想做一个支持手动曝光、变焦、滤镜、RAW、连拍、夜景模式的项目。如果第一次就全加上一旦某个场景拍出来颜色不对你很难判断是白平衡问题、滤镜问题还是输出格式问题。我更建议的功能开发顺序是相机预览正常。单次拍照保存成功。点按对焦和曝光。输出格式切换HEIC / JPEG / RAW。曝光补偿滑杆。连拍和批量保存。最后才是滤镜、美颜、水印、人脸检测。每完成一步就真机验证一次。这样你的定位范围非常小出了问题也能马上知道是哪一环。4.2 常见报错和排查顺序跑自定义相机应用我遇到过的绝大多数问题都不是“代码写得不对”而是环境、权限、资源和设备能力没有对齐。下面是一份按优先级排列的排查顺序现象优先检查再检查预览黑屏相机权限是否已授权captureSession 是否在运行previewLayer 是否加入视图拍照后没有回调photoOutput 是否被 addOutputdelegate 是否被释放、是否在主线程调用 capturePhoto照片保存失败Info.plist 的两个相册权限 key相册权限状态、磁盘剩余空间从后台切回相机卡死viewWillDisappear 有没有 stopRunning是否重复创建 captureSession某台设备上功能按钮不可用功能支持检测是否做全系统版本和设备型号差异拍出来的照片还是过度锐化是否输出 HEIC/JPEG是否启用了 RAW是否在后期步骤被重新压回 JPEG排查顺序不要乱。先看现象是“没画面”“没照片”还是“照片不对”再去看日志、权限、设备能力。不要一开始就怀疑模型、滤镜、色彩空间。很多时候问题只是相册权限的 usage description 没写全或者预览图层的 frame 是零。注意如果拍照后照片保存失败但控制台没有报错先检查 UIImageWriteToSavedPhotosAlbum 的回调参数和 Info.plist 权限。这个 API 的失败经常是静默的不弹窗也不抛异常。4.3 从个人 Demo 到长期使用的工程化建议如果你只是学习默认配置够用。但如果你要把这个相机应用当作长期项目来维护就要提前做好几件事。第一资源生命周期管理。相机会话是硬件资源不能一直在后台运行。我会在 viewWillAppear 里启动在 viewWillDisappear 里停止。如果用户在拍照过程中按 Home 键再切回来session 必须能恢复。第二输出文件命名和目录。不要每次都把照片丢进系统相册就算完。如果是批量拍摄或测试任务我一般会先写入沙盒目录文件名加上时间戳、设备型号、参数类型方便后续分析。等用户确认需要保存到相册再调用系统接口。这样既能减少权限弹窗也能避免相册里堆满测试图。第三失败重试和日志。连续拍摄任务里可能因为手抖、对焦失败、内存压力导致单张照片丢失。应用层面要留日志至少记录“什么时候拍了、用了什么参数、是否成功、失败在哪个阶段”。没有日志的相机项目后期调低光表现会非常痛苦。第四签名和部署。免费 Apple ID 的真机签名有效期有限如果只是自己测试过期后重装一次即可如果要做长期使用或分发给别人需要开发者证书。这个不是技术问题但会卡住进度。4.4 边界提醒哪些问题不是写代码能解决的回到 Camera Sun 的出发点。iPhone 自带相机不好用不是因为它“拍得差”而是因为它替用户做了太多决定。第三方相机应用能做的是把决定权还给你但并不能改变物理硬件和系统底层的限制。传感器太小低光下依然会有噪点软件只能降噪不能增加进光量。镜头没有机械光圈景深和虚化依赖计算模拟不是物理光学效果。HEIC/JPEG 输出本身已经过系统处理想要完全绕过锐化就必须用 RAW而 RAW 的数据量和处理成本都很高。系统在某些模式下不会开放全部控制权你要接受“能控制的参数有限”这个现实。竖屏预览和最终照片存在视角差异这是预览层与照片输出层不同导致的不是 bug。真正想“修好 iPhone 相机”通常不是写一个漂亮的相机界面而是要把拍摄、存储、后期、排查整套流程都梳理清楚。先把单拍跑稳再考虑连拍、RAW、边缘场景否则你只会从一个坑跳进另一个坑。踩过几轮之后你会发现Camera Sun 这类项目最大的价值不是把界面重画一遍而是让人重新拿回拍摄参数的控制权。iPhone 自带相机不差差的是它总替用户做决定。很多问题不是增加一个按钮就能解决但如果你能先跑通一条完整链路再逐个验证曝光、对焦、白平衡、输出格式很多“原生相机不好用”的抱怨都会变成可复现、可调整、可排查的具体问题。