summaryrefslogtreecommitdiff
path: root/src/audio.c
blob: 3db0d709d86bc4b6f3ace77b6d99b49ea6917f58 (plain)
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);
  }
}