[theora-dev] [Cortado] How to support seeking in on-the-fly generated Theora stream?
David Schueler
david.schueler at wapkamera.de
Mon May 23 03:02:32 PDT 2011
Hello all!
i want to use Theora in a little video portal, because its free and open
source and i want to contribute some code to get the Java Theora player
(Cortado) more feature-rich.
But now I'm stuck and hope that someone can point me into the right
direction.
I have videos stored in several formats (mostly H.264 or MPEG4) and use
ffmpeg2theora to recode them on the fly. The recoded output is send via
HTTP stream directly to the Cotrado player which stores the data and
begins playing if enough data is buffered.
In practice this looks like this is a PHP file:
passthru("ffmpeg2theora -o - ".$source." 2> /dev/null");
I recoded the Queue.java (Queue class) that the queue array is not a dump
FIFO anymore and called it QueueSeek.java.
Now the Buffer array is filled at the end and a variable points to the
current play position which gets increased on every loop and sends the
data to the next sink pad (at OggDemux).
QueueSeek Buffer:
+--# data added at the end
+0-1-2-3-4-v-------
|#|#|#|#|#| | | | -> unlimited capacity
+----|-------------
v
current play position
data will be send to demuxer
but not removed from the FIFO
Now i managed that the fill level of the buffer is the difference between
the size of the data in the FIFO and the current play position. So the
play begins if the data is enough and stops if the "watermark" gets below
the lowest level.
+ current play position
+0-1-V-3-4-5-6-----
|#|#|#|#|#|#|#| |
+----|-------|-----
|buffer-|
level=4
Now i want to seek. And thats where the problems start to begin. I
understood how the demuxer works. It gets the data from the Queue, stores
it in its own buffer and seeks for a "OggS" pattern which marks the start
of a new Theora page. Then is checks if that whole page is present in the
buffer and calculates a checksum. If all went well the page is processed
in a way i did not look about any further.
But if i seek in my new Queue - which means just setting the play position
to any value - then the demuxer stumbles, because the checksum and the
length of the page are not correct.
To get around this i did a little trick:
On my new FIFO i seek though the incoming data and look for the OggS
pattern. If it is present the data will be split into two segments and
both will be inserted info the FIFO. Now every page starts at the
beginning of a FIFO buffer:
+------0-------------1-------------2------------3-----
| OggS...data | OggS...data.|....data | OggS...
+-----------------------------------------------------
As you can see, sometimes the data is lager than the 4096 byte of one
buffer, so it takes more than one buffer for a page. I've taken care of
that in the seeking method by rewinding the play position until the buffer
starts with an OggS pattern.
But now a second problem exists: The seeking should start if one page is
completely submittet. And thats where i'm stucking now.
The pads of the elements are separate threads which work independently
from each other.
----+ +-----------[QueueSeek element]---------+ +----
prev| | | to | sink pad
src O--push->O sink pad --> [FIFO magic] --> src pad O--next-->O of the
pad | | | element | demuxer
----+ +---------------------------------------+ +----
Thats why many code blocks are synchronized on the queue object, that only
one thread can access the queue at one time. Now, i want so synchronize
the sink- and sourcepad threads of the QueueSeek class on another object,
which is called "permissionToSeek". The sinkpad should wait until it gets
a permission to seek.
I tried to achieve this with wait() and notifyAll().
the sinkpad has a method which gets called if a seek request arrives. It
synchronizes on the permission object and waits for the srcpad to grant
it:
private void doSeek(Event event) {
// wait until we get the permission to seek
synchronized (permissionToSeek) {
try {
Debug.info("[BUFFER] sink -> doSeek() waiting for
permissionToSeek");
// wait until we are allowed to seek
permissionToSeek.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
/* ... code to seek ... */
}
The sinkpad checks if the next buffer begins with OggS pattern and calls
the notifyAll() method:
protected void taskFunc() {
boolean runSeekIfWanted = false;
synchronized (queue) {
/* ... */
if (playPosition+1 < queue.size()) {
obj = queue.elementAt(playPosition+1);
if (obj instanceof Buffer) {
Buffer bb = (Buffer) obj;
if (bb.data[0] == 'O' &&
bb.data[1] == 'g' &&
bb.data[2] == 'g' &&
bb.data[3] == 'S') {
runSeekIfWanted = true;
Debug.info("[BUFFER] src setting permissionToSeek");
}
}
}
/* ... */
}
/* ... main method code ... */
synchronized (permissionToSeek) {
if (runSeekIfWanted) {
Debug.info("[BUFFER] src granting permissionToSeek");
permissionToSeek.notifyAll();
}
}
}
The problem is that the applet stops in the doSeek() method at the wait()
and waits forever. The whole applet does not respond anymore:
[INFO] [BUFFER] playPosition = 324, queue.size = 669 Elements, size =
1359872 bytePlayPosition = 678325 -> 64% full
[INFO] [BUFFER] src granting permissionToSeek
[INFO] [BUFFER] playPosition = 325, queue.size = 669 Elements, size =
1359872 bytePlayPosition = 679936 -> 64% full
[INFO] [BUFFER] playPosition = 326, queue.size = 669 Elements, size =
1359872 bytePlayPosition = 679962 -> 64% full
[INFO] [BUFFER] src setting permissionToSeek
[INFO] doSeek(): aPos=0.05273069679849341
[INFO] doSeek(): Element: [pipeline].sendEvent [Event] type: SEEK, format:
5, position: 52730
[INFO] Element: [pipeline] sendEvent(): [Event] type: SEEK, format: 5,
position: 52730
[INFO] Element: [pipeline] doSeek(): [Event] type: SEEK, format: 5,
position: 52730
[INFO] Element: [pipeline] doSendEvent(): [Event] type: SEEK, format: 5,
position: 52730 to Pad: httpsrc:src
[INFO] [BUFFER] sink -> doSeek() waiting for permissionToSeek
If i do a wait for 5 seconds to get around this issue, i get discontinue
messages in the console, and i don't know why:
[INFO] theora: got discont
[INFO] theora: got discont
[INFO] theora: got discont
So my first question is:
Is my whole thinking right with providing full pages to the demuxer? Is
that the right way for seeking in a Theora video stream?
Why does my applet wait forever? I suppose the srcpad does not fire the
notifyAll() method but i have no idea why or where the srcpad gets stopped
or interrupted.
I will provide the whole code project in a .tar.bz here, so everyone can
use and experiment with my code changes:
http://www.wapkamera.de/cortado.tar.bz
Hopefully someone is able to help me, because i just started in coding
Java and i don't know the Theora video stream structure in detail.
Best regards to everyone.
David
More information about the theora-dev
mailing list