Skip to content

Commit

Permalink
ScreenCaptureKit: Add support for callback APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
jkarthic committed May 12, 2024
1 parent a4d5c72 commit 28acd77
Showing 1 changed file with 93 additions and 27 deletions.
120 changes: 93 additions & 27 deletions src/hostapi/screencapturekit/pa_mac_screencapturekit.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include <CoreMedia/CoreMedia.h>
#include <ScreenCaptureKit/ScreenCaptureKit.h>
#include <pthread.h>
#include <pa_debugprint.h>
#include <pa_hostapi.h>
#include <pa_ringbuffer.h>
Expand All @@ -54,6 +55,7 @@ @interface ScreenCaptureKitStreamOutput : NSObject <SCStreamOutput>
{
PaUtilHostApiRepresentation inheritedHostApiRep;
PaUtilStreamInterface blockingStreamInterface;
PaUtilStreamInterface callbackStreamInterface;
} PaScreenCaptureKitHostApiRepresentation;

typedef struct PaScreenCaptureKitStream
Expand All @@ -64,6 +66,10 @@ @interface ScreenCaptureKitStreamOutput : NSObject <SCStreamOutput>
PaUtilRingBuffer ringBuffer;
BOOL isStopped;
int sampleRate;
PaStreamCallback *streamCallback;
void *userData;
unsigned long framesPerBuffer;
pthread_t callbackThreadId;
} PaScreenCaptureKitStream;

@implementation ScreenCaptureKitStreamOutput
Expand Down Expand Up @@ -137,6 +143,27 @@ - (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampl
}
@end

static PaError StopStreamInternal(PaStream *s)
{
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)s;
__block PaError result = paNoError;
dispatch_group_t handlerGroup = dispatch_group_create();
dispatch_group_enter(handlerGroup);
// Stop the audio capture session
[stream->audioStream stopCaptureWithCompletionHandler:^(NSError *error) {
if (error)
{
PA_DEBUG(("Failed to stop audio capture: %s\n", [[error localizedDescription] UTF8String]));
result = paInternalError;
return;
}
dispatch_group_leave(handlerGroup);
}];
stream->isStopped = TRUE;
dispatch_group_wait(handlerGroup, DISPATCH_TIME_FOREVER);
return result;
}

// PortAudio host API stream read function
static PaError ReadStream(PaStream *s, void *buffer, unsigned long frames)
{
Expand All @@ -153,6 +180,35 @@ static PaError ReadStream(PaStream *s, void *buffer, unsigned long frames)
return paNoError;
}

static void *StreamProcessingThread(void *userData)
{
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)userData;
while (!stream->isStopped)
{
// Wait until enough data is available in the ring buffer
if (PaUtil_GetRingBufferReadAvailable(&stream->ringBuffer) >= stream->framesPerBuffer)
{
float buffer[stream->framesPerBuffer];
// Copy the data to the buffer
ring_buffer_size_t read = PaUtil_ReadRingBuffer(&stream->ringBuffer, buffer, stream->framesPerBuffer);

const PaStreamCallbackTimeInfo timeInfo = {0, 0, 0}; // TODO : Fill the timestamps
PaStreamCallbackFlags statusFlags = 0; // TODO : Determine underflow/overflow flags as needed

// Call the user callback
PaStreamCallbackResult callbackResult = stream->streamCallback(buffer, NULL,
stream->framesPerBuffer, &timeInfo, statusFlags, stream->userData);

if (callbackResult != paContinue)
{
StopStreamInternal((PaStream *)stream);
}
}
usleep(1000);
}
return NULL;
}

// PortAudio host API is format supported function
static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters *inputParameters,
const PaStreamParameters *outputParameters, double sampleRate)
Expand Down Expand Up @@ -200,8 +256,6 @@ static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStream
PaError result = IsFormatSupported(hostApi, inputParameters, outputParameters, sampleRate);
if (result != paFormatIsSupported)
return result;
if (streamCallback != NULL)
return paCanNotWriteToACallbackStream;
PaScreenCaptureKitHostApiRepresentation *paSck = (PaScreenCaptureKitHostApiRepresentation *)hostApi;
PaScreenCaptureKitStream *stream = NULL;
result = paNoError;
Expand Down Expand Up @@ -307,7 +361,19 @@ static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStream
goto error;
}

PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, &paSck->blockingStreamInterface, NULL, NULL);
if (streamCallback)
{
PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, &paSck->callbackStreamInterface,
streamCallback, userData);
stream->streamCallback = streamCallback;
stream->userData = userData;
stream->framesPerBuffer = framesPerBuffer;
}
else
{
PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, &paSck->blockingStreamInterface, NULL,
NULL);
}
stream->isStopped = TRUE;

// Around 1 second of ringbuffer (allocated to nearest power of 2)
Expand Down Expand Up @@ -335,15 +401,6 @@ static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStream
return result;
}

// PortAudio host API stream close function
static PaError CloseStream(PaStream *s)
{
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)s;
PaUtil_FreeMemory(stream->ringBuffer.buffer);
PaUtil_FreeMemory(stream);
return paNoError;
}

static PaError IsStreamStopped(PaStream *s)
{
return ((PaScreenCaptureKitStream *)s)->isStopped;
Expand All @@ -360,6 +417,7 @@ static PaError StartStream(PaStream *s)
return paStreamIsNotStopped;
__block PaError result = paNoError;
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)s;

dispatch_group_t handlerGroup = dispatch_group_create();
dispatch_group_enter(handlerGroup);

Expand All @@ -377,6 +435,14 @@ static PaError StartStream(PaStream *s)
{
stream->isStopped = FALSE;
}
if (stream->streamCallback != NULL)
{
int result = pthread_create(&stream->callbackThreadId, NULL, StreamProcessingThread, stream);
if (result != 0) {
PA_DEBUG(("Failed to create audio processing thread\n"));
return paUnanticipatedHostError;
}
}

return result;
}
Expand All @@ -386,22 +452,9 @@ static PaError StopStream(PaStream *s)
{
if (IsStreamStopped(s))
return paStreamIsStopped;
__block PaError result = paNoError;
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)s;
dispatch_group_t handlerGroup = dispatch_group_create();
dispatch_group_enter(handlerGroup);
// Stop the audio capture session
[stream->audioStream stopCaptureWithCompletionHandler:^(NSError *error) {
if (error)
{
PA_DEBUG(("Failed to stop audio capture: %s\n", [[error localizedDescription] UTF8String]));
result = paInternalError;
return;
}
dispatch_group_leave(handlerGroup);
}];
stream->isStopped = TRUE;
dispatch_group_wait(handlerGroup, DISPATCH_TIME_FOREVER);
PaError result = StopStreamInternal(s);
pthread_join(stream->callbackThreadId, NULL);

return result;
}
Expand All @@ -412,6 +465,16 @@ static PaError AbortStream(PaStream *s)
return StopStream(s);
}

// PortAudio host API stream close function
static PaError CloseStream(PaStream *s)
{
StopStream(s);
PaScreenCaptureKitStream *stream = (PaScreenCaptureKitStream *)s;
PaUtil_FreeMemory(stream->ringBuffer.buffer);
PaUtil_FreeMemory(stream);
return paNoError;
}

static void Terminate(struct PaUtilHostApiRepresentation *hostApi)
{
// Free the host API representation
Expand Down Expand Up @@ -484,6 +547,9 @@ PaError PaMacScreenCapture_Initialize(PaUtilHostApiRepresentation **hostApi, PaH
IsStreamStopped, IsStreamActive, GetStreamTime, PaUtil_DummyGetCpuLoad, ReadStream,
PaUtil_DummyWrite, PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable);

PaUtil_InitializeStreamInterface(&paSck->callbackStreamInterface, CloseStream, StartStream, StopStream, AbortStream,
IsStreamStopped, IsStreamActive, GetStreamTime, PaUtil_DummyGetCpuLoad, PaUtil_DummyRead,
PaUtil_DummyWrite, PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable);
return result;

error:
Expand Down

0 comments on commit 28acd77

Please sign in to comment.