Initial commit of mpg123-1.29.3
This commit is contained in:
391
src/libout123/modules/win32.c
Normal file
391
src/libout123/modules/win32.c
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
win32: audio output for Windows 32bit
|
||||
|
||||
copyright ?-2013 by the mpg123 project - free software under the terms of the LGPL 2.1
|
||||
see COPYING and AUTHORS files in distribution or http://mpg123.org
|
||||
|
||||
initially written (as it seems) by Tony Million
|
||||
rewrite of basic functionality for callback-less and properly ringbuffered operation by ravenexp
|
||||
Closing buffer playback fixed by David Wohlferd <limegreensocks (*) yahoo dod com>
|
||||
*/
|
||||
|
||||
#include "out123_int.h"
|
||||
#include <windows.h>
|
||||
#include "debug.h"
|
||||
|
||||
/*
|
||||
Buffer size and number of buffers in the playback ring
|
||||
NOTE: This particular num/size combination performs best under heavy
|
||||
loads for my system, however this may not be true for any hardware/OS out there.
|
||||
Generally, BUFFER_SIZE < 8k || NUM_BUFFERS > 16 || NUM_BUFFERS < 4 are not recommended.
|
||||
*/
|
||||
#define BUFFER_SIZE 0x10000
|
||||
#define NUM_BUFFERS 8 /* total 512k roughly 2.5 sec of CD quality sound */
|
||||
|
||||
static void wait_for_buffer(WAVEHDR* hdr, HANDLE hEvent);
|
||||
static void drain_win32(out123_handle *ao);
|
||||
|
||||
/* Buffer ring queue state */
|
||||
struct queue_state
|
||||
{
|
||||
WAVEHDR buffer_headers[NUM_BUFFERS];
|
||||
/* The next buffer to be filled and put in playback */
|
||||
int next_buffer;
|
||||
/* Buffer playback completion event */
|
||||
HANDLE play_done_event;
|
||||
HWAVEOUT waveout;
|
||||
};
|
||||
|
||||
static UINT dev_select(out123_handle *ao){
|
||||
UINT ret;
|
||||
if(ao->device) {
|
||||
sscanf(ao->device, "%u", &ret);
|
||||
} else {
|
||||
ret = WAVE_MAPPER;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int open_win32(out123_handle *ao)
|
||||
{
|
||||
struct queue_state* state;
|
||||
int i;
|
||||
MMRESULT res;
|
||||
WAVEFORMATEX out_fmt;
|
||||
UINT dev_id;
|
||||
|
||||
if(!ao) return -1;
|
||||
if(ao->rate == -1) return 0;
|
||||
|
||||
/* only 8 and 16 supported */
|
||||
if(!(ao->format == MPG123_ENC_SIGNED_8 || ao->format == MPG123_ENC_SIGNED_16)) return -1;
|
||||
/* only mono and stereo supported */
|
||||
if(!(ao->channels == 1 || ao->channels == 2)) return -1;
|
||||
|
||||
/* Allocate queue state struct for this device */
|
||||
state = calloc(1, sizeof(struct queue_state));
|
||||
if(!state) return -1;
|
||||
|
||||
ao->userptr = state;
|
||||
|
||||
state->play_done_event = CreateEvent(0,FALSE,FALSE,0);
|
||||
if(state->play_done_event == INVALID_HANDLE_VALUE) return -1;
|
||||
|
||||
dev_id = dev_select(ao);
|
||||
|
||||
out_fmt.wFormatTag = WAVE_FORMAT_PCM;
|
||||
out_fmt.wBitsPerSample = ao->format == MPG123_ENC_SIGNED_8 ? 8 : 16;
|
||||
out_fmt.nChannels = ao->channels;
|
||||
out_fmt.nSamplesPerSec = ao->rate;
|
||||
out_fmt.nBlockAlign = out_fmt.nChannels*out_fmt.wBitsPerSample/8;
|
||||
out_fmt.nAvgBytesPerSec = out_fmt.nBlockAlign*out_fmt.nSamplesPerSec;
|
||||
out_fmt.cbSize = 0;
|
||||
|
||||
res = waveOutOpen(&state->waveout, dev_id, &out_fmt,
|
||||
(DWORD_PTR)state->play_done_event, 0, CALLBACK_EVENT);
|
||||
|
||||
switch(res)
|
||||
{
|
||||
case MMSYSERR_NOERROR:
|
||||
break;
|
||||
case MMSYSERR_ALLOCATED:
|
||||
ereturn(-1, "Audio output device is already allocated.");
|
||||
case MMSYSERR_NODRIVER:
|
||||
ereturn(-1, "No device driver is present.");
|
||||
case MMSYSERR_NOMEM:
|
||||
ereturn(-1, "Unable to allocate or lock memory.");
|
||||
case WAVERR_BADFORMAT:
|
||||
ereturn(-1, "Unsupported waveform-audio format.");
|
||||
default:
|
||||
ereturn(-1, "Unable to open wave output device.");
|
||||
}
|
||||
|
||||
/* Reset event from the "device open" message */
|
||||
ResetEvent(state->play_done_event);
|
||||
/* Allocate playback buffers */
|
||||
for(i = 0; i < NUM_BUFFERS; i++)
|
||||
if(!(state->buffer_headers[i].lpData = (LPSTR)malloc(BUFFER_SIZE)))
|
||||
{
|
||||
ereturn(-1, "Out of memory for playback buffers.");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Tell waveOutPrepareHeader the maximum value of dwBufferLength
|
||||
we will ever send */
|
||||
state->buffer_headers[i].dwBufferLength = BUFFER_SIZE;
|
||||
state->buffer_headers[i].dwFlags = 0;
|
||||
res = waveOutPrepareHeader(state->waveout, &state->buffer_headers[i], sizeof(WAVEHDR));
|
||||
if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device (prepare).");
|
||||
|
||||
/* set the current size of the buffer to 0 */
|
||||
state->buffer_headers[i].dwBufferLength = 0;
|
||||
|
||||
/* set flags to unprepared - must reset this to WHDR_PREPARED before calling write */
|
||||
state->buffer_headers[i].dwFlags = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wait_for_buffer(WAVEHDR* hdr, HANDLE hEvent)
|
||||
{
|
||||
/* At this point there are several possible states:
|
||||
1) Empty or partial buffer (unqueued) - dwFlags == 0
|
||||
2) Buffer queued or being played - dwFlags == WHDR_PREPARED | WHDR_INQUEUE
|
||||
3) Buffer unqueued and finished being played - dwFlags == WHDR_PREPARED | WHDR_DONE
|
||||
4) Buffer removed from queue, but not yet marked as done - dwFlags == WHDR_PREPARED
|
||||
*/
|
||||
|
||||
/* Check buffer header and wait if it's being played. */
|
||||
if (hdr->dwFlags & WHDR_PREPARED)
|
||||
{
|
||||
while(!(hdr->dwFlags & WHDR_DONE))
|
||||
{
|
||||
/*debug1("waiting for buffer %i...", state->next_buffer);*/
|
||||
/* Waits for *a* buffer to finish. May not be the one we
|
||||
want, so check again */
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
}
|
||||
hdr->dwFlags = 0;
|
||||
hdr->dwBufferLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_formats_win32(out123_handle *ao)
|
||||
{
|
||||
WAVEOUTCAPSA caps;
|
||||
MMRESULT mr;
|
||||
int ret = 0;
|
||||
UINT dev_id = dev_select(ao);
|
||||
|
||||
mr = waveOutGetDevCaps(dev_id, &caps, sizeof(caps));
|
||||
if(mr != MMSYSERR_NOERROR)
|
||||
return 0; /* no formats? */
|
||||
|
||||
if(ao->channels == 1) {
|
||||
switch(ao->rate) {
|
||||
case 44100:
|
||||
if(caps.dwFormats & WAVE_FORMAT_4M08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_4M16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
case 22050:
|
||||
if(caps.dwFormats & WAVE_FORMAT_2M08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_2M16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
case 11025:
|
||||
if(caps.dwFormats & WAVE_FORMAT_1M08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_1M16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ao->channels == 2) {
|
||||
switch(ao->rate) {
|
||||
case 44100:
|
||||
if(caps.dwFormats & WAVE_FORMAT_4S08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_4S16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
case 22050:
|
||||
if(caps.dwFormats & WAVE_FORMAT_2S08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_2S16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
case 11025:
|
||||
if(caps.dwFormats & WAVE_FORMAT_2S08)
|
||||
ret |= MPG123_ENC_SIGNED_8;
|
||||
if(caps.dwFormats & WAVE_FORMAT_2S16)
|
||||
ret |= MPG123_ENC_SIGNED_16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Stores audio data to the fixed size buffers and pushes them into the playback queue.
|
||||
I have one grief with that: The last piece of a track may not reach the output,
|
||||
only full buffers sent... But we don't get smooth audio otherwise. */
|
||||
static int write_win32(out123_handle *ao, unsigned char *buf, int len)
|
||||
{
|
||||
struct queue_state* state;
|
||||
MMRESULT res;
|
||||
WAVEHDR* hdr;
|
||||
|
||||
int rest_len; /* Input data bytes left for next recursion. */
|
||||
int bufill; /* Bytes we stuff into buffer now. */
|
||||
|
||||
if(!ao || !ao->userptr) return -1;
|
||||
if(!buf || len <= 0) return 0;
|
||||
|
||||
state = (struct queue_state*)ao->userptr;
|
||||
hdr = &state->buffer_headers[state->next_buffer];
|
||||
|
||||
wait_for_buffer(hdr, state->play_done_event);
|
||||
|
||||
/* Now see how much we want to stuff in and then stuff it in. */
|
||||
bufill = BUFFER_SIZE - hdr->dwBufferLength;
|
||||
if(len < bufill) bufill = len;
|
||||
|
||||
rest_len = len - bufill;
|
||||
memcpy(hdr->lpData + hdr->dwBufferLength, buf, bufill);
|
||||
hdr->dwBufferLength += bufill;
|
||||
if(hdr->dwBufferLength == BUFFER_SIZE)
|
||||
{ /* Send the buffer out when it's full. */
|
||||
hdr->dwFlags |= WHDR_PREPARED;
|
||||
|
||||
res = waveOutWrite(state->waveout, hdr, sizeof(WAVEHDR));
|
||||
if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device.");
|
||||
|
||||
/* Cycle to the next buffer in the ring queue */
|
||||
state->next_buffer = (state->next_buffer + 1) % NUM_BUFFERS;
|
||||
}
|
||||
/* I'd like to propagate error codes or something... but there are no catchable surprises left.
|
||||
Anyhow: Here is the recursion that makes ravenexp happy;-) */
|
||||
if(rest_len && write_win32(ao, buf + bufill, rest_len) < 0) /* Write the rest. */
|
||||
return -1;
|
||||
else
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Flush means abort any pending playback */
|
||||
static void flush_win32(out123_handle *ao)
|
||||
{
|
||||
struct queue_state* state;
|
||||
WAVEHDR* hdr;
|
||||
|
||||
if(!ao || !ao->userptr) return;
|
||||
state = (struct queue_state*)ao->userptr;
|
||||
|
||||
/* Cancel any buffers in queue. Ignore errors since we are void and
|
||||
can't return them anyway */
|
||||
waveOutReset(state->waveout);
|
||||
|
||||
/* Discard any partial buffer */
|
||||
hdr = &state->buffer_headers[state->next_buffer];
|
||||
|
||||
/* If WHDR_PREPARED is not set, this is (potentially) a partial buffer */
|
||||
if (!(hdr->dwFlags & WHDR_PREPARED))
|
||||
hdr->dwBufferLength = 0;
|
||||
|
||||
/* Finish processing the buffers */
|
||||
drain_win32(ao);
|
||||
}
|
||||
|
||||
/* output final buffer (if any) */
|
||||
static void write_final_buffer(struct queue_state *state)
|
||||
{
|
||||
WAVEHDR* hdr;
|
||||
hdr = &state->buffer_headers[state->next_buffer];
|
||||
if((!(hdr->dwFlags & WHDR_PREPARED)) && (hdr->dwBufferLength != 0))
|
||||
{
|
||||
hdr->dwFlags |= WHDR_PREPARED;
|
||||
/* ignore any errors */
|
||||
waveOutWrite(state->waveout, hdr, sizeof(WAVEHDR));
|
||||
|
||||
/* Cycle to the next buffer in the ring queue */
|
||||
state->next_buffer = (state->next_buffer + 1) % NUM_BUFFERS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note: I tried to fix this stuff without testing.
|
||||
There were some obvious errors in the code.
|
||||
Someone run this on a win32 machine! -- ThOr */
|
||||
static void drain_win32(out123_handle *ao)
|
||||
{
|
||||
int i, z;
|
||||
struct queue_state* state;
|
||||
|
||||
if(!ao || !ao->userptr) return;
|
||||
state = (struct queue_state*)ao->userptr;
|
||||
|
||||
/* output final buffer (if any) */
|
||||
write_final_buffer(state);
|
||||
|
||||
/* I _think_ I understood how this should work. -- ThOr */
|
||||
z = state->next_buffer;
|
||||
for(i = 0; i < NUM_BUFFERS; i++)
|
||||
{
|
||||
wait_for_buffer(&state->buffer_headers[z], state->play_done_event);
|
||||
z = (z + 1) % NUM_BUFFERS;
|
||||
}
|
||||
}
|
||||
|
||||
static int close_win32(out123_handle *ao)
|
||||
{
|
||||
int i;
|
||||
struct queue_state* state;
|
||||
|
||||
if(!ao || !ao->userptr) return -1;
|
||||
state = (struct queue_state*)ao->userptr;
|
||||
|
||||
/* wait for all active buffers to complete */
|
||||
drain_win32(ao);
|
||||
CloseHandle(state->play_done_event);
|
||||
|
||||
for(i = 0; i < NUM_BUFFERS; i++)
|
||||
{
|
||||
state->buffer_headers[i].dwFlags |= WHDR_PREPARED;
|
||||
waveOutUnprepareHeader(state->waveout, &state->buffer_headers[i], sizeof(WAVEHDR));
|
||||
free(state->buffer_headers[i].lpData);
|
||||
}
|
||||
|
||||
waveOutClose(state->waveout);
|
||||
free(ao->userptr);
|
||||
ao->userptr = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enumerate_win32( out123_handle *ao, int (*store_device)(void *devlist
|
||||
, const char *name, const char *description), void *devlist )
|
||||
{
|
||||
char id[10];
|
||||
WAVEOUTCAPSA caps;
|
||||
MMRESULT mr;
|
||||
UINT i, devices = waveOutGetNumDevs();
|
||||
for(i = 0; i < devices; i++){
|
||||
memset(id, 0, sizeof(id));
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
mr = waveOutGetDevCaps(i, &caps, sizeof(caps));
|
||||
mdebug("waveOutGetDevCaps mr %x", mr);
|
||||
snprintf(id, sizeof(id) - 1, "%u", i);
|
||||
store_device(devlist, id, caps.szPname);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_win32(out123_handle* ao)
|
||||
{
|
||||
if(!ao) return -1;
|
||||
|
||||
/* Set callbacks */
|
||||
ao->open = open_win32;
|
||||
ao->flush = flush_win32;
|
||||
ao->write = write_win32;
|
||||
ao->get_formats = get_formats_win32;
|
||||
ao->close = close_win32;
|
||||
ao->enumerate = enumerate_win32;
|
||||
|
||||
/* Success */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Module information data structure
|
||||
*/
|
||||
mpg123_module_t mpg123_output_module_info = {
|
||||
/* api_version */ MPG123_MODULE_API_VERSION,
|
||||
/* name */ "win32",
|
||||
/* description */ "Audio output for Windows (winmm).",
|
||||
/* revision */ "$Rev:$",
|
||||
/* handle */ NULL,
|
||||
|
||||
/* init_output */ init_win32,
|
||||
};
|
||||
Reference in New Issue
Block a user