tdesktop/Telegram/SourceFiles/media/media_audio.cpp

1976 lines
59 KiB
C++
Raw Normal View History

2014-09-04 07:33:44 +00:00
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
2014-09-04 07:33:44 +00:00
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
2015-10-03 13:16:42 +00:00
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
2014-09-04 07:33:44 +00:00
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
2016-02-08 10:56:18 +00:00
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
2014-09-04 07:33:44 +00:00
*/
#include "stdafx.h"
#include "media/media_audio.h"
#include "media/media_audio_ffmpeg_loader.h"
#include "media/media_child_ffmpeg_loader.h"
#include "media/media_audio_loaders.h"
2014-09-04 07:33:44 +00:00
#include <AL/al.h>
#include <AL/alc.h>
2015-06-03 12:18:46 +00:00
#define AL_ALEXT_PROTOTYPES
#include <AL/alext.h>
extern "C" {
#ifdef Q_OS_MAC
#include <iconv.h>
#undef iconv_open
#undef iconv
#undef iconv_close
iconv_t iconv_open(const char* tocode, const char* fromcode) {
return libiconv_open(tocode, fromcode);
}
size_t iconv(iconv_t cd, char** inbuf, size_t *inbytesleft, char** outbuf, size_t *outbytesleft) {
return libiconv(cd, inbuf, inbytesleft, outbuf, outbytesleft);
}
int iconv_close(iconv_t cd) {
return libiconv_close(cd);
}
#endif // Q_OS_MAC
} // extern "C"
2014-09-04 07:33:44 +00:00
namespace {
ALCdevice *audioDevice = 0;
ALCcontext *audioContext = 0;
ALuint notifySource = 0;
ALuint notifyBuffer = 0;
2015-05-29 18:52:43 +00:00
uint64 notifyLengthMs = 0;
2015-05-29 18:52:43 +00:00
QMutex playerMutex;
AudioPlayer *player = 0;
float64 suppressAllGain = 1., suppressSongGain = 1.;
2015-05-29 18:52:43 +00:00
AudioCapture *capture = 0;
2014-09-04 07:33:44 +00:00
}
bool _checkALCError() {
ALenum errCode;
if ((errCode = alcGetError(audioDevice)) != ALC_NO_ERROR) {
2015-05-29 18:52:43 +00:00
LOG(("Audio Error: (alc) %1, %2").arg(errCode).arg((const char *)alcGetString(audioDevice, errCode)));
return false;
}
return true;
}
bool _checkCaptureError(ALCdevice *device) {
ALenum errCode;
if ((errCode = alcGetError(device)) != ALC_NO_ERROR) {
LOG(("Audio Error: (capture) %1, %2").arg(errCode).arg((const char *)alcGetString(audioDevice, errCode)));
2014-09-04 07:33:44 +00:00
return false;
}
return true;
}
bool _checkALError() {
ALenum errCode;
if ((errCode = alGetError()) != AL_NO_ERROR) {
2015-05-29 18:52:43 +00:00
LOG(("Audio Error: (al) %1, %2").arg(errCode).arg((const char *)alGetString(errCode)));
2014-09-04 07:33:44 +00:00
return false;
}
return true;
}
Q_DECLARE_METATYPE(AudioMsgId);
Q_DECLARE_METATYPE(VoiceWaveform);
2014-09-04 07:33:44 +00:00
void audioInit() {
2015-05-29 18:52:43 +00:00
if (!capture) {
capture = new AudioCapture();
cSetHasAudioCapture(capture->check());
2015-05-29 18:52:43 +00:00
}
2014-09-04 07:33:44 +00:00
if (audioDevice) return;
2015-05-29 18:52:43 +00:00
audioDevice = alcOpenDevice(0);
2014-09-04 07:33:44 +00:00
if (!audioDevice) {
LOG(("Audio Error: default sound device not present."));
return;
}
2014-09-04 07:33:44 +00:00
ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 };
audioContext = alcCreateContext(audioDevice, attributes);
alcMakeContextCurrent(audioContext);
if (!_checkALCError()) return audioFinish();
ALfloat v[] = { 0.f, 0.f, -1.f, 0.f, 1.f, 0.f };
alListener3f(AL_POSITION, 0.f, 0.f, 0.f);
alListener3f(AL_VELOCITY, 0.f, 0.f, 0.f);
alListenerfv(AL_ORIENTATION, v);
alDistanceModel(AL_NONE);
alGenSources(1, &notifySource);
alSourcef(notifySource, AL_PITCH, 1.f);
alSourcef(notifySource, AL_GAIN, 1.f);
alSource3f(notifySource, AL_POSITION, 0, 0, 0);
alSource3f(notifySource, AL_VELOCITY, 0, 0, 0);
alSourcei(notifySource, AL_LOOPING, 0);
alGenBuffers(1, &notifyBuffer);
if (!_checkALError()) return audioFinish();
QFile notify(st::newMsgSound);
if (!notify.open(QIODevice::ReadOnly)) return audioFinish();
QByteArray blob = notify.readAll();
const char *data = blob.constData();
if (blob.size() < 44) return audioFinish();
if (*((const uint32*)(data + 0)) != 0x46464952) return audioFinish(); // ChunkID - "RIFF"
if (*((const uint32*)(data + 4)) != uint32(blob.size() - 8)) return audioFinish(); // ChunkSize
2014-09-04 07:33:44 +00:00
if (*((const uint32*)(data + 8)) != 0x45564157) return audioFinish(); // Format - "WAVE"
if (*((const uint32*)(data + 12)) != 0x20746d66) return audioFinish(); // Subchunk1ID - "fmt "
uint32 subchunk1Size = *((const uint32*)(data + 16)), extra = subchunk1Size - 16;
if (subchunk1Size < 16 || (extra && extra < 2)) return audioFinish();
2014-09-04 07:33:44 +00:00
if (*((const uint16*)(data + 20)) != 1) return audioFinish(); // AudioFormat - PCM (1)
uint16 numChannels = *((const uint16*)(data + 22));
if (numChannels != 1 && numChannels != 2) return audioFinish();
uint32 sampleRate = *((const uint32*)(data + 24));
uint32 byteRate = *((const uint32*)(data + 28));
uint16 blockAlign = *((const uint16*)(data + 32));
uint16 bitsPerSample = *((const uint16*)(data + 34));
if (bitsPerSample % 8) return audioFinish();
uint16 bytesPerSample = bitsPerSample / 8;
if (bytesPerSample != 1 && bytesPerSample != 2) return audioFinish();
if (blockAlign != numChannels * bytesPerSample) return audioFinish();
if (byteRate != sampleRate * blockAlign) return audioFinish();
if (extra) {
uint16 extraSize = *((const uint16*)(data + 36));
if (uint32(extraSize + 2) != extra) return audioFinish();
if (uint32(blob.size()) < 44 + extra) return audioFinish();
2014-09-04 07:33:44 +00:00
}
if (*((const uint32*)(data + extra + 36)) != 0x61746164) return audioFinish(); // Subchunk2ID - "data"
uint32 subchunk2Size = *((const uint32*)(data + extra + 40));
if (subchunk2Size % (numChannels * bytesPerSample)) return audioFinish();
uint32 numSamples = subchunk2Size / (numChannels * bytesPerSample);
if (uint32(blob.size()) < 44 + extra + subchunk2Size) return audioFinish();
2014-09-04 07:33:44 +00:00
data += 44 + extra;
ALenum format = 0;
switch (bytesPerSample) {
case 1:
switch (numChannels) {
case 1: format = AL_FORMAT_MONO8; break;
case 2: format = AL_FORMAT_STEREO8; break;
}
break;
case 2:
switch (numChannels) {
case 1: format = AL_FORMAT_MONO16; break;
case 2: format = AL_FORMAT_STEREO16; break;
}
break;
}
if (!format) return audioFinish();
int32 addBytes = (sampleRate * 15 / 100) * bytesPerSample * numChannels; // add 150ms of silence
QByteArray fullData(addBytes + subchunk2Size, (bytesPerSample == 1) ? 128 : 0);
memcpy(fullData.data() + addBytes, data, subchunk2Size);
alBufferData(notifyBuffer, format, fullData.constData(), fullData.size(), sampleRate);
2014-09-04 07:33:44 +00:00
alSourcei(notifySource, AL_BUFFER, notifyBuffer);
notifyLengthMs = (numSamples * 1000ULL / sampleRate);
2014-09-04 07:33:44 +00:00
if (!_checkALError()) return audioFinish();
qRegisterMetaType<AudioMsgId>();
qRegisterMetaType<VoiceWaveform>();
2015-05-29 18:52:43 +00:00
player = new AudioPlayer();
2015-06-03 12:18:46 +00:00
alcDevicePauseSOFT(audioDevice);
cSetHasAudioPlayer(true);
2014-09-04 07:33:44 +00:00
}
void audioPlayNotify() {
2015-05-29 18:52:43 +00:00
if (!audioPlayer()) return;
2014-09-04 07:33:44 +00:00
2015-06-03 12:18:46 +00:00
audioPlayer()->resumeDevice();
2014-09-04 07:33:44 +00:00
alSourcePlay(notifySource);
emit audioPlayer()->suppressAll();
2015-05-29 18:52:43 +00:00
emit audioPlayer()->faderOnTimer();
2014-09-04 07:33:44 +00:00
}
// can be called at any moment when audio error
2014-09-04 07:33:44 +00:00
void audioFinish() {
2015-05-29 18:52:43 +00:00
if (player) {
delete player;
player = nullptr;
2015-05-29 18:52:43 +00:00
}
if (capture) {
delete capture;
capture = nullptr;
2014-09-04 07:33:44 +00:00
}
alSourceStop(notifySource);
if (alIsBuffer(notifyBuffer)) {
alDeleteBuffers(1, &notifyBuffer);
notifyBuffer = 0;
}
if (alIsSource(notifySource)) {
alDeleteSources(1, &notifySource);
notifySource = 0;
}
if (audioContext) {
alcMakeContextCurrent(nullptr);
2014-09-04 07:33:44 +00:00
alcDestroyContext(audioContext);
audioContext = nullptr;
2014-09-04 07:33:44 +00:00
}
if (audioDevice) {
alcCloseDevice(audioDevice);
audioDevice = nullptr;
2014-09-04 07:33:44 +00:00
}
cSetHasAudioCapture(false);
cSetHasAudioPlayer(false);
2014-09-04 07:33:44 +00:00
}
void AudioPlayer::AudioMsg::clear() {
audio = AudioMsgId();
file = FileLocation();
data = QByteArray();
playbackState = defaultState();
skipStart = skipEnd = 0;
loading = false;
started = 0;
if (alIsSource(source)) {
alSourceStop(source);
}
for (int i = 0; i < 3; ++i) {
if (samplesCount[i]) {
ALuint buffer = 0;
// This cleans some random queued buffer, not exactly the buffers[i].
alSourceUnqueueBuffers(source, 1, &buffer);
samplesCount[i] = 0;
}
}
nextBuffer = 0;
videoData = nullptr;
videoPlayId = 0;
}
AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0),
2015-05-29 18:52:43 +00:00
_fader(new AudioPlayerFader(&_faderThread)),
_loader(new AudioPlayerLoaders(&_loaderThread)) {
2014-09-04 07:33:44 +00:00
connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer()));
connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong()));
connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong()));
connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll()));
2015-07-03 08:47:16 +00:00
connect(this, SIGNAL(songVolumeChanged()), _fader, SLOT(onSongVolumeChanged()));
connect(this, SIGNAL(videoVolumeChanged()), _fader, SLOT(onVideoVolumeChanged()));
2015-07-03 08:47:16 +00:00
connect(this, SIGNAL(loaderOnStart(const AudioMsgId&,qint64)), _loader, SLOT(onStart(const AudioMsgId&,qint64)));
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)));
2014-09-04 07:33:44 +00:00
connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit()));
connect(&_loaderThread, SIGNAL(started()), _loader, SLOT(onInit()));
connect(&_faderThread, SIGNAL(finished()), _fader, SLOT(deleteLater()));
connect(&_loaderThread, SIGNAL(finished()), _loader, SLOT(deleteLater()));
connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer()));
connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&)));
connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&)));
connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)));
connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&)));
connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&)));
connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)), Qt::QueuedConnection);
2014-09-04 07:33:44 +00:00
_loaderThread.start();
_faderThread.start();
}
2015-05-29 18:52:43 +00:00
AudioPlayer::~AudioPlayer() {
2014-09-04 07:33:44 +00:00
{
2015-05-29 18:52:43 +00:00
QMutexLocker lock(&playerMutex);
player = nullptr;
2014-09-04 07:33:44 +00:00
}
auto clearAudioMsg = [](AudioMsg *msg) {
alSourceStop(msg->source);
if (alIsBuffer(msg->buffers[0])) {
alDeleteBuffers(3, msg->buffers);
for (int j = 0; j < 3; ++j) {
msg->buffers[j] = msg->samplesCount[j] = 0;
}
}
if (alIsSource(msg->source)) {
alDeleteSources(1, &msg->source);
msg->source = 0;
2014-09-04 07:33:44 +00:00
}
};
for (int i = 0; i < AudioSimultaneousLimit; ++i) {
clearAudioMsg(dataForType(AudioMsgId::Type::Voice, i));
clearAudioMsg(dataForType(AudioMsgId::Type::Song, i));
2014-09-04 07:33:44 +00:00
}
clearAudioMsg(&_videoData);
2014-09-04 07:33:44 +00:00
_faderThread.quit();
_loaderThread.quit();
_faderThread.wait();
_loaderThread.wait();
}
void AudioPlayer::onError(const AudioMsgId &audio) {
2015-07-03 08:47:16 +00:00
emit stoppedOnError(audio);
if (audio.type() == AudioMsgId::Type::Voice) {
emit unsuppressSong();
}
}
void AudioPlayer::onStopped(const AudioMsgId &audio) {
emit updated(audio);
if (audio.type() == AudioMsgId::Type::Voice) {
emit unsuppressSong();
}
2014-09-04 07:33:44 +00:00
}
AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index) {
if (index < 0) index = *currentIndex(type);
switch (type) {
case AudioMsgId::Type::Voice: return &_audioData[index];
case AudioMsgId::Type::Song: return &_songData[index];
case AudioMsgId::Type::Video: return &_videoData;
}
return nullptr;
}
const AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index) const {
return const_cast<AudioPlayer*>(this)->dataForType(type, index);
}
int *AudioPlayer::currentIndex(AudioMsgId::Type type) {
switch (type) {
case AudioMsgId::Type::Voice: return &_audioCurrent;
case AudioMsgId::Type::Song: return &_songCurrent;
case AudioMsgId::Type::Video: { static int videoIndex = 0; return &videoIndex; }
}
return nullptr;
}
const int *AudioPlayer::currentIndex(AudioMsgId::Type type) const {
return const_cast<AudioPlayer*>(this)->currentIndex(type);
}
bool AudioPlayer::updateCurrentStarted(AudioMsgId::Type type, int32 pos) {
auto data = dataForType(type);
if (!data) return false;
2014-09-04 07:33:44 +00:00
if (pos < 0) {
if (alIsSource(data->source)) {
alGetSourcei(data->source, AL_SAMPLE_OFFSET, &pos);
2014-09-04 07:33:44 +00:00
} else {
pos = 0;
}
2015-07-03 08:47:16 +00:00
if (!_checkALError()) {
setStoppedState(data, AudioPlayerStoppedAtError);
onError(data->audio);
2015-07-03 08:47:16 +00:00
return false;
}
2014-09-04 07:33:44 +00:00
}
data->started = data->playbackState.position = pos + data->skipStart;
2014-09-04 07:33:44 +00:00
return true;
}
bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) {
auto current = dataForType(type);
if (!current) return false;
2014-09-04 07:33:44 +00:00
switch (current->playbackState.state) {
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying:
current->playbackState.state = AudioPlayerFinishing;
updateCurrentStarted(type);
2015-07-03 08:47:16 +00:00
if (fadedStart) *fadedStart = true;
break;
case AudioPlayerPausing:
current->playbackState.state = AudioPlayerFinishing;
2015-07-03 08:47:16 +00:00
if (fadedStart) *fadedStart = true;
break;
case AudioPlayerPaused:
2015-07-03 08:47:16 +00:00
case AudioPlayerPausedAtEnd:
setStoppedState(current);
return true;
}
return false;
}
2015-07-03 08:47:16 +00:00
void AudioPlayer::play(const AudioMsgId &audio, int64 position) {
auto type = audio.type();
AudioMsgId stopped;
{
QMutexLocker lock(&playerMutex);
bool fadedStart = false;
auto current = dataForType(type);
if (!current) return;
if (current->audio != audio) {
if (fadedStop(type, &fadedStart)) {
stopped = current->audio;
}
if (current->audio) {
emit loaderOnCancel(current->audio);
emit faderOnTimer();
}
2014-09-04 07:33:44 +00:00
auto foundCurrent = currentIndex(type);
int index = 0;
for (; index < AudioSimultaneousLimit; ++index) {
if (dataForType(type, index)->audio == audio) {
*foundCurrent = index;
break;
}
}
if (index == AudioSimultaneousLimit && ++*foundCurrent >= AudioSimultaneousLimit) {
*foundCurrent -= AudioSimultaneousLimit;
}
current = dataForType(type);
}
current->audio = audio;
current->file = audio.audio()->location(true);
current->data = audio.audio()->data();
if (current->file.isEmpty() && current->data.isEmpty()) {
if (audio.type() == AudioMsgId::Type::Song) {
setStoppedState(current);
if (!audio.audio()->loading()) {
DocumentOpenClickHandler::doOpen(audio.audio());
}
} else {
setStoppedState(current, AudioPlayerStoppedAtError);
onError(audio);
2015-07-03 08:47:16 +00:00
}
} else {
current->playbackState.state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying;
current->loading = true;
emit loaderOnStart(audio, position);
if (type == AudioMsgId::Type::Voice) {
emit suppressSong();
}
2014-09-04 07:33:44 +00:00
}
}
if (stopped) emit updated(stopped);
2014-09-04 07:33:44 +00:00
}
void AudioPlayer::initFromVideo(uint64 videoPlayId, std_::unique_ptr<VideoSoundData> &&data, int64 position) {
AudioMsgId stopped;
{
QMutexLocker lock(&playerMutex);
// Pause current song.
auto currentSong = dataForType(AudioMsgId::Type::Song);
float64 suppressGain = suppressSongGain * Global::SongVolume();
switch (currentSong->playbackState.state) {
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying:
currentSong->playbackState.state = AudioPlayerPausing;
updateCurrentStarted(AudioMsgId::Type::Song);
break;
case AudioPlayerFinishing: currentSong->playbackState.state = AudioPlayerPausing; break;
}
auto type = AudioMsgId::Type::Video;
auto current = dataForType(type);
t_assert(current != nullptr);
if (current->audio) {
fadedStop(type);
stopped = current->audio;
emit loaderOnCancel(current->audio);
}
emit faderOnTimer();
current->clear();
current->audio = AudioMsgId(AudioMsgId::Type::Video);
current->videoPlayId = videoPlayId;
current->videoData = std_::move(data);
{
QMutexLocker videoLock(&_lastVideoMutex);
_lastVideoPlayId = current->videoPlayId;
_lastVideoPlaybackWhen = 0;
_lastVideoPlaybackCorrectedMs = 0;
}
_loader->startFromVideo(current->videoPlayId);
current->playbackState.state = AudioPlayerPaused;
current->loading = true;
emit loaderOnStart(current->audio, position);
}
if (stopped) emit updated(stopped);
}
void AudioPlayer::stopFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&playerMutex);
auto data = dataForType(AudioMsgId::Type::Video);
t_assert(data != nullptr);
if (data->videoPlayId != videoPlayId) {
return;
}
current = data->audio;
fadedStop(AudioMsgId::Type::Video);
data->clear();
}
if (current) emit updated(current);
}
void AudioPlayer::pauseFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&playerMutex);
auto type = AudioMsgId::Type::Video;
auto data = dataForType(type);
t_assert(data != nullptr);
if (data->videoPlayId != videoPlayId) {
return;
}
current = data->audio;
switch (data->playbackState.state) {
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying: {
data->playbackState.state = AudioPlayerPaused;
updateCurrentStarted(type);
ALint state = AL_INITIAL;
alGetSourcei(data->source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state == AL_PLAYING) {
alSourcePause(data->source);
if (!checkCurrentALError(type)) return;
}
} break;
}
emit faderOnTimer();
}
if (current) emit updated(current);
}
void AudioPlayer::resumeFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&playerMutex);
auto type = AudioMsgId::Type::Video;
auto data = dataForType(type);
t_assert(data != nullptr);
if (data->videoPlayId != videoPlayId) {
return;
}
float64 suppressGain = suppressSongGain * Global::VideoVolume();
current = data->audio;
switch (data->playbackState.state) {
case AudioPlayerPausing:
case AudioPlayerPaused:
case AudioPlayerPausedAtEnd: {
if (data->playbackState.state == AudioPlayerPaused) {
updateCurrentStarted(type);
} else if (data->playbackState.state == AudioPlayerPausedAtEnd) {
if (alIsSource(data->source)) {
alSourcei(data->source, AL_SAMPLE_OFFSET, qMax(data->playbackState.position - data->skipStart, 0LL));
if (!checkCurrentALError(type)) return;
}
}
data->playbackState.state = AudioPlayerPlaying;
ALint state = AL_INITIAL;
alGetSourcei(data->source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state != AL_PLAYING) {
audioPlayer()->resumeDevice();
alSourcef(data->source, AL_GAIN, suppressGain);
if (!checkCurrentALError(type)) return;
alSourcePlay(data->source);
if (!checkCurrentALError(type)) return;
}
} break;
}
emit faderOnTimer();
}
if (current) emit updated(current);
}
void AudioPlayer::feedFromVideo(VideoSoundPart &&part) {
_loader->feedFromVideo(std_::move(part));
}
int64 AudioPlayer::getVideoCorrectedTime(uint64 playId, int64 frameMs, uint64 systemMs) {
int64 result = frameMs;
QMutexLocker videoLock(&_lastVideoMutex);
if (_lastVideoPlayId == playId && _lastVideoPlaybackWhen > 0) {
result = static_cast<int64>(_lastVideoPlaybackCorrectedMs);
if (systemMs > _lastVideoPlaybackWhen) {
result += (systemMs - _lastVideoPlaybackWhen);
}
}
return result;
}
void AudioPlayer::videoSoundProgress(const AudioMsgId &audio) {
auto type = audio.type();
t_assert(type == AudioMsgId::Type::Video);
QMutexLocker lock(&playerMutex);
QMutexLocker videoLock(&_lastVideoMutex);
auto current = dataForType(type);
t_assert(current != nullptr);
if (current->videoPlayId == _lastVideoPlayId && current->playbackState.duration && current->playbackState.frequency) {
_lastVideoPlaybackWhen = getms();
_lastVideoPlaybackCorrectedMs = (current->playbackState.position * 1000ULL) / current->playbackState.frequency;
}
}
bool AudioPlayer::checkCurrentALError(AudioMsgId::Type type) {
2015-07-03 08:47:16 +00:00
if (_checkALError()) return true;
auto data = dataForType(type);
if (!data) {
setStoppedState(data, AudioPlayerStoppedAtError);
onError(data->audio);
2015-07-03 08:47:16 +00:00
}
return false;
}
void AudioPlayer::pauseresume(AudioMsgId::Type type, bool fast) {
2015-05-29 18:52:43 +00:00
QMutexLocker lock(&playerMutex);
2014-09-04 07:33:44 +00:00
auto current = dataForType(type);
float64 suppressGain = 1.;
switch (type) {
case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break;
case AudioMsgId::Type::Song: suppressGain = suppressSongGain * Global::SongVolume(); break;
case AudioMsgId::Type::Video: suppressGain = suppressSongGain * Global::VideoVolume(); break;
}
switch (current->playbackState.state) {
2015-05-29 18:52:43 +00:00
case AudioPlayerPausing:
case AudioPlayerPaused:
2015-07-03 08:47:16 +00:00
case AudioPlayerPausedAtEnd: {
if (current->playbackState.state == AudioPlayerPaused) {
updateCurrentStarted(type);
} else if (current->playbackState.state == AudioPlayerPausedAtEnd) {
2015-07-03 08:47:16 +00:00
if (alIsSource(current->source)) {
alSourcei(current->source, AL_SAMPLE_OFFSET, qMax(current->playbackState.position - current->skipStart, 0LL));
2015-07-03 08:47:16 +00:00
if (!checkCurrentALError(type)) return;
}
}
current->playbackState.state = fast ? AudioPlayerPlaying : AudioPlayerResuming;
2015-07-03 08:47:16 +00:00
ALint state = AL_INITIAL;
alGetSourcei(current->source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state != AL_PLAYING) {
audioPlayer()->resumeDevice();
alSourcef(current->source, AL_GAIN, suppressGain);
if (!checkCurrentALError(type)) return;
alSourcePlay(current->source);
if (!checkCurrentALError(type)) return;
2014-09-04 07:33:44 +00:00
}
if (type == AudioMsgId::Type::Voice) emit suppressSong();
2015-07-03 08:47:16 +00:00
} break;
2015-05-29 18:52:43 +00:00
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying:
current->playbackState.state = AudioPlayerPausing;
updateCurrentStarted(type);
if (type == AudioMsgId::Type::Voice) emit unsuppressSong();
2014-09-04 07:33:44 +00:00
break;
case AudioPlayerFinishing: current->playbackState.state = AudioPlayerPausing; break;
2014-09-04 07:33:44 +00:00
}
emit faderOnTimer();
}
2015-07-03 08:47:16 +00:00
void AudioPlayer::seek(int64 position) {
QMutexLocker lock(&playerMutex);
auto type = AudioMsgId::Type::Song;
auto current = dataForType(type);
2015-07-03 08:47:16 +00:00
float64 suppressGain = 1.;
switch (type) {
case AudioMsgId::Type::Voice: suppressGain = suppressAllGain; break;
case AudioMsgId::Type::Song: suppressGain = suppressSongGain * Global::SongVolume(); break;
2015-07-03 08:47:16 +00:00
}
auto audio = current->audio;
2015-07-03 08:47:16 +00:00
bool isSource = alIsSource(current->source);
bool fastSeek = (position >= current->skipStart && position < current->playbackState.duration - current->skipEnd - (current->skipEnd ? AudioVoiceMsgFrequency : 0));
2015-07-03 08:47:16 +00:00
if (fastSeek && isSource) {
alSourcei(current->source, AL_SAMPLE_OFFSET, position - current->skipStart);
if (!checkCurrentALError(type)) return;
alSourcef(current->source, AL_GAIN, 1. * suppressGain);
if (!checkCurrentALError(type)) return;
updateCurrentStarted(type, position - current->skipStart);
} else {
setStoppedState(current);
if (isSource) alSourceStop(current->source);
}
switch (current->playbackState.state) {
2015-07-03 08:47:16 +00:00
case AudioPlayerPausing:
case AudioPlayerPaused:
case AudioPlayerPausedAtEnd: {
if (current->playbackState.state == AudioPlayerPausedAtEnd) {
current->playbackState.state = AudioPlayerPaused;
2015-07-03 08:47:16 +00:00
}
lock.unlock();
return pauseresume(type, true);
} break;
case AudioPlayerStarting:
case AudioPlayerResuming:
case AudioPlayerPlaying:
current->playbackState.state = AudioPlayerPausing;
2015-07-03 08:47:16 +00:00
updateCurrentStarted(type);
if (type == AudioMsgId::Type::Voice) emit unsuppressSong();
2015-07-03 08:47:16 +00:00
break;
case AudioPlayerFinishing:
case AudioPlayerStopped:
case AudioPlayerStoppedAtEnd:
case AudioPlayerStoppedAtError:
case AudioPlayerStoppedAtStart:
lock.unlock();
return play(audio, position);
2015-07-03 08:47:16 +00:00
}
emit faderOnTimer();
}
void AudioPlayer::stop(AudioMsgId::Type type) {
AudioMsgId current;
{
QMutexLocker lock(&playerMutex);
auto data = dataForType(type);
t_assert(data != nullptr);
current = data->audio;
fadedStop(type);
if (type == AudioMsgId::Type::Video) {
data->clear();
}
}
if (current) emit updated(current);
}
void AudioPlayer::stopAndClear() {
AudioMsg *current_audio = nullptr, *current_song = nullptr;
{
QMutexLocker lock(&playerMutex);
if ((current_audio = dataForType(AudioMsgId::Type::Voice))) {
setStoppedState(current_audio);
}
if ((current_song = dataForType(AudioMsgId::Type::Song))) {
setStoppedState(current_song);
}
}
if (current_song) {
emit updated(current_song->audio);
}
if (current_audio) {
emit updated(current_audio->audio);
}
{
QMutexLocker lock(&playerMutex);
auto clearAndCancel = [this](AudioMsgId::Type type, int index) {
auto data = dataForType(type, index);
if (data->audio) {
emit loaderOnCancel(data->audio);
}
data->clear();
};
for (int index = 0; index < AudioSimultaneousLimit; ++index) {
clearAndCancel(AudioMsgId::Type::Voice, index);
clearAndCancel(AudioMsgId::Type::Song, index);
}
_videoData.clear();
_loader->stopFromVideo();
2015-07-03 08:47:16 +00:00
}
}
AudioPlaybackState AudioPlayer::currentVideoState(uint64 videoPlayId) {
QMutexLocker lock(&playerMutex);
auto current = dataForType(AudioMsgId::Type::Video);
if (!current || current->videoPlayId != videoPlayId) return AudioPlaybackState();
return current->playbackState;
}
AudioPlaybackState AudioPlayer::currentState(AudioMsgId *audio, AudioMsgId::Type type) {
QMutexLocker lock(&playerMutex);
auto current = dataForType(type);
if (!current) return AudioPlaybackState();
if (audio) *audio = current->audio;
return current->playbackState;
}
void AudioPlayer::setStoppedState(AudioMsg *current, AudioPlayerState state) {
current->playbackState.state = state;
current->playbackState.position = 0;
2015-07-03 08:47:16 +00:00
}
void AudioPlayer::clearStoppedAtStart(const AudioMsgId &audio) {
2015-05-29 18:52:43 +00:00
QMutexLocker lock(&playerMutex);
auto data = dataForType(audio.type());
if (data && data->audio == audio && data->playbackState.state == AudioPlayerStoppedAtStart) {
setStoppedState(data);
}
2014-09-04 07:33:44 +00:00
}
2015-06-03 12:18:46 +00:00
void AudioPlayer::resumeDevice() {
_fader->resumeDevice();
2015-01-10 13:08:30 +00:00
}
namespace internal {
QMutex *audioPlayerMutex() {
return &playerMutex;
}
float64 audioSuppressGain() {
return suppressAllGain;
}
float64 audioSuppressSongGain() {
return suppressSongGain;
}
bool audioCheckError() {
return _checkALError();
}
} // namespace internal
2015-05-29 18:52:43 +00:00
AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) {
connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart()));
connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool)));
connect(_capture, SIGNAL(done(QByteArray,VoiceWaveform,qint32)), this, SIGNAL(onDone(QByteArray,VoiceWaveform,qint32)));
connect(_capture, SIGNAL(update(quint16,qint32)), this, SIGNAL(onUpdate(quint16,qint32)));
2015-05-29 18:52:43 +00:00
connect(_capture, SIGNAL(error()), this, SIGNAL(onError()));
connect(&_captureThread, SIGNAL(started()), _capture, SLOT(onInit()));
connect(&_captureThread, SIGNAL(finished()), _capture, SLOT(deleteLater()));
_captureThread.start();
}
void AudioCapture::start() {
emit captureOnStart();
}
void AudioCapture::stop(bool needResult) {
emit captureOnStop(needResult);
}
bool AudioCapture::check() {
if (auto defaultDevice = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) {
if (auto device = alcCaptureOpenDevice(defaultDevice, AudioVoiceMsgFrequency, AL_FORMAT_MONO16, AudioVoiceMsgFrequency / 5)) {
alcCaptureCloseDevice(device);
return _checkALCError();
}
}
return false;
}
2015-05-29 18:52:43 +00:00
AudioCapture::~AudioCapture() {
capture = nullptr;
2015-05-29 18:52:43 +00:00
_captureThread.quit();
_captureThread.wait();
}
AudioPlayer *audioPlayer() {
return player;
}
AudioCapture *audioCapture() {
return capture;
2014-09-04 07:33:44 +00:00
}
AudioPlayerFader::AudioPlayerFader(QThread *thread) : _timer(this), _pauseFlag(false), _paused(true),
_suppressAll(false), _suppressAllAnim(false), _suppressSong(false), _suppressSongAnim(false),
_suppressAllGain(1., 1.), _suppressSongGain(1., 1.),
_suppressAllStart(0), _suppressSongStart(0) {
2014-09-04 07:33:44 +00:00
moveToThread(thread);
_timer.moveToThread(thread);
2015-06-03 12:18:46 +00:00
_pauseTimer.moveToThread(thread);
2015-01-10 13:08:30 +00:00
2014-09-04 07:33:44 +00:00
_timer.setSingleShot(true);
connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer()));
2015-01-10 13:08:30 +00:00
2015-06-03 12:18:46 +00:00
_pauseTimer.setSingleShot(true);
connect(&_pauseTimer, SIGNAL(timeout()), this, SLOT(onPauseTimer()));
connect(this, SIGNAL(stopPauseDevice()), this, SLOT(onPauseTimerStop()), Qt::QueuedConnection);
2014-09-04 07:33:44 +00:00
}
2015-05-29 18:52:43 +00:00
void AudioPlayerFader::onInit() {
2014-09-04 07:33:44 +00:00
}
2015-05-29 18:52:43 +00:00
void AudioPlayerFader::onTimer() {
QMutexLocker lock(&playerMutex);
AudioPlayer *voice = audioPlayer();
2014-09-04 07:33:44 +00:00
if (!voice) return;
bool suppressAudioChanged = false, suppressSongChanged = false;
if (_suppressAll || _suppressSongAnim) {
uint64 ms = getms();
float64 wasSong = suppressSongGain;
if (_suppressAll) {
float64 wasAudio = suppressAllGain;
if (ms >= _suppressAllStart + notifyLengthMs || ms < _suppressAllStart) {
_suppressAll = _suppressAllAnim = false;
_suppressAllGain = anim::fvalue(1., 1.);
} else if (ms > _suppressAllStart + notifyLengthMs - AudioFadeDuration) {
if (_suppressAllGain.to() != 1.) _suppressAllGain.start(1.);
_suppressAllGain.update(1. - ((_suppressAllStart + notifyLengthMs - ms) / float64(AudioFadeDuration)), anim::linear);
} else if (ms >= _suppressAllStart + st::notifyFastAnim) {
if (_suppressAllAnim) {
_suppressAllGain.finish();
_suppressAllAnim = false;
2014-09-04 07:33:44 +00:00
}
} else if (ms > _suppressAllStart) {
_suppressAllGain.update((ms - _suppressAllStart) / st::notifyFastAnim, anim::linear);
2014-09-04 07:33:44 +00:00
}
suppressAllGain = _suppressAllGain.current();
suppressAudioChanged = (suppressAllGain != wasAudio);
}
if (_suppressSongAnim) {
if (ms >= _suppressSongStart + AudioFadeDuration) {
_suppressSongGain.finish();
_suppressSongAnim = false;
} else {
_suppressSongGain.update((ms - _suppressSongStart) / float64(AudioFadeDuration), anim::linear);
2014-09-04 07:33:44 +00:00
}
}
suppressSongGain = qMin(suppressAllGain, _suppressSongGain.current());
suppressSongChanged = (suppressSongGain != wasSong);
2014-09-04 07:33:44 +00:00
}
bool hasFading = (_suppressAll || _suppressSongAnim);
bool hasPlaying = false;
auto updatePlayback = [this, voice, &hasPlaying, &hasFading](AudioMsgId::Type type, int index, float64 suppressGain, bool suppressGainChanged) {
auto data = voice->dataForType(type, index);
if ((data->playbackState.state & AudioPlayerStoppedMask) || data->playbackState.state == AudioPlayerPaused || !data->source) return;
int32 emitSignals = updateOnePlayback(data, hasPlaying, hasFading, suppressGain, suppressGainChanged);
if (emitSignals & EmitError) emit error(data->audio);
if (emitSignals & EmitStopped) emit audioStopped(data->audio);
if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(data->audio);
if (emitSignals & EmitNeedToPreload) emit needToPreload(data->audio);
};
auto suppressGainForMusic = suppressSongGain * Global::SongVolume();
auto suppressGainForMusicChanged = suppressSongChanged || _songVolumeChanged;
for (int i = 0; i < AudioSimultaneousLimit; ++i) {
updatePlayback(AudioMsgId::Type::Voice, i, suppressAllGain, suppressAudioChanged);
updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged);
}
auto suppressGainForVideo = suppressSongGain * Global::VideoVolume();
auto suppressGainForVideoChanged = suppressSongChanged || _videoVolumeChanged;
updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForVideo, suppressGainForVideoChanged);
_songVolumeChanged = _videoVolumeChanged = false;
if (!hasFading) {
if (!hasPlaying) {
ALint state = AL_INITIAL;
alGetSourcei(notifySource, AL_SOURCE_STATE, &state);
if (_checkALError() && state == AL_PLAYING) {
hasPlaying = true;
}
2015-01-10 13:08:30 +00:00
}
}
2014-09-04 07:33:44 +00:00
if (hasFading) {
_timer.start(AudioFadeTimeout);
2015-06-03 12:18:46 +00:00
resumeDevice();
2014-09-04 07:33:44 +00:00
} else if (hasPlaying) {
_timer.start(AudioCheckPositionTimeout);
2015-06-03 12:18:46 +00:00
resumeDevice();
2015-01-10 13:08:30 +00:00
} else {
2015-06-03 12:18:46 +00:00
QMutexLocker lock(&_pauseMutex);
_pauseFlag = true;
_pauseTimer.start(AudioPauseDeviceTimeout);
2014-09-04 07:33:44 +00:00
}
}
int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged) {
bool playing = false, fading = false;
ALint pos = 0;
ALint state = AL_INITIAL;
alGetSourcei(m->source, AL_SAMPLE_OFFSET, &pos);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
alGetSourcei(m->source, AL_SOURCE_STATE, &state);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
int32 emitSignals = 0;
switch (m->playbackState.state) {
case AudioPlayerFinishing:
case AudioPlayerPausing:
case AudioPlayerStarting:
case AudioPlayerResuming:
fading = true;
break;
case AudioPlayerPlaying:
playing = true;
break;
}
if (fading && (state == AL_PLAYING || !m->loading)) {
if (state != AL_PLAYING) {
fading = false;
if (m->source) {
alSourceStop(m->source);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
alSourcef(m->source, AL_GAIN, 1);
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
}
if (m->playbackState.state == AudioPlayerPausing) {
m->playbackState.state = AudioPlayerPausedAtEnd;
2015-07-03 08:47:16 +00:00
} else {
setStoppedState(m, AudioPlayerStoppedAtEnd);
}
emitSignals |= EmitStopped;
} else if (1000 * (pos + m->skipStart - m->started) >= AudioFadeDuration * m->playbackState.frequency) {
fading = false;
alSourcef(m->source, AL_GAIN, 1. * suppressGain);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
switch (m->playbackState.state) {
2015-07-03 08:47:16 +00:00
case AudioPlayerFinishing:
alSourceStop(m->source);
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
setStoppedState(m);
state = AL_STOPPED;
break;
case AudioPlayerPausing:
alSourcePause(m->source);
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
m->playbackState.state = AudioPlayerPaused;
2015-07-03 08:47:16 +00:00
break;
case AudioPlayerStarting:
case AudioPlayerResuming:
m->playbackState.state = AudioPlayerPlaying;
playing = true;
break;
}
} else {
float64 newGain = 1000. * (pos + m->skipStart - m->started) / (AudioFadeDuration * m->playbackState.frequency);
if (m->playbackState.state == AudioPlayerPausing || m->playbackState.state == AudioPlayerFinishing) {
newGain = 1. - newGain;
}
alSourcef(m->source, AL_GAIN, newGain * suppressGain);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
}
} else if (playing && (state == AL_PLAYING || !m->loading)) {
if (state != AL_PLAYING) {
playing = false;
if (m->source) {
alSourceStop(m->source);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
alSourcef(m->source, AL_GAIN, 1);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
}
2015-07-03 08:47:16 +00:00
setStoppedState(m, AudioPlayerStoppedAtEnd);
emitSignals |= EmitStopped;
} else if (suppressGainChanged) {
alSourcef(m->source, AL_GAIN, suppressGain);
2015-07-03 08:47:16 +00:00
if (!_checkALError()) { setStoppedState(m, AudioPlayerStoppedAtError); return EmitError; }
}
}
if (state == AL_PLAYING && pos + m->skipStart - m->playbackState.position >= AudioCheckPositionDelta) {
m->playbackState.position = pos + m->skipStart;
emitSignals |= EmitPositionUpdated;
}
if (playing || m->playbackState.state == AudioPlayerStarting || m->playbackState.state == AudioPlayerResuming) {
if (!m->loading && m->skipEnd > 0 && m->playbackState.position + AudioPreloadSamples + m->skipEnd > m->playbackState.duration) {
m->loading = true;
emitSignals |= EmitNeedToPreload;
}
}
if (playing) hasPlaying = true;
if (fading) hasFading = true;
return emitSignals;
}
void AudioPlayerFader::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) {
m->playbackState.state = state;
m->playbackState.position = 0;
2015-07-03 08:47:16 +00:00
}
2015-06-03 12:18:46 +00:00
void AudioPlayerFader::onPauseTimer() {
QMutexLocker lock(&_pauseMutex);
if (_pauseFlag) {
_paused = true;
alcDevicePauseSOFT(audioDevice);
2015-01-10 13:08:30 +00:00
}
}
2015-06-03 12:18:46 +00:00
void AudioPlayerFader::onPauseTimerStop() {
if (_pauseTimer.isActive()) _pauseTimer.stop();
2015-01-10 13:08:30 +00:00
}
void AudioPlayerFader::onSuppressSong() {
if (!_suppressSong) {
_suppressSong = true;
_suppressSongAnim = true;
_suppressSongStart = getms();
_suppressSongGain.start(st::suppressSong);
onTimer();
}
}
void AudioPlayerFader::onUnsuppressSong() {
if (_suppressSong) {
_suppressSong = false;
_suppressSongAnim = true;
_suppressSongStart = getms();
_suppressSongGain.start(1.);
onTimer();
}
}
void AudioPlayerFader::onSuppressAll() {
_suppressAll = true;
_suppressAllStart = getms();
_suppressAllGain.start(st::suppressAll);
onTimer();
}
2015-07-03 08:47:16 +00:00
void AudioPlayerFader::onSongVolumeChanged() {
_songVolumeChanged = true;
onTimer();
}
void AudioPlayerFader::onVideoVolumeChanged() {
_videoVolumeChanged = true;
onTimer();
}
2015-06-03 12:18:46 +00:00
void AudioPlayerFader::resumeDevice() {
QMutexLocker lock(&_pauseMutex);
_pauseFlag = false;
emit stopPauseDevice();
if (_paused) {
_paused = false;
alcDeviceResumeSOFT(audioDevice);
}
2015-01-10 13:08:30 +00:00
}
2015-05-29 18:52:43 +00:00
struct AudioCapturePrivate {
AudioCapturePrivate()
: device(0)
, fmt(0)
, ioBuffer(0)
, ioContext(0)
, fmtContext(0)
, stream(0)
, codec(0)
, codecContext(0)
, opened(false)
, srcSamples(0)
, dstSamples(0)
, maxDstSamples(0)
, dstSamplesSize(0)
, fullSamples(0)
, srcSamplesData(0)
, dstSamplesData(0)
, swrContext(0)
, lastUpdate(0)
, levelMax(0)
2016-02-14 19:46:01 +00:00
, dataPos(0)
, waveformMod(0)
, waveformEach(AudioVoiceMsgFrequency / 100)
2016-02-14 19:46:01 +00:00
, waveformPeak(0) {
2015-05-29 18:52:43 +00:00
}
ALCdevice *device;
AVOutputFormat *fmt;
uchar *ioBuffer;
AVIOContext *ioContext;
AVFormatContext *fmtContext;
AVStream *stream;
AVCodec *codec;
AVCodecContext *codecContext;
bool opened;
int32 srcSamples, dstSamples, maxDstSamples, dstSamplesSize, fullSamples;
uint8_t **srcSamplesData, **dstSamplesData;
SwrContext *swrContext;
int32 lastUpdate;
uint16 levelMax;
2015-05-29 18:52:43 +00:00
QByteArray data;
int32 dataPos;
int64 waveformMod, waveformEach;
uint16 waveformPeak;
QVector<uchar> waveform;
2015-05-29 18:52:43 +00:00
static int _read_data(void *opaque, uint8_t *buf, int buf_size) {
AudioCapturePrivate *l = reinterpret_cast<AudioCapturePrivate*>(opaque);
int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
if (nbytes <= 0) {
return 0;
}
memcpy(buf, l->data.constData() + l->dataPos, nbytes);
l->dataPos += nbytes;
return nbytes;
}
static int _write_data(void *opaque, uint8_t *buf, int buf_size) {
AudioCapturePrivate *l = reinterpret_cast<AudioCapturePrivate*>(opaque);
if (buf_size <= 0) return 0;
if (l->dataPos + buf_size > l->data.size()) l->data.resize(l->dataPos + buf_size);
memcpy(l->data.data() + l->dataPos, buf, buf_size);
l->dataPos += buf_size;
return buf_size;
}
static int64_t _seek_data(void *opaque, int64_t offset, int whence) {
AudioCapturePrivate *l = reinterpret_cast<AudioCapturePrivate*>(opaque);
int32 newPos = -1;
switch (whence) {
case SEEK_SET: newPos = offset; break;
case SEEK_CUR: newPos = l->dataPos + offset; break;
case SEEK_END: newPos = l->data.size() + offset; break;
}
if (newPos < 0) {
return -1;
}
l->dataPos = newPos;
return l->dataPos;
}
};
AudioCaptureInner::AudioCaptureInner(QThread *thread) : d(new AudioCapturePrivate()) {
moveToThread(thread);
_timer.moveToThread(thread);
connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
}
AudioCaptureInner::~AudioCaptureInner() {
onStop(false);
delete d;
}
void AudioCaptureInner::onInit() {
}
void AudioCaptureInner::onStart() {
2015-05-29 18:52:43 +00:00
// Start OpenAL Capture
2015-06-01 11:15:07 +00:00
const ALCchar *dName = alcGetString(0, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
DEBUG_LOG(("Audio Info: Capture device name '%1'").arg(dName));
d->device = alcCaptureOpenDevice(dName, AudioVoiceMsgFrequency, AL_FORMAT_MONO16, AudioVoiceMsgFrequency / 5);
2015-05-29 18:52:43 +00:00
if (!d->device) {
LOG(("Audio Error: capture device not present!"));
emit error();
return;
}
alcCaptureStart(d->device);
if (!_checkCaptureError(d->device)) {
alcCaptureCloseDevice(d->device);
d->device = 0;
emit error();
return;
}
// Create encoding context
d->ioBuffer = (uchar*)av_malloc(AVBlockSize);
2015-05-29 18:52:43 +00:00
d->ioContext = avio_alloc_context(d->ioBuffer, AVBlockSize, 1, static_cast<void*>(d), &AudioCapturePrivate::_read_data, &AudioCapturePrivate::_write_data, &AudioCapturePrivate::_seek_data);
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
AVOutputFormat *fmt = 0;
while ((fmt = av_oformat_next(fmt))) {
if (fmt->name == qstr("opus")) {
2015-05-29 18:52:43 +00:00
break;
}
}
if (!fmt) {
LOG(("Audio Error: Unable to find opus AVOutputFormat for capture"));
onStop(false);
emit error();
return;
}
if ((res = avformat_alloc_output_context2(&d->fmtContext, fmt, 0, 0)) < 0) {
LOG(("Audio Error: Unable to avformat_alloc_output_context2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
d->fmtContext->pb = d->ioContext;
d->fmtContext->flags |= AVFMT_FLAG_CUSTOM_IO;
d->opened = true;
// Add audio stream
d->codec = avcodec_find_encoder(fmt->audio_codec);
if (!d->codec) {
LOG(("Audio Error: Unable to avcodec_find_encoder for capture"));
onStop(false);
emit error();
return;
}
2015-06-01 11:15:07 +00:00
d->stream = avformat_new_stream(d->fmtContext, d->codec);
2015-05-29 18:52:43 +00:00
if (!d->stream) {
LOG(("Audio Error: Unable to avformat_new_stream for capture"));
onStop(false);
emit error();
return;
}
d->stream->id = d->fmtContext->nb_streams - 1;
d->codecContext = d->stream->codec;
av_opt_set_int(d->codecContext, "refcounted_frames", 1, 0);
d->codecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
2015-06-01 11:15:07 +00:00
d->codecContext->bit_rate = 64000;
d->codecContext->channel_layout = AV_CH_LAYOUT_MONO;
2015-05-29 18:52:43 +00:00
d->codecContext->sample_rate = AudioVoiceMsgFrequency;
d->codecContext->channels = 1;
if (d->fmtContext->oformat->flags & AVFMT_GLOBALHEADER) {
d->codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
// Open audio stream
if ((res = avcodec_open2(d->codecContext, d->codec, nullptr)) < 0) {
2015-05-29 18:52:43 +00:00
LOG(("Audio Error: Unable to avcodec_open2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
// Alloc source samples
d->srcSamples = (d->codecContext->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE) ? 10000 : d->codecContext->frame_size;
//if ((res = av_samples_alloc_array_and_samples(&d->srcSamplesData, 0, d->codecContext->channels, d->srcSamples, d->codecContext->sample_fmt, 0)) < 0) {
// LOG(("Audio Error: Unable to av_samples_alloc_array_and_samples for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
// onStop(false);
// emit error();
// return;
//}
// Using _captured directly
// Prepare resampling
d->swrContext = swr_alloc();
if (!d->swrContext) {
fprintf(stderr, "Could not allocate resampler context\n");
exit(1);
}
av_opt_set_int(d->swrContext, "in_channel_count", d->codecContext->channels, 0);
av_opt_set_int(d->swrContext, "in_sample_rate", d->codecContext->sample_rate, 0);
av_opt_set_sample_fmt(d->swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(d->swrContext, "out_channel_count", d->codecContext->channels, 0);
av_opt_set_int(d->swrContext, "out_sample_rate", d->codecContext->sample_rate, 0);
av_opt_set_sample_fmt(d->swrContext, "out_sample_fmt", d->codecContext->sample_fmt, 0);
if ((res = swr_init(d->swrContext)) < 0) {
LOG(("Audio Error: Unable to swr_init for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
d->maxDstSamples = d->srcSamples;
if ((res = av_samples_alloc_array_and_samples(&d->dstSamplesData, 0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0)) < 0) {
LOG(("Audio Error: Unable to av_samples_alloc_array_and_samples for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0);
// Write file header
if ((res = avformat_write_header(d->fmtContext, 0)) < 0) {
LOG(("Audio Error: Unable to avformat_write_header for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
_timer.start(50);
_captured.clear();
_captured.reserve(AudioVoiceMsgBufferSize);
DEBUG_LOG(("Audio Capture: started!"));
}
void AudioCaptureInner::onStop(bool needResult) {
if (!_timer.isActive()) return; // in onStop() already
_timer.stop();
2015-06-01 11:15:07 +00:00
if (d->device) {
alcCaptureStop(d->device);
onTimeout(); // get last data
}
2015-05-29 18:52:43 +00:00
// Write what is left
if (!_captured.isEmpty()) {
int32 fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000, capturedSamples = _captured.size() / sizeof(short);
if ((_captured.size() % sizeof(short)) || (d->fullSamples + capturedSamples < AudioVoiceMsgFrequency) || (capturedSamples < fadeSamples)) {
d->fullSamples = 0;
d->dataPos = 0;
d->data.clear();
d->waveformMod = 0;
d->waveformPeak = 0;
d->waveform.clear();
2015-05-29 18:52:43 +00:00
} else {
float64 coef = 1. / fadeSamples, fadedFrom = 0;
for (short *ptr = ((short*)_captured.data()) + capturedSamples, *end = ptr - fadeSamples; ptr != end; ++fadedFrom) {
--ptr;
*ptr = qRound(fadedFrom * coef * *ptr);
}
if (capturedSamples % d->srcSamples) {
int32 s = _captured.size();
_captured.resize(s + (d->srcSamples - (capturedSamples % d->srcSamples)) * sizeof(short));
memset(_captured.data() + s, 0, _captured.size() - s);
}
int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0;
while (_captured.size() >= encoded + framesize) {
writeFrame(encoded, framesize);
encoded += framesize;
}
if (encoded != _captured.size()) {
d->fullSamples = 0;
d->dataPos = 0;
d->data.clear();
d->waveformMod = 0;
d->waveformPeak = 0;
d->waveform.clear();
2015-05-29 18:52:43 +00:00
}
}
}
DEBUG_LOG(("Audio Capture: stopping (need result: %1), size: %2, samples: %3").arg(Logs::b(needResult)).arg(d->data.size()).arg(d->fullSamples));
2015-05-29 18:52:43 +00:00
_captured = QByteArray();
// Finish stream
if (d->device) {
av_write_trailer(d->fmtContext);
}
QByteArray result = d->fullSamples ? d->data : QByteArray();
VoiceWaveform waveform;
2015-05-29 18:52:43 +00:00
qint32 samples = d->fullSamples;
if (samples && !d->waveform.isEmpty()) {
int64 count = d->waveform.size(), sum = 0;
if (count >= WaveformSamplesCount) {
QVector<uint16> peaks;
peaks.reserve(WaveformSamplesCount);
uint16 peak = 0;
for (int32 i = 0; i < count; ++i) {
uint16 sample = uint16(d->waveform.at(i)) * 256;
if (peak < sample) {
peak = sample;
}
sum += WaveformSamplesCount;
if (sum >= count) {
sum -= count;
peaks.push_back(peak);
peak = 0;
}
}
int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
waveform.resize(peaks.size());
for (int32 i = 0, l = peaks.size(); i != l; ++i) {
waveform[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
}
}
}
2015-05-29 18:52:43 +00:00
if (d->device) {
alcCaptureStop(d->device);
alcCaptureCloseDevice(d->device);
d->device = nullptr;
2015-05-29 18:52:43 +00:00
if (d->codecContext) {
avcodec_close(d->codecContext);
d->codecContext = nullptr;
2015-05-29 18:52:43 +00:00
}
if (d->srcSamplesData) {
if (d->srcSamplesData[0]) {
av_freep(&d->srcSamplesData[0]);
}
av_freep(&d->srcSamplesData);
}
if (d->dstSamplesData) {
if (d->dstSamplesData[0]) {
av_freep(&d->dstSamplesData[0]);
}
av_freep(&d->dstSamplesData);
}
d->fullSamples = 0;
if (d->swrContext) {
swr_free(&d->swrContext);
d->swrContext = nullptr;
2015-05-29 18:52:43 +00:00
}
if (d->opened) {
avformat_close_input(&d->fmtContext);
d->opened = false;
}
if (d->ioContext) {
av_free(d->ioContext->buffer);
av_free(d->ioContext);
d->ioContext = nullptr;
d->ioBuffer = nullptr;
2015-05-29 18:52:43 +00:00
} else if (d->ioBuffer) {
av_free(d->ioBuffer);
d->ioBuffer = nullptr;
2015-05-29 18:52:43 +00:00
}
if (d->fmtContext) {
avformat_free_context(d->fmtContext);
d->fmtContext = nullptr;
2015-05-29 18:52:43 +00:00
}
d->fmt = nullptr;
d->stream = nullptr;
d->codec = nullptr;
2015-05-29 18:52:43 +00:00
d->lastUpdate = 0;
d->levelMax = 0;
2015-05-29 18:52:43 +00:00
d->dataPos = 0;
d->data.clear();
d->waveformMod = 0;
d->waveformPeak = 0;
d->waveform.clear();
2015-05-29 18:52:43 +00:00
}
if (needResult) emit done(result, waveform, samples);
2015-05-29 18:52:43 +00:00
}
void AudioCaptureInner::onTimeout() {
if (!d->device) {
_timer.stop();
return;
}
ALint samples;
alcGetIntegerv(d->device, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples);
if (!_checkCaptureError(d->device)) {
onStop(false);
emit error();
return;
}
if (samples > 0) {
// Get samples from OpenAL
int32 s = _captured.size(), news = s + samples * sizeof(short);
if (news / AudioVoiceMsgBufferSize > s / AudioVoiceMsgBufferSize) {
_captured.reserve(((news / AudioVoiceMsgBufferSize) + 1) * AudioVoiceMsgBufferSize);
}
_captured.resize(news);
alcCaptureSamples(d->device, (ALCvoid *)(_captured.data() + s), samples);
if (!_checkCaptureError(d->device)) {
onStop(false);
emit error();
return;
}
// Count new recording level and update view
int32 skipSamples = AudioVoiceMsgSkip * AudioVoiceMsgFrequency / 1000, fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000;
int32 levelindex = d->fullSamples + (s / sizeof(short));
for (const short *ptr = (const short*)(_captured.constData() + s), *end = (const short*)(_captured.constData() + news); ptr < end; ++ptr, ++levelindex) {
if (levelindex > skipSamples) {
uint16 value = qAbs(*ptr);
2015-05-29 18:52:43 +00:00
if (levelindex < skipSamples + fadeSamples) {
value = qRound(value * float64(levelindex - skipSamples) / fadeSamples);
}
if (d->levelMax < value) {
d->levelMax = value;
2015-05-29 18:52:43 +00:00
}
}
}
qint32 samplesFull = d->fullSamples + _captured.size() / sizeof(short), samplesSinceUpdate = samplesFull - d->lastUpdate;
if (samplesSinceUpdate > AudioVoiceMsgUpdateView * AudioVoiceMsgFrequency / 1000) {
emit update(d->levelMax, samplesFull);
2015-05-29 18:52:43 +00:00
d->lastUpdate = samplesFull;
d->levelMax = 0;
2015-05-29 18:52:43 +00:00
}
// Write frames
int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0;
while (uint32(_captured.size()) >= encoded + framesize + fadeSamples * sizeof(short)) {
2015-05-29 18:52:43 +00:00
writeFrame(encoded, framesize);
encoded += framesize;
}
// Collapse the buffer
if (encoded > 0) {
int32 goodSize = _captured.size() - encoded;
memmove(_captured.data(), _captured.constData() + encoded, goodSize);
_captured.resize(goodSize);
}
} else {
DEBUG_LOG(("Audio Capture: no samples to capture."));
}
}
void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) {
// Prepare audio frame
if (framesize % sizeof(short)) { // in the middle of a sample
LOG(("Audio Error: Bad framesize in writeFrame() for capture, framesize %1, %2").arg(framesize));
onStop(false);
emit error();
return;
}
int32 samplesCnt = framesize / sizeof(short);
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
short *srcSamplesDataChannel = (short*)(_captured.data() + offset), **srcSamplesData = &srcSamplesDataChannel;
// memcpy(d->srcSamplesData[0], _captured.constData() + offset, framesize);
int32 skipSamples = AudioVoiceMsgSkip * AudioVoiceMsgFrequency / 1000, fadeSamples = AudioVoiceMsgFade * AudioVoiceMsgFrequency / 1000;
if (d->fullSamples < skipSamples + fadeSamples) {
int32 fadedCnt = qMin(samplesCnt, skipSamples + fadeSamples - d->fullSamples);
float64 coef = 1. / fadeSamples, fadedFrom = d->fullSamples - skipSamples;
short *ptr = srcSamplesDataChannel, *zeroEnd = ptr + qMin(samplesCnt, qMax(0, skipSamples - d->fullSamples)), *end = ptr + fadedCnt;
2015-05-29 18:52:43 +00:00
for (; ptr != zeroEnd; ++ptr, ++fadedFrom) {
*ptr = 0;
}
for (; ptr != end; ++ptr, ++fadedFrom) {
*ptr = qRound(fadedFrom * coef * *ptr);
}
}
d->waveform.reserve(d->waveform.size() + (samplesCnt / d->waveformEach) + 1);
for (short *ptr = srcSamplesDataChannel, *end = ptr + samplesCnt; ptr != end; ++ptr) {
uint16 value = qAbs(*ptr);
if (d->waveformPeak < value) {
d->waveformPeak = value;
}
if (++d->waveformMod == d->waveformEach) {
d->waveformMod -= d->waveformEach;
d->waveform.push_back(uchar(d->waveformPeak / 256));
d->waveformPeak = 0;
}
}
2015-05-29 18:52:43 +00:00
// Convert to final format
d->dstSamples = av_rescale_rnd(swr_get_delay(d->swrContext, d->codecContext->sample_rate) + d->srcSamples, d->codecContext->sample_rate, d->codecContext->sample_rate, AV_ROUND_UP);
if (d->dstSamples > d->maxDstSamples) {
d->maxDstSamples = d->dstSamples;
av_free(d->dstSamplesData[0]);
if ((res = av_samples_alloc(d->dstSamplesData, 0, d->codecContext->channels, d->dstSamples, d->codecContext->sample_fmt, 0)) < 0) {
LOG(("Audio Error: Unable to av_samples_alloc for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0);
}
if ((res = swr_convert(d->swrContext, d->dstSamplesData, d->dstSamples, (const uint8_t **)srcSamplesData, d->srcSamples)) < 0) {
LOG(("Audio Error: Unable to swr_convert for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
// Write audio frame
AVPacket pkt;
memset(&pkt, 0, sizeof(pkt)); // data and size must be 0;
AVFrame *frame = av_frame_alloc();
2015-05-29 18:52:43 +00:00
int gotPacket;
av_init_packet(&pkt);
frame->nb_samples = d->dstSamples;
avcodec_fill_audio_frame(frame, d->codecContext->channels, d->codecContext->sample_fmt, d->dstSamplesData[0], d->dstSamplesSize, 0);
if ((res = avcodec_encode_audio2(d->codecContext, &pkt, frame, &gotPacket)) < 0) {
LOG(("Audio Error: Unable to avcodec_encode_audio2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
if (gotPacket) {
pkt.stream_index = d->stream->index;
if ((res = av_interleaved_write_frame(d->fmtContext, &pkt)) < 0) {
LOG(("Audio Error: Unable to av_interleaved_write_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
onStop(false);
emit error();
return;
}
}
d->fullSamples += samplesCnt;
av_frame_free(&frame);
2015-06-01 11:15:07 +00:00
}
class FFMpegAttributesReader : public AbstractFFMpegLoader {
public:
FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) {
}
bool open(qint64 position = 0) override {
2016-02-15 07:09:14 +00:00
if (!AbstractFFMpegLoader::open()) {
return false;
}
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
int videoStreamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
if (videoStreamId >= 0) {
DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(videoStreamId).arg(av_make_error_string(err, sizeof(err), streamId)));
return false;
}
for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) {
AVStream *stream = fmtContext->streams[i];
if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
const AVPacket &packet(stream->attached_pic);
if (packet.size) {
bool animated = false;
QByteArray cover((const char*)packet.data, packet.size), format;
_cover = App::readImage(cover, &format, true, &animated);
if (!_cover.isNull()) {
_coverBytes = cover;
_coverFormat = format;
break;
}
}
}
}
extractMetaData(fmtContext->streams[streamId]->metadata);
extractMetaData(fmtContext->metadata);
return true;
}
void trySet(QString &to, AVDictionary *dict, const char *key) {
if (!to.isEmpty()) return;
if (AVDictionaryEntry* tag = av_dict_get(dict, key, 0, 0)) {
to = QString::fromUtf8(tag->value);
}
}
void extractMetaData(AVDictionary *dict) {
trySet(_title, dict, "title");
trySet(_performer, dict, "artist");
trySet(_performer, dict, "performer");
trySet(_performer, dict, "album_artist");
//for (AVDictionaryEntry *tag = av_dict_get(dict, "", 0, AV_DICT_IGNORE_SUFFIX); tag; tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX)) {
// const char *key = tag->key;
// const char *value = tag->value;
// QString tmp = QString::fromUtf8(value);
//}
}
int32 format() override {
return 0;
}
QString title() {
return _title;
}
QString performer() {
return _performer;
}
QImage cover() {
return _cover;
}
QByteArray coverBytes() {
return _coverBytes;
}
QByteArray coverFormat() {
return _coverFormat;
}
ReadResult readMore(QByteArray &result, int64 &samplesAdded) override {
DEBUG_LOG(("Audio Read Error: should not call this"));
return ReadResult::Error;
}
~FFMpegAttributesReader() {
}
private:
QString _title, _performer;
QImage _cover;
QByteArray _coverBytes, _coverFormat;
};
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) {
FFMpegAttributesReader reader(FileLocation(StorageFilePartial, fname), data);
if (reader.open()) {
int32 duration = reader.duration() / reader.frequency();
if (reader.duration() > 0) {
cover = reader.cover();
coverBytes = reader.coverBytes();
coverFormat = reader.coverFormat();
return MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring());
}
}
return MTP_documentAttributeFilename(MTP_string(fname));
}
class FFMpegWaveformCounter : public FFMpegLoader {
public:
FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) {
}
bool open(qint64 position = 0) override {
if (!FFMpegLoader::open(position)) {
return false;
}
QByteArray buffer;
buffer.reserve(AudioVoiceMsgBufferSize);
int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
if (duration() < WaveformSamplesCount) {
return false;
}
QVector<uint16> peaks;
peaks.reserve(WaveformSamplesCount);
int32 fmt = format();
uint16 peak = 0;
while (processed < countbytes) {
buffer.resize(0);
int64 samples = 0;
auto res = readMore(buffer, samples);
if (res == ReadResult::Error) {
break;
}
if (buffer.isEmpty()) {
continue;
}
const char *data = buffer.data();
if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) {
2016-02-14 19:46:01 +00:00
for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uchar)) <= l;) {
uint16 sample = qAbs((int32(*(uchar*)(data + i)) - 128) * 256);
if (peak < sample) {
peak = sample;
}
i += sizeof(uchar);
sumbytes += WaveformSamplesCount;
if (sumbytes >= countbytes) {
sumbytes -= countbytes;
peaks.push_back(peak);
peak = 0;
}
}
} else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) {
2016-02-14 19:46:01 +00:00
for (int32 i = 0, l = buffer.size(); i + int32(sizeof(uint16)) <= l;) {
uint16 sample = qAbs(int32(*(int16*)(data + i)));
if (peak < sample) {
peak = sample;
}
i += sizeof(uint16);
sumbytes += sizeof(uint16) * WaveformSamplesCount;
if (sumbytes >= countbytes) {
sumbytes -= countbytes;
peaks.push_back(peak);
peak = 0;
}
}
}
processed += sampleSize * samples;
}
if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) {
peaks.push_back(peak);
}
if (peaks.isEmpty()) {
return false;
}
int64 sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0ULL);
peak = qMax(int32(sum * 1.8 / peaks.size()), 2500);
result.resize(peaks.size());
for (int32 i = 0, l = peaks.size(); i != l; ++i) {
result[i] = char(qMin(31U, uint32(qMin(peaks.at(i), peak)) * 31 / peak));
}
return true;
}
const VoiceWaveform &waveform() const {
return result;
}
~FFMpegWaveformCounter() {
}
private:
VoiceWaveform result;
};
VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data) {
FFMpegWaveformCounter counter(file, data);
if (counter.open()) {
return counter.waveform();
}
return VoiceWaveform();
}