8 Commits

Author SHA1 Message Date
hans 7de84cbf30 final linux version 2026-05-11 09:21:12 +02:00
hans fc4b6f8ced MInimal ffmpeg implementation 2026-05-10 01:02:54 +02:00
hans b8996d55e4 updated version after bug hunt ao_playasync.c 2026-05-07 09:04:29 +02:00
hans 977eb5a456 Bug hunting ao_playasync 2026-05-07 08:32:41 +02:00
hans a358a8593d mac os x instructions (brew) 2026-05-06 17:29:53 +02:00
hans f4e2a6aa31 changes for Mac OS X 2026-05-06 17:26:55 +02:00
hans 51a0138877 one extra linefeet 2026-05-04 17:12:30 +02:00
hans 4134cf0549 ffi_version added 2026-05-04 13:11:50 +02:00
20 changed files with 1381 additions and 229 deletions
+3
View File
@@ -20,5 +20,8 @@ build
lib/linux-x86_64 lib/linux-x86_64
build-ffmpeg build-ffmpeg
.DS_Store
*.user
/ffmpeg-audio/.qtcreator /ffmpeg-audio/.qtcreator
+2
View File
@@ -11,9 +11,11 @@ all:
install: all install: all
mkdir -p lib/$(SUBDIR) mkdir -p lib/$(SUBDIR)
@echo "copying from src/$(SUBDIR) to lib/$(SUBDIR)" @echo "copying from src/$(SUBDIR) to lib/$(SUBDIR)"
exit
(cd src/$(SUBDIR);tar cf - . ) | (cd lib/$(SUBDIR); tar xvf - ) (cd src/$(SUBDIR);tar cf - . ) | (cd lib/$(SUBDIR); tar xvf - )
FILES=`ls build/*.so build-ffmpeg/*.so` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi FILES=`ls build/*.so build-ffmpeg/*.so` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi
FILES=`ls build/*.dll build-ffmpeg/*.dll` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi FILES=`ls build/*.dll build-ffmpeg/*.dll` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi
FILES=`ls build/*.dylib build-ffmpeg/*.dylib` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$(SUBDIR); fi
test: install test: install
cp lib/linux-x86_64/*.so ~/.local/share/racket/racket-sound-lib/linux-x86_64 cp lib/linux-x86_64/*.so ~/.local/share/racket/racket-sound-lib/linux-x86_64
+12 -1
View File
@@ -1,3 +1,14 @@
# racket-sound-lib # racket-sound-lib
Binary packages for the racket-sound FFI binding Binary packages for the racket-sound FFI binding
## Using on Mac OS X
Make sure you have libao, libFLAC, mpg123 and ffmpeg-full installed using brew.
% brew install libao
% brew install flac
% brew install mpg123
% brew install ffmpeg-full
+8
View File
@@ -5,6 +5,8 @@ project(ao-play-async LANGUAGES C)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(ao-play-async SHARED add_library(ao-play-async SHARED
ao_playasync.c ao_playasync.c
ao_playasync.h ao_playasync.h
@@ -16,6 +18,12 @@ if(WIN32)
target_link_directories(ao-play-async PRIVATE ../lib/windows-x86_64) target_link_directories(ao-play-async PRIVATE ../lib/windows-x86_64)
endif() endif()
if(APPLE)
set(AO_HOME /opt/homebrew)
include_directories(${AO_HOME}/include)
target_link_directories(ao-play-async PRIVATE ${AO_HOME}/lib)
endif()
if(WIN32) if(WIN32)
target_link_libraries(ao-play-async PRIVATE libao-1.2.2 winmm.lib ksuser.lib) target_link_libraries(ao-play-async PRIVATE libao-1.2.2 winmm.lib ksuser.lib)
else() else()
+398 -227
View File
@@ -1,46 +1,29 @@
#include "ao_playasync.h" #include "ao_playasync.h"
#include "../ffi_version.h"
#ifdef WIN32 #define TRUE (1==1)
#define FALSE (1==0)
#if defined(_WIN32) || defined(WIN32)
#define AO_ASYNC_WINDOWS
#include <windows.h> #include <windows.h>
#define USE_WINDOWS_THREADS #define USE_WINDOWS_THREADS
#define sleep_ms(ms) Sleep(ms) #define sleep_ms(ms) Sleep(ms)
#else #else
#ifdef __APPLE__
#define AO_ASYNC_APPLE
#define USE_DISPATCH
#include <time.h>
#define sleep_ms(ms) msleep(ms)
#else
#define USE_PTHREADS #define USE_PTHREADS
#include <time.h>
#include <sched.h>
#define sleep_ms(ms) usleep(ms * 1000) #define sleep_ms(ms) usleep(ms * 1000)
#endif #endif
#ifdef USE_WINDOWS_THREADS
#define MUTEX_LOCK(m) WaitForSingleObject(m, INFINITE)
#define MUTEX_UNLOCK(m) ReleaseMutex(m)
#define SEM_WAIT(sem, ms) (WaitForSingleObject(sem, ms) == WAIT_OBJECT_0)
#define SEM_TRYWAIT(sem) (WaitForSingleObject(sem, 0) == WAIT_OBJECT_0)
#define SEM_POST(sem) ReleaseSemaphore(sem, 1, NULL)
#define YIELD() sleep_ms(5)
#endif #endif
#ifdef USE_PTHREADS #if defined(USE_PTHREADS) || defined(USE_DISPATCH)
#include <pthread.h>
#include <semaphore.h>
#include <sched.h>
#define TIME_NS_IN_MSEC 1000000ULL
static void makeSemTimeoutTime(struct timespec *ts, int ms) {
clock_gettime(CLOCK_REALTIME, ts);
ts->tv_sec += ms / 1000;
ts->tv_nsec += (ms % 1000) * TIME_NS_IN_MSEC;
if (ts->tv_nsec >= 1000000000L) {
ts->tv_sec++;
ts->tv_nsec = ts->tv_nsec - 1000000000L;
}
}
static int _SEM_WAIT(sem_t *sem, int ms)
{
struct timespec ts;
makeSemTimeoutTime(&ts, ms);
int r = sem_timedwait(sem, &ts);
return (r == 0);
}
static int msleep(long msec) static int msleep(long msec)
{ {
@@ -62,33 +45,104 @@
{ {
msleep(5); msleep(5);
} }
#endif
#ifdef USE_DISPATCH
#include <pthread.h>
#include <dispatch/dispatch.h>
#include <stdint.h>
static inline dispatch_time_t makeDispatchTimeoutTime(int ms)
{
return dispatch_time(DISPATCH_TIME_NOW,
(int64_t)ms * NSEC_PER_MSEC);
}
static int _SEM_WAIT(dispatch_semaphore_t sem, int ms)
{
return dispatch_semaphore_wait(sem, makeDispatchTimeoutTime(ms)) == 0;
}
#define MUTEX_LOCK(m) pthread_mutex_lock(&m)
#define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m)
#define SEM_WAIT(sem, ms) _SEM_WAIT(sem, ms)
#define SEM_TRYWAIT(sem) (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW) == 0)
#define SEM_WAIT_INFINITE(sem) (dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) == 0)
#define SEM_POST(sem) dispatch_semaphore_signal(sem)
#define YIELD() sleep_ms(5)
#endif
#ifdef USE_WINDOWS_THREADS
#define MUTEX_LOCK(m) WaitForSingleObject(m, INFINITE)
#define MUTEX_UNLOCK(m) ReleaseMutex(m)
#define SEM_WAIT(sem, ms) (WaitForSingleObject(sem, ms) == WAIT_OBJECT_0)
#define SEM_TRYWAIT(sem) (WaitForSingleObject(sem, 0) == WAIT_OBJECT_0)
#define SEM_WAIT_INFINITE(sem) (WaitForSingleObject(sem, INFINITE) == WAIT_OBJECT_0)
#define SEM_POST(sem) ReleaseSemaphore(sem, 1, NULL)
#define YIELD() sleep_ms(5)
#endif
#ifdef USE_PTHREADS
#include <pthread.h>
#include <semaphore.h>
#define TIME_NS_IN_MSEC 1000000ULL
static void makeSemTimeoutTime(struct timespec *ts, int ms) {
clock_gettime(CLOCK_REALTIME, ts);
ts->tv_sec += ms / 1000;
ts->tv_nsec += (ms % 1000) * TIME_NS_IN_MSEC;
if (ts->tv_nsec >= 1000000000L) {
ts->tv_sec++;
ts->tv_nsec = ts->tv_nsec - 1000000000L;
}
}
static int _SEM_WAIT(sem_t *sem, int ms)
{
struct timespec ts;
makeSemTimeoutTime(&ts, ms);
int r = sem_timedwait(sem, &ts);
return (r == 0);
}
#define MUTEX_LOCK(m) pthread_mutex_lock(&m) #define MUTEX_LOCK(m) pthread_mutex_lock(&m)
#define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m) #define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m)
#define SEM_WAIT(sem, ms) _SEM_WAIT(&sem, ms) #define SEM_WAIT(sem, ms) _SEM_WAIT(&sem, ms)
#define SEM_WAIT_INFINITE(sem) (sem_wait(&sem) == 0)
#define SEM_TRYWAIT(sem) (sem_trywait(&sem) == 0) #define SEM_TRYWAIT(sem) (sem_trywait(&sem) == 0)
#define SEM_POST(sem) sem_post(&sem) #define SEM_POST(sem) sem_post(&sem)
#define YIELD() yield() #define YIELD() yield()
#endif #endif
#ifndef WIN32 #ifndef AO_ASYNC_WINDOWS
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifndef AO_ASYNC_APPLE
#include <malloc.h> #include <malloc.h>
#endif
#include <string.h> #include <string.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#include <ao/ao.h> #include <ao/ao.h>
#include <errno.h>
#ifdef AO_ASYNC_WINDOWS
#else
#include <dlfcn.h>
#endif
/**************************************************************************
* Internal data structures
**************************************************************************/
typedef enum { typedef enum {
PLAY = 1, PLAY = 1,
STOP = 2 STOP = 2
} Command_t; } Command_t;
typedef struct _queue_ { typedef struct _queue_ {
Command_t command; Command_t command;
void *buf; void *buf;
@@ -107,6 +161,7 @@ typedef struct {
Queue_t *last_frame; Queue_t *last_frame;
int paused; int paused;
int stopped;
ao_device *ao_device; ao_device *ao_device;
int requested_bits_per_sample; int requested_bits_per_sample;
@@ -117,26 +172,49 @@ typedef struct {
#ifdef USE_WINDOWS_THREADS #ifdef USE_WINDOWS_THREADS
HANDLE mutex; HANDLE mutex;
HANDLE pause_mutex;
HANDLE clear_mutex; HANDLE clear_mutex;
HANDLE thread; HANDLE thread;
DWORD thread_id; DWORD thread_id;
HANDLE queue_sem; HANDLE queue_sem;
HANDLE pause_sem;
#endif #endif
#ifdef USE_PTHREADS #if defined(USE_PTHREADS) || defined(USE_DISPATCH)
pthread_mutex_t mutex; pthread_mutex_t mutex;
pthread_mutex_t pause_mutex;
pthread_mutex_t clear_mutex; pthread_mutex_t clear_mutex;
pthread_t thread; pthread_t thread;
#ifdef USE_PTHREADS
sem_t queue_sem; sem_t queue_sem;
sem_t pause_sem;
#endif
#ifdef USE_DISPATCH
dispatch_semaphore_t queue_sem;
dispatch_semaphore_t pause_sem;
#endif
#endif #endif
double at_second; double at_second;
double music_duration; double music_duration;
int at_music_id; int at_music_id;
int buf_size; int buf_size;
int volume_in_10000; // volume in 100000 steps, i.e. 100000 equals 100% int volume_in_10000; // volume in 10000 steps, i.e. 10000 equals 100%
} AO_Handle; } AO_Handle;
/**************************************************************************
* Forward function declarations
**************************************************************************/
static void del_elem(Queue_t *q);
/**************************************************************************
* Support functions, for internal state, elements and queuing
**************************************************************************/
static inline int stopped(AO_Handle *h, int lock_mutex)
{
if (lock_mutex) MUTEX_LOCK(h->mutex);
int stopped = h->stopped;
if (lock_mutex) MUTEX_UNLOCK(h->mutex);
return stopped;
}
static Queue_t *get(AO_Handle *h, int ms_wait) static Queue_t *get(AO_Handle *h, int ms_wait)
{ {
@@ -164,9 +242,20 @@ static Queue_t *get(AO_Handle *h, int ms_wait)
return q; return q;
} }
static void add(AO_Handle *h, Queue_t *elem) static void add(AO_Handle *h, Queue_t *elem, int in_clear)
{ {
if (!in_clear) {
MUTEX_LOCK(h->clear_mutex);
}
MUTEX_LOCK(h->mutex); MUTEX_LOCK(h->mutex);
if (!in_clear && stopped(h, FALSE)) {
MUTEX_UNLOCK(h->mutex);
MUTEX_UNLOCK(h->clear_mutex);
del_elem(elem);
return;
}
if (h->last_frame == NULL) { if (h->last_frame == NULL) {
h->play_queue = elem; h->play_queue = elem;
elem->next = NULL; elem->next = NULL;
@@ -180,17 +269,34 @@ static void add(AO_Handle *h, Queue_t *elem)
} }
h->buf_size += elem->buflen; h->buf_size += elem->buflen;
SEM_POST(h->queue_sem); SEM_POST(h->queue_sem);
MUTEX_UNLOCK(h->mutex); MUTEX_UNLOCK(h->mutex);
if (!in_clear) {
MUTEX_UNLOCK(h->clear_mutex);
}
} }
static Queue_t *new_elem(int command, int music_id, double at_second, double music_duration, int buf_len, void *buf) static Queue_t *new_elem(int command, int music_id, double at_second, double music_duration, int buf_len, void *buf)
{ {
Queue_t *q = (Queue_t *) malloc(sizeof(Queue_t)); Queue_t *q = (Queue_t *) malloc(sizeof(Queue_t));
if (q == NULL) {
fprintf(stderr, "new_elem: Cannot allocate Queue Element!\n");
return NULL;
}
void *new_buf; void *new_buf;
if (buf_len != 0) { if (buf_len > 0) {
new_buf = (void *) malloc(buf_len); new_buf = (void *) malloc(buf_len);
memcpy(new_buf, buf, buf_len); if (new_buf != NULL) {
memcpy(new_buf, buf, buf_len);
} else {
fprintf(stderr, "new_elem: Cannot allocate memory of size %d\n", buf_len);
buf_len = 0;
free(q);
return NULL;
}
} else { } else {
new_buf = NULL; new_buf = NULL;
} }
@@ -213,7 +319,7 @@ static void del_elem(Queue_t *q)
free(q); free(q);
} }
static void clear(AO_Handle *h) static void clear(AO_Handle *h, int do_stop)
{ {
//fprintf(stderr, "Wait for clear mutex\n"); //fprintf(stderr, "Wait for clear mutex\n");
MUTEX_LOCK(h->clear_mutex); MUTEX_LOCK(h->clear_mutex);
@@ -226,59 +332,173 @@ static void clear(AO_Handle *h)
q = get(h, 0); q = get(h, 0);
} }
fprintf(stderr, "%d elements cleared\n", count); fprintf(stderr, "%d elements cleared\n", count);
if (do_stop) {
Queue_t *q = new_elem(STOP, 0, 0.0, 0.0, 0, NULL);
if (q == NULL) {
fprintf(stderr, "Unexpected! Cannot allocate STOP element, aborting\n");
abort();
}
add(h, q, TRUE);
}
MUTEX_UNLOCK(h->clear_mutex); MUTEX_UNLOCK(h->clear_mutex);
} }
/**************************************************************************
* Calculations on samples
**************************************************************************/
static int inline littleEndian() static int inline littleEndian()
{ {
int n = 1; int n = 1;
return (*(char *)&n) == 1; return (*(char *)&n) == 1;
} }
static void inline adjustVolume(AO_Handle *handle, char *_buf, int buf_size, int volume_in_10000) static inline int ao_format_little_endian(int endianess)
{ {
int bytes_per_sample = (handle->dev_bits_per_sample >> 3); if (endianess == AO_FMT_LITTLE) return TRUE;
register int endianess = handle->dev_endianess; if (endianess == AO_FMT_BIG) return FALSE;
register unsigned char *buf = (unsigned char *) _buf;
register int i; /*
register long long sample; AO_FMT_NATIVE, or anything treated as native.
register int k; */
return littleEndian();
}
int little_endian = (endianess == AO_FMT_LITTLE); static inline int32_t read_sample(unsigned char *mem, int req_bytes, int little_endian)
if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian(); {
uint32_t v = 0;
for(i = 0; i < buf_size; i += bytes_per_sample) { for (int i = 0; i < req_bytes; i++) {
if (little_endian) { int idx = little_endian ? i : (req_bytes - i - 1);
sample = buf[bytes_per_sample + i - 1] > 127 ? -1 : 0; v |= ((uint32_t) mem[idx]) << (8 * i);
for(k = bytes_per_sample + i - 1; k >= i; k--) { }
sample <<= 8;
sample |= buf[k]; if (req_bytes < 4) {
} int bits = req_bytes * 8;
} else { uint32_t sign = 1u << (bits - 1);
sample = (buf[i] > 127) ? -1 : 0; v = (v ^ sign) - sign;
for(k = i; k < (i + bytes_per_sample); k++) { }
sample <<= 8;
sample += buf[k]; return (int32_t) v;
} }
}
sample *= volume_in_10000; static inline void store_sample(unsigned char *dst, int32_t in_sample, int out_bytes, int is_little_endian)
sample /= 10000; {
if (little_endian) { uint32_t sample = (uint32_t) in_sample;
for(k = i; k < (i + bytes_per_sample); k++) { for (int i = 0; i < out_bytes; i++) {
buf[k] = sample&0xff; int idx = is_little_endian ? i : (out_bytes - i - 1);
sample >>= 8; dst[idx] = sample & 0xff;
} sample >>= 8;
} else {
for(k = i + bytes_per_sample - 1; k >= i; k--) {
buf[k] = sample&0xff;
sample >>= 8;
}
}
} }
} }
#ifdef USE_PTHREADS /**** Volume adjustment */
static inline int32_t limit_sample(int64_t sample, int bits)
{
int64_t min = -((int64_t)1 << (bits - 1));
int64_t max = ((int64_t)1 << (bits - 1)) - 1;
if (sample < min) return (int32_t) min;
if (sample > max) return (int32_t) max;
return (int32_t) sample;
}
static inline void adjustVolume(AO_Handle *handle,
char *_buf,
int buf_size,
int volume_in_10000)
{
int bytes_per_sample = handle->dev_bits_per_sample >> 3;
int endianess = handle->dev_endianess;
int little_endian = (endianess == AO_FMT_LITTLE);
if (!little_endian && endianess == AO_FMT_NATIVE) {
little_endian = littleEndian();
}
unsigned char *buf = (unsigned char *) _buf;
for (int i = 0; i + bytes_per_sample <= buf_size; i += bytes_per_sample) {
int32_t sample = read_sample(buf + i, bytes_per_sample, little_endian);
int64_t scaled = ((int64_t) sample * volume_in_10000) / 10000;
int32_t clipped = limit_sample(scaled, handle->dev_bits_per_sample);
store_sample(buf + i, clipped, bytes_per_sample, little_endian);
}
}
/**** Bit conversion from input samples to device bits */
static inline int32_t convert_bits(int32_t sample, int req_bits, int out_bits)
{
if (req_bits == out_bits) return sample;
int shift = req_bits > out_bits
? req_bits - out_bits
: out_bits - req_bits;
if (req_bits > out_bits) {
return sample / (1 << shift);
} else {
return (int32_t)((int64_t)sample * ((int64_t)1 << shift));
}
}
static void *convert_req_to_real(AO_Handle *h, void *mem, int mem_size, BufferInfo_t *info, int *out_size)
{
if (mem_size <= 0) {
*out_size = 0;
return NULL;
}
int output_little_endian = ao_format_little_endian(h->dev_endianess);
int input_little_endian = ao_format_little_endian(info->endiannes);
int requested_bits = info->sample_bits;
int output_bits = h->dev_bits_per_sample;
int req_bytes = (requested_bits / 8);
int out_bytes = (output_bits / 8);
int samples = (mem_size / req_bytes);
unsigned char *buf_out = (unsigned char *) malloc((samples + 1) * out_bytes);
if (buf_out == NULL) {
fprintf(stderr, "Allocation of output buffer of %d samples of %d bits gives NULL", samples, output_bits);
*out_size = 0;
return NULL;
}
if (requested_bits == output_bits && input_little_endian == output_little_endian) {
*out_size = mem_size;
memcpy(buf_out, mem, mem_size);
} else {
// convert samples.
int sample;
unsigned char *p_in = (unsigned char *) mem;
unsigned char *p_out = buf_out;
for(sample = 0; sample < samples; sample++, p_in += req_bytes, p_out += out_bytes) {
register int32_t smpl;
smpl = read_sample(p_in, req_bytes, input_little_endian);
int32_t out_smpl = convert_bits(smpl, requested_bits, output_bits);
store_sample(p_out, out_smpl, out_bytes, output_little_endian);
}
*out_size = samples * out_bytes;
}
return buf_out;
}
/**************************************************************************
* Play thread of queues samples
**************************************************************************/
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
static void *run(void *arg) static void *run(void *arg)
#endif #endif
#ifdef USE_WINDOWS_THREADS #ifdef USE_WINDOWS_THREADS
@@ -289,25 +509,36 @@ static DWORD run(LPVOID arg)
int go_on = (1 == 1); int go_on = (1 == 1);
while(go_on) { while(go_on) {
MUTEX_LOCK(handle->pause_mutex); SEM_WAIT_INFINITE(handle->pause_sem);
MUTEX_UNLOCK(handle->pause_mutex); SEM_POST(handle->pause_sem);
MUTEX_LOCK(handle->clear_mutex); MUTEX_LOCK(handle->clear_mutex);
Queue_t *q = get(handle, 250); Queue_t *q = get(handle, 250);
MUTEX_UNLOCK(handle->clear_mutex); MUTEX_UNLOCK(handle->clear_mutex);
if (q != NULL) { if (q != NULL) {
MUTEX_LOCK(handle->mutex);
handle->at_second = q->at_second; handle->at_second = q->at_second;
handle->music_duration = q->music_duration; handle->music_duration = q->music_duration;
handle->at_music_id = q->music_id; handle->at_music_id = q->music_id;
int volume = handle->volume_in_10000;
MUTEX_UNLOCK(handle->mutex);
if (handle->volume_in_10000 != 10000) { if (volume != 10000 && q->command == PLAY) {
// adjust volume // adjust volume
adjustVolume(handle, q->buf, q->buflen, handle->volume_in_10000); adjustVolume(handle, q->buf, q->buflen, volume);
} }
if (q->command == STOP) { if (q->command == STOP) {
go_on = (1 == 0); go_on = FALSE;
} else { } else {
ao_play(handle->ao_device, (char *) q->buf, q->buflen); if (!ao_play(handle->ao_device, (char *) q->buf, q->buflen)) {
fprintf(stderr, "Unexpected, ao_play returns 0 --> ao device must be closed\n");
fprintf(stderr, "Stopping play loop\n");
go_on = FALSE;
MUTEX_LOCK(handle->mutex);
handle->stopped = TRUE;
MUTEX_UNLOCK(handle->mutex);
}
} }
del_elem(q); del_elem(q);
@@ -316,7 +547,7 @@ static DWORD run(LPVOID arg)
} }
} }
#ifdef USE_PTHREADS #if defined(USE_PTHREADS) || defined(USE_DISPATCH)
return NULL; return NULL;
#endif #endif
#ifdef USE_WINDOWS_THREADS #ifdef USE_WINDOWS_THREADS
@@ -324,17 +555,20 @@ static DWORD run(LPVOID arg)
#endif #endif
} }
/**************************************************************************
* External API
**************************************************************************/
/**** ffi version for ao_play_async and ffmpeg_audio */
int ao_async_version() int ao_async_version()
{ {
return VERSION; return ffi_version();
} }
#ifdef _WIN32 /**** ao initialization and shutdown */
#else
#include <dlfcn.h>
#endif
#ifdef _WIN32 #ifdef AO_ASYNC_WINDOWS
static void at_exit_shutdown_ao(void) static void at_exit_shutdown_ao(void)
#else #else
static void at_exit_shutdown_ao() static void at_exit_shutdown_ao()
@@ -353,6 +587,7 @@ static void init_ao()
} }
} }
/**** Opening ao_devices */
static ao_device *try_open_device(int bits, int rate, int channels, int byte_format, static ao_device *try_open_device(int bits, int rate, int channels, int byte_format,
const char *wav_file_output, const char *wav_file_output,
@@ -392,100 +627,15 @@ static ao_device *try_open_device(int bits, int rate, int channels, int byte_for
return NULL; return NULL;
} }
static inline int32_t convert_bits(int32_t sample, int req_bits, int out_bits)
{
if (req_bits > out_bits) {
sample >>= (req_bits - out_bits);
} else if (req_bits < out_bits) {
sample <<= (out_bits - req_bits);
}
return sample;
}
static inline int32_t read_sample(unsigned char *mem, int req_bytes, int little_endian)
{
uint32_t v = 0;
for (int i = 0; i < req_bytes; i++) {
int idx = little_endian ? i : (req_bytes - i - 1);
v |= ((uint32_t) mem[idx]) << (8 * i);
}
if (req_bytes < 4) {
int bits = req_bytes * 8;
uint32_t sign = 1u << (bits - 1);
v = (v ^ sign) - sign;
}
return (int32_t) v;
}
static inline void store_sample(unsigned char *dst, int32_t sample, int out_bytes, int is_little_endian)
{
for (int i = 0; i < out_bytes; i++) {
int idx = is_little_endian ? i : (out_bytes - i - 1);
dst[idx] = sample & 0xff;
sample >>= 8;
}
}
static void *convert_req_to_real(AO_Handle *h, void *mem, int mem_size, BufferInfo_t *info, int *out_size)
{
int endianess = h->dev_endianess;
int little_endian = (endianess == AO_FMT_LITTLE);
if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian();
int requested_bits = h->requested_bits_per_sample;
int output_bits = h->dev_bits_per_sample;
int req_bytes = (requested_bits / 8);
int out_bytes = (output_bits / 8);
int samples = (mem_size / req_bytes);
unsigned char *buf_out = (unsigned char *) malloc(samples * out_bytes);
if (buf_out == NULL) {
fprintf(stderr, "Allocation of output buffer of %d samples of %d bits gives NULL", samples, output_bits);
*out_size = 0;
return NULL;
}
if (requested_bits == output_bits) {
*out_size = mem_size;
memcpy(buf_out, mem, mem_size);
} else {
// convert samples.
int sample;
unsigned char *p_in = (unsigned char *) mem;
unsigned char *p_out = buf_out;
for(sample = 0; sample < samples; sample++, p_in += req_bytes, p_out += out_bytes) {
register int32_t smpl;
smpl = read_sample(p_in, req_bytes, little_endian);
int32_t out_smpl = convert_bits(smpl, requested_bits, output_bits);
store_sample(p_out, out_smpl, out_bytes, little_endian);
}
*out_size = samples * out_bytes;
}
return buf_out;
}
void *ao_create_async(int bits, int rate, int channels, int byte_format, const char *wav_file_output) void *ao_create_async(int bits, int rate, int channels, int byte_format, const char *wav_file_output)
{ {
init_ao(); init_ao();
AO_Handle *handle = (AO_Handle *) malloc(sizeof(AO_Handle)); AO_Handle *handle = (AO_Handle *) malloc(sizeof(AO_Handle));
if (handle == NULL) {
ao_sample_format fmt; fprintf(stderr, "Cannot allocate ao_handle!\n");
fmt.bits = bits; return NULL;
fmt.rate = rate; }
fmt.byte_format = byte_format;
fmt.channels = channels;
fmt.matrix = NULL;
int opened_bits = 0; int opened_bits = 0;
ao_device *dev = try_open_device(bits, rate, channels, byte_format, wav_file_output, &opened_bits); ao_device *dev = try_open_device(bits, rate, channels, byte_format, wav_file_output, &opened_bits);
@@ -508,39 +658,40 @@ void *ao_create_async(int bits, int rate, int channels, int byte_format, const c
handle->last_frame = NULL; handle->last_frame = NULL;
handle->at_second = -1; handle->at_second = -1;
handle->at_music_id = -1; handle->at_music_id = -1;
handle->music_duration = 0;
handle->buf_size = 0; handle->buf_size = 0;
handle->paused = (1 == 0); handle->paused = FALSE;
handle->stopped = FALSE;
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
pthread_mutex_init(&handle->clear_mutex, NULL);
pthread_mutex_init(&handle->mutex, NULL);
#ifdef USE_PTHREADS #ifdef USE_PTHREADS
pthread_mutex_t p = PTHREAD_MUTEX_INITIALIZER;
handle->pause_mutex = p;
pthread_mutex_t c = PTHREAD_MUTEX_INITIALIZER;
handle->clear_mutex = c;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
handle->mutex = m;
sem_init(&handle->queue_sem, 0, 0); sem_init(&handle->queue_sem, 0, 0);
sem_init(&handle->pause_sem, 0, 1);
#endif
#ifdef USE_DISPATCH
handle->queue_sem = dispatch_semaphore_create(0);
handle->pause_sem = dispatch_semaphore_create(1);
#endif
pthread_create(&handle->thread, NULL, run, handle); pthread_create(&handle->thread, NULL, run, handle);
#endif #endif
#ifdef USE_WINDOWS_THREADS #ifdef USE_WINDOWS_THREADS
handle->mutex = CreateMutex(NULL, // default security attributes handle->mutex = CreateMutex(NULL, // default security attributes
FALSE, // initially not owned FALSE, // initially not owned
NULL); NULL);
handle->pause_mutex = CreateMutex(NULL, // default security attributes
FALSE, // initially not owned
NULL);
handle->clear_mutex = CreateMutex(NULL, handle->clear_mutex = CreateMutex(NULL,
FALSE, FALSE,
NULL); NULL);
handle->queue_sem = CreateSemaphore(NULL, 0, 1000000, NULL); handle->queue_sem = CreateSemaphore(NULL, 0, 1000000, NULL);
handle->pause_sem = CreateSemaphore(NULL, 1, 1000000, NULL);
handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id); handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id);
#endif #endif
MUTEX_UNLOCK(handle->pause_mutex);
MUTEX_UNLOCK(handle->clear_mutex);
return (void *) handle; return (void *) handle;
} }
@@ -549,26 +700,47 @@ void ao_stop_async(void *ao_handle)
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
fprintf(stderr, "stopping ao_async, calling clear\n"); fprintf(stderr, "stopping ao_async, calling clear\n");
clear(h); MUTEX_LOCK(h->mutex);
h->stopped = TRUE;
MUTEX_UNLOCK(h->mutex);
clear(h, TRUE);
fprintf(stderr, "queue cleared\n"); fprintf(stderr, "queue cleared\n");
MUTEX_LOCK(h->mutex);
if (h->paused) { if (h->paused) {
MUTEX_UNLOCK(h->pause_mutex); SEM_POST(h->pause_sem);
} }
Queue_t *q = new_elem(STOP, 0, 0.0, 0.0, 0, NULL); MUTEX_UNLOCK(h->mutex);
add(h, q);
fprintf(stderr, "stop command queued\n"); fprintf(stderr, "stop command queued\n");
#ifdef USE_PTHREADS #if defined(USE_PTHREADS) || defined(USE_DISPATCH)
void *retval; void *retval;
pthread_join(h->thread, &retval); pthread_join(h->thread, &retval);
#ifdef USE_PTHREADS
sem_destroy(&h->queue_sem);
sem_destroy(&h->pause_sem);
#endif
#ifdef USE_DISPATCH
dispatch_release(h->queue_sem);
dispatch_release(h->pause_sem);
#endif
pthread_mutex_destroy(&h->clear_mutex);
pthread_mutex_destroy(&h->mutex);
#endif #endif
#ifdef USE_WINDOWS_THREADS #ifdef USE_WINDOWS_THREADS
WaitForSingleObject(h->thread, INFINITE); WaitForSingleObject(h->thread, INFINITE);
CloseHandle(h->thread); CloseHandle(h->thread);
CloseHandle(h->mutex); CloseHandle(h->mutex);
CloseHandle(h->pause_sem);
CloseHandle(h->queue_sem);
CloseHandle(h->clear_mutex);
#endif #endif
ao_close(h->ao_device); ao_close(h->ao_device);
@@ -579,51 +751,44 @@ void ao_stop_async(void *ao_handle)
fprintf(stderr, "async handle freed\n"); fprintf(stderr, "async handle freed\n");
} }
#define AO_FMT_LITTLE 1
#define AO_FMT_BIG 2
#define AO_FMT_NATIVE 4
static inline void make_sample_bytes(int32_t sample, int bytes_per_sample, int big_endian, unsigned char b[4]) /**** play a sample */
{
int i;
for (i = 0; i < bytes_per_sample; i++) {
b[i] = sample&0xff;
sample >>= 8;
}
if (big_endian) {
unsigned char b1[4] = { 0, 0, 0, 0 };
for(i = 0; i < bytes_per_sample; i++) {
b1[bytes_per_sample - i - 1] = b[i];
}
for(i = 0; i < bytes_per_sample; i++) {
b[i] = b1[i];
}
}
}
void ao_play_async(void *ao_handle, int music_id, double at_second, double music_duration, int buf_size, void *mem, BufferInfo_t info) void ao_play_async(void *ao_handle, int music_id, double at_second, double music_duration, int buf_size, void *mem, BufferInfo_t info)
{ {
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
if (stopped(h, TRUE)) { return; }
Queue_t *q = NULL; Queue_t *q = NULL;
int ao_size = 0; int ao_size = 0;
void *ao_mem = convert_req_to_real(ao_handle, mem, buf_size, &info, &ao_size); void *ao_mem = convert_req_to_real(ao_handle, mem, buf_size, &info, &ao_size);
if (ao_mem == NULL || ao_size <= 0) {
if (ao_mem != NULL) { free(ao_mem); }
return;
}
q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem); q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem);
free(ao_mem); free(ao_mem);
add(h, q); if (q != NULL && q->buf != NULL) { // memory error has already been given.
add(h, q, FALSE);
}
} }
/**** clear the sample queue */
void ao_clear_async(void *ao_handle) void ao_clear_async(void *ao_handle)
{ {
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
clear(h); if (stopped(h, TRUE)) { return; }
clear(h, FALSE);
} }
/**** Information on the current samples being played by the player thread */
double ao_is_at_second_async(void *ao_handle) double ao_is_at_second_async(void *ao_handle)
{ {
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
@@ -660,6 +825,8 @@ int ao_bufsize_async(void *ao_handle)
return s; return s;
} }
/**** Volume related functions */
void ao_set_volume_async(void *ao_handle, double percentage) void ao_set_volume_async(void *ao_handle, double percentage)
{ {
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
@@ -682,6 +849,8 @@ double ao_volume_async(void *ao_handle)
return volume; return volume;
} }
/**** Pausing playback */
void ao_pause_async(void *ao_handle, int paused) void ao_pause_async(void *ao_handle, int paused)
{ {
AO_Handle *h = (AO_Handle *) ao_handle; AO_Handle *h = (AO_Handle *) ao_handle;
@@ -690,23 +859,25 @@ void ao_pause_async(void *ao_handle, int paused)
if (h->paused) { if (h->paused) {
if (!paused) { if (!paused) {
h->paused = paused; h->paused = paused;
MUTEX_UNLOCK(h->pause_mutex); SEM_POST(h->pause_sem);
} }
} else { } else {
if (paused) { if (paused) {
h->paused = paused; h->paused = paused;
MUTEX_LOCK(h->pause_mutex); SEM_WAIT_INFINITE(h->pause_sem);
} }
} }
MUTEX_UNLOCK(h->mutex); MUTEX_UNLOCK(h->mutex);
} }
/**** Information about the output device */
int ao_real_output_bits_async(void *handle) int ao_real_output_bits_async(void *handle)
{ {
AO_Handle *h = (AO_Handle *) handle; AO_Handle *h = (AO_Handle *) handle;
return h->dev_bits_per_sample; return h->dev_bits_per_sample;
} }
+2 -1
View File
@@ -11,7 +11,7 @@
#define AOPLAYASYNC_EXPORT extern #define AOPLAYASYNC_EXPORT extern
#endif #endif
#define VERSION 3 #include "../ffi_version.h"
typedef enum { typedef enum {
ao = 1, ao = 1,
@@ -46,4 +46,5 @@ AOPLAYASYNC_EXPORT double ao_volume_async(void *ao_handle);
AOPLAYASYNC_EXPORT int ao_bufsize_async(void *handle); AOPLAYASYNC_EXPORT int ao_bufsize_async(void *handle);
AOPLAYASYNC_EXPORT int ao_real_output_bits_async(void *handle); AOPLAYASYNC_EXPORT int ao_real_output_bits_async(void *handle);
#endif // AO_PLAYASYNC_H #endif // AO_PLAYASYNC_H
+10
View File
@@ -0,0 +1,10 @@
#ifndef __FFI_VERSION__
#define __FFI_VERSION__
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_PATCH 1
#define ffi_version() ((VERSION_MAJOR << 16) + (VERSION_MINOR << 8) + VERSION_PATCH)
#endif
+7
View File
@@ -8,6 +8,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(ffmpeg_audio SHARED add_library(ffmpeg_audio SHARED
ffmpeg_audio.cpp ffmpeg_audio.cpp
ffmpeg_audio.h ffmpeg_audio.h
../ffi_version.h
ffmpeg_audio_refactored.cpp
) )
add_executable(demo_ffmpeg_audio add_executable(demo_ffmpeg_audio
@@ -21,6 +23,11 @@ if(WIN32)
target_link_directories(ffmpeg_audio PRIVATE ${FFMPEG_LIB}) target_link_directories(ffmpeg_audio PRIVATE ${FFMPEG_LIB})
set(FFMPEG_LIBRARIES avcodec.lib avformat.lib swresample.lib avutil.lib avdevice.lib avfilter.lib swscale.lib) set(FFMPEG_LIBRARIES avcodec.lib avformat.lib swresample.lib avutil.lib avdevice.lib avfilter.lib swscale.lib)
target_link_directories(demo_ffmpeg_audio PRIVATE ${FFMPEG_LIB}) target_link_directories(demo_ffmpeg_audio PRIVATE ${FFMPEG_LIB})
elseif(APPLE)
target_link_directories(ffmpeg_audio PRIVATE /opt/homebrew/opt/ffmpeg-full/lib)
include_directories(/opt/homebrew/opt/ffmpeg-full/include)
set(FFMPEG_LIBRARIES avcodec avformat swresample avutil avdevice avfilter swscale)
target_link_directories(demo_ffmpeg_audio PRIVATE /opt/homebrew/opt/ffmpeg-full/lib)
else() else()
set(FFMPEG_LIBRARIES avcodec avformat swresample avutil avdevice avfilter swscale) set(FFMPEG_LIBRARIES avcodec avformat swresample avutil avdevice avfilter swscale)
endif() endif()
+6
View File
@@ -13,6 +13,7 @@
*/ */
#include "ffmpeg_audio.h" #include "ffmpeg_audio.h"
#include "../ffi_version.h"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@@ -1021,3 +1022,8 @@ const char *fmpg_int_version2string(int ver)
return version; return version;
} }
int fmpg_version()
{
return ffi_version();
}
+1
View File
@@ -150,6 +150,7 @@ FFMPEG_EXTERN int64_t fmpg_sample_position(fmpg_instance *instance);
/* Approximate start time of the current decoded block in seconds. */ /* Approximate start time of the current decoded block in seconds. */
FFMPEG_EXTERN double fmpg_timecode(fmpg_instance *instance); FFMPEG_EXTERN double fmpg_timecode(fmpg_instance *instance);
FFMPEG_EXTERN int fmpg_version();
FFMPEG_EXTERN const char *fmpg_ffmpeg_version(); FFMPEG_EXTERN const char *fmpg_ffmpeg_version();
FFMPEG_EXTERN const char *fmpg_int_version2string(int ver); FFMPEG_EXTERN const char *fmpg_int_version2string(int ver);
FFMPEG_EXTERN int fmpg_compatible_ffmpeg(); FFMPEG_EXTERN int fmpg_compatible_ffmpeg();
+900
View File
@@ -0,0 +1,900 @@
/*
* Audio-only FFmpeg wrapper with a plain C ABI.
*
* This implementation intentionally hides FFmpeg concepts from the public API:
*
* - no stream_index in the API;
* - no AVPacket/package object in the API;
* - no explicit decoder object in the API;
* - no metadata/tag API exposed to C callers.
*
* Internally the instance owns everything needed to decode one selected audio
* stream. The caller simply opens a file and repeatedly calls fmpg_decode_next().
*/
#include "ffmpeg_audio.h"
#include "../ffi_version.h"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <vector>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
}
#define MSG0(type, msg) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": %s\n", msg)
#define MSG1(type, msg, a) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a)
#define MSG2(type, msg, a, b) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a, b)
#define MSG3(type, msg, a, b, c) fprintf(stderr, type ":");fprintf(stderr, "%s", __FUNCTION__);fprintf(stderr, ": " msg "\n", a, b, c)
#define INFO0(msg) MSG0("info", msg)
#define INFO1(msg, a) MSG1("info", msg, a)
#define INFO2(msg, a, b) MSG2("info", msg, a, b)
#define INFO3(msg, a, b, c) MSG3("info", msg, a, b, c)
#define ERROR0(msg) MSG0("error", msg)
#define ERROR1(msg, a) MSG1("error", msg, a)
#define ERROR2(msg, a, b) MSG2("error", msg, a, b)
#define ERROR3(msg, a, b, c) MSG3("error", msg, a, b, c)
static constexpr int FMPG_OUTPUT_BITS = 32;
static constexpr int FMPG_OUTPUT_BYTES = 4;
static constexpr AVSampleFormat FMPG_OUTPUT_FMT = AV_SAMPLE_FMT_S32;
struct audio_info_storage {
int audio_stream_count = 0;
int selected_stream_index = -1; /* Internal FFmpeg stream index. */
int sample_rate = 0;
int channels = 0;
int64_t duration_ms = -1;
int64_t duration_samples = -1; /* Output sample frames. */
void clear()
{
audio_stream_count = 0;
selected_stream_index = -1;
sample_rate = 0;
channels = 0;
duration_ms = -1;
duration_samples = -1;
}
};
struct decoder_storage {
const AVCodec *codec = nullptr;
AVCodecContext *codec_ctx = nullptr;
AVFrame *frame = nullptr;
SwrContext *swr_ctx = nullptr;
std::vector<uint8_t> pcm;
bool eof_seen = false;
bool decoder_drained = false;
double timecode = 0.0;
int64_t last_samples = 0;
int64_t buffer_start_sample = 0;
int64_t next_sample_position = 0;
/* >= 0 while a seek has requested us to discard decoded pre-roll samples. */
int64_t discard_until_sample = -1;
void clear_output()
{
pcm.clear();
last_samples = 0;
buffer_start_sample = next_sample_position;
}
void free_ffmpeg()
{
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
swr_free(&swr_ctx);
codec = nullptr;
pcm.clear();
eof_seen = false;
decoder_drained = false;
timecode = 0.0;
last_samples = 0;
buffer_start_sample = 0;
next_sample_position = 0;
discard_until_sample = -1;
}
~decoder_storage()
{
free_ffmpeg();
}
};
struct __fmpg_instance__ {
bool opened = false;
AVFormatContext *format_ctx = nullptr;
audio_info_storage audio_info;
decoder_storage decoder;
~__fmpg_instance__()
{
if (format_ctx) {
avformat_close_input(&format_ctx);
}
}
};
static int count_audio_streams(const AVFormatContext *ctx)
{
if (!ctx) {
return 0;
}
int count = 0;
for (unsigned i = 0; i < ctx->nb_streams; ++i) {
const AVCodecParameters *par = ctx->streams[i]->codecpar;
if (par && par->codec_type == AVMEDIA_TYPE_AUDIO) {
++count;
}
}
return count;
}
static int64_t milliseconds_from_seconds(double seconds)
{
if (seconds < 0.0) {
return -1;
}
return static_cast<int64_t>(seconds * 1000.0 + 0.5);
}
static int64_t samples_from_seconds(double seconds, int sample_rate)
{
if (seconds < 0.0 || sample_rate <= 0) {
return -1;
}
return static_cast<int64_t>(seconds * static_cast<double>(sample_rate) +
0.5);
}
static double stream_duration_seconds(const AVStream *stream)
{
if (!stream || stream->duration == AV_NOPTS_VALUE) {
return -1.0;
}
return static_cast<double>(stream->duration) * av_q2d(stream->time_base);
}
static double format_duration_seconds(const AVFormatContext *ctx)
{
if (!ctx || ctx->duration == AV_NOPTS_VALUE) {
return -1.0;
}
return static_cast<double>(ctx->duration) /
static_cast<double>(AV_TIME_BASE);
}
static int64_t timestamp_to_samples(int64_t timestamp,
const AVStream *stream,
int sample_rate)
{
if (!stream || timestamp == AV_NOPTS_VALUE || sample_rate <= 0) {
return -1;
}
const double seconds = static_cast<double>(timestamp) *
av_q2d(stream->time_base);
return samples_from_seconds(seconds, sample_rate);
}
static bool fill_audio_info(fmpg_instance *self)
{
AVFormatContext *ctx = self->format_ctx;
self->audio_info.clear();
self->audio_info.audio_stream_count = count_audio_streams(ctx);
const int best = av_find_best_stream(ctx,
AVMEDIA_TYPE_AUDIO,
-1,
-1,
nullptr,
0);
if (best < 0) {
return false;
}
AVStream *stream = ctx->streams[best];
const AVCodecParameters *par = stream->codecpar;
if (!par || par->codec_type != AVMEDIA_TYPE_AUDIO ||
par->sample_rate <= 0 || par->ch_layout.nb_channels <= 0) {
return false;
}
self->audio_info.selected_stream_index = best;
self->audio_info.sample_rate = par->sample_rate;
self->audio_info.channels = par->ch_layout.nb_channels;
double seconds = stream_duration_seconds(stream);
if (seconds < 0.0) {
seconds = format_duration_seconds(ctx);
}
self->audio_info.duration_ms = milliseconds_from_seconds(seconds);
self->audio_info.duration_samples =
samples_from_seconds(seconds, self->audio_info.sample_rate);
return true;
}
static bool instance_ready(const fmpg_instance *instance)
{
return instance && instance->opened && instance->format_ctx &&
instance->audio_info.selected_stream_index >= 0 &&
instance->decoder.codec_ctx && instance->decoder.swr_ctx;
}
static bool init_codec_context(fmpg_instance *self)
{
decoder_storage &dec = self->decoder;
const int stream_index = self->audio_info.selected_stream_index;
const AVCodecParameters *par =
self->format_ctx->streams[stream_index]->codecpar;
dec.codec = avcodec_find_decoder(par->codec_id);
if (!dec.codec) {
return false;
}
dec.codec_ctx = avcodec_alloc_context3(dec.codec);
if (!dec.codec_ctx) {
return false;
}
if (avcodec_parameters_to_context(dec.codec_ctx, par) < 0) {
return false;
}
if (avcodec_open2(dec.codec_ctx, dec.codec, nullptr) < 0) {
return false;
}
dec.frame = av_frame_alloc();
return dec.frame != nullptr;
}
static bool init_resampler(fmpg_instance *self)
{
decoder_storage &dec = self->decoder;
const AVChannelLayout *layout = &dec.codec_ctx->ch_layout;
if (layout->nb_channels <= 0 || dec.codec_ctx->sample_rate <= 0) {
return false;
}
if (swr_alloc_set_opts2(&dec.swr_ctx,
layout,
FMPG_OUTPUT_FMT,
dec.codec_ctx->sample_rate,
layout,
dec.codec_ctx->sample_fmt,
dec.codec_ctx->sample_rate,
0,
nullptr) < 0) {
return false;
}
return swr_init(dec.swr_ctx) >= 0;
}
static bool init_decoder(fmpg_instance *self)
{
self->decoder.free_ffmpeg();
return init_codec_context(self) && init_resampler(self);
}
int fmpg_compatible_ffmpeg()
{
int compiled_avformat_major = LIBAVFORMAT_VERSION_MAJOR;
int compiled_avcodec_major = LIBAVCODEC_VERSION_MAJOR;
int compiled_swresample_major = LIBSWRESAMPLE_VERSION_MAJOR;
int compiled_avutil_major = LIBAVUTIL_VERSION_MAJOR;
int current_avformat_major = AV_VERSION_MAJOR(avformat_version());
int current_avcodec_major = AV_VERSION_MAJOR(avcodec_version());
int current_swresample_major = AV_VERSION_MAJOR(swresample_version());
int current_avutil_major = AV_VERSION_MAJOR(avutil_version());
auto comp = [](const char *lib, int cv, int rv) {
if (cv != rv) {
ERROR3("FFMPEG %s Major versions not equal. Compile time: %d, runtime: %d", lib, cv, rv);
}
};
comp("AVFormat", compiled_avformat_major, current_avformat_major);
comp("AVCodec", compiled_avcodec_major, current_avcodec_major);
comp("SWResample", compiled_swresample_major, current_swresample_major);
comp("AVUtil", compiled_avutil_major, current_avutil_major);
int compatible = (compiled_avformat_major == current_avformat_major) &&
(compiled_avcodec_major == current_avcodec_major) &&
(compiled_swresample_major == current_swresample_major) &&
(compiled_avutil_major == current_avutil_major);
return compatible;
}
fmpg_instance *fmpg_init(void)
{
if (!fmpg_compatible_ffmpeg()) {
ERROR0("Compiled major ffmpeg version ≃ runtime major version, not compatible.");
return nullptr;
}
try {
return new fmpg_instance();
} catch (...) {
return nullptr;
}
}
void fmpg_free(fmpg_instance *instance)
{
delete instance;
}
int fmpg_open_file(fmpg_instance *instance, const char *filename)
{
if (!instance || instance->opened || !filename) {
return 0;
}
if (instance->format_ctx != nullptr) {
return 0;
}
int r = avformat_open_input(&instance->format_ctx,
filename,
nullptr,
nullptr);
if (r < 0) {
fmpg_close(instance);
return 0;
}
if (avformat_find_stream_info(instance->format_ctx, nullptr) < 0) {
fmpg_close(instance);
return 0;
}
if (!fill_audio_info(instance) || !init_decoder(instance)) {
fmpg_close(instance);
return 0;
}
instance->opened = true;
return 1;
}
void fmpg_close(fmpg_instance *instance)
{
if (!instance) {
return;
}
instance->decoder.free_ffmpeg();
if (instance->format_ctx) {
avformat_close_input(&instance->format_ctx);
}
instance->opened = false;
instance->audio_info.clear();
}
int fmpg_is_open(fmpg_instance *instance)
{
return instance_ready(instance) ? 1 : 0;
}
int fmpg_audio_stream_count(fmpg_instance *instance)
{
return instance && instance->opened ? instance->audio_info.audio_stream_count
: 0;
}
int fmpg_audio_sample_rate(fmpg_instance *instance)
{
return instance_ready(instance) ? instance->audio_info.sample_rate : 0;
}
int fmpg_audio_channels(fmpg_instance *instance)
{
return instance_ready(instance) ? instance->audio_info.channels : 0;
}
int fmpg_audio_bits_per_sample(fmpg_instance *)
{
return FMPG_OUTPUT_BITS;
}
int fmpg_audio_bytes_per_sample(fmpg_instance *)
{
return FMPG_OUTPUT_BYTES;
}
int64_t fmpg_duration_ms(fmpg_instance *instance)
{
return instance_ready(instance) ? instance->audio_info.duration_ms : -1;
}
int64_t fmpg_duration_samples(fmpg_instance *instance)
{
return instance_ready(instance) ? instance->audio_info.duration_samples : -1;
}
static bool append_bytes(decoder_storage &dec,
const uint8_t *src,
size_t bytes)
{
if (!bytes) {
return true;
}
if (bytes > static_cast<size_t>(std::numeric_limits<int>::max()) -
dec.pcm.size()) {
return false;
}
try {
const size_t old_size = dec.pcm.size();
dec.pcm.resize(old_size + bytes);
std::memcpy(dec.pcm.data() + old_size, src, bytes);
return true;
} catch (...) {
return false;
}
}
static bool append_converted_frame(fmpg_instance *self,
const AVFrame *frame)
{
decoder_storage &dec = self->decoder;
const int channels = self->audio_info.channels;
const int sample_rate = self->audio_info.sample_rate;
if (channels <= 0 || frame->nb_samples <= 0) {
return true;
}
const int max_out_samples = swr_get_out_samples(dec.swr_ctx,
frame->nb_samples);
if (max_out_samples <= 0) {
return false;
}
const int max_bytes = av_samples_get_buffer_size(nullptr,
channels,
max_out_samples,
FMPG_OUTPUT_FMT,
1);
if (max_bytes <= 0) {
return false;
}
std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes));
uint8_t *out_planes[1] = { tmp.data() };
const int out_samples = swr_convert(dec.swr_ctx,
out_planes,
max_out_samples,
const_cast<const uint8_t **>(frame->data),
frame->nb_samples);
if (out_samples < 0) {
return false;
}
const int used_bytes = av_samples_get_buffer_size(nullptr,
channels,
out_samples,
FMPG_OUTPUT_FMT,
1);
if (used_bytes < 0) {
return false;
}
const int stream_index = self->audio_info.selected_stream_index;
const AVStream *stream = self->format_ctx->streams[stream_index];
int64_t frame_start = timestamp_to_samples(frame->best_effort_timestamp,
stream,
sample_rate);
if (frame_start < 0) {
frame_start = dec.next_sample_position;
}
int64_t keep_start = frame_start;
int keep_samples = out_samples;
size_t byte_offset = 0;
/*
* After seeking, FFmpeg may first return decoded samples from before the
* requested position. Discard them so public sample positions refer to the
* actual music position requested by the caller.
*/
if (dec.discard_until_sample >= 0) {
const int64_t target = dec.discard_until_sample;
const int64_t frame_end = frame_start + out_samples;
if (frame_end <= target) {
dec.next_sample_position = frame_end;
return true;
}
if (frame_start < target) {
const int64_t drop = target - frame_start;
if (drop > 0 && drop < out_samples) {
byte_offset = static_cast<size_t>(drop) *
static_cast<size_t>(channels) *
FMPG_OUTPUT_BYTES;
keep_samples = static_cast<int>(out_samples - drop);
keep_start = target;
}
}
dec.discard_until_sample = -1;
}
if (keep_samples <= 0) {
dec.next_sample_position = frame_start + out_samples;
return true;
}
if (dec.pcm.empty()) {
dec.buffer_start_sample = keep_start;
dec.timecode = static_cast<double>(keep_start) /
static_cast<double>(sample_rate);
}
const size_t keep_bytes = static_cast<size_t>(keep_samples) *
static_cast<size_t>(channels) *
FMPG_OUTPUT_BYTES;
if (!append_bytes(dec, tmp.data() + byte_offset, keep_bytes)) {
return false;
}
dec.last_samples += keep_samples;
dec.next_sample_position = keep_start + keep_samples;
return true;
}
static int receive_available_frames(fmpg_instance *self)
{
decoder_storage &dec = self->decoder;
int produced = 0;
for (;;) {
const int ret = avcodec_receive_frame(dec.codec_ctx, dec.frame);
if (ret == AVERROR(EAGAIN)) {
return produced;
}
if (ret == AVERROR_EOF) {
dec.decoder_drained = true;
return produced;
}
if (ret < 0) {
return -1;
}
if (!append_converted_frame(self, dec.frame)) {
av_frame_unref(dec.frame);
return -1;
}
produced = dec.last_samples > 0 ? 1 : produced;
av_frame_unref(dec.frame);
}
}
static bool read_selected_audio_packet(fmpg_instance *self, AVPacket *pkt)
{
const int wanted_stream = self->audio_info.selected_stream_index;
for (;;) {
const int ret = av_read_frame(self->format_ctx, pkt);
if (ret < 0) {
return false;
}
if (pkt->stream_index == wanted_stream) {
return true;
}
av_packet_unref(pkt);
}
}
static int drain_resampler(fmpg_instance *self)
{
decoder_storage &dec = self->decoder;
const int channels = self->audio_info.channels;
const int sample_rate = self->audio_info.sample_rate;
int produced = 0;
for (;;) {
const int delay = static_cast<int>(swr_get_delay(dec.swr_ctx,
sample_rate));
if (delay <= 0) {
break;
}
const int max_bytes = av_samples_get_buffer_size(nullptr,
channels,
delay,
FMPG_OUTPUT_FMT,
1);
if (max_bytes <= 0) {
break;
}
std::vector<uint8_t> tmp(static_cast<size_t>(max_bytes));
uint8_t *out_planes[1] = { tmp.data() };
const int out_samples = swr_convert(dec.swr_ctx,
out_planes,
delay,
nullptr,
0);
if (out_samples <= 0) {
break;
}
const int used_bytes = av_samples_get_buffer_size(nullptr,
channels,
out_samples,
FMPG_OUTPUT_FMT,
1);
if (used_bytes < 0) {
break;
}
if (dec.pcm.empty()) {
dec.buffer_start_sample = dec.next_sample_position;
dec.timecode = static_cast<double>(dec.buffer_start_sample) /
static_cast<double>(sample_rate);
}
if (!append_bytes(dec, tmp.data(), static_cast<size_t>(used_bytes))) {
return -1;
}
dec.last_samples += out_samples;
dec.next_sample_position += out_samples;
produced = 1;
}
return produced;
}
int fmpg_decode_next(fmpg_instance *instance)
{
if (!instance_ready(instance)) {
return 0;
}
decoder_storage &dec = instance->decoder;
dec.clear_output();
/* First return any frames that are already pending in the decoder. */
int produced = receive_available_frames(instance);
if (produced < 0) {
return 0;
}
if (produced > 0 && !dec.pcm.empty()) {
return 1;
}
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
return 0;
}
while (!dec.eof_seen) {
if (!read_selected_audio_packet(instance, pkt)) {
dec.eof_seen = true;
av_packet_unref(pkt);
break;
}
int ret = avcodec_send_packet(dec.codec_ctx, pkt);
av_packet_unref(pkt);
if (ret == AVERROR(EAGAIN)) {
produced = receive_available_frames(instance);
if (produced < 0) {
av_packet_free(&pkt);
return 0;
}
if (produced > 0 && !dec.pcm.empty()) {
av_packet_free(&pkt);
return 1;
}
continue;
}
if (ret < 0) {
av_packet_free(&pkt);
return 0;
}
produced = receive_available_frames(instance);
if (produced < 0) {
av_packet_free(&pkt);
return 0;
}
if (produced > 0 && !dec.pcm.empty()) {
av_packet_free(&pkt);
return 1;
}
}
av_packet_free(&pkt);
if (!dec.decoder_drained) {
const int ret = avcodec_send_packet(dec.codec_ctx, nullptr);
if (ret < 0 && ret != AVERROR_EOF) {
return 0;
}
produced = receive_available_frames(instance);
if (produced < 0) {
return 0;
}
if (produced > 0 && !dec.pcm.empty()) {
return 1;
}
}
produced = drain_resampler(instance);
return produced > 0 && !dec.pcm.empty() ? 1 : 0;
}
int fmpg_seek_ms(fmpg_instance *instance, int64_t target_pos_ms)
{
if (!instance_ready(instance) || target_pos_ms < 0) {
return 0;
}
const int stream_index = instance->audio_info.selected_stream_index;
AVStream *stream = instance->format_ctx->streams[stream_index];
const int64_t pos_us = av_rescale(target_pos_ms, AV_TIME_BASE, 1000);
const int64_t stream_ts = av_rescale_q(pos_us,
AV_TIME_BASE_Q,
stream->time_base);
if (av_seek_frame(instance->format_ctx,
stream_index,
stream_ts,
AVSEEK_FLAG_BACKWARD) < 0) {
return 0;
}
decoder_storage &dec = instance->decoder;
const int64_t target_samples = samples_from_seconds(target_pos_ms / 1000.0,
instance->audio_info.sample_rate);
avcodec_flush_buffers(dec.codec_ctx);
swr_close(dec.swr_ctx);
if (swr_init(dec.swr_ctx) < 0) {
return 0;
}
dec.pcm.clear();
dec.last_samples = 0;
dec.buffer_start_sample = target_samples >= 0 ? target_samples : 0;
dec.next_sample_position = target_samples >= 0 ? target_samples : 0;
dec.discard_until_sample = target_samples;
dec.timecode = target_pos_ms / 1000.0;
dec.eof_seen = false;
dec.decoder_drained = false;
return 1;
}
const uint8_t *fmpg_buffer(fmpg_instance *instance)
{
return instance && !instance->decoder.pcm.empty()
? instance->decoder.pcm.data()
: nullptr;
}
int fmpg_buffer_size(fmpg_instance *instance)
{
if (!instance || instance->decoder.pcm.size() >
static_cast<size_t>(std::numeric_limits<int>::max())) {
return 0;
}
return static_cast<int>(instance->decoder.pcm.size());
}
int64_t fmpg_buffer_samples(fmpg_instance *instance)
{
return instance ? instance->decoder.last_samples : 0;
}
int64_t fmpg_buffer_start_sample(fmpg_instance *instance)
{
return instance ? instance->decoder.buffer_start_sample : 0;
}
int64_t fmpg_buffer_end_sample(fmpg_instance *instance)
{
if (!instance) {
return 0;
}
return instance->decoder.buffer_start_sample + instance->decoder.last_samples;
}
int64_t fmpg_sample_position(fmpg_instance *instance)
{
return instance ? instance->decoder.next_sample_position : 0;
}
double fmpg_timecode(fmpg_instance *instance)
{
return instance ? instance->decoder.timecode : 0.0;
}
const char *fmpg_ffmpeg_version()
{
static char *version = nullptr;
if (version == nullptr) {
version = static_cast<char *>(malloc(1024));
}
sprintf(version, "avformat: %u.%u.%u (%d), avcodec: %u.%u.%u (%d), swresample: %u.%u.%u (%d), avutil: %u.%u.%u (%d)",
LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO, LIBAVFORMAT_VERSION_INT,
LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO, LIBAVCODEC_VERSION_INT,
LIBSWRESAMPLE_VERSION_MAJOR, LIBSWRESAMPLE_VERSION_MINOR, LIBSWRESAMPLE_VERSION_MICRO, LIBSWRESAMPLE_VERSION_INT,
LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO, LIBAVUTIL_VERSION_INT
);
return version;
}
const char *fmpg_int_version2string(int ver)
{
static char *version = nullptr;
if (version == nullptr) {
version = static_cast<char *>(malloc(1024));
}
int major = AV_VERSION_MAJOR(ver);
int minor = AV_VERSION_MINOR(ver);
int micro = AV_VERSION_MICRO(ver);
sprintf(version, "%u.%u.%u", major, minor, micro);
return version;
}
int fmpg_version()
{
return ffi_version();
}
+17
View File
@@ -0,0 +1,17 @@
Building for Win32.
Extract the taglib sources.
Add this in CMakeLists.txt:
set(ZLIB_LIBRARY "c:/devel/libraries/nwin64/lib/zlib.lib")
set(ZLIB_INCLUDE_DIR "c:/devel/libraries/nwin64/include")
set(BUILD_TESTING OFF)
set(BUILD_SHARED_LIBS ON)
Or any place you have zlib.lib stored.
Copy the shared libraries to ../lib/windows_<arch>/
+15
View File
@@ -0,0 +1,15 @@
TAGLIB=taglib-2.2.1
all:
tar xf ${TAGLIB}.tar.gz
SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \
(cd ${TAGLIB}; cmake -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=../lib/$$SUBDIR -DCMAKE_BUILD_TYPE=Release .)
(cd ${TAGLIB}; make)
(cd ${TAGLIB}; make install)
SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \
(cd lib/$$SUBDIR/lib; tar cf - *so *.so.*) | (cd ../lib/$$SUBDIR; tar xvf - )
clean:
rm -rf ${TAGLIB}
rm -rf lib
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.