mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-26 04:20:30 +00:00
Get printvideo audio working on Windows and MacOS
This commit is contained in:
parent
07fde68d52
commit
5d3b91d8b9
18 changed files with 93470 additions and 212 deletions
3
dsp/audio/cosmoaudio/.gitignore
vendored
Normal file
3
dsp/audio/cosmoaudio/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
/Debug
|
||||
/Release
|
86
dsp/audio/cosmoaudio/Makefile.msvc
Normal file
86
dsp/audio/cosmoaudio/Makefile.msvc
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Makefile for MSVC x64 Command Line Developer Tools
|
||||
#
|
||||
# nmake /f Makefile.msvc check
|
||||
# nmake /f Makefile.msvc MODE=debug check
|
||||
#
|
||||
|
||||
# Compiler and linker
|
||||
CC=cl
|
||||
LINK=link
|
||||
|
||||
# Build mode (can be overridden from command line)
|
||||
!IFNDEF MODE
|
||||
MODE=release
|
||||
!ENDIF
|
||||
|
||||
# Library dependencies.
|
||||
TEST_LIBS=OneCore.lib
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS_COMMON=/nologo /W4 /Gy /EHsc
|
||||
CFLAGS_DEBUG=/Od /Zi /MDd /D_DEBUG
|
||||
CFLAGS_RELEASE=/O2 /MD /DNDEBUG
|
||||
|
||||
!IF "$(MODE)"=="debug"
|
||||
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_DEBUG)
|
||||
LDFLAGS=/DEBUG
|
||||
OUT_DIR=Debug
|
||||
!ELSE
|
||||
CFLAGS=$(CFLAGS_COMMON) $(CFLAGS_RELEASE) /GL
|
||||
LDFLAGS=/RELEASE /OPT:REF /OPT:ICF /LTCG /INCREMENTAL:NO
|
||||
OUT_DIR=Release
|
||||
!ENDIF
|
||||
|
||||
# Additional flags for DLL
|
||||
DLL_CFLAGS=$(CFLAGS) /D_USRDLL /D_WINDLL
|
||||
|
||||
# Linker flags
|
||||
LDFLAGS=/NOLOGO /SUBSYSTEM:CONSOLE $(LDFLAGS)
|
||||
|
||||
# Output file names
|
||||
DLL_TARGET=$(OUT_DIR)\cosmoaudio.dll
|
||||
TEST_TARGET=$(OUT_DIR)\test.exe
|
||||
|
||||
# Source files
|
||||
DLL_SOURCES=cosmoaudio.c
|
||||
TEST_SOURCES=test.c
|
||||
|
||||
# Object files
|
||||
DLL_OBJECTS=$(OUT_DIR)\cosmoaudio.obj
|
||||
TEST_OBJECTS=$(OUT_DIR)\test.obj
|
||||
|
||||
# Default target
|
||||
all: $(OUT_DIR) $(DLL_TARGET) $(TEST_TARGET)
|
||||
|
||||
# Create output directory
|
||||
$(OUT_DIR):
|
||||
if not exist $(OUT_DIR) mkdir $(OUT_DIR)
|
||||
|
||||
# Rule to build the DLL
|
||||
$(DLL_TARGET): $(OUT_DIR) $(DLL_OBJECTS)
|
||||
$(LINK) /DLL $(LDFLAGS) /OUT:$(DLL_TARGET) $(DLL_OBJECTS)
|
||||
|
||||
# Rule to build the test program
|
||||
$(TEST_TARGET): $(OUT_DIR) $(TEST_OBJECTS) $(DLL_TARGET)
|
||||
$(LINK) $(LDFLAGS) /OUT:$(TEST_TARGET) $(TEST_OBJECTS) $(DLL_TARGET:.dll=.lib) $(TEST_LIBS)
|
||||
|
||||
# Rules to compile .c files to .obj files with header dependencies
|
||||
{.}.c{$(OUT_DIR)}.obj:
|
||||
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ $<
|
||||
|
||||
$(OUT_DIR)\test.obj: $(OUT_DIR) test.c cosmoaudio.h
|
||||
$(CC) $(CFLAGS) /c /Fo$(OUT_DIR)\ test.c
|
||||
|
||||
$(OUT_DIR)\cosmoaudio.obj: $(OUT_DIR) cosmoaudio.c miniaudio.h cosmoaudio.h
|
||||
$(CC) $(DLL_CFLAGS) /c /Fo$(OUT_DIR)\ cosmoaudio.c
|
||||
|
||||
# Clean target
|
||||
clean:
|
||||
if exist $(OUT_DIR) rmdir /s /q $(OUT_DIR)
|
||||
|
||||
# Run tests (now called 'check')
|
||||
check: $(TEST_TARGET)
|
||||
$(TEST_TARGET)
|
||||
|
||||
# Phony targets
|
||||
.PHONY: all clean check
|
251
dsp/audio/cosmoaudio/cosmoaudio.c
Normal file
251
dsp/audio/cosmoaudio/cosmoaudio.c
Normal file
|
@ -0,0 +1,251 @@
|
|||
#define COSMOAUDIO_BUILD
|
||||
#include "cosmoaudio.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MA_STATIC
|
||||
#define MA_NO_DECODING
|
||||
#define MA_NO_ENCODING
|
||||
#ifdef NDEBUG
|
||||
#define MA_DR_MP3_NO_STDIO
|
||||
#endif
|
||||
#define MINIAUDIO_IMPLEMENTATION
|
||||
#include "miniaudio.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define LOG(...) fprintf(stderr, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(...) (void)0
|
||||
#endif
|
||||
|
||||
struct CosmoAudio {
|
||||
ma_device device;
|
||||
ma_pcm_rb input;
|
||||
ma_pcm_rb output;
|
||||
ma_uint32 sampleRate;
|
||||
ma_uint32 channels;
|
||||
};
|
||||
|
||||
static int read_ring_buffer(ma_pcm_rb* rb, float* pOutput, ma_uint32 frameCount,
|
||||
ma_uint32 channels) {
|
||||
ma_result result;
|
||||
ma_uint32 framesRead;
|
||||
ma_uint32 framesToRead;
|
||||
for (framesRead = 0; framesRead < frameCount; framesRead += framesToRead) {
|
||||
framesToRead = frameCount - framesRead;
|
||||
void* pMappedBuffer;
|
||||
result = ma_pcm_rb_acquire_read(rb, &framesToRead, &pMappedBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
LOG("ma_pcm_rb_acquire_read failed: %s\n", ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
if (!framesToRead)
|
||||
break;
|
||||
memcpy(pOutput + framesRead * channels, pMappedBuffer,
|
||||
framesToRead * channels * sizeof(float));
|
||||
result = ma_pcm_rb_commit_read(rb, framesToRead);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (result == MA_AT_END)
|
||||
break;
|
||||
LOG("ma_pcm_rb_commit_read failed: %s\n", ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
static int write_ring_buffer(ma_pcm_rb* rb, const float* pInput,
|
||||
ma_uint32 frameCount, ma_uint32 channels) {
|
||||
ma_result result;
|
||||
ma_uint32 framesWritten;
|
||||
ma_uint32 framesToWrite;
|
||||
for (framesWritten = 0; framesWritten < frameCount;
|
||||
framesWritten += framesToWrite) {
|
||||
framesToWrite = frameCount - framesWritten;
|
||||
void* pMappedBuffer;
|
||||
result = ma_pcm_rb_acquire_write(rb, &framesToWrite, &pMappedBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
LOG("ma_pcm_rb_acquire_write failed: %s\n",
|
||||
ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
if (!framesToWrite)
|
||||
break;
|
||||
memcpy(pMappedBuffer, pInput + framesWritten * channels,
|
||||
framesToWrite * channels * sizeof(float));
|
||||
result = ma_pcm_rb_commit_write(rb, framesToWrite);
|
||||
if (result != MA_SUCCESS) {
|
||||
if (result == MA_AT_END)
|
||||
break;
|
||||
LOG("ma_pcm_rb_commit_write failed: %s\n", ma_result_description(result));
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
}
|
||||
return framesWritten;
|
||||
}
|
||||
|
||||
static void data_callback_f32(ma_device* pDevice, float* pOutput,
|
||||
const float* pInput, ma_uint32 frameCount) {
|
||||
struct CosmoAudio* ca = (struct CosmoAudio*)pDevice->pUserData;
|
||||
read_ring_buffer(&ca->output, pOutput, frameCount, ca->channels);
|
||||
write_ring_buffer(&ca->input, pInput, frameCount, ca->channels);
|
||||
}
|
||||
|
||||
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
|
||||
ma_uint32 frameCount) {
|
||||
data_callback_f32(pDevice, (float*)pOutput, (const float*)pInput, frameCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens access to speaker and microphone.
|
||||
*
|
||||
* @param cap will receive pointer to allocated CosmoAudio object on success,
|
||||
* which must be freed by caller with cosmoaudio_close()
|
||||
* @param sampleRate is sample rate in Hz, e.g. 44100
|
||||
* @param channels is number of channels (1 for mono, 2 for stereo)
|
||||
* @return 0 on success, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio** cap, int sampleRate,
|
||||
int channels) {
|
||||
|
||||
// Allocate cosmo audio object.
|
||||
struct CosmoAudio* ca;
|
||||
if (!(ca = (struct CosmoAudio*)malloc(sizeof(struct CosmoAudio))))
|
||||
return COSMOAUDIO_ERROR;
|
||||
ca->channels = channels;
|
||||
ca->sampleRate = sampleRate;
|
||||
|
||||
// Initialize device.
|
||||
ma_result result;
|
||||
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||
deviceConfig.sampleRate = sampleRate;
|
||||
deviceConfig.capture.channels = channels;
|
||||
deviceConfig.capture.format = ma_format_f32;
|
||||
deviceConfig.capture.shareMode = ma_share_mode_shared;
|
||||
deviceConfig.playback.channels = channels;
|
||||
deviceConfig.playback.format = ma_format_f32;
|
||||
deviceConfig.dataCallback = data_callback;
|
||||
deviceConfig.pUserData = ca;
|
||||
result = ma_device_init(NULL, &deviceConfig, &ca->device);
|
||||
if (result != MA_SUCCESS) {
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
// Initialize the speaker ring buffer.
|
||||
result = ma_pcm_rb_init(ma_format_f32, channels,
|
||||
ca->device.playback.internalPeriodSizeInFrames * 10,
|
||||
NULL, NULL, &ca->output);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_device_uninit(&ca->device);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
|
||||
|
||||
// Initialize the microphone ring buffer.
|
||||
result = ma_pcm_rb_init(ma_format_f32, channels,
|
||||
ca->device.capture.internalPeriodSizeInFrames * 10,
|
||||
NULL, NULL, &ca->input);
|
||||
if (result != MA_SUCCESS) {
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
ma_device_uninit(&ca->device);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
ma_pcm_rb_set_sample_rate(&ca->output, sampleRate);
|
||||
|
||||
// Start audio playback.
|
||||
if (ma_device_start(&ca->device) != MA_SUCCESS) {
|
||||
ma_pcm_rb_uninit(&ca->input);
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
ma_device_uninit(&ca->device);
|
||||
free(ca);
|
||||
return COSMOAUDIO_ERROR;
|
||||
}
|
||||
|
||||
*cap = ca;
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes audio device and frees all associated resources.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @return 0 on success, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) {
|
||||
ma_device_uninit(&ca->device);
|
||||
ma_pcm_rb_uninit(&ca->output);
|
||||
free(ca);
|
||||
return COSMOAUDIO_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes raw audio data to speaker.
|
||||
*
|
||||
* The data is written to a ring buffer in real-time, which is then
|
||||
* played back very soon on the audio device. This has tolerence for
|
||||
* a certain amount of buffering, but expects that this function is
|
||||
* repeatedly called at a regular time interval. The caller should
|
||||
* have its own sleep loop for this purpose.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @param data is pointer to raw audio samples, expected to be in the range
|
||||
* -1.0 to 1.0, where channels are interleaved
|
||||
* @param frames is the number of frames (i.e. number of samples divided by
|
||||
* number of channels) from `data` to write to audio device
|
||||
* @return number of frames written, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data,
|
||||
int frames) {
|
||||
return write_ring_buffer(&ca->output, data, frames, ca->channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads raw audio data from microphone.
|
||||
*
|
||||
* The data is read from a ring buffer in real-time, which is then
|
||||
* played back very soon on the audio device. This has tolerence for
|
||||
* a certain amount of buffering, but expects that this function is
|
||||
* repeatedly called at a regular time interval. The caller should
|
||||
* have its own sleep loop for this purpose.
|
||||
*
|
||||
* @param ca is CosmoAudio object returned earlier by cosmoaudio_open()
|
||||
* @param data is pointer to raw audio samples, expected to be in the range
|
||||
* -1.0 to 1.0, where channels are interleaved
|
||||
* @param frames is the number of frames (i.e. number of samples divided by
|
||||
* number of channels) from `data` to read from microphone
|
||||
* @return number of frames read, or negative error code on failure
|
||||
*/
|
||||
COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data,
|
||||
int frames) {
|
||||
int read;
|
||||
for (int i = 0; i < frames; i += read) {
|
||||
int remaining = frames - i;
|
||||
read = read_ring_buffer(&ca->input, data + i * ca->channels, remaining,
|
||||
ca->channels);
|
||||
if (read < 0)
|
||||
return read;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <Windows.h>
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved) {
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
(void)hModule;
|
||||
(void)lpReserved;
|
||||
(void)ul_reason_for_call;
|
||||
}
|
||||
#endif
|
BIN
dsp/audio/cosmoaudio/cosmoaudio.dll
Normal file
BIN
dsp/audio/cosmoaudio/cosmoaudio.dll
Normal file
Binary file not shown.
41
dsp/audio/cosmoaudio/cosmoaudio.h
Normal file
41
dsp/audio/cosmoaudio/cosmoaudio.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef COSMOAUDIO_H_
|
||||
#define COSMOAUDIO_H_
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define COSMOAUDIO_ABI
|
||||
#ifdef COSMOAUDIO_BUILD
|
||||
#define COSMOAUDIO_API __declspec(dllexport)
|
||||
#else
|
||||
#define COSMOAUDIO_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define COSMOAUDIO_API
|
||||
#ifdef __x86_64__
|
||||
#define COSMOAUDIO_ABI __attribute__((__ms_abi__))
|
||||
#else
|
||||
#define COSMOAUDIO_ABI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define COSMOAUDIO_SUCCESS 0
|
||||
#define COSMOAUDIO_ERROR -1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct CosmoAudio;
|
||||
|
||||
COSMOAUDIO_API int cosmoaudio_open(struct CosmoAudio **, int,
|
||||
int) COSMOAUDIO_ABI;
|
||||
COSMOAUDIO_API int cosmoaudio_close(struct CosmoAudio *) COSMOAUDIO_ABI;
|
||||
COSMOAUDIO_API int cosmoaudio_write(struct CosmoAudio *, const float *,
|
||||
int) COSMOAUDIO_ABI;
|
||||
COSMOAUDIO_API int cosmoaudio_read(struct CosmoAudio *, float *,
|
||||
int) COSMOAUDIO_ABI;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* COSMOAUDIO_H_ */
|
0
dsp/audio/cosmoaudio/miniaudio.c
Normal file
0
dsp/audio/cosmoaudio/miniaudio.c
Normal file
92621
dsp/audio/cosmoaudio/miniaudio.h
Normal file
92621
dsp/audio/cosmoaudio/miniaudio.h
Normal file
File diff suppressed because it is too large
Load diff
36
dsp/audio/cosmoaudio/test.c
Normal file
36
dsp/audio/cosmoaudio/test.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include "cosmoaudio.h"
|
||||
|
||||
#ifndef M_PIf
|
||||
#define M_PIf 3.14159265358979323846f
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
|
||||
int hz = 44100;
|
||||
int channels = 2;
|
||||
struct CosmoAudio *ca;
|
||||
if (cosmoaudio_open(&ca, hz, channels) != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "%s: failed to open audio\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n = 1000;
|
||||
int sample = 0;
|
||||
float *buf = (float *)malloc(sizeof(float) * channels * n);
|
||||
for (;;) {
|
||||
for (int i = 0; i < 128; i++) {
|
||||
float freq = 440;
|
||||
float t = (float)sample++ / hz;
|
||||
if (sample == hz)
|
||||
sample = 0;
|
||||
buf[i * channels] = sinf(freq * 2.f * M_PIf * t);
|
||||
buf[i * channels + 1] = sinf(freq * 2.f * M_PIf * t);
|
||||
}
|
||||
cosmoaudio_write(ca, buf, 128);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue