Get an openshot::Frame object for a specific frame number of this reader.
| 432 | |
| 433 | // Get an openshot::Frame object for a specific frame number of this reader. |
| 434 | std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame) |
| 435 | { |
| 436 | // Check final cache, and just return the frame (if it's available) |
| 437 | std::shared_ptr<Frame> final_frame = final_cache.GetFrame(requested_frame); |
| 438 | if (final_frame) return final_frame; |
| 439 | |
| 440 | // Create a scoped lock, allowing only a single thread to run the following code at one time |
| 441 | const std::lock_guard<std::recursive_mutex> lock(getFrameMutex); |
| 442 | |
| 443 | // Find parent properties (if any) |
| 444 | Clip *parent = static_cast<Clip *>(ParentClip()); |
| 445 | bool is_increasing = true; |
| 446 | bool direction_flipped = false; |
| 447 | |
| 448 | { |
| 449 | const std::lock_guard<std::recursive_mutex> lock(directionMutex); |
| 450 | |
| 451 | // One-shot: if a hint exists, consume it for THIS call, regardless of frame number. |
| 452 | if (have_hint) { |
| 453 | is_increasing = hint_increasing; |
| 454 | have_hint = false; |
| 455 | } else if (previous_frame > 0 && std::llabs(requested_frame - previous_frame) == 1) { |
| 456 | // Infer from request order when adjacent |
| 457 | is_increasing = (requested_frame > previous_frame); |
| 458 | } else if (last_dir_initialized) { |
| 459 | // Reuse last known direction if non-adjacent and no hint |
| 460 | is_increasing = last_is_increasing; |
| 461 | } else { |
| 462 | is_increasing = true; // default on first call |
| 463 | } |
| 464 | |
| 465 | // Detect flips so we can reset SR context |
| 466 | if (!last_dir_initialized) { |
| 467 | last_is_increasing = is_increasing; |
| 468 | last_dir_initialized = true; |
| 469 | } else if (last_is_increasing != is_increasing) { |
| 470 | direction_flipped = true; |
| 471 | last_is_increasing = is_increasing; |
| 472 | } |
| 473 | } |
| 474 | if (parent) { |
| 475 | float position = parent->Position(); |
| 476 | float start = parent->Start(); |
| 477 | if (parent_position != position || parent_start != start) { |
| 478 | // Force dirty if parent clip has moved or been trimmed |
| 479 | // since this heavily affects frame #s and audio mappings |
| 480 | is_dirty = true; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | // Check if mappings are dirty (and need to be recalculated) |
| 485 | if (is_dirty) |
| 486 | Init(); |
| 487 | |
| 488 | // Check final cache a 2nd time (due to potential lock already generating this frame) |
| 489 | final_frame = final_cache.GetFrame(requested_frame); |
| 490 | if (final_frame) return final_frame; |
| 491 |
no test coverage detected