1 | /**************************************************************************/ |
2 | /* ogg_packet_sequence.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "ogg_packet_sequence.h" |
32 | |
33 | #include "core/variant/typed_array.h" |
34 | |
35 | void OggPacketSequence::push_page(int64_t p_granule_pos, const Vector<PackedByteArray> &p_data) { |
36 | Vector<PackedByteArray> data_stored; |
37 | for (int i = 0; i < p_data.size(); i++) { |
38 | data_stored.push_back(p_data[i]); |
39 | } |
40 | page_granule_positions.push_back(p_granule_pos); |
41 | page_data.push_back(data_stored); |
42 | data_version++; |
43 | } |
44 | |
45 | void OggPacketSequence::set_packet_data(const TypedArray<Array> &p_data) { |
46 | data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. |
47 | page_data.clear(); |
48 | for (int page_idx = 0; page_idx < p_data.size(); page_idx++) { |
49 | // Push a new page. We cleared the vector so this will be at index `page_idx`. |
50 | page_data.push_back(Vector<PackedByteArray>()); |
51 | TypedArray<PackedByteArray> this_page_data = p_data[page_idx]; |
52 | for (int packet = 0; packet < this_page_data.size(); packet++) { |
53 | page_data.write[page_idx].push_back(this_page_data[packet]); |
54 | } |
55 | } |
56 | } |
57 | |
58 | TypedArray<Array> OggPacketSequence::get_packet_data() const { |
59 | TypedArray<Array> ret; |
60 | for (const Vector<PackedByteArray> &page : page_data) { |
61 | Array page_variant; |
62 | for (const PackedByteArray &packet : page) { |
63 | page_variant.push_back(packet); |
64 | } |
65 | ret.push_back(page_variant); |
66 | } |
67 | return ret; |
68 | } |
69 | |
70 | void OggPacketSequence::set_packet_granule_positions(const PackedInt64Array &p_granule_positions) { |
71 | data_version++; // Update the data version so old playbacks know that they can't rely on us anymore. |
72 | page_granule_positions.clear(); |
73 | for (int page_idx = 0; page_idx < p_granule_positions.size(); page_idx++) { |
74 | int64_t granule_pos = p_granule_positions[page_idx]; |
75 | page_granule_positions.push_back(granule_pos); |
76 | } |
77 | } |
78 | |
79 | PackedInt64Array OggPacketSequence::get_packet_granule_positions() const { |
80 | PackedInt64Array ret; |
81 | for (int64_t granule_pos : page_granule_positions) { |
82 | ret.push_back(granule_pos); |
83 | } |
84 | return ret; |
85 | } |
86 | |
87 | void OggPacketSequence::set_sampling_rate(float p_sampling_rate) { |
88 | sampling_rate = p_sampling_rate; |
89 | } |
90 | |
91 | float OggPacketSequence::get_sampling_rate() const { |
92 | return sampling_rate; |
93 | } |
94 | |
95 | int64_t OggPacketSequence::get_final_granule_pos() const { |
96 | if (!page_granule_positions.is_empty()) { |
97 | return page_granule_positions[page_granule_positions.size() - 1]; |
98 | } |
99 | return -1; |
100 | } |
101 | |
102 | float OggPacketSequence::get_length() const { |
103 | int64_t granule_pos = get_final_granule_pos(); |
104 | if (granule_pos < 0) { |
105 | return 0; |
106 | } |
107 | return granule_pos / sampling_rate; |
108 | } |
109 | |
110 | Ref<OggPacketSequencePlayback> OggPacketSequence::instantiate_playback() { |
111 | Ref<OggPacketSequencePlayback> playback; |
112 | playback.instantiate(); |
113 | playback->ogg_packet_sequence = Ref<OggPacketSequence>(this); |
114 | playback->data_version = data_version; |
115 | |
116 | return playback; |
117 | } |
118 | |
119 | void OggPacketSequence::_bind_methods() { |
120 | ClassDB::bind_method(D_METHOD("set_packet_data" , "packet_data" ), &OggPacketSequence::set_packet_data); |
121 | ClassDB::bind_method(D_METHOD("get_packet_data" ), &OggPacketSequence::get_packet_data); |
122 | |
123 | ClassDB::bind_method(D_METHOD("set_packet_granule_positions" , "granule_positions" ), &OggPacketSequence::set_packet_granule_positions); |
124 | ClassDB::bind_method(D_METHOD("get_packet_granule_positions" ), &OggPacketSequence::get_packet_granule_positions); |
125 | |
126 | ClassDB::bind_method(D_METHOD("set_sampling_rate" , "sampling_rate" ), &OggPacketSequence::set_sampling_rate); |
127 | ClassDB::bind_method(D_METHOD("get_sampling_rate" ), &OggPacketSequence::get_sampling_rate); |
128 | |
129 | ClassDB::bind_method(D_METHOD("get_length" ), &OggPacketSequence::get_length); |
130 | |
131 | ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "packet_data" , PROPERTY_HINT_ARRAY_TYPE, "PackedByteArray" , PROPERTY_USAGE_NO_EDITOR), "set_packet_data" , "get_packet_data" ); |
132 | ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT64_ARRAY, "granule_positions" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_packet_granule_positions" , "get_packet_granule_positions" ); |
133 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sampling_rate" , PROPERTY_HINT_NONE, "" , PROPERTY_USAGE_NO_EDITOR), "set_sampling_rate" , "get_sampling_rate" ); |
134 | } |
135 | |
136 | bool OggPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { |
137 | ERR_FAIL_COND_V(data_version != ogg_packet_sequence->data_version, false); |
138 | ERR_FAIL_COND_V(ogg_packet_sequence->page_data.is_empty(), false); |
139 | ERR_FAIL_COND_V(ogg_packet_sequence->page_granule_positions.is_empty(), false); |
140 | ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); |
141 | |
142 | // Move on to the next page if need be. This happens first to help simplify seek logic. |
143 | while (packet_cursor >= ogg_packet_sequence->page_data[page_cursor].size()) { |
144 | packet_cursor = 0; |
145 | page_cursor++; |
146 | if (page_cursor >= ogg_packet_sequence->page_data.size()) { |
147 | return false; |
148 | } |
149 | } |
150 | |
151 | ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); |
152 | |
153 | packet->b_o_s = page_cursor == 0 && packet_cursor == 0; |
154 | packet->e_o_s = page_cursor == ogg_packet_sequence->page_data.size() - 1 && packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1; |
155 | packet->granulepos = packet_cursor == ogg_packet_sequence->page_data[page_cursor].size() - 1 ? ogg_packet_sequence->page_granule_positions[page_cursor] : -1; |
156 | packet->packetno = packetno++; |
157 | packet->bytes = ogg_packet_sequence->page_data[page_cursor][packet_cursor].size(); |
158 | packet->packet = (unsigned char *)(ogg_packet_sequence->page_data[page_cursor][packet_cursor].ptr()); |
159 | |
160 | *p_packet = packet; |
161 | |
162 | packet_cursor++; |
163 | |
164 | return true; |
165 | } |
166 | |
167 | uint32_t OggPacketSequencePlayback::seek_page_internal(int64_t granule, uint32_t after_page_inclusive, uint32_t before_page_inclusive) { |
168 | // FIXME: This function needs better corner case handling. |
169 | if (before_page_inclusive == after_page_inclusive) { |
170 | return before_page_inclusive; |
171 | } |
172 | uint32_t actual_middle_page = after_page_inclusive + (before_page_inclusive - after_page_inclusive) / 2; |
173 | // Complicating the bisection search algorithm, the middle page might not have a packet that ends on it, |
174 | // which means it might not have a correct granule position. Find a nearby page that does have a packet ending on it. |
175 | uint32_t bisection_page = -1; |
176 | // Don't include before_page_inclusive because that always succeeds and will cause infinite recursion later. |
177 | for (uint32_t test_page = actual_middle_page; test_page < before_page_inclusive; test_page++) { |
178 | if (ogg_packet_sequence->page_data[test_page].size() > 0) { |
179 | bisection_page = test_page; |
180 | break; |
181 | } |
182 | } |
183 | // Check if we have to go backwards. |
184 | if (bisection_page == (unsigned int)-1) { |
185 | for (uint32_t test_page = actual_middle_page; test_page >= after_page_inclusive; test_page--) { |
186 | if (ogg_packet_sequence->page_data[test_page].size() > 0) { |
187 | bisection_page = test_page; |
188 | break; |
189 | } |
190 | } |
191 | } |
192 | if (bisection_page == (unsigned int)-1) { |
193 | return -1; |
194 | } |
195 | |
196 | int64_t bisection_granule_pos = ogg_packet_sequence->page_granule_positions[bisection_page]; |
197 | if (granule > bisection_granule_pos) { |
198 | return seek_page_internal(granule, bisection_page + 1, before_page_inclusive); |
199 | } else { |
200 | return seek_page_internal(granule, after_page_inclusive, bisection_page); |
201 | } |
202 | } |
203 | |
204 | bool OggPacketSequencePlayback::seek_page(int64_t p_granule_pos) { |
205 | int correct_page = seek_page_internal(p_granule_pos, 0, ogg_packet_sequence->page_data.size() - 1); |
206 | if (correct_page == -1) { |
207 | return false; |
208 | } |
209 | |
210 | packet_cursor = 0; |
211 | page_cursor = correct_page; |
212 | |
213 | // Don't pretend subsequent packets are contiguous with previous ones. |
214 | packetno = 0; |
215 | |
216 | return true; |
217 | } |
218 | |
219 | OggPacketSequencePlayback::OggPacketSequencePlayback() { |
220 | packet = new ogg_packet(); |
221 | } |
222 | |
223 | OggPacketSequencePlayback::~OggPacketSequencePlayback() { |
224 | delete packet; |
225 | } |
226 | |