Bad Apple 播放效果视频
http://www.qustsatia.cn/2018/09/25/%E5%88%A9%E7%94%A8pyboardcn-v2%E6%92%AD%E6%94%BEbad-apple/
一.前言
前段时间较为忙碌,一直未来得及学习交流。这次利用pyboardCN V2自带的SD卡来存储图片解码文件,通过定时器读取像素点信息文件并通过串口将信息发送给显示部分来播放B站比较火的Bad apple。
本来打算直接利用板子的I2C通信播放图片。但是官方的SSD1306库不是很完善,播放帧数略微感人。当然本人使用刷屏的方式也确实不太合理导致延迟严重。故使用外置的stm32f103来进行专门的图片播放,来减缓pyboard的压力。
二.工具
一款舒服的文本编辑器,如notepad++。
视频截取工具。如Potplayer。
图片转换器,将MP4转换为2位像素图。可以用ps进行批处理操作,也可以用专门软件进行转换。本文使用pconverter。下载链接:http://www.zxt2007.com/imageconverter.html
图片取模软件。可以用OLED附赠的资料,但是手动截图有些麻烦。本人参考他人设计,利用matlab进行图片取模。参考文章:https://blog.csdn.net/howlclat/article/details/50668521
MDK,给播放设备编程。
注:实际上本设备分为2部分,pyboardCN负责读取文件,定时串口发送,播放设备负责将串口接收到的图片码显示到OLED上。两个设备可以完全分开独立设计,互不影响。
三.图片处理
关于OLED显示一帧帧图片播放视频,大体思路如下:
首先,找到视频源,一般为MP4文件。由于使用黑白OLED,所选取的视频尽量颜色少,最好也为黑白视频,因此选择Bad apple是较为合适的方案。
第二步,截取视频,将图像进行转换。图像转换有如下要求:
(1)黑白颜色
(2)图像大小 128*64
(3)图像格式BMP 先用potplayer截取。
在pconverter中设置如下图所示:
第三步,根据自己的刷屏方式,选择合适的图片取模方式。本人刚开始根据上文提到的参考作者的方案,进行横向8个像素点拼接成一位,如此调用pixel函数描点,结果由于循环次数较多,运算较为复杂,对micropython来说处理较慢,效果并不好。后来参考汉子显示的思路,利用OLED购买提供的例程直接显示图片,效果好很多。在pyboard里移植下这个函数应该也会快一些,不过由于本身在跑micropython,本人直接改用其他设备进行播放。这里虽然没能在pyboard上流畅播放,但是也是受了很大启发,感谢两位大大
http://bbs.eeworld.com.cn/forum.php?mod=viewthread&tid=528783&typeid=426
https://www.cnblogs.com/xxosu/p/7602859.html
*程序中已将I2C直接驱动OLED部分注释掉了,同时刷屏方式也改了。可以参考注释掉的思路进行图片播放。程序见下文。
因此,这里的刷屏设置如下:
matlab程序如下,注意由于图片处理的软件有bug,图像整体右移了一列,因此要先左移回来再进行处理。
clc;clear;
P = 'D:/pic-apple/';
D = dir([P '*.bmp']);
for i = 1 : length(D)
raw_data = imread([P D(i).name]); %软件有bug,第1列错位了
c = [raw_data(:,2:end) raw_data(:,1)];
raw_data = c;
raw_data = double(raw_data‘);
for j = 1:1024 % 128*64=8192=8*1024
byte = 0;
for k = 1:8 % 每个字节的处理,移位实现
byte = byte + bitshift(raw_data(mod((j-1),128)+1,k+floor((j-1)/128)*8),k-1);
end
B(j) = byte;
end
newName=sprintf('%d.bin', i); % 构造文件名
fid = fopen(newName,'wb');
fwrite(fid,B,'uint8');
fclose(fid);
end
四.py程序
程序主要用到文件操作和定时器的使用以及串口通信。源码如下:
# main.py -- put your code here!
from machine import I2C
from ssd1306 import SSD1306_I2C
from pyb import UART,Timer
import pyb
import micropython
micropython.alloc_emergency_exception_buf(100)
#i2c=machine.I2C(-1, sda=machine.Pin("X10"), scl=machine.Pin("X9"), freq=400000)
u1 = UART(1, 921600)
u1.write('ok')
pyb.delay(1000)
pyb.delay(1000)
num=[]
flag = 0
i = 1
#for i in range(1,342):
filename="pic/"+str(i)+".bin"
f = open(filename,"rb")
num = f.read()
#pyb.delay(36)
def f2(t):
global num
global u1
global i
global flag
u1.write(num)
i = i+1
flag = 1
#filename="pic/"+str(i)+".bin"
#f = open(filename,"rb")
#num = f.read()
tm=Timer(4, freq=10, callback=f2)
while True:
if flag == 1:
filename="pic/"+str(i)+".bin"
f = open(filename,"rb")
num = f.read()
u1.write(num)
flag = 0
首先是文件操作。类似C语言,直接打开文件,读取,存到数组。比C方便的是在python中有类似C++的字符串拼接操作,循环简单。
其次是串口,直接write即可。不过在用之前需要确定波特率。这里为了速度用了921600
最关键的还是定时器。还是这位大大的文章,帮了不小的忙。链接:http://bbs.eeworld.com.cn/thread-485618-1-1.html 关于定时器的使用,大大说的很清楚了,文档里也有示例。这里主要记录下出现的问题。
(1)输出调试中的错误。在调试过程中可能会有内存、堆栈等问题,导致程序直接崩掉。我们需要分配中断异常处理的堆栈,才能正常输出我们的问题。
import micropython
micropython.alloc_emergency_exception_buf(100)
(2)定时器使用回调函数方式,因此不能直接在定时器中断里分配内存。例如本程序里涉及到文件操作,对文件进行打开、处理等操作,无法在回调函数中进行。程序中对回调函数做了一个信号量控制主循环文件读取使能以及串口发送。外部变量要用global声明。
def f2(t):
global num
global u1
global i
global flag
u1.write(num)
i = i+1
flag = 1
#filename="pic/"+str(i)+".bin"
#f = open(filename,"rb")
#num = f.read()
tm=Timer(4, freq=10, callback=f2)
五.stm32程序
stm32端主要是一个串口中断,贴上代码,暂不详细展开。
#define MAX_LENGTH 4096
volatile char receiveBuffer[MAX_LENGTH];
volatile uint16_t receiveLength = 0;
volatile uint8_t rxFlag = 0;
unsigned int Times = 1;
static unsigned char str[MAX_LENGTH + 1];
unsigned char* USART_GetString(void)
{
uint16_t temp;
while(!rxFlag); //等待接收数据,在串口接收到一帧数据时 rxFlag 将会被置 1 (USART3 中断服务函数中)
while(rxFlag)
{
temp = receiveLength;
delay_us(8); //等待 500us
if(temp == receiveLength) //判断 receiveLength 是否发生变化(USART3 中断函数中 receiveLength 会有变化),如果没有,证明已经收完所有的数据,否则等待接收完成
{
rxFlag = 0;
}
}
// for(temp = 0; temp < receiveLength; temp++)
// {
// str[temp] = receiveBuffer[temp];
// }
receiveLength = 0;
//show
OLED_DrawBMP(0,0,128,8,(unsigned char*)receiveBuffer);
return str;
}
void USART1_IRQHandler(void)
{
char temp;
if(USART_GetITStatus(USART1, USART_IT_RXNE))
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
temp = USART_ReceiveData(USART1);
if(receiveLength == MAX_LENGTH)
{
return;
}
if(!rxFlag)
{
rxFlag = 1;
}
receiveBuffer[receiveLength++] = temp;
}
}
六.效果
将串口连接好,py以及生成的图片码bin文件放到SD卡中,播放。
由于速度问题,视频截取频率为10Hz,即100ms一张。最高能到13Hz,需要改一下定时器数值。
附件:
链接:https://pan.baidu.com/s/1UaEq1j10xK2zc9-thLU_Hg 密码:g1c5
原文发表在 电子工程世界 论坛,由原作者转载。转载请注明出处。