Pygame与OpenCV联合播放视频并保证音画同步
作者:h281001460 发布时间:2021-09-04 22:23:11
Pygame是一个超好用的SDL绑定。自从有了Pygame,妈妈再也不用担心我内存泄漏了。
但是这里有一个问题,Pygame的Movie模块已经废弃多年,这次做课题项目却要在一个游戏中来段视频播放。有点蒙圈。Ren'py提供的解决方案是使用libav,我尝试了一早上也搞不明白pyav怎么用。后来干脆用手边的OpenCV硬读视频吧。
这里说下第三方库:
pygame
numpy
opencv-python
其中,numpy是Anaconda自带,我没自己装过不知道,但是其他两个都是可以用pip直接安装的。
其实Pygame使用opencv或者moviepy不少见,就是使用pygame的pygame.surfarray接口可以把numpy矩阵转化为surface,而这些视频读取库能把每帧转为numpy矩阵。这部分在下面代码中都会体现。
我测试的视频是来自ドラッグオンドラグーン(龙背上的骑兵3)的一段音游部分。音乐就是用pygame的音频模块随便播放下就好。但是注意,视频帧率必须设置为跟你的视频一致,我这里是25fps。建议稍微多设置一点,比如25.5帧是对应25帧最好的选择。因为留有余地。
平时你的游戏基本不可能莫名其妙的在逻辑中出现视频播放,所以尽量单独播放视频,防止帧数掉出25.当然现代电脑这种情况比较少。
上图:
还是喜欢零姐啊!!!
下面是基本的实现代码
# -*- coding:utf-8 -*-
import pygame
import sys
import cv2
import numpy as np
import os
if __name__ == '__main__':
pygame.init() # 初始化pygame
FPS = 25.5
#设置窗口位置
os.environ['SDL_VIDEO_WINDOW_POS']="%d,%d" % (50,70)
FPSClock = pygame.time.Clock()
size = width, height = 1280, 720 # 设置窗口大小
screen = pygame.display.set_mode(size) # 显示窗口
pygame.display.set_caption(u'打字游戏:反应练习')
color = (255, 255, 255) # 设置颜色
ogg=pygame.mixer.Sound("game.ogg")
# pygame.mixer.music.load("")
videoCapture = cv2.VideoCapture("test.mp4")
ogg.play()
while True:
a=pygame.time.get_ticks()
if videoCapture.isOpened():
#从opncv读一段视频进来
ret, frame = videoCapture.read()
#下面这句可以获取图像大小
# print('frame.shape:', frame.shape)
#
# rot90(m, k=1, axes=(0, 1)
# m是要旋转的数组(矩阵),
# k是旋转的次数,默认旋转1次,
# 那是顺时针还是逆时针呢?
# 正数表示逆时针,而k为负数时则是对数组进行顺时针方向的旋转。
# 视频结束时会丢出一个ValueError异常
try:
frame = np.rot90(frame,k=-1)
except:
continue
frame = pygame.surfarray.make_surface(frame)
frame=pygame.transform.flip(frame,False,True)
# frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
screen.blit(frame, (0,0))
# print()
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭窗口,则退出
sys.exit()
pygame.display.flip() # 更新全部显示
time_next= FPSClock.tick(FPS)
b=pygame.time.get_ticks()
pygame.quit() # 退出pygame
好了。这样就实现了pygame播放视频~~~~
才怪呢。
因为手欠,拖动了以下窗口,然后发现,程序居然一直停留在拖动前的最后一帧,但我的音频还是在播放,当我松开鼠标的时候,视频图像才重新开始显示,这样造成视音频的同步上的问题,使鼠标松开后的视频直接跟不上音频。
尤其是这个部分是音游,一旦不同步,节奏对不上,感觉是要疯。
这问题太难受了。
于是动手解决吧……
也不难,就是时间方面的问题。掌控时间,就是掌握了全局。
于是方案如下:
方案1:通过时间和跳帧。通过计算时间【就是时间差大于某个阈值,比如100ms】发现曾经发生过停止,然后进行跳帧。因为人类手速有限,所以完全可以阈值稍微大点,防止由于cpu方面的原因造成误判。
方案2:重置音频播放,计算视频播放时间,让音频回到之前的状态。这个简单点,但是这不会太精确。需要仔细考虑使用哪个方案。
理论上跳帧方案比较好,因为帧量大,误差小。并且音频感官可以不缺失。
反复比较,选用方案一
算法如下:
设已有变量:
时间参量=下一帧-上一帧
跳帧计数器=0【在循环外】
阈值=200[时间参量大于此值说明出现故障]
跳帧系统检测到阈值超标,使用以下算法跳帧:
使用 故障值/正确延时 ,得到要跳的帧数量。然后使计数器增长到这个数量,然后跳帧。
以下是带有跳帧器的代码
# -*- coding:utf-8 -*-
import pygame
import sys
import cv2
import numpy as np
import os
# 音频同步跳帧实验
# 新增:跳帧系统
if __name__ == '__main__':
pygame.init() # 初始化pygame
FPS = 25.5
# 设置窗口位置
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (50, 70)
FPSClock = pygame.time.Clock()
size = width, height = 1280, 720 # 设置窗口大小
# screen = pygame.display.set_mode(size,pygame.FULLSCREEN) # 显示窗口
screen = pygame.display.set_mode(size) # 显示窗口
pygame.display.set_caption(u'打字游戏:反应练习')
color = (255, 255, 255) # 设置颜色
ogg = pygame.mixer.Sound("game.ogg")
# pygame.mixer.music.load("")
videoCapture = cv2.VideoCapture("test.mp4")
ogg.play()
# 真正时间
realtime = int(1000 / FPS)
# 阈值-要稍微留下空间
jumpyu = 100
# 跳帧计数器
jumpnum = 0
while True:
a = pygame.time.get_ticks()
if videoCapture.isOpened():
#这里是跳帧工具
if jumpnum != 0:
#这个函数光读东西,比read少一个解码过程,所以能省点算点
videoCapture.grab()
jumpnum -= 1
continue
ret, frame = videoCapture.read()
try:
frame = np.rot90(frame, k=-1)
except:
continue
frame = pygame.surfarray.make_surface(frame)
frame = pygame.transform.flip(frame, False, True)
# frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
screen.blit(frame, (0, 0))
# print()
for event in pygame.event.get(): # 遍历所有事件
if event.type == pygame.QUIT: # 如果单击关闭窗口,则退出
sys.exit()
pygame.display.flip() # 更新全部显示
time_next = FPSClock.tick(FPS)
b = pygame.time.get_ticks()
k = b - a
#此处检测阈值,大于阈值做记录准备跳帧
if k > jumpyu:
print('超过阈值,跳帧')
temp = (k - realtime)/realtime
tempstr=str(temp)
tempstr=tempstr[tempstr.find('.')+1:]
if len(tempstr)>1 or tempstr[0]!='0':
jumpnum+=(int(temp)+1)
print('跳小数帧')
else:
print('跳整数帧')
jumpnum+=int(temp)
print('跳'+str(jumpnum)+'帧')
pygame.quit() # 退出pygame
看看成果:为了截图拖了几下窗口,于是就已经智能跳帧了多次了。
搞定收工,这次将手贱拖动窗口造成的音画同步问题降低到最小。至少这个视频是没怎么出现节奏问题了。当然,最好的建议还是别乱拖动窗口,毕竟再精确也无法做到帧级别的完全精确。就算是全屏与窗口切换也是会有帧损失的。emm…
来源:https://blog.csdn.net/h281001460/article/details/94229007


猜你喜欢
- 我们最常用的 DBD::mysql 模块,我发现是难住很多人的地方.因为安装老是失败,下面我介绍一下解决方法,比如我使用 cpanm 安装,
- SQLserver 2000中出现“指定的服务并未以已安装的服务存在" 解决方案一、将计算机名改成大写。二、将sql server
- 1.设置Headers有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们
- 前言有的时候我们在查看数据库数据时,会看到乱码。实际上,无论何种数据库只要出现乱码问题,这大多是由于数据库字符集设定的问题。下面我们就介绍一
- Player.playState0 Undefined Windows Media Player is in an undefined st
- 值得学习的地方:1.选择合法索引的方式2.数组转图像显示import numpy as npfrom PIL import Image#in
- 在使用django restframework serializer 序列化在django中定义的model时,有时候我们需要额外在seri
- 前言相信大家都玩过斗地主,规则就不再介绍了。直接上一张朋友圈看到的残局图:这道题我刚看到时,曾尝试用手工来破解,每次都以为找到了农民的必胜策
- 升级背景:为了解决mysql低版本的漏洞,从mysql5.5升级到了8.0.11版本,再次升级到了8.0.17版本(从版本是2019.7.2
- 本文实例讲述了python函数装饰器用法。分享给大家供大家参考。具体如下:装饰器经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、
- 我们已经知道,null 没有任何的属性值,并且无法获取其实体(existence)值。所以 null.property 返回的是错误(err
- 本文实例讲述了Python实现的圆形绘制。分享给大家供大家参考,具体如下:# -*- coding:utf-8 -*-#! python3i
- 刚开始使用webpack时,可能很多人都会有过这样的想法,在require文件时,能不能不写静态的字符串路径,而是使用一个更灵活的方式,比如
- 一、变量的定义 mysql中变量定义用declare来定义一局部变量,该变量的使用范围只能在begin...end 块中使用,变量必须定义在
- 登陆万事开头难,做什么事都要有个起点,后面才能更好的进行下去,因此我选择的起点就是最为直观的登陆页面 /login/index.vue/sr
- 此文刊登在《程序员》2009年5月期:SQL全名是结构化查询语言(Structured Query Language),一直是后台开发者用来
- 本文实例讲述了PHP获取指定日期是星期几的实现方法。分享给大家供大家参考,具体如下:<?php header("
- 一、如何将列表数据写入文件 ⾸先,我们来看看下⾯这段代码,并思考:这段代码有没有问题,如果有问题的话,要怎么改?li = [
- asp中我们可以利用ERR对象来判断sql语句执行是否成功:SQL="Insert INTO TABLE(F1,F2) value
- 在ACCESS数据库中可以用MSSQL的形式定义操作字符串,也可以采用OLEDB的形式。MSSQL 形式string sqlText = @