30
30
31
31
#include " video_stream_theora.h"
32
32
33
+ #include " core/hash_map.h"
34
+ #include " core/list.h"
33
35
#include " core/os/os.h"
34
36
#include " core/project_settings.h"
35
37
36
38
#include " thirdparty/misc/yuv2rgb.h"
37
39
40
+ #include < float.h>
41
+
38
42
int VideoStreamPlaybackTheora::buffer_data () {
39
43
40
- char *buffer = ogg_sync_buffer (&oy, 4096 );
44
+ char *buffer = ogg_sync_buffer (&oy, BUFFERSIZE );
41
45
42
46
#ifdef THEORA_USE_THREAD_STREAMING
43
47
44
48
int read ;
45
49
46
50
do {
47
51
thread_sem->post ();
48
- read = MIN (ring_buffer.data_left (), 4096 );
52
+ read = MIN (ring_buffer.data_left (), BUFFERSIZE );
49
53
if (read ) {
50
54
ring_buffer.read ((uint8_t *)buffer, read );
51
55
ogg_sync_wrote (&oy, read );
@@ -59,7 +63,7 @@ int VideoStreamPlaybackTheora::buffer_data() {
59
63
60
64
#else
61
65
62
- int bytes = file->get_buffer ((uint8_t *)buffer, 4096 );
66
+ int bytes = file->get_buffer ((uint8_t *)buffer, BUFFERSIZE );
63
67
ogg_sync_wrote (&oy, bytes);
64
68
return (bytes);
65
69
@@ -618,9 +622,198 @@ float VideoStreamPlaybackTheora::get_playback_position() const {
618
622
return get_time ();
619
623
};
620
624
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
+ }
622
662
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;
624
817
};
625
818
626
819
void VideoStreamPlaybackTheora::set_mix_callback (AudioMixCallback p_callback, void *p_userdata) {
0 commit comments