Codecs: mp4: Improve support for long files

Load stts (time to sample) table on demand if it doesn't fit in RAM.

Fixes FS#13889 (in most cases I've seen files with single element in this table and problematic one has 111200 elements)

Change-Id: I719f92a4512a45472739587e81861b9bc545f349
This commit is contained in:
Roman Artiukhin 2026-05-19 10:49:18 +03:00 committed by Solomon Peachy
parent aae46a822a
commit 69b6c97e58
4 changed files with 76 additions and 18 deletions

View file

@ -249,7 +249,8 @@ enum codec_status codec_run(void)
if (i == demux_res.num_sample_byte_sizes - 1)
{
// Size of the last frame
const uint32_t sample_duration = (demux_res.num_time_to_samples > 0) ?
// Currently ignored if time_to_sample table fails to fit.
const uint32_t sample_duration = (demux_res.num_time_to_samples > 0 && demux_res.time_to_sample) ?
demux_res.time_to_sample[demux_res.num_time_to_samples - 1].sample_duration :
frame_samples;

View file

@ -317,13 +317,29 @@ static bool read_chunk_stts(qtmovie_t *qtmovie, size_t chunk_len)
qtmovie->res->num_time_to_samples = numentries;
qtmovie->res->time_to_sample = malloc(numentries * sizeof(*qtmovie->res->time_to_sample));
if (qtmovie->res->time_to_sample && numentries > 1)
{
//Make sure we leave space for at least 100 records in lookup table
void* enough_mem = malloc(sizeof(*qtmovie->res->lookup_table)*100);
if (!enough_mem)
{
free(qtmovie->res->time_to_sample);
qtmovie->res->time_to_sample = NULL;
}
else
{
free(enough_mem);
}
}
if (!qtmovie->res->time_to_sample)
{
DEBUGF("stts too large\n");
return false;
qtmovie->res->time_to_sample_offset = stream_tell(qtmovie->stream);
stream_skip(qtmovie->stream, size_remaining);
DEBUGF("stts too large %ld, save time_to_sample_offset\n", size_remaining);
return true;
}
DEBUGF("stts numentires %d, size: %ld\n", numentries, numentries * sizeof(*qtmovie->res->time_to_sample));
for (i = 0; i < numentries; i++)
{
qtmovie->res->time_to_sample[i].sample_count = stream_read_uint32(qtmovie->stream);
@ -465,7 +481,7 @@ static bool read_chunk_stco(qtmovie_t *qtmovie, size_t chunk_len)
fit_numentries = numentries / accuracy_divider;
}
}
DEBUGF("lookup_table numentries %d, fit_numentries %d\n", numentries, fit_numentries);
DEBUGF("lookup_table numentries %d, fit_numentries %ld\n", numentries, fit_numentries);
qtmovie->res->num_lookup_table = fit_numentries;
if (!qtmovie->res->lookup_table)

View file

@ -137,6 +137,12 @@ int m4a_check_sample_offset(demux_res_t *demux_res, uint32_t frame, uint32_t *st
return -1;
}
static inline void read_time_to_sample_entry(stream_t* stream, uint32_t* time_cnt, uint32_t* time_dur)
{
*time_cnt = stream_read_uint32(stream);
*time_dur = stream_read_uint32(stream);
}
/* Seek to desired sound sample location. Return 1 on success (and modify
* sound_samples_done and current_sample), 0 if failed. */
unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream,
@ -161,10 +167,21 @@ unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream,
/* The 'sound_sample_loc' we have is PCM-based and not directly usable.
* We need to convert it to an MP4 sample number 'sample_i' first. */
sample_i = sound_sample_i = 0;
if (tts_tab == NULL)
{
stream_seek(stream, demux_res->time_to_sample_offset);
}
for (time = 0; time < demux_res->num_time_to_samples; ++time)
{
time_cnt = tts_tab[time].sample_count;
time_dur = tts_tab[time].sample_duration;
if (tts_tab)
{
time_dur = tts_tab[time].sample_duration;
time_cnt = tts_tab[time].sample_count;
}
else
{
read_time_to_sample_entry(stream, &time_cnt, &time_dur);
}
uint32_t time_var = time_cnt * time_dur;
if (sound_sample_loc < sound_sample_i + time_var)
@ -194,10 +211,22 @@ unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream,
/* Compute the PCM sample number of the chunk's first sample
* to get an accurate base for sound_sample_i. */
i = sound_sample_i = 0;
if (tts_tab == NULL)
{
stream_seek(stream, demux_res->time_to_sample_offset);
}
for (time = 0; time < demux_res->num_time_to_samples; ++time)
{
time_cnt = tts_tab[time].sample_count;
time_dur = tts_tab[time].sample_duration;
if (tts_tab)
{
time_dur = tts_tab[time].sample_duration;
time_cnt = tts_tab[time].sample_count;
}
else
{
read_time_to_sample_entry(stream, &time_cnt, &time_dur);
}
if (chunk_first_sample < i + time_cnt)
{
@ -223,7 +252,7 @@ unsigned int m4a_seek(demux_res_t* demux_res, stream_t* stream,
i < sample_i && i < demux_res->num_sample_byte_sizes; ++i)
{
/* this could be unnecessary */
if (time_cnt == 0 && ++time < demux_res->num_time_to_samples)
if (time_cnt == 0 && tts_tab && ++time < demux_res->num_time_to_samples)
{
time_cnt = tts_tab[time].sample_count;
time_dur = tts_tab[time].sample_duration;
@ -279,8 +308,8 @@ unsigned int m4a_seek_raw(demux_res_t* demux_res, stream_t* stream,
uint32_t chunk_sample = 0;
uint32_t total_samples = 0;
uint64_t new_sound_sample = 0;
uint32_t tmp_dur;
uint32_t tmp_cnt;
uint32_t time_dur;
uint32_t time_cnt;
uint32_t new_pos;
/* We know the desired byte offset, search for the chunk right before.
@ -297,16 +326,27 @@ unsigned int m4a_seek_raw(demux_res_t* demux_res, stream_t* stream,
/* Get sound sample offset. */
i = 0;
time_to_sample_t *tab2 = demux_res->time_to_sample;
time_to_sample_t *tts_tab = demux_res->time_to_sample;
if (tts_tab == NULL)
{
stream_seek(stream, demux_res->time_to_sample_offset);
}
while (i < demux_res->num_time_to_samples)
{
tmp_dur = tab2[i].sample_duration;
tmp_cnt = tab2[i].sample_count;
total_samples += tmp_cnt;
new_sound_sample += tmp_cnt * tmp_dur;
if (tts_tab)
{
time_dur = tts_tab[i].sample_duration;
time_cnt = tts_tab[i].sample_count;
}
else
{
read_time_to_sample_entry(stream, &time_cnt, &time_dur);
}
total_samples += time_cnt;
new_sound_sample += time_cnt * time_dur;
if (chunk_sample <= total_samples)
{
new_sound_sample -= (total_samples - chunk_sample) * tmp_dur;
new_sound_sample -= (total_samples - chunk_sample) * time_dur;
break;
}
++i;

View file

@ -79,6 +79,7 @@ typedef struct
time_to_sample_t *time_to_sample;
uint32_t num_time_to_samples;
uint32_t time_to_sample_offset;
uint32_t *sample_byte_sizes;
uint32_t num_sample_byte_sizes;