tdesktop/Telegram/SourceFiles/media/media_audio_loaders.cpp
John Preston 9fe714189d updateNewMessage now can request getDifference(), if data is absent.
Video sync and frame duration count improved.
Seek in not 44100 and not 48000 hz audio streams fixed.
2016-07-21 20:35:55 +03:00

435 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 = false;
{
QMutexLocker lock(&_fromVideoMutex);
if (_fromVideoPlayId == part.videoPlayId) {
_fromVideoQueue.enqueue(FFMpeg::dataWrapFromPacket(*part.packet));
invoke = true;
} else {
FFMpeg::freePacket(part.packet);
}
}
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 &packetData : queue) {
AVPacket packet;
FFMpeg::packetFromDataWrap(packet, packetData);
FFMpeg::freePacket(&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->playbackState.state = state;
m->playbackState.position = 0;
}
void AudioPlayerLoaders::emitError(AudioMsgId::Type type) {
emit error(clear(type));
}
void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) {
loadData(audio, 0);
}
void AudioPlayerLoaders::loadData(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->playbackState.state = AudioPlayerStoppedAtStart;
}
emitError(type);
return;
}
finished = true;
break;
} else if (res == Result::EndOfFile) {
finished = true;
break;
} else if (res == Result::Ok) {
errAtStart = false;
} else if (res == Result::Wait) {
waiting = (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->playbackState.duration - position;
m->playbackState.position = position;
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->playbackState.duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2];
clear(type);
}
m->loading = false;
if (m->playbackState.state == AudioPlayerResuming || m->playbackState.state == AudioPlayerPlaying || m->playbackState.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() * Global::SongVolume()); break;
case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::VideoVolume()); 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 = (_video == audio); break;
}
if (l && (!isGoodId || !l->check(data->file, data->data))) {
clear(audio.type());
l = nullptr;
}
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) {
if (!data->videoData) {
data->playbackState.state = AudioPlayerStoppedAtError;
emit error(audio);
LOG(("Audio Error: video sound data not ready"));
return nullptr;
}
_videoLoader = std_::make_unique<ChildFFMpegLoader>(data->videoPlayId, 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->playbackState.state = AudioPlayerStoppedAtStart;
return nullptr;
}
int64 duration = l->duration();
if (duration <= 0) {
data->playbackState.state = AudioPlayerStoppedAtStart;
return nullptr;
}
data->playbackState.duration = duration;
data->playbackState.frequency = l->frequency();
if (!data->playbackState.frequency) data->playbackState.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;
}
}
}