424 lines
12 KiB
C++
424 lines
12 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-2016 John Preston, https://desktop.telegram.org
|
||
|
*/
|
||
|
#include "stdafx.h"
|
||
|
#include "media/media_audio_loaders.h"
|
||
|
|
||
|
#include "media/media_audio.h"
|
||
|
#include "media/media_audio_ffmpeg_loader.h"
|
||
|
#include "media/media_child_ffmpeg_loader.h"
|
||
|
|
||
|
AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _fromVideoNotify(this, "onVideoSoundAdded") {
|
||
|
moveToThread(thread);
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) {
|
||
|
bool invoke = true;
|
||
|
{
|
||
|
QMutexLocker lock(&_fromVideoMutex);
|
||
|
if (_fromVideoPlayId == part.videoPlayId) {
|
||
|
_fromVideoQueue.enqueue(*part.packet);
|
||
|
} else {
|
||
|
av_packet_unref(part.packet);
|
||
|
invoke = false;
|
||
|
}
|
||
|
}
|
||
|
if (invoke) {
|
||
|
_fromVideoNotify.call();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::startFromVideo(uint64 videoPlayId) {
|
||
|
QMutexLocker lock(&_fromVideoMutex);
|
||
|
_fromVideoPlayId = videoPlayId;
|
||
|
clearFromVideoQueue();
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::stopFromVideo() {
|
||
|
startFromVideo(0);
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::onVideoSoundAdded() {
|
||
|
bool waitingAndAdded = false;
|
||
|
{
|
||
|
QMutexLocker lock(&_fromVideoMutex);
|
||
|
if (_videoLoader && _videoLoader->playId() == _fromVideoPlayId && !_fromVideoQueue.isEmpty()) {
|
||
|
_videoLoader->enqueuePackets(_fromVideoQueue);
|
||
|
waitingAndAdded = _videoLoader->holdsSavedDecodedSamples();
|
||
|
}
|
||
|
}
|
||
|
if (waitingAndAdded) {
|
||
|
onLoad(_video);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AudioPlayerLoaders::~AudioPlayerLoaders() {
|
||
|
QMutexLocker lock(&_fromVideoMutex);
|
||
|
clearFromVideoQueue();
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::clearFromVideoQueue() {
|
||
|
auto queue = createAndSwap(_fromVideoQueue);
|
||
|
for (auto &packet : queue) {
|
||
|
av_packet_unref(&packet);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::onInit() {
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) {
|
||
|
auto type = audio.type();
|
||
|
clear(type);
|
||
|
{
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
AudioPlayer *voice = audioPlayer();
|
||
|
if (!voice) return;
|
||
|
|
||
|
auto data = voice->dataForType(type);
|
||
|
if (!data) return;
|
||
|
|
||
|
data->loading = true;
|
||
|
}
|
||
|
|
||
|
loadData(audio, position);
|
||
|
}
|
||
|
|
||
|
AudioMsgId AudioPlayerLoaders::clear(AudioMsgId::Type type) {
|
||
|
AudioMsgId result;
|
||
|
switch (type) {
|
||
|
case AudioMsgId::Type::Voice: std::swap(result, _audio); _audioLoader = nullptr; break;
|
||
|
case AudioMsgId::Type::Song: std::swap(result, _song); _songLoader = nullptr; break;
|
||
|
case AudioMsgId::Type::Video: std::swap(result, _video); _videoLoader = nullptr; break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) {
|
||
|
m->state = state;
|
||
|
m->position = 0;
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::emitError(AudioMsgId::Type type) {
|
||
|
emit error(clear(type));
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) {
|
||
|
loadData(audio, 0);
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::loadData(const AudioMsgId &audio, qint64 position) {
|
||
|
SetupError err = SetupNoErrorStarted;
|
||
|
auto type = audio.type();
|
||
|
AudioPlayerLoader *l = setupLoader(audio, err, position);
|
||
|
if (!l) {
|
||
|
if (err == SetupErrorAtStart) {
|
||
|
emitError(type);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool started = (err == SetupNoErrorStarted);
|
||
|
bool finished = false;
|
||
|
bool waiting = false;
|
||
|
bool errAtStart = started;
|
||
|
|
||
|
QByteArray samples;
|
||
|
int64 samplesCount = 0;
|
||
|
if (l->holdsSavedDecodedSamples()) {
|
||
|
l->takeSavedDecodedSamples(&samples, &samplesCount);
|
||
|
}
|
||
|
while (samples.size() < AudioVoiceMsgBufferSize) {
|
||
|
auto res = l->readMore(samples, samplesCount);
|
||
|
using Result = AudioPlayerLoader::ReadResult;
|
||
|
if (res == Result::Error) {
|
||
|
if (errAtStart) {
|
||
|
{
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
AudioPlayer::AudioMsg *m = checkLoader(type);
|
||
|
if (m) m->state = AudioPlayerStoppedAtStart;
|
||
|
}
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
finished = true;
|
||
|
break;
|
||
|
} else if (res == Result::Ok) {
|
||
|
errAtStart = false;
|
||
|
} else if (res == Result::Wait) {
|
||
|
waiting = samples.isEmpty();// (samples.size() < AudioVoiceMsgBufferSize);
|
||
|
if (waiting) {
|
||
|
l->saveDecodedSamples(&samples, &samplesCount);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
if (!checkLoader(type)) {
|
||
|
clear(type);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
AudioPlayer::AudioMsg *m = checkLoader(type);
|
||
|
if (!m) {
|
||
|
clear(type);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (started) {
|
||
|
if (m->source) {
|
||
|
alSourceStop(m->source);
|
||
|
for (int32 i = 0; i < 3; ++i) {
|
||
|
if (m->samplesCount[i]) {
|
||
|
ALuint buffer = 0;
|
||
|
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||
|
m->samplesCount[i] = 0;
|
||
|
}
|
||
|
}
|
||
|
m->nextBuffer = 0;
|
||
|
}
|
||
|
m->skipStart = position;
|
||
|
m->skipEnd = m->duration - position;
|
||
|
m->position = 0;
|
||
|
m->started = 0;
|
||
|
}
|
||
|
if (samplesCount) {
|
||
|
if (!m->source) {
|
||
|
alGenSources(1, &m->source);
|
||
|
alSourcef(m->source, AL_PITCH, 1.f);
|
||
|
alSource3f(m->source, AL_POSITION, 0, 0, 0);
|
||
|
alSource3f(m->source, AL_VELOCITY, 0, 0, 0);
|
||
|
alSourcei(m->source, AL_LOOPING, 0);
|
||
|
}
|
||
|
if (!m->buffers[m->nextBuffer]) {
|
||
|
alGenBuffers(3, m->buffers);
|
||
|
}
|
||
|
|
||
|
// If this buffer is queued, try to unqueue some buffer.
|
||
|
if (m->samplesCount[m->nextBuffer]) {
|
||
|
ALint processed = 0;
|
||
|
alGetSourcei(m->source, AL_BUFFERS_PROCESSED, &processed);
|
||
|
if (processed < 1) { // No processed buffers, wait.
|
||
|
l->saveDecodedSamples(&samples, &samplesCount);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Unqueue some processed buffer.
|
||
|
ALuint buffer = 0;
|
||
|
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||
|
if (!internal::audioCheckError()) {
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Find it in the list and make it the nextBuffer.
|
||
|
bool found = false;
|
||
|
for (int i = 0; i < 3; ++i) {
|
||
|
if (m->buffers[i] == buffer) {
|
||
|
found = true;
|
||
|
m->nextBuffer = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!found) {
|
||
|
LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(m->source).arg(processed));
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (m->samplesCount[m->nextBuffer]) {
|
||
|
m->skipStart += m->samplesCount[m->nextBuffer];
|
||
|
m->samplesCount[m->nextBuffer] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto frequency = l->frequency();
|
||
|
auto format = l->format();
|
||
|
m->samplesCount[m->nextBuffer] = samplesCount;
|
||
|
alBufferData(m->buffers[m->nextBuffer], format, samples.constData(), samples.size(), frequency);
|
||
|
|
||
|
alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer);
|
||
|
m->skipEnd -= samplesCount;
|
||
|
|
||
|
m->nextBuffer = (m->nextBuffer + 1) % 3;
|
||
|
|
||
|
if (!internal::audioCheckError()) {
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (waiting) {
|
||
|
return;
|
||
|
}
|
||
|
finished = true;
|
||
|
}
|
||
|
|
||
|
if (finished) {
|
||
|
m->skipEnd = 0;
|
||
|
m->duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2];
|
||
|
clear(type);
|
||
|
}
|
||
|
|
||
|
m->loading = false;
|
||
|
if (m->state == AudioPlayerResuming || m->state == AudioPlayerPlaying || m->state == AudioPlayerStarting) {
|
||
|
ALint state = AL_INITIAL;
|
||
|
alGetSourcei(m->source, AL_SOURCE_STATE, &state);
|
||
|
if (internal::audioCheckError()) {
|
||
|
if (state != AL_PLAYING) {
|
||
|
audioPlayer()->resumeDevice();
|
||
|
|
||
|
switch (type) {
|
||
|
case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, internal::audioSuppressGain()); break;
|
||
|
case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break;
|
||
|
case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * cSongVolume()); break;
|
||
|
}
|
||
|
if (!internal::audioCheckError()) {
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
alSourcePlay(m->source);
|
||
|
if (!internal::audioCheckError()) {
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
emit needToCheck();
|
||
|
}
|
||
|
} else {
|
||
|
setStoppedState(m, AudioPlayerStoppedAtError);
|
||
|
emitError(type);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position) {
|
||
|
err = SetupErrorAtStart;
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
AudioPlayer *voice = audioPlayer();
|
||
|
if (!voice) return nullptr;
|
||
|
|
||
|
auto data = voice->dataForType(audio.type());
|
||
|
if (!data || data->audio != audio || !data->loading) {
|
||
|
emit error(audio);
|
||
|
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
|
||
|
err = SetupErrorNotPlaying;
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
bool isGoodId = false;
|
||
|
AudioPlayerLoader *l = nullptr;
|
||
|
switch (audio.type()) {
|
||
|
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (_audio == audio); break;
|
||
|
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (_song == audio); break;
|
||
|
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_song == audio); break;
|
||
|
}
|
||
|
|
||
|
if (l && (!isGoodId || !l->check(data->file, data->data))) {
|
||
|
clear(audio.type());
|
||
|
}
|
||
|
|
||
|
if (!l) {
|
||
|
std_::unique_ptr<AudioPlayerLoader> *loader = nullptr;
|
||
|
switch (audio.type()) {
|
||
|
case AudioMsgId::Type::Voice: _audio = audio; loader = &_audioLoader; break;
|
||
|
case AudioMsgId::Type::Song: _song = audio; loader = &_songLoader; break;
|
||
|
case AudioMsgId::Type::Video: _video = audio; break;
|
||
|
}
|
||
|
|
||
|
if (audio.type() == AudioMsgId::Type::Video) {
|
||
|
_videoLoader = std_::make_unique<ChildFFMpegLoader>(std_::move(data->videoData));
|
||
|
l = _videoLoader.get();
|
||
|
} else {
|
||
|
*loader = std_::make_unique<FFMpegLoader>(data->file, data->data);
|
||
|
l = loader->get();
|
||
|
}
|
||
|
|
||
|
if (!l->open(position)) {
|
||
|
data->state = AudioPlayerStoppedAtStart;
|
||
|
return nullptr;
|
||
|
}
|
||
|
int64 duration = l->duration();
|
||
|
if (duration <= 0) {
|
||
|
data->state = AudioPlayerStoppedAtStart;
|
||
|
return nullptr;
|
||
|
}
|
||
|
data->duration = duration;
|
||
|
data->frequency = l->frequency();
|
||
|
if (!data->frequency) data->frequency = AudioVoiceMsgFrequency;
|
||
|
err = SetupNoErrorStarted;
|
||
|
} else {
|
||
|
if (!data->skipEnd) {
|
||
|
err = SetupErrorLoadedFull;
|
||
|
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
||
|
return nullptr;
|
||
|
}
|
||
|
}
|
||
|
return l;
|
||
|
}
|
||
|
|
||
|
AudioPlayer::AudioMsg *AudioPlayerLoaders::checkLoader(AudioMsgId::Type type) {
|
||
|
AudioPlayer *voice = audioPlayer();
|
||
|
if (!voice) return 0;
|
||
|
|
||
|
auto data = voice->dataForType(type);
|
||
|
bool isGoodId = false;
|
||
|
AudioPlayerLoader *l = nullptr;
|
||
|
switch (type) {
|
||
|
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (data->audio == _audio); break;
|
||
|
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (data->audio == _song); break;
|
||
|
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (data->audio == _video); break;
|
||
|
}
|
||
|
if (!l || !data) return nullptr;
|
||
|
|
||
|
if (!isGoodId || !data->loading || !l->check(data->file, data->data)) {
|
||
|
LOG(("Audio Error: playing changed while loading"));
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) {
|
||
|
switch (audio.type()) {
|
||
|
case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break;
|
||
|
case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break;
|
||
|
case AudioMsgId::Type::Video: if (_video == audio) clear(audio.type()); break;
|
||
|
}
|
||
|
|
||
|
QMutexLocker lock(internal::audioPlayerMutex());
|
||
|
AudioPlayer *voice = audioPlayer();
|
||
|
if (!voice) return;
|
||
|
|
||
|
for (int i = 0; i < AudioSimultaneousLimit; ++i) {
|
||
|
auto data = voice->dataForType(audio.type(), i);
|
||
|
if (data->audio == audio) {
|
||
|
data->loading = false;
|
||
|
}
|
||
|
}
|
||
|
}
|