感兴趣的可以去看看channels官方chat聊天室案例,这里主要讲解如何实现websocket异步视频推流,来看一下效果图:
我使用的是python3.6版本,django3.2.13版本
先cd到对应目录,新建一个django项目
django-admin startproject mysite
新建的项目结构如下:
接下来cd进入mysite目录,创建一个video App,命令如下:
cd mysite
python manage.py startapp video
修改mysite/setting.py,添加ASGI_APPLICATION并且在INSTALLED_APPS中添加’channels’和’video’
ASGI_APPLICATION = 'mysite.asgi.application'
INSTALLED_APPS = [
'channels',
'video',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
同时在video/urls.py和mysite/urls.py中添加路由,video目录下如果没有urls.py就先创建一个
video/urls.py
# video/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('/' , views.v_name, name='v_name'),
]
mysite/urls.py
# mysite/urls.py
from django.urls import include,path
from django.contrib import admin
urlpatterns = [
path('video/', include('video.urls')),
path('admin/', admin.site.urls),
]
添加video/views.py视图:
# video/views.py
from django.shortcuts import render
def index(request):
return render(request, 'video/index.html')
def v_name(request, v_name):
return render(request, 'video/video.html', {
'v_name': v_name
})
在video目录下新建一个consumers.py:
# video/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class VideoConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['v_name']
self.room_group_name = 'video_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'video_message',
'message': text_data,
}
)
# Receive message from room group
async def video_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
在video目录新建路由routing.py:
# video/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/video/(?P\w+)/$' , consumers.VideoConsumer.as_asgi())
]
修改mysite/asgi.py
# mysite/asgi.py
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from channels.auth import AuthMiddlewareStack
import video.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
video.routing.websocket_urlpatterns
)
),
})
在根目录新建pullVideo.py:
# pullVideo.py
import asyncio
import websockets
import numpy as np
import cv2
import cv2 as cv
import base64
import time
import mediapipe as mp
capture = cv2.VideoCapture(r'./1.mp4') # cv2.VideoCapture(0)为获取摄像头,因为电脑没有摄像头所以使用视频代替
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils
# 设置点的颜色与大小
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)
# 设置线的颜色与大小
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 0), thickness=10)
if not capture.isOpened():
print('quit')
quit()
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 95]
# 开始推流视频
async def pullVideo(websocket):
while True:
ret, frame = capture.read()
if ret:
frame_RGB = cv.cvtColor(frame, cv.COLOR_BGR2RGB) # 将RGB转为BGR
result = hands.process(frame_RGB)
if result.multi_hand_landmarks:
for handLms in result.multi_hand_landmarks:
mpDraw.draw_landmarks(frame, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)
else:
break
time.sleep(0.01)
result, imgencode = cv2.imencode('.jpg', frame, encode_param)
data = np.array(imgencode)
img = data.tobytes()
# base64编码传输
img = base64.b64encode(img).decode()
await websocket.send("data:image/jpg;base64," + img)
async def main_logic():
async with websockets.connect('ws://127.0.0.1:8000/ws/video/wms/', ping_interval=None) as websocket:
await pullVideo(websocket)
asyncio.get_event_loop().run_until_complete(main_logic())
在根目录新建getVideo.html:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Videotitle>
<style>
#img_div
{
width:500px;
border:3px solid cyan;
border-radius:25px;
}
style>
head>
<body>
<div>
<h1>Videoh1>
div>
<div id="img_div">
<img id="resImg" src="" align="middle"/>
div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js" >script>
<script>
const ws = new WebSocket(
'ws://'
+ '127.0.0.1:8000'
+ '/ws/video/'
+ 'wms'
+ '/'
);
console.log("新建websocket")
ws.onopen=function (ev) {
console.log('建立连接');
ws.send("开始");
}
ws.onmessage = function(evt) {
v_data = JSON.parse(evt.data);
$("#resImg").attr("src", v_data.message);
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
script>
body>
html>
现在直接开始运行会报错
server rejected WebSocket connection: HTTP 500
这是因为没有开启Redis
在mysite/setting.py代码底部添加CHANNEL_LAYERS
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
django与Redis直接通信需要安装django-redis
使用命令安装:
pip install django-redis
下载Redis:https://github.com/tporadowski/redis/releases
下载完后解压到c盘,并将文件夹重新命名为 redis
打开cmd cd进入C:\redis 执行下面代码:
redis-server.exe redis.windows.conf
打开终端,输入命令:
python manage.py runserver
python pullVideo.py
成功运行后,打开getVideo.html,就可以看到效果了
经历千辛万苦终于实现了websocket异步视频推流,接下来说说
与channels官方chat聊天室案例不同的地方。
channels官方使用Docker安装和运行Redis,我并不了解Docker,所以并没有去使用它,而是直接下载了Redis,然后通过redis-server.exe redis.windows.conf命令运行。