1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
/* 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 <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#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);
}
}
|