Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,33 @@ jobs:
run: swift build
- name: Run tests
run: swift test --parallel

build-ios:
# swift build compiles the macOS slice only. The codec dependencies ship as
# xcframeworks, so linking against their iOS slices is a separate risk and
# is not covered by the job above.
name: Build for iOS
runs-on: macOS-latest
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Build for iOS device
run: |
xcodebuild build \
-scheme AudioStreaming \
-destination 'generic/platform=iOS' \
-skipPackagePluginValidation

test-asan:
# The Ogg codec bridges are C: a ring buffer, raw pointer arithmetic, and
# callbacks driven by libvorbisfile/libopusfile. Address Sanitizer is what
# turns a silent overrun there into a failing test.
name: Test under Address Sanitizer
runs-on: macOS-latest
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run tests with ASan
run: swift test --sanitize=address
134 changes: 134 additions & 0 deletions AudioCodecs/OggRingBuffer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// OggRingBuffer.c
// AudioCodecs
//

#include "OggRingBuffer.h"

#include <stdlib.h>
#include <string.h>

size_t ogg_rb_write_locked(struct OggRingBuffer *s, const uint8_t *src, size_t len) {
size_t written = 0;
while (written < len) {
size_t free_space = s->cap - s->size;
if (free_space == 0) break;
size_t chunk = s->cap - s->tail;
if (chunk > len - written) chunk = len - written;
if (chunk > free_space) chunk = free_space;
memcpy(s->buf + s->tail, src + written, chunk);
s->tail = (s->tail + chunk) % s->cap;
s->size += chunk;
written += chunk;
}
return written;
}

size_t ogg_rb_read_locked(struct OggRingBuffer *s, uint8_t *dst, size_t len) {
size_t read = 0;
while (read < len && s->size > 0) {
size_t chunk = s->cap - s->head;
if (chunk > s->size) chunk = s->size;
if (chunk > len - read) chunk = len - read;
memcpy(dst + read, s->buf + s->head, chunk);
s->head = (s->head + chunk) % s->cap;
s->size -= chunk;
read += chunk;
}
return read;
}

struct OggRingBuffer *ogg_rb_create(size_t capacity_bytes) {
struct OggRingBuffer *s = (struct OggRingBuffer *)calloc(1, sizeof(struct OggRingBuffer));
if (!s) return NULL;
s->buf = (uint8_t *)malloc(capacity_bytes);
if (!s->buf) { free(s); return NULL; }
s->cap = capacity_bytes;
pthread_mutex_init(&s->m, NULL);
pthread_cond_init(&s->cv, NULL);
return s;
}

void ogg_rb_destroy(struct OggRingBuffer *s) {
if (!s) return;
pthread_mutex_destroy(&s->m);
pthread_cond_destroy(&s->cv);
free(s->buf);
free(s);
}

size_t ogg_rb_available(struct OggRingBuffer *s) {
if (!s) return 0;
pthread_mutex_lock(&s->m);
size_t sz = s->size;
pthread_mutex_unlock(&s->m);
return sz;
}

void ogg_rb_push(struct OggRingBuffer *s, const uint8_t *data, size_t len) {
if (!s || !data || len == 0) return;

pthread_mutex_lock(&s->m);
size_t written_total = 0;
while (written_total < len) {
size_t w = ogg_rb_write_locked(s, data + written_total, len - written_total);
written_total += w;
if (written_total < len) {
// Buffer full, wait for consumer to read
pthread_cond_wait(&s->cv, &s->m);
}
}
s->total_pushed += (long long)len;
pthread_cond_broadcast(&s->cv);
pthread_mutex_unlock(&s->m);
}

void ogg_rb_mark_eof(struct OggRingBuffer *s) {
if (!s) return;
pthread_mutex_lock(&s->m);
s->eof = 1;
pthread_cond_broadcast(&s->cv);
pthread_mutex_unlock(&s->m);
}

size_t ogg_rb_take(struct OggRingBuffer *s, uint8_t *dst, size_t len) {
if (!s || !dst || len == 0) return 0;

size_t got = 0;
pthread_mutex_lock(&s->m);
// Read what's available NOW - don't block waiting for more data.
while (got < len && s->size > 0) {
size_t chunk = ogg_rb_read_locked(s, dst + got, len - got);
if (chunk == 0) break;
s->pos += (long long)chunk;
got += chunk;
// Allow producer to push more
pthread_cond_broadcast(&s->cv);
}
pthread_mutex_unlock(&s->m);
return got;
}

long long ogg_rb_position(struct OggRingBuffer *s) {
if (!s) return -1;
pthread_mutex_lock(&s->m);
long long p = s->pos;
pthread_mutex_unlock(&s->m);
return p;
}

int ogg_rb_rewind_to(struct OggRingBuffer *s, long long saved_pos) {
if (!s) return 0;
int ok = 0;
pthread_mutex_lock(&s->m);
long long consumed = s->pos - saved_pos;
if (consumed > 0 && (size_t)consumed <= s->cap - s->size) {
s->head = (s->head + s->cap - ((size_t)consumed % s->cap)) % s->cap;
s->size += (size_t)consumed;
s->pos = saved_pos;
ok = 1;
}
pthread_cond_broadcast(&s->cv);
pthread_mutex_unlock(&s->m);
return ok;
}
61 changes: 61 additions & 0 deletions AudioCodecs/OggRingBuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// OggRingBuffer.h
// AudioCodecs
//
// Shared blocking ring buffer for the Ogg codec bridges.
//
// Both libvorbisfile and libopusfile are pull-based: they call a read callback
// when they want bytes. The streaming layer is push-based. This buffer bridges
// the two, blocking the producer when it fills and handing the consumer
// whatever is available without blocking.
//
// Internal to the AudioCodecs target — not part of the public umbrella header.
//

#ifndef OGG_RING_BUFFER_H
#define OGG_RING_BUFFER_H

#include <pthread.h>
#include <stddef.h>
#include <stdint.h>

// Fields are exposed rather than opaque because the codec bridges' seek
// callbacks reposition the buffer directly.
struct OggRingBuffer {
uint8_t *buf;
size_t cap, head, tail, size;
int eof;
long long pos; // Current read position in the stream
long long total_pushed; // Total bytes pushed into the buffer
pthread_mutex_t m;
pthread_cond_t cv;
};

struct OggRingBuffer *ogg_rb_create(size_t capacity_bytes);
void ogg_rb_destroy(struct OggRingBuffer *s);

// Bytes currently buffered.
size_t ogg_rb_available(struct OggRingBuffer *s);

// Appends `len` bytes, blocking while the buffer is full.
void ogg_rb_push(struct OggRingBuffer *s, const uint8_t *data, size_t len);

void ogg_rb_mark_eof(struct OggRingBuffer *s);

// Consumes up to `len` bytes into `dst` and advances the stream position.
// Returns what was available now; does not wait for more.
size_t ogg_rb_take(struct OggRingBuffer *s, uint8_t *dst, size_t len);

// Current stream position, for callers that need to rewind later.
long long ogg_rb_position(struct OggRingBuffer *s);

// Returns the buffer to `saved_pos`, undoing consumption since that point.
// Only valid while no producer has overwritten the reclaimed region.
// Returns 1 if the rewind happened, 0 if it was not safe.
int ogg_rb_rewind_to(struct OggRingBuffer *s, long long saved_pos);

// Unlocked primitives, for callers already holding the lock.
size_t ogg_rb_write_locked(struct OggRingBuffer *s, const uint8_t *src, size_t len);
size_t ogg_rb_read_locked(struct OggRingBuffer *s, uint8_t *dst, size_t len);

#endif // OGG_RING_BUFFER_H
Loading