Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

seek theora in progress #32539

Closed
wants to merge 11 commits into from
244 changes: 235 additions & 9 deletions modules/theora/video_stream_theora.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,26 @@

#include "video_stream_theora.h"

#include "core/hash_map.h"
#include "core/list.h"
#include "core/os/os.h"
#include "core/project_settings.h"

#include "thirdparty/misc/yuv2rgb.h"

#include <float.h>

int VideoStreamPlaybackTheora::buffer_data() {

char *buffer = ogg_sync_buffer(&oy, 4096);
char *buffer = ogg_sync_buffer(&oy, BUFFERSIZE);

#ifdef THEORA_USE_THREAD_STREAMING

int read;

do {
thread_sem->post();
read = MIN(ring_buffer.data_left(), 4096);
read = MIN(ring_buffer.data_left(), BUFFERSIZE);
if (read) {
ring_buffer.read((uint8_t *)buffer, read);
ogg_sync_wrote(&oy, read);
Expand All @@ -59,7 +63,7 @@ int VideoStreamPlaybackTheora::buffer_data() {

#else

int bytes = file->get_buffer((uint8_t *)buffer, 4096);
int bytes = file->get_buffer((uint8_t *)buffer, BUFFERSIZE);
ogg_sync_wrote(&oy, bytes);
return (bytes);

Expand Down Expand Up @@ -552,11 +556,8 @@ void VideoStreamPlaybackTheora::update(float p_delta) {

void VideoStreamPlaybackTheora::play() {

if (!playing)
time = 0;
else {
if (playing)
stop();
}

playing = true;
delay_compensation = ProjectSettings::get_singleton()->get("audio/video_delay_compensation_ms");
Expand Down Expand Up @@ -618,9 +619,234 @@ float VideoStreamPlaybackTheora::get_playback_position() const {
return get_time();
};

void VideoStreamPlaybackTheora::seek(float p_time){
void VideoStreamPlaybackTheora::seek(float p_time) {
bool play_state = playing; //Save the playing state
stop();

struct _page_info {
size_t block_number;
double_t time;
ogg_int64_t granulepos;
};
//https://xiph.org/oggz/doc/group__basics.html
float true_time = p_time - AudioServer::get_singleton()->get_output_latency() - delay_compensation;
p_time = fmax(0, true_time);

size_t buffer_size = (size_t)BUFFERSIZE; //Cast BUFFERSIZE
size_t end_file = file->get_len();
size_t start_file = 0;
size_t number_of_blocks = (end_file - start_file) / buffer_size;

ogg_packet op;
size_t left = 0;
size_t right = number_of_blocks;

struct _page_info left_page = { .time = 0, .block_number = 0, .granulepos = 0 };
struct _page_info mid_page = { .time = 0, .block_number = 0, .granulepos = 0 };
struct _page_info right_page = { .time = DBL_MAX, .block_number = right, .granulepos = 0x7FFFFFFFFFFFFFFF };
HashMap<ogg_int64_t, _page_info> page_info_table;
HashMap<int, double> block_time;

//Binary Search by finding the proper begin page
while (left <= right) {
//Seek to block
size_t mid_block = left + (right - left) / 2;
int block = mid_block;

if (block_time.has(block)) {
//Check whether this block has been visited
break;
}

//clear the sync state
ogg_sync_reset(&oy);
file->seek(block * buffer_size);
buffer_data();

bool next_midpoint = true;
while (true) {
//keep syncing until a page is found. Buffer is only 4k while ogg pages can be up to 65k in size
int ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
if (ogg_page_sync_state == -1) {
//Give up when the file advances past the right boundary
if (buffer_data() == 0) {
right = mid_block;
break;
} else {
//increment block size we buffered the next block
block++;
}
} else {
if (ogg_page_sync_state == 0) {
//Check if I reached the end of the file
if (buffer_data() == 0) {
right = mid_block;
break;
} else {
block++;
}
} else {
//Only pages with a end packet have granulepos. Check the stream
if (ogg_page_packets(&og) > 0 && ogg_page_serialno(&og) == to.serialno) {
next_midpoint = false;
break;
}
}
}
}
if (next_midpoint)
continue;

ogg_int64_t granulepos = ogg_page_granulepos(&og);
ogg_int64_t page_number = ogg_page_pageno(&og);
struct _page_info pg_info = { .time = th_granule_time(td, granulepos), .block_number = mid_block, .granulepos = granulepos };
page_info_table.set(page_number, pg_info);
block_time.set(mid_block, pg_info.time);
mid_page = pg_info;

//I can finally implement the binary search comparisons
if (abs(p_time - pg_info.time) < .001) {
//The video managed to be equal
right_page = pg_info;
break;
}
if (pg_info.time > p_time) {
if (pg_info.granulepos < right_page.granulepos)
right_page = pg_info;
right = mid_block;
} else {
if (pg_info.granulepos > left_page.granulepos)
left_page = pg_info;
left = mid_block;
}
}
//print_line(rtos(th_granule_time(td,mid_page.granulepos)) + " " + rtos(mid_page.time));
//Now I have to find the closest lower keyframe
int current_block = mid_page.block_number;
//I have the midblock, check if is worthwhile process
if (mid_page.time > p_time || ogg_page_continued(&og)) {
current_block--;
}

// Backtrack to find the keyframe
// Keyframes seem to reside on their own page
while (current_block >= 0) {
ogg_stream_reset(&to);
ogg_stream_reset(&vo);
ogg_sync_reset(&oy);
file->seek(current_block * buffer_size);
buffer_data();
bool seeked_file = false;
bool keyframe_found = false;
while (!seeked_file) {
int ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
if (ogg_page_sync_state == -1) {
ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
}
while (ogg_page_sync_state == 0) {
buffer_data();
seeked_file = true;
ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
}
if (ogg_page_sync_state == 1) {
//Only queue pages with a single packet
if (ogg_page_packets(&og) == 1) {
queue_page(&og);
ogg_stream_packetpeek(&to, &op); //Just attempt it
while (ogg_stream_packetpeek(&to, &op) == 0) {
if (ogg_sync_pageout(&oy, &og) > 0) {
queue_page(&og);
} else {
buffer_data();
}
}
if (th_packet_iskeyframe(&op)) {
videobuf_time = th_granule_time(td, op.granulepos);
if (videobuf_time < p_time) {
keyframe_found = true;
break;
}
} else
ogg_stream_packetout(&to, &op);
}
}
}
if(keyframe_found) {
break;
}
current_block--;
}
ogg_int64_t audio_granulepos = 0;
ogg_int64_t total_packets = 0;
vorbis_synthesis_restart(&vd);
//Set to the lowest processing level
pp_level = 0;
th_decode_ctl(td, TH_DECCTL_SET_PPLEVEL, &pp_level, sizeof(pp_level));

//Process keyframe
ogg_int64_t video_granulepos;
th_decode_packetin(td, &op, &video_granulepos);
th_decode_ctl(td, TH_DECCTL_SET_GRANPOS, &video_granulepos, sizeof(video_granulepos));
th_ycbcr_buffer yuv;
th_decode_ycbcr_out(td, yuv); //dump frame
ogg_stream_packetout(&to, &op);

// no
//decode video until the decoder catches up to the seek time
while (videobuf_time <= p_time) {
int ogg_sync_state = ogg_sync_pageout(&oy, &og);
while (ogg_sync_state < 1) {
buffer_data();
ogg_sync_state = ogg_sync_pageout(&oy, &og);
}
if(ogg_page_serialno(&og) == to.serialno) {
queue_page(&og);
while (ogg_stream_packetout(&to, &op) > 0) {
ogg_int64_t tmp_granulepos;
th_decode_packetin(td, &op, &tmp_granulepos);
if (op.granulepos > 0) {
th_decode_ctl(td, TH_DECCTL_SET_GRANPOS, &op.granulepos, sizeof(op.granulepos));
videobuf_time = th_granule_time(td, tmp_granulepos);
video_granulepos = tmp_granulepos;
} else {
videobuf_time = th_granule_time(td, video_granulepos++);
}
th_ycbcr_buffer yuv;
th_decode_ycbcr_out(td, yuv); //dump frames
}
//Needed to calculate the time without extra memory allocation
} else {
//Drop pages behind the seek
double end_music_time = vorbis_granule_time(&vd, ogg_page_granulepos(&og));
if (end_music_time > p_time) {
queue_page(&og);
//Queue the page which music time is greater than the seek time.
audio_granulepos = ogg_page_granulepos(&og);
total_packets = ogg_page_packets(&og);
}
}
}
//Update the audioframe time
audio_frames_wrote = videobuf_time * vi.rate;
while(ogg_stream_packetout(&vo, &op) > 0) {
ogg_int64_t offset = vorbis_packet_blocksize(&vi, &op) * total_packets--;
double current_audio_time = vorbis_granule_time(&vd, audio_granulepos - offset);
double diff = current_audio_time - videobuf_time;
if( diff > 0 ) {
int blank_frames = diff * vi.rate * vi.channels;
const int AUXBUF_LEN = 4096;
float aux_buffer[AUXBUF_LEN];
int m = MIN(AUXBUF_LEN / vi.channels, blank_frames);
for(int i = 0; i < m; i++)
aux_buffer[i] = 0;
int mixed = mix_callback(mix_udata, aux_buffer, m);
audio_frames_wrote = mixed + videobuf_time * vi.rate;
break;
}
}
time = videobuf_time; //Set the time to current frame
//Easier to call stop and play to reset the video
if(play_state)
play();
};

void VideoStreamPlaybackTheora::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) {
Expand Down
1 change: 1 addition & 0 deletions modules/theora/video_stream_theora.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback {

enum {
MAX_FRAMES = 4,
BUFFERSIZE = 4096,
};

//Image frames[MAX_FRAMES];
Expand Down