Search code examples
pythonimageframes

How To Use Memory Mapped File in Python Linux


As I saw in the Python documentation,

https://docs.python.org/3/library/mmap.html.

Python in Linux can fully support the memory-mapped file. However, while I am trying to apply this idea to my application. I cannot run the sample.

My application is that sending the frames from Python file (client) to the another Python file (server).

Client Code

import mmap
import time
import os
import cv2 as cv

print("Opening camera...")
cap = cv.VideoCapture('/home/hunglv/Downloads/IMG_8442.MOV')

mm = None
try:
    while True:
        ret, img = cap.read()
        if not ret:
            break
        if mm is None:
            mm = mmap.mmap(-1,img.size,mmap.MAP_SHARED, mmap.PROT_WRITE)

        # write image
        start = time.time()
        buf = img.tobytes()
        mm.seek(0)
        mm.write(buf)
        mm.flush()  
        stop = time.time()
        print("Writing Duration:", (stop - start) * 1000, "ms")
except KeyboardInterrupt:
    pass
print("Closing resources")
cap.release()
mm.close()

Server Code

import mmap
import time
import os
import cv2 as cv
import numpy as np

shape = (1080, 1920, 3)
n = np.prod(shape)
mm = mmap.mmap(-1, n)

while True:
    # read image
    print (mm)
    start = time.perf_counter()
    mm.seek(0)
    buf = mm.read(12)
    img = np.frombuffer(buf, dtype=np.uint8).reshape(shape)
    stop = time.perf_counter()

    print("Reading Duration:", (stop - start) * 1000, "ms")
    cv.imshow("img", img)
    key = cv.waitKey(1) & 0xFF
    key = chr(key)
    if key.lower() == "q":
        break
cv.destroyAllWindows()
mm.close()

On the server-side, I set the memory index at 0, and try to read the bytes from memory. However, it seems to be that the server cannot read correctly the data from client.

[Updated] I've tried to read out the first 12 bytes at the server-side. The value is constant, not changing anymore.

b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Besides, The first 12 bytes of a random frame is

b'\xf5\xff\xff\xf0\xfa\xfe\xdf\xe9\xed\xd2\xdc\xe0'


Solution

  • First I found example which probably works but it uses tagName (the same for client and server) and it means it is only for Window:

    python-mmap-ipc


    Next I found code which works on Linux:

    Sharing Python data between processes using mmap.

    It creates real file on disk, resizes it to size of image and then it uses its fd in mmap()


    I use web camera for test.

    Server

    import mmap
    import time
    import os
    import cv2
    
    print("Opening camera...")
    
    cap = cv2.VideoCapture(0)
    #print(cap.get(cv.CAP_PROP_FRAME_WIDTH))  # 640
    #print(cap.get(cv.CAP_PROP_FRAME_HEIGHT)) # 480
    
    shape = (480, 640, 3)
    n = (480*640*3)
    
    fd = os.open('/tmp/mmaptest', os.O_CREAT | os.O_TRUNC | os.O_RDWR)
    #os.write(fd, b'\x00' * n)  # resize file
    os.truncate(fd, n)  # resize file
    
    mm = None
    try:
        while True:
            ret, img = cap.read()
            
            if not ret:
                break
            
            if mm is None:
                mm = mmap.mmap(fd, n, mmap.MAP_SHARED, mmap.PROT_WRITE)  # it has to be only for writing
    
            # write image
            start = time.perf_counter()
            
            buf = img.tobytes()
            mm.seek(0)
            mm.write(buf)
            mm.flush()
            
            stop = time.perf_counter()
    
            print("Writing Duration:", (stop - start) * 1000, "ms")
    except KeyboardInterrupt:
        pass
    
    print("Closing resources")
    cap.release()
    mm.close()
    

    Client

    import mmap
    import time
    import os
    import cv2
    import numpy as np
    
    shape = (480, 640, 3)
    n = (480*640*3)
    
    fd = os.open('/tmp/mmaptest', os.O_RDONLY)
    
    mm = mmap.mmap(fd, n, mmap.MAP_SHARED, mmap.PROT_READ)  # it has to be only for reading
    
    while True:
        # read image
        start = time.perf_counter()
        
        mm.seek(0)
        buf = mm.read(n)
        img = np.frombuffer(buf, dtype=np.uint8).reshape(shape)
        
        stop = time.perf_counter()
    
        print("Reading Duration:", (stop - start) * 1000, "ms")
    
        cv2.imshow("img", img)
        key = cv2.waitKey(1) & 0xFF
        key = chr(key)
        if key.lower() == "q":
            break
        
    cv2.destroyAllWindows()
    mm.close()
    

    BTW: probably mmap() with -1 (without creating file on disk) could work with threads (or forks) because they share the same memory.