Hi,
I'm well on my way in creating a basic videoplayer using pyffmpeg and
pygame, but I have some trouble getting the audio and video in sync.
I've tried using the set_hurry() function of videotrack or specifying
the 'hurry_up' parameter in the init of the videotrack class. Neither
had any effect. I've been reading about the syncing topic a bit as
well in other sources and found out I have to use the PTS data to sync
both streams, but I have no idea how to do this. I've tried setting
the pts of the video to the pts of the audio, using the get_cur_pts
from the audiotrack and the seek_to_pts of the videotrack, but this
just seems to get the program into an infinite loop.
Is there any example/documentation/source where I can found out how to
tackle this using pts or the 'hurry' parameters, or has anyone already
solved this using pyffmpeg? I tried using
http://dranger.com/ffmpeg/tutorial05.html
as a lead, but didn't get any wiser.
Furthermore, when I play a file which has no audiotrack, the video
gets played way to fast. I've tried to solve this with a wait() of
sleep() after rendering a frame, but this slowed the video down too
much again when I used the simple calculation
pygame.time.wait(frameTime - calculationTime)
where frametime =1000/videotrack.get_fps() (1000 ms / fps of the
movie)
and calculationTime is the time lost by processing the frame and
presenting it to the display.
a quick hack was to just shorten the sleeptime by a fixed number, but
this seems a very bad solution. Is there a way to more efficiently get
the display time per frame to use in a sleep() function or another way
to make sure the movie doesn't haste when there is no sound present?
Thanks very much for any help, and I would like to congratulate the
whole pyffmpeg crew for the great work they have done!
For anyone interested; here's what I have written now:
--------------------------------------------------------------------------------------------------------------------------------------------------
import pyffmpeg
import pygame
import pyaudio
import thread
import sys
import traceback
class MediaPlayer():
def __init__(self, vfile="",screensize="default"):
"""
Initialize module. Link to the video can already be specified
but this is optional
file: The path to the file to be played
screensize: An optional tuple with the preferred (w,h) of the
output frame. Otherwise, the video is played in its original size
"""
pygame.init()
pygame.display.init()
self.mp = pyffmpeg.FFMpegReader(0,False)
self.videoTrack = None
self.audioTrack = None
self.file_loaded = False
self.frameTime = 0
self.screensize = screensize
self.paused = False
if vfile != "":
self.load(vfile)
def load(self,vfile):
"""
Load a videofile and make it ready for playback
vfile: The path to the file to be played
"""
if self.screensize == "default":
TS_VIDEO_RGB24={ 'video1':(0, -1,
{'pixel_format':pyffmpeg.PixelFormats.RGB24,'videoframebanksz':1}),
'audio1':(1,-1,{})}
elif type(self.screensize)==tuple and len(self.screensize)==2:
TS_VIDEO_RGB24={ 'video1':(0, -1,
{'pixel_format':pyffmpeg.PixelFormats.RGB24,'videoframebanksz':
1,'dest_width':self.screensize[0], 'dest_height':self.screensize[1]}),
'audio1':(1,-1,{})}
else:
raise ValueError
#Check if audio stream is present, if not only process the
video
try:
self.mp.open(vfile,TS_VIDEO_RGB24) # will
throw IOError if it doesn't find an audio stream
(self.videoTrack,self.audioTrack) = self.mp.get_tracks()
self.samplingFreq = self.audioTrack.get_samplerate()
#pygame.mixer.init(frequency=self.audioTrack.get_samplerate(),
channels=self.audioTrack.get_channels())
self.audioTrack.set_observer(self.handleAudioFrame)
print "Channels: %s" % self.audioTrack.get_channels()
self.audiostream =
pyaudio.PyAudio().open(rate=self.samplingFreq,
channels=self.audioTrack.get_channels(),
format=pyaudio.paInt16,
output=True)
print "as: %s" % self.audiostream
self.hasSound = True
except:
print "Error: %s:\n%s\n%s" % (sys.exc_info()
[0],sys.exc_info()[1],traceback.print_tb(sys.exc_info()[2]))
try:
del TS_VIDEO_RGB24['audio1']
self.mp.open(vfile)
except IOError:
print "File not found"
sys.exit()
else:
self.videoTrack = self.mp.get_tracks()[0]
self.hasSound = False
print "No audio track found"
self.videoTrack.set_observer(self.handleVideoFrame)
self.videoTrack.check_start()
self.audioTrack.check_start()
print "FPS: %s" % self.videoTrack.get_fps()
self.frameTime = 1000/self.videoTrack.get_fps()
print "Frametime: %s" % self.frameTime
# Set pygame window to size of video, check for
self.screensize in future
if type(self.screensize)==tuple and len(self.screensize)==2:
pygame.display.set_mode( self.screensize,
pygame.DOUBLEBUF)
else:
pygame.display.set_mode( self.videoTrack.get_size(),
pygame.DOUBLEBUF)
# Get the pygame surface to draw to
self.screen = pygame.display.get_surface()
self.file_loaded = True
# The video and audio callback methods.
def handleVideoFrame(self, f):
"""
Callback method called by the VideoTrack object for handling a
video frame
"""
start = pygame.time.get_ticks()
pygame.surfarray.use_arraytype("numpy")
f=f.swapaxes(0,1)
pygame.surfarray.blit_array(self.screen,f)
pygame.display.flip()
self.frame_calc_start = pygame.time.get_ticks()
def handleAudioFrame(self, a):
"""
Callback method called by the AudioTrack object for handling
an audio frame
"""
self.audiostream.write(a[0].data)
#print "Audioframe ready!"
def pause(self):
"""
Pauses playback. Continue with unpause()
"""
self.paused = True
def unpause(self):
"""
Continues playback
"""
self.paused = False
def play(self, eventHandler=None):
"""
Starts the playback of the video file. You can specify an
optional callable object to handle events between frames (like
keypresses)
This function needs to return a boolean, because it determines
if playback is continued or stopped. If no callable object is provided
playback will stop when the ESC key is pressed
"""
if self.file_loaded:
self.playing = True
while self.playing:
try:
if self.paused == False:
print "--- Begin step ---"
self.mp.step()
print "Videotrack pts: %s" %
self.videoTrack.get_cur_pts()
print "Audiotrack pts: %s" %
self.audioTrack.get_cur_pts()
print "Offsycn: %s" %
(self.audioTrack.get_cur_pts()-self.videoTrack.get_cur_pts())
#self.videoTrack.set_hurry(self.audioTrack.get_cur_pts()-
self.videoTrack.get_cur_pts())
print "--- End step ---"
except IOError:
self.playing = False
print "Warning: %s: %s" % (sys.exc_info()
[0],sys.exc_info()[1])
else:
if callable(eventHandler):
result = eventHandler()
if type(result) == bool:
self.playing = result
else:
print "Invalid return type of specified
event handler: must be bool"
self.playing = False
else:
e = pygame.event.poll()
if e.type== pygame.KEYDOWN:
if e.key== pygame.K_ESCAPE:
self.playing = False
if e.key== pygame.K_SPACE:
if self.paused == True:
self.paused = False
else:
self.paused = True
#Sleep for remainder of a frame duration that's
left after all the processing time
if not(self.hasSound):
sleeptime = int(self.frameTime -
pygame.time.get_ticks() + self.frame_calc_start)
print "Sleeptime: %s" % sleeptime
pygame.time.wait(sleeptime)
# Clean up on aisle 4!
self.mp.close()
self.audiostream.close()
else:
print "No video loaded"
return False
if __name__ == "__main__":
vfile = sys.argv[1]
player = MediaPlayer(vfile)
player.play()
pygame.quit()