Milk-V Duo S 使用Cvitek MFF硬件解码MP4改进
一、概述
上次使用Cvitek MFF硬件解码H264视频并播放的效果实在是不咋地,于是尝试进行改进,在几周后有了显著的提升,在这里进行一下思路分享。
上一篇文章 Milk-V Duo S 硬件解码H264并在LCD屏幕上显示。
这一次的演示视频 milkv duos 硬件解码mp4视频并在lcd屏上播放 改进。
主要提升方向
- 使能spi dma传输
使得在向/dev/fb0
写入数据后不会造成很严重的堵塞,让解码的图像按pts稳定播放成为可能。 - 修改tinydrm下的ili9488驱动
添加rgb888
的格式并设为默认,可以直接使用memcpy
将rgb888
的原始数据拷贝到/dev/fb0
中而不需要额外处理。 - 修改之前的 sample_my_dec.c 代码
使用POSIX
定时器进行码流发送和向fb写入每帧的速度控制,修改写入的逻辑,减少CVI_SYS_Mmap
的重复使用次数。
二、使能spi dma传输
直接修改build/boards/default/dts/cv181x/cv181x_base.dtsi
下相关代码
- 修改
sysdma_remap
在ch-remap
第3个和第4个位置修改为CVI_SPI3_RX
和CVI_SPI3_TX
。
CVI_SPI3_RX
不添加应该也行,因为用不到。
sysdma_remap {
compatible = "cvitek,sysdma_remap";
reg = <0x0 0x03000154 0x0 0x10>;
ch-remap = <CVI_I2S0_RX CVI_I2S2_TX CVI_SPI3_RX CVI_SPI3_TX
CVI_SPI_NAND CVI_SPI_NAND CVI_I2S2_RX CVI_I2S3_TX>;
int_mux_base = <0x03000298>;
};
- 去除spi3节点下的
#if 0
和#end
spi3:spi3@041B0000 {
compatible = "snps,dw-apb-ssi";
reg = <0x0 0x041B0000 0x0 0x10000>;
clocks = <&clk CV181X_CLK_SPI>;
#address-cells = <1>;
#size-cells = <0>;
dmas = <&dmac 2 1 1
&dmac 3 1 1>;
dma-names = "rx", "tx";
capability = "txrx";
};
- 给i2s1节点加上
i2s1: i2s@04110000 {
compatible = "cvitek,cv1835-i2s";
reg = <0x0 0x04110000 0x0 0x2000>;
clocks = <&i2s_mclk 0>;
clock-names = "i2sclk";
dev-id = <1>;
#sound-dai-cells = <0>;
#if 0
dmas = <&dmac 2 1 1 /* read channel */
&dmac 3 1 1>; /* write channel */
dma-names = "rx", "tx";
capability = "txrx";
#endif
mclk_out = "false";
};
在内核make menuconfig界面配置DesignWare SPI controller core support为y
或者直接在build/boards/cv181x/cv1813h_milkv_duos_sd/linux/cvitek_cv1813h_milkv_duos_sd_defconfig
里添加CONFIG_SPI_DW_DMA=y
,
我不确定这个是不是必要的,但应该是。
三、修改ili9488驱动
- 修改默认为24bit,大小端反转。
static int ili9488_probe(struct spi_device *spi){
......
- dbidev->drm.mode_config.preferred_depth = 16;
+ dbidev->drm.mode_config.preferred_depth = 24;
+ dbi->swap_bytes = !dbi->swap_bytes;
......
}
- 添加RGB888相关代码
static void ili9488_rgb888_to_rgb666(u8 *dst, void *vaddr,
struct drm_framebuffer *fb,
struct drm_rect *rect)
{
//一行中的像素数 linepixels
size_t linepixels = rect->x2 - rect->x1;
//源缓冲区每行长度
size_t src_len = linepixels * 3;
//目标缓冲区每行长度
size_t dst_len = linepixels * 3;
unsigned int y, lines = rect->y2 - rect->y1;
/*
* The cma memory is write-combined so reads are uncached.
* Speed up by fetching one line at a time.
*/
// y 0->320 x 0->480
vaddr += rect->y1 * fb->pitches[0] + rect->x1 * 3;
for (y = 0; y < lines; y++) {
memcpy(dst, vaddr, src_len);
vaddr += fb->pitches[0];
dst += dst_len;
}
}
static int ili9488_buf_copy(void *dst, struct drm_framebuffer *fb,
struct drm_rect *rect)
{
......
case DRM_FORMAT_RGB565:
ili9488_rgb565_to_rgb666(dst, src, fb, rect);
break;
+ case DRM_FORMAT_RGB888:
+ ili9488_rgb888_to_rgb666(dst, src, fb, rect);
+ break;
default:
dev_err_once(fb->dev->dev, "Format is not supported: %s\n",
drm_get_format_name(fb->format->format,&format_name));
......
}
static void ili9488_fb_dirty(struct drm_framebuffer *fb, struct drm_rect *rect)
{
......
if (!dbi->dc || !full ||
- fb->format->format == DRM_FORMAT_RGB565) {
+ fb->format->format == DRM_FORMAT_RGB565 ||
+ fb->format->format == DRM_FORMAT_RGB888 ) {
tr = dbidev->tx_buf;
ret = ili9488_buf_copy(dbidev->tx_buf, fb, rect);
if (ret)
......
}
static const u32 ili9488_formats[] = {
+ DRM_FORMAT_RGB888,
DRM_FORMAT_RGB565,
};
四、修改sample_my_dec.c代码
改动比较多,就直接放源码链接了:https://github.com/PrettyMisaka/cvitek-tdl-sdk-sg200x/blob/main/sample/cvi_tdl/sample_my_dec.c。
减少CVI_SYS_Mmap
重复使用主要是通过类似键值对的方式,将已经通过CVI_SYS_Mmap
转换后的虚拟地址与物理地址存储在键值对结构体上,然后对一个键值对结构体数组进行索引,这样可以减少CVI_SYS_Mmap
的使用次数。
添加了一些互斥锁和变量实现了对码流传输速度的控制和简单的快进,暂停,回退等功能。
五、后记
查阅手册得知sg2000的spi最高速率为46.875MHz,使用四线spi,ili9488主控480x320的LCD屏最大刷新率也就12.7帧,对于现在的25帧,30帧视频还是很难跑满的,还是需要换别的接口的显示屏。
不过相比第一版写的程序播放速度已经有显著提升,在没发现dma配置方法时也查看了许多spi,dma的相关驱动源码和寄存器手册,收获良多(。
下次可以考虑学习音频方面解码的操作,和视频解码串起来。