Skip to content

Commit

Permalink
holy crap, playback working on 3 platforms
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Hamilton committed Aug 22, 2015
1 parent f767850 commit 3709f20
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 53 deletions.
3 changes: 1 addition & 2 deletions readme.md
Expand Up @@ -25,5 +25,4 @@

## investigate

* test what happens when the audio is short enough that only 1 of the buffers is needed on OSX and win
* see if ALSA or win have basic volume control
* test what happens when the audio is short enough that only 1 of the buffers is needed on OSX and win
7 changes: 3 additions & 4 deletions setup.py
Expand Up @@ -5,7 +5,6 @@
platform_sources = []
platform_libs = []
platform_link_args = []
platform_inc_dirs = []

if sys.platform == 'darwin':
platform_sources = ['simpleaudio_mac.c', 'posix_mutex.c']
Expand All @@ -15,21 +14,21 @@
platform_libs = ['asound']
elif sys.platform == 'win32':
platform_sources = ['simpleaudio_win.c', 'windows_mutex.c']
platform_libs = ['Winmm', 'User32']
else:
pass
# define a compiler macro for unsupported ?

_simpleaudio_module = Extension(
'_simpleaudio',
'_simpleaudio',
sources=platform_sources+['simpleaudio.c'],
libraries=platform_libs,
extra_link_args=platform_link_args,
include_dirs=platform_inc_dirs,
define_macros = [('DEBUG', '1')])

setup(name = 'simpleaudio',
version = '1.0',
description = """The simpleaudio package contains the simpleaudio module
description = """The simpleaudio package contains the simpleaudio module
which makes playing wave files in Python very simple.""",
test_suite="tests",
py_modules = ["simpleaudio.shiny"],
Expand Down
2 changes: 1 addition & 1 deletion simpleaudio.c
Expand Up @@ -18,7 +18,7 @@ static PyObject* play_buffer(PyObject *self, PyObject *args)
unsigned int num_channels;
unsigned int bytes_per_sample;
unsigned int sample_rate;
len_samples_t num_samples;
int num_samples;

#if DEBUG > 0
fprintf(DBG_OUT, DBG_PRE"play_buffer call\n");
Expand Down
5 changes: 2 additions & 3 deletions simpleaudio.h
Expand Up @@ -16,7 +16,7 @@
/* some handy macros for debug prints used in ultiple places */
#if DEBUG > 0
#define DBG_PLAY_OS_CALL \
fprintf(DBG_OUT, DBG_PRE"play_os call: buffer at %p, %llu samples, %d channels, %d bytes-per-chan, sample rate %d, list head at %p\n", \
fprintf(DBG_OUT, DBG_PRE"play_os call: buffer at %p, %d samples, %d channels, %d bytes-per-chan, sample rate %d, list head at %p\n", \
buffer_obj.buf, len_samples, num_channels, bytes_per_chan, sample_rate, play_list_head);

#define DBG_DESTROY_BLOB fprintf(DBG_OUT, DBG_PRE"destroying audio blob at %p\n", audio_blob);
Expand All @@ -34,7 +34,6 @@ enum {
};

typedef unsigned long long play_id_t;
typedef unsigned long long len_samples_t;

/* linked list structure used to track the active playback items/threads */
typedef struct play_item_s {
Expand All @@ -52,7 +51,7 @@ typedef struct play_item_s {
extern PyObject* sa_python_error;

/* prototypes */
PyObject* play_os(Py_buffer buffer_obj, len_samples_t len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head);
PyObject* play_os(Py_buffer buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head);

void delete_list_item(play_item_t* play_item);
play_item_t* new_list_item(play_item_t* list_head);
Expand Down
2 changes: 1 addition & 1 deletion simpleaudio_alsa.c
Expand Up @@ -99,7 +99,7 @@ void* playback_thread(void* thread_param) {
pthread_exit(0);
}

PyObject* play_os(Py_buffer buffer_obj, len_samples_t len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
PyObject* play_os(Py_buffer buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
char err_msg_buf[SA_ERR_STR_LEN];
alsa_audio_blob_t* audio_blob;
int bytesPerFrame = bytes_per_chan * num_channels;
Expand Down
14 changes: 7 additions & 7 deletions simpleaudio_mac.c
Expand Up @@ -14,7 +14,7 @@ typedef struct {
Py_buffer buffer_obj;
int used_bytes;
int len_bytes;
int buffers;
int num_buffers;
play_item_t* play_list_item;
void* list_mutex;
} mac_audio_blob_t;
Expand Down Expand Up @@ -73,11 +73,11 @@ static void audio_callback(void* param, AudioQueueRef audio_queue, AudioQueueBuf
fprintf(DBG_OUT, DBG_PRE"done enqueue'ing - dellocating a buffer\n");
#endif

if (audio_blob->buffers > 0) {
if (audio_blob->num_buffers > 0) {
AudioQueueFreeBuffer(audio_queue, queue_buffer);
audio_blob->buffers--;
audio_blob->num_buffers--;
}
if (audio_blob->buffers == 0) {
if (audio_blob->num_buffers == 0) {
/* all done, cleanup */
AudioQueueStop(audio_queue, true);
AudioQueueDispose(audio_queue, true);
Expand All @@ -86,7 +86,7 @@ static void audio_callback(void* param, AudioQueueRef audio_queue, AudioQueueBuf
}
}

PyObject* play_os(Py_buffer buffer_obj, len_samples_t len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
PyObject* play_os(Py_buffer buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
char err_msg_buf[SA_ERR_STR_LEN];
AudioQueueRef audio_queue;
AudioStreamBasicDescription audio_fmt;
Expand All @@ -108,7 +108,7 @@ PyObject* play_os(Py_buffer buffer_obj, len_samples_t len_samples, int num_chann
audio_blob->list_mutex = play_list_head->mutex;
audio_blob->len_bytes = len_samples * bytesPerFrame;
audio_blob->used_bytes = 0;
audio_blob->buffers = 0;
audio_blob->num_buffers = 0;

/* setup the linked list item for this playback buffer */
grab_mutex(play_list_head->mutex);
Expand Down Expand Up @@ -152,7 +152,7 @@ PyObject* play_os(Py_buffer buffer_obj, len_samples_t len_samples, int num_chann
destroy_audio_blob(audio_blob);
return NULL;
}
audio_blob->buffers++;
audio_blob->num_buffers++;
/* fill a buffer using the callback */
audio_callback(audio_blob, audio_queue, queue_buffer);
}
Expand Down
70 changes: 35 additions & 35 deletions simpleaudio_win.c
Expand Up @@ -19,15 +19,15 @@ typedef struct {
Py_buffer buffer_obj;
HWAVEOUT wave_out_hdr;
int used_bytes;
int len_bytes;
int len_bytes;
int num_buffers;
play_item_t* play_list_item;
void* list_mutex;
} win_audio_blob_t;

void destroy_audio_blob(win_audio_blob_t* audio_blob) {
PyGILState_STATE gstate;

DBG_DESTROY_BLOB

/* release the buffer view so Python can
Expand All @@ -44,14 +44,14 @@ void destroy_audio_blob(win_audio_blob_t* audio_blob) {

MMRESULT fillBuffer(WAVEHDR* wave_header, win_audio_blob_t* audio_blob) {
int want = wave_header->dwBufferLength;
int have = audio_blob->len_bytes - audio_blob->used_bytes;
int have = audio_blob->len_bytes - audio_blob->used_bytes;
int stop_flag = 0;
MMRESULT result;

grab_mutex(audio_blob->play_list_item->mutex);
stop_flag = audio_blob->play_list_item->stop_flag;
release_mutex(audio_blob->play_list_item->mutex);

/* if there's still audio yet to buffer ... */
if (have > 0 && !stop_flag) {
if (have > want) {have = want;}
Expand All @@ -66,10 +66,10 @@ MMRESULT fillBuffer(WAVEHDR* wave_header, win_audio_blob_t* audio_blob) {
audio_blob->used_bytes += have;
/* ... no more audio left to buffer */
} else {
if (audio_blob->num_buffers > 0) {
if (audio_blob->num_buffers > 0) {
PyMem_Free(wave_header->lpData);
PyMem_Free(wave_header);
audio_blob->num_buffers--;
audio_blob->num_buffers--;
} else {
/* all done, cleanup */
waveOutClose(audio_blob->wave_out_hdr);
Expand All @@ -78,7 +78,7 @@ MMRESULT fillBuffer(WAVEHDR* wave_header, win_audio_blob_t* audio_blob) {
return MMSYSERR_NOERROR - 1;
}
}

return MMSYSERR_NOERROR;
}

Expand All @@ -87,7 +87,7 @@ DWORD WINAPI bufferThread(LPVOID threadParam) {
MSG message;
WAVEHDR* wave_header;
MMRESULT result;

/* wait for the "audio block done" message" */
while (1) {
GetMessage(&message, NULL, 0, 0);
Expand All @@ -102,94 +102,94 @@ DWORD WINAPI bufferThread(LPVOID threadParam) {
return 0;
}

PyObject* play_os(void* audio_data, len_samples_t len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
PyObject* play_os(Py_buffer buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head) {
char err_msg_buf[SA_ERR_STR_LEN];
char sys_msg_buf[SA_ERR_STR_LEN / 2];
win_audio_blob_t* audio_blob;
WAVEFORMATEX audio_format;
WAVEFORMATEX audio_format;
MMRESULT result;
HANDLE thread_handle = NULL;
DWORD thread_id;
int bytes_per_frame = bytes_per_chan * num_channels;
WAVEHDR* temp_wave_hdr;
int i;

DBG_PLAY_OS_CALL

/* initial allocation and audio buffer copy */
audio_blob = PyMem_Malloc(sizeof(win_audio_blob_t));

DBG_CREATE_BLOB

audio_blob->buffer_obj = buffer_obj;
audio_blob->list_mutex = play_list_head->mutex;
audio_blob->len_bytes = len_samples * bytes_per_frame;
audio_blob->used_bytes = 0;
audio_blob->num_buffers = 0;

/* setup the linked list item for this playback buffer */
grab_mutex(play_list_head->mutex);
audio_blob->play_list_item = new_list_item(play_list_head);
release_mutex(play_list_head->mutex);

/* windows audio device and format headers setup */
audio_format.wFormatTag = WAVE_FORMAT_PCM;
audio_format.nChannels = num_channels;
audio_format.nSamplesPerSec = sample_rate;
audio_format.nBlockAlign = bytes_per_frame;

audio_format.nSamplesPerSec = sample_rate;
audio_format.nBlockAlign = bytes_per_frame;
/* per MSDN WAVEFORMATEX documentation */
audio_format.nAvgBytesPerSec = audio_format.nSamplesPerSec * audio_format.nBlockAlign;
audio_format.wBitsPerSample = bitsPerChan;
audio_format.wBitsPerSample = bytes_per_chan * 8;
audio_format.cbSize = 0;
/* create the cleanup thread so we can return after calling waveOutWrite
SEE :http://msdn.microsoft.com/en-us/library/windows/desktop/ms682516(v=vs.85).aspx

/* create the cleanup thread so we can return after calling waveOutWrite
SEE :http://msdn.microsoft.com/en-us/library/windows/desktop/ms682516(v=vs.85).aspx
*/
thread_handle = CreateThread(NULL, 0, bufferThread, audio_blob, 0, &thread_id);
if (thread_handle == NULL) {
DWORD lastError = GetLastError();
/* lang code : US En */
FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS), NULL, lastError, 0x0409, sys_msg_buf, SYS_STR_LEN, NULL);
WIN_EXCEPTION("Failed to start cleanup thread.", 0, sys_msg_buff, err_msg_buf);
WIN_EXCEPTION("Failed to start cleanup thread.", 0, sys_msg_buf, err_msg_buf);

destroy_audio_blob(audio_blob);
return NULL;
}

/* open a handle to the default audio device */
result = waveOutOpen(&audio_blob->wave_out_hdr, WAVE_MAPPER, &audio_format, thread_id, 0, CALLBACK_THREAD);
if (result != MMSYSERR_NOERROR) {
waveOutGetErrorText(result, sys_msg_buf, SYS_STR_LEN);
WIN_EXCEPTION("Failed to open audio device.", result, sys_msg_buff, err_msg_buf);
WIN_EXCEPTION("Failed to open audio device.", result, sys_msg_buf, err_msg_buf);

PostThreadMessage(thread_id, WM_QUIT, 0, 0);
destroy_audio_blob(audio_blob);
return NULL;
}

/* fill and write two buffers */
for (i = 0; i < 2; i++) {
temp_wave_hdr = PyMem_Malloc(sizeof(WAVEHDR));
memset(temp_wave_hdr, 0, sizeof(WAVEHDR));
temp_wave_hdr->lpData = PyMem_Malloc(SIMPLEAUDIO_BUFSZ);
temp_wave_hdr->dwBufferLength = SIMPLEAUDIO_BUFSZ;

result = fillBuffer(temp_wave_hdr, audio_blob);
if (result != MMSYSERR_NOERROR) {
waveOutGetErrorText(result, sys_msg_buf, SYS_STR_LEN);
WIN_EXCEPTION("Failed to buffer audio.", result, sys_msg_buff, err_msg_buf);
WIN_EXCEPTION("Failed to buffer audio.", result, sys_msg_buf, err_msg_buf);

PostThreadMessage(thread_id, WM_QUIT, 0, 0);
waveOutUnprepareHeader(audio_blob->wave_out_hdr, temp_wave_hdr, sizeof(WAVEHDR));
waveOutClose(audio_blob->wave_out_hdr);
destroy_audio_blob(audio_blob);
return NULL;
}

audio_blob->num_buffers++;
}

return PyLong_FromUnsignedLongLong(audio_blob->play_list_item->play_id);
}

0 comments on commit 3709f20

Please sign in to comment.