Skip to content

Commit

Permalink
ao: software volume control
Browse files Browse the repository at this point in the history
Previous volume control was in AudioThread. Thas not reasonable because
user must use AVPlayer. Now audio samples are scaled in AudioOutput.
Also scale function is updated less.
TODO: if SetVolume feature is set (for example backend supports volume
control or set by user), use backend implemention
  • Loading branch information
wang-bin committed Feb 12, 2015
1 parent 6f546b7 commit 097bb91
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 99 deletions.
93 changes: 1 addition & 92 deletions src/AudioThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,81 +52,6 @@ AudioThread::AudioThread(QObject *parent)
{
}

/// from libavfilter/af_volume begin
static inline void scale_samples_u8(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
for (int i = 0; i < nb_samples; i++)
dst[i] = av_clip_uint8(((((qint64)src[i] - 128) * volume + 128) >> 8) + 128);
}

static inline void scale_samples_u8_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
for (int i = 0; i < nb_samples; i++)
dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
}

static inline void scale_samples_s16(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
int16_t *smp_dst = (int16_t *)dst;
const int16_t *smp_src = (const int16_t *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clip_int16(((qint64)smp_src[i] * volume + 128) >> 8);
}

static inline void scale_samples_s16_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
int16_t *smp_dst = (int16_t *)dst;
const int16_t *smp_src = (const int16_t *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
}

static inline void scale_samples_s32(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
qint32 *smp_dst = (qint32 *)dst;
const qint32 *smp_src = (const qint32 *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clipl_int32((((qint64)smp_src[i] * volume + 128) >> 8));
}
/// from libavfilter/af_volume end

template<typename T>
static inline void scale_samples(quint8 *dst, const quint8 *src, int nb_samples, int, float volume)
{
T *smp_dst = (T *)dst;
const T *smp_src = (const T *)src;
for (int i = 0; i < nb_samples; ++i)
smp_dst[i] = smp_src[i] * (T)volume;
}

typedef void (*scale_t)(quint8 *dst, const quint8 *src, int nb_samples, int volume, float volumef);

scale_t get_scaler(AudioFormat::SampleFormat fmt, qreal vol, int* voli)
{
int v = (int)(vol * 256.0 + 0.5);
if (voli)
*voli = v;
switch (fmt) {
case AudioFormat::SampleFormat_Unsigned8:
case AudioFormat::SampleFormat_Unsigned8Planar:
return v < 0x1000000 ? scale_samples_u8_small : scale_samples_u8;
case AudioFormat::SampleFormat_Signed16:
case AudioFormat::SampleFormat_Signed16Planar:
return v < 0x10000 ? scale_samples_s16_small : scale_samples_s16;
case AudioFormat::SampleFormat_Signed32:
case AudioFormat::SampleFormat_Signed32Planar:
return scale_samples_s32;
case AudioFormat::SampleFormat_Float:
case AudioFormat::SampleFormat_FloatPlanar:
return scale_samples<float>;
case AudioFormat::SampleFormat_Double:
case AudioFormat::SampleFormat_DoublePlanar:
return scale_samples<double>;
default:
return 0;
}
}

/*
*TODO:
* if output is null or dummy, the use duration to wait
Expand All @@ -143,7 +68,6 @@ void AudioThread::run()
//TODO: bool need_sync in private class
bool is_external_clock = d.clock->clockType() == AVClock::ExternalClock;
Packet pkt;
scale_t scale = 0;
while (!d.stop) {
processNextTask();
//TODO: why put it at the end of loop then playNextFrame() not work?
Expand Down Expand Up @@ -289,10 +213,6 @@ void AudioThread::run()
const AudioFormat &af = dec->resampler()->outAudioFormat();
const qreal byte_rate = af.bytesPerSecond();
const qreal vol = ao->volume(); //keep const for 1 frame
int volume_i = 0;
if (has_ao) {
scale = get_scaler(af.sampleFormat(), vol, &volume_i);
}
while (decodedSize > 0) {
if (d.stop) {
qDebug("audio thread stop after decode()");
Expand All @@ -304,24 +224,13 @@ void AudioThread::run()
const qreal chunk_delay = (qreal)chunk/(qreal)byte_rate;
pkt.pts += chunk_delay;
pkt.dts += chunk_delay;
QByteArray decodedChunk(chunk, 0); //volume == 0 || mute
if (has_ao) {
//TODO: volume filter and other filters!!!
if (!ao->isMute()) {
decodedChunk = QByteArray::fromRawData(decoded.constData() + decodedPos, chunk);
if (vol != 1.0 && scale) {
// TODO: af_volume needs samples_align to get nb_samples
const int nb_samples = decodedChunk.size()/ao->audioFormat().bytesPerSample();
quint8 *dst = (quint8*)decodedChunk.constData();
scale(dst, dst, nb_samples, volume_i, vol);
}
}
QByteArray decodedChunk = QByteArray::fromRawData(decoded.constData() + decodedPos, chunk);
ao->play(decodedChunk, pkt.pts);
d.clock->updateValue(ao->timestamp());
emit frameDelivered();
} else {
d.clock->updateDelay(delay += chunk_delay);

/*
* why need this even if we add delay? and usleep sounds weird
* the advantage is if no audio device, the play speed is ok too
Expand Down
13 changes: 7 additions & 6 deletions src/QtAV/AudioOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ class Q_AV_EXPORT AudioOutput : public QObject, public AVOutput
Q_DECLARE_FLAGS(BufferControls, BufferControl)
/*!
* \brief The Feature enum
* features (set when playing) supported by the audio playback api
* features supported by the audio playback api
*/
enum Feature {
SetVolume = 1,
SetMuted = 1 << 1,
SetSampleRate = 1 << 2,
SetVolume = 1, /// NOT IMPLEMENTED. Use backend volume control api rather than software scale. Ignore if backend does not support.
SetMuted = 1 << 1, /// NOT IMPLEMENTED
SetSampleRate = 1 << 2, /// NOT IMPLEMENTED
};
Q_DECLARE_FLAGS(Features, Feature)
/*!
Expand Down Expand Up @@ -115,8 +115,8 @@ class Q_AV_EXPORT AudioOutput : public QObject, public AVOutput
int channels() const; //deprecated
/*!
* \brief setVolume
* If SetVolume feature is not set or not supported, only store the value and you should process the audio data outside to the given volume value.
* Otherwise, call this also set the volume by the audio playback api //in slot?
* Set volume level.
* If SetVolume feature is not set or not supported, software implemention will be used.
* \param volume linear. 1.0: original volume.
*/
void setVolume(qreal volume);
Expand All @@ -130,6 +130,7 @@ class Q_AV_EXPORT AudioOutput : public QObject, public AVOutput
* audio clock. For example, play a video contains audio without special configurations.
* To change the playing speed in other cases, use AVPlayer::setSpeed(qreal)
* \param speed linear. > 0
* TODO: resample internally
*/
void setSpeed(qreal speed);
qreal speed() const;
Expand Down
7 changes: 7 additions & 0 deletions src/QtAV/private/AudioOutput_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ namespace QtAV {
const int kBufferSize = 1024*4;
const int kBufferCount = 8;

typedef void (*scale_samples_func)(quint8 *dst, const quint8 *src, int nb_samples, int volume, float volumef);
class Q_AV_PRIVATE_EXPORT AudioOutputPrivate : public AVOutputPrivate
{
public:
AudioOutputPrivate():
mute(false)
, volume_i(256)
, vol(1)
, speed(1.0)
, nb_buffers(8)
Expand All @@ -55,6 +57,7 @@ class Q_AV_PRIVATE_EXPORT AudioOutputPrivate : public AVOutputPrivate
, features(0)
, play_pos(0)
, processed_remain(0)
, scale_samples(0)
, index_enqueue(-1)
, index_deuqueue(-1)
{
Expand Down Expand Up @@ -127,8 +130,11 @@ class Q_AV_PRIVATE_EXPORT AudioOutputPrivate : public AVOutputPrivate
frame_infos.clear();
frame_infos.resize(nb_buffers);
}
/// call this if sample format or volume is changed
void updateSampleScaleFunc();

bool mute;
int volume_i;
qreal vol;
qreal speed;
AudioFormat format;
Expand All @@ -143,6 +149,7 @@ class Q_AV_PRIVATE_EXPORT AudioOutputPrivate : public AVOutputPrivate
#if AO_USE_TIMER
QElapsedTimer timer;
#endif
scale_samples_func scale_samples;
private:
// the index of current enqueue/dequeue
int index_enqueue, index_deuqueue;
Expand Down
98 changes: 98 additions & 0 deletions src/output/audio/AudioOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,90 @@

#include "QtAV/AudioOutput.h"
#include "QtAV/private/AudioOutput_p.h"
#include "QtAV/private/AVCompat.h"
#include "utils/Logger.h"

namespace QtAV {

/// from libavfilter/af_volume begin
static inline void scale_samples_u8(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
for (int i = 0; i < nb_samples; i++)
dst[i] = av_clip_uint8(((((qint64)src[i] - 128) * volume + 128) >> 8) + 128);
}

static inline void scale_samples_u8_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
for (int i = 0; i < nb_samples; i++)
dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
}

static inline void scale_samples_s16(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
int16_t *smp_dst = (int16_t *)dst;
const int16_t *smp_src = (const int16_t *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clip_int16(((qint64)smp_src[i] * volume + 128) >> 8);
}

static inline void scale_samples_s16_small(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
int16_t *smp_dst = (int16_t *)dst;
const int16_t *smp_src = (const int16_t *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
}

static inline void scale_samples_s32(quint8 *dst, const quint8 *src, int nb_samples, int volume, float)
{
qint32 *smp_dst = (qint32 *)dst;
const qint32 *smp_src = (const qint32 *)src;
for (int i = 0; i < nb_samples; i++)
smp_dst[i] = av_clipl_int32((((qint64)smp_src[i] * volume + 128) >> 8));
}
/// from libavfilter/af_volume end

//TODO: simd
template<typename T>
static inline void scale_samples(quint8 *dst, const quint8 *src, int nb_samples, int, float volume)
{
T *smp_dst = (T *)dst;
const T *smp_src = (const T *)src;
for (int i = 0; i < nb_samples; ++i)
smp_dst[i] = smp_src[i] * (T)volume;
}

scale_samples_func get_scaler(AudioFormat::SampleFormat fmt, qreal vol, int* voli)
{
int v = (int)(vol * 256.0 + 0.5);
if (voli)
*voli = v;
switch (fmt) {
case AudioFormat::SampleFormat_Unsigned8:
case AudioFormat::SampleFormat_Unsigned8Planar:
return v < 0x1000000 ? scale_samples_u8_small : scale_samples_u8;
case AudioFormat::SampleFormat_Signed16:
case AudioFormat::SampleFormat_Signed16Planar:
return v < 0x10000 ? scale_samples_s16_small : scale_samples_s16;
case AudioFormat::SampleFormat_Signed32:
case AudioFormat::SampleFormat_Signed32Planar:
return scale_samples_s32;
case AudioFormat::SampleFormat_Float:
case AudioFormat::SampleFormat_FloatPlanar:
return scale_samples<float>;
case AudioFormat::SampleFormat_Double:
case AudioFormat::SampleFormat_DoublePlanar:
return scale_samples<double>;
default:
return 0;
}
}

void AudioOutputPrivate::updateSampleScaleFunc()
{
scale_samples = get_scaler(format.sampleFormat(), vol, &volume_i);
}

AudioOutput::AudioOutput()
:AVOutput(*new AudioOutputPrivate())
{
Expand Down Expand Up @@ -60,6 +141,21 @@ bool AudioOutput::receiveData(const QByteArray &data, qreal pts)
if (d.paused)
return false;
d.data = data;
if (isMute()) {
//if (!(features() & SetMuted))
d.data.fill(0);
} else {
if (!qFuzzyCompare(volume(), (qreal)1.0)
&& d.scale_samples
// TODO: check backend support. user can disable this feature even if backend supports.
//&& !(features() & SetVolume)
) {
// TODO: af_volume needs samples_align to get nb_samples
const int nb_samples = d.data.size()/d.format.bytesPerSample();
quint8 *dst = (quint8*)d.data.constData();
d.scale_samples(dst, dst, nb_samples, d.volume_i, volume());
}
}
d.nextEnqueueInfo().data_size = data.size();
d.nextEnqueueInfo().timestamp = pts;
d.bufferAdded();
Expand All @@ -74,6 +170,7 @@ void AudioOutput::setAudioFormat(const AudioFormat& format)
}
if (d.format == format)
return;
d.updateSampleScaleFunc();
d.format = format;
}

Expand Down Expand Up @@ -118,6 +215,7 @@ void AudioOutput::setVolume(qreal volume)
return;
d.vol = volume;
emit volumeChanged(d.vol);
d.updateSampleScaleFunc();
}

qreal AudioOutput::volume() const
Expand Down
3 changes: 2 additions & 1 deletion tests/ao/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/******************************************************************************
QtAV: Media play library based on Qt and FFmpeg
Copyright (C) 2014 Wang Bin <[email protected]>
Copyright (C) 2014-2015 Wang Bin <[email protected]>
* This file is part of QtAV
Expand Down Expand Up @@ -87,6 +87,7 @@ int main(int argc, char** argv)
left = (left+1) % kTableSize;
right = (right+3)% kTableSize;
}
ao->setVolume(2*sin(2.0*M_PI/1000.0*timer.elapsed()));
ao->play(data);
}
ao->close();
Expand Down

0 comments on commit 097bb91

Please sign in to comment.