MJPEG (Motion JPEG) Nedir?
Motion JPEG, her bir karesi JPEG ile sıkıştırılmış bir video sıkıştırma formatıdır. Multimedya programları için geliştirilmiştir. Şu anda genel olarak dijital kameralar, IP kameralar ve webcamler tarafından kullanılıyor. Bunun yanında, QuickTime Player, PlayStation konsolu ve Safari, Chrome, Mozilla Firefox, Microsoft Edge gibi tarayıcılar tarafından desteklenmektedir.
Bu formatta, her bir kare, “0xff 0xd8” ile başlayıp “0xff 0xd9” ile bitmektedir. Bunu kullanarak her bir kareyi parse edebileceğiz. Aşağıda yapısı bulunuyor.
...(http)
0xff 0xd8 --|
[jpeg data] |-- frame
0xff 0xd9 --|
...(http)
0xff 0xd8 --|
[jpeg data] |-- frame
0xff 0xd9 --|
...(http)
Python Uygulaması
GitHub: https://github.com/nulls0/mjpeg-reader
class MJPEGStream(object):
def __init__(self, stream_url):
self.stream_url = stream_url
self.stream = urllib2.urlopen(self.stream_url)
self.data = ''
self.frame = None
thread = threading.Thread(target=self.start)
thread.daemon = True
thread.start()
def read(self):
return self.frame
def start(self):
while True:
self.data += self.stream.read(5120)
start_pos = self.data.find('\xff\xd8')
end_pos = self.data.find('\xff\xd9')
if start_pos != -1 and end_pos != -1:
self.frame = self.data[start_pos:end_pos+2]
self.data = self.data[end_pos+2:]
Gelin, beraber neler olup bittiğini inceleylim. İlk olarak, sınıfı örneklerken, “stream_url” adında bir parametre veriyoruz. Bu URL, yayının yapıldığı yer. Sınıf örneklendikten sonra, yeni bir thread içerisinde sürekli frameleri çekecek olan “start” fonksiyonunu çalıştırıyor.
def start(self):
while True:
self.data += self.stream.read(5120)
start_pos = self.data.find('\xff\xd8')
end_pos = self.data.find('\xff\xd9')
if start_pos != -1 and end_pos != -1:
self.frame = self.data[start_pos:end_pos+2]
self.data = self.data[end_pos+2:]
Burada da görüldüğü üzere, döngü her başa dondüğünde yayının yapıldığı yerden 5MB boyutunda data alıyor. Ardından alınan data içerisinde bir karenin başlangıcının ve bitişinin konumlarını bulmaya çalışıyor. Eğer bulursa, aranan değeri “self.frame” değişkenine atıyor ve datadan bu değeri siliyor. Bundan sonra datadan geriye sadece sonraki karenin baştan bir bölümü kalmış oluyor, döngü başa dönüp tekrar data alınca bu sefer yarım kalan kare tamamlanıyor ve datadan siliniyor. Böyle devam ederek her bir kareyi parse etmiş oluyoruz.
class MJPEGStreamViewer(object):
def __init__(self, stream):
self.stream = stream
def start(self):
while True:
frame = self.stream.read()
if frame == None:
continue
frame = numpy.fromstring(frame, dtype=numpy.uint8)
frame = cv2.resize(cv2.imdecode(frame, 1), (640, 480))
cv2.imshow('MJPEG Stream Viewer', frame)
if (cv2.waitKey(5) & 0xFF) == 27:
sys.exit()
Bu kadar yazmışken, parse edilen frameleri bir videoymuş gibi izleyebilmek için basit bir sınıfta yazdım. Burada, “stream” parametresine bir “MJPEGStream” sınıfı veriyoruz, alınan her frame kullanıcıya bir pencerede gösteriliyor. İnternetten bulduğum bir yayın ile test edelim:
Görüldüğü üzere, uygulamamız gayet güzel çalışıyor.