Wednesday, January 11, 2012

pyframe transfer speed sufficient even for video

I was wondering about the transfer speed of pictures to the frame, given that Python is an interpreted language. It turned out to be much faster than expected:

Two very different pictures were loaded by the script, prepared, stored in memory and alternatively transferred to the frame. I used one pair of pictures which were simple and small (<<16384 Bytes), and another one with rather complex and hence larger (ca 100kB after resizing to 800x480) pictures. All are attached to this post. The transfer of the pics resulted in a CPU load of only about 1-2%. Here the measured data:

Picture_Pair_______ Picture Size (B) _______Pictures/sec ___Total Transfer MB/sec
red.jpg/blue.jpg ____ 6631/2536 __________ 28 _____________ 0.46
i244,jpg/i247.jpg ___ 98792/123768 ______ 16 _____________ 1.93

Since a minimum chunk size of 16384 bytes per picture needs to be transferred irrespective of the picture size, the small pictures do not benefit much from their small size with respect to transfer speed. Generally, 20+ Pictures/sec should be achievable.

Since the USB bus can transfer at least 10x as much, and the CPU even more, I conclude that the transfer speed is limited by the frame.

Next I took a video clip and converted each frame into a JPG picture using ffmpeg, which I then tried to transfer to the frame as a fast sequence. Suprisingly, I could not transfer even a single picture out of several hundred, although each picture could be viewed correctly with all photoview programs on my computer. I tried a variety of permutations of the ffmpeg parameters, but without success.

However, reading and rewriting each picture using the Python IMAGE Modul and this code resulted in pictures fit for transfer to the frame:
import Image
filename = "inpicture.jpg" 
image = Image.open(filename)
image.save( "outpicture", "JPEG", quality=95)
Transfering those pictures was possible with a flicker-free frame rate of some 26 pictures/sec (1.7 MB/sec) - the "picture" frame turned into a "video" frame! Reading the pictures from a fast SSD did not improve the speed, which is consistent with the frame itself being the bottleneck.


The test script is this:
#!/usr/bin/python

import sys
import os
import struct
import usb.core
import time

device = "SPF87H Mini Monitor"

dev = usb.core.find(idVendor=0x04e8, idProduct=0x2034)

if dev is None:
    print "Could not find", device, " - Exiting\n"
    sys.exit()

print "Found", device

dev.ctrl_transfer(0xc0, 4 )   

if len(sys.argv) < 3:
    print "I need 2 pictures  - Exiting."
    sys.exit()

filename1 = sys.argv[1]
filename2 = sys.argv[2]
filesize1 = os.path.getsize(filename1)
filesize2 = os.path.getsize(filename2)
print "Pictures to show are:", filename1, "filesize:", filesize1
print "Pictures to show are:", filename2, "filesize:", filesize2

# Open the picture file and read into a string
infile1 = open(filename1, "rb")
pic1 = infile1.read()
infile1.close()

infile2 = open(filename2, "rb")
pic2 = infile2.read()
infile2.close()

# The photo frame expects a header of 12 bytes, followed by the picture data.
# The first 4 and the last 4 bytes are always the same.
# The middle 4 bytes are the picture size (excluding the header) with the least significant byte first
rawdata1 = b"\xa5\x5a\x18\x04" + struct.pack('<I', len(pic1)) + b"\x48\x00\x00\x00" + pic1
rawdata2 = b"\xa5\x5a\x18\x04" + struct.pack('<I', len(pic2)) + b"\x48\x00\x00\x00" + pic2

# The photo frame expects transfers in complete chunks of 16384 bytes (=2^14 bytes).
# If last chunk of rawdata is not complete, then make it complete by padding with zeros.
pad1 = 16384 - (len(rawdata1) % 16384)
tdata1 = rawdata1 + pad1 * b'\x00'

pad2 = 16384 - (len(rawdata2) % 16384)
tdata2 = rawdata2 + pad2 * b'\x00'

# For unknown reasons, some pictures will only transfer successfully, when at least one
# additional zero byte is added. Possibly a firmware bug of the frame?
#tdata1 = tdata1 + b'\x00'
#tdata2 = tdata2 + b'\x00'

# Write the data. Must write to USB endpoint 2
endpoint = 0x02

bytes_written1 = 0
bytes_written2 = 0

ts = time.time()
nr = 100
for i in range(nr):
    bytes_written1 += dev.write(endpoint, tdata1 )
    bytes_written2 += dev.write(endpoint, tdata2 )  
   
te = time.time()

sum = bytes_written1 + bytes_written2
td = te -ts
print "time lapsed writing:", td, "sec"

print "total no of pictures transferred:", nr * 2, ", rate: ", "%02.1f Bilder/sec"% (nr * 2 / td)
print "total no of bytes transferred:", sum, ", rate:",  "%03d kB/sec"%(sum/td/1000.)

sumfs = nr * (filesize1 + filesize2)
print "transfer overhead: %3d%% " % ((100.*sum/sumfs) - 100.)
The test pictures follow, each one 800x480



No comments:

Post a Comment