使用 OpenCV 和 ffpyplayer 进行音视频同步

Posted

技术标签:

【中文标题】使用 OpenCV 和 ffpyplayer 进行音视频同步【英文标题】:Audio and video synchronization with OpenCV and ffpyplayer 【发布时间】:2021-09-22 14:42:42 【问题描述】:

我目前正在开发一个“游戏”,它允许用户在观看视频时移动圆圈的位置。视频显示了两个人,每个人轮流发言。用户的任务是改变出现在当前演讲者面前的圆圈的位置。在发生这种情况时,我计划在某个时候更改视频,而不会让用户注意到,同时“游戏”和圆圈继续显示。

为了达到这个目的,我写了下面的代码。该代码从用户那里获取输入,并将所有数据发送到 TCP 服务器并将信息打印到记录器文件中。但是我遇到了一个问题。首先,音频和视频不同步,即使使用waitkey(1)的最低值,音频也比视频快

任何有关如何解决此问题的帮助将不胜感激。提前致谢。

PS- 我使用的是 Visual Studio 代码,我的 python 版本是 3.9.6 64 位。

import cv2 as cv #import the OpenCV library
import numpy as np #Import Numpy Library
import socket  # socket creation for Telnet
from datetime import datetime
from telnetlib import Telnet #telnet client
from ffpyplayer.player import MediaPlayer #ffpyplayer for playing audio

current_date_time = datetime.now().strftime('%Y-%m-%d-%H:%M:%S')
HOST = '127.0.0.1'  # The remote host
PORT = 4212  # The same port as used by the server
TCP_PORT = 9999  # port used to connect with the server file

def send_data(message, s, output):
    s.sendall(message.encode())
    data = s.recv(1024)
    output.write('\n'+ current_date_time+' '+message+ '\n')
    return data

def circle(frame, left):
    if left:
        cv.circle(frame,(450,250),20,(255,255,255),50)
    if not left:
        cv.circle(frame,(1400,250),20,(255,255,255),50)

def video():
    cap1 = cv.VideoCapture('P1.mp4') # the video that we want
    player = MediaPlayer('P1.mp4')
    circle_is_left = True
    if (cap1.isOpened()== False):
        print("Error opening video 1")  
    while (cap1.isOpened()):
        ret,frame = cap1.read() #capture frame-by-frame video
        audio_frame,val=player.get_frame() # capture frame-by-frame audio
        if ret== True:
            key_pressed = cv.waitKey(1)
            if key_pressed == ord(' '): #pressing space bar ends the video
                with open('out.txt', 'a') as output:
                    send_data('video 1 is changed',s,output)
                break
            elif key_pressed == 2: #left key pressed changes circle to lett
                circle_is_left = True
                with open('out.txt', 'a') as output:
                    send_data('left',s,output)
            elif key_pressed == 3: # right key pressed changes circle to right
                circle_is_left = False
                with open('out.txt', 'a') as output:
                    send_data('Right ',s,output)
            circle(frame, circle_is_left) #display the circle at all times
            cv.imshow('cap1',frame) #display resulting frame 
            if val != 'eof' and audio_frame is not None:
                img,t = audio_frame
    cap1.release()
    cv.destroyAllWindows()
 
    cap2 = cv.VideoCapture('P2.mov') # the video that we want
    player2 = MediaPlayer('P2.mov')
    circle_is_left = True
    if (cap2.isOpened()== False):
        print("Error opening video 2")  
    while (cap2.isOpened()):
        ret,frame = cap2.read() #capture frame-by-frame video
        audio_frame,val=player2.get_frame() # capture frame-by-frame audio
        if ret== True:
            key_pressed = cv.waitKey(1)
            if key_pressed == ord(' '): #pressing space bar ends the video
                with open('out.txt', 'a') as output:
                    send_data('video 1 is changed',s,output)
                break
            elif key_pressed == 2: #left key pressed changes circle to lett
                circle_is_left = True
                with open('out.txt', 'a') as output:
                    send_data('left',s,output)
            elif key_pressed == 3: # right key pressed changes circle to right
                circle_is_left = False
                with open('out.txt', 'a') as output:
                    send_data('Right ',s,output)
            circle(frame, circle_is_left) #display the circle at all times
            cv.imshow('cap2',frame) #display resulting frame 
            if val != 'eof' and audio_frame is not None:
                img,t = audio_frame
    cap2.release()
    cv.destroyAllWindows()

def main():
    print("Game1.py is connected to TCP server")
    video()
    
if __name__=='__main__':
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, TCP_PORT))
    main()

【问题讨论】:

您似乎为 P1P2 运行相同的代码 - 因此您可以创建函数并使用不同的文件名运行它。对于 P1,您对 cv 和 mediaplayer 使用相同的 P1.mp4 - 但对于 P2,您使用不同的名称 P2.movP2.mp4 - 所以您运行的文件可能带有错误的音频。 @furas 我做到了,现在视频和音频至少平衡了。但我仍在努力解决同步问题。 synchronisation 是个大问题,我不知道解决方案。 MediaPlayer 运行在不同时刻启动的外部程序,然后是 VideoCapture,您无法控制它。我会尝试使用一些模块来直接访问音频设备 - 即。 pyaudio, winsound, playsound 您无法加快速度 - 您只能使用waitKey(millisecond) 来决定它的显示速度。理论上,如果您设置waitKey(33),那么将每隔33ms 运行一个帧,这会给出1000ms/33ms = 30 Frames Per Second - 但代码还会运行其他需要时间的函数,因此您必须测量帧之间的时间并在waitKey 中使用校正值。它MediaPlayer 启动得比cv 更快,然后你可以在第一个cap1.read() 开始时在循环内启动MediaPlayer - 也许它几乎会在同一时间启动。 1000 FPS 只是一个理论。 waitKey(1) 至少等待 1 毫秒,但它可能会等待更多时间 - 除了 read() 和其他功能也需要一些时间 - 所以最后你会得到慢得多的视频。 waitKey 可以在视频速度过快时降低视频速度,但在您的情况下,其他代码可能会减慢您的速度 - 也许您应该在单独的线程中使用 send_data。或者也许你应该只打开一次文件,因为一次又一次地打开也会减慢代码的速度。 【参考方案1】:

您可以使用cv2.CAP_PROP_POS_MSEC来同步视频和音频,如下:

import cv2
import time
from ffpyplayer.player import MediaPlayer


def video(file):
    cap = cv2.VideoCapture(file)
    player = MediaPlayer(file)
    start_time = time.time()

    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break
        _, val = player.get_frame(show=False)
        if val == 'eof':
            break

        cv2.imshow(file, frame)

        elapsed = (time.time() - start_time) * 1000  # msec
        play_time = int(cap.get(cv2.CAP_PROP_POS_MSEC))
        sleep = max(1, int(play_time - elapsed))
        if cv2.waitKey(sleep) & 0xFF == ord("q"):
            break

    player.close_player()
    cap.release()
    cv2.destroyAllWindows()

见https://github.com/otamajakusi/opencv_video_with_audio

【讨论】:

以上是关于使用 OpenCV 和 ffpyplayer 进行音视频同步的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Android 上播放音频(ffpyplayer) - buildozer

Python GUI编程:视频播放器(opencvtkinterPILffpyplayerthreading)

Python GUI编程:视频播放器(opencvtkinterPILffpyplayerthreading)

Python GUI编程:视频播放器(opencvtkinterPILffpyplayerthreading)

使用opencv和覆盆子相机模块进行人脸检测的最佳算法是啥

使用 C++ 和 opencv 进行人脸识别 [关闭]