jeudi 4 janvier 2018

Writing and reading movies with Red

As explained in a previous post, Red is able to access usb cameras under macOS and Windows. The idea is now to record the content of the camera and then to read back the recorded movie file. To do that I implemented a specific format for Red Camera which allows to record and to read  red video files.


RCAM (RedCAMera) Format

RCAM files contain rgb values.
RCAM files are stored as binary values.
RCAM files contain a 32-byte header starting at offset 0 within the binary file. Video data are beginning just after the header at offset 32. 
RgbSize (Image x size * Image y size * 3) is used to calculate the offset of each image contained in the file.  


Offset
Size
Description
0
4
RCAM Four CC Red signature
4
4
Number of images in the file
8
4
Image x size
12
4
Image y size
16
8
duration in sec (float value)
24
4
Frames by Sec
28
4
compressed data (1) or not (0)
32
Image x size * Image y size * 3
Binary values for all images


Each image is then stored in a block! which is very convenient for a fast access and for all operations that can be performed on blocks with Red.

Storing video data

Basically we need a block that will be used to store the rgb values provided by the camera such as
movie: copy []
Then for each sampled image it is really simple to store the values into te block:
img: to-image cam
append movie img/rgb
Lastly a simple write/binary is used to store the data into a binary file (see saveData function for details)

Code sample 

Red [
    Title:   "Test camera Red VID "
    Author:  "Francois Jouen"
    File:    %reccam.red
    Needs:   View
]

iSize: 320x240
margins: 10x10
cam: none ; for camera
imgSize: 0
count: 0
cti: 0%
movie: copy []
t1: t2: now/time/precise
fn: %video.rvf
d: 0.0
fps: 0
compression: 0

processCam: does [
    count: count + 1
    ct/text: form count
    if cb/data [
        img: to-image cam
        append movie img/rgb
    ]
]

saveData: does [
    n: length? movie
    i: 1
    write/binary fn "RCAM"                          ;Four CC Red signature
    write/binary/append fn to-binary n              ;Number of images
    write/binary/append fn to-binary img/size/x     ;Image x size
    write/binary/append fn to-binary img/size/y     ;Image y size
    write/binary/append fn to-binary d              ;duration in sec
    write/binary/append fn to-binary fps            ;FPS
    write/binary/append fn to-binary compression    ;compressed data (1) or not (0)
    foreach im movie [
        cti: to-percent i / to-float n
        write/binary/append fn movie/:i             ;binary values
        p1/data: cti
        i: i + 1
    ]   
]

view win: layout [
        title "Red Cam"
        origin margins space margins
        cSize: field 100
        cb: check "Record Camera" true
        pad 25x0
        btnQuit: button "Quit" 60x24 on-click [quit]
        return
        ct: field 100
        button "Save" [saveData]
        p1: progress 130 
        return
        cam: camera iSize 
        return
        active: text 55 "Camera" rate 0:0:1 on-time [processCam]
        cam-list: drop-list 160 on-create [
                face/data: cam/data
        ]
        onoff: button "Start/Stop" on-click [
                either cam/selected [
                    t2: now/time/precise
                    d: to-float t2 - t1
                    fps: to-integer round count / d
                    cam/selected: none
                    active/rate: none
                ][
                    cam/selected: cam-list/selected
                    active/rate: 0:0:0.04;  max 1/25 fps in ms
                    img: to-image cam
                    cSize/text: form img/size
                    imgSize: (img/size/x * img/size/y) * 3 
                    movie: copy []
                    count: 0
                    t1: now/time/precise
                ]
        ]
        do [cam-list/selected: 1 active/rate: none]
]

Reading video files

The reading of red video files is also very simple. You just need to load the file as a binary file and get the 32-bytes header which contains all information required for a correct reading such as the number of images, the size of images or the duration of the movie. With x and y size of the image it's easy to compute the offset of image. Since we use rgb value, the total size of each image equals to x * y * 3. With this value (rgbSize) a simple skip on data allows to get rgb values for each image:

i: 0
while [i < nImages] [
if i > 0 [f: skip f rgbSize]
rgb: copy/part f rgbSize
append movie rgb
i: i + 1
]
]

Code sample

Red [
    Title:   "Test camera Red VID "
    Author:  "Francois Jouen"
    File:    %movie.red
    Needs:   View redCV
]

margins: 5x5
fn: %video.rvf
iSize: 640x480
imgSize: 0x0
cSize: 0x0
n: 0
nImages: 0
strSize: 0
f: none
isFile: false
rgbSize: 0
img: make image! reduce [iSize black] 
activeImage: 1
duration: 0.0
fps: 0
compression: 0
freq: none

readImage: func [n [integer!]][
    if isFile [
        f5/text: form n
        img/rgb: movie/:n
    ]
    canvas/image: img
]

readAllImages: does [
    either activeImage < nImages [activeImage: activeImage + 1 readImage activeImage]
                                  [activeImage: 1]
]

loadMovie: func [] [
    ;tmp: request-file/filter ["Red Video Files" "*.rvf"] ; pb with -t macOS
    tmp: request-file
    if not none? tmp [
        f: read/binary tmp
        ; read header
        s: to-string copy/part f 4  ; should be "RCAM"
        f: skip f 4                 ;
        nImages: to-integer copy/part f 4
        
        f: skip f 4
        imgSize/x: to-integer copy/part f 4
        f: skip f 4
        imgSize/y: to-integer copy/part f 4
        
        rgbSize: (imgSize/x * imgSize/y) * 3
        f: skip f 4
        duration: to-float copy/part f 8
        f: skip f 8
        fps: to-integer copy/part f 4
        f: skip f 4
        compression: to-integer copy/part f 4
        either compression = 0 [f6/text: rejoin [ form compression " : Uncompressed video"]] 
                            [f6/text: rejoin [ form compression " : ZLib compressed video"]]
        f1/text: rejoin [form nImages " images"]
        f2/text: form imgSize
        f3/text: rejoin [form round duration " sec"]
        f4/text: rejoin [form fps " FPS"]
        freq: to-time compose [0 0 (1.0 / fps)]
        f: skip f 4
        
        ; read red movie data
        movie: copy []
        i: 0 
        while [i < nImages] [
            if i > 0 [f: skip f rgbSize]
            rgb: copy/part f rgbSize
            append movie rgb
            i: i + 1
        ]
        img: make image! reduce [imgSize rgb]
        isFile: true 
        activeImage: 1
        readImage activeImage 
    ]
]

view win: layout [
    title "Reading red movie"
    origin margins space margins
    button "Load" [loadMovie]
    f1: field 100
    f2: field 100
    f3: field 100
    f4: field 100
    bt: base 20x20 on-time [readAllImages]
    button "Quit" [quit]
    return
    canvas: base iSize img
    return
    button "<<" [activeImage: 1 readImage activeImage]
    button ">>" [activeImage: nImages readImage activeImage]
    button ">"  [if activeImage < nImages [activeImage: activeImage + 1 readImage activeImage]]
    button "<"  [if activeImage > 1 [activeImage: activeImage - 1 readImage activeImage]]
    button "<>" [if isFile [bt/rate: freq]]
    button "||" [bt/rate: none]
    f5: field 60
    f6: field 160
    do [bt/rate: none]
]

Result



The movie reader is complete with a frame by frame access or a complete reading of the video.






5 commentaires:

  1. So you have a hard image limit of 32bit? Do you ever fore see needing more than that? High speed videos on a camera that is monitoring tree growth over several years, or something?

    RépondreSupprimer
  2. Thanks William
    This code is still experimental and under progress :). I'm actually try to compress image data for a better rendering and downsizing video files.

    RépondreSupprimer
  3. See new version in Read movies with Red new post

    RépondreSupprimer