精华内容
下载资源
问答
  • VB编写的局域网语音聊天工具,支持点对点语音聊天,可以当做对讲机使用。
  • windows 下底层函数的多媒体编程,实现局域网语音通信,播放流畅。实现是用windows api底层函数编写比如WaveInStart等该类函数写成。 使用过程中一方作为客户端,一方作为服务端进行连接通信。局域网可以实现,...
  • 聊天工具是基于局域网内的基于DIRECTX的实时语音传输工具,编写工具是C# vs2008。
  • 功能:同一局域网的两台PC可实现全双工语音聊天实现环境:Windows XP + VS2005 + C++设计步骤:一,初始化工作: 1,检查音频输入输出设备工作是否正常 2,建立TCP侦听套接字二, 建立连接 既是server , 又是client, 三, ...

     功能:同一局域网的两台PC可实现全双工语音聊天

    实现环境:Windows XP + VS2005 + C++

    设计步骤:

    一,初始化工作:

        1,检查音频输入输出设备工作是否正常

        2,建立TCP侦听套接字

    二, 建立连接

        既是server , 又是client,

    三, 通话过程

        1,两块接收对方数据内存A1,A2, 接收对方发过来的音频数据, A1满后,用A2接收,将A1拷贝到播放内存上播放. 如此循环…
        2,两块接收本地音频输入的内存C1,C2, 工作模式跟A1,A2一样, 区别在于一块接收满后先在本地播放,再发送过去
        3,两块播放内存B1, B2, 工作模式跟A1,A2 一样

    四, 断开.

     

    有这些问题亟待解决:

    1,如何获取音频输入数据?

    2,如何将音频数据写入程序定义的变量中?

    3,如何将音频数据发送出去?

    4,程序如何根据麦克风的状态工作?即说话时才采集数据,不说话时不采集.

    展开全文
  • 局域网视频语音聊天工具 1.0┊实现局域网语音视频及传送┊简体中文绿色免费版
  • 安卓局域网语音聊天源码是一套关于局域网聊天的安卓项目源码。实现了语音对讲、文字对讲和文件发送功能。但是小编周围没有什么可用的无线网就没有搭建环境真正的去实验一下。模拟器还是自己玩自己的,不在一个局域网...
  • 局域网视频语音聊天工具 V1.0 局域网聊天
  • 本软件为绿色软件,不用安装,复制即用。支持文本信息、视频信息、语音信息、文件传送。...使用语音聊天时,程序会有1秒左右的延迟时间,并且,为了不产生回音,最好使用耳机进行语音聊天,或者将音响音量开小。
  • 本项目源码是一套关于局域网聊天的安卓项目源码。实现了语音对讲、文字对讲和文件发送功能。但是小编周围没有什么可用的无线网就没有搭建环境真正的去实验一下。模拟器还是自己玩自己的,不在一个局域网。也没法子...
  • 毕业设计制作的局域网语音聊天软件的毕业论文、答辩ppt一并上传了,不得已,赚点分,有需要的朋友下吧。论文。答辩成绩为“良好”。目录结构如下: 目录 局域网聊天工具设计 1 ——文本聊天、文件传输及用户管理 1 ...
  • 当前版本提供功能包括:收发信息,收发文件(包括文件夹),多人电话会议(语音聊天),可自定义语言文件等。使用语音功能前请确定您的语音输入为麦克风,且录音输入音量调至最大。运行环境:windows2000或以上版本...
  • 修正了之前发布的GLSpeaker的几个bug,将其中的exe文件覆盖之前的同名文件即可。 如果你还未下载GLSpeaker软件,可通过以下地址下载: ...
  • 这是局域网语音聊天的服务器,支持用户的注册、登陆和注销等操作,维护一个用户信息数据库。
  • 局域网视频聊天软件,,可文字聊天,语音聊天,视频聊天,文件传输
  • Python3 实现简易局域网视频聊天工具 1.环境 操作系统为 Ubuntu 16.04 python 3.5opencv-python 3.4.1.15numpy 1.14.5PyAudio 0.2.11 2. 内容简介本实验实现简易的视频通信工具在视频通信的基础上加入语音...

    Python3 实现简易局域网视频聊天工具

     

    1.环境

    操作系统为 Ubuntu 16.04

    python 3.5
    opencv-python 3.4.1.15
    numpy 1.14.5
    PyAudio 0.2.11

    2. 内容简介
    本实验实现简易的视频通信工具
    在视频通信的基础上加入语音
    用户可以选择通信的质量,即画质、停顿等参数
    支持IPv6

    3.安装环境

    $ sudo pip3 install numpy
    $ sudo pip3 install opencv_python
    这一步下载了我们需要的opencv-python和numpy两个包。


    剩下的PyAudio,由于本虚拟环境的部分问题,我们单独分开下载。

    $ sudo apt-get install portaudio19-dev python-all-dev python3-all-dev
    $ sudo pip3 install pyaudio==0.2.11
    现在,我们的实验环境就搭好了。

     

    4. 实验原理
    实验实现了简易的视频通信工具,基于 OpenCV 和 PyAudio,使用 TCP 协议通信,通信双方建立双向 CS 连接,
    双方均维护一个客户端和一个服务器端。在捕获视频信息后,根据用户指定的参数对画面做压缩并传输。

     实验步骤
    接下来我们分步骤讲解本实验。

    4.1 实现双向 C/S 连接
    先为双方的通信设计 Server 类和 Client类,两个类均继承 threading.Thread,只需要分别实现 __init__、__del__和run方法,
    之后对象调用.start()方法即可在独立线程中执行run方法中的内容。首先Client类需要存储远端的IP地址和端口,
    而Server类需要存储本地服务器监听的端口号。用户还应当可以指定通信双方使用的协议版本,即基于IPv4 还是IPv6 的TCP连接。
    因此Server类的初始化需要传入两个参数(端口、版本),Client类的初始化需要三个参数(远端IP、端口、版本)。新建文件vchat.py,
    在其中定义基础的两个类如下。

     

    1 from socket import *
    2 import threading
    3 class Video_Server(threading.Thread):
    4 def __init__(self, port, version) :
    5 threading.Thread.__init__(self)
    6 self.setDaemon(True)
    7 self.ADDR = ('', port)
    8 if version == 4:
    9 self.sock = socket(AF_INET ,SOCK_STREAM)
    10 else:
    11 self.sock = socket(AF_INET6 ,SOCK_STREAM)
    12 def __del__(self):
    13 self.sock.close()
    14 # TODO
    15 def run(self):
    16 print("server starts...")
    17 self.sock.bind(self.ADDR)
    18 self.sock.listen(1)
    19 conn, addr = self.sock.accept()
    20 print("remote client success connected...")
    21 # TODO
    22
    23 class Video_Client(threading.Thread):
    24 def __init__(self ,ip, port, version):
    25 threading.Thread.__init__(self)
    26 self.setDaemon(True)
    27 self.ADDR = (ip, port)
    28 if version == 4:
    29 self.sock = socket(AF_INET, SOCK_STREAM)
    30 else:
    31 self.sock = socket(AF_INET6, SOCK_STREAM)
    32 def __del__(self) :
    33 self.sock.close()
    34 # TODO
    35 def run(self):
    36 print("client starts...")
    37 while True:
    38 try:
    39 self.sock.connect(self.ADDR)
    40 break
    41 except:
    42 time.sleep(3)
    43 continue
    44 print("client connected...")
    45 # TODO
    复制代码
    4.2 实现摄像头数据流捕获
    OpenCV 为 Python 提供的接口非常简单并且易于理解。捕获视频流的任务应当由Client类完成,
    下面完善Client的run函数。在下面的代码中,我们为类添加了一个成员变量cap,它用来捕获默认摄像头的输出。

    复制代码
    1 class Video_Client(threading.Thread):
    2 def __init__(self ,ip, port, version):
    3 threading.Thread.__init__(self)
    4 self.setDaemon(True)
    5 self.ADDR = (ip, port)
    6 if version == 4:
    7 self.sock = socket(AF_INET, SOCK_STREAM)
    8 else:
    9 self.sock = socket(AF_INET6, SOCK_STREAM)
    10 self.cap = cv2.VideoCapture(0)
    11 def __del__(self) :
    12 self.sock.close()
    13 self.cap.release()
    14 def run(self):
    15 print("client starts...")
    16 while True:
    17 try:
    18 self.sock.connect(self.ADDR)
    19 break
    20 except:
    21 time.sleep(3)
    22 continue
    23 print("client connected...")
    24 while self.cap.isOpened():
    25 ret, frame = self.cap.read()
    26 # TODO
    复制代码
    4.3 发送捕获到的数据到服务器
    已经捕获到数据,接下来要发送字节流。首先我们继续编写Client,为其添加发送数据功能的实现。这里只改动了run方法。
    在捕获到帧后,我们使用pickle.dumps方法对其打包,并用sock.sendall方法发送。
    注意发送过程中我们用struct.pack方法为每批数据加了一个头,用于接收方确认接受数据的长度。

    复制代码
    1 def run(self):
    2 while True:
    3 try:
    4 self.sock.connect(self.ADDR)
    5 break
    6 except:
    7 time.sleep(3)
    8 continue
    9 print("client connected...")
    10 while self.cap.isOpened():
    11 ret, frame = self.cap.read()
    12 data = pickle.dumps(frame)
    13 try:
    14 self.sock.sendall(struct.pack("L", len(data)) + data)
    15 except:
    16 break
    复制代码
    下面编写Server,在服务器端连接成功后,应当创建一个窗口用于显示接收到的视频。因为连接不一定创建成功,
    因此cv.destroyAllWindows()被放在一个try..catch块中防止出现错误。在接收数据过程中,
    我们使用payload_size记录当前从缓冲区读入的数据长度,这个长度通过struct.calcsize('L')来读取。
    使用该变量的意义在于缓冲区中读出的数据可能不足一个帧,也可能由多个帧构成。为了准确提取每一帧,我们用payload_size区分帧的边界。
    在从缓冲区读出的数据流长度超过payload_size时,剩余部分和下一次读出的数据流合并,
    不足payload_size时将合并下一次读取的数据流到当前帧中。在接收完完整的一帧后,显示在创建的窗口中。同时我们为窗口创建一个键盘响应,
    当按下Esc 或 q键时退出程序。

    复制代码
    class Video_Server(threading.Thread):
    def __init__(self, port, version) :
    threading.Thread.__init__(self)
    self.setDaemon(True)
    self.ADDR = ('', port)
    if version == 4:
    self.sock = socket(AF_INET ,SOCK_STREAM)
    else:
    self.sock = socket(AF_INET6 ,SOCK_STREAM)
    def __del__(self):
    self.sock.close()
    try:
    cv2.destroyAllWindows()
    except:
    pass
    def run(self):
    print("server starts...")
    self.sock.bind(self.ADDR)
    self.sock.listen(1)
    conn, addr = self.sock.accept()
    print("remote client success connected...")
    data = "".encode("utf-8")
    payload_size = struct.calcsize("L")
    cv2.namedWindow('Remote', cv2.WINDOW_NORMAL)
    while True:
    while len(data) < payload_size:
    data += conn.recv(81920)
    packed_size = data[:payload_size]
    data = data[payload_size:]
    msg_size = struct.unpack("L", packed_size)[0]
    while len(data) < msg_size:
    data += conn.recv(81920)
    zframe_data = data[:msg_size]
    data = data[msg_size:]
    frame_data = zlib.decompress(zframe_data)
    frame = pickle.loads(frame_data)
    cv2.imshow('Remote', frame)
    if cv2.waitKey(1) & 0xFF == 27:
    break
    复制代码
    4.4 视频缩放和数据压缩
    现在的服务器和客户端已经可以运行,你可以在代码中创建一个Client类实例和一个Server类实例,并将IP地址设为127.0.0.1,
    端口设为任意合法的(0-65535)且不冲突的值,版本设为IPv4。执行代码等同于自己和自己通信。如果网络状况不好,
    你也许会发现自己和自己的通信也有卡顿现象。为了使画面质量、延迟能够和现实网络状况相匹配,我们需要允许用户指定通信中画面的质量,
    同时我们的代码应当本身具有压缩数据的能力,以尽可能利用带宽。

     

     


    当用户指定使用低画质通信,我们应当对原始数据做变换,最简单的方式即将捕获的每一帧按比例缩放,同时降低传输的帧速,
    在代码中体现为resize,该函数的第二个参数为缩放中心,后两个参数为缩放比例,并且根据用户指定的等级,不再传输捕获的每一帧,
    而是间隔几帧传输一帧。为了防止用户指定的画质过差,代码中限制了最坏情况下的缩放比例为0.3,最大帧间隔为3。此外,
    我们在发送每一帧的数据前使用zlib.compress对其压缩,尽量降低带宽负担。

    复制代码
    1 class Video_Client(threading.Thread):
    2 def __init__(self ,ip, port, level, version):
    3 threading.Thread.__init__(self)
    4 self.setDaemon(True)
    5 self.ADDR = (ip, port)
    6 if level <= 3:
    7 self.interval = level
    8 else:
    9 self.interval = 3
    10 self.fx = 1 / (self.interval + 1)
    11 if self.fx < 0.3:
    12 self.fx = 0.3
    13 if version == 4:
    14 self.sock = socket(AF_INET, SOCK_STREAM)
    15 else:
    16 self.sock = socket(AF_INET6, SOCK_STREAM)
    17 self.cap = cv2.VideoCapture(0)
    18 def __del__(self) :
    19 self.sock.close()
    20 self.cap.release()
    21 def run(self):
    22 print("VEDIO client starts...")
    23 while True:
    24 try:
    25 self.sock.connect(self.ADDR)
    26 break
    27 except:
    28 time.sleep(3)
    29 continue
    30 print("VEDIO client connected...")
    31 while self.cap.isOpened():
    32 ret, frame = self.cap.read()
    33 sframe = cv2.resize(frame, (0,0), fx=self.fx, fy=self.fx)
    34 data = pickle.dumps(sframe)
    35 zdata = zlib.compress(data, zlib.Z_BEST_COMPRESSION)
    36 try:
    37 self.sock.sendall(struct.pack("L", len(zdata)) + zdata)
    38 except:
    39 break
    40 for i in range(self.interval):
    41 self.cap.read()
    复制代码
    服务器端最终代码如下,增加了对接收到数据的解压缩处理。

    复制代码
    1 class Video_Server(threading.Thread):
    2 def __init__(self, port, version) :
    3 threading.Thread.__init__(self)
    4 self.setDaemon(True)
    5 self.ADDR = ('', port)
    6 if version == 4:
    7 self.sock = socket(AF_INET ,SOCK_STREAM)
    8 else:
    9 self.sock = socket(AF_INET6 ,SOCK_STREAM)
    10 def __del__(self):
    11 self.sock.close()
    12 try:
    13 cv2.destroyAllWindows()
    14 except:
    15 pass
    16 def run(self):
    17 print("VEDIO server starts...")
    18 self.sock.bind(self.ADDR)
    19 self.sock.listen(1)
    20 conn, addr = self.sock.accept()
    21 print("remote VEDIO client success connected...")
    22 data = "".encode("utf-8")
    23 payload_size = struct.calcsize("L")
    24 cv2.namedWindow('Remote', cv2.WINDOW_NORMAL)
    25 while True:
    26 while len(data) < payload_size:
    27 data += conn.recv(81920)
    28 packed_size = data[:payload_size]
    29 data = data[payload_size:]
    30 msg_size = struct.unpack("L", packed_size)[0]
    31 while len(data) < msg_size:
    32 data += conn.recv(81920)
    33 zframe_data = data[:msg_size]
    34 data = data[msg_size:]
    35 frame_data = zlib.decompress(zframe_data)
    36 frame = pickle.loads(frame_data)
    37 cv2.imshow('Remote', frame)
    38 if cv2.waitKey(1) & 0xFF == 27:
    39 break
    复制代码
    4.5 加入音频的捕获和传输
    在完成视频通信的基础上,整体框架对于音频通信可以直接挪用,只需要修改其中捕获视频/音频的代码和服务器解码播放的部分。
    这里我们使用 PyAudio 库处理音频,在 Linux 下你也可以选择 sounddevice。关于sounddevice这里不做过多介绍,
    将vchat.py复制一份,重命名为achat.py,简单修改几处,最终音频捕获、传输的完整代码如下。
    我将上面代码中的Server和Client分别加上Video和Audio前缀以区分,同时显示给用户的print输出语句也做了一定修改,
    对于视频加上VIDEO前缀,音频加上AUDIO前缀。如果你对代码中使用到的 PyAudio 提供的库函数有所疑问,

    复制代码
    1 class Audio_Server(threading.Thread):
    2 def __init__(self, port, version) :
    3 threading.Thread.__init__(self)
    4 self.setDaemon(True)
    5 self.ADDR = ('', port)
    6 if version == 4:
    7 self.sock = socket(AF_INET ,SOCK_STREAM)
    8 else:
    9 self.sock = socket(AF_INET6 ,SOCK_STREAM)
    10 self.p = pyaudio.PyAudio()
    11 self.stream = None
    12 def __del__(self):
    13 self.sock.close()
    14 if self.stream is not None:
    15 self.stream.stop_stream()
    16 self.stream.close()
    17 self.p.terminate()
    18 def run(self):
    19 print("AUDIO server starts...")
    20 self.sock.bind(self.ADDR)
    21 self.sock.listen(1)
    22 conn, addr = self.sock.accept()
    23 print("remote AUDIO client success connected...")
    24 data = "".encode("utf-8")
    25 payload_size = struct.calcsize("L")
    26 self.stream = self.p.open(format=FORMAT,
    27 channels=CHANNELS,
    28 rate=RATE,
    29 output=True,
    30 frames_per_buffer = CHUNK
    31 )
    32 while True:
    33 while len(data) < payload_size:
    34 data += conn.recv(81920)
    35 packed_size = data[:payload_size]
    36 data = data[payload_size:]
    37 msg_size = struct.unpack("L", packed_size)[0]
    38 while len(data) < msg_size:
    39 data += conn.recv(81920)
    40 frame_data = data[:msg_size]
    41 data = data[msg_size:]
    42 frames = pickle.loads(frame_data)
    43 for frame in frames:
    44 self.stream.write(frame, CHUNK)
    45
    46 class Audio_Client(threading.Thread):
    47 def __init__(self ,ip, port, version):
    48 threading.Thread.__init__(self)
    49 self.setDaemon(True)
    50 self.ADDR = (ip, port)
    51 if version == 4:
    52 self.sock = socket(AF_INET, SOCK_STREAM)
    53 else:
    54 self.sock = socket(AF_INET6, SOCK_STREAM)
    55 self.p = pyaudio.PyAudio()
    56 self.stream = None
    57 def __del__(self) :
    58 self.sock.close()
    59 if self.stream is not None:
    60 self.stream.stop_stream()
    61 self.stream.close()
    62 self.p.terminate()
    63 def run(self):
    64 print("AUDIO client starts...")
    65 while True:
    66 try:
    67 self.sock.connect(self.ADDR)
    68 break
    69 except:
    70 time.sleep(3)
    71 continue
    72 print("AUDIO client connected...")
    73 self.stream = self.p.open(format=FORMAT,
    74 channels=CHANNELS,
    75 rate=RATE,
    76 input=True,
    77 frames_per_buffer=CHUNK)
    78 while self.stream.is_active():
    79 frames = []
    80 for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    81 data = self.stream.read(CHUNK)
    82 frames.append(data)
    83 senddata = pickle.dumps(frames)
    84 try:
    85 self.sock.sendall(struct.pack("L", len(senddata)) + senddata)
    86 except:
    87 break
    复制代码
    至此我们完成了 vchat.py 的编写。

     

    4.6 编写程序入口 main.py
    为了提供用户参数解析,代码使用了argparse。你可能对此前几个类中初始化方法的self.setDaemon(True)有疑惑。
    这个方法的调用使每个线程在主线程结束之后自动退出,保证程序不会出现崩溃且无法销毁的情况。在main.py中,
    我们通过每隔1s做一次线程的保活检查,如果视频/音频中出现阻塞/故障,主线程会终止。

    复制代码
    1 import sys
    2 import time
    3 import argparse
    4 from vchat import Video_Server, Video_Client
    5 from achat import Audio_Server, Audio_Client
    6
    7 parser = argparse.ArgumentParser()
    8
    9 parser.add_argument('--host', type=str, default='127.0.0.1')
    10 parser.add_argument('--port', type=int, default=10087)
    11 parser.add_argument('--level', type=int, default=1)
    12 parser.add_argument('-v', '--version', type=int, default=4)
    13
    14 args = parser.parse_args()
    15
    16 IP = args.host
    17 PORT = args.port
    18 VERSION = args.version
    19 LEVEL = args.level
    20
    21 if __name__ == '__main__':
    22 vclient = Video_Client(IP, PORT, LEVEL, VERSION)
    23 vserver = Video_Server(PORT, VERSION)
    24 aclient = Audio_Client(IP, PORT+1, VERSION)
    25 aserver = Audio_Server(PORT+1, VERSION)
    26 vclient.start()
    27 aclient.start()
    28 time.sleep(1) # make delay to start server
    29 vserver.start()
    30 aserver.start()
    31 while True:
    32 time.sleep(1)
    33 if not vserver.isAlive() or not vclient.isAlive():
    34 print("Video connection lost...")
    35 sys.exit(0)
    36 if not aserver.isAlive() or not aclient.isAlive():
    37 print("Audio connection lost...")
    38 sys.exit(0)
    复制代码

     

     

    最终可以实现和自己视频聊天

    最终代码:三个文件

    代码一:achat.py

    复制代码
      1 from socket import *
      2 import threading
      3 import cv2
      4 import re
      5 import sys
      6 import os
      7 import time
      8 import pyaudio
      9 import struct
     10 import pickle
     11 import zlib
     12 import wave
     13 
     14 
     15 CHUNK = 1024
     16 FORMAT = pyaudio.paInt16
     17 CHANNELS = 2
     18 RATE = 44100
     19 RECORD_SECONDS = 5
     20 WAVE_OUTPUT_FILENAME = "output.wav"
     21 
     22 
     23 
     24 class Audio_Server(threading.Thread):
     25     def __init__(self, port, version) :
     26         threading.Thread.__init__(self)
     27         self.setDaemon(True)
     28         self.ADDR = ('', port)
     29         if version == 4:
     30             self.sock = socket(AF_INET ,SOCK_STREAM)
     31         else:
     32             self.sock = socket(AF_INET6 ,SOCK_STREAM)
     33         self.p = pyaudio.PyAudio()
     34         self.stream = None
     35     def __del__(self):
     36         self.sock.close()
     37         if self.stream is not None:
     38             self.stream.stop_stream()
     39             self.stream.close()
     40         self.p.terminate()
     41 
     42     def run(self):
     43         print("AUDIO server starts...")
     44         self.sock.bind(self.ADDR)
     45         self.sock.listen(1)
     46         conn, addr = self.sock.accept()
     47         print("remote AUDIO client success connected...")
     48         data = "".encode("utf-8")
     49         payload_size = struct.calcsize("L")
     50         self.stream = self.p.open(format=FORMAT,
     51                                   channels=CHANNELS,
     52                                   rate=RATE,
     53                                   output=True,
     54                                   frames_per_buffer = CHUNK
     55                                   )
     56 
     57         while True:
     58             while len(data) < payload_size:
     59                 data += conn.recv(81920)
     60             packed_size = data[:payload_size]
     61             data = data[payload_size:]
     62             msg_size = struct.unpack("L", packed_size)[0]
     63             while len(data) < msg_size:
     64                 data += conn.recv(81920)
     65             frame_data = data[:msg_size]
     66             data = data[msg_size:]
     67             frames = pickle.loads(frame_data)
     68             for frame in frames:
     69                 self.stream.write(frame, CHUNK)
     70 
     71 class Audio_Client(threading.Thread):
     72     def __init__(self ,ip, port, version):
     73         threading.Thread.__init__(self)
     74         self.setDaemon(True)
     75         self.ADDR = (ip, port)
     76         if version == 4:
     77             self.sock = socket(AF_INET, SOCK_STREAM)
     78         else:
     79             self.sock = socket(AF_INET6, SOCK_STREAM)
     80         self.p = pyaudio.PyAudio()
     81         self.stream = None
     82     def __del__(self) :
     83         self.sock.close()
     84         if self.stream is not None:
     85             self.stream.stop_stream()
     86             self.stream.close()
     87         self.p.terminate()
     88     def run(self):
     89         print("AUDIO client starts...")
     90         while True:
     91             try:
     92                 self.sock.connect(self.ADDR)
     93                 break
     94             except:
     95                 time.sleep(3)
     96                 continue
     97         print("AUDIO client connected...")
     98         self.stream = self.p.open(format=FORMAT,
     99                              channels=CHANNELS,
    100                              rate=RATE,
    101                              input=True,
    102                              frames_per_buffer=CHUNK)
    103         while self.stream.is_active():
    104             frames = []
    105             for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    106                 data = self.stream.read(CHUNK)
    107                 frames.append(data)
    108             senddata = pickle.dumps(frames)
    109             try:
    110                 self.sock.sendall(struct.pack("L", len(senddata)) + senddata)
    111             except:
    112                 break
    复制代码

    代码二:vchat.py

    复制代码
    from socket import *
    import threading
    import cv2
    import re
    import time
    import sys
    import os
    import struct
    import pickle
    import zlib
    import wave
    
    
    class Video_Client(threading.Thread):
        def __init__(self ,ip, port, level, version):
            threading.Thread.__init__(self)
            self.setDaemon(True)
            self.ADDR = (ip, port)
            if level <= 3:
                self.interval = level
            else:
                self.interval = 3
            self.fx = 1 / (self.interval + 1)
            if self.fx < 0.3:
                self.fx = 0.3
            if version == 4:
                self.sock = socket(AF_INET, SOCK_STREAM)
            else:
                self.sock = socket(AF_INET6, SOCK_STREAM)
            self.cap = cv2.VideoCapture(0)
        def __del__(self) :
            self.sock.close()
            self.cap.release()
        def run(self):
            print("VEDIO client starts...")
            while True:
                try:
                    self.sock.connect(self.ADDR)
                    break
                except:
                    time.sleep(3)
                    continue
            print("VEDIO client connected...")
            while self.cap.isOpened():
                ret, frame = self.cap.read()
                sframe = cv2.resize(frame, (0,0), fx=self.fx, fy=self.fx)
                data = pickle.dumps(sframe)
                zdata = zlib.compress(data, zlib.Z_BEST_COMPRESSION)
                try:
                    self.sock.sendall(struct.pack("L", len(zdata)) + zdata)
                except:
                    break
                for i in range(self.interval):
                    self.cap.read()
    # 服务器端最终代码如下,增加了对接收到数据的解压缩处理。
    
    class Video_Server(threading.Thread):
        def __init__(self, port, version) :
            threading.Thread.__init__(self)
            self.setDaemon(True)
            self.ADDR = ('', port)
            if version == 4:
                self.sock = socket(AF_INET ,SOCK_STREAM)
            else:
                self.sock = socket(AF_INET6 ,SOCK_STREAM)
        def __del__(self):
            self.sock.close()
            try:
                cv2.destroyAllWindows()
            except:
                pass
        def run(self):
            print("VEDIO server starts...")
            self.sock.bind(self.ADDR)
            self.sock.listen(1)
            conn, addr = self.sock.accept()
            print("remote VEDIO client success connected...")
            data = "".encode("utf-8")
            payload_size = struct.calcsize("L")
            cv2.namedWindow('Remote', cv2.WINDOW_NORMAL)
            while True:
                while len(data) < payload_size:
                    data += conn.recv(81920)
                packed_size = data[:payload_size]
                data = data[payload_size:]
                msg_size = struct.unpack("L", packed_size)[0]
                while len(data) < msg_size:
                    data += conn.recv(81920)
                zframe_data = data[:msg_size]
                data = data[msg_size:]
                frame_data = zlib.decompress(zframe_data)
                frame = pickle.loads(frame_data)
                cv2.imshow('Remote', frame)
                if cv2.waitKey(1) & 0xFF == 27:
                    break
    复制代码

    代码三:main.py

    复制代码
    import sys
    import time
    import argparse
    import cv2
    import re
    import pyaudio
    import pickle
    import os
    import struct
    import zlib
    import wave
    from vchat import Video_Server, Video_Client
    from achat import Audio_Server, Audio_Client
    
    parser = argparse.ArgumentParser()
    
    parser.add_argument('--host', type=str, default='127.0.0.1')
    parser.add_argument('--port', type=int, default=10087)
    parser.add_argument('--level', type=int, default=1)
    parser.add_argument('-v', '--version', type=int, default=4)
    
    args = parser.parse_args()
    
    IP = args.host
    PORT = args.port
    VERSION = args.version
    LEVEL = args.level
    
    if __name__ == '__main__':
        vclient = Video_Client(IP, PORT, LEVEL, VERSION)
        vserver = Video_Server(PORT, VERSION)
        aclient = Audio_Client(IP, PORT+1, VERSION)
        aserver = Audio_Server(PORT+1, VERSION)
        vclient.start()
        aclient.start()
        time.sleep(1)    # make delay to start server
        vserver.start()
        aserver.start()
        while True:
    
            time.sleep(1)
            if not vserver.isAlive() or not vclient.isAlive():
                print("Video connection lost...")
                sys.exit(0)
            if not aserver.isAlive() or not aclient.isAlive():
                print("Audio connection lost...")
                sys.exit(0)
    复制代码

    新人学习python,多有不足,谢谢大家观看。

    转载于:https://www.cnblogs.com/nandadao/p/10181388.html

    展开全文
  • 本资源是本人前面发过的一个局域网聊天工具的升级版。主要在界面上进行了美化,并添加了语音聊天的功能。具体功能有: 1.采用了全新的界面风格(新增) 2.实现了基本文字聊天功能 3.实现了基本文件传送功能 4.实现了...
  • 点对点的局域网聊天软件,实现了文件传输和语音聊天
  • 局域网聊天工具

    2015-12-17 14:17:10
    模拟飞鸽传书的功能,只实现局域网聊天工具,暂不支持文件传送,语音的功能,留待后期优化
  • 局域网聊天通讯工具

    2018-01-06 16:43:54
    飞秋 飞Q 飞鸽 聊天 通讯 局域网 可以在相同网络中使用 办公方便快捷 直接聊天 语音 视频 传输文件等
  • 局域网语音/文件传送工具 支持同网段内语音/文字聊天、文件传送、聊天分组。 很小很强悍~!
  • delphi局域网聊天工具

    2017-05-09 13:33:56
    自带语音提示上线功能。局域网聊天,无服务端,简洁易懂。
  • 飞鸽传书v3.1 局域网内文件互传共享,聊天工具,支持语音,远程协助,表情等
  • QQ这类的通信工具不便于老板对员工的管理,而今QQ病毒大肆流行,老板们也需要考虑他们的商业机密是否会被盗窃的可能,因此,局域网聊天工具的发展便成了可能,老板们可以使用这类的工具,用文字告知公司里的人该做些...
  • 最近比较有空,花了点时间写了个android局域网聊天工具,使用java的异步tcp通信。基本功能实现(简单的界面,聊天记录,发送文字,发送语音),在此小结一下。 1,聊天功能 (ServerSocketChannel & SocketChannel) ...

    最近比较有空,花了点时间写了个android局域网聊天工具,使用java的异步tcp通信。基本功能实现(简单的界面,聊天记录,发送文字,发送语音),在此小结一下。

     

    Java (非android)局域网聊天工具源码,跟android的差别不大,参考:

    http://download.csdn.net/detail/yarkey09/7052573

     

    0,整个程序源码结构

    1,聊天功能 (ServerSocketChannel & SocketChannel)

    实现这个功能的时候有一个非常大的感受,就是写java程序真是方便!因为自己以前就写过windows上的java异步socket通信程序,所以这次几乎不需要修改很多代码,就可以搬过来。颇有Write one, run everywhere的feel。

    个人认为java.nio的核心就是Selector和Buffer吧。通过Selector轮询各个已注册的socket的事件。若没有事件,则阻塞,若有事件则返回。因为在android,主线程不能做太多事情,

    所以我起了一个新的线程,让Selector自个儿跑去。

    以下是TcpWorkerThread类的源码,主要完成三件事

    1,"开启"一个Selector

    2,提供registToSelector方法

    3,处理客户端的连接事件,处理socket接收消息事件

    Class : TcpWorkerThread

    package com.yarkey.tcp;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    import java.util.Set;
    
    import android.os.Handler;
    import android.util.Log;
    
    public class TcpWorkerThread extends Thread {
    
    	private static final String TAG = "TcpWorkerThread";
    
    	/** 出错!返回String,描述出错原因 */
    	public static final int EVENT_ERROR = 0;
    	/** 线程结束, 停止运行 */
    	public static final int EVENT_STOPPED = 1;
    	/** 收到来自客户端的tcp连接, 报告一个socketchannel */
    	public static final int EVENT_ACCEPTED = 2;
    	/** 收到来自客户端的tcp消息, 报告一个TcpArgs, content为FileSerial对象 */
    	public static final int EVENT_RECEIVED = 3;
    	/** SocketChannel,ServerSocketChannel关闭 */
    	public static final int EVENT_CLOSED = 4;
    
    	/** TCP线程往主线程通信 */
    	private Handler mHandler;
    
    	private Selector mSelector;
    	private boolean mIsRun = true;
    
    	protected static class TcpArgs {
    
    		SocketChannel sc;
    		Object content;// 接收消息
    	}
    
    	/**
    	 * 如果抛出异常,不能进行异步通信了
    	 * 
    	 * @throws Exception
    	 */
    	public TcpWorkerThread(Handler handler) throws Exception {
    		Log.d(TAG, "TcpWorkerThread contructor");
    		if (handler == null) {
    			throw new Exception("Handler is null!");
    		} else {
    			mHandler = handler;
    		}
    		mSelector = Selector.open();
    	}
    
    	/**
    	 * 将一个服务端的ServerSocketChannel设置为非阻塞模式,并将其注册到selector中(OP_ACCEPT)
    	 * 
    	 * @param ssc
    	 * @throws IOException
    	 */
    	public void registToSelector(ServerSocketChannel ssc) throws IOException {
    		Log.d(TAG, "registToSelector, ServerSocketChannel");
    		ssc.configureBlocking(false);
    		mSelector.wakeup();
    		ssc.register(mSelector, SelectionKey.OP_ACCEPT);
    	}
    
    	/**
    	 * 将一个客户端的SocketChannel设置为非阻塞模式,并将其注册到selector中(OP_READ)
    	 * 
    	 * @param ss
    	 * @throws IOException
    	 */
    	public void registToSelector(SocketChannel ss) throws IOException {
    		Log.d(TAG, "registToSelector, SocketChannel");
    		ss.configureBlocking(false);
    		mSelector.wakeup();
    		ss.register(mSelector, SelectionKey.OP_READ);
    	}
    
    	/**
    	 * 停止线程运行
    	 */
    	public void stopWorkerThread() {
    		Log.d(TAG, "stopWorkerThread");
    		mIsRun = false;
    		mSelector.wakeup();
    	}
    
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		Log.d(TAG, "线程开始运行,run()");
    
    		// 用于装入接收到的数据
    		ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    		while (mIsRun) {
    
    			int events = 0;
    			try {
    				events = mSelector.select();
    			} catch (IOException e1) {
    				// TODO Auto-generated catch block
    				e1.printStackTrace();
    				mHandler.obtainMessage(EVENT_ERROR, "Selector IOException").sendToTarget();// 出错
    				break;
    			}
    			if (events <= 0) {
    				// 走到这里,只能说明被wakeup了,应该是别的地方需要,因此这里暂停100ms
    				Log.d(TAG, "sleep 100 ms >>>");
    				try {
    					sleep(100);
    				} catch (InterruptedException e) {
    					// interrupt! ignore this
    					e.printStackTrace();
    				}
    				Log.d(TAG, "sleep 100 ms <<< wake up.");
    				continue;
    			}
    			Log.d(TAG, "mSelector.select(), events ===========================> " + events);
    			Set<SelectionKey> selectionKeys = mSelector.selectedKeys();
    			Iterator<SelectionKey> iter = selectionKeys.iterator();
    
    			// 代表连接成功后的socket
    			SocketChannel socketChannel;
    
    			while (iter.hasNext()) {
    
    				SelectionKey key = iter.next();
    				socketChannel = null;
    
    				// 服务端收到连接
    				if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
    					ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    					try {
    						socketChannel = ssc.accept();
    						Log.d(TAG, "ssc.accept()");
    					} catch (IOException e) {
    						e.printStackTrace();
    						try {
    							ssc.close();
    						} catch (IOException e1) {
    							// TODO Auto-generated catch block
    							e1.printStackTrace();
    						}
    						mHandler.obtainMessage(EVENT_CLOSED, ssc).sendToTarget();
    					}
    					if (socketChannel != null) {
    						try {
    							socketChannel.configureBlocking(false);
    							socketChannel.register(mSelector, SelectionKey.OP_READ);
    							Log.d(TAG, "来自客户端的新连接");
    							mHandler.obtainMessage(EVENT_ACCEPTED, socketChannel).sendToTarget();
    						} catch (IOException e) {
    							e.printStackTrace();
    							try {
    								socketChannel.close();
    							} catch (IOException e1) {
    								// TODO Auto-generated catch block
    								e1.printStackTrace();
    							}
    							mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();
    						}
    					} else {
    						Log.e(TAG, "socketChannel is null !");
    					}
    					iter.remove();
    				}
    				// 接收到客户的消息
    				else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
    					socketChannel = (SocketChannel) key.channel();
    					Log.d(TAG, "接收到新消息");
    
    					boolean hasException = false;
    					ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
    					while (true) {
    						// 把position设为0,把limit设为capacity
    						buffer.clear();
    						int a = 0;
    						try {
    							a = socketChannel.read(buffer);
    						} catch (Exception e) {
    							e.printStackTrace();
    
    							try {
    								socketChannel.close();
    							} catch (IOException e1) {
    								// TODO Auto-generated catch block
    								e1.printStackTrace();
    							}
    							hasException = true;
    							mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();
    							break;
    						}
    						Log.d(TAG, "a=" + a);
    						if (a == 0) {
    							break;
    						}
    						if (a == -1) {
    							try {
    								socketChannel.close();
    							} catch (IOException e) {
    								// TODO Auto-generated catch block
    								e.printStackTrace();
    							}
    							hasException = true;
    							mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();
    							Log.w(TAG, "读取到EOS,我们关闭了一个连接!");
    							break;
    						}
    						if (a > 0) {
    							buffer.flip();
    							try {
    								byteOutput.write(buffer.array());
    							} catch (IOException e) {
    								// TODO Auto-generated catch block
    								hasException = true;
    								e.printStackTrace();
    							}
    						}
    					}
    					if (!hasException) {
    						byte[] b = byteOutput.toByteArray();
    						TcpArgs args = new TcpArgs();
    						args.sc = socketChannel;
    						args.content = SerialUtil.toObject(b);
    						mHandler.obtainMessage(EVENT_RECEIVED, args).sendToTarget();
    					}
    					iter.remove();
    				}
    			}
    		}
    		// 关闭资源
    		try {
    			mSelector.close();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		Log.d(TAG, "线程结束运行");
    		mHandler.sendEmptyMessage(EVENT_STOPPED);
    	}
    }

    一般我们需要对socket处理的事件应该有三个:来自客户端的连接(accept),接收消息(read),发送消息(write)。TcpWorkerThread完成了前两件事,至于发送消息,我在另外一个类里面完成,也是新起一个线程,不过消息发送完后,发送线程也就停止了。

    以下TcpManager类有几个特点:

    1,静态单例

    2,拥有一个TcpWorkerThread对象

    3,拥有当前所有连接成功的socket

    4,具有“新建服务端”“新建客户端”“发送消息”方法

    5,采用register/notify机制,提供注册监听的方法

    6,处理两个特殊的TCP事件( socket连接后,还需要双方互发昵称,才算聊天建立成功;如果接收到音频文件,需要保存到SD卡中;)

    Class : TcpManager

    package com.yarkey.tcp;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.ArrayList;
    
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    
    import com.yarkey.groupchat.Global;
    import com.yarkey.tcp.TcpWorkerThread.TcpArgs;
    import com.yarkey.utils.RegistrantList;
    
    /**
     * 拥有TcpWorkerThread
     * 
     * @author yeqi.zhang
     * 
     */
    public class TcpManager {
    
    	private static final String TAG = "TcpManager";
    
    	private static TcpManager mInstance;// 单例
    
    	private static final String mRecFolder = TcpConfig.TCP_REC_FOLDER;// 保存文件的路径
    
    	private TcpWorkerThread mThread;// 异步线程
    
    	private ServerSocketChannel mServerSocketChannel;
    	private ArrayList<SocketChannel> mSocketChannelList;// 保存连接的socketChannel
    	private ArrayList<String> mNameList;// 保存对方名字
    
    	public static class MessageArgs {
    
    		public String name;// 对方的昵称
    		public String content;// 文件路径,或者消息内容
    	}
    
    	protected RegistrantList mRegistrantListConnect = new RegistrantList();
    	protected RegistrantList mRegistrantListReceiveAudio = new RegistrantList();
    	protected RegistrantList mRegistrantListReceiveText = new RegistrantList();
    	protected RegistrantList mRegistrantListError = new RegistrantList();
    
    	public ServerSocketChannel getServerSocketChannel() {
    		return mServerSocketChannel;
    	}
    
    	public ArrayList<SocketChannel> getSocketChannelList() {
    		return mSocketChannelList;
    	}
    
    	public ArrayList<String> getNameList() {
    		return mNameList;
    	}
    
    	/** 连接建立成功, obj=name */
    	public void registerForConnect(Handler h, int what, Object obj) {
    		mRegistrantListConnect.addUnique(h, what, obj);
    	}
    
    	public void unRegisterForConnect(Handler h) {
    		mRegistrantListConnect.remove(h);
    	}
    
    	/** 收到音频消息, obj=MessageArgs */
    	public void registerForReceiveAudio(Handler h, int what, Object obj) {
    		mRegistrantListReceiveAudio.addUnique(h, what, obj);
    	}
    
    	public void unRegisterForReceiveAudio(Handler h) {
    		mRegistrantListReceiveAudio.remove(h);
    	}
    
    	/** 收到文字消息, obj=MessageArgs */
    	public void registerForReceiveText(Handler h, int what, Object obj) {
    		mRegistrantListReceiveText.addUnique(h, what, obj);
    	}
    
    	public void unRegisterForReceiveText(Handler h) {
    		mRegistrantListReceiveText.remove(h);
    	}
    
    	/** 有错误发生, obj=errorReason */
    	public void registerForError(Handler h, int what, Object obj) {
    		mRegistrantListError.addUnique(h, what, obj);
    	}
    
    	public void unRegisterForError(Handler h) {
    		mRegistrantListError.remove(h);
    	}
    
    	/**
    	 * 如果TcpWorkerThread没有初始化成功,返回null !
    	 * 
    	 * @return
    	 */
    	public static TcpManager getInstance() {
    		Log.d(TAG, "getInstance");
    		if (mInstance == null) {
    			mInstance = new TcpManager();
    		}
    		if (mInstance.mThread == null) {
    			Log.e(TAG, "TcpWorkerThread 初始化不成功,这是致命的错误!");
    			return null;
    		}
    		return mInstance;
    	}
    
    	public static void release() {
    		Log.d(TAG, "release");
    		mInstance.mThread.stopWorkerThread();
    		// 释放监听的socket
    		if (mInstance.mServerSocketChannel != null) {
    			try {
    				mInstance.mServerSocketChannel.close();
    				Log.d(TAG, "release ServerSocketChannel!");
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		// 释放连接的socket
    		int i = 0;
    		for (; i < mInstance.mSocketChannelList.size(); i++) {
    			try {
    				mInstance.mSocketChannelList.get(i).close();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		Log.i(TAG, "release " + (i - 1) + " SocketChannels!");
    	}
    
    	private TcpManager() {
    		Log.d(TAG, "TcpManager private contructor");
    		mSocketChannelList = new ArrayList<SocketChannel>();
    		mNameList = new ArrayList<String>();
    		try {
    			mThread = new TcpWorkerThread(mThreadEventHandler);
    			mThread.start();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    			mThread = null;// 这个会导致getInstance 返回null
    		}
    	}
    
    	// public void setHandler(Handler h) {
    	// Log.d(TAG, "setHandler,h=" + h);
    	// mHandler = h;
    	// }
    
    	/**
    	 * 处理TcpWorkerThread
    	 */
    	private Handler mThreadEventHandler = new Handler() {
    
    		@Override
    		public void handleMessage(Message msg) {
    			Log.d(TAG, "mThreadEventHandler, handleMessage");
    			// TODO Auto-generated method stub
    			switch (msg.what) {
    			case TcpWorkerThread.EVENT_ACCEPTED:
    				Log.d(TAG, "TcpWorkerThread.EVENT_ACCEPTED");
    				sendName((SocketChannel) msg.obj);
    				break;
    
    			case TcpWorkerThread.EVENT_RECEIVED:
    				Log.d(TAG, "TcpWorkerThread.EVENT_RECEIVED");
    				TcpWorkerThread.TcpArgs argsRec = (TcpArgs) msg.obj;
    				SocketChannel socketch = argsRec.sc;
    				FileSerial objSerial = (FileSerial) argsRec.content;
    
    				// 准备发送一个MessageArgs对象
    				MessageArgs msgargs = null;
    				if (objSerial.getType() != FileSerial.TYPE_NAME) {
    					msgargs = new MessageArgs();
    					int index = 0;
    					for (; index < mSocketChannelList.size(); index++) {
    						if (socketch.equals(mSocketChannelList.get(index))) {
    							break;
    						}
    					}
    					msgargs.name = mNameList.get(index);
    				}
    
    				switch (objSerial.getType()) {
    
    				case FileSerial.TYPE_AUDIO:
    					Log.d(TAG, "收到音频信息");
    					FileOutputStream fileOut;
    					try {
    						fileOut = new FileOutputStream(mRecFolder + objSerial.getFileName());
    						fileOut.write(objSerial.getFileContent(), 0, (int) objSerial.getFileLength());
    						fileOut.close();
    						Log.d(TAG, "音频文件已保存到本地!");
    					} catch (IOException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					// if (mHandler != null) {
    					// mHandler.obtainMessage(EVENT_REC_AUDIO, mRecFolder +
    					// objSerial.getFileName()).sendToTarget();
    					// }
    					msgargs.content = mRecFolder + objSerial.getFileName();
    					mRegistrantListReceiveAudio.notifyResult(msgargs);
    					break;
    
    				case FileSerial.TYPE_TEXT:
    					Log.d(TAG, "收到文字信息");
    					// if (mHandler != null) {
    					// mHandler.obtainMessage(EVENT_REC_TEXT,
    					// objSerial.getFileName()).sendToTarget();
    					// }
    					msgargs.content = objSerial.getFileName();
    					Log.d(TAG, "message=" + objSerial.getFileName());
    					mRegistrantListReceiveText.notifyResult(msgargs);
    					break;
    
    				case FileSerial.TYPE_NAME:
    					Log.d(TAG, "收到对方名称,连接正式成功!");
    					mSocketChannelList.add(argsRec.sc);
    					mNameList.add(objSerial.getFileName());
    					// if (mHandler != null) {
    					// mHandler.obtainMessage(EVENT_CONNECT,
    					// objSerial.getFileName()).sendToTarget();
    					// }
    					mRegistrantListConnect.notifyResult(objSerial.getFileName());
    					break;
    				}
    				break;
    
    			case TcpWorkerThread.EVENT_ERROR:
    				Log.w(TAG, "error:" + (String) msg.obj);
    				break;
    			case TcpWorkerThread.EVENT_STOPPED:
    				Log.w(TAG, "EVENT_STOPPED");
    				break;
    			// ---------------------------------------------
    			case TcpAsyncClient.EVENT_CONNECTED:
    				TcpAsyncClient.TcpArgs args = (TcpAsyncClient.TcpArgs) msg.obj;
    				try {
    					mThread.registToSelector(args.result);
    					Log.d(TAG, "客户端连接服务端成功,发送昵称...");
    					sendName(args.result);
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    					this.sendEmptyMessage(TcpAsyncClient.EVENT_ERROR);
    				}
    				break;
    			case TcpAsyncClient.EVENT_ERROR:
    				Log.e(TAG, "客户端发起连接,发生错误!");
    				// mHandler.obtainMessage(EVENT_ERROR,
    				// "客户端发起连接失败").sendToTarget();
    				mRegistrantListError.notifyResult("客户端发起连接失败");
    				break;
    			}
    		}
    
    	};
    
    	/**
    	 * 作为客户端,连接到指定的地址。如果返回false,表示连接没成功,如果返回true,那么需要等待
    	 * 
    	 * @deprecated android较高的版本,不允许在主线程访问network
    	 * @param ip
    	 * @param port
    	 * @return
    	 */
    	public boolean newConnection(String ip, int port) {
    		TcpClient client = new TcpClient(ip, port);
    		SocketChannel sc = client.connect();
    		try {
    			mThread.registToSelector(sc);
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    			return false;
    		}
    		return true;
    	}
    
    	/**
    	 * 作为客户端,异步创建连接
    	 * 
    	 * @param ip
    	 * @param port
    	 */
    	public void newAsyncConnection(String ip, int port) {
    		Log.d(TAG, "newAsyncConnection, ip=" + ip + ",port=" + port);
    		TcpAsyncClient client = new TcpAsyncClient();
    		TcpAsyncClient.TcpArgs args = new TcpAsyncClient.TcpArgs();
    		args.handler = mThreadEventHandler;
    		args.ip = ip;
    		args.port = port;
    		client.connect(args);
    	}
    
    
    	/**
    	 * 作为服务端,启动服务监听
    	 * 
    	 * @param port
    	 * @return
    	 */
    	public boolean newServer(int port) {
    		Log.d(TAG, "newServer, port=" + port);
    
    		if (mServerSocketChannel != null) {
    			// serversocketchannel 不一定注册到selector成功,但是这里暂时只能允许存在一个
    			Log.w(TAG, "can only have one server for accepting !");
    		} else {
    			TcpServer server = new TcpServer(port);
    			mServerSocketChannel = server.accepting();
    		}
    
    		try {
    			mThread.registToSelector(mServerSocketChannel);
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    			return false;
    		}
    		return true;
    	}
    
    	/**
    	 * 发送给对方,昵称/文字消息/文件
    	 * 
    	 * @param sc
    	 *            SocketChannel
    	 * @param content
    	 *            name/message/filepath
    	 * @param fileName
    	 *            if type==AUDIO, you should set fileName
    	 * @param type
    	 *            {@link FileSerial#TYPE_TEXT}, {@link FileSerial#TYPE_NAME},
    	 *            {@link FileSerial#TYPE_AUDIO}
    	 */
    	private void send(final SocketChannel sc, String content, String fileName, int type) {
    		Log.d(TAG, "content=" + content + ",fileName=" + fileName + ",type=" + type);
    		FileSerial fpo = new FileSerial();
    		// type
    		fpo.setType(type);
    		switch (type) {
    		case FileSerial.TYPE_AUDIO:
    			// name
    			fpo.setFileName(fileName);
    			// length
    			File f = new File(content);
    			long fileLength = f.length();
    			fpo.setFileLength(fileLength);
    			// content
    			FileInputStream fis = null;
    			byte[] fileContent = new byte[(int) fileLength];
    			try {
    				fis = new FileInputStream(content);
    				fis.read(fileContent, 0, (int) fileLength);
    				fis.close();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    
    			fpo.setFileContent(fileContent);
    
    			break;
    		case FileSerial.TYPE_TEXT:
    		case FileSerial.TYPE_NAME:
    			// name
    			fpo.setFileName(content);
    			// length
    			fpo.setFileLength(content.length());
    			// content
    			// null
    			break;
    		}
    		// toByte -> send
    		byte[] bytes = SerialUtil.toByte(fpo);
    		final ByteBuffer buffer = ByteBuffer.wrap(bytes);
    
    		// E/AndroidRuntime(18606): android.os.NetworkOnMainThreadException
    		new Thread() {
    
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				Log.d(TAG, "send, async ! 异步发送 ... ");
    				try {
    					// 此处在主线程调用, 有可能会阻塞!
    					sc.write(buffer);
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    
    		}.start();
    	}
    
    	/**
    	 * 发送音频文件
    	 * 
    	 * @param index
    	 * @param content
    	 * @param fileName
    	 */
    	public void sendAudioFile(int index, String content, String fileName) {
    		Log.d(TAG, "sendAudioFile, index=" + index + ",content=" + content + ",fileName=" + fileName);
    		SocketChannel sc = mSocketChannelList.get(index);
    		send(sc, content, fileName, FileSerial.TYPE_AUDIO);
    	}
    
    	/**
    	 * 发送音频文件
    	 * 
    	 * @param name
    	 * @param content
    	 * @param fileName
    	 */
    	public void sendAudioFile(String name, String content, String fileName) {
    		Log.d(TAG, "sendAudioFile, name=" + name + ",content=" + content + ",fileName=" + fileName);
    		int index = -1;
    		for (int i = 0; i < mNameList.size(); i++) {
    			if (mNameList.get(i).equals(name)) {
    				index = i;
    				break;
    			}
    		}
    		if (index != -1) {
    			sendAudioFile(index, content, fileName);
    		} else {
    			Log.w(TAG, "sendAudioFile, name=" + name + " not found !");
    		}
    	}
    
    	/**
    	 * 发送文字消息
    	 * 
    	 * @param index
    	 * @param msg
    	 */
    	public void sendMessage(int index, String msg) {
    		Log.d(TAG, "sendMessage, index=" + index + ",msg=" + msg);
    		SocketChannel sc = mSocketChannelList.get(index);
    		send(sc, msg, null, FileSerial.TYPE_TEXT);
    	}
    
    	/**
    	 * 发送文字消息
    	 * 
    	 * @param name
    	 * @param msg
    	 */
    	public void sendMessage(String name, String msg) {
    		Log.d(TAG, "sendMessage, name=" + name + ",msg=" + msg);
    		int index = -1;
    		for (int i = 0; i < mNameList.size(); i++) {
    			if (mNameList.get(i).equals(name)) {
    				index = i;
    				break;
    			}
    		}
    		if (index != -1) {
    			sendMessage(index, msg);
    		} else {
    			Log.w(TAG, "sendMessage, name=" + name + " not found !");
    		}
    	}
    
    	/**
    	 * TCP连接后,需要双方相互发送昵称,才能算连接正式成立!
    	 * 
    	 * @param sc
    	 * @param name
    	 */
    	private void sendName(SocketChannel sc) {
    		Log.d(TAG, "sendName, name=" + Global.NAME_LOCAL);
    		send(sc, Global.NAME_LOCAL, null, FileSerial.TYPE_NAME);
    	}
    }
    

    请注意类里面每个方法的权限(private,protected,public),我把这个类作为整个聊天工具TCP的核心类,具有所有需要的TCP操作方法。

    TcpManager中还用到TcpAsyncClient,TcpServer,这两个类完成ServerSocketChannel, SocketChannel的“打开”,打开完成后便可以将他们注册到Selector了。

    Class : TcpAsyncClient

    主要方法:由于较新版本的android,不允许在主线程做网络操作,所以以下代码在一个新的线程中执行!

    SocketChannel sc = SocketChannel.open();
    sc.connect(new InetSocketAddress(args.ip, args.port));

    Class : TcpServer

    主要方法:

    	/**
    	 * 如果初始化不成功,返回null
    	 * 
    	 * @param port
    	 * @return
    	 */
    	protected ServerSocketChannel accepting(int port) {
    		Log.d(TAG, "accepting,port=" + port);
    		ServerSocketChannel ssc = null;
    		try {
    			ssc = ServerSocketChannel.open();
    			ssc.configureBlocking(false);
    			ServerSocket ss = ssc.socket();
    			ss.bind(new InetSocketAddress(port));
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    			return null;
    		}
    		return ssc;
    	}

    TCP通信功能大概就这么多吧,另外有一个关于序列化的类FileSerial,在“语音发送与接收”再说一下。

    2,聊天记录 (AsyncQueryHandler)

    聊天记录使用android自带的Sqlite数据库做持久化。由于数据库访问可能会花很多时间,不宜在主线程操作,所以在这里主要关键考虑如果完成“异步”访问数据库。

    android的AsyncQueryHandler类写得很好,我只是在它的基础上做了一点小修改就可以用了。拿来主义^_^

    Class : ChatAsyncQueryHandler

    package com.yarkey.database;
    
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.os.Handler;
    import android.os.HandlerThread;
    import android.os.Looper;
    import android.os.Message;
    import android.util.Log;
    
    
    public abstract class ChatAsyncQueryHandler extends Handler {
    
    	private static final String TAG = "ChatAsyncQueryHandler";
    
    	private ChatDatabase mDatabase;
    
    	private static Looper mLooper = null;
    	private Handler mWorkerHandler = null;
    
    	private static final int EVENT_QUERY = 0;
    	private static final int EVENT_INSERT = 1;
    	private static final int EVENT_UPDATE = 2;
    	private static final int EVENT_DELECT = 3;
    
    
    	public ChatAsyncQueryHandler(ChatDatabase database) {
    		mDatabase = database;
    		synchronized (ChatAsyncQueryHandler.class) {
    			if (mLooper == null) {
    				HandlerThread thread = new HandlerThread("ChatAsyncQueryHandler");
    				thread.start();
    				mLooper = thread.getLooper();
    			}
    		}
    		mWorkerHandler = new WorkerHandler(mLooper);
    	}
    
    	protected static final class WorkerArgs {
    
    		public Handler handler;
    		public String[] projection;
    		public String selection;
    		public ContentValues values;
    
    		public Object result;
    	}
    
    	/**
    	 * 查询。通过两个字段,查询ID,DATA,TIME,TYPE,SELF,CONTENT
    	 * 
    	 * @param token
    	 * @param localName
    	 * @param remoteName
    	 */
    	public void startQuery(int token, String localName, String remoteName) {
    		Log.d(TAG, "startQuery, token=" + token + ",localName=" + localName + ",remoteName=" + remoteName);
    		Message msg = mWorkerHandler.obtainMessage(token);
    		msg.arg1 = EVENT_QUERY;
    		WorkerArgs args = new WorkerArgs();
    		args.handler = this;
    		args.projection = new String[] { ChatLog.C_ID, ChatLog.C_DATE, ChatLog.C_TIME, ChatLog.C_TYPE, ChatLog.C_SELF,
    				ChatLog.C_CONTENT };
    		args.selection = ChatLog.C_LOCAL + "=\"" + localName + "\" AND " + ChatLog.C_REMOTE + "=\"" + remoteName + "\"";
    		msg.obj = args;
    		mWorkerHandler.sendMessage(msg);
    	}
    
    	public void startInsert(int token, ContentValues values) {
    		Log.d(TAG, "startInsert, values=" + values);
    		Message msg = mWorkerHandler.obtainMessage(token);
    		msg.arg1 = EVENT_INSERT;
    		WorkerArgs args = new WorkerArgs();
    		args.handler = this;
    		args.values = values;
    		msg.obj = args;
    		mWorkerHandler.sendMessage(msg);
    	}
    
    	protected class WorkerHandler extends Handler {
    
    		public WorkerHandler(Looper looper) {
    			super(looper);
    			// TODO Auto-generated constructor stub
    		}
    
    		@Override
    		public void handleMessage(Message msg) {
    			// TODO Auto-generated method stub
    			WorkerArgs args = (WorkerArgs) msg.obj;
    			int token = msg.what;
    			int event = msg.arg1;
    
    			switch (event) {
    			case EVENT_QUERY:
    				args.result = mDatabase.query(args.projection, args.selection);
    				break;
    			case EVENT_INSERT:
    				mDatabase.insert(args.values);
    				break;
    			case EVENT_UPDATE:
    				break;
    			case EVENT_DELECT:
    				break;
    			}
    
    			Message reply = args.handler.obtainMessage(token);
    			reply.obj = args;
    			reply.arg1 = event;
    			reply.sendToTarget();
    		}
    
    	}
    
    	protected void onQueryCompleted(int token, Cursor cursor) {
    		// empty
    	}
    
    	protected void onInsertCompleted(int token) {
    		// empty
    	}
    
    	@Override
    	public void handleMessage(Message msg) {
    		// TODO Auto-generated method stub
    		WorkerArgs args = (WorkerArgs) msg.obj;
    		int token = msg.what;
    		int event = msg.arg1;
    		switch (event) {
    		case EVENT_QUERY:
    			onQueryCompleted(token, (Cursor) args.result);
    			break;
    		case EVENT_INSERT:
    			onInsertCompleted(token);
    			break;
    		case EVENT_UPDATE:
    			break;
    		case EVENT_DELECT:
    			break;
    		}
    	}
    }
    


    至于ChatDatabase类,继承于SQLiteOpenHelper,就不多说了。

    3,语音发送与接收 (ObjectOutputStream)

    本聊天工具通过发送音频文件,来实现语音聊天的。其他真正的聊天工具怎么实现的就不得而知了,有知道的网友也请分享一下^^。在这里主要需要解决几个问题:

    1,录音与录音播放 (MediaRecorder, MediaPlayer)

    MediaRecorder 与 MediaPlayer 的例子网上有很多了,本人也是一知半解,不敢在这里说太多。主要是通过MediaRecorder调用录音方法,结束后,我们得到一个存放在SD卡中的.3gp文件,有了这个文件,便可以调用MediaPlayer来播放它了!

    可能需要注意的地方有几个:MediaRecorder 录音超时,系统是有一个上限的,设计程序的时候需要注意一下;另外,是关于MediaRecorder什么时候释放,在本程序里面,每次用的时候就new一个,每次用完都调用release方法释放它。不过,这里似乎需要考虑一些问题,就没有做过多了解了。

    2,录音文件的发送与接收

    TCP发送文件,一开始用java.io的时候,我用的是ObjectOutputStream, ObjectInputStream来序列化一个类然后发出去( 即writeObject方法 )。

    writeObject 方法的输入参数是一个FileSerial对象,实现Serializable接口

    Class : FileSerial

    package com.yarkey.tcp;
    
    import java.io.Serializable;
    
    public class FileSerial implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    
    	private String fileName; // 文件名称
    	private long fileLength; // 文件长度
    	private byte[] fileContent; // 文件内容
    	private int type;
    
    	/** name保存在filename字段里面! */
    	protected static final int TYPE_NAME = 0;// 连接成功后,向对方发出自己的名字
    	/** text保存在filename字段里面! */
    	protected static final int TYPE_TEXT = 1;// 文字
    	protected static final int TYPE_AUDIO = 2;// 音频
    
    	// public static final int TYPE_PICTURE = 3;// 图片
    
    	public int getType() {
    		return type;
    	}
    
    	public void setType(int t) {
    		type = t;
    	}
    
    	public String getFileName() {
    		return fileName;
    	}
    
    	public void setFileName(String fileName) {
    		this.fileName = fileName;
    	}
    
    	public long getFileLength() {
    		return fileLength;
    	}
    
    	public void setFileLength(long fileLength) {
    		this.fileLength = fileLength;
    	}
    
    	public byte[] getFileContent() {
    		return fileContent;
    	}
    
    	public void setFileContent(byte[] fileContent) {
    		this.fileContent = fileContent;
    	}
    }
    

    java.io发送FileSerial对象源码:其中:

    ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());

    	/**
    	 * 
    	 * @param filePath
    	 *            C:/haha.java
    	 * @param fileName
    	 *            haha.java
    	 */
    	public void sendFile(String filePath, String fileName) {
    		Log.d(TAG, fileName);
    		if (out == null) {
    			return;
    		}
    		try {
    			FileSerial fpo = new FileSerial();
    			// type
    			fpo.setType(FileSerial.TYPE_AUDIO);
    			// name
    			fpo.setFileName(fileName);
    			// length
    			File f = new File(filePath);
    			long fileLength = f.length();
    			fpo.setFileLength(fileLength);
    			// content
    			FileInputStream fis = new FileInputStream(filePath);
    			byte[] fileContent = new byte[(int) fileLength];
    			fis.read(fileContent, 0, (int) fileLength);
    			fis.close();
    			fpo.setFileContent(fileContent);
    			// send
    			long start = System.currentTimeMillis();
    			out.writeObject(fpo);
    			long end = System.currentTimeMillis();
    			System.out.println("It takes " + (end - start) + "ms");
    			out.flush();
    			out.reset();
    
    		} catch (FileNotFoundException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    
    	}


    可是,用java.nio的时候,就会出错了!我们需要将需要发送的内容装在一个Buffer( ByteBuffer )中,然后通过SocketChannel的 write 方法发送出去。

    		// toByte -> send
    		byte[] bytes = SerialUtil.toByte(fpo);
    		final ByteBuffer buffer = ByteBuffer.wrap(bytes);
    
    		// E/AndroidRuntime(18606): android.os.NetworkOnMainThreadException
    		new Thread() {
    
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				Log.d(TAG, "send, async ! 异步发送 ... ");
    				try {
    					// 此处不能在主线程调用!
    					sc.write(buffer);
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    
    		}.start();


    用到一个工具类SerialUtil,转换对象 FileSerial -> byte[] , byte[] -> FileSerial

    class : SerialUtil

    package com.yarkey.tcp;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    import android.util.Log;
    
    
    public class SerialUtil {
    
    	private static final String TAG = "SerialUtil";
    
    	public static byte[] toByte(Object obj) {
    		Log.d(TAG, "toByte");
    		ByteArrayOutputStream baos = new ByteArrayOutputStream();
    		ObjectOutputStream oos = null;
    		try {
    			oos = new ObjectOutputStream(baos);
    			oos.writeObject(obj);
    			byte[] bytes = baos.toByteArray();
    			return bytes;
    		} catch (IOException ex) {
    			throw new RuntimeException(ex.getMessage(), ex);
    		} finally {
    			try {
    				oos.close();
    			} catch (Exception e) {
    			}
    		}
    	}
    
    	/** 此方法byte[]数组长度确定 */
    	public static Object toObject(byte[] bytes) {
    		Log.d(TAG, "toObject");
    		ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    		ObjectInputStream ois = null;
    		try {
    			ois = new ObjectInputStream(bais);
    			Object object = ois.readObject();
    			return object;
    		} catch (IOException ex) {
    			throw new RuntimeException(ex.getMessage(), ex);
    		} catch (ClassNotFoundException ex) {
    			throw new RuntimeException(ex.getMessage(), ex);
    		} finally {
    			try {
    				ois.close();
    			} catch (Exception e) {
    			}
    		}
    	}
    }
    

    音频文件的发送大概情况就是上面说的了,但是接收有另外一个问题。我们不知道音频文件的大小,所以不知道需要多大的ByteBuffer来接收它。

    所以,在TcpWorkerThread中,处理接收到的消息时,还用到一个

    ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();

    for( 循环 ){

        byteOutput.write(buffer.array()); // 分次保存byteBuffer中的数据

    }

    byte[] b = byteOutput.toByteArray(); //最后一口气弄出来


    我们申请的bytebuffer就1024个字节的空间,超过1024个字节,所以我们分次“存”到这个ByteArrayOutputStream中,最后一口气全部弄出来。然后调用SerialUtil将byte[]装换成FileSerial类对象。

    展开全文
  • 局域网聊天工具飞秋FeiQ,有很强大的功能,包括远程桌面,语音,下载,共享,截图,发图片等等。
  • 很简单的局域网聊天工具 可以局域网聊天,不需要服务器,可传送文件,可语音聊天
  • 1、信息收发送 2、语音聊天 3、记事本 4、提醒功能 5、远程协助功能 6、动态截图功能
  • 《布谷鸟2012》企业通讯软件/企业通讯工具/局域网聊天软件/局域网通讯软件/局域网通讯工具/局域网即时通讯软件/局域网即时通讯工具。  布谷鸟具有通讯聊天、群聊、传送文件、离线文件、远程协助、录音留言、消息...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 196
精华内容 78
关键字:

局域网语音聊天工具