吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7462|回复: 10
收起左侧

[Python 原创] DroidCam客户端

[复制链接]
邓大侠 发表于 2020-9-4 15:42
本帖最后由 邓大侠 于 2020-9-4 19:35 编辑

DroidCamX介绍

DroidCamX是一个可以将手机摄像头作为笔记本摄像头的app,有一个客户端.我通过Wireshark抓包模拟客户端向app发送tcp报文,从而实现展示图像,打开闪光灯,聚焦,限制FPS等功能.

可以在此基础上通过cv2的模型实现了人脸识别等等操作.

因为客户端的不同会导致部分功能的缺失.

在此附上DroidCamX.apk 破解版

基于TCP连接的DroidCamX客户端

#!/usr/bin/bin python3
# -*- coding: utf-8 -*-
# @Time    : 2020/8/28 11:31
# @AuThor  : SuperDeng
# @Email   : 1821333144@qq.com
# @file    : DroidCam_TCP_Client.py

# 基于TCP协议实现的DroidCam客户端

import sys
import time
import cv2
import socket, threading
import numpy as np
import requests
from tkinter import Radiobutton, IntVar, Button, Tk, messagebox

def bytes2cv(im):
    '''二进制图片转cv2

    :param im: 二进制图片数据,bytes
    :return: cv2图像,numpy.ndarray
    '''
    return cv2.imdecode(np.array(bytearray(im), dtype='uint8'), cv2.IMREAD_UNCHANGED)  # 从二进制图片数据中读取

def cv2bytes(im):
    '''cv2转二进制图片

    :param im: cv2图像,numpy.ndarray
    :return: 二进制图片数据,bytes
    '''
    return np.array(cv2.imencode('.png', im)[1]).tobytes()

size_dict = {
    '240x320': '240x320',
    '320x240': '320x240',
    '352x288': '352x288',
    '480x320': '480x320',
    '480x360': '480x360',
    '480x640': '480x640',
    '640x360': '640x360',
    '640x480': '640x480',
    '640x640': '640x640',
    '720x480': '720x480',
    '864x480': '864x480',
    '1280x640': '1280x640',
    '1280x720': '1280x720',
    '1280x960': '1280x960',
    '1920x960': '1920x960',
    '1920x1080': '1920x1080',
}

tcp_func = {
    # 'Limit_FPS': '/cam/1/fpslimit',
    'Autofocus': b'CMD /v1/ctl?8',
    'Toggle_LED': b'CMD /v1/ctl?9',
    'Zoom_In': b'CMD /v1/ctl?7',
    'Zoom_Out': b'CMD /v1/ctl?6',
    # 'Save_Photo_on_SD': '/cam/1/takepic',        # url
    # 音频流 udp 发送至4748服务端
    'Audio': b'CMD /v2/audio',  # udp 客户端向4748发送,然后获取字节
    'Stop': b'CMD /v1/stop'  # #  udp 客户端向4748发送
}

get_url = {
    'Limit_FPS': '/cam/1/fpslimit',
    'Autofocus': '/cam/1/af',
    'Toggle_LED': '/cam/1/led_toggle',
    'Zoom_In': '/cam/1/zoomin',
    'Zoom_Out': '/cam/1/zoomout',
    'Save_Photo_on_SD': '/cam/1/takepic',
}

s = b'\xff\xd8'
e = b'\xff\xd9'

class DroidCam_Client:
    def __init__(self, master):
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.handler)
        self.playEvent = threading.Event()
        self.size = size_dict['640x640']
        self.v = IntVar()
        self.v.set(8)
        self.createWidgets()
        self.PlatState = False

    def set_size(self):
        self.size = list(size_dict.values())[self.v.get()]

    def createWidgets(self):
        """Build GUI."""

        j = 0
        rr = 0
        cc = -1
        for key in size_dict:
            if cc < 6:
                cc += 1
            else:
                rr += 1
                cc = 0
            Radiobutton(self.master, variable=self.v, text=key, value=j, command=self.set_size).grid(row=rr, column=cc)
            j += 1

        s = len(size_dict) + 1
        self.setup = Button(self.master, width=15, padx=3, pady=3)
        self.setup["text"] = "Play"
        self.setup["command"] = self.Play
        self.setup.grid(row=s, column=0, padx=2, pady=2)

        self.setup = Button(self.master, width=15, padx=3, pady=3)
        self.setup["text"] = "Get_Audio"
        self.setup["command"] = self.Get_Audio
        self.setup.grid(row=s+1, column=0, padx=2, pady=2)

        c = 1
        for func in get_url:
            btn = Button(self.master, width=15, padx=3, pady=3)
            btn["text"] = func
            btn["command"] = getattr(self, func)
            btn.grid(row=s, column=c, padx=2, pady=2)
            c += 1

    def play(self, size, event):
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

        eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')

        smile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml')
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.connect(("192.168.124.3", 4747))
        s1 = 'CMD /v2/video{}?'.format(size)
        print(s1)
        sk.send(s1.encode('utf-8'))
        s = sk.recv(1024)
        jpeg_data = b''
        tmp_data = b''
        while 1:
            msg = sk.recv(1024)
            if e not in msg:
                jpeg_data += msg
            else:
                if msg.endswith(e):
                    jpeg_data += msg
                else:
                    a, b = msg.split(e)
                    jpeg_data = jpeg_data + a + e
                    tmp_data = b
                frame = bytes2cv(jpeg_data[4:])
                faces = face_cascade.detectMultiScale(frame, 1.3, 2)
                img = frame
                for (x, y, w, h) in faces:
                    # 画出人脸框,蓝色,画笔宽度微
                    img = cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
                    # 框选出人脸区域,在人脸区域而不是全图中进行人眼检测,节省计算资源
                    face_area = img[y:y + h, x:x + w]

                    ## 人眼检测
                    # 用人眼级联分类器引擎在人脸区域进行人眼识别,返回的eyes为眼睛坐标列表
                    eyes = eye_cascade.detectMultiScale(face_area, 1.3, 10)
                    for (ex, ey, ew, eh) in eyes:
                        # 画出人眼框,绿色,画笔宽度为1
                        cv2.rectangle(face_area, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 1)

                    ## 微笑检测
                    # 用微笑级联分类器引擎在人脸区域进行人眼识别,返回的eyes为眼睛坐标列表
                    smiles = smile_cascade.detectMultiScale(face_area, scaleFactor=1.16, minNeighbors=65,
                                                            minSize=(25, 25),
                                                            flags=cv2.CASCADE_SCALE_IMAGE)
                    for (ex, ey, ew, eh) in smiles:
                        # 画出微笑框,红色(BGR色彩体系),画笔宽度为1
                        cv2.rectangle(face_area, (ex, ey), (ex + ew, ey + eh), (0, 0, 255), 1)
                        cv2.putText(img, 'Smile', (x, y - 7), 3, 1.2, (0, 0, 255), 2, cv2.LINE_AA)

                # 实时展示效果画面
                cv2.imshow(f"DroidCam{size}", img)
                # 每5毫秒监听一次键盘动作
                if cv2.waitKey(5) & 0xFF == ord('q'):
                    break

                self.s = f"{len(img[0])}x{len(img)}"
                key = cv2.waitKey(1)
                if key == 27:
                    cv2.destroyWindow(f"DroidCam{size}")
                    event.set()
                    break
                jpeg_data = tmp_data
                tmp_data = b''
            if event.isSet():
                cv2.destroyWindow(f"DroidCam{size}")
                break

    def Play(self):
        if not self.PlatState:
            threading.Thread(target=self.play, args=(self.size, self.playEvent)).start()
            self.PlatState = True
        else:
            self.playEvent.set()
            self.OverRide()
            self.playEvent.clear()
            time.sleep(1)
            threading.Thread(target=self.play, args=(self.size, self.playEvent)).start()
            self.PlatState = True

    def handler(self):
        """Handler on explicitly closing the GUI window."""
        if messagebox.askokcancel("Quit?", "Are you sure you want to quit?"):
            self.playEvent.set()
            time.sleep(1)
            self.master.destroy()  # Close the gui window
            sys.exit(0)
        else:  # When the user presses cancel, resume playing.
            return

    def Toggle_LED(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/led_toggle')

    def Limit_FPS(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/fpslimit')

    def Autofocus(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/af')

    def Zoom_In(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/zoomin')

    def Zoom_Out(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/zoomout')

    def OverRide(self):
        ret = requests.get('http://192.168.124.3:4747/override')

    def Save_Photo_on_SD(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/takepic')

    def Get_Audio(self):
        f = open("123.mp3","wb")
        sk = socket.socket(type=socket.SOCK_DGRAM)
        ipaddr = ("192.168.124.3", 4748)
        sk.sendto(b"CMD /v2/audio", ipaddr)
        count = 0
        while 1:
            count += 1
            msg, _ = sk.recvfrom(1024)
            if msg:
                # 解析音频数据
                print(count)
                f.write(msg[1:])
            else:
                break
            if count == 10000: break

        print("exit")
        f.flush()
        f.close()
        sk.close()

def run():
    root = Tk()
    app = DroidCam_Client(root)
    app.master.title("DroidCam_TCP_Client")
    root.mainloop()

if __name__ == '__main__':
    run()

基于http连接的的DroidCamX客户端

#!/usr/bin/bin python3
# -*- coding: utf-8 -*-
# @Time    : 2020/8/1 19:42
# @Author  : SuperDeng
# @Email   : 1821333144@qq.com
# @File    : socket_client.py
# 基于http协议传输的视频数据流播放
import sys
import time
import cv2
import socket, threading
import numpy as np
import requests
from tkinter import Radiobutton, IntVar, Button, Tk, messagebox

def bytes2cv(im):
    '''二进制图片转cv2

    :param im: 二进制图片数据,bytes
    :return: cv2图像,numpy.ndarray
    '''
    return cv2.imdecode(np.array(bytearray(im), dtype='uint8'), cv2.IMREAD_UNCHANGED)  # 从二进制图片数据中读取

def cv2bytes(im):
    '''cv2转二进制图片

    :param im: cv2图像,numpy.ndarray
    :return: 二进制图片数据,bytes
    '''
    return np.array(cv2.imencode('.png', im)[1]).tobytes()

size_dict = {
    '240p': '320x240',
    '480p': '640x480',
    '720p': '960x720',
    'FHD 720p': '1280x720',
    'FHD 1080p': '1920x1080',
}

get_url = {
    'Limit_FPS': '/cam/1/fpslimit',
    'Autofocus': '/cam/1/af',
    'Toggle_LED': '/cam/1/led_toggle',
    'Zoom_In': '/cam/1/zoomin',
    'Zoom_Out': '/cam/1/zoomout',
    'Save_Photo_on_SD': '/cam/1/takepic',
}

class DroidCam_Client:
    def __init__(self, master):
        self.master = master
        self.master.protocol("WM_DELETE_WINDOW", self.handler)
        self.playEvent = threading.Event()
        self.size = size_dict['480p']
        self.v = IntVar()
        self.v.set(1)
        self.createWidgets()
        self.PlatState = False

    def set_size(self):
        self.size = list(size_dict.values())[self.v.get()]

    def createWidgets(self):
        """Build GUI."""

        j = 0
        for key in size_dict:
            Radiobutton(self.master, variable=self.v, text=key, value=j, command=self.set_size).grid()
            j += 1

        self.setup = Button(self.master, width=15, padx=3, pady=3)
        self.setup["text"] = "Play"
        self.setup["command"] = self.Play
        self.setup.grid(row=5, column=0, padx=2, pady=2)

        c = 1
        for func in get_url:
            btn = Button(self.master, width=15, padx=3, pady=3)
            btn["text"] = func
            btn["command"] = getattr(self, func)
            btn.grid(row=5, column=c, padx=2, pady=2)
            c += 1

    def play(self, size, event):
        print(size)
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.connect(("192.168.124.3", 4747))
        data = 'GET /mjpegfeed?{} HTTP/1.1\r\nHost: 192.168.124.3:4747\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\n\r\n'.format(
            size)
        sk.send(data.encode('utf-8'))
        msg = sk.recv(1024)
        recv_dict = msg.decode('utf-8').split('\r\n')
        if 'Connection: Keep-Alive' in recv_dict:
            self.__play(sk,size,event)
        else:
            print('DroidCam is connected to another client.')
            print('Disconnect the other client and Take Over')
            sk.close()

    def __play(self,sk,size,event):
        JPEG_header = b''
        JPEG = b''
        while True:
            msg = sk.recv(1024)
            if msg:
                if b'\r\n\r\n' in msg:
                    # 分界点到了
                    JPEG_header += msg.split(b'\r\n\r\n')[0]
                    long = int(JPEG_header.split(b'\r\n')[-1].split(b':')[-1])
                    JPEG_header = b''
                    JPEG += msg.split(b'\r\n\r\n')[1]
                    while True:
                        JPEG += sk.recv(1024)
                        if len(JPEG) >= long:
                            img = bytes2cv(JPEG[:long])
                            cv2.imshow(f"DroidCam{size}", img)
                            cv2.waitKey(1)
                            JPEG_header += JPEG[long:]
                            JPEG = b''
                            break
                else:
                    JPEG_header += msg
                if event.isSet():
                    cv2.destroyWindow(f"DroidCam{size}")
                    break
        sk.close()

    def Play(self):
        if not self.PlatState:
            threading.Thread(target=self.play, args=(self.size, self.playEvent)).start()
            self.PlatState = True
        else:
            self.playEvent.set()
            self.OverRide()
            self.playEvent.clear()
            time.sleep(1)
            threading.Thread(target=self.play, args=(self.size, self.playEvent)).start()
            self.PlatState = True

    def handler(self):
        """Handler on explicitly closing the GUI window."""
        if messagebox.askokcancel("Quit?", "Are you sure you want to quit?"):
            self.playEvent.set()
            time.sleep(1)
            self.master.destroy()  # Close the gui window
            sys.exit(0)
        else:  # When the user presses cancel, resume playing.
            return

    def Toggle_LED(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/led_toggle')

    def Limit_FPS(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/fpslimit')

    def Autofocus(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/af')

    def Zoom_In(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/zoomin')

    def Zoom_Out(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/zoomout')

    def OverRide(self):
        ret = requests.get('http://192.168.124.3:4747/override')

    def Save_Photo_on_SD(self):
        ret = requests.get('http://192.168.124.3:4747/cam/1/takepic')

def run():
    root = Tk()
    app = DroidCam_Client(root)
    app.master.title("DroidCam_Client")
    root.mainloop()

if __name__ == '__main__':
    run()

问题

无法实现音频流的解读,我先抛砖引玉,看看诸位大佬有啥办法没.

其他应用

手机摄像头,联网,调用百度API人脸识别等等,可以试着手机安装linux deploy装ubuntu系统,模拟成一个树莓派,来进行各种操作.

DroidCamX.Wireless.Webcam.Pro.v6.4.8.APKReal.com.rar

1.42 MB, 下载次数: 775, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 2威望 +1 吾爱币 +10 热心值 +2 收起 理由
苏紫方璇 + 1 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
楠宝 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

serafwind 发表于 2020-11-16 15:29
这不是老版本的么?
头像被屏蔽
rsaudio 发表于 2020-12-5 17:49
alick123 发表于 2020-12-16 13:58
木子° 发表于 2021-1-29 11:34
楼主有成品嘛。最近也在折腾这个,无奈功力不够
kis8 发表于 2021-2-3 15:18
RE: DroidCam客户端 [修改]
yccyjie 发表于 2022-5-11 09:22
本帖最后由 yccyjie 于 2022-5-11 09:25 编辑

测试成功,多谢提供如此好的范例!!!
lrr0515 发表于 2022-7-7 20:45
希望好用,下载试试
坚如磐石 发表于 2022-7-27 16:33
iriun这个好用
yangyoucai 发表于 2022-8-2 15:08
学习中,非常感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-13 14:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表