☰
MIPI RAW与Unpacked RAW:链路格式、内存格式及调试实战
2026/10/11 1:43:16 网站建设 项目流程

调camera驱动这些年,MIPI raw和unpacked raw这两个词反反复复出现,几乎每一次sensor出图异常都能牵扯到它们。Sensor明明输出的RAW10,ISP那边却收到了一堆错位的bit流,画面全是斜条纹;又或者DMA里配的是unpacked存储,sensor手册里写的却是packed传输,两边对不上,光排查格式就耗掉一下午。这篇文章把这两个概念的来龙去脉一次说透:链路格式和内存格式到底是什么关系,packed和unpacked在带宽、对齐、处理效率上的差异有多少,从硬件解包到软件转换怎么做,最后给一套可以直接复用的排查方法,适合驱动工程师、ISP开发者和做采集设备的人参考。

1. MIPI raw和unpacked raw先分清:链路格式与内存格式完全两回事

很多人把MIPI raw和unpacked raw当成两种可以二选一的存储格式,这是个误区。MIPI raw描述的是MIPI CSI-2链路上的传输格式,unpacked raw描述的是数据落到内存后的布局。链路和内存中间还隔着一层DMA搬运,这层可能做解包也可能不做,所以两者不能直接画等号。

1.1 MIPI链路上的RAW数据长什么样

MIPI CSI-2是串行协议,数据以包为单位在D-PHY或C-PHY物理链路上传输。每个包有包头、载荷和包尾,包头里的数据类型字段(Data Type,简称DT)负责告诉接收端,载荷里装的到底是YUV422还是RAW Bayer,位深是多少。RAW数据的常见位深包括RAW6、RAW7、RAW8、RAW10、RAW12、RAW14、RAW16。其中RAW8因为正好1字节一个像素,天然对齐,不存在打包问题。RAW10开始就麻烦了:10bit不是字节的整数倍,为了在物理链路上不浪费bit位,标准做法是把多个像素的bit连续拼凑起来。

RAW10的打包规则是4个像素拼成5字节。为什么是4个?因为4乘10bit等于40bit,正好5字节,这是能整除的最小组合,再往下3个像素30bit等于3.75字节,没法按字节封包。RAW12则是2个像素拼3字节,RAW14是4个像素拼7字节。这种把有效bit紧密排列的传输格式就是packed。打个比方,这就像几个人合住一间房,按床位分配空间,利用率高,但你要找某个人的东西就得在一堆行李里翻。

链路侧还有一个容易踩的坑:不同版本规范的DT编号不一样。比如内核驱动里常见的定义中,RAW8对应0x2B,RAW10对应0x2C,RAW12对应0x2D,RAW14对应0x2E。但老的sensor datasheet里可能写的是其他编号,甚至有些厂商把RAW10 unpacked单独编了一个DT码。实际开发一律以sensor手册和控制器驱动里的枚举为准,不要拿着记忆里的编号套所有平台。

1.2 内存里的unpacked到底是什么

当MIPI控制器把包解析完,payload会交给DMA写进DRAM。这时候数据到底以什么方式排列,取决于DMA控制器,而不是MIPI链路本身。如果DMA直接原样搬运,内存里保存的就是packed格式;如果DMA在做搬运时顺带做了像素解包,每个10bit像素被扩展成16bit,内存里就是unpacked格式,低10位是有效数据,高6位一般补0。

unpacked的存储特点可以概括为:每个像素独立对齐到一个固定的字节容器里。10bit和12bit像素最常见的容器是2字节,少数老平台会放到4字节里。这样做的直接好处是CPU、DSP、ISP在处理时可以一次load一个像素,不需要做位移拼接。坏处也直观:存储容量和带宽都会明显增加。

说一个容易混淆的点:有很多sensor直接输出unpacked格式走MIPI链路,此时DT可能仍然是RAW10,但packet payload里的bit不是紧密排列的,而是每个像素占固定bit数或者字节数。这种场景在工业相机里很常见。所以拿到一帧数据,不能只看DT判断内存排布,链路格式和内存格式要分别确认,这是我反复强调的核心。

2. 一本账看清存储差异:带宽、内存、对齐全量化

理解了定义,接下来用数字说话。不同位深的packed和unpacked在数据量上的差距有多大,会影响系统设计时选链路条数、DDR带宽、buffer大小,值得认真算一笔。

2.1 不同位深下每像素占用量对比

位深packed每像素字节数unpacked常见容器字节数1080p单帧packed数据量1080p单帧unpacked数据量unpacked相对packed增幅
RAW8112,073,600 B2,073,600 B0%
RAW101.2522,592,000 B4,147,200 B60%
RAW121.523,110,400 B4,147,200 B33%
RAW141.7523,628,800 B4,147,200 B14%

以1920乘1080的RAW10为例,packed一帧数据量是1920乘1080乘1.25等于2,592,000字节,unpacked是1920乘1080乘2等于4,147,200字节。差距1,555,200字节,换算成30fps实时流,带宽从77.76MB/s直接涨到124.42MB/s。D-PHY链路上每lane的带宽本来就要精打细算,这一下多出46.66MB/s,很可能逼着你多开一条lane。所以我见过很多项目在选型阶段就把RAW10定成packed传输,目的就是省MIPI lane。

2.2 内存带宽与系统性能的连锁反应

数据量增加不只是占存储空间,更直接影响DDR带宽。四路摄像头同时采集的场景里,一路RAW10从packed切到unpacked,每帧多出1.5MB数据,四路就是6MB,30fps就是180MB/s额外写入量。DDR总带宽是共享的,CPU跑算法、GPU做渲染、显示控制器扫屏都在抢这部分资源。之前某多路采集系统就是这样,某一路改成unpacked后,主核跑计算任务时周期性卡顿,perf一查,DDR write带宽被camera pipeline吃掉了将近一半,只能把unpacked改回packed才解决。

有人可能会说,那就让DMA在传输时保持packed,算法处理时再临时解包,这样链路省带宽,内存省空间。没错,这是很多移动平台的默认做法。代价是需要额外的解包开销,要么放在DMA硬件里做,要么占用CPU。后续章节会讲这两条路怎么选。

2.3 行宽与stride对齐:最容易忽略的坑

数据量不是唯一要关心的,对齐规则才是debug噩梦的来源。MIPI payload按字节对齐,RAW10一行数据在链路层按4像素一组展开,行宽如果正好是4的倍数,字节数是整数;如果不是,就会产生多余的bit。多数sensor通过内部裁切或忽略处理来避免这种尴尬,但作为接收方你不能假设行宽一定整除。

DMA写入内存时,硬件对行起始地址有对齐要求,常见是32字节或64字节。假设你开一个1200宽度的ROI,RAW10 packed一行的有效字节是1200乘1.25等于1500字节,但DMA要求32字节对齐,stride会被拉大到1504字节,末尾多4字节填充。如果读取端没有按stride跳行,而是按有效字节数依次读,第二行起就会错位4字节,之后每行累加错位,画面呈现典型的斜向横纹,和sensor输出坏点非常像。

这类问题最好的排查方法是打印每一行起始地址或者用工具查看buffer的线性排列,确认stride跟有效宽度到底差多少。我在实际调试中,这个坑遇到过不止三次,每次都被当成sensor时序问题查半天,其实只要把格式配置里的stride字段改成DMA要求的值就正常了。

2.4 选型逻辑:什么场景该用哪种

不存在绝对正确的存储方式,只看瓶颈在哪里。移动端SoC的摄像头pipeline,MIPI lane数量和DDR带宽都紧张,普遍采用packed传输、硬件解包后以unpacked存储到内存,兼顾链路节省和后续处理速度。工业相机或医疗设备,如果分辨率低、帧率要求不高,CPU拿到数据只想赶紧算,直接配置sensor输出unpacked,链路上也不打包,省掉一层转换。还有些系统内存非常充裕、MIPI lane数固定,这时候unpacked链路的简洁性更值得,因为不需要在CSI控制器里配像素转换寄存器,软件复杂度更低。

3. 从链路到内存的格式转换:硬件解包与软件解包

格式在链路和内存之间发生转换,中间是有真实硬件在干活的。搞明白这条通路,你才能知道问题到底出在哪一层。我习惯把它拆成三步:sensor把RAW发出来,CSI控制器收包解析,DMA按配置写内存。任何一步理解错,图像都会坏。

3.1 一条完整的RAW数据通路

Sensor输出的RAW数据经过MIPI D-PHY或C-PHY物理层,进入CSI-2控制器。控制器做协议解析,包括ECC校验、CRC校验、把串行bit恢复成字节,再把payload交给下游。下游有两种常见架构:一种是CSI和ISP内联,ISP直接消费payload,内存里只留ISP处理后的图像或者解包后的raw副本;另一种是CSI后面只挂DMA,数据原样写进DDR,由软件决定如何解释。第二种架构下,内存里的布局就完全由DMA配置决定。

我调过的某平台,CSI控制器支持一种raw dump模式,专门用来把sensor原始数据存到内存给调试工具用。这个模式里有个像素格式选择字段,写成RAW10 packed,DMA就把5字节一组原样搬运;写成RAW10 unpacked,DMA内部做位扩展,把4像素5字节展开成4像素8字节。第一次听说这个功能的人经常以为链路DT会跟着变,其实DT还是同一个,变的只是DMA的写内存策略。

3.2 硬件自动解包的原理与配置

硬件解包本质上是个位重排电路。以RAW10为例,DMA收到5字节,要把其中4个像素分别提取出来,每个像素扩展成16bit,高6位补0,然后连续写8字节。这个过程完全可以在搬运的同时做,不额外增加DDR读写次数,代价只是芯片面积和功耗增加一点。所以很多移动芯片都内置这个功能,驱动里往往有一个类似pixel_store_format的寄存器字段。

配置这类寄存器时,最容易翻车的是把链路的DT和store format搞混。DT是sensor层面描述payload含义的,store format是CSI控制器描述自己怎么落地的。两个独立配置,一错就出现"链路明明对,内存全乱"的场面。我建议每调一个新平台,先把这两项单独列出来,跟sensor手册和SoC用户手册逐字核对一遍,不要想当然。

3.3 软件解包代码:RAW10 packed转unpacked

硬件不支持解包,或者你想脱离平台在PC上处理裸数据时,就需要软件解包。下面这段C代码是RAW10 packed转unpacked的常用实现,前提是位序约定和标准MIPI规范一致,即每个像素的高8bit在前、低2bit在后,一组负载按5字节连续排布。

static void raw10_packed_to_unpacked(const uint8_t *src, uint16_t *dst, int width) { int i = 0; while (i + 3 < width) { uint8_t b0 = src[0]; uint8_t b1 = src[1]; uint8_t b2 = src[2]; uint8_t b3 = src[3]; uint8_t b4 = src[4]; dst[0] = (uint16_t)((b0 << 2) | (b1 >> 6)); dst[1] = (uint16_t)(((b1 & 0x3f) << 4) | (b2 >> 4)); dst[2] = (uint16_t)(((b2 & 0x0f) << 6) | (b3 >> 2)); dst[3] = (uint16_t)(((b3 & 0x03) << 8) | b4); src += 5; dst += 4; i += 4; } /* 剩余不足4个像素时,按实际需求处理,通常丢弃或补零 */ while (i < width) { dst[i] = 0; i++; } }

这段代码看起来简单,但位运算的细节一个都不能错。b0是高8bit,直接放到结果的高8位;b1里高6bit属于第一个像素的低2位?不对,反过来,b1的高2位是第一个像素的低2位,其余6位是第二个像素的高6位。所以dst[0]等于b0左移2位,加上b1右移6位,这个右移拿到的正是b1的高2位。后面三个像素同理,只是移动量逐级递减。如果你最终调试出来的图像颜色顺序不对,先检查位序是LSB first还是MSB first,再决定是否把所有位移方向反过来。

3.4 反向转换:unpacked回packed

有时候你会遇到反过来的需求:算法处理完了,想把unpacked RAW重新打包传给下游。逆操作的逻辑正好是上面解包的倒放,把4个16bit像素的低10位重新拼接成5字节。

static void raw10_unpacked_to_packed(const uint16_t *src, uint8_t *dst, int width) { int i = 0; while (i + 3 < width) { uint16_t p0 = src[0] & 0x03ff; uint16_t p1 = src[1] & 0x03ff; uint16_t p2 = src[2] & 0x03ff; uint16_t p3 = src[3] & 0x03ff; dst[0] = (uint8_t)(p0 >> 2); dst[1] = (uint8_t)(((p0 & 0x03) << 6) | (p1 >> 4)); dst[2] = (uint8_t)(((p1 & 0x0f) << 4) | (p2 >> 6)); dst[3] = (uint8_t)(((p2 & 0x3f) << 2) | (p3 >> 8)); dst[4] = (uint8_t)(p3 & 0xff); src += 4; dst += 5; i += 4; } }

和packed转unpacked一样,这个函数对位序极其敏感。如果你是在某个具体平台上跑,强烈建议先用一张已知像素值的测试图验证,我一般会先构造全0、全1023、0到1023渐变三张图跑一遍,输出和预期一致才放心大批量处理。

3.5 Linux V4L2里如何区分packed与unpacked

在Linux下做camera驱动,格式命名更直接。链路侧用media bus format,比如10bit Bayer有SRGGB10_1X10和SRGGB10_2X8_PADHI_LE两种,前者是packed,后者是unpacked小端且高位补0。内存侧用V4L2 pixel format,SRGGB10P是packed,SRGGB10是unpacked。两套命名对应两个不同配置点,配置时必须保证上下游一致。

我调过的某平台sensor驱动,sensor侧上报的bus format写成SRGGB10_2X8_PADHI_LE,也就是unpacked链路,但CSI DMA侧配置成了输出packed到内存。结果图像看着像蒙了层噪声,每个像素的高6bit全是随机垃圾。当时还怀疑sensor寄存器配错,后来用喂值法把2乘2像素的已知图像发进去,才发现总线格式和内存格式根本没对应上。这件事之后,我在所有camera驱动代码里都养成了一个习惯:链路格式和内存格式分开打印,注册时严格校验匹配关系,不匹配直接报错。

4. 实操:把packed raw切到unpacked的四个关键步骤

如果你接到一个任务,要让某颗sensor从默认的packed RAW10改成unpacked输出,或者反过来,让CSI DMA从packed解包改成原样搬运,下面这套流程基本通用。每一步都有检查点,防止把问题拖到下一个环节。

4.1 第一步:确认sensor输出模式与DT编号

先在sensor datasheet里找RAW格式输出寄存器,确认它支持的输出模式。有的sensor只支持packed,有的支持两种,靠寄存器bit切换。同时把MIPI packet里带出的DT编号记下来。判断标准很简单:如果sensor手册里写明MIPI DT是RAW10,且没有提供unpacked寄存器开关,那无论内存里想用什么格式,链路侧都是packed,剩下的事情交给DMA去做解包。

还有一种容易搞混的情况:sensor手册里写"RAW10 unpacked,每像素2字节",这时DT仍然可能是RAW10,但packet payload不再是紧密排列,每个像素占2字节或者按2字节对齐。这种模式对MIPI lane带宽极不友好,所以很少在手机camera里用。遇到这种描述,你反而要确认CSI控制器能不能识别这种payload,有些老控制器不支持,会按照packed去读,整帧画面必花。

4.2 第二步:配置DMA像素存储格式

确认sensor输出后,打开SoC的CSI控制器驱动,找到像素存储格式配置项。通常会有packed、unpacked、半打包之类的选项。这里的单词可能叫raw_store_mode、pixel_width、fmt_store等等,不同平台命名差异很大,但概念一致。你要想清楚到底想从这一步开始让内存变成什么布局,而不是简单照搬sensor侧的格式。

配置完成后,抓一帧raw,用十六进制编辑器直接看buffer头几个字节。如果sensor输出的是纯灰阶图,理论上所有像素值应该接近同一个数。如果看到每5字节里出现了明显的规律性空位,比如每个10bit像素后面跟着6bit零,说明DMA把unpacked写出来了,而sensor给的还是packed,中间一定有某个转换被跳过。反过来也一样,内存看起来紧密排列但预期是unpacked,就能定位到DMA没有做解包。

4.3 第三步:用工具链验证media bus和V4L2格式

在Linux环境里,我习惯用media-ctl直接看sensor subdev输出的当前bus format,再用v4l2-ctl查capture节点的像素格式。两条命令的输出会被很多新手忽略,但它们一对比,就能看出链路和内存两个层面有没有对齐。如果sensor那边显示SRGGB10_1X10,capture这边却是V4L2_PIX_FMT_SRGGB10,那中间必然存在某个转换层,要么是DMA解包,要么是CSI驱动内做了一次格式翻译。你确认这个转换逻辑确实存在且配置正确,才能继续往下走。

有些驱动会在set_format回调里自动把sensor的bus format翻译成对应的V4L2 pixel format,翻译表做得不完整时,会出现一个能设一个报错的情况。遇到这种,"格式链验证"就不是简单的命令查看,而是要翻驱动的格式映射表,看看有没有遗漏的位深组合。

4.4 第四步:抓帧验证字节序和像素值

格式链没问题,不代表bit顺序一定对。强烈建议用黑白半图卡或者纯色画面做最终验证。抓回raw后,先看左上角第一个像素值是否接近预期,再看整幅图像是否还有左右颠倒、上下颠倒、颜色通道错乱的现象。字节序反了的典型表现是:图像看起来有错位,但raw buffer里每个像素的数值转移位置后完全正常。

我之前调试一颗500万sensor时,默认配置出来整幅画面每两列就有一条黑线,怎么看都像sensor行拼接问题。后来把抓到的raw按每像素2字节展开,发现每两个有效像素中间夹着一个固定值0x00C0,这根本不是图像数据,而是DMA把unpacked当成packed输出后残留的填充。改完DMA的存储格式配置,一条黑线都没有了。备查的小技巧:只看像素均值是看不出问题的,把某一行数据打印出来当文本看,任何规律性插入都是配置错误的线索。

5. 花屏、错位、噪点:常见问题与排查实录

做camera调试,格式问题导致的故障表现有规律可循。看到特定现象,先往特定方向查,能省很多时间。下面把常见问题按现象归类,附上排查优先级。

5.1 现象一:斜向条纹,整个画面像"歪"了

这种通常不是sensor坏,而是接收端按错误的stride读数据。每一行实际比预期宽了几个字节,导致下一行起始位置偏移,越往下偏移越多,形成斜纹。排查顺序:先确认DMA的stride配置和图像宽度算出来的字节数是否一致,比如你是不是用1200像素去读而DMA配的是1500字节的stride,两者不匹配。其次确认sensor输出的行有效像素是否包含水平消隐,很多sensor datasheet里有效宽度并不等于整行长度,驱动要用包含消隐的总长度去算stride。

5.2 现象二:整幅图像像蒙了一层雪花噪声

每个像素的高位或低位出现随机值,这种大多是packed/unpacked解包错位。链路传输的是packed,但内存按unpacked读,或者DMA解包做的位扩展方向和实际bit序相反。先打印一行raw数据,数一下每几个字节构成一个像素。如果发现每5字节对应4像素的规律,基本就是packed格式;每2字节对应1像素就是unpacked。然后看这2字节里高6位是不是全0,如果不为0且没有规律,说明解包位序错了或补位方向反了。

5.3 现象三:左侧边缘或者每行开头出现半像素错位

这种多在RAW10或者RAW12下出现。MIPI包里的行payload可能不是从sensor第一个有效像素开始,而是包含几个用于对齐的dummy像素,过去我们在解析时直接忽略了。但DMA搬运时不会自动跳过,会把dummy也当作有效数据写进内存,于是每行开头就多了几个像素,图像看起来像每行左移了一点。解决方法是在驱动的行配置里设置像素偏移量,或者把sensor的水平尺寸配大,再在内存侧做裁剪。

5.4 快速确认packed/unpacked的土办法

不用示波器,也不用高端仿真器,打印第一行前20个像素的十六进制值,就能判断当前内存布局。假设画面是均匀灰卡,RAW10有效值理论上应该集中在某个小区间。如果前20个值都是0xFF、0xFC这种高8位连续的数,且每5字节一组的字节数严格等于4像素,那是packed。如果每2字节一组、低10位有效、高6位全0,那是unpacked。如果完全看不出规律,先检查DMA是否被错误配置,再检查sensor是否真的在输出RAW而不是YUV。

5.5 避坑速查表

检查项正确做法常见错误
链路DT以sensor手册和驱动枚举为准凭记忆认定某个编号
行stride用DMA对齐要求后的值直接用有效像素字节数
字节序用已知图像验证LSB/MSB方向默认一定是大端
补位方向unpacked高位补0还是低位补0要确认不分PADHI/PADLO
media bus与V4L2格式链路、内存分别确认并匹配只配一边
水平消隐像素总数包含消隐只算可见像素

个人经验里,大部分RAW格式问题最终都能归结为某层配置和实际数据不对应。不要一上来怀疑sensor,先花10分钟验证内存帧的字节规律,经常比翻半天datasheet更高效。

最后再分享一个我目前所有camera调试都会做的动作:拿到一颗新sensor或新平台,先抓一帧已知灰阶图,按上面说的方式把整个buffer的字节规律看一遍,再决定要不要继续调别的寄存器。这比直接对着文档猜格式靠谱得多。格式链理顺了,后面算法处理、ISP调参都会顺畅一大截。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询