python(opencv + pyaudio + moviepy)实现录制音视频文件并合并

使用opencv录制视频文件
def record_webcam(filename):
    """
        cv2.VideoCapture(0, cv2.CAP_DSHOW)
        参数1:打开前置摄像头参数是0,打开后置摄像头参数是1,如果多个摄像头,需要测试2,3其他参数,参数是视频文件路径则打开视频,如cap = cv2.VideoCapture(“../test.avi”)
        参数2: ***设置cv2.CAP_DSHOW参数初始化摄像头,否则无法使用更高分辨率
        ***(win7需要使用cv2.CAP_DSHOW的方式初始化摄像头开始录屏,默认的方式(CAP_ANY),不能打开。win10和winserver可以使用默认的方式初始化摄像头)
    """
    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    """
       ***调用cv2.VideoWriter()方法保存视频之前需先设置摄像头,否则保存视频时设备的宽高不能生效
    """
    cap.set(3, 640) # 视频流的帧的宽度
    cap.set(4, 480) # 在视频流的帧的高度
    print('开始录屏')
    """ 
    ***视频的帧率,即每秒传递的帧数。需要根据实现摄像头的帧率调整,达到最好的效果,否则,录制的视频可能偏快或者偏慢
    """
    ftp = 22 
    """
    ***使用设摄像头录制高清视频的时候一定要设置为MJPG,别的格式无法支持高清
    """
    cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) 
    aviFile = cv2.VideoWriter(filename,
                            cv2.VideoWriter_fourcc(*'MJPG'),
                              ftp, (640, 480), True) 
    print("初始化好摄像头之后开始录屏", str(datetime.now()))
    while True:
        ret, frame = cap.read()
        if ret:
            font = cv2.FONT_HERSHEY_SIMPLEX
            datet = str(datetime.now())[:19].replace(":", "-")
            frame = cv2.putText(frame, datet, (10, 30), font, 0.5,
                                (255, 255, 255), 1, cv2.LINE_AA)  # 是视频里面显示时间或者文字
            aviFile.write(frame)
            cv2.imshow('frame', frame)
        else:
            break
    print("结束录屏", str(datetime.now()))
    aviFile.release()
    cap.release()

注意 *** 标注的地方


使用pyaudio录制音频文件
"""
chunk_size: 每个缓冲区的帧数
channels: 单声道
rate: 采样频率
"""
CHUNK_SIZE = 1024
CHANNELS = 2
FORMAT = pyaudio.paInt16
RATE = 48000
allowRecording = True

def record_audio(filename):
    p = pyaudio.PyAudio()
    print('开始录音')
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK_SIZE
                    )
    print("stream", str(datetime.now()))

    wf = wave.open(filename, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    while allowRecording:
        data = stream.read(CHUNK_SIZE)
        wf.writeframes(data)
    print("streame结束", str(datetime.now()))
    wf.close()
    stream.stop_stream()
    stream.close()
    p.terminate()

使用moviepy合并文件
 # # # 实现音频视频合成
print("video audio merge!!!!!")
audioclip = AudioFileClip(audio_filename)
videoclip = VideoFileClip(video_filename)
videoclip2 = videoclip.set_audio(audioclip)
video = CompositeVideoClip([videoclip2])
 """ *** bitrate 设置比特率,比特率越高, 合并的视频越清晰,视频文件也越大,合并的速度会很慢"""
video.write_videofile(video_name, codec='mpeg4', bitrate='2000k')  

依赖的版本

	python==3.6.0
	opencv-python==4.6.0.66  下载过程中会报错,但是可以使用
	moviepy==1.0.3
	PyAudio==0.2.11

完整的文件

# !/usr/bin/env python
# -*-coding:utf-8 -*-

"""
# File       : manage.py
# Time       :2021/7/13 21:15
# Author     : JuanZi
# version    :python 3.6
# Description:
"""

import wave
import threading
from datetime import datetime, date, timedelta
import pyaudio
import cv2
from moviepy.editor import *
import time
import os
from ftplib import FTP
import socket  # 主要用于获取当前主机IP地址
import requests
import json
from multiprocessing import Process, Queue

"""全局变量,主要控制录音和录屏的同步"""
allowRecording = False
Q = Queue()


def run_test(main_class, file_name):
    marge = main_class()
    marge.run(file_name)


class AudioThread(threading.Thread):
    def __init__(self, event, filename):
        threading.Thread.__init__(self)
        self.p = pyaudio.PyAudio()
        self.event = event
        self.FORMAT = pyaudio.paInt16
        self.stream = self.p.open(format= pyaudio.paInt16,
                                  channels=2,
                                  rate=48000,
                                  input=True,
                                  frames_per_buffer=1024
                                  )  # 打开数据流

        self.wf = wave.open(filename, 'wb')
        self.wf.setnchannels(2)
        self.wf.setsampwidth(self.p.get_sample_size(self.FORMAT))
        self.wf.setframerate(48000)

    def run(self):
        self.event.wait()
        print('初始化完成,开始录音', str(datetime.now()))
        while allowRecording:
            data = self.stream.read(1024)
            self.wf.writeframes(data)
        self.wf.close()
        self.stream.stop_stream()  # 关闭流
        self.stream.close()
        self.p.terminate()


class VideoThread(threading.Thread):
    """
    out 是VideoWriter的实列对象,就是写入视频的方式,第一个参数是存放写入视频的位置,
    第二个是编码方式,20是帧率,最后是视频的高宽,如果录入视频为灰度,则还需加一个false
    """

    def __init__(self, cap, event, filename):
        threading.Thread.__init__(self)
        self.event = event
        self.cap = cap
        self.cap.set(3, 640)
        self.cap.set(4, 480)
        fourcc = cv2.VideoWriter_fourcc(*'MJPG')  # 设置视频编码方式,如果设置CAP_DSHOW的方式打开摄像头必须设置*'MJPG'的便码方式
        fps = 15 + 0.000001 * 15
        self.out = cv2.VideoWriter(filename, fourcc, fps, (640, 480))

    def run(self):
        """
        read()函数表示按帧读取视频,success,frame是read()的两个返回值,
        ret是布尔值——如果读取帧是正确的则返回True,如果文件读取到结尾则返回False,Frame表示的是每一帧的图像,是一个三维矩阵
        """
        time.sleep(0.5)
        self.event.set()
        print('初始化完成,开始录屏 %s',str(datetime.now()))
        while allowRecording:
            ret, frame = self.cap.read()
            if ret:
                font = cv2.FONT_HERSHEY_SIMPLEX
                datet = str(datetime.now())[:19].replace(":", "-")
                frame = cv2.putText(frame, datet, (10, 30), font, 0.5,
                                    (255, 255, 255), 1, cv2.LINE_AA) # 是视频里面显示时间或者文字
                cv2.imshow('frame', frame)  # 显示视频
                self.out.write(frame)  # 写视频到视频文件
            else:
                break
        self.out.release()


# 使用线程的方式合并音视频
class VideoMerge(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.cur_path = os.path.abspath(os.path.dirname(__file__))
        self.path = self.cur_path + '\\static\\'
        self.merge_path = self.path + 'merge\\'
        self.Lock = threading.Lock()
        self.need_video_merge = list()
        self.init()

    def init(self):
        if not os.path.exists(self.path):
            os.mkdir(self.path)
        if not os.path.exists(self.merge_path):
            os.mkdir(self.merge_path)

    def run(self):
        while True:
            filename = None
            with self.Lock:
                if len(self.need_video_merge) > 0:
                    filename = self.need_video_merge[0]
            if filename is not None:
                try:
                    audio_filename = self.path + filename + '.mp3'
                    video_filename = self.path + filename + '.avi'
                    merge_filename = self.merge_path + filename + '.avi'
                    audioclip = AudioFileClip(audio_filename)
                    videoclip = VideoFileClip(video_filename)
                    videoclip2 = videoclip.set_audio(audioclip)
                    video = CompositeVideoClip([videoclip2])
                    video.write_videofile(merge_filename, codec='mpeg4', bitrate='2000k') # bitrate 设置比特率,比特率越高, 合并的视频越清晰,视频文件也越大
                    print("删除本地视频,音频文件……")
                    os.remove(video_filename)
                    os.remove(audio_filename)
                    self.need_video_merge.pop(0)
                    print("文件 %s 合并成功" % filename)
                except Exception as e:
                    print("文件 %s 合并失败 %s" % (filename, e))

    def push_list(self, filename):
        with self.Lock:
            self.need_video_merge.append(filename)

# 使用进程的方式合并音视频
# class VideoMerge():
#     def __init__(self):
#         self.cur_path = os.path.abspath(os.path.dirname(__file__))
#         self.path = self.cur_path + '\\static\\'
#         self.merge_path = self.path + 'merge\\'
#         self.Lock = threading.Lock()
#         self.need_video_merge = list()
#         self.init()
#
#     def init(self):
#         if not os.path.exists(self.path):
#             os.mkdir(self.path)
#         if not os.path.exists(self.merge_path):
#             os.mkdir(self.merge_path)
#
#     def run(self, filename):
#         if filename is not None:
#             try:
#                 audio_filename = self.path + filename + '.mp3'
#                 video_filename = self.path + filename + '.avi'
#                 merge_filename = self.merge_path + filename + '.avi'
#                 audioclip = AudioFileClip(audio_filename)
#                 videoclip = VideoFileClip(video_filename)
#                 videoclip2 = videoclip.set_audio(audioclip)
#                 video = CompositeVideoClip([videoclip2])
#                 video.write_videofile(merge_filename, codec='mpeg4', bitrate='2000k') # bitrate 设置比特率,比特率越高, 合并的视频越清晰,视频文件也越大
#                 print("删除本地视频,音频文件……")
#                 os.remove(video_filename)
#                 os.remove(audio_filename)
#                 # self.need_video_merge.pop(0)
#                 print("文件 %s 合并成功" % filename)
#             except Exception as e:
#                 print("文件 %s 合并失败 %s" % (filename, e))


class CameraRecord(object):
    def __init__(self):
        self.cap = None
        self.p = None
        self.video = None
        self.audio = None
        self.merge = None
        self.event = threading.Event()
        self.cur_time = ''
        self.filename = ''

    def init(self):

        """
            参数1:打开前置摄像头参数是0,打开后置摄像头参数是1,如果多个摄像头,需要测试2,3其他参数,参数是视频文件路径则打开视频,如cap = cv2.VideoCapture(“../test.avi”)
            参数2: 设置cv2.CAP_DSHOW参数初始化摄像头,否则无法使用更高分辨率
        """
        self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        print('打开系统摄像头')

        # 使用线程的方式合并音视频
        self.merge = VideoMerge()
        self.merge.start()

    def start_record(self):
        if self.video is not None:
            print('摄像功能正在使用中')
            return False
        global allowRecording
        allowRecording = True
        self.filename = str(datetime.now())[:19].replace(":", "-").replace(' ', "-").replace('-', "")
        audio_filename = f'static\\{self.filename}.mp3'
        video_filename = f'static\\{self.filename}.avi'
        """启用线程开始录音、录屏"""
        self.video = VideoThread(self.cap, self.event, video_filename)
        self.audio = AudioThread(self.event, audio_filename)
        for t in (self.video, self.audio):
            t.start()

    def stop_record(self):
        """结束录屏"""
        if self.video is None:
            print('摄像功能没有在使用中')
            return False
        global allowRecording
        allowRecording = False
        self.video = None
        self.audio = None
        # 使用进程的方式去处理音视频的合并
        self.merge.push_list(self.filename)
        # 使用进程的方法处理音视频的合并,进度的启用开销大
        # a = Process(target=run_test, args=(VideoMerge, self.filename))
        # a.start()
        # a.join()

        print('此次录屏结束, 录屏开始时间:%s,' % self.filename)


if __name__ == '__main__':
    camera = CameraRecord()
    camera.init()

    camera.start_record()
    count = 30
    while count:
        count -= 1
        print(count)
        time.sleep(1)
    camera.stop_record()
  • 完整的文件做了封装处理,实现了控制开始录制、结束录制、合并文件过程
  • 使用了三个线程,一个处理录音,一个处理录屏,使用threading.Event()方法实现同步录音录屏
    一个处理合并文件(线程处理合并文件,当有其他线程处理很多其他事情的时候,合并会很慢)
    同时也介绍了使用进程的方式合并文件(开启进程的开销大,合并快)
  • 使用了 allowRecording 控制 音视频的开始录制和结束录制
原创不易,有不对的地方望指正~

你可能感兴趣的:(Python,opencv,python,音视频)