Compare commits
10 Commits
1-0-0
..
3c28763a61
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c28763a61 | |||
| 33b79e55e2 | |||
| 7de84cbf30 | |||
| fc4b6f8ced | |||
| b8996d55e4 | |||
| 977eb5a456 | |||
| a358a8593d | |||
| f4e2a6aa31 | |||
| 51a0138877 | |||
| 4134cf0549 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -0,0 +1,901 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
@@ -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>/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user