Files
Dimitris C. 45178a9afc feature(Ogg): Adds OggVorbis playback support (#122)
* park

* park - working

* Adds support for Ogg Vorbis

* fixes

* fix rateNode issue

* Update README to remove M4A limitation note

Removed limitation note for non-optimized M4A files.

* Update iOS requirement from 13.0 to 15.0

* improvements

* removes debug message

* improves `isSeekable` property

* fix

* Refactors project use pure Package.swift and swift test

* Refactor GitHub Actions workflow for Swift package testing
2025-11-08 20:22:56 +02:00

322 lines
9.3 KiB
C

#include "include/VorbisFileBridge.h"
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <vorbis/vorbisfile.h>
struct VFRemoteStream {
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;
};
// Simple ring buffer write
static size_t rb_write(struct VFRemoteStream *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;
}
// Simple ring buffer read
static size_t rb_read(struct VFRemoteStream *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;
}
// Create a stream buffer
VFStreamRef VFStreamCreate(size_t capacity_bytes) {
struct VFRemoteStream *s = (struct VFRemoteStream *)calloc(1, sizeof(struct VFRemoteStream));
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;
}
// Destroy a stream buffer
void VFStreamDestroy(VFStreamRef sr) {
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
if (!s) return;
pthread_mutex_destroy(&s->m);
pthread_cond_destroy(&s->cv);
free(s->buf);
free(s);
}
// Get available bytes in the buffer
size_t VFStreamAvailableBytes(VFStreamRef sr) {
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
if (!s) return 0;
pthread_mutex_lock(&s->m);
size_t sz = s->size;
pthread_mutex_unlock(&s->m);
return sz;
}
// Push data into the stream
void VFStreamPush(VFStreamRef sr, const uint8_t *data, size_t len) {
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
if (!s || !data || len == 0) return;
pthread_mutex_lock(&s->m);
size_t written_total = 0;
while (written_total < len) {
size_t w = rb_write(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);
}
// Mark the stream as EOF
void VFStreamMarkEOF(VFStreamRef sr) {
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
if (!s) return;
pthread_mutex_lock(&s->m);
s->eof = 1;
pthread_cond_broadcast(&s->cv);
pthread_mutex_unlock(&s->m);
}
// libvorbisfile callbacks
// Read callback for libvorbisfile
static size_t read_cb(void *ptr, size_t size, size_t nmemb, void *datasrc) {
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
size_t want_bytes = size * nmemb;
size_t got = 0;
pthread_mutex_lock(&s->m);
// Read what's available NOW - don't block waiting for more data
while (got < want_bytes && s->size > 0) {
size_t chunk = rb_read(s, (uint8_t *)ptr + got, want_bytes - got);
s->pos += (long long)chunk;
got += chunk;
if (chunk == 0) break;
// Allow producer to push more
pthread_cond_broadcast(&s->cv);
}
// If nothing available and EOF, we're done
if (got == 0 && s->eof) {
// Return 0 to signal EOF to libvorbisfile
}
pthread_mutex_unlock(&s->m);
return size ? (got / size) : 0;
}
// Seek callback - seek within the ring buffer
static int seek_cb(void *datasrc, ogg_int64_t offset, int whence) {
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
if (!s) return -1;
pthread_mutex_lock(&s->m);
ogg_int64_t new_pos = 0;
switch (whence) {
case SEEK_SET:
new_pos = offset;
break;
case SEEK_CUR:
new_pos = s->pos + offset;
break;
case SEEK_END:
new_pos = s->total_pushed + offset;
break;
default:
pthread_mutex_unlock(&s->m);
return -1;
}
// Check if the new position is valid (within available data)
if (new_pos < 0 || new_pos > s->total_pushed) {
pthread_mutex_unlock(&s->m);
return -1; // Can't seek outside available data
}
// Calculate how much data we've already consumed from the buffer
long long already_consumed = s->pos - ((long long)s->total_pushed - (long long)s->size);
// Calculate the new head position
long long pos_delta = new_pos - s->pos;
// For forward seeks, we need to have enough data in the buffer
if (pos_delta > 0 && pos_delta > (long long)s->size) {
pthread_mutex_unlock(&s->m);
return -1; // Not enough data in buffer to seek forward
}
// For backward seeks, check if that data is still in the buffer
if (pos_delta < 0 && (-pos_delta) > already_consumed) {
pthread_mutex_unlock(&s->m);
return -1; // Data has been discarded from buffer
}
// Adjust head pointer
if (pos_delta >= 0) {
// Forward seek: advance head
s->head = (s->head + pos_delta) % s->cap;
s->size -= (size_t)pos_delta;
} else {
// Backward seek: rewind head
size_t rewind = (size_t)(-pos_delta);
if (s->head >= rewind) {
s->head -= rewind;
} else {
s->head = s->cap - (rewind - s->head);
}
s->size += rewind;
}
s->pos = new_pos;
pthread_mutex_unlock(&s->m);
return 0;
}
// Close callback - no-op
static int close_cb(void *datasrc) {
(void)datasrc;
return 0;
}
// Tell callback - return current position
static long tell_cb(void *datasrc) {
struct VFRemoteStream *s = (struct VFRemoteStream *)datasrc;
return (long)s->pos;
}
// Open a vorbis file using callbacks
int VFOpen(VFStreamRef sr, VFFileRef *out_vf) {
struct VFRemoteStream *s = (struct VFRemoteStream *)sr;
if (!s || !out_vf) return -1;
OggVorbis_File *vf = (OggVorbis_File *)malloc(sizeof(OggVorbis_File));
if (!vf) return -1;
ov_callbacks cbs;
cbs.read_func = read_cb;
cbs.seek_func = NULL; // Non-seekable streaming (seeking handled at Swift level)
cbs.close_func = close_cb;
cbs.tell_func = tell_cb;
int rc = ov_open_callbacks((void *)s, vf, NULL, 0, cbs);
if (rc < 0) { free(vf); return rc; }
*out_vf = (VFFileRef)vf;
return 0;
}
// Clear a vorbis file
void VFClear(VFFileRef fr) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf) return;
ov_clear(vf);
free(vf);
}
// Get stream info
int VFGetInfo(VFFileRef fr, VFStreamInfo *out_info) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf || !out_info) return -1;
vorbis_info const *info = ov_info(vf, -1);
if (!info) return -1;
out_info->sample_rate = info->rate;
out_info->channels = info->channels;
out_info->total_pcm_samples = ov_pcm_total(vf, -1);
out_info->duration_seconds = ov_time_total(vf, -1);
out_info->bitrate_nominal = info->bitrate_nominal;
return 0;
}
// Read deinterleaved float PCM frames
long VFReadFloat(VFFileRef fr, float ***out_pcm, int max_frames) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf || !out_pcm || max_frames <= 0) return -1;
int bitstream = 0;
long frames = ov_read_float(vf, out_pcm, max_frames, &bitstream);
// Returns: frames read (0 = EOF, <0 = error)
return frames;
}
// Read interleaved float PCM frames (legacy, less efficient)
long VFReadInterleavedFloat(VFFileRef fr, float *dst, int max_frames) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf || !dst || max_frames <= 0) return -1;
int bitstream = 0;
float **pcm = NULL;
long frames = ov_read_float(vf, &pcm, max_frames, &bitstream);
if (frames <= 0) return frames; // 0 EOF, <0 error/hole
vorbis_info const *info = ov_info(vf, -1);
int ch = info->channels;
// Interleave the PCM data
for (long f = 0; f < frames; ++f) {
for (int c = 0; c < ch; ++c) {
dst[f * ch + c] = pcm[c][f];
}
}
return frames;
}
// Seek to a specific time in seconds
int VFSeekTime(VFFileRef fr, double time_seconds) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf) return -1;
// Use ov_time_seek for time-based seeking
// Returns 0 on success, nonzero on failure
return ov_time_seek(vf, time_seconds);
}
// Check if the stream is seekable
int VFIsSeekable(VFFileRef fr) {
OggVorbis_File *vf = (OggVorbis_File *)fr;
if (!vf) return 0;
// Returns nonzero if the stream is seekable
return ov_seekable(vf);
}