Skip to content

Commit

Permalink
perform volume scaling in capture/playback
Browse files Browse the repository at this point in the history
  • Loading branch information
i-rinat committed Mar 8, 2017
1 parent f9286a6 commit 543ddcb
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/apulse-stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ data_available_for_stream(pa_mainloop_api *a, pa_io_event *ioe, int fd, pa_io_ev
size_t bytecnt = MIN(buf_size, frame_count * frame_size);
bytecnt = ringbuffer_read(s->rb, buf, bytecnt);

pa_apply_volume_multiplier(buf, bytecnt, s->c->sink_volume, &s->ss);

if (bytecnt == 0) {
// application is not ready yet, play silence
bytecnt = MIN(buf_size, frame_count * frame_size);
Expand Down Expand Up @@ -163,6 +165,7 @@ data_available_for_stream(pa_mainloop_api *a, pa_io_event *ioe, int fd, pa_io_ev

if (bytecnt > 0) {
snd_pcm_readi(s->ph, buf, bytecnt / frame_size);
pa_apply_volume_multiplier(buf, bytecnt, s->c->source_volume, &s->ss);
ringbuffer_write(s->rb, buf, bytecnt);
}

Expand Down
71 changes: 71 additions & 0 deletions src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

#include "util.h"
#include "trace.h"

int
pa_format_to_alsa(pa_sample_format_t format)
Expand Down Expand Up @@ -87,3 +88,73 @@ pa_find_multiple_of(size_t number, size_t multiple_of)
{
return number - (number % multiple_of);
}

void
pa_apply_volume_multiplier(void *buf, size_t sz, const pa_volume_t volume[PA_CHANNELS_MAX],
const pa_sample_spec *ss)
{
char *p = buf;
char *last = p + sz;
float fvol[PA_CHANNELS_MAX];
uint32_t channels = MIN(ss->channels, PA_CHANNELS_MAX);

if (channels == 0) {
// No channels — nothing to scale.
return;
}

int all_normal = 1;
for (uint32_t k = 0; k < channels; k++)
all_normal = all_normal && (volume[k] == PA_VOLUME_NORM);

if (all_normal) {
// No scaling required.
return;
}

for (uint32_t k = 0; k < channels; k++)
fvol[k] = pa_sw_volume_to_linear(volume[k]);

switch (ss->format) {
case PA_SAMPLE_FLOAT32NE:
while (p < last) {
for (uint32_t k = 0; k < channels && p < last; k++) {
float sample;
memcpy(&sample, p, sizeof(sample));
sample *= fvol[k];
memcpy(p, &sample, sizeof(sample));
p += sizeof(sample);
}
}
break;

case PA_SAMPLE_S16NE:
while (p < last) {
for (uint32_t k = 0; k < channels && p < last; k++) {
uint16_t sample;
memcpy(&sample, p, sizeof(sample));
float sample_scaled = sample * fvol[k];
sample = CLAMP(sample_scaled, 0.0, 65535.0);
memcpy(p, &sample, sizeof(sample));
p += sizeof(sample);
}
}
break;

case PA_SAMPLE_U8:
case PA_SAMPLE_ALAW:
case PA_SAMPLE_ULAW:
case PA_SAMPLE_S16RE:
case PA_SAMPLE_FLOAT32RE:
case PA_SAMPLE_S32NE:
case PA_SAMPLE_S32RE:
case PA_SAMPLE_S24NE:
case PA_SAMPLE_S24RE:
case PA_SAMPLE_S24_32NE:
case PA_SAMPLE_S24_32RE:
default:
trace_error("format %s is not implemented in %s\n", pa_sample_format_to_string(ss->format),
__func__);
break;
}
}
4 changes: 4 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ pa_sample_format_from_string(const char *str);
size_t
pa_find_multiple_of(size_t number, size_t multiple_of);

void
pa_apply_volume_multiplier(void *buf, size_t sz, const pa_volume_t volume[PA_CHANNELS_MAX],
const pa_sample_spec *ss);

#endif // APULSE__UTIL_H

2 comments on commit 543ddcb

@iiv3
Copy link

@iiv3 iiv3 commented on 543ddcb Mar 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. S16NE translates to Signed 16 bit Native Endian. Why are you sampling it as unsigned - uint16_t ?
    Doing it wrong should cause audible distortion.

  2. I don't think that memcpy() of a single sample is needed. Just cast p as float* inside case block. The void * is compatible with all casts and you won't get aliasing, since you access the memory only as the given type. Think of it as separate function that takes float * buf .

  3. You shouldn't need clamping in the S16NE case, since fval <= 1.0 .
    Yeh, I know, you can't trust floats.

@i-rinat
Copy link
Owner Author

@i-rinat i-rinat commented on 543ddcb Mar 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you sampling it as unsigned - uint16_t?

I've made a bug. Thank you for pointing this out. Fix pushed in ae3ea44.

Just cast p as float* inside case block. <...>

I was indeed afraid of aliasing warnings from a compiler. Some time ago I found that small fixed-sized memcpy's GCC replaces with __builtin_memcpy, which in turn are emited as direct copy instructions, even without optimization enabled. And, I think, there was also something about memory alignment. Just dereferencing unaligned pointer to a type is undefined behavior. So I ended up using memcpy trick, which should work in all cases. (Of course, it doesn't matter on x86, since it can do unaligned loads transparently).

You shouldn't need clamping in the S16NE case, since fval <= 1.0

fval could be greater than 1.0, since PulseAudio have higher-than-100% volumes. PA_VOLUME_NORM (== 0x10000U) is 100%, while maximum allowed volume is PA_VOLUME_MAX, which is UINT32_MAX/2.

Please sign in to comment.