-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaudio.c
200 lines (162 loc) · 6.15 KB
/
audio.c
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include "audio.h"
#include <assert.h>
#define CLOWNCD_AUDIO_SAMPLE_RATE 44100
#define CLOWNCD_AUDIO_TOTAL_CHANNELS 2
/* TODO: Move this to a user-provided buffer, in case the user doesn't want to always keep this allocated? */
static ClownResampler_Precomputed clowncd_precomputed;
static cc_bool clowncd_precomputed_done;
cc_bool ClownCD_AudioOpen(ClownCD_Audio* const audio, ClownCD_File* const file)
{
ClownCD_AudioMetadata metadata;
audio->format = CLOWNCD_AUDIO_INVALID;
#ifdef CLOWNCD_LIBSNDFILE
if (ClownCD_libSndFileOpen(&audio->formats.libsndfile, file, &metadata))
audio->format = CLOWNCD_AUDIO_LIBSNDFILE;
else
#else
if (ClownCD_FLACOpen(&audio->formats.flac, file, &metadata))
audio->format = CLOWNCD_AUDIO_FLAC;
else if (ClownCD_MP3Open(&audio->formats.mp3, file, &metadata))
audio->format = CLOWNCD_AUDIO_MP3;
else if (ClownCD_VorbisOpen(&audio->formats.vorbis, file, &metadata))
audio->format = CLOWNCD_AUDIO_VORBIS;
else if (ClownCD_WAVOpen(&audio->formats.wav, file, &metadata))
audio->format = CLOWNCD_AUDIO_WAV;
#endif
/* Verify that the audio is in a supported format. */
/* TODO: Support mono audio! */
if (audio->format == CLOWNCD_AUDIO_INVALID || (metadata.total_channels != 1 && metadata.total_channels != 2))
return cc_false;
if (!clowncd_precomputed_done)
{
clowncd_precomputed_done = cc_true;
ClownResampler_Precompute(&clowncd_precomputed);
}
/* Resample to the native CD sample rate. */
ClownResampler_HighLevel_Init(&audio->resampler, metadata.total_channels, metadata.sample_rate, CLOWNCD_AUDIO_SAMPLE_RATE, CLOWNCD_AUDIO_SAMPLE_RATE);
return cc_true;
}
void ClownCD_AudioClose(ClownCD_Audio* const audio)
{
switch (audio->format)
{
case CLOWNCD_AUDIO_INVALID:
break;
#ifdef CLOWNCD_LIBSNDFILE
case CLOWNCD_AUDIO_LIBSNDFILE:
ClownCD_libSndFileClose(&audio->formats.libsndfile);
break;
#else
case CLOWNCD_AUDIO_FLAC:
ClownCD_FLACClose(&audio->formats.flac);
break;
case CLOWNCD_AUDIO_MP3:
ClownCD_MP3Close(&audio->formats.mp3);
break;
case CLOWNCD_AUDIO_VORBIS:
ClownCD_VorbisClose(&audio->formats.vorbis);
break;
case CLOWNCD_AUDIO_WAV:
ClownCD_WAVClose(&audio->formats.wav);
break;
#endif
default:
assert(cc_false);
break;
}
}
cc_bool ClownCD_AudioSeek(ClownCD_Audio* const audio, const size_t frame)
{
/* Cheeky hack to account for resampling. */
/* This is a fixed-point multiplication that splits the multiplicand to avoid overflow. */
const size_t corrected_frame_upper = audio->resampler.low_level.increment * (frame / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE);
const size_t corrected_frame_lower = (audio->resampler.low_level.increment * (frame % CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE)) / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE;
const size_t corrected_frame = corrected_frame_upper + corrected_frame_lower;
switch (audio->format)
{
case CLOWNCD_AUDIO_INVALID:
return cc_false;
#ifdef CLOWNCD_LIBSNDFILE
case CLOWNCD_AUDIO_LIBSNDFILE:
return ClownCD_libSndFileSeek(&audio->formats.libsndfile, corrected_frame);
#else
case CLOWNCD_AUDIO_FLAC:
return ClownCD_FLACSeek(&audio->formats.flac, corrected_frame);
case CLOWNCD_AUDIO_MP3:
return ClownCD_MP3Seek(&audio->formats.mp3, corrected_frame);
case CLOWNCD_AUDIO_VORBIS:
return ClownCD_VorbisSeek(&audio->formats.vorbis, corrected_frame);
case CLOWNCD_AUDIO_WAV:
return ClownCD_WAVSeek(&audio->formats.wav, corrected_frame);
#endif
default:
assert(cc_false);
return cc_false;
}
}
typedef struct ClownCD_ResamplerCallbackData
{
ClownCD_Audio *audio;
short *output_pointer;
size_t output_buffer_frames_remaining;
} ClownCD_ResamplerCallbackData;
static size_t ClownCD_ResamplerInputCallback(void* const user_data, cc_s16l* const buffer, const size_t total_frames)
{
const ClownCD_ResamplerCallbackData* const callback_data = (const ClownCD_ResamplerCallbackData*)user_data;
ClownCD_Audio* const audio = callback_data->audio;
switch (audio->format)
{
case CLOWNCD_AUDIO_INVALID:
return 0;
#ifdef CLOWNCD_LIBSNDFILE
case CLOWNCD_AUDIO_LIBSNDFILE:
return ClownCD_libSndFileRead(&audio->formats.libsndfile, buffer, total_frames);
#else
case CLOWNCD_AUDIO_FLAC:
return ClownCD_FLACRead(&audio->formats.flac, buffer, total_frames);
case CLOWNCD_AUDIO_MP3:
return ClownCD_MP3Read(&audio->formats.mp3, buffer, total_frames);
case CLOWNCD_AUDIO_VORBIS:
return ClownCD_VorbisRead(&audio->formats.vorbis, buffer, total_frames);
case CLOWNCD_AUDIO_WAV:
return ClownCD_WAVRead(&audio->formats.wav, buffer, total_frames);
#endif
default:
assert(cc_false);
return 0;
}
}
static cc_bool ClownCD_ResamplerOutputCallback(void* const user_data, const cc_s32f* const frame, const cc_u8f total_samples)
{
ClownCD_ResamplerCallbackData* const callback_data = (ClownCD_ResamplerCallbackData*)user_data;
cc_u8f i;
/* Output the frame. */
for (i = 0; i < total_samples; ++i)
{
cc_s32f sample;
sample = frame[i];
/* Clamp the sample to 16-bit. */
if (sample > 0x7FFF)
sample = 0x7FFF;
else if (sample < -0x7FFF)
sample = -0x7FFF;
/* Push the sample to the output buffer. */
callback_data->output_pointer[i] = (short)sample;
}
/* Upsample mono to stereo. */
if (total_samples == 1)
callback_data->output_pointer[1] = callback_data->output_pointer[0];
callback_data->output_pointer += CLOWNCD_AUDIO_TOTAL_CHANNELS;
/* Signal whether there is more room in the output buffer. */
return --callback_data->output_buffer_frames_remaining != 0;
}
size_t ClownCD_AudioRead(ClownCD_Audio* const audio, short* const buffer, const size_t total_frames)
{
ClownCD_ResamplerCallbackData callback_data;
callback_data.audio = audio;
callback_data.output_pointer = buffer;
callback_data.output_buffer_frames_remaining = total_frames;
/* Resample the decoded audio data. */
ClownResampler_HighLevel_Resample(&audio->resampler, &clowncd_precomputed, ClownCD_ResamplerInputCallback, ClownCD_ResamplerOutputCallback, &callback_data);
return total_frames - callback_data.output_buffer_frames_remaining;
}