camera/avfoundation: reuse frameBuffer for color-bars fallback, null after free

Audit of the frameBuffer lifecycle turned up three small issues in
camera/drivers/avfoundation.m that all cluster around the same ivar:

  1. avfoundation_init's setupCameraSession-failed error path does
     free(avf->manager.frameBuffer) but does not null the field.
     The manager is a singleton, so the next avfoundation_init
     inherits a dangling pointer until its own calloc overwrites
     the slot. Everything in this driver runs on the main thread
     (capture delegate queue is dispatch_get_main_queue(), poll
     runs from rarch_draw_observer on the main run loop, init/free
     are on the main thread) so no concurrent access can observe
     the dangling value - but it is still defensive-programming-
     wrong to leave a singleton ivar pointing at freed memory.

  2. avfoundation_poll's color-bars fallback calloc'd and freed a
     throwaway buffer on every poll while the camera session was
     warming up or stopped. For a typical 1280x720 camera that is
     ~3.5 MB of allocator churn per poll, fired at runloop rate
     until the session reports isRunning. Since we already have
     a cached per-driver frameBuffer on the manager (allocated in
     init, size matches avf->width x avf->height exactly), paint
     the color-bars pattern directly into it and hand that to
     frame_raw_cb. The captureOutput callback will overwrite the
     same buffer with real camera frames once the session starts.

  3. The 'Camera not running, generating color bars...' log at the
     top of that fallback path fires every poll during the warmup
     window - the same per-frame log-spam pattern that commit
     141d2a6 already fixed for two other logs in the file. Gate
     this one behind #ifdef DEBUG to match.

Threading model (unchanged): the capture delegate is invoked on the
main queue (set via setSampleBufferDelegate:queue: to
dispatch_get_main_queue()) and the consumer of frameBuffer reads on
the main thread as well (runloop_iterate runs from a
CFRunLoopObserver attached to CFRunLoopGetMain() on macOS, from
CADisplayLink on iOS/tvOS - both main-thread). Writer and reader
are fully serialized by the main run loop, so reuse of frameBuffer
across both paths needs no additional synchronization.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-23 19:26:32 +02:00
1 parent 0ed525cf10
commit f3b3d69fcb
1 file changed
+24 -9
+24 -9
View File
@@ -656,7 +656,14 @@ static void *avfoundation_init(const char *device, uint64_t caps,
if (!setupSuccess)
{
RARCH_ERR("[Camera] Failed to setup camera.\n");
/* Null out frameBuffer on the singleton manager after freeing,
* so a subsequent init doesn't observe a stale pointer. The
* main-thread-only access pattern means nothing actually sees
* the dangling value between these two statements and the next
* init's calloc on the same field, but leaving a dangling
* pointer in a singleton ivar is defensive-programming-wrong. */
free(avf->manager.frameBuffer);
avf->manager.frameBuffer = NULL;
free(avf);
return NULL;
}
@@ -741,16 +748,24 @@ static bool avfoundation_poll(void *data,
if (!avf->manager.session.isRunning)
{
/* Session not running yet (or already stopped). Deliver a
* color-bars test pattern so the core gets a well-formed
* frame rather than nothing. Paint into the manager's own
* frameBuffer rather than allocating a throwaway: the
* generation cost is trivial (two nested loops of direct
* assignments, no conversion) and it avoids a calloc+free
* pair on every poll while the camera warms up. Once the
* session starts and captureOutput:didOutputSampleBuffer:
* begins overwriting frameBuffer with real frames, this
* branch stops firing. */
#ifdef DEBUG
RARCH_LOG("[Camera] Camera not running, generating color bars...\n");
uint32_t *tempBuffer = (uint32_t*)calloc(avf->width * avf->height, sizeof(uint32_t));
if (tempBuffer)
{
generateColorBars(tempBuffer, avf->width, avf->height);
frame_raw_cb(tempBuffer, avf->width, avf->height, avf->width * 4);
free(tempBuffer);
return true;
}
return false;
#endif
if (!avf->manager.frameBuffer)
return false;
generateColorBars(avf->manager.frameBuffer, avf->width, avf->height);
frame_raw_cb(avf->manager.frameBuffer, avf->width, avf->height, avf->width * 4);
return true;
}
#ifdef DEBUG