Lesson 6 : Writing / reading stream

Download lesson [here]

In this lesson, we are (a) writing from a live stream to a file and (b) reading the file, decoding the stream and presenting it on the screen. The filtergraph goes like this:

*** (a) writing ***

(LiveThread:livethread) --> {FileFrameFilter:file_filter}


*** (b) reading ***

Reading part
(FileThread:filethread) -----+
                             |
Decoding part                |
(AVThread:avthread) << ------+
      |
      |     Presentation part
      +-->> (OpenGLThread:glthread)

Note that live and file streams are treated on an equal basis and with a similar filtergraph. We could also send the file over the net as a multicast stream.

Let’s start by importing Valkka:

import time
from valkka.core import *

debug_log_all()

Writing is done by piping the stream into a FileFrameFilter:

file_filter  =FileFrameFilter("file_filter")
livethread   =LiveThread("livethread")

For reading, decoding and presenting, we construct the filtergraph as usual, from end-to-beginning:

# presentation part
glthread      =OpenGLThread ("glthread")
gl_in_filter  =glthread.getFrameFilter()

For file streams, the execution should block for frame bursts, so we request a blocking input FrameFilter from the AVThread:

avthread      =AVThread("avthread",gl_in_filter)
av_in_filter  =avthread.getBlockingFrameFilter()

# reading part
filethread    =FileThread("filethread")

Starting LiveThread will stream the frames to FileFrameFilter:

livethread .startCall()

ctx          =LiveConnectionContext(LiveConnectionType_rtsp, "rtsp://admin:nordic12345@192.168.1.41", 1, file_filter)
# stream from 192.168.1.41, tag frames with slot number 1 and write to file_filter

livethread .registerStreamCall(ctx)
livethread .playStreamCall(ctx)

In order to start writing to disk, FileFrameFilter’s “activate” method must be called with the filename. Only matroska (.mkv) files are supported:

print("writing to file during 30 secs")
file_filter.activate("kokkelis.mkv")

# stream for 30 secs
time.sleep(30)

# close the file
file_filter.deActivate()

# stop livethread
livethread.stopCall()

File “kokkelis.mkv” has been created. Next, let’s setup stream decoding, presenting, etc. as usual and read the file:

print("reading file")
glthread   .startCall()
filethread .startCall()
avthread   .startCall()

# start decoding
avthread.decodingOnCall()

# create an x-window
window_id =glthread.createWindow()
glthread.newRenderGroupCall(window_id)

# maps stream with slot 1 to window "window_id"
context_id =glthread.newRenderContextCall(1, window_id, 0)

Open the file by sending it a call with the FileContext (file_ctx) identifying the file stream:

print("open file")
file_ctx =FileContext("kokkelis.mkv", 1, av_in_filter) # read from file "kokkelis.mkv", tag frames with slot number 1 and write to av_in_filter
filethread.openFileStreamCall(file_ctx)

Playing, seeking and stopping is done as follows:

print("play file")
filethread.playFileStreamCall(file_ctx)

# play the file for 10 secs
time.sleep(10)

# let's seek to seekpoint 2 seconds
print("seeking")
file_ctx.seektime_=2000
filethread.seekFileStreamCall(file_ctx)

# pause for 3 secs
print("pausing")
filethread.stopFileStreamCall(file_ctx)
time.sleep(3)

# continue playing for 5 secs
print("play again")
filethread.playFileStreamCall(file_ctx)
time.sleep(5)

Finally, exit:

glthread.delRenderContextCall(context_id)
glthread.delRenderGroupCall(window_id)

# exit
avthread  .stopCall()
filethread.stopCall()
glthread  .stopCall()

print("bye")