Milk-V Duo S 使用Cvitek MFF硬件解码MP4改进

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 的格式并设为默认,可以直接使用 memcpyrgb888的原始数据拷贝到 /dev/fb0 中而不需要额外处理。
  • 修改之前的 sample_my_dec.c 代码

    使用 POSIX 定时器进行码流发送和向fb写入每帧的速度控制,修改写入的逻辑,减少 CVI_SYS_Mmap 的重复使用次数。

二、使能spi dma传输

直接修改build/boards/default/dts/cv181x/cv181x_base.dtsi下相关代码

  • 修改sysdma_remapch-remap第3个和第4个位置修改为CVI_SPI3_RXCVI_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,
};

修改后源码:https://github.com/PrettyMisaka/duo-buildroot-sdk/blob/develop/linux_5.10/drivers/gpu/drm/tiny/ili9488.c。

四、修改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的相关驱动源码和寄存器手册,收获良多(。

下次可以考虑学习音频方面解码的操作,和视频解码串起来。