1408|11

381

帖子

0

资源

一粒金砂(高级)

基于51单片机的简易“视频播放器” [复制链接]

 
本帖最后由 hjl240 于 2022-5-3 20:21 编辑

前言

大概10年前,我在咱们EEWORLD论坛看到有人分享了用单片机在液晶屏上播放bad apple视频,当时觉得很有趣,但是奈何当时刚入门单片机,很多都不太懂,自己想做却不会做,只能作罢。如今,工作也好几年了,也不做嵌入式这一行。前段时间,趁着周末,工作也不太忙,突然想起这个事情了,于是从网上买了元器件,自己动手再实现这个功能,以弥补自己当年的遗憾,哈哈。

 

介绍

本文介绍在51单片机上,使用OLED12864(SSD1306)播放视频,并且使用蜂鸣器播放音乐。

因为是gif的原因,看着会比较卡,实际上是不会有卡顿的,实际效果可以看文末的视频链接

最终的效果如下:

播放bad apple视频:

bad_apple_2.gif

播放数码宝贝的视频:
smbb_2.gif  

使用到的主要元器件如下:

  • 国产51单片机:STC15F2K60S2

  • OLED显示屏:SSD1306,分辨率为128*64

  • 无源蜂鸣器,8550三极管等

 

原理图如下:

 

原理图.png  

具体方案

由于视频文件比较大(MB级别),而51单片机的flash一般都比较小(KB级别),因此把视频文件直接存储在单片机内部显然是不行的。可以把视频文件存储在SD卡里面,然后单片机读取SD卡里面的内容;或者视频文件直接存储在电脑上,然后电脑通过串口实时发送视频数据给单片机,单片机实时显示视频画面。文本采用后者的方案。

 

OLED12864绘图

我们购买OLED12864(SSD1306)显示屏时,一般卖家都会提供51单片机的示例代码,或者网上也能找到很多相关的代码。

使用这些代码在整个屏幕上绘图时,发现刷新率比较低,在11.0592M时钟频率的情况下,实测大概只有8.6fps。测试方法如下:

void main()
{
	for(;;)
	{
		p27 = ~p27;
		oled_drawbmp(pic);
	}
}

oled_drawbmp为卖家提供的绘图的函数,每次屏幕刷新一次,p27 IO口翻转一次。使用逻辑分析仪测试p27的电平变化如下,可以看到频率约为4.3Hz,那么屏幕的刷新率大概为8.6Hz(fps)。
  测试结果1.png  

因为我们的目的是使用单片机在这款显示屏上播放视频,而一般视频的帧率需要大于25fps,帧率过低就会有卡顿的感觉。显然,上面提到屏幕8.6Hz的刷新率是比较低的,因此我们需要做一些优化。

比较直观且容易的优化方式之一,就是提高时钟频率,把11.0592M提高到24M或者27M的时钟频率。

第二个优化方法就是优化绘图函数。

先来看看iic的开始信号和结束信号的代码:

void iic_start() //开始iic
{
	scl = 1;
	sda = 1;
	delay_5us();
	sda = 0;
	delay_5us();
	scl = 0;
}
void iic_stop() //停止iic
{
	scl = 0;
	sda = 0;
	scl = 1;
	delay_5us();
	sda = 1;
	delay_5us();
}

可以看到,里面有一些延时5微妙(delay_5us),其实这个不是必须的,去掉这个延时,iic同样可以正常通信。因此,去掉这个延时,可以加快显示屏的刷新速率。

 

其次,我们再看看卖家提供绘图部分的函数:

/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y)*****************/
void oled_drawbmp(unsigned char bmp[]) //画图
{
	unsigned int j = 0;
	unsigned char x, y;
	for (y = 0; y < 8; y++)
	{
		oled_set_pos(0,  y);
		for (x = 0; x < 128; x++)
		{
			iic_writedata(bmp[j++]);
		}
	}
}

而iic_writedata的实现如下:

void iic_writedata(unsigned char iic_data) //写数据
{
    iic_start();
	write_byte(0x78);
	write_byte(0x40);
	write_byte(iic_data);
	iic_stop();
}

可以看到,每写一次图像数据bmp[j],都会有一次iic开始与结束动作,也都会先发送两个控制指令(0x78, 0x40),这其实没有必要,优化后的函数如下:

// 快速绘制图像
void oled_drawbmp_fast(unsigned char BMP[])
{
	unsigned int j = 0;
	unsigned char x, y;
	for (y = 0; y < 8; y++)
	{
		oled_set_pos(0, y);
		iic_start();
		write_byte(0x78);
		write_byte(0x40);
		for (x = 0; x < 128; x++)
		{
			write_byte(BMP[j++]);
		}
		iic_stop();
	}
}

可以看到,上面的函数减少了启动iic、结束iic,减少了写控制命令(0x78, 0x40)。使用跟之前同样的测试方法,经过上述的优化,最终屏幕的刷新率如下:

测试结果2.png  

上述的结果为,使用27M时钟频率,加上上面提到的几点优化,可以看出最终屏幕的刷新率约为34.5*2=69Hz(fps),这已经满足我们播放视频所需的屏幕刷新率了。

 

另外,再提一点,其实还可以进一步优化,使得屏幕刷新率达到100fps以上,测试结果如下。在这里卖个关子,感兴趣可以去B站看下,视频地址为:51单片机播放视频-原理介绍

测试结果3.png  

视频转码成十六进制格式

单片机播放视频,我们需要将视频转码为单片机可以读取的十六进制数据。

首先我们需要将视频分解为一帧一帧的图像,然后可以用如下的取模软件获得图像的十六进制字模。

 

取模软件.png  

但是,由于视频的帧数比较多,我们一帧一帧手动的使用取模软件获取字模,显然是一个比较累的活。因此,我们可以写个python代码,批量生成每帧画面的十六进制数据。python代码如下:

import cv2

def bit2num(pixcels):
   output_val = 0
   for i, pix in enumerate(pixcels):
      if pix > 128: # 白色
         output_val += pow(2, i)
   return output_val

def main():
   video_path = 'BadApple.flv'
   cap = cv2.VideoCapture(video_path) # 打开视频
   cnt = 0
   fout = open('bad_apple_data.txt', 'w') # 
   while True:
      ret, frame = cap.read()       # 一帧一帧的读取
      if not ret:
         break
      cnt += 1
      frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 转换为灰度的画面
      frame = cv2.resize(frame, (128, 64))  # 图像尺寸调整到128*64大小
      convert_val = []
      for row in range(0, 8):   # page0 ~ page7
         for col in range(0, 128): # seg0 ~ seg127
            cur_data = frame[row*8: row*8+8, col] # 取出对应的8个像素点
            convert_val.append(str(bit2num(cur_data))) # 转换成8位的数据
      
      fout.write("%s\n"%(','.join(convert_val)))

      # cv2.imshow("capture", frame)  #显示画面
      # if cv2.waitKey(30) & 0xff == ord('q'): #按q退出
      #    break

main()

代码比较简单,其中会调用OpenCV的库,用来读取视频以及视频画面对应的像素值。

 

串口发送

视频数据准备好之后,我们需要把视频数据通过串口发送给单片机,单片机接收到完整的一帧数据(一帧画面)之后,就可以开始显示画面。我们同样可以写个python代码来将视频数据发送给单片机,代码如下:

import serial  # 导入串口相关的库
from time import sleep

def get_bmp_data():
    filepath = 'bad_apple_data.txt'
    f = open(filepath)
    bmp_data = []
    for line in f:
        val = line.strip().split(',')
        if len(val) == 0:
            continue
        bmp_data.append([int(x) for x in val])
    f.close()
    return bmp_data

def main():
    com = serial.Serial('com10', 345600, timeout=10) # 设置端口号,波特率,超时时间
    if not com.isOpen():  # 判断端口是否打开成功
        raise "端口打开失败"

    bmp_data = get_bmp_data() # 读取刚刚生成的TXT文件
    for frame in bmp_data: # 一帧一帧的发送数据
        ret = com.write(bytes(frame)) # 将数据转换成二进制后发送
        sleep(0.03) # 延时适当时间

main()

代码比较简单,其中会调用串口相关的serial库,然后每次循环发送一帧数据,直至全部发送完成。

 

小结

最后,总结一下 在51单片机播放视频的大致流程:

  1. 视频解码成一帧帧的图像,然后再转码成显示屏可以显示的十六进制格式(这一步可以提前完成)

  2. 电脑通过串口把十六进制格式的视频数据发送给单片机

  3. 单片机接收到完整的一帧数据(一幅图像)后,显示屏开始显示画面

  4. 同时,蜂鸣器播放音乐(可选)

 

注:

想进一步了解细节的朋友,可以去B站看下我上传的视频,里面的介绍更加详细一点,也有最终的效果演示。

51单片机播放视频-原理介绍

51单片机之超级简易视频播放器

 

 

 

补充内容 (2022-6-11 09:38): 代码地址:https://gitee.com/hejunlin/stcmcu-play-video
此帖出自51单片机论坛

赞赏

1

查看全部赞赏


回复

1万

帖子

2853

资源

管理员

可以通过编辑器里的视频功能添加视频链接就可以直接看了


回复

1271

帖子

0

资源

版主

51能做到这么流畅,很不错了!


回复

2万

帖子

71

资源

管理员

这个帖子也见证了你10年的成长:)

个人签名

不管是哪年,都要加油!继续为中国电子行业做出小小的贡献吧! 扣扣 1206973913


回复

1038

帖子

0

资源

纯净的硅(高级)

有点厉害了,这算的上是最简单的方案实现了视频播放功能

不过还是需要借助计算机将视频解码并转换成16进制

楼主可以开发下播放SD卡的视频方法吗

点评

用51单片机直接解码MP4格式视频,然后实时播放,51单片机的性能似乎还不够,可以尝试下用性能更强的单片机试试  详情 回复 发表于 2022-5-7 21:56

回复

381

帖子

0

资源

一粒金砂(高级)

se7ens 发表于 2022-5-6 16:46 有点厉害了,这算的上是最简单的方案实现了视频播放功能 不过还是需要借助计算机将视频解码并转换成16进 ...

用51单片机直接解码MP4格式视频,然后实时播放,51单片机的性能似乎还不够,可以尝试下用性能更强的单片机试试


回复

2

帖子

0

资源

一粒金砂(初级)

学习


回复

74

帖子

0

资源

一粒金砂(中级)

大神 高手呀  虽然看不懂 但是继续学习基础知识  再来拜读

回复

5381

帖子

18

资源

五彩晶圆(中级)

51做这样流畅不容易

个人签名

默认摸鱼


回复

381

帖子

0

资源

一粒金砂(高级)


回复

211

帖子

15

资源

版主

我的思路是:在各种类型的屏幕上播放视频都是把视频切成很多张图片,每张图片转换成点阵,让屏幕每秒刷新30张。这个难就难在用有限的资源产生最好的效果。楼主厉害。


回复

3

帖子

0

资源

一粒金砂(初级)

学习谢谢!                                            


回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

最新文章 更多>>
    关闭
    站长推荐上一条 1/8 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表