支持的芯片
概述
MJPEG 是一个集成了 JPEG 解码器和后处理器的解码器。JPEG 解码器和后处理器可以工作在独立模式或者组合模式。
在独立模式下,JPEG 解码器和后处理器独立工作:
JPEG 解码器将输入的基线交错式的 JPEG 格式的图片解码为 YCbCr 格式的数据并输出到内存。
后处理器可以处理内存中的 YCbCr 格式的数据,经过处理后,将 YCbCr 或者 RGB 格式的数据输出到内存。
在组合模式下,JPEG 解码器解码后的数据直接发送到后处理单元进行处理,然后将 YCbCr 或者 RGB 格式的数据输出到内存。
MJPEG 架构图如下:
JPEG 解码器
JPEG 解码器可以工作在独立模式或者和后处理器一起工作在组合模式。JPEG 解码器有以下特性:
输入格式:采用 YCbCr400, YCbCr420, YCbCr422, YCbCr440, YCbCr411, YCbCr444 采样格式的 基线交错式 的 JPEG 图片。
输出格式:
半平面光栅扫描式 YCbCr420、YCbCr422、YCbCr440、YCbCr411、YCbCr444 格式。
仅有亮度分量的 YCbCr400 格式
支持常规模式,Input buffer 模式和 Slice 模式三种工作模式。其中,slice 模式仅能工作在独立模式下。
备注
如果输入图片宽度和高度非 16 整数倍,JPEG 解码器将会向上取整,输出 16 整数倍的宽度和高度的分辨率的数据,多出来的部分,将在图片右边和下边填充 0 数据。
JPEG 解码器无法支持亮度分量和色度分量使用不同量化表的 JPEG 图片。
工作模式
JPEG 解码器可以工作在以下三种模式:
常规模式:JPEG 解码器将整张图片为输入,并解码成完整的 YCbCr 格式数据输出。使用方法参考示例 raw_jpegdec_standalone_normal (JPEG 解码器独立模式)和 raw_combined_normal (组合模式)。
Input Buffer 模式:JPEG 解码器将输入的图片分多次输入进行解码。这种模式可以节省输入缓冲区的大小,也可以减小因等待完整 JPEG 图片导致的解码延迟。使用方法参考 raw_combined_input_buffer (组合模式)。
Slice 模式:JPEG 解码器将整张图片作为输入,并分段解码成 YCbCr 格式数据输出。这种模式可以节省输出缓冲区的大小。使用方法参考 raw_jpegdec_standalone_slice (JPEG 解码器独立模式)。
备注
Slice 模式仅能工作在 JPEG 解码器独立模式下。
Slice 模式采用宏块的方法来分块输出。在示例 raw_jpegdec_standalone_slice 中,输入图片为 800x480 分辨率的 YCbCr422 格式的图片,在垂直方向上有 480 / 8 = 60 个宏块,如果 SliceMb 设为 10, 将分 6 次输出。
Input Buffer 模式采用 JPEG 图片大小除以缓冲区大小来计算解码次数。
API 参考
API |
描述 |
---|---|
JpegDecInit |
初始化 JPEG 解码器实例 |
JpegDecGetImageInfo |
初步进行软件解码并获取 JPEG 的图片信息 |
JpegDecDecode |
对 JPEG 图片进行硬件解码 |
JpegDecRelease |
释放 JPEG 解码器实例 |
后处理器
后处理器可以工作在独立模式或者和 JPEG 解码器一起工作在组合模式。
功能特性
后处理器支持多种图像处理功能:
输入数据格式
后处理器的输入数据来源可以:
内存 (独立模式) 的特定格式的 YCbCr 格式数据。
JPEG 解码器 (组合模式) 输出的任意格式数据。
独立模式下,后处理器支持的数据格式如下:
半平面光栅扫描式 YCbCr420
半平面块状布局 (16x16) YCbCr420
平面 YCbCr420
YCbYCr 4:2:2, YCrYCb 4:2:2
CbYCrY 4:2:2, CrYCbY 4:2:2
输出数据格式
后处理器支持以下输出数据格式:
半平面 YCbCr420
YCbYCr 4:2:2 逐行扫描或 4x4 块状布局
YCbYCr 4:2:2 逐行扫描或 4x4 块状布局
YCbYCr 4:2:2 逐行扫描或 4x4 块状布局
YCbYCr 4:2:2 逐行扫描或 4x4 块状布局
RGB 32 位
RGB 16 位
对于 RGB 输出格式,alpha 通道和颜色通道的位宽和位置可配,因此可以配置成 ARGB8888,RGB565,ARGB4444 等颜色格式。
旋转
后处理器支持 90, 180, 270 度的旋转和垂直,水平的翻转。

备注
后处理器仅在独立模式下支持旋转和翻转,且无法使用后解码器其他特性,使用方法参考 raw_pp_standalone_rotation。
缩放
后处理器的缩放功能使用四抽头水平核和两抽头垂直核的双立方多项式插值,实现良好的缩放效果。缩放功能有以下限制:
只能实现同方向上缩放,即要么横纵方向都放大,要么都缩小。
放大倍数最高为 3。
颜色转换标准
后处理器在将 YCbCr 格式数据转换为 RGB 格式数据时,可以选用多种标准:
BT.601-5 规范
BT.709 规范
用户自定义转换参数
转换的公式如下,用户需要提供参数 a,b,c,d,e。
图像增强
后处理器提供去抖动,亮度调整,饱和度调整,对比度调整这 4 项图像增强功能。
裁剪和画中画
后处理器可以对原图进行矩形裁剪,可以选择裁剪的宽度和高度以及坐标。也可以将原图输出到一张更大的背景图中,将原图作为背景图的一部分,实现画中画功能。
遮蔽区域和 alpha 混合
后处理器提供了两个矩形的遮蔽区域,这两个遮蔽区域可以:
放在原图的任意位置
可以选择为遮蔽功能,即原图在遮蔽区域不输出。
可以选择为 alpha 混合功能,即可以将原图和输入的数据进行 alpha 混合后输出,原图作为底层。
下图是一个示例,mask2 作为遮蔽区域,将原图部分遮蔽,露出背景图底色。mask1 作为 aplha 混合区域,将输入的数据和原图进行 aplha 混合。

备注
alpha 混合的上层数据来源于内存,它的格式只能是:
8 位 alpha 值 + YCbCr444 (每个分量 8bit)
ARGB8888
后处理器参数的可整除性要求
参数 |
YCbCr420 |
YCbCr422 |
RGB 16位 |
RGB 32位 |
---|---|---|---|---|
输入图片宽高 |
16 |
16 |
16 |
16 |
裁剪图片宽高 |
8 |
8 |
8 |
8 |
裁剪图片坐标 |
16 |
16 |
16 |
16 |
输出图片宽度 |
2 |
2 |
1 |
1 |
输出图片高度 |
2 |
1 |
1 |
1 |
遮蔽区域宽度和 X 坐标 |
2 |
2 |
1 |
1 |
遮蔽区域高度和 Y 坐标 |
2 |
1 |
1 |
1 |
背景图区域的宽度和图片在背景图上的 X 坐标 |
8 |
4 |
4 |
2 |
背景图区域的高度和图片在背景图上的 Y 坐标 |
2 |
1 |
1 |
1 |
API 参考
API |
描述 |
---|---|
PPInit |
初始化 后处理器 实例 |
PPDecCombinedModeEnable |
使能组合模式 |
PPGetConfig |
初始化 后处理器 配置 |
PPSetConfig |
设定 后处理器 配置 |
PPGetResult |
获取 后处理器 处理结果 |
PPDecCombinedModeDisable |
关闭组合模式 |
PPRelease |
释放 后处理器 实例 |
PPConfig
结构体
通过此结构体来配置后处理器的各种功能。
成员 |
应用 |
---|---|
PPInImage ppInImg; |
输入图片参数 |
PPInCropping ppInCrop; |
输入图片裁剪参数 |
PPInRotation ppInRotation; |
输入图片旋转参数 |
PPOutImage ppOutImg; |
输出图片参数 |
PPOutRgb ppOutRgb; |
输出图片 RGB 参数 |
PPOutMask1 ppOutMask1; |
遮蔽区域 1 参数 |
PPOutMask2 ppOutMask2; |
遮蔽区域 2 参数 |
PPOutFrameBuffer ppOutFrmBuffer; |
写帧缓冲区参数 |
应用示例
SDK 提供应用示例,帮助开发者了解和使用 MJPEG 功能:
raw 示例
路径:
{SDK}\component\example\peripheral\raw\MJPEG\{demo}
展示如何使用 MJPEG
以下是对示例功能的简要说明:
raw_jpegdec_standalone_normal : 演示 JPEG 解码器工作在独立模式下的普通模式下,将一张 JPEG 图片解码为 YCbCr 格式数据。
raw_jpegdec_standalone_slice : 演示 JPEG 解码器工作在独立模式下的 slice 模式下,将一张 JPEG 图片解码为 YCbCr 格式数据。
raw_pp_standalone_rotation : 演示后处理器工作在独立模式下,将一张 YCbCr 格式数据旋转 90 度。
raw_combined_normal : 演示在合作模式下,将一张 JPEG 图片解码为 RGB 32 位数据,不做对图片本身做任何处理。
raw_combined_multi_function : 演示在合作模式下,将一张 JPEG 图片解码为 RGB 32 位数据,对图片做裁剪,缩放,掩盖区域,alpha 混合,调整亮度,画中画等处理。
raw_combined_input_buffer : 演示在合作模式下,JPEG 解码器工作在 input buffer 模式,将一张 JPEG 图片解码为 RGB 格式数据。
示例解析
示例 raw_combined_normal
void combined_normal(void) { JpegDecInst jpegInst = NULL; JpegDecRet jpegRet; JpegDecImageInfo imageInfo; JpegDecInput jpegIn; JpegDecOutput jpegOut; JpegDecLinearMem input; PPInst pp = NULL; PPResult ppRet; PPConfig pPpConf; _memset(&jpegIn, 0, sizeof(JpegDecInput)); _memset(&jpegOut, 0, sizeof(JpegDecOutput)); _memset(&imageInfo, 0, sizeof(JpegDecImageInfo)); _memset(&pPpConf, 0, sizeof(PPConfig)); RCC_PeriphClockCmd(APBPeriph_MJPEG, APBPeriph_MJPEG_CLOCK, ENABLE); /* 使能 MJPEG 时钟 */ hx170dec_init(); /* 初始化底层驱动 */ jpegRet = JpegDecInit(&jpegInst); /* 初始化 JPEG 实例 */ if (jpegRet != JPEGDEC_OK) { RTK_LOGE(TAG, "JpegDecInit error: %d\n", jpegRet); goto end; } else { RTK_LOGI(TAG, "JpegDecInit OK\n"); } input.pVirtualAddress = (u32 *) 422_jpeg; input.busAddress = (u32) 422_jpeg; jpegIn.streamBuffer.pVirtualAddress = (u32 *) input.pVirtualAddress; /* 配置 JPEG 图片地址 */ jpegIn.streamBuffer.busAddress = input.busAddress; jpegIn.streamLength = 422_jpeg_len; /* 配置 JPEG 图片大小 */ jpegIn.bufferSize = 0; /* 不使用 input buffer 模式 */ jpegRet = JpegDecGetImageInfo(jpegInst, &jpegIn, &imageInfo); /* 软件解码获取图片信息 */ if (jpegRet != JPEGDEC_OK) { RTK_LOGE(TAG, "JpegDecGetImageInfo error: %d\n", jpegRet); goto end; } ppRet = PPInit(&pp); /* 初始化后处理器实例 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPInit error: %d\n", ppRet); goto end; } ppRet = PPDecCombinedModeEnable(pp, jpegInst, PP_PIPELINED_DEC_TYPE_JPEG); /* 使能组合模式 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPDecCombinedModeEnable error: %d\n", ppRet); goto end; } ppRet = PPGetConfig(pp, &pPpConf); /* 初始化后处理器配置参数 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPGetConfig error: %d\n", ppRet); goto end; } pPpConf.ppInImg.width = imageInfo.outputWidth; /* 后处理的输入宽度配置成 JPEG 解码器输出的宽度 */ pPpConf.ppInImg.height = imageInfo.outputHeight; /* 后处理的输入高度配置成 JPEG 解码器输出的高度 */ pPpConf.ppInImg.pixFormat = PP_PIX_FMT_YCBCR_4_2_2_SEMIPLANAR; /* 图片配置成 422 格式 (也可以通过 JPEG 解码器输出的格式来判断) */ pPpConf.ppOutImg.width = pPpConf.ppInImg.width; /* 后处理的输出宽度配置和输入宽度一样 */ pPpConf.ppOutImg.height = pPpConf.ppInImg.height; /* 后处理的输出高度配置和输入高度一样 */ pPpConf.ppOutImg.pixFormat = PP_PIX_FMT_RGB32; /* 输出格式配置成 ARGB8888 */ pPpConf.ppOutImg.bufferBusAddr = (u32) rtos_mem_malloc(pPpConf.ppOutImg.width * pPpConf.ppOutImg.height * 4); /* 输出数据地址 */ if(pPpConf.ppOutImg.bufferBusAddr == NULL) { RTK_LOGE(TAG, "PP out memory malloc failed\n"); goto end; } ppRet = PPSetConfig(pp, &pPpConf); /* 将配置写入后处理器 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPSetConfig error: %d\n", ppRet); goto end; } jpegRet = JpegDecDecode(jpegInst, &jpegIn, &jpegOut); /* JPEG 解码器开始解码,解码的数据直接输入到后处理器处理后输出 */ if (jpegRet != JPEGDEC_FRAME_READY) { RTK_LOGE(TAG, "JpegDecDecode error: %d\n", jpegRet); goto end; } else { RTK_LOGI(TAG, "JpegDecDecode OK\n"); } ppRet = PPDecCombinedModeDisable(pp, jpegInst); /* 关闭组合模式 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPDecCombinedModeDisable error: %d\n", ppRet); goto end; } end: PPRelease(pp); /* 释放后处理器实例 */ JpegDecRelease(jpegInst); /* 释放 JPEG 解码器实例 */ }
示例 raw_combined_multi_function
void combined_multi_function(void) { JpegDecInst jpegInst = NULL; JpegDecRet jpegRet; JpegDecImageInfo imageInfo; JpegDecInput jpegIn; JpegDecOutput jpegOut; JpegDecLinearMem input; PPInst pp = NULL; PPResult ppRet; PPConfig pPpConf; u32 * frame_buffer; _memset(&jpegIn, 0, sizeof(JpegDecInput)); _memset(&jpegOut, 0, sizeof(JpegDecOutput)); _memset(&imageInfo, 0, sizeof(JpegDecImageInfo)); _memset(&pPpConf, 0, sizeof(PPConfig)); frame_buffer = (u32*)rtos_mem_malloc(FRAME_BUFFER_WIDTH * FRAME_BUFFER_HEIGHT * 4); for(int i = 0; i < FRAME_BUFFER_WIDTH * FRAME_BUFFER_HEIGHT; i++) { frame_buffer[i] = 0x009FE4D4; /* 配置背景色 */ } RCC_PeriphClockCmd(APBPeriph_MJPEG, APBPeriph_MJPEG_CLOCK, ENABLE); /* 使能 MJPEG 时钟 */ hx170dec_init(); /* 初始化底层驱动 */ jpegRet = JpegDecInit(&jpegInst); /* 初始化 JPEG 实例 */ if (jpegRet != JPEGDEC_OK) { RTK_LOGE(TAG, "JpegDecInit error: %d\n", jpegRet); goto end; } else { RTK_LOGI(TAG, "JpegDecInit OK\n"); } input.pVirtualAddress = (u32 *) jpeg_422; input.busAddress = (u32) jpeg_422; /* Pointer to the input JPEG */ jpegIn.streamBuffer.pVirtualAddress = (u32 *) input.pVirtualAddress; /* 配置 JPEG 图片地址 */ jpegIn.streamBuffer.busAddress = input.busAddress; jpegIn.streamLength = jpeg_422_len; /* 配置 JPEG 图片大小 */ jpegIn.bufferSize = 0; /* 不使用 input buffer 模式 */ jpegRet = JpegDecGetImageInfo(jpegInst, &jpegIn, &imageInfo); /* 软件解码获取图片信息 */ if (jpegRet != JPEGDEC_OK) { RTK_LOGE(TAG, "JpegDecGetImageInfo error: %d\n", jpegRet); goto end; } ppRet = PPInit(&pp); /* 初始化后处理器实例 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPInit error: %d\n", ppRet); goto end; } ppRet = PPDecCombinedModeEnable(pp, jpegInst, PP_PIPELINED_DEC_TYPE_JPEG); /* 使能组合模式 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPDecCombinedModeEnable error: %d\n", ppRet); goto end; } ppRet = PPGetConfig(pp, &pPpConf); /* 初始化后处理器配置参数 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPGetConfig error: %d\n", ppRet); goto end; } // set input pPpConf.ppInImg.width = imageInfo.outputWidth; /* 后处理的输入宽度配置成 JPEG 解码器输出的宽度 */ pPpConf.ppInImg.height = imageInfo.outputHeight; /* 后处理的输入高度配置成 JPEG 解码器输出的高度 */ pPpConf.ppInImg.pixFormat = PP_PIX_FMT_YCBCR_4_2_2_SEMIPLANAR; /* 图片配置成 422 格式 (也可以通过 JPEG 解码器输出的格式来判断) */ pPpConf.ppInImg.videoRange = 1; /* 配置动态范围 */ // set crop pPpConf.ppInCrop.enable = 1; /* 使能裁剪 */ pPpConf.ppInCrop.originX = 96; /* 裁剪的 X 坐标 */ pPpConf.ppInCrop.originY = 96; /* 裁剪的 Y 坐标 */ pPpConf.ppInCrop.width = 480; /* 裁剪的宽度 */ pPpConf.ppInCrop.height = 360; /* 裁剪的高度 */ //set output pPpConf.ppOutImg.pixFormat = PP_PIX_FMT_RGB32; /* 输出格式配置成 ARGB8888 */ pPpConf.ppOutRgb.rgbTransform = PP_YCBCR2RGB_TRANSFORM_BT_709; /* 配置颜色转换标准 */ pPpConf.ppOutImg.bufferBusAddr = (u32) frame_buffer; /* 配置背景图缓冲区地址 */ pPpConf.ppOutImg.width = 640; /* 配置后处理输出宽度,使用了缩放,480->640 */ pPpConf.ppOutImg.height = 400; /* 配置后处理输出高度,使用了缩放,360->400 */ // set brightness pPpConf.ppOutRgb.brightness = 100; /* 增加亮度 */ // set pip pPpConf.ppOutFrmBuffer.enable = 1; /* 使能画中画 */ pPpConf.ppOutFrmBuffer.frameBufferWidth = FRAME_BUFFER_WIDTH; /* 配置背景图宽度 */ pPpConf.ppOutFrmBuffer.frameBufferHeight = FRAME_BUFFER_HEIGHT; /* 配置背景图高度 */ pPpConf.ppOutFrmBuffer.writeOriginX = 100; /* 配置图片在背景图的 X 坐标 */ pPpConf.ppOutFrmBuffer.writeOriginY = 100; /* 配置图片在背景图的 Y 坐标 */ //set mask1 pPpConf.ppOutMask1.enable = 1; /* 使能遮蔽区域 1 */ pPpConf.ppOutMask1.originX = 16; /* 遮蔽区域 1 在图片中的 X 坐标 */ pPpConf.ppOutMask1.originY = 32; /* 遮蔽区域 1 在图片中的 Y 坐标 */ pPpConf.ppOutMask1.width = 48; /* 遮蔽区域 1 的宽度 */ pPpConf.ppOutMask1.height = 48; /* 遮蔽区域 1 的高度 */ pPpConf.ppOutMask1.alphaBlendEna = 1; /* 使能遮蔽区域 1 的 alpha 混合功能 */ pPpConf.ppOutMask1.blendComponentBase = (u32)mask1_data; /* alpha 混合数据区域的地址*/ pPpConf.ppOutMask1.blendOriginX = 0; /* 从 alpha 混合数据区域取数据的起始 X 坐标 */ pPpConf.ppOutMask1.blendOriginY = 10; /* 从 alpha 混合数据区域取数据的起始 Y 坐标 */ pPpConf.ppOutMask1.blendWidth = MASK1_WIDTH; /* alpha 混合数据区域的宽度 */ pPpConf.ppOutMask1.blendHeight = MASK1_HEIGHT; /* alpha 混合数据区域的高度 */ //set mask2 pPpConf.ppOutMask2.enable = 1; /* 使能遮蔽区域 2 */ pPpConf.ppOutMask2.originX = 300; /* 遮蔽区域 1 在图片中的 X 坐标 */ pPpConf.ppOutMask2.originY = 300; /* 遮蔽区域 1 在图片中的 Y 坐标 */ pPpConf.ppOutMask2.width = MASK2_WIDTH; /* 遮蔽区域 1 的宽度 */ pPpConf.ppOutMask2.height = MASK2_HEIGHT; /* 遮蔽区域 1 的高度 */ ppRet = PPSetConfig(pp, &pPpConf); /* 将配置写入后处理器 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPSetConfig error: %d\n", ppRet); goto end; } jpegRet = JpegDecDecode(jpegInst, &jpegIn, &jpegOut); /* JPEG 解码器开始解码,解码的数据直接输入到后处理器处理后输出 */ if (jpegRet != JPEGDEC_FRAME_READY) { RTK_LOGE(TAG, "JpegDecDecode error: %d\n", jpegRet); goto end; } else { RTK_LOGI(TAG, "JpegDecDecode OK\n"); } ppRet = PPDecCombinedModeDisable(pp, jpegInst); /* 关闭组合模式 */ if (ppRet != PP_OK) { RTK_LOGE(TAG, "PPDecCombinedModeDisable error: %d\n", ppRet); goto end; } end: PPRelease(pp); /* 释放后处理器实例 */ JpegDecRelease(jpegInst); /* 释放 JPEG 解码器实例 */ }
处理过程图示:
错误排查
图片右侧或下侧产生非预期的边缘模糊
现象 : 图片右侧或下侧产生非预期的边缘模糊或者黑边。
可能原因 : 图片长度和宽度不是 16 的整数倍,而 JPEG 解码器只能输出 16 整数倍向上取整的分辨率,多出来的边缘会进行填充。
- 解决方法 :
使用图像处理工具,将图片缩放为和原始分辨率最接近的 16 的整数倍分辨率。
使用后处理器的裁剪功能,将 JPEG 解码器输出的图裁剪为原尺寸,黑边将会被裁剪掉。