#define AL_LOG_SECTION "audio_miniaudio" #include #if defined(__clang__) || defined(__GNUC__) AL_IGNORE_WARNING("-Wunused-function") AL_IGNORE_WARNING("-Wunused-variable") AL_IGNORE_WARNING("-Wincompatible-pointer-types") AL_IGNORE_WARNING("-Wunknown-pragmas") #elif _MSC_VER #pragma warning(push, 0) #endif #define MINIAUDIO_IMPLEMENTATION #include "audio_miniaudio.h" #if defined(__clang__) || defined(__GNUC__) AL_IGNORE_WARNING_END #elif _MSC_VER #pragma warning(pop) #endif static ma_backend backends[] = { #ifdef MA_ENABLE_PULSEAUDIO ma_backend_pulseaudio, #endif #ifdef MA_ENABLE_ALSA ma_backend_alsa, #endif #ifdef MA_ENABLE_WASAPI ma_backend_wasapi, #endif #ifdef MA_ENABLE_DSOUND ma_backend_dsound, #endif #ifdef MA_ENABLE_AAUDIO ma_backend_aaudio, #endif }; static void *ma_on_malloc(size_t size, void *userdata) { (void)userdata; return al_malloc(size); } static void *ma_on_realloc(void *ptr, size_t size, void *userdata) { (void)userdata; return al_realloc(ptr, size); } static void ma_on_free(void *ptr, void *userdata) { (void)userdata; al_free(ptr); } static ma_allocation_callbacks alloc_callbacks = { .pUserData = NULL, .onMalloc = ma_on_malloc, .onRealloc = ma_on_realloc, .onFree = ma_on_free }; static void log_callback(void *userdata, u32 level, const char *message) { (void)userdata; if (level == MA_LOG_LEVEL_ERROR) { log_error(message); } else if (level == MA_LOG_LEVEL_WARNING) { log_warn(message); } else { log_debug(message); } } static bool audio_ma_init(struct camu_audio *audio, str *name) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; #ifdef CAMU_NO_MINIAUDIO_BACKENDS al_assert_and_return(false); #endif ma_log_init(&alloc_callbacks, &ma->log); ma_log_register_callback(&ma->log, ma_log_callback_init(log_callback, NULL)); ma->context_config = ma_context_config_init(); ma->context_config.pLog = &ma->log; #ifdef MA_ENABLE_PULSEAUDIO char *c_str = al_str_to_c_str(name); ma->context_config.pulse.pApplicationName = c_str; ma->context_config.pulse.tryAutoSpawn = MA_TRUE; #else (void)name; #endif #ifdef MA_ENABLE_ALSA ma->context_config.alsa.useVerboseDeviceEnumeration = MA_TRUE; #endif s32 ret = ma_context_init(backends, ARRAY_SIZE(backends), &ma->context_config, &ma->context); if (ret != MA_SUCCESS) { log_error("Failed to initialize context (%d).", ret); return false; } ma->selected_device = NULL; /* if (ma_context_get_devices(&ma->context, &ma->infos, &ma->info_count, NULL, NULL) != MA_SUCCESS) { return false; } for (u32 i = 0; i < ma->info_count; i++) { ma_device_info *info = &ma->infos[i]; if (strcmp(info->id.alsa, "front:CARD=D10,DEV=0") == 0) { ma->selected_device = info; break; } ma_device_info real_info; ma_context_get_device_info(&ma->context, ma_device_type_playback, &info->id, &real_info); for (u32 j = 0; j < real_info.nativeDataFormatCount; j++) { al_printf("%s\n", real_info.id.alsa); al_printf("%s\n", real_info.id.pulse); al_printf("%i\n", real_info.nativeDataFormats[j].format); al_printf("%i\n", real_info.nativeDataFormats[j].channels); al_printf("%i\n", real_info.nativeDataFormats[j].sampleRate); al_printf("\n"); } } */ return true; } static s32 camu_sample_format_from_miniaudio(s32 fmt) { switch (fmt) { case ma_format_u8: return CAMU_SAMPLE_FORMAT_U8; case ma_format_s16: return CAMU_SAMPLE_FORMAT_S16; case ma_format_s24: case ma_format_s32: return CAMU_SAMPLE_FORMAT_S32; case ma_format_f32: return CAMU_SAMPLE_FORMAT_FLT; case ma_format_unknown: default: al_assert_and_return(CAMU_SAMPLE_FORMAT_NONE); } } static void audio_ma_pick_format(struct camu_audio *audio, struct camu_resampler_format *fmt) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; fmt->req.format = camu_sample_format_from_miniaudio(ma->device.playback.format); fmt->req.sample_rate = ma->device.playback.internalSampleRate; u32 channels = ma->device.playback.channels; #ifdef CAMU_HAVE_FFMPEG camu_default_channel_layout(&fmt->req.channel_layout, channels); #endif fmt->req.channel_count = channels; } static void data_callback(ma_device *device, void *output, const void *input, u32 frame_count) { struct camu_audio_ma *ma = (struct camu_audio_ma *)device->pUserData; (void)input; bool silence; ma->a.data_callback(ma->a.userdata, (u8 *)output, frame_count, &silence); } //#define MINIAUDIO_LOW_LATENCY #ifdef MINIAUDIO_LOW_LATENCY #define DEFAULT_PERIODS 2 #define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 8 #else #define DEFAULT_PERIODS 3 #define DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 48 #endif static bool audio_ma_configure_stream(struct camu_audio *audio, void *opaque) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; struct camu_ff_resample_fmt *fmt = (struct camu_ff_resample_fmt *)opaque; (void)fmt; ma->config = ma_device_config_init(ma_device_type_playback); if (ma->selected_device) { ma->config.playback.pDeviceID = &ma->selected_device->id; } #ifdef MINIAUDIO_LOW_LATENCY ma->config.performanceProfile = ma_performance_profile_low_latency; #else ma->config.performanceProfile = ma_performance_profile_conservative; #endif ma->config.periods = DEFAULT_PERIODS; ma->config.periodSizeInMilliseconds = DEFAULT_PERIOD_SIZE_IN_MILLISECONDS; ma->config.periodSizeInFrames = 0; // Derive from periodSizeInMilliseconds. ma->config.playback.format = ma_format_unknown; ma->config.playback.channels = 2; ma->config.sampleRate = 0; ma->config.dataCallback = data_callback; ma->config.pUserData = ma; ma->config.noPreSilencedOutputBuffer = MA_TRUE; ma->config.noFixedSizedCallback = MA_TRUE; ma->config.noClip = MA_TRUE; ma->config.playback.shareMode = ma_share_mode_shared; #ifdef MA_ENABLE_PULSEAUDIO ma->config.pulse.pStreamNamePlayback = ma->context_config.pulse.pApplicationName; #endif #ifdef MA_ENABLE_ALSA ma->config.alsa.noAutoFormat = MA_TRUE; ma->config.alsa.noAutoChannels = MA_TRUE; ma->config.alsa.noAutoResample = MA_TRUE; #endif #ifdef MA_ENABLE_WASAPI ma->config.wasapi.noAutoConvertSRC = MA_TRUE; ma->config.wasapi.noDefaultQualitySRC = MA_TRUE; #endif #ifdef MA_ENABLE_AAUDIO ma->config.aaudio.usage = ma_aaudio_usage_media; ma->config.aaudio.contentType = ma_aaudio_content_type_music; ma->config.aaudio.allowedCapturePolicy = ma_aaudio_allow_capture_by_all; #endif s32 ret = ma_device_init(&ma->context, &ma->config, &ma->device); if (ret != MA_SUCCESS) { log_error("Failed to initialize device (%d).", ret); return false; } s32 format = camu_sample_format_from_miniaudio(ma->device.playback.internalFormat); const char *format_name = camu_audio_format_name(format); log_info("Output: %s (%dch) %dHz.", format_name, ma->device.playback.internalChannels, ma->device.playback.internalSampleRate); return true; } static u64 audio_ma_get_latency(struct camu_audio *audio) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; log_debug("periods: %u, period_size_in_frames: %u, sample_rate: %u.", ma->device.playback.internalPeriods, ma->device.playback.internalPeriodSizeInFrames, ma->device.playback.internalSampleRate); u32 periods = ma->device.playback.internalPeriods; return (ma->device.playback.internalPeriodSizeInFrames * periods) / (ma->device.playback.internalSampleRate / 1000000.0); } static void audio_ma_start(struct camu_audio *audio) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; ma_device_start(&ma->device); } static void audio_ma_stop(struct camu_audio *audio) { struct camu_audio_ma *ma = (struct camu_audio_ma *)audio; ma_device_stop(&ma->device); } static void audio_ma_free(struct camu_audio **audio) { struct camu_audio_ma *ma = (struct camu_audio_ma *)*audio; ma_device_uninit(&ma->device); ma_context_uninit(&ma->context); #ifdef MA_ENABLE_PULSEAUDIO al_free((void *)ma->context_config.pulse.pApplicationName); #endif al_free(ma); *audio = NULL; } struct camu_audio *camu_audio_miniaudio_create(void) { struct camu_audio_ma *ma = al_alloc_object(struct camu_audio_ma); ma->a = (struct camu_audio){ .init = audio_ma_init, .pick_format = audio_ma_pick_format, .configure_stream = audio_ma_configure_stream, .get_latency = audio_ma_get_latency, .start = audio_ma_start, .stop = audio_ma_stop, .free = audio_ma_free, .data_callback = NULL, .userdata = NULL }; return (struct camu_audio *)ma; }