AyuGramDesktop/Telegram/SourceFiles/media/media_audio.cpp
John Preston 08167a6a91 Removed #include "stdafx.h" from all files.
Currently the build without implicitly included precompiled header
is not supported anyway (because Qt MOC source files do not include
stdafx.h, they include plain headers).

So when we decide to support building without implicitly included
precompiled headers we'll have to fix all the headers anyway.
2017-03-04 12:27:52 +03:00

1781 lines
48 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#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"
#include "platform/platform_audio.h"
#include <AL/al.h>
#include <AL/alc.h>
#define AL_ALEXT_PROTOTYPES
#include <AL/alext.h>
#include <numeric>
Q_DECLARE_METATYPE(AudioMsgId);
Q_DECLARE_METATYPE(VoiceWaveform);
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"
namespace {
QMutex AudioMutex;
ALCdevice *AudioDevice = nullptr;
ALCcontext *AudioContext = nullptr;
auto suppressAllGain = 1.;
auto suppressSongGain = 1.;
} // namespace
namespace Media {
namespace Player {
namespace {
constexpr auto kPreloadSamples = 2LL * 48000; // preload next part if less than 2 seconds remains
constexpr auto kFadeDuration = TimeMs(500);
constexpr auto kCheckPlaybackPositionTimeout = TimeMs(100); // 100ms per check audio position
constexpr auto kCheckPlaybackPositionDelta = 2400LL; // update position called each 2400 samples
constexpr auto kCheckFadingTimeout = TimeMs(7); // 7ms
constexpr auto kDetachDeviceTimeout = TimeMs(500); // destroy the audio device after 500ms of silence
struct NotifySound {
QByteArray data;
TimeMs lengthMs = 0;
int sampleRate = 0;
ALenum alFormat = 0;
ALuint source = 0;
ALuint buffer = 0;
};
NotifySound DefaultNotify;
void PrepareNotifySound() {
auto content = ([] {
QFile soundFile(":/gui/art/newmsg.wav");
soundFile.open(QIODevice::ReadOnly);
return soundFile.readAll();
})();
auto data = content.constData();
auto size = content.size();
t_assert(size >= 44);
t_assert(*((const uint32*)(data + 0)) == 0x46464952); // ChunkID - "RIFF"
t_assert(*((const uint32*)(data + 4)) == uint32(size - 8)); // ChunkSize
t_assert(*((const uint32*)(data + 8)) == 0x45564157); // Format - "WAVE"
t_assert(*((const uint32*)(data + 12)) == 0x20746d66); // Subchunk1ID - "fmt "
auto subchunk1Size = *((const uint32*)(data + 16));
auto extra = subchunk1Size - 16;
t_assert(subchunk1Size >= 16 && (!extra || extra >= 2));
t_assert(*((const uint16*)(data + 20)) == 1); // AudioFormat - PCM (1)
auto numChannels = *((const uint16*)(data + 22));
t_assert(numChannels == 1 || numChannels == 2);
auto sampleRate = *((const uint32*)(data + 24));
auto byteRate = *((const uint32*)(data + 28));
auto blockAlign = *((const uint16*)(data + 32));
auto bitsPerSample = *((const uint16*)(data + 34));
t_assert(!(bitsPerSample % 8));
auto bytesPerSample = bitsPerSample / 8;
t_assert(bytesPerSample == 1 || bytesPerSample == 2);
t_assert(blockAlign == numChannels * bytesPerSample);
t_assert(byteRate == sampleRate * blockAlign);
if (extra) {
auto extraSize = *((const uint16*)(data + 36));
t_assert(uint32(extraSize + 2) == extra);
t_assert(uint32(size) >= 44 + extra);
}
t_assert(*((const uint32*)(data + extra + 36)) == 0x61746164); // Subchunk2ID - "data"
auto subchunk2Size = *((const uint32*)(data + extra + 40));
t_assert(!(subchunk2Size % (numChannels * bytesPerSample)));
auto numSamples = subchunk2Size / (numChannels * bytesPerSample);
t_assert(uint32(size) >= 44 + extra + subchunk2Size);
data += 44 + extra;
auto format = ALenum(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;
}
t_assert(format != 0);
DefaultNotify.alFormat = format;
DefaultNotify.sampleRate = sampleRate;
auto addBytes = (sampleRate * 15 / 100) * bytesPerSample * numChannels; // add 150ms of silence
DefaultNotify.data = QByteArray(addBytes + subchunk2Size, (bytesPerSample == 1) ? 128 : 0);
memcpy(DefaultNotify.data.data() + addBytes, data, subchunk2Size);
DefaultNotify.lengthMs = (numSamples * 1000LL / sampleRate);
}
base::Observable<AudioMsgId> UpdatedObservable;
Mixer *MixerInstance = nullptr;
bool ContextErrorHappened() {
ALenum errCode;
if ((errCode = alcGetError(AudioDevice)) != ALC_NO_ERROR) {
LOG(("Audio Context Error: %1, %2").arg(errCode).arg((const char *)alcGetString(AudioDevice, errCode)));
return true;
}
return false;
}
bool PlaybackErrorHappened() {
ALenum errCode;
if ((errCode = alGetError()) != AL_NO_ERROR) {
LOG(("Audio Playback Error: %1, %2").arg(errCode).arg((const char *)alGetString(errCode)));
return true;
}
return false;
}
void EnumeratePlaybackDevices() {
auto deviceNames = QStringList();
auto devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
t_assert(devices != nullptr);
while (*devices != 0) {
auto deviceName8Bit = QByteArray(devices);
auto deviceName = QString::fromLocal8Bit(deviceName8Bit);
deviceNames.append(deviceName);
devices += deviceName8Bit.size() + 1;
}
LOG(("Audio Playback Devices: %1").arg(deviceNames.join(';')));
if (auto device = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER)) {
LOG(("Audio Playback Default Device: %1").arg(QString::fromLocal8Bit(device)));
} else {
LOG(("Audio Playback Default Device: (null)"));
}
}
void EnumerateCaptureDevices() {
auto deviceNames = QStringList();
auto devices = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
t_assert(devices != nullptr);
while (*devices != 0) {
auto deviceName8Bit = QByteArray(devices);
auto deviceName = QString::fromLocal8Bit(deviceName8Bit);
deviceNames.append(deviceName);
devices += deviceName8Bit.size() + 1;
}
LOG(("Audio Capture Devices: %1").arg(deviceNames.join(';')));
if (auto device = alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)) {
LOG(("Audio Capture Default Device: %1").arg(QString::fromLocal8Bit(device)));
} else {
LOG(("Audio Capture Default Device: (null)"));
}
}
ALuint CreateSource() {
auto source = ALuint(0);
alGenSources(1, &source);
alSourcef(source, AL_PITCH, 1.f);
alSourcef(source, AL_GAIN, 1.f);
alSource3f(source, AL_POSITION, 0, 0, 0);
alSource3f(source, AL_VELOCITY, 0, 0, 0);
alSourcei(source, AL_LOOPING, 0);
return source;
}
ALuint CreateBuffer() {
auto buffer = ALuint(0);
alGenBuffers(1, &buffer);
return buffer;
}
void CreateDefaultNotify() {
if (alIsSource(DefaultNotify.source)) {
return;
}
DefaultNotify.source = CreateSource();
DefaultNotify.buffer = CreateBuffer();
alBufferData(DefaultNotify.buffer, DefaultNotify.alFormat, DefaultNotify.data.constData(), DefaultNotify.data.size(), DefaultNotify.sampleRate);
alSourcei(DefaultNotify.source, AL_BUFFER, DefaultNotify.buffer);
}
// can be called at any moment when audio error
void CloseAudioPlaybackDevice() {
if (!AudioDevice) return;
LOG(("Audio Info: closing audio playback device"));
if (alIsSource(DefaultNotify.source)) {
alSourceStop(DefaultNotify.source);
alSourcei(DefaultNotify.source, AL_BUFFER, AL_NONE);
alDeleteBuffers(1, &DefaultNotify.buffer);
alDeleteSources(1, &DefaultNotify.source);
}
DefaultNotify.buffer = 0;
DefaultNotify.source = 0;
if (mixer()) {
mixer()->detachTracks();
}
if (AudioContext) {
alcMakeContextCurrent(nullptr);
alcDestroyContext(AudioContext);
AudioContext = nullptr;
}
if (AudioDevice) {
alcCloseDevice(AudioDevice);
AudioDevice = nullptr;
}
}
} // namespace
void InitAudio() {
t_assert(AudioDevice == nullptr);
qRegisterMetaType<AudioMsgId>();
qRegisterMetaType<VoiceWaveform>();
PrepareNotifySound();
auto loglevel = getenv("ALSOFT_LOGLEVEL");
LOG(("OpenAL Logging Level: %1").arg(loglevel ? loglevel : "(not set)"));
EnumeratePlaybackDevices();
EnumerateCaptureDevices();
MixerInstance = new Mixer();
Platform::Audio::Init();
}
void DeInitAudio() {
Platform::Audio::DeInit();
delete base::take(MixerInstance);
CloseAudioPlaybackDevice();
}
base::Observable<AudioMsgId> &Updated() {
return UpdatedObservable;
}
bool CreateAudioPlaybackDevice() {
if (AudioDevice) return true;
AudioDevice = alcOpenDevice(nullptr);
if (!AudioDevice) {
LOG(("Audio Error: Could not create default playback device, enumerating.."));
EnumeratePlaybackDevices();
return false;
}
ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 };
AudioContext = alcCreateContext(AudioDevice, attributes);
alcMakeContextCurrent(AudioContext);
if (ContextErrorHappened()) {
CloseAudioPlaybackDevice();
return false;
}
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);
return true;
}
void DetachFromDeviceByTimer() {
QMutexLocker lock(&AudioMutex);
if (mixer()) {
mixer()->detachFromDeviceByTimer();
}
}
void DetachFromDevice() {
QMutexLocker lock(&AudioMutex);
CloseAudioPlaybackDevice();
if (mixer()) {
mixer()->reattachIfNeeded();
}
}
void PlayNotify() {
QMutexLocker lock(&AudioMutex);
if (!mixer()) return;
mixer()->reattachTracks();
if (!AudioDevice) return;
CreateDefaultNotify();
alSourcePlay(DefaultNotify.source);
if (PlaybackErrorHappened()) {
CloseAudioPlaybackDevice();
return;
}
emit mixer()->suppressAll();
emit mixer()->faderOnTimer();
}
bool NotifyIsPlaying() {
if (alIsSource(DefaultNotify.source)) {
ALint state = AL_INITIAL;
alGetSourcei(DefaultNotify.source, AL_SOURCE_STATE, &state);
if (!PlaybackErrorHappened() && state == AL_PLAYING) {
return true;
}
}
return false;
}
float64 ComputeVolume(AudioMsgId::Type type) {
switch (type) {
case AudioMsgId::Type::Voice: return suppressAllGain;
case AudioMsgId::Type::Song: return suppressSongGain * Global::SongVolume();
case AudioMsgId::Type::Video: return suppressSongGain * Global::VideoVolume();
}
return 1.;
}
Mixer *mixer() {
return MixerInstance;
}
void Mixer::Track::createStream() {
alGenSources(1, &stream.source);
alSourcef(stream.source, AL_PITCH, 1.f);
alSource3f(stream.source, AL_POSITION, 0, 0, 0);
alSource3f(stream.source, AL_VELOCITY, 0, 0, 0);
alSourcei(stream.source, AL_LOOPING, 0);
alGenBuffers(3, stream.buffers);
}
void Mixer::Track::destroyStream() {
if (isStreamCreated()) {
alDeleteBuffers(3, stream.buffers);
alDeleteSources(1, &stream.source);
}
stream.source = 0;
for (auto i = 0; i != 3; ++i) {
stream.buffers[i] = 0;
}
}
void Mixer::Track::reattach(AudioMsgId::Type type) {
if (isStreamCreated() || !samplesCount[0]) {
return;
}
createStream();
for (auto i = 0; i != kBuffersCount; ++i) {
if (!samplesCount[i]) {
break;
}
alBufferData(stream.buffers[i], format, bufferSamples[i].constData(), bufferSamples[i].size(), frequency);
alSourceQueueBuffers(stream.source, 1, stream.buffers + i);
}
alSourcei(stream.source, AL_SAMPLE_OFFSET, qMax(state.position - bufferedPosition, 0LL));
if (IsActive(state.state)) {
alSourcef(stream.source, AL_GAIN, ComputeVolume(type));
alSourcePlay(stream.source);
}
}
void Mixer::Track::detach() {
resetStream();
destroyStream();
}
void Mixer::Track::clear() {
detach();
state = TrackState();
file = FileLocation();
data = QByteArray();
bufferedPosition = 0;
bufferedLength = 0;
loading = false;
loaded = false;
fadeStartPosition = 0;
format = 0;
frequency = kDefaultFrequency;
for (int i = 0; i != kBuffersCount; ++i) {
samplesCount[i] = 0;
bufferSamples[i] = QByteArray();
}
videoData = nullptr;
videoPlayId = 0;
}
void Mixer::Track::started() {
resetStream();
bufferedPosition = 0;
bufferedLength = 0;
loaded = false;
fadeStartPosition = 0;
format = 0;
frequency = kDefaultFrequency;
for (auto i = 0; i != kBuffersCount; ++i) {
samplesCount[i] = 0;
bufferSamples[i] = QByteArray();
}
}
bool Mixer::Track::isStreamCreated() const {
return alIsSource(stream.source);
}
void Mixer::Track::ensureStreamCreated() {
if (!isStreamCreated()) {
createStream();
}
}
int Mixer::Track::getNotQueuedBufferIndex() {
// See if there are no free buffers right now.
while (samplesCount[kBuffersCount - 1] != 0) {
// Try to unqueue some buffer.
ALint processed = 0;
alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed);
if (processed < 1) { // No processed buffers, wait.
return -1;
}
// Unqueue some processed buffer.
ALuint buffer = 0;
alSourceUnqueueBuffers(stream.source, 1, &buffer);
// Find it in the list and clear it.
bool found = false;
for (auto i = 0; i != kBuffersCount; ++i) {
if (stream.buffers[i] == buffer) {
auto samplesInBuffer = samplesCount[i];
bufferedPosition += samplesInBuffer;
bufferedLength -= samplesInBuffer;
for (auto j = i + 1; j != kBuffersCount; ++j) {
samplesCount[j - 1] = samplesCount[j];
stream.buffers[j - 1] = stream.buffers[j];
bufferSamples[j - 1] = bufferSamples[j];
}
samplesCount[kBuffersCount - 1] = 0;
stream.buffers[kBuffersCount - 1] = buffer;
bufferSamples[kBuffersCount - 1] = QByteArray();
found = true;
break;
}
}
if (!found) {
LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(stream.source).arg(processed));
return -1;
}
}
for (auto i = 0; i != kBuffersCount; ++i) {
if (!samplesCount[i]) {
return i;
}
}
return -1;
}
void Mixer::Track::resetStream() {
if (isStreamCreated()) {
alSourceStop(stream.source);
alSourcei(stream.source, AL_BUFFER, AL_NONE);
}
}
Mixer::Mixer()
: _fader(new Fader(&_faderThread))
, _loader(new Loaders(&_loaderThread)) {
connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer()), Qt::QueuedConnection);
connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong()));
connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong()));
connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll()));
subscribe(Global::RefSongVolumeChanged(), [this] {
QMetaObject::invokeMethod(_fader, "onSongVolumeChanged");
});
subscribe(Global::RefVideoVolumeChanged(), [this] {
QMetaObject::invokeMethod(_fader, "onVideoVolumeChanged");
});
connect(this, SIGNAL(loaderOnStart(const AudioMsgId&, qint64)), _loader, SLOT(onStart(const AudioMsgId&, qint64)));
connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&)));
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);
connect(this, SIGNAL(updated(const AudioMsgId&)), this, SLOT(onUpdated(const AudioMsgId&)));
_loaderThread.start();
_faderThread.start();
}
Mixer::~Mixer() {
{
QMutexLocker lock(&AudioMutex);
for (auto i = 0; i != kTogetherLimit; ++i) {
trackForType(AudioMsgId::Type::Voice, i)->clear();
trackForType(AudioMsgId::Type::Song, i)->clear();
}
_videoTrack.clear();
CloseAudioPlaybackDevice();
MixerInstance = nullptr;
}
_faderThread.quit();
_loaderThread.quit();
_faderThread.wait();
_loaderThread.wait();
}
void Mixer::onUpdated(const AudioMsgId &audio) {
if (audio.type() == AudioMsgId::Type::Video) {
videoSoundProgress(audio);
}
Media::Player::Updated().notify(audio);
}
void Mixer::onError(const AudioMsgId &audio) {
emit stoppedOnError(audio);
if (audio.type() == AudioMsgId::Type::Voice) {
emit unsuppressSong();
}
}
void Mixer::onStopped(const AudioMsgId &audio) {
emit updated(audio);
if (audio.type() == AudioMsgId::Type::Voice) {
emit unsuppressSong();
}
}
Mixer::Track *Mixer::trackForType(AudioMsgId::Type type, int index) {
if (index < 0) {
if (auto indexPtr = currentIndex(type)) {
index = *indexPtr;
} else {
return nullptr;
}
}
switch (type) {
case AudioMsgId::Type::Voice: return &_audioTracks[index];
case AudioMsgId::Type::Song: return &_songTracks[index];
case AudioMsgId::Type::Video: return &_videoTrack;
}
return nullptr;
}
const Mixer::Track *Mixer::trackForType(AudioMsgId::Type type, int index) const {
return const_cast<Mixer*>(this)->trackForType(type, index);
}
int *Mixer::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 *Mixer::currentIndex(AudioMsgId::Type type) const {
return const_cast<Mixer*>(this)->currentIndex(type);
}
void Mixer::resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered) {
auto track = trackForType(type);
if (!track) return;
if (positionInBuffered < 0) {
reattachTracks();
if (track->isStreamCreated()) {
ALint currentPosition = 0;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &currentPosition);
if (Media::Player::PlaybackErrorHappened()) {
setStoppedState(track, State::StoppedAtError);
onError(track->state.id);
return;
}
if (currentPosition == 0 && !internal::CheckAudioDeviceConnected()) {
track->fadeStartPosition = track->state.position;
return;
}
positionInBuffered = currentPosition;
} else {
positionInBuffered = 0;
}
}
auto fullPosition = track->bufferedPosition + positionInBuffered;
track->state.position = fullPosition;
track->fadeStartPosition = fullPosition;
}
bool Mixer::fadedStop(AudioMsgId::Type type, bool *fadedStart) {
auto current = trackForType(type);
if (!current) return false;
switch (current->state.state) {
case State::Starting:
case State::Resuming:
case State::Playing: {
current->state.state = State::Finishing;
resetFadeStartPosition(type);
if (fadedStart) *fadedStart = true;
} break;
case State::Pausing: {
current->state.state = State::Finishing;
if (fadedStart) *fadedStart = true;
} break;
case State::Paused:
case State::PausedAtEnd: {
setStoppedState(current);
} return true;
}
return false;
}
void Mixer::play(const AudioMsgId &audio, int64 position) {
auto type = audio.type();
AudioMsgId stopped;
auto notLoadedYet = false;
{
QMutexLocker lock(&AudioMutex);
reattachTracks();
if (!AudioDevice) return;
bool fadedStart = false;
auto current = trackForType(type);
if (!current) return;
if (current->state.id != audio) {
if (fadedStop(type, &fadedStart)) {
stopped = current->state.id;
}
if (current->state.id) {
emit loaderOnCancel(current->state.id);
emit faderOnTimer();
}
auto foundCurrent = currentIndex(type);
auto index = 0;
for (; index != kTogetherLimit; ++index) {
if (trackForType(type, index)->state.id == audio) {
*foundCurrent = index;
break;
}
}
if (index == kTogetherLimit && ++*foundCurrent >= kTogetherLimit) {
*foundCurrent -= kTogetherLimit;
}
current = trackForType(type);
}
current->state.id = audio;
current->file = audio.audio()->location(true);
current->data = audio.audio()->data();
if (current->file.isEmpty() && current->data.isEmpty()) {
notLoadedYet = true;
if (audio.type() == AudioMsgId::Type::Song) {
setStoppedState(current);
} else {
setStoppedState(current, State::StoppedAtError);
}
} else {
current->state.position = position;
current->state.state = fadedStart ? State::Starting : State::Playing;
current->loading = true;
emit loaderOnStart(audio, position);
if (type == AudioMsgId::Type::Voice) {
emit suppressSong();
}
}
}
if (notLoadedYet) {
if (audio.type() == AudioMsgId::Type::Song) {
DocumentOpenClickHandler::doOpen(audio.audio(), App::histItemById(audio.contextId()));
} else {
onError(audio);
}
}
if (stopped) {
emit updated(stopped);
}
}
void Mixer::initFromVideo(uint64 videoPlayId, std::unique_ptr<VideoSoundData> &&data, int64 position) {
AudioMsgId stopped;
{
QMutexLocker lock(&AudioMutex);
// Pause current song.
auto songType = AudioMsgId::Type::Song;
auto currentSong = trackForType(songType);
switch (currentSong->state.state) {
case State::Starting:
case State::Resuming:
case State::Playing: {
currentSong->state.state = State::Pausing;
resetFadeStartPosition(songType);
} break;
case State::Finishing: {
currentSong->state.state = State::Pausing;
} break;
}
auto type = AudioMsgId::Type::Video;
auto current = trackForType(type);
t_assert(current != nullptr);
if (current->state.id) {
fadedStop(type);
stopped = current->state.id;
emit loaderOnCancel(current->state.id);
}
emit faderOnTimer();
current->clear();
current->state.id = 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->state.state = State::Paused;
current->loading = true;
emit loaderOnStart(current->state.id, position);
}
if (stopped) emit updated(stopped);
}
void Mixer::stopFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto track = trackForType(AudioMsgId::Type::Video);
t_assert(track != nullptr);
if (track->videoPlayId != videoPlayId) {
return;
}
current = track->state.id;
fadedStop(AudioMsgId::Type::Video);
track->clear();
}
if (current) emit updated(current);
}
void Mixer::pauseFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto type = AudioMsgId::Type::Video;
auto track = trackForType(type);
t_assert(track != nullptr);
if (track->videoPlayId != videoPlayId) {
return;
}
current = track->state.id;
switch (track->state.state) {
case State::Starting:
case State::Resuming:
case State::Playing: {
track->state.state = State::Paused;
resetFadeStartPosition(type);
if (track->isStreamCreated()) {
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state == AL_PLAYING) {
alSourcePause(track->stream.source);
if (!checkCurrentALError(type)) return;
}
}
} break;
}
emit faderOnTimer();
QMutexLocker videoLock(&_lastVideoMutex);
if (_lastVideoPlayId == videoPlayId) {
_lastVideoPlaybackWhen = 0;
_lastVideoPlaybackCorrectedMs = 0;
}
}
if (current) emit updated(current);
}
void Mixer::resumeFromVideo(uint64 videoPlayId) {
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto type = AudioMsgId::Type::Video;
auto track = trackForType(type);
t_assert(track != nullptr);
if (track->videoPlayId != videoPlayId) {
return;
}
current = track->state.id;
switch (track->state.state) {
case State::Pausing:
case State::Paused:
case State::PausedAtEnd: {
reattachTracks();
if (track->state.state == State::Paused) {
// This calls reattachTracks().
resetFadeStartPosition(type);
} else {
reattachTracks();
if (track->state.state == State::PausedAtEnd) {
if (track->isStreamCreated()) {
alSourcei(track->stream.source, AL_SAMPLE_OFFSET, qMax(track->state.position - track->bufferedPosition, 0LL));
if (!checkCurrentALError(type)) return;
}
}
}
track->state.state = State::Playing;
if (track->isStreamCreated()) {
// When starting the video audio is in paused state and
// gets resumed before the stream is created with any data.
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state != AL_PLAYING) {
if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
return;
}
alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type));
if (!checkCurrentALError(type)) return;
alSourcePlay(track->stream.source);
if (!checkCurrentALError(type)) return;
}
}
} break;
}
emit faderOnTimer();
}
if (current) emit updated(current);
}
void Mixer::feedFromVideo(VideoSoundPart &&part) {
_loader->feedFromVideo(std::move(part));
}
TimeMs Mixer::getVideoCorrectedTime(uint64 playId, TimeMs frameMs, TimeMs systemMs) {
auto result = frameMs;
QMutexLocker videoLock(&_lastVideoMutex);
if (_lastVideoPlayId == playId && _lastVideoPlaybackWhen > 0) {
result = static_cast<TimeMs>(_lastVideoPlaybackCorrectedMs);
if (systemMs > _lastVideoPlaybackWhen) {
result += (systemMs - _lastVideoPlaybackWhen);
}
}
return result;
}
void Mixer::videoSoundProgress(const AudioMsgId &audio) {
auto type = audio.type();
t_assert(type == AudioMsgId::Type::Video);
QMutexLocker lock(&AudioMutex);
QMutexLocker videoLock(&_lastVideoMutex);
auto current = trackForType(type);
t_assert(current != nullptr);
if (current->videoPlayId == _lastVideoPlayId && current->state.duration && current->state.frequency) {
if (current->state.state == State::Playing) {
_lastVideoPlaybackWhen = getms();
_lastVideoPlaybackCorrectedMs = (current->state.position * 1000ULL) / current->state.frequency;
}
}
}
bool Mixer::checkCurrentALError(AudioMsgId::Type type) {
if (!Media::Player::PlaybackErrorHappened()) return true;
auto data = trackForType(type);
if (!data) {
setStoppedState(data, State::StoppedAtError);
onError(data->state.id);
}
return false;
}
void Mixer::pauseresume(AudioMsgId::Type type, bool fast) {
QMutexLocker lock(&AudioMutex);
auto current = trackForType(type);
switch (current->state.state) {
case State::Pausing:
case State::Paused:
case State::PausedAtEnd: {
reattachTracks();
if (current->state.state == State::Paused) {
resetFadeStartPosition(type);
} else if (current->state.state == State::PausedAtEnd) {
if (current->isStreamCreated()) {
alSourcei(current->stream.source, AL_SAMPLE_OFFSET, qMax(current->state.position - current->bufferedPosition, 0LL));
if (!checkCurrentALError(type)) return;
}
}
current->state.state = fast ? State::Playing : State::Resuming;
ALint state = AL_INITIAL;
alGetSourcei(current->stream.source, AL_SOURCE_STATE, &state);
if (!checkCurrentALError(type)) return;
if (state != AL_PLAYING) {
if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
return;
}
alSourcef(current->stream.source, AL_GAIN, ComputeVolume(type));
if (!checkCurrentALError(type)) return;
alSourcePlay(current->stream.source);
if (!checkCurrentALError(type)) return;
}
if (type == AudioMsgId::Type::Voice) emit suppressSong();
} break;
case State::Starting:
case State::Resuming:
case State::Playing: {
current->state.state = State::Pausing;
resetFadeStartPosition(type);
if (type == AudioMsgId::Type::Voice) emit unsuppressSong();
} break;
case State::Finishing: {
current->state.state = State::Pausing;
} break;
}
emit faderOnTimer();
}
void Mixer::seek(AudioMsgId::Type type, int64 position) {
QMutexLocker lock(&AudioMutex);
auto current = trackForType(type);
auto audio = current->state.id;
reattachTracks();
auto streamCreated = current->isStreamCreated();
auto fastSeek = (position >= current->bufferedPosition && position < current->bufferedPosition + current->bufferedLength - (current->loaded ? 0 : kDefaultFrequency));
if (!streamCreated) {
fastSeek = false;
} else if (IsStopped(current->state.state) || (current->state.state == State::Finishing)) {
fastSeek = false;
}
if (fastSeek) {
alSourcei(current->stream.source, AL_SAMPLE_OFFSET, position - current->bufferedPosition);
if (!checkCurrentALError(type)) return;
alSourcef(current->stream.source, AL_GAIN, ComputeVolume(type));
if (!checkCurrentALError(type)) return;
resetFadeStartPosition(type, position - current->bufferedPosition);
} else {
setStoppedState(current);
if (streamCreated) alSourceStop(current->stream.source);
}
switch (current->state.state) {
case State::Pausing:
case State::Paused:
case State::PausedAtEnd: {
if (current->state.state == State::PausedAtEnd) {
current->state.state = State::Paused;
}
lock.unlock();
return pauseresume(type, true);
} break;
case State::Starting:
case State::Resuming:
case State::Playing: {
current->state.state = State::Pausing;
resetFadeStartPosition(type);
if (type == AudioMsgId::Type::Voice) {
emit unsuppressSong();
}
} break;
case State::Finishing:
case State::Stopped:
case State::StoppedAtEnd:
case State::StoppedAtError:
case State::StoppedAtStart: {
lock.unlock();
} return play(audio, position);
}
emit faderOnTimer();
}
void Mixer::stop(AudioMsgId::Type type) {
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto track = trackForType(type);
t_assert(track != nullptr);
current = track->state.id;
fadedStop(type);
if (type == AudioMsgId::Type::Video) {
track->clear();
}
}
if (current) emit updated(current);
}
void Mixer::stopAndClear() {
Track *current_audio = nullptr, *current_song = nullptr;
{
QMutexLocker lock(&AudioMutex);
if ((current_audio = trackForType(AudioMsgId::Type::Voice))) {
setStoppedState(current_audio);
}
if ((current_song = trackForType(AudioMsgId::Type::Song))) {
setStoppedState(current_song);
}
}
if (current_song) {
emit updated(current_song->state.id);
}
if (current_audio) {
emit updated(current_audio->state.id);
}
{
QMutexLocker lock(&AudioMutex);
auto clearAndCancel = [this](AudioMsgId::Type type, int index) {
auto track = trackForType(type, index);
if (track->state.id) {
emit loaderOnCancel(track->state.id);
}
track->clear();
};
for (auto index = 0; index != kTogetherLimit; ++index) {
clearAndCancel(AudioMsgId::Type::Voice, index);
clearAndCancel(AudioMsgId::Type::Song, index);
}
_videoTrack.clear();
_loader->stopFromVideo();
}
}
TrackState Mixer::currentVideoState(uint64 videoPlayId) {
QMutexLocker lock(&AudioMutex);
auto current = trackForType(AudioMsgId::Type::Video);
if (!current || current->videoPlayId != videoPlayId) {
return TrackState();
}
return current->state;
}
TrackState Mixer::currentState(AudioMsgId::Type type) {
QMutexLocker lock(&AudioMutex);
auto current = trackForType(type);
if (!current) {
return TrackState();
}
return current->state;
}
void Mixer::setStoppedState(Track *current, State state) {
current->state.state = state;
current->state.position = 0;
}
void Mixer::clearStoppedAtStart(const AudioMsgId &audio) {
QMutexLocker lock(&AudioMutex);
auto track = trackForType(audio.type());
if (track && track->state.id == audio && track->state.state == State::StoppedAtStart) {
setStoppedState(track);
}
}
void Mixer::detachFromDeviceByTimer() {
QMetaObject::invokeMethod(_fader, "onDetachFromDeviceByTimer", Qt::QueuedConnection, Q_ARG(bool, true));
}
void Mixer::detachTracks() {
for (auto i = 0; i != kTogetherLimit; ++i) {
trackForType(AudioMsgId::Type::Voice, i)->detach();
trackForType(AudioMsgId::Type::Song, i)->detach();
}
_videoTrack.detach();
}
void Mixer::reattachIfNeeded() {
_fader->keepAttachedToDevice();
auto reattachNeeded = [this] {
auto isPlayingState = [](const Track &track) {
auto state = track.state.state;
return (state == State::Starting)
|| (state == State::Playing)
|| (state == State::Finishing)
|| (state == State::Pausing)
|| (state == State::Resuming);
};
for (auto i = 0; i != kTogetherLimit; ++i) {
if (isPlayingState(*trackForType(AudioMsgId::Type::Voice, i))
|| isPlayingState(*trackForType(AudioMsgId::Type::Song, i))) {
return true;
}
}
return isPlayingState(_videoTrack);
};
if (reattachNeeded()) {
reattachTracks();
}
}
void Mixer::reattachTracks() {
if (!AudioDevice) {
LOG(("Audio Info: recreating audio device and reattaching the tracks"));
CreateAudioPlaybackDevice();
for (auto i = 0; i != kTogetherLimit; ++i) {
trackForType(AudioMsgId::Type::Voice, i)->reattach(AudioMsgId::Type::Voice);
trackForType(AudioMsgId::Type::Song, i)->reattach(AudioMsgId::Type::Song);
}
_videoTrack.reattach(AudioMsgId::Type::Video);
emit faderOnTimer();
}
}
Fader::Fader(QThread *thread) : QObject()
, _timer(this)
, _suppressAllGain(1., 1.)
, _suppressSongGain(1., 1.) {
moveToThread(thread);
_timer.moveToThread(thread);
_detachFromDeviceTimer.moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(onInit()));
connect(thread, SIGNAL(finished()), this, SLOT(deleteLater()));
_timer.setSingleShot(true);
connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer()));
_detachFromDeviceTimer.setSingleShot(true);
connect(&_detachFromDeviceTimer, SIGNAL(timeout()), this, SLOT(onDetachFromDeviceTimer()));
}
void Fader::onInit() {
}
void Fader::onTimer() {
QMutexLocker lock(&AudioMutex);
if (!mixer()) return;
bool suppressAudioChanged = false, suppressSongChanged = false;
if (_suppressAll || _suppressSongAnim) {
auto ms = getms();
auto wasSong = suppressSongGain;
if (_suppressAll) {
auto notifyLengthMs = Media::Player::DefaultNotify.lengthMs;
auto wasAudio = suppressAllGain;
if (ms >= _suppressAllStart + notifyLengthMs || ms < _suppressAllStart) {
_suppressAll = _suppressAllAnim = false;
_suppressAllGain = anim::value(1., 1.);
} else if (ms > _suppressAllStart + notifyLengthMs - kFadeDuration) {
if (_suppressAllGain.to() != 1.) _suppressAllGain.start(1.);
_suppressAllGain.update(1. - ((_suppressAllStart + notifyLengthMs - ms) / float64(kFadeDuration)), anim::linear);
} else if (ms >= _suppressAllStart + st::mediaPlayerSuppressDuration) {
if (_suppressAllAnim) {
_suppressAllGain.finish();
_suppressAllAnim = false;
}
} else if (ms > _suppressAllStart) {
_suppressAllGain.update((ms - _suppressAllStart) / float64(st::mediaPlayerSuppressDuration), anim::linear);
}
suppressAllGain = _suppressAllGain.current();
suppressAudioChanged = (suppressAllGain != wasAudio);
}
if (_suppressSongAnim) {
if (ms >= _suppressSongStart + kFadeDuration) {
_suppressSongGain.finish();
_suppressSongAnim = false;
} else {
_suppressSongGain.update((ms - _suppressSongStart) / float64(kFadeDuration), anim::linear);
}
}
suppressSongGain = qMin(suppressAllGain, _suppressSongGain.current());
suppressSongChanged = (suppressSongGain != wasSong);
}
bool hasFading = (_suppressAll || _suppressSongAnim);
bool hasPlaying = false;
auto updatePlayback = [this, &hasPlaying, &hasFading](AudioMsgId::Type type, int index, float64 suppressGain, bool suppressGainChanged) {
auto track = mixer()->trackForType(type, index);
if (IsStopped(track->state.state) || track->state.state == State::Paused || !track->isStreamCreated()) return;
int32 emitSignals = updateOnePlayback(track, hasPlaying, hasFading, suppressGain, suppressGainChanged);
if (emitSignals & EmitError) emit error(track->state.id);
if (emitSignals & EmitStopped) emit audioStopped(track->state.id);
if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(track->state.id);
if (emitSignals & EmitNeedToPreload) emit needToPreload(track->state.id);
};
auto suppressGainForMusic = suppressSongGain * Global::SongVolume();
auto suppressGainForMusicChanged = suppressSongChanged || _songVolumeChanged;
for (auto i = 0; i != kTogetherLimit; ++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 && !hasPlaying && Media::Player::NotifyIsPlaying()) {
hasPlaying = true;
}
if (hasFading) {
_timer.start(kCheckFadingTimeout);
keepAttachedToDevice();
} else if (hasPlaying) {
_timer.start(kCheckPlaybackPositionTimeout);
keepAttachedToDevice();
} else {
onDetachFromDeviceByTimer(false);
}
}
int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged) {
bool playing = false, fading = false;
auto errorHappened = [this, track] {
if (PlaybackErrorHappened()) {
setStoppedState(track, State::StoppedAtError);
return true;
}
return false;
};
ALint positionInBuffered = 0;
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &positionInBuffered);
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (errorHappened()) return EmitError;
int32 emitSignals = 0;
if (state == AL_STOPPED && positionInBuffered == 0 && !internal::CheckAudioDeviceConnected()) {
return emitSignals;
}
switch (track->state.state) {
case State::Finishing:
case State::Pausing:
case State::Starting:
case State::Resuming: {
fading = true;
} break;
case State::Playing: {
playing = true;
} break;
}
auto fullPosition = track->bufferedPosition + positionInBuffered;
if (fading && (state == AL_PLAYING || !track->loading)) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition);
if (state != AL_PLAYING) {
fading = false;
if (track->stream.source) {
alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError;
}
if (track->state.state == State::Pausing) {
track->state.state = State::PausedAtEnd;
} else {
setStoppedState(track, State::StoppedAtEnd);
}
emitSignals |= EmitStopped;
} else if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) {
fading = false;
alSourcef(track->stream.source, AL_GAIN, 1. * suppressGain);
if (errorHappened()) return EmitError;
switch (track->state.state) {
case State::Finishing: {
alSourceStop(track->stream.source);
if (errorHappened()) return EmitError;
setStoppedState(track);
state = AL_STOPPED;
} break;
case State::Pausing: {
alSourcePause(track->stream.source);
if (errorHappened()) return EmitError;
track->state.state = State::Paused;
} break;
case State::Starting:
case State::Resuming: {
track->state.state = State::Playing;
playing = true;
} break;
}
} else {
auto newGain = TimeMs(1000) * fadingForSamplesCount / float64(kFadeDuration * track->state.frequency);
if (track->state.state == State::Pausing || track->state.state == State::Finishing) {
newGain = 1. - newGain;
}
alSourcef(track->stream.source, AL_GAIN, newGain * suppressGain);
if (errorHappened()) return EmitError;
}
} else if (playing && (state == AL_PLAYING || !track->loading)) {
if (state != AL_PLAYING) {
playing = false;
if (track->isStreamCreated()) {
alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError;
}
setStoppedState(track, State::StoppedAtEnd);
emitSignals |= EmitStopped;
} else if (suppressGainChanged) {
alSourcef(track->stream.source, AL_GAIN, suppressGain);
if (errorHappened()) return EmitError;
}
}
if (state == AL_PLAYING && fullPosition >= track->state.position + kCheckPlaybackPositionDelta) {
track->state.position = fullPosition;
emitSignals |= EmitPositionUpdated;
}
if (playing || track->state.state == State::Starting || track->state.state == State::Resuming) {
if (!track->loaded && !track->loading) {
auto needPreload = (track->state.position + kPreloadSamples > track->bufferedPosition + track->bufferedLength);
if (needPreload) {
track->loading = true;
emitSignals |= EmitNeedToPreload;
}
}
}
if (playing) hasPlaying = true;
if (fading) hasFading = true;
return emitSignals;
}
void Fader::setStoppedState(Mixer::Track *track, State state) {
track->state.state = state;
track->state.position = 0;
}
void Fader::onDetachFromDeviceTimer() {
QMutexLocker lock(&_detachFromDeviceMutex);
_detachFromDeviceForce = false;
lock.unlock();
DetachFromDevice();
}
void Fader::onSuppressSong() {
if (!_suppressSong) {
_suppressSong = true;
_suppressSongAnim = true;
_suppressSongStart = getms();
_suppressSongGain.start(st::suppressSong);
onTimer();
}
}
void Fader::onUnsuppressSong() {
if (_suppressSong) {
_suppressSong = false;
_suppressSongAnim = true;
_suppressSongStart = getms();
_suppressSongGain.start(1.);
onTimer();
}
}
void Fader::onSuppressAll() {
_suppressAll = true;
_suppressAllStart = getms();
_suppressAllGain.start(st::suppressAll);
onTimer();
}
void Fader::onSongVolumeChanged() {
_songVolumeChanged = true;
onTimer();
}
void Fader::onVideoVolumeChanged() {
_videoVolumeChanged = true;
onTimer();
}
void Fader::keepAttachedToDevice() {
QMutexLocker lock(&_detachFromDeviceMutex);
if (!_detachFromDeviceForce) {
_detachFromDeviceTimer.stop();
}
}
void Fader::onDetachFromDeviceByTimer(bool force) {
QMutexLocker lock(&_detachFromDeviceMutex);
if (force) {
_detachFromDeviceForce = true;
}
if (!_detachFromDeviceTimer.isActive()) {
_detachFromDeviceTimer.start(kDetachDeviceTimeout);
}
}
} // namespace Player
} // namespace Media
namespace internal {
QMutex *audioPlayerMutex() {
return &AudioMutex;
}
bool audioCheckError() {
return !Media::Player::PlaybackErrorHappened();
}
bool audioDeviceIsConnected() {
if (!AudioDevice) {
return false;
}
ALint connected = 0;
alcGetIntegerv(AudioDevice, ALC_CONNECTED, 1, &connected);
if (Media::Player::ContextErrorHappened()) {
return false;
}
return (connected != 0);
}
bool CheckAudioDeviceConnected() {
if (audioDeviceIsConnected()) {
return true;
}
if (auto mixer = Media::Player::mixer()) {
mixer->detachFromDeviceByTimer();
}
return false;
}
} // namespace internal
class FFMpegAttributesReader : public AbstractFFMpegLoader {
public:
FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) {
}
bool open(qint64 &position) override {
if (!AbstractFFMpegLoader::open(position)) {
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);
qint64 position = 0;
if (reader.open(position)) {
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) override {
if (!FFMpegLoader::open(position)) {
return false;
}
QByteArray buffer;
buffer.reserve(AudioVoiceMsgBufferSize);
int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
if (duration() < Media::Player::kWaveformSamplesCount) {
return false;
}
QVector<uint16> peaks;
peaks.reserve(Media::Player::kWaveformSamplesCount);
int32 fmt = format();
uint16 peak = 0;
while (processed < countbytes) {
buffer.resize(0);
int64 samples = 0;
auto res = readMore(buffer, samples);
if (res == ReadResult::Error || res == ReadResult::EndOfFile) {
break;
}
if (buffer.isEmpty()) {
continue;
}
const char *data = buffer.data();
if (fmt == AL_FORMAT_MONO8 || fmt == AL_FORMAT_STEREO8) {
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 += Media::Player::kWaveformSamplesCount;
if (sumbytes >= countbytes) {
sumbytes -= countbytes;
peaks.push_back(peak);
peak = 0;
}
}
} else if (fmt == AL_FORMAT_MONO16 || fmt == AL_FORMAT_STEREO16) {
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) * Media::Player::kWaveformSamplesCount;
if (sumbytes >= countbytes) {
sumbytes -= countbytes;
peaks.push_back(peak);
peak = 0;
}
}
}
processed += sampleSize * samples;
}
if (sumbytes > 0 && peaks.size() < Media::Player::kWaveformSamplesCount) {
peaks.push_back(peak);
}
if (peaks.isEmpty()) {
return false;
}
auto sum = std::accumulate(peaks.cbegin(), peaks.cend(), 0LL);
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);
qint64 position = 0;
if (counter.open(position)) {
return counter.waveform();
}
return VoiceWaveform();
}