LCOV - code coverage report
Current view: top level - src/utils - frame_queue.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 361 469 77.0 %
Date: 2025-03-29 09:04:10 Functions: 22 27 81.5 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 296 514 57.6 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * This file is part of libplacebo.
       3                 :            :  *
       4                 :            :  * libplacebo is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * libplacebo is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12                 :            :  * GNU Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : #include <errno.h>
      19                 :            : #include <math.h>
      20                 :            : 
      21                 :            : #include "common.h"
      22                 :            : #include "log.h"
      23                 :            : #include "pl_thread.h"
      24                 :            : 
      25                 :            : #include <libplacebo/utils/frame_queue.h>
      26                 :            : 
      27                 :            : struct cache_entry {
      28                 :            :     pl_tex tex[4];
      29                 :            : };
      30                 :            : 
      31                 :            : struct entry {
      32                 :            :     pl_rc_t rc;
      33                 :            :     double pts;
      34                 :            :     struct cache_entry cache;
      35                 :            :     struct pl_source_frame src;
      36                 :            :     struct pl_frame frame;
      37                 :            :     uint64_t signature;
      38                 :            :     bool mapped;
      39                 :            :     bool ok;
      40                 :            : 
      41                 :            :     // for interlaced frames
      42                 :            :     enum pl_field field;
      43                 :            :     struct entry *primary;
      44                 :            :     struct entry *prev, *next;
      45                 :            :     bool dirty;
      46                 :            : };
      47                 :            : 
      48                 :            : // Hard limits for vsync timing validity
      49                 :            : #define MIN_FPS 10
      50                 :            : #define MAX_FPS 400
      51                 :            : 
      52                 :            : // Limits for FPS estimation state
      53                 :            : #define MAX_SAMPLES 32
      54                 :            : #define MIN_SAMPLES 4
      55                 :            : 
      56                 :            : // Stickiness to prevent `interpolation_threshold` oscillation
      57                 :            : #define THRESHOLD_MAX_RATIO 0.3
      58                 :            : #define THRESHOLD_FRAMES 5
      59                 :            : 
      60                 :            : // Maximum number of not-yet-mapped frames to allow queueing in advance
      61                 :            : #define PREFETCH_FRAMES 2
      62                 :            : 
      63                 :            : struct pool {
      64                 :            :     float samples[MAX_SAMPLES];
      65                 :            :     float estimate;
      66                 :            :     float sum;
      67                 :            :     int idx;
      68                 :            :     int num;
      69                 :            :     int total;
      70                 :            : };
      71                 :            : 
      72                 :            : struct pl_queue_t {
      73                 :            :     pl_gpu gpu;
      74                 :            :     pl_log log;
      75                 :            : 
      76                 :            :     // For multi-threading, we use two locks. The `lock_weak` guards the queue
      77                 :            :     // state itself. The `lock_strong` has a bigger scope and should be held
      78                 :            :     // for the duration of any functions that expect the queue state to
      79                 :            :     // remain more or less valid (with the exception of adding new members).
      80                 :            :     //
      81                 :            :     // In particular, `pl_queue_reset` and `pl_queue_update` will take
      82                 :            :     // the strong lock, while `pl_queue_push_*` will only take the weak
      83                 :            :     // lock.
      84                 :            :     pl_mutex lock_strong;
      85                 :            :     pl_mutex lock_weak;
      86                 :            :     pl_cond wakeup;
      87                 :            : 
      88                 :            :     // Frame queue and state
      89                 :            :     PL_ARRAY(struct entry *) queue;
      90                 :            :     uint64_t signature;
      91                 :            :     int threshold_frames;
      92                 :            :     bool want_frame;
      93                 :            :     bool eof;
      94                 :            : 
      95                 :            :     // Average vsync/frame fps estimation state
      96                 :            :     struct pool vps, fps;
      97                 :            :     float reported_vps;
      98                 :            :     float reported_fps;
      99                 :            :     double prev_pts;
     100                 :            :     double pts_offset;
     101                 :            : 
     102                 :            :     // Storage for temporary arrays
     103                 :            :     PL_ARRAY(uint64_t) tmp_sig;
     104                 :            :     PL_ARRAY(float) tmp_ts;
     105                 :            :     PL_ARRAY(const struct pl_frame *) tmp_frame;
     106                 :            : 
     107                 :            :     // Queue of GPU objects to reuse
     108                 :            :     PL_ARRAY(struct cache_entry) cache;
     109                 :            : };
     110                 :            : 
     111                 :          4 : pl_queue pl_queue_create(pl_gpu gpu)
     112                 :            : {
     113                 :          4 :     pl_queue p = pl_alloc_ptr(NULL, p);
     114                 :          4 :     *p = (struct pl_queue_t) {
     115                 :            :         .gpu = gpu,
     116                 :          4 :         .log = gpu->log,
     117                 :            :     };
     118                 :            : 
     119         [ -  + ]:          4 :     pl_mutex_init(&p->lock_strong);
     120         [ -  + ]:          4 :     pl_mutex_init(&p->lock_weak);
     121                 :          4 :     int ret = pl_cond_init(&p->wakeup);
     122         [ -  + ]:          4 :     if (ret) {
     123                 :          0 :         PL_ERR(p, "Failed to init conditional variable: %d", ret);
     124                 :          0 :         return NULL;
     125                 :            :     }
     126                 :            :     return p;
     127                 :            : }
     128                 :            : 
     129                 :        320 : static void recycle_cache(pl_queue p, struct cache_entry *cache, bool recycle)
     130                 :            : {
     131                 :            :     bool has_textures = false;
     132         [ +  + ]:       1600 :     for (int i = 0; i < PL_ARRAY_SIZE(cache->tex); i++) {
     133         [ +  - ]:       1280 :         if (!cache->tex[i])
     134                 :       1280 :             continue;
     135                 :            : 
     136                 :            :         has_textures = true;
     137         [ #  # ]:          0 :         if (recycle) {
     138                 :          0 :             pl_tex_invalidate(p->gpu, cache->tex[i]);
     139                 :            :         } else {
     140                 :          0 :             pl_tex_destroy(p->gpu, &cache->tex[i]);
     141                 :            :         }
     142                 :            :     }
     143                 :            : 
     144         [ -  + ]:        320 :     if (recycle && has_textures)
     145   [ #  #  #  #  :          0 :         PL_ARRAY_APPEND(p, p->cache, *cache);
                   #  # ]
     146                 :            : 
     147                 :            :     memset(cache, 0, sizeof(*cache)); // sanity
     148                 :        320 : }
     149                 :            : 
     150                 :       1464 : static void entry_deref(pl_queue p, struct entry **pentry, bool recycle)
     151                 :            : {
     152                 :       1464 :     struct entry *entry = *pentry;
     153                 :       1464 :     *pentry = NULL;
     154   [ +  +  +  + ]:       1464 :     if (!entry || !pl_rc_deref(&entry->rc))
     155                 :       1144 :         return;
     156                 :            : 
     157   [ +  +  -  + ]:        320 :     if (!entry->mapped && entry->src.discard) {
     158                 :          0 :         PL_TRACE(p, "Discarding unused frame id %"PRIu64" with PTS %f",
     159                 :            :                  entry->signature, entry->src.pts);
     160                 :          0 :         entry->src.discard(&entry->src);
     161                 :            :     }
     162                 :            : 
     163   [ +  +  +  -  :        320 :     if (entry->mapped && entry->ok && entry->src.unmap) {
                   -  + ]
     164                 :          0 :         PL_TRACE(p, "Unmapping frame id %"PRIu64" with PTS %f",
     165                 :            :                  entry->signature, entry->src.pts);
     166                 :          0 :         entry->src.unmap(p->gpu, &entry->frame, &entry->src);
     167                 :            :     }
     168                 :            : 
     169                 :        320 :     recycle_cache(p, &entry->cache, recycle);
     170                 :        320 :     pl_free(entry);
     171                 :            : }
     172                 :            : 
     173                 :            : static struct entry *entry_ref(struct entry *entry)
     174                 :            : {
     175                 :        480 :     pl_rc_ref(&entry->rc);
     176                 :            :     return entry;
     177                 :            : }
     178                 :            : 
     179                 :        320 : static void entry_cull(pl_queue p, struct entry *entry, bool recycle)
     180                 :            : {
     181                 :            :     // Forcibly clean up references to prev/next frames, even if `entry` has
     182                 :            :     // remaining refs pointing at it. This is to prevent cyclic references.
     183                 :        320 :     entry_deref(p, &entry->primary, recycle);
     184                 :        320 :     entry_deref(p, &entry->prev, recycle);
     185                 :        320 :     entry_deref(p, &entry->next, recycle);
     186                 :        320 :     entry_deref(p, &entry, recycle);
     187                 :        320 : }
     188                 :            : 
     189                 :          4 : void pl_queue_destroy(pl_queue *queue)
     190                 :            : {
     191                 :          4 :     pl_queue p = *queue;
     192         [ +  - ]:          4 :     if (!p)
     193                 :            :         return;
     194                 :            : 
     195         [ -  + ]:          4 :     for (int n = 0; n < p->queue.num; n++)
     196                 :          0 :         entry_cull(p, p->queue.elem[n], false);
     197         [ -  + ]:          4 :     for (int n = 0; n < p->cache.num; n++) {
     198         [ #  # ]:          0 :         for (int i = 0; i < PL_ARRAY_SIZE(p->cache.elem[n].tex); i++)
     199                 :          0 :             pl_tex_destroy(p->gpu, &p->cache.elem[n].tex[i]);
     200                 :            :     }
     201                 :            : 
     202                 :          4 :     pl_cond_destroy(&p->wakeup);
     203                 :          4 :     pl_mutex_destroy(&p->lock_weak);
     204                 :          4 :     pl_mutex_destroy(&p->lock_strong);
     205                 :          4 :     pl_free(p);
     206                 :          4 :     *queue = NULL;
     207                 :            : }
     208                 :            : 
     209                 :         12 : void pl_queue_reset(pl_queue p)
     210                 :            : {
     211                 :         12 :     pl_mutex_lock(&p->lock_strong);
     212                 :         12 :     pl_mutex_lock(&p->lock_weak);
     213                 :            : 
     214         [ +  + ]:         20 :     for (int i = 0; i < p->queue.num; i++)
     215                 :          8 :         entry_cull(p, p->queue.elem[i], false);
     216                 :            : 
     217                 :         12 :     *p = (struct pl_queue_t) {
     218                 :         12 :         .gpu = p->gpu,
     219                 :         12 :         .log = p->log,
     220                 :            : 
     221                 :            :         // Reuse lock objects
     222                 :         12 :         .lock_strong = p->lock_strong,
     223                 :         12 :         .lock_weak = p->lock_weak,
     224                 :         12 :         .wakeup = p->wakeup,
     225                 :            : 
     226                 :            :         // Explicitly preserve allocations
     227                 :         12 :         .queue.elem = p->queue.elem,
     228                 :         12 :         .tmp_sig.elem = p->tmp_sig.elem,
     229                 :         12 :         .tmp_ts.elem = p->tmp_ts.elem,
     230                 :         12 :         .tmp_frame.elem = p->tmp_frame.elem,
     231                 :            : 
     232                 :            :         // Reuse GPU object cache entirely
     233                 :         12 :         .cache = p->cache,
     234                 :            :     };
     235                 :            : 
     236                 :         12 :     pl_cond_signal(&p->wakeup);
     237                 :         12 :     pl_mutex_unlock(&p->lock_weak);
     238                 :         12 :     pl_mutex_unlock(&p->lock_strong);
     239                 :         12 : }
     240                 :            : 
     241                 :            : static inline float delta(float old, float new)
     242                 :            : {
     243                 :       3056 :     return fabsf((new - old) / PL_MIN(new, old));
     244                 :            : }
     245                 :            : 
     246                 :            : static inline void default_estimate(struct pool *pool, float val)
     247                 :            : {
     248   [ +  -  +  -  :        256 :     if (!pool->estimate && isnormal(val) && val > 0.0)
          +  +  +  -  +  
                      - ]
     249                 :         28 :         pool->estimate = val;
     250                 :            : }
     251                 :            : 
     252                 :        764 : static inline void update_estimate(struct pool *pool, float cur)
     253                 :            : {
     254         [ +  + ]:        764 :     if (pool->num) {
     255                 :            :         static const float max_delta = 0.3;
     256   [ +  +  +  + ]:       1024 :         if (delta(pool->sum / pool->num, cur) > max_delta) {
     257                 :          4 :             pool->sum = 0.0;
     258                 :          4 :             pool->num = pool->idx = 0;
     259                 :            :         }
     260                 :            :     }
     261                 :            : 
     262         [ +  + ]:        764 :     if (pool->num++ == MAX_SAMPLES) {
     263                 :        216 :         pool->sum -= pool->samples[pool->idx];
     264                 :        216 :         pool->num--;
     265                 :            :     }
     266                 :            : 
     267                 :        764 :     pool->sum += pool->samples[pool->idx] = cur;
     268                 :        764 :     pool->idx = (pool->idx + 1) % MAX_SAMPLES;
     269                 :        764 :     pool->total++;
     270                 :            : 
     271   [ +  +  +  + ]:        764 :     if (pool->total < MIN_SAMPLES || pool->num >= MIN_SAMPLES)
     272                 :        760 :         pool->estimate = pool->sum / pool->num;
     273                 :        764 : }
     274                 :            : 
     275                 :        256 : static void queue_push(pl_queue p, const struct pl_source_frame *src)
     276                 :            : {
     277   [ -  +  -  - ]:        256 :     if (p->eof && !src)
     278                 :            :         return; // ignore duplicate EOF
     279                 :            : 
     280   [ -  +  -  - ]:        256 :     if (p->eof && src) {
     281                 :          0 :         PL_INFO(p, "Received frame after EOF signaled... discarding frame!");
     282         [ #  # ]:          0 :         if (src->discard)
     283                 :          0 :             src->discard(src);
     284                 :          0 :         return;
     285                 :            :     }
     286                 :            : 
     287                 :        256 :     pl_cond_signal(&p->wakeup);
     288                 :            : 
     289         [ +  + ]:        256 :     if (!src) {
     290                 :         16 :         PL_TRACE(p, "Received EOF, draining frame queue...");
     291                 :         16 :         p->eof = true;
     292                 :         16 :         p->want_frame = false;
     293                 :         16 :         return;
     294                 :            :     }
     295                 :            : 
     296                 :            :     // Update FPS estimates if possible/reasonable
     297         [ +  + ]:        240 :     default_estimate(&p->fps, src->first_field ? src->duration / 2 : src->duration);
     298         [ +  + ]:        240 :     if (p->queue.num) {
     299                 :        228 :         double last_pts = p->queue.elem[p->queue.num - 1]->pts;
     300                 :        228 :         float delta = src->pts - last_pts;
     301         [ +  + ]:        228 :         if (delta <= 0.0f) {
     302                 :         64 :             PL_DEBUG(p, "Non monotonically increasing PTS %f -> %f", last_pts, src->pts);
     303   [ +  -  +  + ]:        164 :         } else if (p->fps.estimate && delta > 10.0 * p->fps.estimate) {
     304                 :          4 :             PL_DEBUG(p, "Discontinuous source PTS jump %f -> %f", last_pts, src->pts);
     305                 :            :         } else {
     306                 :        160 :             update_estimate(&p->fps, delta);
     307                 :            :         }
     308         [ -  + ]:         12 :     } else if (src->pts != 0) {
     309                 :          0 :         PL_DEBUG(p, "First frame received with non-zero PTS %f", src->pts);
     310                 :            :     }
     311                 :            : 
     312                 :        240 :     struct entry *entry = pl_alloc_ptr(NULL, entry);
     313                 :        240 :     *entry = (struct entry) {
     314                 :        240 :         .signature = p->signature++,
     315                 :        240 :         .pts = src->pts,
     316                 :        240 :         .src = *src,
     317                 :            :     };
     318                 :        240 :     pl_rc_init(&entry->rc);
     319         [ -  + ]:        240 :     PL_ARRAY_POP(p->cache, &entry->cache);
     320                 :        240 :     PL_TRACE(p, "Added new frame id %"PRIu64" with PTS %f",
     321                 :            :              entry->signature, entry->pts);
     322                 :            : 
     323                 :            :     // Insert new entry into the correct spot in the queue, sorted by PTS
     324                 :        672 :     for (int i = p->queue.num;; i--) {
     325   [ +  +  +  + ]:        672 :         if (i == 0 || p->queue.elem[i - 1]->pts <= entry->pts) {
     326         [ +  + ]:        240 :             if (src->first_field == PL_FIELD_NONE) {
     327                 :            :                 // Progressive
     328   [ -  +  -  -  :        160 :                 PL_ARRAY_INSERT_AT(p, p->queue, i, entry);
          -  +  +  +  +  
                +  -  + ]
     329                 :        160 :                 break;
     330                 :            :             } else {
     331                 :            :                 // Interlaced
     332         [ +  + ]:         80 :                 struct entry *prev = i > 0 ? p->queue.elem[i - 1] : NULL;
     333         [ +  + ]:         80 :                 struct entry *next = i < p->queue.num ? p->queue.elem[i] : NULL;
     334                 :         80 :                 struct entry *entry2 = pl_zalloc_ptr(NULL, entry2);
     335                 :         80 :                 pl_rc_init(&entry2->rc);
     336         [ +  + ]:         80 :                 if (next) {
     337                 :         32 :                     entry2->pts = (entry->pts + next->pts) / 2;
     338         [ +  - ]:         48 :                 } else if (src->duration) {
     339                 :         48 :                     entry2->pts = entry->pts + src->duration / 2;
     340         [ #  # ]:          0 :                 } else if (p->fps.estimate) {
     341                 :          0 :                     entry2->pts = entry->pts + p->fps.estimate;
     342                 :            :                 } else {
     343                 :          0 :                     PL_ERR(p, "Frame with PTS %f specified as interlaced, but "
     344                 :            :                            "no FPS information known yet! Please specify a "
     345                 :            :                            "valid `pl_source_frame.duration`. Treating as "
     346                 :            :                            "progressive...", src->pts);
     347   [ #  #  #  #  :          0 :                     PL_ARRAY_INSERT_AT(p, p->queue, i, entry);
          #  #  #  #  #  
                #  #  # ]
     348                 :          0 :                     pl_free(entry2);
     349                 :          0 :                     break;
     350                 :            :                 }
     351                 :            : 
     352                 :         80 :                 entry->field = src->first_field;
     353                 :         80 :                 entry2->primary = entry_ref(entry);
     354      [ -  -  + ]:         80 :                 entry2->field = pl_field_other(entry->field);
     355                 :         80 :                 entry2->signature = p->signature++;
     356                 :            : 
     357                 :         80 :                 PL_TRACE(p, "Added second field id %"PRIu64" with PTS %f",
     358                 :            :                          entry2->signature, entry2->pts);
     359                 :            : 
     360                 :            :                 // Link previous/next frames
     361         [ +  + ]:         80 :                 if (prev) {
     362         [ +  - ]:         76 :                     entry->prev = entry_ref(PL_DEF(prev->primary, prev));
     363         [ +  - ]:         76 :                     entry2->prev = entry_ref(PL_DEF(prev->primary, prev));
     364                 :            :                     // Retroactively re-link the previous frames that should
     365                 :            :                     // be referencing this frame
     366         [ +  + ]:        228 :                     for (int j = i - 1; j >= 0; --j) {
     367                 :        224 :                         struct entry *e = p->queue.elem[j];
     368   [ +  +  +  + ]:        224 :                         if (e != prev && e != prev->primary)
     369                 :            :                             break;
     370                 :        152 :                         entry_deref(p, &e->next, true);
     371                 :        152 :                         e->next = entry_ref(entry);
     372         [ -  + ]:        152 :                         if (e->dirty) { // reset signature to signal change
     373                 :          0 :                             e->signature = p->signature++;
     374                 :          0 :                             e->dirty = false;
     375                 :            :                         }
     376                 :            :                     }
     377                 :            :                 }
     378                 :            : 
     379         [ +  + ]:         80 :                 if (next) {
     380         [ -  + ]:         32 :                     entry->next = entry_ref(PL_DEF(next->primary, next));
     381         [ -  + ]:         32 :                     entry2->next = entry_ref(PL_DEF(next->primary, next));
     382         [ +  - ]:         64 :                     for (int j = i; j < p->queue.num; j++) {
     383                 :         64 :                         struct entry *e = p->queue.elem[j];
     384   [ +  +  -  + ]:         64 :                         if (e != next && e != next->primary)
     385                 :            :                             break;
     386                 :         32 :                         entry_deref(p, &e->prev, true);
     387                 :         32 :                         e->prev = entry_ref(entry);
     388         [ -  + ]:         32 :                         if (e->dirty) {
     389                 :          0 :                             e->signature = p->signature++;
     390                 :          0 :                             e->dirty = false;
     391                 :            :                         }
     392                 :            :                     }
     393                 :            :                 }
     394                 :            : 
     395   [ -  +  -  -  :         80 :                 PL_ARRAY_INSERT_AT(p, p->queue, i, entry);
          -  +  -  +  +  
             +  -  +  -  
                      + ]
     396   [ -  +  -  -  :         80 :                 PL_ARRAY_INSERT_AT(p, p->queue, i+1, entry2);
          -  +  -  +  +  
                +  -  + ]
     397                 :         80 :                 break;
     398                 :            :             }
     399                 :            :         }
     400                 :            :     }
     401                 :            : 
     402                 :        240 :     p->want_frame = false;
     403                 :            : }
     404                 :            : 
     405                 :        248 : void pl_queue_push(pl_queue p, const struct pl_source_frame *frame)
     406                 :            : {
     407                 :        248 :     pl_mutex_lock(&p->lock_weak);
     408                 :        248 :     queue_push(p, frame);
     409                 :        248 :     pl_mutex_unlock(&p->lock_weak);
     410                 :        248 : }
     411                 :            : 
     412                 :            : static inline bool entry_mapped(struct entry *entry)
     413                 :            : {
     414   [ -  +  -  - ]:        148 :     return entry->mapped || (entry->primary && entry->primary->mapped);
     415                 :            : }
     416                 :            : 
     417                 :         80 : static bool queue_has_room(pl_queue p)
     418                 :            : {
     419         [ -  + ]:         80 :     if (p->want_frame)
     420                 :            :         return true;
     421                 :            : 
     422                 :            :     int wanted_frames = PREFETCH_FRAMES;
     423   [ +  +  -  +  :         80 :     if (p->fps.estimate && p->vps.estimate && p->vps.estimate <= 1.0f / MIN_FPS)
                   -  - ]
     424                 :          0 :         wanted_frames += ceilf(p->vps.estimate / p->fps.estimate) - 1;
     425                 :            : 
     426                 :            :     // Examine the queue tail
     427         [ +  + ]:        156 :     for (int i = p->queue.num - 1; i >= 0; i--) {
     428         [ -  + ]:        148 :         if (entry_mapped(p->queue.elem[i]))
     429                 :            :             return true;
     430         [ +  + ]:        148 :         if (p->queue.num - i >= wanted_frames)
     431                 :            :             return false;
     432                 :            :     }
     433                 :            : 
     434                 :            :     return true;
     435                 :            : }
     436                 :            : 
     437                 :         80 : bool pl_queue_push_block(pl_queue p, uint64_t timeout,
     438                 :            :                          const struct pl_source_frame *frame)
     439                 :            : {
     440                 :         80 :     pl_mutex_lock(&p->lock_weak);
     441   [ +  -  -  + ]:         80 :     if (!timeout || !frame || p->eof)
     442                 :          0 :         goto skip_blocking;
     443                 :            : 
     444   [ +  +  +  - ]:         80 :     while (!queue_has_room(p) && !p->eof) {
     445         [ +  - ]:         72 :         if (pl_cond_timedwait(&p->wakeup, &p->lock_weak, timeout) == ETIMEDOUT) {
     446                 :         72 :             pl_mutex_unlock(&p->lock_weak);
     447                 :         72 :             return false;
     448                 :            :         }
     449                 :            :     }
     450                 :            : 
     451                 :          8 : skip_blocking:
     452                 :            : 
     453                 :          8 :     queue_push(p, frame);
     454                 :          8 :     pl_mutex_unlock(&p->lock_weak);
     455                 :          8 :     return true;
     456                 :            : }
     457                 :            : 
     458                 :        604 : static void report_estimates(pl_queue p)
     459                 :            : {
     460   [ +  +  +  + ]:        604 :     if (p->fps.total >= MIN_SAMPLES && p->vps.total >= MIN_SAMPLES) {
     461   [ +  +  +  - ]:        540 :         if (p->reported_fps && p->reported_vps) {
     462                 :            :             // Only re-report the estimates if they've changed considerably
     463                 :            :             // from the previously reported values
     464                 :            :             static const float report_delta = 0.3f;
     465         [ +  - ]:        528 :             float delta_fps = delta(p->reported_fps, p->fps.estimate);
     466         [ +  - ]:        528 :             float delta_vps = delta(p->reported_vps, p->vps.estimate);
     467   [ +  -  -  + ]:        528 :             if (delta_fps < report_delta && delta_vps < report_delta)
     468                 :            :                 return;
     469                 :            :         }
     470                 :            : 
     471                 :         12 :         PL_INFO(p, "Estimated source FPS: %.3f, display FPS: %.3f",
     472                 :            :                 1.0 / p->fps.estimate, 1.0 / p->vps.estimate);
     473                 :            : 
     474                 :         12 :         p->reported_fps = p->fps.estimate;
     475                 :         12 :         p->reported_vps = p->vps.estimate;
     476                 :            :     }
     477                 :            : }
     478                 :            : 
     479                 :            : // note: may add more than one frame, since it releases the lock
     480                 :        148 : static enum pl_queue_status get_frame(pl_queue p, const struct pl_queue_params *params)
     481                 :            : {
     482         [ +  + ]:        148 :     if (p->eof)
     483                 :            :         return PL_QUEUE_EOF;
     484                 :            : 
     485         [ +  + ]:         92 :     if (!params->get_frame) {
     486         [ +  - ]:          4 :         if (!params->timeout)
     487                 :            :             return PL_QUEUE_MORE;
     488                 :            : 
     489                 :          0 :         p->want_frame = true;
     490                 :          0 :         pl_cond_signal(&p->wakeup);
     491                 :            : 
     492         [ #  # ]:          0 :         while (p->want_frame) {
     493         [ #  # ]:          0 :             if (pl_cond_timedwait(&p->wakeup, &p->lock_weak, params->timeout) == ETIMEDOUT)
     494                 :            :                 return PL_QUEUE_MORE;
     495                 :            :         }
     496                 :            : 
     497                 :          0 :         return p->eof ? PL_QUEUE_EOF : PL_QUEUE_OK;
     498                 :            :     }
     499                 :            : 
     500                 :            :     // Don't hold the weak mutex while calling into `get_frame`, to allow
     501                 :            :     // `pl_queue_push` to run concurrently while we're waiting for frames
     502                 :         88 :     pl_mutex_unlock(&p->lock_weak);
     503                 :            : 
     504                 :            :     struct pl_source_frame src;
     505                 :            :     enum pl_queue_status ret;
     506      [ +  +  - ]:         88 :     switch ((ret = params->get_frame(&src, params))) {
     507                 :         80 :     case PL_QUEUE_OK:
     508                 :         80 :         pl_queue_push(p, &src);
     509                 :         80 :         break;
     510                 :          8 :     case PL_QUEUE_EOF:
     511                 :          8 :         pl_queue_push(p, NULL);
     512                 :          8 :         break;
     513                 :            :     case PL_QUEUE_MORE:
     514                 :            :     case PL_QUEUE_ERR:
     515                 :            :         break;
     516                 :            :     }
     517                 :            : 
     518                 :         88 :     pl_mutex_lock(&p->lock_weak);
     519                 :         88 :     return ret;
     520                 :            : }
     521                 :            : 
     522                 :       2512 : static inline bool map_frame(pl_queue p, struct entry *entry)
     523                 :            : {
     524         [ +  + ]:       2512 :     if (!entry->mapped) {
     525                 :        240 :         PL_TRACE(p, "Mapping frame id %"PRIu64" with PTS %f",
     526                 :            :                  entry->signature, entry->pts);
     527                 :        240 :         entry->mapped = true;
     528                 :        480 :         entry->ok = entry->src.map(p->gpu, entry->cache.tex,
     529                 :        240 :                                    &entry->src, &entry->frame);
     530         [ -  + ]:        240 :         if (!entry->ok)
     531                 :          0 :             PL_ERR(p, "Failed mapping frame id %"PRIu64" with PTS %f",
     532                 :            :                    entry->signature, entry->pts);
     533                 :            :     }
     534                 :            : 
     535                 :       2512 :     return entry->ok;
     536                 :            : }
     537                 :            : 
     538                 :       1756 : static bool map_entry(pl_queue p, struct entry *entry)
     539                 :            : {
     540         [ +  + ]:       1956 :     bool ok = map_frame(p, entry->primary ? entry->primary : entry);
     541         [ +  + ]:       1756 :     if (entry->prev)
     542                 :        376 :         ok &= map_frame(p, entry->prev);
     543         [ +  + ]:       1756 :     if (entry->next)
     544                 :        380 :         ok &= map_frame(p, entry->next);
     545         [ +  - ]:       1756 :     if (!ok)
     546                 :            :         return false;
     547                 :            : 
     548         [ +  + ]:       1756 :     if (entry->primary)
     549                 :        200 :         entry->frame = entry->primary->frame;
     550                 :            : 
     551         [ +  + ]:       1756 :     if (entry->field) {
     552                 :        396 :         entry->frame.field = entry->field;
     553         [ +  + ]:        396 :         entry->frame.first_field = PL_DEF(entry->primary, entry)->src.first_field;
     554         [ +  + ]:        396 :         entry->frame.prev = entry->prev ? &entry->prev->frame : NULL;
     555         [ +  + ]:        396 :         entry->frame.next = entry->next ? &entry->next->frame : NULL;
     556                 :        396 :         entry->dirty = true;
     557                 :            :     }
     558                 :            : 
     559                 :            :     return true;
     560                 :            : }
     561                 :            : 
     562                 :            : static bool entry_complete(struct entry *entry)
     563                 :            : {
     564   [ -  +  -  -  :        368 :     return entry->field ? !!entry->next : true;
                   +  + ]
     565                 :            : }
     566                 :            : 
     567                 :            : // Advance the queue as needed to make sure idx 0 is the last frame before
     568                 :            : // `pts`, and idx 1 is the first frame after `pts` (unless this is the last).
     569                 :            : //
     570                 :            : // Returns PL_QUEUE_OK only if idx 0 is still legal under ZOH semantics.
     571                 :        620 : static enum pl_queue_status advance(pl_queue p, double pts,
     572                 :            :                                     const struct pl_queue_params *params)
     573                 :            : {
     574                 :            :     // Cull all frames except the last frame before `pts`
     575                 :            :     int culled = 0;
     576         [ +  + ]:       7320 :     for (int i = 1; i < p->queue.num; i++) {
     577         [ +  + ]:       6700 :         if (p->queue.elem[i]->pts <= pts) {
     578                 :        304 :             entry_cull(p, p->queue.elem[i - 1], true);
     579                 :        304 :             culled++;
     580                 :            :         }
     581                 :            :     }
     582         [ -  + ]:        620 :     PL_ARRAY_REMOVE_RANGE(p->queue, 0, culled);
     583                 :            : 
     584                 :            :     // Keep adding new frames until we find one in the future, or EOF
     585                 :            :     enum pl_queue_status ret = PL_QUEUE_OK;
     586         [ +  + ]:       1312 :     while (p->queue.num < 2) {
     587   [ +  +  -  - ]:         96 :         switch ((ret = get_frame(p, params))) {
     588                 :            :         case PL_QUEUE_ERR:
     589                 :            :             return ret;
     590                 :         24 :         case PL_QUEUE_EOF:
     591         [ +  + ]:         24 :             if (!p->queue.num)
     592                 :            :                 return ret;
     593                 :         20 :             goto done;
     594                 :            :         case PL_QUEUE_MORE:
     595                 :            :         case PL_QUEUE_OK:
     596   [ +  -  -  + ]:         72 :             while (p->queue.num > 1 && p->queue.elem[1]->pts <= pts) {
     597                 :          0 :                 entry_cull(p, p->queue.elem[0], true);
     598         [ #  # ]:          0 :                 PL_ARRAY_REMOVE_AT(p->queue, 0);
     599                 :            :             }
     600         [ +  - ]:         72 :             if (ret == PL_QUEUE_MORE)
     601                 :            :                 return ret;
     602                 :         72 :             continue;
     603                 :            :         }
     604                 :            :     }
     605                 :            : 
     606   [ +  +  +  + ]:       1192 :     if (!entry_complete(p->queue.elem[1])) {
     607   [ -  -  +  - ]:          8 :         switch (get_frame(p, params)) {
     608                 :            :         case PL_QUEUE_ERR:
     609                 :            :             return PL_QUEUE_ERR;
     610                 :          0 :         case PL_QUEUE_MORE:
     611                 :            :             ret = PL_QUEUE_MORE;
     612                 :            :             // fall through
     613                 :          8 :         case PL_QUEUE_EOF:
     614                 :            :         case PL_QUEUE_OK:
     615                 :          8 :             goto done;
     616                 :            :         }
     617                 :            :     }
     618                 :            : 
     619                 :        588 : done:
     620   [ +  +  +  + ]:        616 :     if (p->eof && p->queue.num == 1) {
     621   [ +  -  +  - ]:         20 :         if (p->queue.elem[0]->pts == 0.0 || !p->fps.estimate) {
     622                 :            :             // If the last frame has PTS 0.0, or we have no FPS estimate, then
     623                 :            :             // this is probably a single-frame file, in which case we want to
     624                 :            :             // extend the ZOH to infinity, rather than returning. Not a perfect
     625                 :            :             // heuristic, but w/e
     626                 :            :             return PL_QUEUE_OK;
     627                 :            :         }
     628                 :            : 
     629                 :            :         // Last frame is held for an extra `p->fps.estimate` duration,
     630                 :            :         // afterwards this function just returns EOF.
     631         [ +  + ]:         20 :         if (pts < p->queue.elem[0]->pts + p->fps.estimate) {
     632                 :            :             ret = PL_QUEUE_OK;
     633                 :            :         } else {
     634                 :          8 :             entry_cull(p, p->queue.elem[0], true);
     635                 :          8 :             p->queue.num = 0;
     636                 :          8 :             return PL_QUEUE_EOF;
     637                 :            :         }
     638                 :            :     }
     639                 :            : 
     640         [ -  + ]:        608 :     pl_assert(p->queue.num);
     641                 :            :     return ret;
     642                 :            : }
     643                 :            : 
     644                 :         12 : static inline enum pl_queue_status point(pl_queue p, struct pl_frame_mix *mix,
     645                 :            :                                          const struct pl_queue_params *params)
     646                 :            : {
     647         [ -  + ]:         12 :     if (!p->queue.num) {
     648                 :          0 :         *mix = (struct pl_frame_mix) {0};
     649                 :            :         return PL_QUEUE_MORE;
     650                 :            :     }
     651                 :            : 
     652                 :            :     // Find closest frame (nearest neighbour semantics)
     653                 :         12 :     struct entry *entry = p->queue.elem[0];
     654         [ -  + ]:         12 :     if (entry->pts > params->pts) { // first frame not visible yet
     655                 :          0 :         *mix = (struct pl_frame_mix) {0};
     656                 :            :         return PL_QUEUE_OK;
     657                 :            :     }
     658                 :            : 
     659                 :         12 :     double best = fabs(entry->pts - params->pts);
     660         [ -  + ]:         12 :     for (int i = 1; i < p->queue.num; i++) {
     661                 :          0 :         double dist = fabs(p->queue.elem[i]->pts - params->pts);
     662         [ #  # ]:          0 :         if (dist < best) {
     663                 :            :             entry = p->queue.elem[i];
     664                 :            :             best = dist;
     665                 :            :             continue;
     666                 :            :         } else {
     667                 :            :             break;
     668                 :            :         }
     669                 :            :     }
     670                 :            : 
     671         [ +  - ]:         12 :     if (!map_entry(p, entry))
     672                 :            :         return PL_QUEUE_ERR;
     673                 :            : 
     674                 :            :     // Return a mix containing only this single frame
     675                 :         12 :     p->tmp_sig.num = p->tmp_ts.num = p->tmp_frame.num = 0;
     676   [ -  +  -  +  :         12 :     PL_ARRAY_APPEND(p, p->tmp_sig, entry->signature);
                   -  + ]
     677   [ -  +  -  +  :         12 :     PL_ARRAY_APPEND(p, p->tmp_frame, &entry->frame);
                   -  + ]
     678   [ -  +  -  +  :         12 :     PL_ARRAY_APPEND(p, p->tmp_ts, 0.0);
                   -  + ]
     679                 :         12 :     *mix = (struct pl_frame_mix) {
     680                 :            :         .num_frames = 1,
     681                 :         12 :         .frames = p->tmp_frame.elem,
     682                 :         12 :         .signatures = p->tmp_sig.elem,
     683                 :            :         .timestamps = p->tmp_ts.elem,
     684                 :            :         .vsync_duration = 1.0,
     685                 :            :     };
     686                 :            : 
     687                 :         12 :     PL_TRACE(p, "Showing single frame id %"PRIu64" with PTS %f for target PTS %f",
     688                 :            :              entry->signature, entry->pts, params->pts);
     689                 :            : 
     690                 :         12 :     report_estimates(p);
     691                 :         12 :     return PL_QUEUE_OK;
     692                 :            : }
     693                 :            : 
     694                 :            : // Present a single frame as appropriate for `pts`
     695                 :          4 : static enum pl_queue_status nearest(pl_queue p, struct pl_frame_mix *mix,
     696                 :            :                                     const struct pl_queue_params *params)
     697                 :            : {
     698                 :            :     enum pl_queue_status ret;
     699      [ -  -  + ]:          4 :     switch ((ret = advance(p, params->pts, params))) {
     700                 :            :     case PL_QUEUE_ERR:
     701                 :            :     case PL_QUEUE_EOF:
     702                 :            :         return ret;
     703                 :          0 :     case PL_QUEUE_OK:
     704                 :            :     case PL_QUEUE_MORE:
     705   [ #  #  #  # ]:          0 :         if (mix && point(p, mix, params) == PL_QUEUE_ERR)
     706                 :            :             return PL_QUEUE_ERR;
     707                 :            :         return ret;
     708                 :            :     }
     709                 :            : 
     710                 :          0 :     pl_unreachable();
     711                 :            : }
     712                 :            : 
     713                 :            : // Special case of `interpolate` for radius = 0, in which case we need exactly
     714                 :            : // the previous frame and the following frame
     715                 :        408 : static enum pl_queue_status oversample(pl_queue p, struct pl_frame_mix *mix,
     716                 :            :                                        const struct pl_queue_params *params)
     717                 :            : {
     718                 :            :     enum pl_queue_status ret;
     719      [ +  -  + ]:        408 :     switch ((ret = advance(p, params->pts, params))) {
     720                 :            :     case PL_QUEUE_ERR:
     721                 :            :     case PL_QUEUE_EOF:
     722                 :            :         return ret;
     723                 :            :     case PL_QUEUE_OK:
     724                 :            :         break;
     725                 :          0 :     case PL_QUEUE_MORE:
     726         [ #  # ]:          0 :         if (!p->queue.num) {
     727         [ #  # ]:          0 :             if (mix)
     728                 :          0 :                 *mix = (struct pl_frame_mix) {0};
     729                 :          0 :             return ret;
     730                 :            :         }
     731                 :            :         break;
     732                 :            :     }
     733                 :            : 
     734         [ +  - ]:        400 :     if (!mix)
     735                 :            :         return PL_QUEUE_OK;
     736                 :            : 
     737                 :            :     // Can't oversample with only a single frame, fall back to point sampling
     738   [ +  +  -  + ]:        400 :     if (p->queue.num < 2 || p->queue.elem[0]->pts > params->pts) {
     739         [ +  - ]:         12 :         if (point(p, mix, params) != PL_QUEUE_OK)
     740                 :            :             return PL_QUEUE_ERR;
     741                 :            :         return ret;
     742                 :            :     }
     743                 :            : 
     744                 :        388 :     struct entry *entries[2] = { p->queue.elem[0], p->queue.elem[1] };
     745         [ -  + ]:        388 :     pl_assert(entries[0]->pts <= params->pts);
     746         [ -  + ]:        388 :     pl_assert(entries[1]->pts >= params->pts);
     747                 :            : 
     748                 :            :     // Returning a mix containing both of these two frames
     749                 :        388 :     p->tmp_sig.num = p->tmp_ts.num = p->tmp_frame.num = 0;
     750         [ +  + ]:       1164 :     for (int i = 0; i < 2; i++) {
     751         [ -  + ]:        776 :         if (!map_entry(p, entries[i]))
     752                 :            :             return PL_QUEUE_ERR;
     753                 :        776 :         float ts = (entries[i]->pts - params->pts) / p->fps.estimate;
     754   [ -  +  -  +  :        776 :         PL_ARRAY_APPEND(p, p->tmp_sig, entries[i]->signature);
                   -  + ]
     755   [ -  +  -  +  :        776 :         PL_ARRAY_APPEND(p, p->tmp_frame, &entries[i]->frame);
                   -  + ]
     756   [ -  +  -  +  :        776 :         PL_ARRAY_APPEND(p, p->tmp_ts, ts);
                   -  + ]
     757                 :            :     }
     758                 :            : 
     759                 :        388 :     *mix = (struct pl_frame_mix) {
     760                 :            :         .num_frames = 2,
     761                 :        388 :         .frames = p->tmp_frame.elem,
     762                 :        388 :         .signatures = p->tmp_sig.elem,
     763                 :        388 :         .timestamps = p->tmp_ts.elem,
     764                 :        388 :         .vsync_duration = p->vps.estimate / p->fps.estimate,
     765                 :            :     };
     766                 :            : 
     767                 :        388 :     PL_TRACE(p, "Oversampling 2 frames for target PTS %f:", params->pts);
     768         [ +  + ]:       1164 :     for (int i = 0; i < mix->num_frames; i++)
     769                 :        776 :         PL_TRACE(p, "    id %"PRIu64" ts %f", mix->signatures[i], mix->timestamps[i]);
     770                 :            : 
     771                 :        388 :     report_estimates(p);
     772                 :        388 :     return ret;
     773                 :            : }
     774                 :            : 
     775                 :            : // Present a mixture of frames, relative to the vsync ratio
     776                 :        620 : static enum pl_queue_status interpolate(pl_queue p, struct pl_frame_mix *mix,
     777                 :            :                                         const struct pl_queue_params *params)
     778                 :            : {
     779                 :            :     // No FPS estimate available, possibly source contains only a single frame,
     780                 :            :     // or this is the first frame to be rendered. Fall back to point sampling.
     781         [ +  + ]:        620 :     if (!p->fps.estimate)
     782                 :          4 :         return nearest(p, mix, params);
     783                 :            : 
     784                 :            :     // Silently disable interpolation if the ratio dips lower than the
     785                 :            :     // configured threshold
     786                 :        616 :     float ratio = fabs(p->fps.estimate / p->vps.estimate - 1.0);
     787         [ -  + ]:        616 :     if (ratio < params->interpolation_threshold) {
     788         [ #  # ]:          0 :         if (!p->threshold_frames) {
     789                 :          0 :             PL_INFO(p, "Detected fps ratio %.4f below threshold %.4f, "
     790                 :            :                     "disabling interpolation",
     791                 :            :                     ratio, params->interpolation_threshold);
     792                 :            :         }
     793                 :            : 
     794                 :          0 :         p->threshold_frames = THRESHOLD_FRAMES + 1;
     795                 :          0 :         return nearest(p, mix, params);
     796   [ +  +  -  + ]:        616 :     } else if (ratio < THRESHOLD_MAX_RATIO && p->threshold_frames > 1) {
     797                 :          0 :         p->threshold_frames--;
     798                 :          0 :         return nearest(p, mix, params);
     799                 :            :     } else {
     800         [ -  + ]:        616 :         if (p->threshold_frames) {
     801                 :          0 :             PL_INFO(p, "Detected fps ratio %.4f exceeds threshold %.4f, "
     802                 :            :                     "re-enabling interpolation",
     803                 :            :                     ratio, params->interpolation_threshold);
     804                 :            :         }
     805                 :        616 :         p->threshold_frames = 0;
     806                 :            :     }
     807                 :            : 
     808                 :            :     // No radius information, special case in which we only need the previous
     809                 :            :     // and next frames.
     810         [ +  + ]:        616 :     if (!params->radius)
     811                 :        408 :         return oversample(p, mix, params);
     812                 :            : 
     813   [ +  -  -  + ]:        208 :     pl_assert(p->fps.estimate && p->vps.estimate);
     814                 :        208 :     float radius = params->radius * fmaxf(1.0f, p->vps.estimate / p->fps.estimate);
     815                 :        208 :     double min_pts = params->pts - radius * p->fps.estimate,
     816                 :        208 :            max_pts = params->pts + radius * p->fps.estimate;
     817                 :            : 
     818                 :            :     enum pl_queue_status ret;
     819      [ -  +  - ]:        208 :     switch ((ret = advance(p, min_pts, params))) {
     820                 :            :     case PL_QUEUE_ERR:
     821                 :            :     case PL_QUEUE_EOF:
     822                 :            :         return ret;
     823                 :          0 :     case PL_QUEUE_MORE:
     824                 :          0 :         goto done;
     825                 :            :     case PL_QUEUE_OK:
     826                 :            :         break;
     827                 :            :     }
     828                 :            : 
     829                 :            :     // Keep adding new frames until we've covered the range we care about
     830         [ -  + ]:        208 :     pl_assert(p->queue.num);
     831         [ +  + ]:        208 :     while (p->queue.elem[p->queue.num - 1]->pts < max_pts) {
     832   [ -  +  +  -  :         36 :         switch ((ret = get_frame(p, params))) {
                      - ]
     833                 :            :         case PL_QUEUE_ERR:
     834                 :            :             return ret;
     835                 :          4 :         case PL_QUEUE_MORE:
     836                 :          4 :             goto done;
     837                 :         32 :         case PL_QUEUE_EOF:;
     838                 :            :             // Don't forward EOF until we've held the last frame for the
     839                 :            :             // desired ZOH hold duration
     840                 :         32 :             double last_pts = p->queue.elem[p->queue.num - 1]->pts;
     841   [ +  -  +  + ]:         32 :             if (last_pts && params->pts >= last_pts + p->fps.estimate)
     842                 :            :                 return ret;
     843                 :            :             ret = PL_QUEUE_OK;
     844                 :         28 :             goto done;
     845                 :          0 :         case PL_QUEUE_OK:
     846                 :          0 :             continue;
     847                 :            :         }
     848                 :            :     }
     849                 :            : 
     850         [ +  - ]:        172 :     if (!entry_complete(p->queue.elem[p->queue.num - 1])) {
     851         [ #  # ]:          0 :         switch ((ret = get_frame(p, params))) {
     852                 :            :         case PL_QUEUE_MORE:
     853                 :            :         case PL_QUEUE_OK:
     854                 :            :             break;
     855                 :            :         case PL_QUEUE_ERR:
     856                 :            :         case PL_QUEUE_EOF:
     857                 :            :             return ret;
     858                 :            :         }
     859                 :            :     }
     860                 :            : 
     861                 :        172 : done: ;
     862                 :            : 
     863         [ +  - ]:        204 :     if (!mix)
     864                 :            :         return PL_QUEUE_OK;
     865                 :            : 
     866                 :            :     // Construct a mix object representing the current queue state, starting at
     867                 :            :     // the last frame before `min_pts` to make sure there's a fallback frame
     868                 :            :     // available for ZOH semantics.
     869                 :        204 :     p->tmp_sig.num = p->tmp_ts.num = p->tmp_frame.num = 0;
     870         [ +  + ]:       1164 :     for (int i = 0; i < p->queue.num; i++) {
     871                 :       1132 :         struct entry *entry = p->queue.elem[i];
     872         [ +  + ]:       1132 :         if (entry->pts > max_pts)
     873                 :            :             break;
     874         [ -  + ]:        960 :         if (!map_entry(p, entry))
     875                 :            :             return PL_QUEUE_ERR;
     876                 :        960 :         float ts = (entry->pts - params->pts) / p->fps.estimate;
     877   [ +  +  -  +  :        960 :         PL_ARRAY_APPEND(p, p->tmp_sig, entry->signature);
                   -  + ]
     878   [ +  +  -  +  :        960 :         PL_ARRAY_APPEND(p, p->tmp_frame, &entry->frame);
                   -  + ]
     879   [ +  +  -  +  :        960 :         PL_ARRAY_APPEND(p, p->tmp_ts, ts);
                   -  + ]
     880                 :            :     }
     881                 :            : 
     882                 :        204 :     *mix = (struct pl_frame_mix) {
     883                 :        204 :         .num_frames = p->tmp_frame.num,
     884                 :        204 :         .frames = p->tmp_frame.elem,
     885                 :        204 :         .signatures = p->tmp_sig.elem,
     886                 :        204 :         .timestamps = p->tmp_ts.elem,
     887                 :        204 :         .vsync_duration = p->vps.estimate / p->fps.estimate,
     888                 :            :     };
     889                 :            : 
     890                 :        204 :     PL_TRACE(p, "Showing mix of %d frames for target PTS %f:",
     891                 :            :              mix->num_frames, params->pts);
     892         [ +  + ]:       1164 :     for (int i = 0; i < mix->num_frames; i++)
     893                 :        960 :         PL_TRACE(p, "    id %"PRIu64" ts %f", mix->signatures[i], mix->timestamps[i]);
     894                 :            : 
     895                 :        204 :     report_estimates(p);
     896                 :        204 :     return ret;
     897                 :            : }
     898                 :            : 
     899                 :          4 : static bool prefill(pl_queue p, const struct pl_queue_params *params)
     900                 :            : {
     901                 :          4 :     int min_frames = 2 * ceilf(params->radius);
     902   [ -  +  -  -  :          4 :     if (p->fps.estimate && p->vps.estimate && p->vps.estimate <= 1.0f / MIN_FPS)
                   -  - ]
     903                 :          0 :         min_frames *= ceilf(p->vps.estimate / p->fps.estimate);
     904                 :          4 :     min_frames = PL_MAX(min_frames, PREFETCH_FRAMES);
     905                 :            : 
     906         [ +  + ]:         12 :     while (p->queue.num < min_frames) {
     907   [ -  -  +  - ]:          8 :         switch (get_frame(p, params)) {
     908                 :            :         case PL_QUEUE_ERR:
     909                 :            :             return false;
     910                 :            :         case PL_QUEUE_EOF:
     911                 :            :         case PL_QUEUE_MORE:
     912                 :            :             return true;
     913                 :          8 :         case PL_QUEUE_OK:
     914                 :          8 :             continue;
     915                 :            :         }
     916                 :            :     }
     917                 :            : 
     918                 :            :     // In the most likely case, the first few frames will all be required. So
     919                 :            :     // force-map them all to initialize GPU state on initial rendering. This is
     920                 :            :     // better than the alternative of missing the cache later, when timing is
     921                 :            :     // more relevant.
     922         [ +  + ]:         12 :     for (int i = 0; i < min_frames; i++) {
     923         [ -  + ]:          8 :         if (!map_entry(p, p->queue.elem[i]))
     924                 :            :             return false;
     925                 :            :     }
     926                 :            : 
     927                 :            :     return true;
     928                 :            : }
     929                 :            : 
     930                 :        620 : enum pl_queue_status pl_queue_update(pl_queue p, struct pl_frame_mix *out_mix,
     931                 :            :                                      const struct pl_queue_params *params)
     932                 :            : {
     933                 :            :     struct pl_queue_params fixed;
     934                 :        620 :     pl_mutex_lock(&p->lock_strong);
     935                 :        620 :     pl_mutex_lock(&p->lock_weak);
     936         [ +  + ]:        620 :     default_estimate(&p->vps, params->vsync_duration);
     937                 :            : 
     938                 :        620 :     float delta = params->pts - p->prev_pts;
     939         [ -  + ]:        620 :     if (delta < 0.0f) {
     940                 :            : 
     941                 :            :         // This is a backwards PTS jump. This is something we can handle
     942                 :            :         // semi-gracefully, but only if we haven't culled past the current
     943                 :            :         // frame yet.
     944   [ #  #  #  # ]:          0 :         if (p->queue.num && p->queue.elem[0]->pts > params->pts) {
     945                 :          0 :             PL_ERR(p, "Requested PTS %f is lower than the oldest frame "
     946                 :            :                    "PTS %f. This is not supported, PTS must be monotonically "
     947                 :            :                    "increasing! Please use `pl_queue_reset` to reset the frame "
     948                 :            :                    "queue on discontinuous PTS jumps.",
     949                 :            :                    params->pts, p->queue.elem[0]->pts);
     950                 :          0 :             pl_mutex_unlock(&p->lock_weak);
     951                 :          0 :             pl_mutex_unlock(&p->lock_strong);
     952                 :          0 :             return PL_QUEUE_ERR;
     953                 :            :         }
     954                 :            : 
     955         [ -  + ]:        620 :     } else if (delta > 1.0f) {
     956                 :            : 
     957                 :            :         // A jump of more than a second is probably the result of a
     958                 :            :         // discontinuous jump after a suspend. To prevent this from exploding
     959                 :            :         // the FPS estimate, treat this as a new frame.
     960                 :          0 :         PL_TRACE(p, "Discontinuous target PTS jump %f -> %f, ignoring...",
     961                 :            :                  p->prev_pts, params->pts);
     962                 :          0 :         p->pts_offset = 0.0;
     963                 :            : 
     964         [ +  + ]:        620 :     } else if (delta > 0) {
     965                 :            : 
     966                 :        604 :         update_estimate(&p->vps, params->pts - p->prev_pts);
     967                 :            : 
     968                 :            :     }
     969                 :            : 
     970                 :        620 :     p->prev_pts = params->pts;
     971                 :            : 
     972         [ -  + ]:        620 :     if (params->drift_compensation > 0.0f) {
     973                 :            :         // Adjust PTS offset if PTS is near-match for existing frame
     974                 :          0 :         double pts = params->pts + p->pts_offset;
     975         [ #  # ]:          0 :         for (int i = 0; i < p->queue.num; i++) {
     976         [ #  # ]:          0 :             if (fabs(p->queue.elem[i]->pts - pts) < params->drift_compensation) {
     977                 :          0 :                 p->pts_offset = p->queue.elem[i]->pts - params->pts;
     978                 :            :                 pts = p->queue.elem[i]->pts;
     979                 :          0 :                 break;
     980                 :            :             }
     981                 :            :         }
     982                 :            : 
     983                 :          0 :         fixed = *params;
     984                 :          0 :         fixed.pts = pts;
     985                 :            :         params = &fixed;
     986                 :            :     }
     987                 :            : 
     988                 :            :     // As a special case, prefill the queue if this is the first frame
     989   [ +  +  +  + ]:        620 :     if (!params->pts && !p->queue.num) {
     990         [ -  + ]:          4 :         if (!prefill(p, params)) {
     991                 :          0 :             pl_mutex_unlock(&p->lock_weak);
     992                 :          0 :             pl_mutex_unlock(&p->lock_strong);
     993                 :          0 :             return PL_QUEUE_ERR;
     994                 :            :         }
     995                 :            :     }
     996                 :            : 
     997                 :            :     // Ignore unrealistically high or low FPS, common near start of playback
     998                 :            :     static const float max_vsync = 1.0 / MIN_FPS;
     999                 :            :     static const float min_vsync = 1.0 / MAX_FPS;
    1000   [ +  -  +  + ]:        620 :     bool estimation_ok = p->vps.estimate > min_vsync && p->vps.estimate < max_vsync;
    1001                 :            :     enum pl_queue_status ret;
    1002                 :            : 
    1003         [ +  - ]:          4 :     if (estimation_ok || params->vsync_duration > 0) {
    1004                 :            :         // We know the vsync duration, so construct an interpolation mix
    1005                 :        620 :         ret = interpolate(p, out_mix, params);
    1006                 :            :     } else {
    1007                 :            :         // We don't know the vsync duration (yet), so just point-sample
    1008                 :          0 :         ret = nearest(p, out_mix, params);
    1009                 :            :     }
    1010                 :            : 
    1011                 :        620 :     pl_cond_signal(&p->wakeup);
    1012                 :        620 :     pl_mutex_unlock(&p->lock_weak);
    1013                 :        620 :     pl_mutex_unlock(&p->lock_strong);
    1014                 :        620 :     return ret;
    1015                 :            : }
    1016                 :            : 
    1017                 :          0 : float pl_queue_estimate_fps(pl_queue p)
    1018                 :            : {
    1019                 :          0 :     pl_mutex_lock(&p->lock_weak);
    1020                 :          0 :     float estimate = p->fps.estimate;
    1021                 :          0 :     pl_mutex_unlock(&p->lock_weak);
    1022         [ #  # ]:          0 :     return estimate ? 1.0f / estimate : 0.0f;
    1023                 :            : }
    1024                 :            : 
    1025                 :          0 : float pl_queue_estimate_vps(pl_queue p)
    1026                 :            : {
    1027                 :          0 :     pl_mutex_lock(&p->lock_weak);
    1028                 :          0 :     float estimate = p->vps.estimate;
    1029                 :          0 :     pl_mutex_unlock(&p->lock_weak);
    1030         [ #  # ]:          0 :     return estimate ? 1.0f / estimate : 0.0f;
    1031                 :            : }
    1032                 :            : 
    1033                 :          0 : int pl_queue_num_frames(pl_queue p)
    1034                 :            : {
    1035                 :          0 :     pl_mutex_lock(&p->lock_weak);
    1036                 :          0 :     int count = p->queue.num;
    1037                 :          0 :     pl_mutex_unlock(&p->lock_weak);
    1038                 :          0 :     return count;
    1039                 :            : }
    1040                 :            : 
    1041                 :          0 : double pl_queue_pts_offset(pl_queue p)
    1042                 :            : {
    1043                 :          0 :     pl_mutex_lock(&p->lock_weak);
    1044                 :          0 :     double offset = p->pts_offset;
    1045                 :          0 :     pl_mutex_unlock(&p->lock_weak);
    1046                 :          0 :     return offset;
    1047                 :            : }
    1048                 :            : 
    1049                 :          0 : bool pl_queue_peek(pl_queue p, int idx, struct pl_source_frame *out)
    1050                 :            : {
    1051                 :          0 :     pl_mutex_lock(&p->lock_weak);
    1052   [ #  #  #  # ]:          0 :     bool ok = idx >= 0 && idx < p->queue.num;
    1053                 :            :     if (ok)
    1054                 :          0 :         *out = p->queue.elem[idx]->src;
    1055                 :          0 :     pl_mutex_unlock(&p->lock_weak);
    1056                 :          0 :     return ok;
    1057                 :            : }

Generated by: LCOV version 1.16