Bug 1027713 - Part 1 - Add a volume API in cubeb and use it instead of doing our own soft gain. r=kinetik

--HG--
extra : rebase_source : c94c9f6792c002d515f2fee0cf708928e76f91a8
This commit is contained in:
Paul Adenot 2014-07-24 17:05:23 +02:00
parent c833d9608c
commit 25e8004fd3
17 changed files with 509 additions and 31 deletions

View File

@ -246,7 +246,6 @@ AudioStream::AudioStream()
, mLatencyRequest(HighLatency)
, mReadPoint(0)
, mDumpFile(nullptr)
, mVolume(1.0)
, mBytesPerFrame(0)
, mState(INITIALIZED)
, mNeedsStart(false)
@ -751,9 +750,11 @@ AudioStream::Available()
void
AudioStream::SetVolume(double aVolume)
{
MonitorAutoLock mon(mMonitor);
NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
mVolume = aVolume;
if (cubeb_stream_set_volume(mCubebStream, aVolume * GetVolumeScale()) != CUBEB_OK) {
NS_WARNING("Could not change volume on cubeb stream.");
}
}
void
@ -1099,9 +1100,6 @@ AudioStream::DataCallback(void* aBuffer, long aFrames)
} else {
servicedFrames = GetTimeStretched(output, aFrames, insertTime);
}
float scaled_volume = float(GetVolumeScale() * mVolume);
ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume);
NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");

View File

@ -361,9 +361,6 @@ private:
// frames.
CircularByteBuffer mBuffer;
// Software volume level. Applied during the servicing of DataCallback().
double mVolume;
// Owning reference to a cubeb_stream. cubeb_stream_destroy is called by
// nsAutoRef's destructor.
nsAutoRef<cubeb_stream> mCubebStream;

View File

@ -135,6 +135,8 @@ cubeb_stream_init
cubeb_stream_start
cubeb_stream_stop
cubeb_stream_get_latency
cubeb_stream_set_volume
cubeb_stream_set_panning
th_comment_clear
th_comment_init
th_decode_alloc

View File

@ -261,6 +261,30 @@ int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
@retval CUBEB_ERROR */
int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
/**
* Set the volume for a stream.
* @param stream the stream for which to adjust the volume.
* @param volumes a float between 0.0 (muted) and 1.0 (maximum volumes)
* @return CUBEB_ERROR_INVALID_PARAMETER if volume is outside [0.0; 1.0]
* @return CUBEB_ERROR_INVALID_PARAMETER if stream is an invalid pointer
* @return CUBEB_OK otherwise
*/
int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
/**
* If the stream is stereo, set the left/right panning. If the stream is mono,
* this has no effect.
* @param stream the stream for which to change the panning
* @param panning a number from -1.0 to 1.0. -1.0 means that the stream is fully
* mixed in the left channel, 1.0 means the stream is fully mixed in the right
* channel. 0.0 is equal power in the right and left channel (default).
* @return CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is outside
* the [-1.0; 1.0] range.
* @return CUBEB_ERROR if this stream is not mono nor stereo.
* @return CUBEB_OK otherwise.
*/
int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
#if defined(__cplusplus)
}
#endif

View File

@ -28,6 +28,8 @@ struct cubeb_ops {
int (* stream_stop)(cubeb_stream * stream);
int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
int (* stream_set_volume)(cubeb_stream * stream, float volumes);
int (* stream_set_panning)(cubeb_stream * stream, float panning);
};
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */

View File

@ -258,3 +258,22 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
return stream->context->ops->stream_get_latency(stream, latency);
}
int
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
{
if (!stream || volume > 1.0 || volume < 0.0) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
return stream->context->ops->stream_set_volume(stream, volume);
}
int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
{
if (!stream || panning < -1.0 || panning > 1.0) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
return stream->context->ops->stream_set_panning(stream, panning);
}

View File

@ -106,6 +106,7 @@ struct cubeb_stream {
PulseAudio where streams would stop requesting new data despite still
being logically active and playing. */
struct timeval last_activity;
float volume;
};
static int
@ -313,7 +314,20 @@ alsa_refill_stream(cubeb_stream * stm)
return ERROR;
}
if (got > 0) {
snd_pcm_sframes_t wrote = snd_pcm_writei(stm->pcm, p, got);
snd_pcm_sframes_t wrote;
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
float * b = (float *) p;
for (uint32_t i = 0; i < got * stm->params.channels; i++) {
b[i] *= stm->volume;
}
} else {
short * b = (short *) p;
for (uint32_t i = 0; i < got * stm->params.channels; i++) {
b[i] *= stm->volume;
}
}
wrote = snd_pcm_writei(stm->pcm, p, got);
if (wrote == -EPIPE) {
snd_pcm_recover(stm->pcm, wrote, 1);
wrote = snd_pcm_writei(stm->pcm, p, got);
@ -813,6 +827,7 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
stm->user_ptr = user_ptr;
stm->params = stream_params;
stm->state = INACTIVE;
stm->volume = 1.0;
r = pthread_mutex_init(&stm->mutex, NULL);
assert(r == 0);
@ -1083,6 +1098,24 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int
alsa_stream_set_volume(cubeb_stream * stm, float volume)
{
/* setting the volume using an API call does not seem very stable/supported */
pthread_mutex_lock(&stm->mutex);
stm->volume = volume;
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
}
int
alsa_stream_set_panning(cubeb_stream * stream, float panning)
{
assert(0 && "not implemented");
return CUBEB_OK;
}
static struct cubeb_ops const alsa_ops = {
.init = alsa_init,
.get_backend_id = alsa_get_backend_id,
@ -1095,5 +1128,7 @@ static struct cubeb_ops const alsa_ops = {
.stream_start = alsa_stream_start,
.stream_stop = alsa_stream_stop,
.stream_get_position = alsa_stream_get_position,
.stream_get_latency = alsa_stream_get_latency
.stream_get_latency = alsa_stream_get_latency,
.stream_set_volume = alsa_stream_set_volume,
.stream_set_panning = alsa_stream_set_panning
};

View File

@ -71,7 +71,7 @@ struct AudioTrack {
/* static */ int (*get_output_latency)(uint32_t* latency, int stream);
/* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
status_t (*set_marker_position)(void* instance, unsigned int);
status_t (*set_volume)(void* instance, float left, float right);
};
struct cubeb {
@ -251,6 +251,7 @@ audiotrack_init(cubeb ** context, char const * context_name)
DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library);
/* check that we have a combination of symbol that makes sense */
c = &ctx->klass;
@ -468,6 +469,27 @@ audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
return 0;
}
int
audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
{
status_t status;
status = stream->context->klass.set_volume(stream->instance, volume, volume);
if (status) {
return CUBEB_ERROR;
}
return CUBEB_OK;
}
int
audiotrack_stream_set_panning(cubeb_stream * stream, float panning)
{
assert(false && "not implemented.");
return CUBEB_OK;
}
static struct cubeb_ops const audiotrack_ops = {
.init = audiotrack_init,
.get_backend_id = audiotrack_get_backend_id,
@ -480,5 +502,7 @@ static struct cubeb_ops const audiotrack_ops = {
.stream_start = audiotrack_stream_start,
.stream_stop = audiotrack_stream_stop,
.stream_get_position = audiotrack_stream_get_position,
.stream_get_latency = audiotrack_stream_get_latency
.stream_get_latency = audiotrack_stream_get_latency,
.stream_set_volume = audiotrack_stream_set_volume,
.stream_set_panning = audiotrack_stream_set_panning
};

View File

@ -14,6 +14,7 @@
#include <CoreFoundation/CoreFoundation.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include "cubeb_panner.h"
#if !defined(kCFCoreFoundationVersionNumber10_7)
/* From CoreFoundation CFBase.h */
@ -46,6 +47,7 @@ struct cubeb_stream {
int draining;
uint64_t current_latency_frames;
uint64_t hw_latency_frames;
float panning;
};
static int64_t
@ -70,6 +72,7 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
unsigned char * buf;
long got;
OSStatus r;
float panning;
assert(bufs->mNumberBuffers == 1);
buf = bufs->mBuffers[0].mData;
@ -79,6 +82,7 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
pthread_mutex_lock(&stm->mutex);
stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
panning = stm->panning;
if (stm->draining || stm->shutdown) {
pthread_mutex_unlock(&stm->mutex);
@ -113,6 +117,10 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
stm->frames_queued += got;
pthread_mutex_unlock(&stm->mutex);
if (stm->sample_spec.mChannelsPerFrame == 2) {
cubeb_pan_stereo_buffer_float((float*)buf, got, panning);
}
return noErr;
}
@ -617,6 +625,35 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int audiounit_stream_set_volume(cubeb_stream * stm, float volume)
{
AudioDeviceID id;
OSStatus r;
r = AudioUnitSetParameter(stm->unit,
kHALOutputParam_Volume,
kAudioUnitScope_Global,
0, volume, 0);
if (r != noErr) {
return CUBEB_ERROR;
}
return CUBEB_OK;
}
int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
{
if (stm->sample_spec.mChannelsPerFrame > 2) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
pthread_mutex_lock(&stm->mutex);
stm->panning = panning;
pthread_mutex_unlock(&stm->mutex);
return CUBEB_OK;
}
static struct cubeb_ops const audiounit_ops = {
.init = audiounit_init,
.get_backend_id = audiounit_get_backend_id,
@ -629,5 +666,7 @@ static struct cubeb_ops const audiounit_ops = {
.stream_start = audiounit_stream_start,
.stream_stop = audiounit_stream_stop,
.stream_get_position = audiounit_stream_get_position,
.stream_get_latency = audiounit_stream_get_latency
.stream_get_latency = audiounit_stream_get_latency,
.stream_set_volume = audiounit_stream_set_volume,
.stream_set_panning = audiounit_stream_set_panning
};

View File

@ -10,6 +10,7 @@
#include <stdlib.h>
#include <pthread.h>
#include <SLES/OpenSLES.h>
#include <math.h>
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#include "android/sles_definitions.h"
@ -34,6 +35,7 @@ struct cubeb {
#if defined(__ANDROID__)
SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
#endif
SLInterfaceID SL_IID_VOLUME;
SLObjectItf engObj;
SLEngineItf eng;
SLObjectItf outmixObj;
@ -49,6 +51,7 @@ struct cubeb_stream {
SLObjectItf playerObj;
SLPlayItf play;
SLBufferQueueItf bufq;
SLVolumeItf volume;
uint8_t *queuebuf[NBUFS];
int queuebuf_idx;
long queuebuf_len;
@ -243,6 +246,7 @@ opensl_init(cubeb ** context, char const * context_name)
(slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
#if defined(__ANDROID__)
ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
@ -521,11 +525,13 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
sink.pFormat = NULL;
#if defined(__ANDROID__)
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE,
ctx->SL_IID_VOLUME,
ctx->SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#else
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE};
const SLboolean req[] = {SL_BOOLEAN_TRUE};
const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
#endif
assert(NELEMS(ids) == NELEMS(req));
SLresult res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj,
@ -609,6 +615,14 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
return CUBEB_ERROR;
}
res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME,
&stm->volume);
if (res != SL_RESULT_SUCCESS) {
opensl_stream_destroy(stm);
return CUBEB_ERROR;
}
res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
if (res != SL_RESULT_SUCCESS) {
opensl_stream_destroy(stm);
@ -724,6 +738,41 @@ opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int
opensl_stream_set_volume(cubeb_stream * stm, float volume)
{
SLresult res;
SLmillibel max_level, millibels;
res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level);
if (res != SL_RESULT_SUCCESS) {
return CUBEB_ERROR;
}
/* convert to millibels */
millibels = lroundf(2000.f * log10f(volume));
/* clamp to supported range */
if (millibels > max_level) {
millibels = max_level;
}
res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels);
if (res != SL_RESULT_SUCCESS) {
return CUBEB_ERROR;
}
return CUBEB_OK;
}
int
opensl_stream_set_panning(cubeb_stream * stream, float panning)
{
assert(0 && "not implemented.");
return CUBEB_OK;
}
static struct cubeb_ops const opensl_ops = {
.init = opensl_init,
.get_backend_id = opensl_get_backend_id,
@ -736,5 +785,7 @@ static struct cubeb_ops const opensl_ops = {
.stream_start = opensl_stream_start,
.stream_stop = opensl_stream_stop,
.stream_get_position = opensl_stream_get_position,
.stream_get_latency = opensl_stream_get_latency
.stream_get_latency = opensl_stream_get_latency,
.stream_set_volume = opensl_stream_set_volume,
.stream_set_panning = opensl_stream_set_panning
};

View File

@ -0,0 +1,56 @@
/*
* Copyright © 2014 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#define _USE_MATH_DEFINES
#include <math.h>
#include <cubeb/cubeb-stdint.h>
#include "cubeb_panner.h"
/**
* We use a cos/sin law.
*/
namespace {
template<typename T>
void cubeb_pan_stereo_buffer(T * buf, uint32_t frames, float pan)
{
if (pan == 0.0) {
return;
}
/* rescale in [0; 1] */
pan += 1;
pan /= 2;
float left_gain = cos(pan * M_PI * 0.5);
float right_gain = sin(pan * M_PI * 0.5);
/* In we are panning on the left, pan the right channel into the left one and
* vice-versa. */
if (pan < 0.5) {
for (uint32_t i = 0; i < frames * 2; i+=2) {
buf[i] = buf[i] + buf[i + 1] * left_gain;
buf[i + 1] = buf[i + 1] * right_gain;
}
} else {
for (uint32_t i = 0; i < frames * 2; i+=2) {
buf[i] = buf[i] * left_gain;
buf[i + 1] = buf[i + 1] + buf[i] * right_gain;
}
}
}
}
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan)
{
cubeb_pan_stereo_buffer(reinterpret_cast<float*>(buf), frames, pan);
}
void cubeb_pan_stereo_buffer_int(short * buf, uint32_t frames, float pan)
{
cubeb_pan_stereo_buffer(reinterpret_cast<short*>(buf), frames, pan);
}

View File

@ -0,0 +1,28 @@
/*
* Copyright © 2014 Mozilla Foundation
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#if !defined(CUBEB_PANNER)
#define CUBEB_PANNER
#if defined(__cplusplus)
extern "C" {
#endif
/**
* Pan an integer or an float stereo buffer according to a cos/sin pan law
* @param buf the buffer to pan
* @param frames the number of frames in `buf`
* @param pan a float in [-1.0; 1.0]
*/
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan);
void cubeb_pan_stereo_buffer_int(short* buf, uint32_t frames, float pan);
#if defined(__cplusplus)
}
#endif
#endif

View File

@ -12,23 +12,28 @@
#include <string.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include <stdio.h>
#ifdef DISABLE_LIBPULSE_DLOPEN
#define WRAP(x) x
#else
#define WRAP(x) cubeb_##x
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x
MAKE_TYPEDEF(pa_channel_map_can_balance);
MAKE_TYPEDEF(pa_channel_map_init_auto);
MAKE_TYPEDEF(pa_context_connect);
MAKE_TYPEDEF(pa_context_disconnect);
MAKE_TYPEDEF(pa_context_drain);
MAKE_TYPEDEF(pa_context_get_server_info);
MAKE_TYPEDEF(pa_context_get_sink_info_by_name);
MAKE_TYPEDEF(pa_context_get_state);
MAKE_TYPEDEF(pa_context_new);
MAKE_TYPEDEF(pa_context_rttime_new);
MAKE_TYPEDEF(pa_context_set_sink_input_volume);
MAKE_TYPEDEF(pa_context_set_state_callback);
MAKE_TYPEDEF(pa_context_unref);
MAKE_TYPEDEF(pa_context_get_sink_info_by_name);
MAKE_TYPEDEF(pa_context_get_server_info);
MAKE_TYPEDEF(pa_cvolume_set);
MAKE_TYPEDEF(pa_cvolume_set_balance);
MAKE_TYPEDEF(pa_frame_size);
MAKE_TYPEDEF(pa_operation_get_state);
MAKE_TYPEDEF(pa_operation_unref);
@ -38,7 +43,10 @@ MAKE_TYPEDEF(pa_stream_cancel_write);
MAKE_TYPEDEF(pa_stream_connect_playback);
MAKE_TYPEDEF(pa_stream_cork);
MAKE_TYPEDEF(pa_stream_disconnect);
MAKE_TYPEDEF(pa_stream_get_channel_map);
MAKE_TYPEDEF(pa_stream_get_index);
MAKE_TYPEDEF(pa_stream_get_latency);
MAKE_TYPEDEF(pa_stream_get_sample_spec);
MAKE_TYPEDEF(pa_stream_get_state);
MAKE_TYPEDEF(pa_stream_get_time);
MAKE_TYPEDEF(pa_stream_new);
@ -47,10 +55,11 @@ MAKE_TYPEDEF(pa_stream_set_write_callback);
MAKE_TYPEDEF(pa_stream_unref);
MAKE_TYPEDEF(pa_stream_update_timing_info);
MAKE_TYPEDEF(pa_stream_write);
MAKE_TYPEDEF(pa_sw_volume_from_linear);
MAKE_TYPEDEF(pa_threaded_mainloop_free);
MAKE_TYPEDEF(pa_threaded_mainloop_get_api);
MAKE_TYPEDEF(pa_threaded_mainloop_lock);
MAKE_TYPEDEF(pa_threaded_mainloop_in_thread);
MAKE_TYPEDEF(pa_threaded_mainloop_lock);
MAKE_TYPEDEF(pa_threaded_mainloop_new);
MAKE_TYPEDEF(pa_threaded_mainloop_signal);
MAKE_TYPEDEF(pa_threaded_mainloop_start);
@ -244,10 +253,12 @@ operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
{
while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context)))
if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
return -1;
if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream)))
}
if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
return -1;
}
}
return 0;
}
@ -324,17 +335,21 @@ pulse_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR; \
} \
} while(0)
LOAD(pa_channel_map_can_balance);
LOAD(pa_channel_map_init_auto);
LOAD(pa_context_connect);
LOAD(pa_context_disconnect);
LOAD(pa_context_drain);
LOAD(pa_context_get_server_info);
LOAD(pa_context_get_sink_info_by_name);
LOAD(pa_context_get_state);
LOAD(pa_context_new);
LOAD(pa_context_rttime_new);
LOAD(pa_context_set_sink_input_volume);
LOAD(pa_context_set_state_callback);
LOAD(pa_context_get_sink_info_by_name);
LOAD(pa_context_get_server_info);
LOAD(pa_context_unref);
LOAD(pa_cvolume_set);
LOAD(pa_cvolume_set_balance);
LOAD(pa_frame_size);
LOAD(pa_operation_get_state);
LOAD(pa_operation_unref);
@ -344,7 +359,10 @@ pulse_init(cubeb ** context, char const * context_name)
LOAD(pa_stream_connect_playback);
LOAD(pa_stream_cork);
LOAD(pa_stream_disconnect);
LOAD(pa_stream_get_channel_map);
LOAD(pa_stream_get_index);
LOAD(pa_stream_get_latency);
LOAD(pa_stream_get_sample_spec);
LOAD(pa_stream_get_state);
LOAD(pa_stream_get_time);
LOAD(pa_stream_new);
@ -353,10 +371,11 @@ pulse_init(cubeb ** context, char const * context_name)
LOAD(pa_stream_unref);
LOAD(pa_stream_update_timing_info);
LOAD(pa_stream_write);
LOAD(pa_sw_volume_from_linear);
LOAD(pa_threaded_mainloop_free);
LOAD(pa_threaded_mainloop_get_api);
LOAD(pa_threaded_mainloop_lock);
LOAD(pa_threaded_mainloop_in_thread);
LOAD(pa_threaded_mainloop_lock);
LOAD(pa_threaded_mainloop_new);
LOAD(pa_threaded_mainloop_signal);
LOAD(pa_threaded_mainloop_start);
@ -661,6 +680,60 @@ pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
void volume_success(pa_context *c, int success, void *userdata)
{
cubeb_stream * stream = userdata;
assert(success);
WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
}
int
pulse_stream_set_volume(cubeb_stream * stm, float volume)
{
uint32_t index;
pa_operation * op;
pa_volume_t vol;
pa_cvolume cvol;
const pa_sample_spec * ss;
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
ss = WRAP(pa_stream_get_sample_spec)(stm->stream);
vol = WRAP(pa_sw_volume_from_linear)(volume);
WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
index = WRAP(pa_stream_get_index)(stm->stream);
op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
index, &cvol, volume_success,
stm);
if (op) {
operation_wait(stm->context, stm->stream, op);
WRAP(pa_operation_unref)(op);
}
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
return CUBEB_OK;
}
int
pulse_stream_set_panning(cubeb_stream * stream, float panning)
{
const pa_channel_map * map;
pa_cvolume vol;
map = WRAP(pa_stream_get_channel_map)(stream->stream);
if (!WRAP(pa_channel_map_can_balance)(map)) {
return CUBEB_ERROR;
}
WRAP(pa_cvolume_set_balance)(&vol, map, panning);
return CUBEB_OK;
}
static struct cubeb_ops const pulse_ops = {
.init = pulse_init,
.get_backend_id = pulse_get_backend_id,
@ -673,5 +746,7 @@ static struct cubeb_ops const pulse_ops = {
.stream_start = pulse_stream_start,
.stream_stop = pulse_stream_stop,
.stream_get_position = pulse_stream_get_position,
.stream_get_latency = pulse_stream_get_latency
.stream_get_latency = pulse_stream_get_latency,
.stream_set_volume = pulse_stream_set_volume,
.stream_set_panning = pulse_stream_set_panning
};

View File

@ -341,6 +341,12 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int
sndio_stream_set_volume(cubeb_stream * stm, float panning)
{
assert(0 && "not implemented");
}
static struct cubeb_ops const sndio_ops = {
.init = sndio_init,
.get_backend_id = sndio_get_backend_id,
@ -353,5 +359,7 @@ static struct cubeb_ops const sndio_ops = {
.stream_start = sndio_stream_start,
.stream_stop = sndio_stream_stop,
.stream_get_position = sndio_stream_get_position,
.stream_get_latency = sndio_stream_get_latency
.stream_get_latency = sndio_stream_get_latency,
.stream_set_volume = sndio_stream_set_volume,
.stream_set_panning = sndio_stream_set_panning
};

View File

@ -102,6 +102,8 @@ struct cubeb_stream
IAudioRenderClient * render_client;
/* Interface pointer to use the clock facilities. */
IAudioClock * audio_clock;
/* Interface pointer to use the volume facilities. */
IAudioStreamVolume * audio_stream_volume;
/* This event is set by the stream_stop and stream_destroy
* function, so the render loop can exit properly. */
HANDLE shutdown_event;
@ -748,6 +750,14 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR;
}
hr = stm->client->GetService(__uuidof(IAudioStreamVolume),
(void **)&stm->audio_stream_volume);
if (FAILED(hr)) {
LOG("Could not get the IAudioStreamVolume %x.", hr);
wasapi_stream_destroy(stm);
return CUBEB_ERROR;
}
hr = stm->audio_clock->GetFrequency(&stm->clock_freq);
if (FAILED(hr)) {
LOG("failed to get audio clock frequency, %x", hr);
@ -882,6 +892,39 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
{
HRESULT hr;
uint32_t channels;
/* up to 9.1 for now */
float volumes[10];
hr = stm->audio_stream_volume->GetChannelCount(&channels);
if (hr != S_OK) {
LOG("could not get the channel count: %x", hr);
return CUBEB_ERROR;
}
assert(channels <= 10 && "bump the array size");
for (uint32_t i = 0; i < channels; i++) {
volumes[i] = volume;
}
hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
if (hr != S_OK) {
LOG("coult not set the channels volume: %x", hr);
return CUBEB_ERROR;
}
return CUBEB_OK;
}
int wasapi_stream_set_panning(cubeb_stream * stream, float panning)
{
assert(false && "not implemented");
return CUBEB_OK;
}
cubeb_ops const wasapi_ops = {
/*.init =*/ wasapi_init,
/*.get_backend_id =*/ wasapi_get_backend_id,
@ -894,7 +937,9 @@ cubeb_ops const wasapi_ops = {
/*.stream_start =*/ wasapi_stream_start,
/*.stream_stop =*/ wasapi_stream_stop,
/*.stream_get_position =*/ wasapi_stream_get_position,
/*.stream_get_latency =*/ wasapi_stream_get_latency
/*.stream_get_latency =*/ wasapi_stream_get_latency,
/*.stream_set_volume =*/ wasapi_stream_set_volume,
/*.stream_set_panning =*/ wasapi_stream_set_panning
};
} // namespace anonymous

View File

@ -18,6 +18,7 @@
#include <mmsystem.h>
#include <process.h>
#include <stdlib.h>
#include <math.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
@ -28,6 +29,9 @@
#define CUBEB_STREAM_MAX 32
#define NBUFS 4
/* When cubeb_stream.soft_volume is set to this value, the device supports
* setting the volume. Otherwise, a gain will be applied manually. */
#define SETTING_VOLUME_SUPPORTED -1.0
const GUID KSDATAFORMAT_SUBTYPE_PCM =
{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
@ -68,6 +72,7 @@ struct cubeb_stream {
HWAVEOUT waveout;
CRITICAL_SECTION lock;
uint64_t written;
float soft_volume;
};
static size_t
@ -155,6 +160,22 @@ winmm_refill_stream(cubeb_stream * stm)
hdr->dwBufferLength = got * bytes_per_frame(stm->params);
assert(hdr->dwBufferLength <= stm->buffer_size);
if (stm->soft_volume != SETTING_VOLUME_SUPPORTED) {
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
short * b = (short *) hdr->lpData;
uint32_t i;
for (i = 0; i < got * stm->params.channels; i++) {
b[i] *= stm->soft_volume;
}
} else {
short * b = (short *) hdr->lpData;
uint32_t i;
for (i = 0; i < got * stm->params.channels; i++) {
b[i] *= stm->soft_volume;
}
}
}
r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
if (r != MMSYSERR_NOERROR) {
LeaveCriticalSection(&stm->lock);
@ -330,6 +351,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
{
MMRESULT r;
WAVEFORMATEXTENSIBLE wfx;
WAVEOUTCAPS waveoutcaps;
cubeb_stream * stm;
int i;
size_t bufsz;
@ -416,6 +438,20 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return CUBEB_ERROR;
}
r = waveOutGetDevCaps(WAVE_MAPPER, &waveoutcaps, sizeof(WAVEOUTCAPS));
if(r != MMSYSERR_NOERROR) {
winmm_stream_destroy(stm);
return CUBEB_ERROR;
}
stm->soft_volume = SETTING_VOLUME_SUPPORTED;
/* if this device does not support setting the volume, do it manually. */
if(!(waveoutcaps.dwSupport & WAVECAPS_VOLUME)) {
stm->soft_volume = 1.0;
}
/* winmm_buffer_callback will be called during waveOutOpen, so all
other initialization must be complete before calling it. */
r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
@ -432,6 +468,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return CUBEB_ERROR;
}
for (i = 0; i < NBUFS; ++i) {
WAVEHDR * hdr = &stm->buffers[i];
@ -628,6 +665,41 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
return CUBEB_OK;
}
static int
winmm_stream_set_volume(cubeb_stream * stm, float volume)
{
MMRESULT r;
DWORD vol;
if (stm->soft_volume != SETTING_VOLUME_SUPPORTED) {
stm->soft_volume = volume;
return CUBEB_OK;
}
// lower order word is the left channel, higher order
// word is the right channel. Full volume on a channel is 0xffff.
vol = volume * 0xffff;
vol |= vol << 16;
EnterCriticalSection(&stm->lock);
r = waveOutSetVolume(stm->waveout, vol);
if (r != MMSYSERR_NOERROR) {
stm->soft_volume = volume;
LeaveCriticalSection(&stm->lock);
return CUBEB_ERROR;
}
LeaveCriticalSection(&stm->lock);
return CUBEB_OK;
}
static int
winmm_stream_set_panning(cubeb_stream * stream, float panning)
{
assert(0 && "not implemented");
return CUBEB_OK;
}
static struct cubeb_ops const winmm_ops = {
/*.init =*/ winmm_init,
/*.get_backend_id =*/ winmm_get_backend_id,
@ -640,5 +712,7 @@ static struct cubeb_ops const winmm_ops = {
/*.stream_start =*/ winmm_stream_start,
/*.stream_stop =*/ winmm_stream_stop,
/*.stream_get_position =*/ winmm_stream_get_position,
/*.stream_get_latency = */ winmm_stream_get_latency
/*.stream_get_latency = */ winmm_stream_get_latency,
/*.stream_set_volume =*/ winmm_stream_set_volume,
/*.stream_set_panning =*/ winmm_stream_set_panning
};

View File

@ -8,6 +8,7 @@ LIBRARY_NAME = 'cubeb'
SOURCES += [
'cubeb.c',
'cubeb_panner.cpp'
]
if CONFIG['MOZ_ALSA']: