summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2022-04-18 14:21:12 +0100
committerAidan MacDonald <amachronic@protonmail.com>2022-05-02 12:16:21 -0400
commit4dd3c2b33ec3d181f54cc1bcf5b596401a8cfcbb (patch)
treefdccdcaed0d0dc3faea515daad410b5f3b064f02 /lib
parentb79eefc8583536da9faa87b50d82eaef8a3e0dde (diff)
downloadrockbox-4dd3c2b33ec3d181f54cc1bcf5b596401a8cfcbb.tar.gz
rockbox-4dd3c2b33ec3d181f54cc1bcf5b596401a8cfcbb.zip
codecs: m4a: improve seek accuracy
Seeking doesn't work well in M4A files with very few chunks due to the seek method used (chunk based using the info in the 'stco' atom). According to libm4a/demux.c the expected seek resolution using this method is 1/4 to 1/2 seconds. However, ffmpeg generates files with a 1 megabyte chunk size, so the resolution is much worse than expected on some files: around 30-40 seconds at 256kbps. There was a bug with the seek position reported back to Rockbox: the codec pretended it could seek exactly to the requested sample, but it would only seek to the start of a chunk. This could leave the UI in a confusing state because the real playback position was different from what the elapsed time showed. Fix this by recalculating the reported sample position using the chunk start. To fix the low seek accuracy, use the table in the 'stsz' atom to skip individual packets within a chunk. This is very accurate, but it takes a lot of RAM to allocate the table. Currently the table is not allowed to use more than half of the codec RAM, which should suffice for short files on most targets. On files where the table is too large the codec will fall back to the less accurate chunk-based seek method. Change-Id: Ide38ea846c1cdd69691e9b1e1cd87eb0fa11cf78
Diffstat (limited to 'lib')
-rw-r--r--lib/rbcodec/codecs/libm4a/demux.c31
-rw-r--r--lib/rbcodec/codecs/libm4a/m4a.c148
-rw-r--r--lib/rbcodec/codecs/libm4a/m4a.h1
3 files changed, 127 insertions, 53 deletions
diff --git a/lib/rbcodec/codecs/libm4a/demux.c b/lib/rbcodec/codecs/libm4a/demux.c
index 3bf46efec6..e29ecb8339 100644
--- a/lib/rbcodec/codecs/libm4a/demux.c
+++ b/lib/rbcodec/codecs/libm4a/demux.c
@@ -349,6 +349,7 @@ static bool read_chunk_stts(qtmovie_t *qtmovie, size_t chunk_len)
349static bool read_chunk_stsz(qtmovie_t *qtmovie, size_t chunk_len) 349static bool read_chunk_stsz(qtmovie_t *qtmovie, size_t chunk_len)
350{ 350{
351 size_t size_remaining = chunk_len - 8; 351 size_t size_remaining = chunk_len - 8;
352 uint32_t numsizes, i;
352 353
353 /* version */ 354 /* version */
354 stream_read_uint8(qtmovie->stream); 355 stream_read_uint8(qtmovie->stream);
@@ -369,9 +370,37 @@ static bool read_chunk_stsz(qtmovie_t *qtmovie, size_t chunk_len)
369 } 370 }
370 size_remaining -= 4; 371 size_remaining -= 4;
371 372
372 qtmovie->res->num_sample_byte_sizes = stream_read_uint32(qtmovie->stream); 373 numsizes = stream_read_uint32(qtmovie->stream);
373 size_remaining -= 4; 374 size_remaining -= 4;
374 375
376 /* Because this table can be really large and is only used to improve seek
377 * accuracy, it's optional. In that case the seek code will fall back to a
378 * less accurate seek method. */
379 qtmovie->res->num_sample_byte_sizes = numsizes;
380 if (numsizes * sizeof(uint32_t) < CODEC_SIZE * 1 / 2)
381 qtmovie->res->sample_byte_sizes = malloc(numsizes * sizeof(uint32_t));
382 else
383 qtmovie->res->sample_byte_sizes = NULL;
384
385 if (qtmovie->res->sample_byte_sizes)
386 {
387 for (i = 0; i < numsizes; ++i)
388 {
389 qtmovie->res->sample_byte_sizes[i] =
390 stream_read_uint32(qtmovie->stream);
391 size_remaining -= 4;
392 }
393
394 if (size_remaining)
395 {
396 DEBUGF("extra bytes after stsz\n");
397 }
398 }
399 else
400 {
401 DEBUGF("stsz too large, ignoring it\n");
402 }
403
375 if (size_remaining) 404 if (size_remaining)
376 { 405 {
377 stream_skip(qtmovie->stream, size_remaining); 406 stream_skip(qtmovie->stream, size_remaining);
diff --git a/lib/rbcodec/codecs/libm4a/m4a.c b/lib/rbcodec/codecs/libm4a/m4a.c
index 5fe778ac03..b967e15e7a 100644
--- a/lib/rbcodec/codecs/libm4a/m4a.c
+++ b/lib/rbcodec/codecs/libm4a/m4a.c
@@ -23,6 +23,13 @@
23#include <inttypes.h> 23#include <inttypes.h>
24#include "m4a.h" 24#include "m4a.h"
25 25
26#undef DEBUGF
27#if defined(DEBUG)
28#define DEBUGF stream->ci->debugf
29#else
30#define DEBUGF(...)
31#endif
32
26/* Implementation of the stream.h functions used by libalac */ 33/* Implementation of the stream.h functions used by libalac */
27 34
28#define _Swap32(v) do { \ 35#define _Swap32(v) do { \
@@ -127,76 +134,113 @@ int m4a_check_sample_offset(demux_res_t *demux_res, uint32_t frame, uint32_t *st
127 return demux_res->lookup_table[i].offset; 134 return demux_res->lookup_table[i].offset;
128} 135}
129 136
130/* Find the exact or preceding frame in lookup_table[]. Return both frame
131 * and byte position of this match. */
132static void gather_offset(demux_res_t *demux_res, uint32_t *frame, uint32_t *offset)
133{
134 uint32_t i = 0;
135 for (i=0; i<demux_res->num_lookup_table; ++i)
136 {
137 if (demux_res->lookup_table[i].offset == 0)
138 break;
139 if (demux_res->lookup_table[i].sample > *frame)
140 break;
141 }
142 i = (i>0) ? i-1 : 0; /* We want the last chunk _before_ *frame. */
143 *frame = demux_res->lookup_table[i].sample;
144 *offset = demux_res->lookup_table[i].offset;
145}
146
147/* Seek to desired sound sample location. Return 1 on success (and modify 137/* Seek to desired sound sample location. Return 1 on success (and modify
148 * sound_samples_done and current_sample), 0 if failed. 138 * sound_samples_done and current_sample), 0 if failed. */
149 *
150 * Find the sample (=frame) that contains the given sound sample, find a best
151 * fit for this sample in the lookup_table[], seek to the byte position. */
152unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream, 139unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream,
153 uint32_t sound_sample_loc, uint32_t* sound_samples_done, 140 uint32_t sound_sample_loc, uint32_t* sound_samples_done,
154 int* current_sample) 141 int* current_sample)
155{ 142{
156 uint32_t i = 0; 143 uint32_t i, sample_i, sound_sample_i;
157 uint32_t tmp_var, tmp_cnt, tmp_dur; 144 uint32_t time, time_cnt, time_dur;
158 uint32_t new_sample = 0; /* Holds the amount of chunks/frames. */ 145 uint32_t chunk, chunk_first_sample;
159 uint32_t new_sound_sample = 0; /* Sums up total amount of samples. */ 146 uint32_t offset;
160 uint32_t new_pos; /* Holds the desired chunk/frame index. */ 147 time_to_sample_t *tts_tab = demux_res->time_to_sample;
161 148 sample_offset_t *tco_tab = demux_res->lookup_table;
162 /* First check we have the appropriate metadata - we should always 149 uint32_t *tsz_tab = demux_res->sample_byte_sizes;
163 * have it. 150
164 */ 151 /* First check we have the required metadata - we should always have it. */
165 if (!demux_res->num_time_to_samples || !demux_res->num_sample_byte_sizes) 152 if (!demux_res->num_time_to_samples || !demux_res->num_sample_byte_sizes)
166 { 153 {
167 return 0; 154 return 0;
168 } 155 }
169 156
170 /* Find the destination block from time_to_sample array */ 157 /* The 'sound_sample_loc' we have is PCM-based and not directly usable.
171 time_to_sample_t *tab = demux_res->time_to_sample; 158 * We need to convert it to an MP4 sample number 'sample_i' first. */
172 while (i < demux_res->num_time_to_samples) 159 sample_i = sound_sample_i = 0;
160 for (time = 0; time < demux_res->num_time_to_samples; ++time)
173 { 161 {
174 tmp_cnt = tab[i].sample_count; 162 time_cnt = tts_tab[time].sample_count;
175 tmp_dur = tab[i].sample_duration; 163 time_dur = tts_tab[time].sample_duration;
176 tmp_var = tmp_cnt * tmp_dur; 164 uint32_t time_var = time_cnt * time_dur;
177 if (sound_sample_loc <= new_sound_sample + tmp_var) 165
166 if (sound_sample_loc < sound_sample_i + time_var)
178 { 167 {
179 tmp_var = (sound_sample_loc - new_sound_sample); 168 time_var = sound_sample_loc - sound_sample_i;
180 new_sample += tmp_var / tmp_dur; 169 sample_i += time_var / time_dur;
181 new_sound_sample += tmp_var;
182 break; 170 break;
183 } 171 }
184 new_sample += tmp_cnt; 172
185 new_sound_sample += tmp_var; 173 sample_i += time_cnt;
186 ++i; 174 sound_sample_i += time_var;
175 }
176
177 /* Find the chunk after 'sample_i'. */
178 for (chunk = 1; chunk < demux_res->num_lookup_table; ++chunk)
179 {
180 if (tco_tab[chunk].offset == 0)
181 break;
182 if (tco_tab[chunk].sample > sample_i)
183 break;
187 } 184 }
188 185
189 /* We know the new sample (=frame), now calculate the file position. */ 186 /* The preceding chunk is the one that contains 'sample_i'. */
190 gather_offset(demux_res, &new_sample, &new_pos); 187 chunk--;
188 chunk_first_sample = tco_tab[chunk].sample;
189 offset = tco_tab[chunk].offset;
191 190
192 /* We know the new file position, so let's try to seek to it */ 191 /* Compute the PCM sample number of the chunk's first sample
193 if (stream->ci->seek_buffer(new_pos)) 192 * to get an accurate base for sound_sample_i. */
193 i = sound_sample_i = 0;
194 for (time = 0; time < demux_res->num_time_to_samples; ++time)
194 { 195 {
195 *sound_samples_done = new_sound_sample; 196 time_cnt = tts_tab[time].sample_count;
196 *current_sample = new_sample; 197 time_dur = tts_tab[time].sample_duration;
198
199 if (chunk_first_sample < i + time_cnt)
200 {
201 sound_sample_i += (chunk_first_sample - i) * time_dur;
202 break;
203 }
204
205 i += time_cnt;
206 sound_sample_i += time_cnt * time_dur;
207 }
208
209 DEBUGF("seek chunk=%lu, sample=%lu, soundsample=%lu, offset=%lu\n",
210 (unsigned long)chunk, (unsigned long)chunk_first_sample,
211 (unsigned long)sound_sample_i, (unsigned long)offset);
212
213 if (tsz_tab) {
214 /* We have a sample-to-bytes table available so we can do accurate
215 * seeking. Move one sample at a time and update the file offset and
216 * PCM sample offset as we go. */
217 for (i = chunk_first_sample;
218 i < sample_i && i < demux_res->num_sample_byte_sizes; ++i)
219 {
220 /* this could be unnecessary */
221 if (time_cnt == 0 && ++time < demux_res->num_time_to_samples)
222 {
223 time_cnt = tts_tab[time].sample_count;
224 time_dur = tts_tab[time].sample_duration;
225 }
226
227 offset += tsz_tab[i];
228 sound_sample_i += time_dur;
229 time_cnt--;
230 }
231 } else {
232 /* No sample-to-bytes table available so we can only seek to the
233 * start of a chunk, which is often much lower resolution. */
234 sample_i = chunk_first_sample;
235 }
236
237 if (stream->ci->seek_buffer(offset))
238 {
239 *sound_samples_done = sound_sample_i;
240 *current_sample = sample_i;
197 return 1; 241 return 1;
198 } 242 }
199 243
200 return 0; 244 return 0;
201} 245}
202 246
diff --git a/lib/rbcodec/codecs/libm4a/m4a.h b/lib/rbcodec/codecs/libm4a/m4a.h
index aa8e768045..81b10c3a27 100644
--- a/lib/rbcodec/codecs/libm4a/m4a.h
+++ b/lib/rbcodec/codecs/libm4a/m4a.h
@@ -80,6 +80,7 @@ typedef struct
80 time_to_sample_t *time_to_sample; 80 time_to_sample_t *time_to_sample;
81 uint32_t num_time_to_samples; 81 uint32_t num_time_to_samples;
82 82
83 uint32_t *sample_byte_sizes;
83 uint32_t num_sample_byte_sizes; 84 uint32_t num_sample_byte_sizes;
84 85
85 uint32_t codecdata_len; 86 uint32_t codecdata_len;