/* SPDX-License-Identifier: GPL-3.0-or-later */ /* * vfetch - neofetch but the ascii part is a video * * Copyright (C) 2026 Doan Luu Duc Tai * */ #include "audio.h" #include #include #include #include "audio_defs.h" #include "miniaudio.h" #include "pl_mpeg.h" /* This warning is suppressed because this function is a callback that * implements a specific signature required by the miniaudio library * (ma_data_callback). The library itself dictates the order of parameters * (pOutput, pInput), and we cannot change it. Since this function is not called * directly by our own code but by the audio library, the risk of a developer * accidentally swapping the parameters is nonexistent. */ static void data_callback(ma_device *pDevice, /* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) */ void *pOutput, const void *pInput, ma_uint32 frame_count) { (void)pInput; AudioState *audio = (AudioState *)pDevice->pUserData; float *out = (float *)pOutput; ma_uint32 total_frames_written = 0; while (total_frames_written < frame_count) { if (audio->buffer_index >= audio->buffer_count) { plm_samples_t *samples = plm_decode_audio(audio->plm); if (!samples) { (void)memset(out + ((size_t)total_frames_written * 2), 0, (frame_count - total_frames_written) * sizeof(float) * 2); return; } (void)memcpy(audio->sample_buffer, samples->interleaved, sizeof(float) * (size_t)samples->count * 2); audio->buffer_index = 0; audio->buffer_count = samples->count; } ma_uint32 frames_to_copy = frame_count - total_frames_written; ma_uint32 frames_available = audio->buffer_count - audio->buffer_index; if (frames_to_copy > frames_available) { frames_to_copy = frames_available; } (void)memcpy(out + ((size_t)total_frames_written * 2), audio->sample_buffer + ((size_t)audio->buffer_index * 2), sizeof(float) * (size_t)frames_to_copy * 2); audio->buffer_index += frames_to_copy; total_frames_written += frames_to_copy; } atomic_fetch_add(&audio->frames_consumed, frame_count); } #define K_MPG_SAMPLES_PER_FRAME 1152 VfetchError initialize_audio(AppState *state, const char *audio_source_file) { AudioState *audio = &state->audio_state; audio->state = state; audio->buffer_index = 0; audio->buffer_count = 0; audio->sample_buffer = NULL; audio->plm = plm_create_with_filename(audio_source_file); if (!audio->plm || !plm_get_num_audio_streams(audio->plm)) { return VFETCH_ERROR_MA_INIT; } ma_device_config config = ma_device_config_init(ma_device_type_playback); config.playback.format = ma_format_f32; config.playback.channels = 2; config.sampleRate = (ma_uint32)plm_get_samplerate(audio->plm); config.dataCallback = data_callback; config.pUserData = audio; if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) { plm_destroy(audio->plm); return VFETCH_ERROR_MA_INIT; } audio->sample_buffer = malloc(sizeof(float) * K_MPG_SAMPLES_PER_FRAME * 2); if (!audio->sample_buffer) { return VFETCH_ERROR_MEMORY_ALLOCATION; } ma_uint32 buffered_frames = audio->device.playback.internalPeriodSizeInFrames * audio->device.playback.internalPeriods; if (audio->device.sampleRate > 0) { audio->output_latency_seconds = (double)buffered_frames / (double)audio->device.sampleRate; } else { audio->output_latency_seconds = 0.0; } if (ma_device_start(&audio->device) != MA_SUCCESS) { shutdown_audio(state); return VFETCH_ERROR_MA_INIT; } return VFETCH_SUCCESS; } #undef K_MPG_SAMPLES_PER_FRAME void shutdown_audio(AppState *state) { ma_device_uninit(&state->audio_state.device); if (state->audio_state.plm) { plm_destroy(state->audio_state.plm); } if (state->audio_state.sample_buffer) { free(state->audio_state.sample_buffer); } }