[theora-dev] [Cortado] How to support seeking in on-the-fly generated Theora stream?

Tom Sparks tom_a_sparks at yahoo.com.au
Mon May 23 05:16:11 PDT 2011


I think byte-range serving[1] would be easy to do
or use Chunked transfer encoding[2] 

[1] http://en.wikipedia.org/wiki/Byte_serving
[2] http://en.wikipedia.org/wiki/Chunked_transfer_encoding
--
tom_a_sparks "It's a nerdy thing I like to do"
Please use ISO approved file formats excluding Office Open XML - http://www.gnu.org/philosophy/no-word-attachments.html
3 x (x)Ubuntu 10.04, Amiga A1200 WB 3.1, UAE AF 2006 WB 3.X, Sam440 AOS 4.1


--- On Mon, 23/5/11, David Schueler <david.schueler at wapkamera.de> wrote:

> From: David Schueler <david.schueler at wapkamera.de>
> Subject: [theora-dev] [Cortado] How to support seeking in on-the-fly generated Theora stream?
> To: theora-dev at xiph.org
> Received: Monday, 23 May, 2011, 8:02 PM
> 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
> _______________________________________________
> theora-dev mailing list
> theora-dev at xiph.org
> http://lists.xiph.org/mailman/listinfo/theora-dev
> 


More information about the theora-dev mailing list