Skip to content

Commit 2e73ef0

Browse files
committed
sndfile.c: Manually scale integer PCM sample formats when writing.
libsndfile scales integer PCM samples by 1/2^(bits-1) when reading and 2^(bits-1)-1 when writing. This way, floating point samples equal to +1.0 are not clipped, but int→float→int conversions are not bit perfect. This commit disables the built-in normalization for SF_FORMAT_PCM_* when writing and applies a scale factor of 2^(bits-1).
1 parent 881848d commit 2e73ef0

File tree

1 file changed

+52
-39
lines changed

1 file changed

+52
-39
lines changed

sndfile.c

+52-39
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ struct sndfile_type_info {
3030

3131
struct sndfile_enc_info {
3232
const char *name;
33-
int prec, can_dither, sf_enc;
33+
int prec, can_dither, do_scale, sf_enc;
3434
};
3535

3636
struct sndfile_state {
3737
SNDFILE *f;
3838
SF_INFO *info;
39+
sample_t scale;
3940
};
4041

4142
static const char codec_name[] = "sndfile";
@@ -71,39 +72,39 @@ static struct sndfile_type_info types[] = {
7172
};
7273

7374
static struct sndfile_enc_info encodings[] = {
74-
{ "s16", 16, 1, SF_FORMAT_PCM_16 },
75-
{ "s8", 8, 1, SF_FORMAT_PCM_S8 },
76-
{ "u8", 8, 1, SF_FORMAT_PCM_U8 },
77-
{ "s24", 24, 1, SF_FORMAT_PCM_24 },
78-
{ "s32", 32, 1, SF_FORMAT_PCM_32 },
79-
{ "float", 24, 0, SF_FORMAT_FLOAT },
80-
{ "double", 53, 0, SF_FORMAT_DOUBLE },
81-
{ "mu-law", 13, 0, SF_FORMAT_ULAW },
82-
{ "a-law", 14, 0, SF_FORMAT_ALAW },
83-
{ "ima_adpcm", 13, 0, SF_FORMAT_IMA_ADPCM },
84-
{ "ms_adpcm", 13, 0, SF_FORMAT_MS_ADPCM },
85-
{ "gsm6.10", 16, 0, SF_FORMAT_GSM610 },
86-
{ "vox_adpcm", 13, 0, SF_FORMAT_VOX_ADPCM },
87-
{ "nms_adpcm_16", 8, 0, SF_FORMAT_NMS_ADPCM_16 },
88-
{ "nms_adpcm_24", 8, 0, SF_FORMAT_NMS_ADPCM_24 },
89-
{ "nms_adpcm_32", 12, 0, SF_FORMAT_NMS_ADPCM_32 },
90-
{ "g721_32", 12, 0, SF_FORMAT_G721_32 },
91-
{ "g723_24", 8, 0, SF_FORMAT_G723_24 },
92-
{ "g723_40", 14, 0, SF_FORMAT_G723_40 },
93-
{ "dwvw_12", 12, 0, SF_FORMAT_DWVW_12 },
94-
{ "dwvw_16", 16, 0, SF_FORMAT_DWVW_16 },
95-
{ "dwvw_24", 24, 0, SF_FORMAT_DWVW_24 },
96-
{ "dpcm_8", 8, 0, SF_FORMAT_DPCM_8 },
97-
{ "dpcm_16", 16, 0, SF_FORMAT_DPCM_16 },
98-
{ "vorbis", 24, 0, SF_FORMAT_VORBIS },
99-
{ "opus", 24, 0, SF_FORMAT_OPUS },
100-
{ "alac_16", 16, 1, SF_FORMAT_ALAC_16 },
101-
{ "alac_20", 20, 1, SF_FORMAT_ALAC_20 },
102-
{ "alac_24", 24, 1, SF_FORMAT_ALAC_24 },
103-
{ "alac_32", 32, 1, SF_FORMAT_ALAC_32 },
104-
{ "mpeg1.1", 24, 0, SF_FORMAT_MPEG_LAYER_I },
105-
{ "mpeg1.2", 24, 0, SF_FORMAT_MPEG_LAYER_II },
106-
{ "mpeg2.3", 24, 0, SF_FORMAT_MPEG_LAYER_III },
75+
{ "s16", 16, 1, 1, SF_FORMAT_PCM_16 },
76+
{ "s8", 8, 1, 1, SF_FORMAT_PCM_S8 },
77+
{ "u8", 8, 1, 1, SF_FORMAT_PCM_U8 },
78+
{ "s24", 24, 1, 1, SF_FORMAT_PCM_24 },
79+
{ "s32", 32, 1, 1, SF_FORMAT_PCM_32 },
80+
{ "float", 24, 0, 0, SF_FORMAT_FLOAT },
81+
{ "double", 53, 0, 0, SF_FORMAT_DOUBLE },
82+
{ "mu-law", 13, 0, 0, SF_FORMAT_ULAW },
83+
{ "a-law", 14, 0, 0, SF_FORMAT_ALAW },
84+
{ "ima_adpcm", 13, 0, 0, SF_FORMAT_IMA_ADPCM },
85+
{ "ms_adpcm", 13, 0, 0, SF_FORMAT_MS_ADPCM },
86+
{ "gsm6.10", 16, 0, 0, SF_FORMAT_GSM610 },
87+
{ "vox_adpcm", 13, 0, 0, SF_FORMAT_VOX_ADPCM },
88+
{ "nms_adpcm_16", 8, 0, 0, SF_FORMAT_NMS_ADPCM_16 },
89+
{ "nms_adpcm_24", 8, 0, 0, SF_FORMAT_NMS_ADPCM_24 },
90+
{ "nms_adpcm_32", 12, 0, 0, SF_FORMAT_NMS_ADPCM_32 },
91+
{ "g721_32", 12, 0, 0, SF_FORMAT_G721_32 },
92+
{ "g723_24", 8, 0, 0, SF_FORMAT_G723_24 },
93+
{ "g723_40", 14, 0, 0, SF_FORMAT_G723_40 },
94+
{ "dwvw_12", 12, 0, 0, SF_FORMAT_DWVW_12 },
95+
{ "dwvw_16", 16, 0, 0, SF_FORMAT_DWVW_16 },
96+
{ "dwvw_24", 24, 0, 0, SF_FORMAT_DWVW_24 },
97+
{ "dpcm_8", 8, 0, 0, SF_FORMAT_DPCM_8 },
98+
{ "dpcm_16", 16, 0, 0, SF_FORMAT_DPCM_16 },
99+
{ "vorbis", 24, 0, 0, SF_FORMAT_VORBIS },
100+
{ "opus", 24, 0, 0, SF_FORMAT_OPUS },
101+
{ "alac_16", 16, 1, 0, SF_FORMAT_ALAC_16 },
102+
{ "alac_20", 20, 1, 0, SF_FORMAT_ALAC_20 },
103+
{ "alac_24", 24, 1, 0, SF_FORMAT_ALAC_24 },
104+
{ "alac_32", 32, 1, 0, SF_FORMAT_ALAC_32 },
105+
{ "mpeg1.1", 24, 0, 0, SF_FORMAT_MPEG_LAYER_I },
106+
{ "mpeg1.2", 24, 0, 0, SF_FORMAT_MPEG_LAYER_II },
107+
{ "mpeg2.3", 24, 0, 0, SF_FORMAT_MPEG_LAYER_III },
107108
};
108109

109110
ssize_t sndfile_read(struct codec *c, sample_t *buf, ssize_t frames)
@@ -116,10 +117,20 @@ ssize_t sndfile_read(struct codec *c, sample_t *buf, ssize_t frames)
116117
return (ssize_t) r;
117118
}
118119

120+
static inline void buf_scale_int(sample_t *buf, const sample_t s, ssize_t samples)
121+
{
122+
const sample_t c = s - 1.0;
123+
for (sample_t *end = buf + samples; buf < end; ++buf) {
124+
*buf *= s;
125+
if (*buf > c) *buf = c;
126+
}
127+
}
128+
119129
ssize_t sndfile_write(struct codec *c, sample_t *buf, ssize_t frames)
120130
{
121131
int e = 0;
122132
struct sndfile_state *state = (struct sndfile_state *) c->data;
133+
if (state->scale > 1.0) buf_scale_int(buf, state->scale, frames * c->channels);
123134
const sf_count_t r = sf_writef_double(state->f, buf, frames);
124135
if (r != frames && (e = sf_error(state->f)) != SF_ERR_NO_ERROR)
125136
LOG_FMT(LL_ERROR, "%s: %s", __func__, sf_error_number(e));
@@ -239,16 +250,18 @@ struct codec * sndfile_codec_init(const struct codec_params *p)
239250
goto fail;
240251
}
241252

242-
#if BIT_PERFECT && 0 /* libsndfile's sample type conversion is broken... */
243-
sf_command(f, SFC_SET_CLIPPING, NULL, SF_TRUE);
244-
#endif
245-
253+
enc_info = sndfile_get_enc_info(info->format);
246254
state = calloc(1, sizeof(struct sndfile_state));
247255
state->f = f;
248256
state->info = info;
257+
#if BIT_PERFECT
258+
if (p->mode == CODEC_MODE_WRITE && enc_info->do_scale) {
259+
state->scale = (sample_t) (((uint32_t) 1) << (enc_info->prec - 1));
260+
sf_command(f, SFC_SET_NORM_DOUBLE, NULL, SF_FALSE);
261+
}
262+
#endif
249263

250264
c = calloc(1, sizeof(struct codec));
251-
enc_info = sndfile_get_enc_info(info->format);
252265
c->path = p->path;
253266
c->type = sndfile_get_type_name(info->format);
254267
c->enc = (enc_info) ? enc_info->name : "unknown";

0 commit comments

Comments
 (0)