Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 143a898

Browse files
author
hungrymonkey
committedOct 4, 2019
seek theora in progress
First Ogg Theora Seek Attempt Ogg Tidbits Ogg lacks a seek table. Timestamp exists on the ogg page with the closing packet stream. Ogg_page_packets can discern which page has a timestamp. Keyframes exist in pages with ogg_pages_packets(&og) == 1 Proposal Split the file into BUFFERSIZE segments for binary searching and backtracking. Algorithm Binary Search Ogg Page Timestamps Backtrack to Keyframe Catch up to Seek Section 1: This section is implemented as nested loops. The inner loops finds an unprocessed buffer segment and retrieves the first ogg_page even if it spans across buffer segments. The outer loop compares the ogg_page into an iterative binary search. Since ogg_page_packets can discern the granulepos or time stamp, no ogg_packet is inspected. Section 2: The section is implemented as a buffer sync step to combine all ogg_pages into one ogg_packet. Section 3: Submit the frames into the theora decoder and advance the video forward until the buffer catches up to the seek time. Modified Implemented Seek Added BUFFERSIZE and remove the hard coded 4096 Known Bugs Seek cannot find subsequent ogg_pages in the buffer segment. When a buffer section has two ogg_pages and the keyframe is encoded in the latter ogg_page. Seek will not find it. VideoTheoraStreak is littered with time resets. Currently, seek only works during play mode. Seek 0 does not work Links https://www.theora.org/doc/libtheora-1.0/group__decfuncs.html https://xiph.org/oggz/doc/group__basics.html Issue #14430
1 parent 3b532aa commit 143a898

File tree

2 files changed

+199
-5
lines changed

2 files changed

+199
-5
lines changed
 

‎modules/theora/video_stream_theora.cpp

+198-5
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,26 @@
3030

3131
#include "video_stream_theora.h"
3232

33+
#include "core/hash_map.h"
34+
#include "core/list.h"
3335
#include "core/os/os.h"
3436
#include "core/project_settings.h"
3537

3638
#include "thirdparty/misc/yuv2rgb.h"
3739

40+
#include <float.h>
41+
3842
int VideoStreamPlaybackTheora::buffer_data() {
3943

40-
char *buffer = ogg_sync_buffer(&oy, 4096);
44+
char *buffer = ogg_sync_buffer(&oy, BUFFERSIZE);
4145

4246
#ifdef THEORA_USE_THREAD_STREAMING
4347

4448
int read;
4549

4650
do {
4751
thread_sem->post();
48-
read = MIN(ring_buffer.data_left(), 4096);
52+
read = MIN(ring_buffer.data_left(), BUFFERSIZE);
4953
if (read) {
5054
ring_buffer.read((uint8_t *)buffer, read);
5155
ogg_sync_wrote(&oy, read);
@@ -59,7 +63,7 @@ int VideoStreamPlaybackTheora::buffer_data() {
5963

6064
#else
6165

62-
int bytes = file->get_buffer((uint8_t *)buffer, 4096);
66+
int bytes = file->get_buffer((uint8_t *)buffer, BUFFERSIZE);
6367
ogg_sync_wrote(&oy, bytes);
6468
return (bytes);
6569

@@ -618,9 +622,198 @@ float VideoStreamPlaybackTheora::get_playback_position() const {
618622
return get_time();
619623
};
620624

621-
void VideoStreamPlaybackTheora::seek(float p_time){
625+
void VideoStreamPlaybackTheora::seek(float p_time) {
626+
627+
struct _page_info {
628+
size_t block_number;
629+
double_t time;
630+
ogg_int64_t granulepos;
631+
};
632+
//https://xiph.org/oggz/doc/group__basics.html
633+
634+
float true_time = p_time - AudioServer::get_singleton()->get_output_latency() - delay_compensation;
635+
p_time = fmax(0, true_time);
636+
637+
size_t buffer_size = (size_t)BUFFERSIZE; //Cast BUFFERSIZE
638+
size_t end_file = file->get_len();
639+
size_t start_file = 0;
640+
size_t number_of_blocks = (end_file - start_file) / buffer_size + ((end_file - start_file) % buffer_size ? 1 : 0);
641+
642+
ogg_packet op;
643+
size_t left = 0;
644+
size_t right = number_of_blocks;
645+
646+
struct _page_info left_page = { .time = 0, .block_number = 0, .granulepos = 0 };
647+
struct _page_info mid_page = { .time = 0, .block_number = 0, .granulepos = 0 };
648+
struct _page_info right_page = { .time = DBL_MAX, .block_number = 0x7FFFFFFFFFFFFFFF, .granulepos = 0x7FFFFFFFFFFFFFFF };
649+
HashMap<ogg_int64_t, _page_info> page_info_table;
650+
HashMap<int, double> block_time;
651+
652+
//Binary Search by finding the proper begin page
653+
while (left <= right) {
654+
//Seek to block
655+
size_t mid_block = left + (right - left) / 2;
656+
int block = mid_block;
657+
658+
if (block_time.has(block)) {
659+
//Check whether this block has been visited
660+
break;
661+
}
622662

623-
// no
663+
//clear the sync state
664+
ogg_sync_reset(&oy);
665+
file->seek(block * buffer_size);
666+
buffer_data();
667+
668+
bool next_midpoint = true;
669+
while (true) {
670+
//keep syncing until a page is found. Buffer is only 4k while ogg pages can be up to 65k in size
671+
int ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
672+
if (ogg_page_sync_state == -1) {
673+
//Give up when the file advances past the right boundary
674+
if (buffer_data() == 0) {
675+
right = mid_block;
676+
break;
677+
} else {
678+
//increment block size we buffered the next block
679+
block++;
680+
}
681+
} else {
682+
if (ogg_page_sync_state == 0) {
683+
//Check if I reached the end of the file
684+
if (buffer_data() == 0) {
685+
right = mid_block;
686+
break;
687+
} else {
688+
block++;
689+
}
690+
} else {
691+
//Only pages with a end packet have granulepos. Check the stream
692+
if (ogg_page_packets(&og) > 0 && ogg_page_serialno(&og) == to.serialno) {
693+
next_midpoint = false;
694+
break;
695+
}
696+
}
697+
}
698+
}
699+
if (next_midpoint)
700+
continue;
701+
702+
ogg_int64_t granulepos = ogg_page_granulepos(&og);
703+
ogg_int64_t page_number = ogg_page_pageno(&og);
704+
struct _page_info pg_info = { .time = th_granule_time(td, granulepos), .block_number = mid_block, .granulepos = granulepos };
705+
page_info_table.set(page_number, pg_info);
706+
block_time.set(mid_block, pg_info.time);
707+
mid_page = pg_info;
708+
709+
//I can finally implement the binary search comparisons
710+
if (abs(p_time - pg_info.time) < .001) {
711+
//The video managed to be equal
712+
right_page = pg_info;
713+
break;
714+
}
715+
if (pg_info.time > p_time) {
716+
if (pg_info.granulepos < right_page.granulepos)
717+
right_page = pg_info;
718+
right = mid_block;
719+
} else {
720+
if (pg_info.granulepos > left_page.granulepos)
721+
left_page = pg_info;
722+
left = mid_block;
723+
}
724+
}
725+
//print_line(rtos(th_granule_time(td,mid_page.granulepos)) + " " + rtos(mid_page.time));
726+
//Now I have to find the closest lower keyframe
727+
int current_block = mid_page.block_number;
728+
//I have the midblock, check if is worthwhile process
729+
if (mid_page.time > p_time || ogg_page_continued(&og)) {
730+
current_block--;
731+
}
732+
/*
733+
ogg_stream_reset(&to);
734+
ogg_stream_reset(&vo);
735+
ogg_sync_reset(&oy);
736+
file->seek(current_block * buffer_size);
737+
while(buffer_data() > 0) {
738+
while(ogg_sync_pageout(&oy, &og) > 0) {
739+
queue_page(&og);
740+
print_line("page: " + itos(ogg_page_pageno(&og)) + "block num: " + itos(current_block));
741+
while(ogg_stream_packetout(&to, &op) > 0) {
742+
print_line(itos(th_packet_iskeyframe(&op)) + " grand " + itos(op.granulepos));
743+
}
744+
}
745+
current_block++;
746+
}*/
747+
// Backtrack to find the keyframe
748+
// Keyframes seem to reside on their own page
749+
while (current_block >= 0) {
750+
ogg_stream_reset(&to);
751+
ogg_stream_reset(&vo);
752+
ogg_sync_reset(&oy);
753+
file->seek(current_block * buffer_size);
754+
buffer_data();
755+
int ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
756+
if (ogg_page_sync_state == -1) {
757+
ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
758+
}
759+
while (ogg_page_sync_state == 0) {
760+
buffer_data();
761+
ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
762+
}
763+
if (ogg_page_sync_state == 1) {
764+
if (ogg_page_packets(&og) == 1) {
765+
queue_page(&og);
766+
ogg_stream_packetpeek(&to, &op); //Just attempt it
767+
while (ogg_stream_packetpeek(&to, &op) == 0) {
768+
if (ogg_sync_pageout(&oy, &og) > 0) {
769+
queue_page(&og);
770+
} else {
771+
buffer_data();
772+
}
773+
}
774+
}
775+
}
776+
if (th_packet_iskeyframe(&op)) {
777+
if (videobuf_time < p_time) {
778+
break;
779+
}
780+
}
781+
current_block--;
782+
}
783+
ogg_packet audio_op;
784+
vorbis_synthesis_restart(&vd);
785+
//decode video until the proper time is found
786+
th_decode_ctl(td, TH_DECCTL_SET_PPLEVEL, &pp_level, sizeof(pp_level));
787+
while (videobuf_time <= p_time) {
788+
queue_page(&og);
789+
while (ogg_stream_packetout(&to, &op) > 0) {
790+
ogg_int64_t videobuf_granulepos;
791+
th_decode_packetin(td, &op, &videobuf_granulepos);
792+
videobuf_time = th_granule_time(td, videobuf_granulepos);
793+
if (op.granulepos > 0)
794+
th_decode_ctl(td, TH_DECCTL_SET_GRANPOS, &op.granulepos, sizeof(op.granulepos));
795+
th_ycbcr_buffer yuv;
796+
th_decode_ycbcr_out(td, yuv); //dump frames
797+
}
798+
//Needed to calculate the time without extra memory allocation
799+
800+
//We have to clear the vorbis buffer
801+
ogg_int64_t total_packets = ogg_page_packets(&og); //Trick to figure out the time
802+
while (ogg_stream_packetout(&vo, &audio_op) > 0) {
803+
double music_time = vorbis_granule_time(&vd, ogg_page_granulepos(&og) + ogg_page_packets(&og) - total_packets--);
804+
if (music_time > p_time) {
805+
if (vorbis_synthesis(&vb, &audio_op) == 0) { /* test for success! */
806+
vorbis_synthesis_blockin(&vd, &vb);
807+
}
808+
}
809+
}
810+
int ogg_sync_state = ogg_sync_pageout(&oy, &og);
811+
while (ogg_sync_state < 1) {
812+
buffer_data();
813+
ogg_sync_state = ogg_sync_pageout(&oy, &og);
814+
}
815+
}
816+
time = p_time;
624817
};
625818

626819
void VideoStreamPlaybackTheora::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) {

‎modules/theora/video_stream_theora.h

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback {
5050

5151
enum {
5252
MAX_FRAMES = 4,
53+
BUFFERSIZE = 4096,
5354
};
5455

5556
//Image frames[MAX_FRAMES];

0 commit comments

Comments
 (0)
Please sign in to comment.