Skip to content

Commit a574c02

Browse files
authored
Merge pull request #80452 from strellydev/ogg-loop-offset-pop-fix
Fix OGG audio loop offset pop
2 parents 325cc01 + 9c9f115 commit a574c02

File tree

5 files changed

+107
-102
lines changed

5 files changed

+107
-102
lines changed

modules/ogg/ogg_packet_sequence.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ bool OggPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const {
159159

160160
*p_packet = packet;
161161

162-
packet_cursor++;
162+
if (!packet->e_o_s) { // Added this so it doesn't try to go to the next packet if it's the last packet of the file.
163+
packet_cursor++;
164+
}
163165

164166
return true;
165167
}
@@ -216,6 +218,20 @@ bool OggPacketSequencePlayback::seek_page(int64_t p_granule_pos) {
216218
return true;
217219
}
218220

221+
int64_t OggPacketSequencePlayback::get_page_number() const {
222+
return page_cursor;
223+
}
224+
225+
bool OggPacketSequencePlayback::set_page_number(int64_t p_page_number) {
226+
if (p_page_number >= 0 && p_page_number < ogg_packet_sequence->page_data.size()) {
227+
page_cursor = p_page_number;
228+
packet_cursor = 0;
229+
packetno = 0;
230+
return true;
231+
}
232+
return false;
233+
}
234+
219235
OggPacketSequencePlayback::OggPacketSequencePlayback() {
220236
packet = new ogg_packet();
221237
}

modules/ogg/ogg_packet_sequence.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ class OggPacketSequencePlayback : public RefCounted {
120120
// Returns true on success, false on failure.
121121
bool seek_page(int64_t p_granule_pos);
122122

123+
// Gets the current page number.
124+
int64_t get_page_number() const;
125+
126+
// Moves to a specific page in the stream.
127+
// Returns true on success, false if the page number is out of bounds.
128+
bool set_page_number(int64_t p_page_number);
129+
123130
OggPacketSequencePlayback();
124131
virtual ~OggPacketSequencePlayback();
125132
};

modules/vorbis/audio_stream_ogg_vorbis.cpp

Lines changed: 67 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,10 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) {
264264
return;
265265
}
266266

267-
vorbis_synthesis_restart(&dsp_state);
268-
269267
if (p_time >= vorbis_stream->get_length()) {
270268
p_time = 0;
271269
}
270+
272271
frames_mixed = uint32_t(vorbis_data->get_sampling_rate() * p_time);
273272

274273
const int64_t desired_sample = p_time * get_stream_sampling_rate();
@@ -278,107 +277,81 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) {
278277
return;
279278
}
280279

281-
ogg_packet *packet;
282-
if (!vorbis_data_playback->next_ogg_packet(&packet)) {
283-
WARN_PRINT_ONCE("seeking beyond limits");
284-
return;
280+
// We want to start decoding before the page that we expect the sample to be in (the sample may
281+
// be part of a partial packet across page boundaries). Otherwise, the decoder may not have
282+
// synchronized before reaching the sample.
283+
int64_t start_page_number = vorbis_data_playback->get_page_number() - 1;
284+
if (start_page_number < 0) {
285+
start_page_number = 0;
285286
}
286287

287-
// The granule position of the page we're seeking through.
288-
int64_t granule_pos = 0;
289-
290-
int headers_remaining = 0;
291-
int samples_in_page = 0;
292-
int err;
293288
while (true) {
294-
if (vorbis_synthesis_idheader(packet)) {
295-
headers_remaining = 3;
296-
}
297-
if (!headers_remaining) {
298-
err = vorbis_synthesis(&block, packet);
299-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err));
300-
301-
err = vorbis_synthesis_blockin(&dsp_state, &block);
302-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err));
303-
304-
int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr);
305-
err = vorbis_synthesis_read(&dsp_state, samples_out);
306-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err));
307-
308-
samples_in_page += samples_out;
309-
310-
} else {
311-
headers_remaining--;
312-
}
313-
if (packet->granulepos != -1 && headers_remaining == 0) {
314-
// This indicates the end of the page.
315-
granule_pos = packet->granulepos;
316-
break;
317-
}
318-
if (packet->e_o_s) {
319-
break;
320-
}
321-
if (!vorbis_data_playback->next_ogg_packet(&packet)) {
322-
// We should get an e_o_s flag before this happens.
323-
WARN_PRINT("Vorbis file ended without warning.");
324-
break;
325-
}
326-
}
289+
ogg_packet *packet;
290+
int err;
327291

328-
int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample);
292+
// We start at an unknown granule position.
293+
int64_t granule_pos = -1;
329294

330-
if (samples_to_burn > samples_in_page) {
331-
WARN_PRINT_ONCE("Burning more samples than we have in this page. Check seek algorithm.");
332-
} else if (samples_to_burn < 0) {
333-
WARN_PRINT_ONCE("Burning negative samples doesn't make sense. Check seek algorithm.");
334-
}
295+
// Decode data until we get to the desired sample or notice that we have read past it.
296+
vorbis_data_playback->set_page_number(start_page_number);
297+
vorbis_synthesis_restart(&dsp_state);
335298

336-
// Seek again, this time we'll burn a specific number of samples instead of all of them.
337-
if (!vorbis_data_playback->seek_page(desired_sample)) {
338-
WARN_PRINT("seek failed");
339-
return;
340-
}
341-
342-
if (!vorbis_data_playback->next_ogg_packet(&packet)) {
343-
WARN_PRINT_ONCE("seeking beyond limits");
344-
return;
345-
}
346-
vorbis_synthesis_restart(&dsp_state);
299+
while (true) {
300+
if (!vorbis_data_playback->next_ogg_packet(&packet)) {
301+
WARN_PRINT_ONCE("Seeking beyond limits");
302+
return;
303+
}
347304

348-
while (true) {
349-
if (vorbis_synthesis_idheader(packet)) {
350-
headers_remaining = 3;
351-
}
352-
if (!headers_remaining) {
353305
err = vorbis_synthesis(&block, packet);
354-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err));
355-
356-
err = vorbis_synthesis_blockin(&dsp_state, &block);
357-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err));
358-
359-
int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr);
360-
int read_samples = samples_to_burn > samples_out ? samples_out : samples_to_burn;
361-
err = vorbis_synthesis_read(&dsp_state, samples_out);
362-
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err));
363-
samples_to_burn -= read_samples;
364-
365-
if (samples_to_burn <= 0) {
366-
break;
306+
if (err != OV_ENOTAUDIO) {
307+
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err) + ".");
308+
309+
err = vorbis_synthesis_blockin(&dsp_state, &block);
310+
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err) + ".");
311+
312+
int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr);
313+
314+
if (granule_pos < 0) {
315+
// We don't know where we are yet, so just keep on decoding.
316+
err = vorbis_synthesis_read(&dsp_state, samples_out);
317+
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + ".");
318+
} else if (granule_pos + samples_out >= desired_sample) {
319+
// Our sample is in this block. Skip the beginning of the block up to the sample, then
320+
// return.
321+
int skip_samples = (int)(desired_sample - granule_pos);
322+
err = vorbis_synthesis_read(&dsp_state, skip_samples);
323+
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + ".");
324+
have_samples_left = skip_samples < samples_out;
325+
have_packets_left = !packet->e_o_s;
326+
return;
327+
} else {
328+
// Our sample is not in this block. Skip it.
329+
err = vorbis_synthesis_read(&dsp_state, samples_out);
330+
ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + ".");
331+
granule_pos += samples_out;
332+
}
333+
}
334+
if (packet->granulepos != -1) {
335+
// We found an update to our granule position.
336+
granule_pos = packet->granulepos;
337+
if (granule_pos > desired_sample) {
338+
// We've read past our sample. We need to start on an earlier page.
339+
if (start_page_number == 0) {
340+
// We didn't find the sample even reading from the beginning.
341+
have_samples_left = false;
342+
have_packets_left = !packet->e_o_s;
343+
return;
344+
}
345+
start_page_number--;
346+
break;
347+
}
348+
}
349+
if (packet->e_o_s) {
350+
// We've reached the end of the stream and didn't find our sample.
351+
have_samples_left = false;
352+
have_packets_left = false;
353+
return;
367354
}
368-
} else {
369-
headers_remaining--;
370-
}
371-
if (packet->granulepos != -1 && headers_remaining == 0) {
372-
// This indicates the end of the page.
373-
break;
374-
}
375-
if (packet->e_o_s) {
376-
break;
377-
}
378-
if (!vorbis_data_playback->next_ogg_packet(&packet)) {
379-
// We should get an e_o_s flag before this happens.
380-
WARN_PRINT("Vorbis file ended without warning.");
381-
break;
382355
}
383356
}
384357
}

modules/vorbis/audio_stream_ogg_vorbis.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ class AudioStreamOggVorbis : public AudioStream {
107107
friend class AudioStreamPlaybackOggVorbis;
108108

109109
int channels = 1;
110-
float length = 0.0;
110+
double length = 0.0;
111111
bool loop = false;
112-
float loop_offset = 0.0;
112+
double loop_offset = 0.0;
113113

114114
// Performs a seek to the beginning of the stream, should not be called during playback!
115115
// Also causes allocation and deallocation.

modules/vorbis/resource_importer_ogg_vorbis.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) {
9797

9898
Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
9999
bool loop = p_options["loop"];
100-
float loop_offset = p_options["loop_offset"];
100+
double loop_offset = p_options["loop_offset"];
101101
double bpm = p_options["bpm"];
102102
int beat_count = p_options["beat_count"];
103103
int bar_beats = p_options["bar_beats"];
@@ -184,14 +184,15 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect
184184
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg stream error " + itos(err));
185185
int desync_iters = 0;
186186

187-
Vector<Vector<uint8_t>> packet_data;
187+
RBMap<uint64_t, Vector<Vector<uint8_t>>> sorted_packets;
188188
int64_t granule_pos = 0;
189189

190190
while (true) {
191191
err = ogg_stream_packetout(&stream_state, &packet);
192192
if (err == -1) {
193193
// According to the docs this is usually recoverable, but don't sit here spinning forever.
194194
desync_iters++;
195+
WARN_PRINT_ONCE("Desync during ogg import.");
195196
ERR_FAIL_COND_V_MSG(desync_iters > 100, Ref<AudioStreamOggVorbis>(), "Packet sync issue during Ogg import");
196197
continue;
197198
} else if (err == 0) {
@@ -207,16 +208,24 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect
207208
}
208209
break;
209210
}
210-
granule_pos = packet.granulepos;
211+
if (packet.granulepos > granule_pos) {
212+
granule_pos = packet.granulepos;
213+
}
211214

212215
PackedByteArray data;
213216
data.resize(packet.bytes);
214217
memcpy(data.ptrw(), packet.packet, packet.bytes);
215-
packet_data.push_back(data);
218+
sorted_packets[granule_pos].push_back(data);
216219
packet_count++;
217220
}
221+
Vector<Vector<uint8_t>> packet_data;
222+
for (const KeyValue<uint64_t, Vector<Vector<uint8_t>>> &pair : sorted_packets) {
223+
for (const Vector<uint8_t> &packets : pair.value) {
224+
packet_data.push_back(packets);
225+
}
226+
}
218227
if (initialized_stream) {
219-
ogg_packet_sequence->push_page(granule_pos, packet_data);
228+
ogg_packet_sequence->push_page(ogg_page_granulepos(&page), packet_data);
220229
}
221230
}
222231
if (initialized_stream) {

0 commit comments

Comments
 (0)