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

Tom Sparks <[email protected]> Mon, 23 May 2011 05:16:11 -0700 (PDT)
Newsgroups gmane.comp.multimedia.ogg.theora.devel
Message-ID <[email protected]>
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 <[email protected]> wrote:

> From: David Schueler <[email protected]>
> Subject: [theora-dev] [Cortado] How to support seeking in on-the-fly gene=
rated Theora stream?
> To: [email protected]
> 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:
> =

> =A0 =A0 =A0 =A0 =A0=A0=A0+--# data
> added at the end
> +0-1-2-3-4-v-------
> |#|#|#|#|#| | | | -> unlimited capacity
> +----|-------------
> =A0 =A0=A0=A0v
> 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.
> =

> =A0 =A0=A0=A0+ current play position
> +0-1-V-3-4-5-6-----
> |#|#|#|#|#|#|#| | =

> +----|-------|-----
> =A0 =A0=A0=A0|buffer-|
> =A0 =A0 =A0 level=3D4
> =

> 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=A0 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=A0 =A0 |
> 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. =

> =

> ----+=A0 =A0 =A0 =A0 +-----------[QueueSeek
> element]---------+=A0 =A0 =A0
> =A0=A0=A0+----
> prev|=A0 =A0 =A0 =A0 |=A0 =A0 =A0
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0=A0=A0|=A0=A0=A0to=A0 =A0 | sink
> pad
> src O--push->O sink pad --> [FIFO magic] --> src
> pad O--next-->O of the
> pad |=A0 =A0 =A0 =A0 |=A0 =A0 =A0
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0=A0=A0| element | demuxer
> ----+=A0 =A0 =A0 =A0
> +---------------------------------------+=A0 =A0
> =A0 =A0=A0=A0+----
> =

> 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) {
> =A0 =A0 // wait until we get the permission to seek
> =A0 =A0 synchronized (permissionToSeek) {
> =A0 =A0 try {
> =A0 =A0 =A0 =A0 Debug.info("[BUFFER] sink ->
> doSeek() waiting for =

> permissionToSeek");
> =A0 =A0 =A0 =A0 // wait until we are allowed to
> seek
> =A0 =A0 =A0 =A0 permissionToSeek.wait();
> =A0 =A0 } catch (InterruptedException e) {
> =A0 =A0 =A0 =A0 e.printStackTrace();
> =A0 =A0 }
> =A0 =A0 /* ... code to seek ... */
> }
> =

> The sinkpad checks if the next buffer begins with OggS
> pattern and calls =

> the notifyAll() method:
> =

> protected void taskFunc() {
> =A0 =A0 boolean runSeekIfWanted =3D false;
> =A0 =A0 synchronized (queue) {
> =A0 =A0 =A0 =A0 /* ... */
> =A0 =A0 =A0 =A0 if (playPosition+1 <
> queue.size()) {
> =A0 =A0 =A0 =A0 =A0 =A0 obj =3D
> queue.elementAt(playPosition+1);
> =A0 =A0 =A0 =A0 =A0 =A0 if (obj
> instanceof Buffer) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> Buffer bb =3D (Buffer) obj;
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if
> (bb.data[0] =3D=3D 'O' &&
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 bb.data[1] =3D=3D 'g' &&
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 bb.data[2] =3D=3D 'g' &&
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 bb.data[3] =3D=3D 'S') {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 runSeekIfWanted =3D true;
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> =A0 =A0 Debug.info("[BUFFER] src setting
> permissionToSeek");
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 }
> =A0 =A0 =A0 =A0 =A0 =A0 }
> =A0 =A0 =A0 =A0 }
> =A0 =A0 =A0 =A0 /* ... */
> =A0 =A0 }
> =A0 =A0 /* ... main method code ... */
> =A0 =A0 synchronized (permissionToSeek) {
> =A0 =A0 =A0 =A0 if (runSeekIfWanted) {
> =A0 =A0 =A0 =A0 =A0 =A0
> Debug.info("[BUFFER] src granting permissionToSeek");
> =A0 =A0 =A0 =A0 =A0 =A0
> permissionToSeek.notifyAll();
> =A0 =A0 =A0 =A0 }
> =A0 =A0 }
> }
> =

> 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 =3D 324, queue.size =3D 669
> Elements, size =3D =

> 1359872 bytePlayPosition =3D 678325 -> 64% full
> [INFO] [BUFFER] src granting permissionToSeek
> [INFO] [BUFFER] playPosition =3D 325, queue.size =3D 669
> Elements, size =3D =

> 1359872 bytePlayPosition =3D 679936 -> 64% full
> [INFO] [BUFFER] playPosition =3D 326, queue.size =3D 669
> Elements, size =3D =

> 1359872 bytePlayPosition =3D 679962 -> 64% full
> [INFO] [BUFFER] src setting permissionToSeek
> [INFO] doSeek(): aPos=3D0.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
> [email protected]
> http://lists.xiph.org/mailman/listinfo/theora-dev
> =