2016-07-05 17:44:02 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-07-05 17:44:02 +00:00
|
|
|
*/
|
2019-02-13 12:36:59 +00:00
|
|
|
#include "media/audio/media_audio_loaders.h"
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2019-02-13 12:36:59 +00:00
|
|
|
#include "media/audio/media_audio.h"
|
|
|
|
#include "media/audio/media_audio_ffmpeg_loader.h"
|
|
|
|
#include "media/audio/media_child_ffmpeg_loader.h"
|
2023-03-17 17:18:31 +00:00
|
|
|
#include "media/media_common.h"
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
namespace Media {
|
|
|
|
namespace Player {
|
2019-02-21 13:40:09 +00:00
|
|
|
|
|
|
|
Loaders::Loaders(QThread *thread)
|
2019-02-28 21:03:25 +00:00
|
|
|
: _fromExternalNotify([=] { videoSoundAdded(); }) {
|
2016-07-05 17:44:02 +00:00
|
|
|
moveToThread(thread);
|
2019-02-28 21:03:25 +00:00
|
|
|
_fromExternalNotify.moveToThread(thread);
|
2016-10-12 19:34:25 +00:00
|
|
|
connect(thread, SIGNAL(started()), this, SLOT(onInit()));
|
|
|
|
connect(thread, SIGNAL(finished()), this, SLOT(deleteLater()));
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 21:03:25 +00:00
|
|
|
void Loaders::feedFromExternal(ExternalSoundPart &&part) {
|
2017-05-18 20:18:59 +00:00
|
|
|
auto invoke = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
{
|
2019-02-28 21:03:25 +00:00
|
|
|
QMutexLocker lock(&_fromExternalMutex);
|
|
|
|
invoke = _fromExternalQueues.empty()
|
|
|
|
&& _fromExternalForceToBuffer.empty();
|
2019-12-19 15:14:05 +00:00
|
|
|
auto &queue = _fromExternalQueues[part.audio];
|
|
|
|
queue.insert(
|
|
|
|
end(queue),
|
|
|
|
std::make_move_iterator(part.packets.begin()),
|
|
|
|
std::make_move_iterator(part.packets.end()));
|
2019-02-21 13:40:09 +00:00
|
|
|
}
|
|
|
|
if (invoke) {
|
2019-02-28 21:03:25 +00:00
|
|
|
_fromExternalNotify.call();
|
2019-02-21 13:40:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-28 21:03:25 +00:00
|
|
|
void Loaders::forceToBufferExternal(const AudioMsgId &audioId) {
|
2019-02-21 13:40:09 +00:00
|
|
|
auto invoke = false;
|
|
|
|
{
|
2019-02-28 21:03:25 +00:00
|
|
|
QMutexLocker lock(&_fromExternalMutex);
|
|
|
|
invoke = _fromExternalQueues.empty()
|
|
|
|
&& _fromExternalForceToBuffer.empty();
|
|
|
|
_fromExternalForceToBuffer.emplace(audioId);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
if (invoke) {
|
2019-02-28 21:03:25 +00:00
|
|
|
_fromExternalNotify.call();
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-06 16:49:42 +00:00
|
|
|
void Loaders::videoSoundAdded() {
|
2019-02-28 21:03:25 +00:00
|
|
|
auto queues = decltype(_fromExternalQueues)();
|
|
|
|
auto forces = decltype(_fromExternalForceToBuffer)();
|
2016-07-05 17:44:02 +00:00
|
|
|
{
|
2019-02-28 21:03:25 +00:00
|
|
|
QMutexLocker lock(&_fromExternalMutex);
|
|
|
|
queues = base::take(_fromExternalQueues);
|
|
|
|
forces = base::take(_fromExternalForceToBuffer);
|
2017-05-18 20:18:59 +00:00
|
|
|
}
|
2019-02-21 13:40:09 +00:00
|
|
|
for (const auto &audioId : forces) {
|
|
|
|
const auto tryLoader = [&](const auto &id, auto &loader) {
|
|
|
|
if (audioId == id && loader) {
|
|
|
|
loader->setForceToBuffer(true);
|
|
|
|
if (loader->holdsSavedDecodedSamples()
|
|
|
|
&& !queues.contains(audioId)) {
|
|
|
|
loadData(audioId);
|
|
|
|
}
|
|
|
|
return true;
|
2017-05-18 20:18:59 +00:00
|
|
|
}
|
2019-02-21 13:40:09 +00:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
tryLoader(_audio, _audioLoader)
|
|
|
|
|| tryLoader(_song, _songLoader)
|
|
|
|
|| tryLoader(_video, _videoLoader);
|
|
|
|
}
|
|
|
|
for (auto &pair : queues) {
|
|
|
|
const auto audioId = pair.first;
|
|
|
|
auto &packets = pair.second;
|
|
|
|
const auto tryLoader = [&](const auto &id, auto &loader) {
|
|
|
|
if (id == audioId && loader) {
|
|
|
|
loader->enqueuePackets(std::move(packets));
|
|
|
|
if (loader->holdsSavedDecodedSamples()) {
|
|
|
|
loadData(audioId);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
2019-02-28 21:03:25 +00:00
|
|
|
tryLoader(_audio, _audioLoader)
|
2019-02-21 13:40:09 +00:00
|
|
|
|| tryLoader(_song, _songLoader)
|
|
|
|
|| tryLoader(_video, _videoLoader);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
void Loaders::onInit() {
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-12-10 08:52:38 +00:00
|
|
|
void Loaders::onStart(const AudioMsgId &audio, qint64 positionMs) {
|
2016-07-05 17:44:02 +00:00
|
|
|
auto type = audio.type();
|
|
|
|
clear(type);
|
|
|
|
{
|
|
|
|
QMutexLocker lock(internal::audioPlayerMutex());
|
2017-01-19 08:24:43 +00:00
|
|
|
if (!mixer()) return;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
auto track = mixer()->trackForType(type);
|
|
|
|
if (!track) return;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
track->loading = true;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-12-10 08:52:38 +00:00
|
|
|
loadData(audio, positionMs);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
AudioMsgId Loaders::clear(AudioMsgId::Type type) {
|
2016-07-05 17:44:02 +00:00
|
|
|
AudioMsgId result;
|
|
|
|
switch (type) {
|
2019-03-12 14:56:35 +00:00
|
|
|
case AudioMsgId::Type::Voice:
|
|
|
|
std::swap(result, _audio);
|
|
|
|
_audioLoader = nullptr;
|
|
|
|
break;
|
2019-02-28 21:03:25 +00:00
|
|
|
case AudioMsgId::Type::Song:
|
|
|
|
std::swap(result, _song);
|
|
|
|
_songLoader = nullptr;
|
|
|
|
break;
|
2019-03-12 14:56:35 +00:00
|
|
|
case AudioMsgId::Type::Video:
|
|
|
|
std::swap(result, _video);
|
|
|
|
_videoLoader = nullptr;
|
|
|
|
break;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
void Loaders::setStoppedState(Mixer::Track *track, State state) {
|
2017-05-23 14:04:59 +00:00
|
|
|
mixer()->setStoppedState(track, state);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
void Loaders::emitError(AudioMsgId::Type type) {
|
2021-03-03 18:22:42 +00:00
|
|
|
error(clear(type));
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
void Loaders::onLoad(const AudioMsgId &audio) {
|
2019-02-21 13:40:09 +00:00
|
|
|
loadData(audio);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2019-02-19 06:57:53 +00:00
|
|
|
void Loaders::loadData(AudioMsgId audio, crl::time positionMs) {
|
2016-07-05 17:44:02 +00:00
|
|
|
auto type = audio.type();
|
2023-03-08 09:31:58 +00:00
|
|
|
auto setup = setupLoader(audio, positionMs);
|
|
|
|
const auto l = setup.loader;
|
2016-07-05 17:44:02 +00:00
|
|
|
if (!l) {
|
2023-03-08 09:31:58 +00:00
|
|
|
if (setup.errorAtStart) {
|
2016-07-05 17:44:02 +00:00
|
|
|
emitError(type);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
const auto sampleSize = l->sampleSize();
|
2023-03-17 17:18:31 +00:00
|
|
|
const auto speedChanged = !EqualSpeeds(setup.newSpeed, setup.oldSpeed);
|
2023-03-08 09:31:58 +00:00
|
|
|
auto updatedWithSpeed = speedChanged
|
|
|
|
? rebufferOnSpeedChange(setup)
|
|
|
|
: std::optional<Mixer::Track::WithSpeed>();
|
|
|
|
if (!speedChanged && setup.oldSpeed > 0.) {
|
|
|
|
const auto normalPosition = Mixer::Track::SpeedIndependentPosition(
|
|
|
|
setup.position,
|
|
|
|
setup.oldSpeed);
|
|
|
|
l->dropFramesTill(normalPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto started = setup.justStarted;
|
2017-01-24 21:24:39 +00:00
|
|
|
auto finished = false;
|
|
|
|
auto waiting = false;
|
|
|
|
auto errAtStart = started;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2023-03-06 09:51:37 +00:00
|
|
|
auto accumulated = QByteArray();
|
|
|
|
auto accumulatedCount = 0;
|
2016-07-05 17:44:02 +00:00
|
|
|
if (l->holdsSavedDecodedSamples()) {
|
2023-03-06 09:51:37 +00:00
|
|
|
l->takeSavedDecodedSamples(&accumulated);
|
2023-03-08 09:31:58 +00:00
|
|
|
accumulatedCount = accumulated.size() / sampleSize;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2023-03-08 09:31:58 +00:00
|
|
|
const auto accumulateTill = l->bytesPerBuffer();
|
|
|
|
while (accumulated.size() < accumulateTill) {
|
|
|
|
using Error = AudioPlayerLoader::ReadError;
|
2023-03-06 09:51:37 +00:00
|
|
|
const auto result = l->readMore();
|
2023-03-08 09:31:58 +00:00
|
|
|
if (result == Error::Retry) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-06 09:51:37 +00:00
|
|
|
const auto sampleBytes = v::is<bytes::const_span>(result)
|
|
|
|
? v::get<bytes::const_span>(result)
|
|
|
|
: bytes::const_span();
|
|
|
|
if (!sampleBytes.empty()) {
|
|
|
|
accumulated.append(
|
|
|
|
reinterpret_cast<const char*>(sampleBytes.data()),
|
|
|
|
sampleBytes.size());
|
2023-03-08 09:31:58 +00:00
|
|
|
accumulatedCount += sampleBytes.size() / sampleSize;
|
|
|
|
} else if (result == Error::Other) {
|
2016-07-05 17:44:02 +00:00
|
|
|
if (errAtStart) {
|
|
|
|
{
|
|
|
|
QMutexLocker lock(internal::audioPlayerMutex());
|
2017-01-24 21:24:39 +00:00
|
|
|
if (auto track = checkLoader(type)) {
|
|
|
|
track->state.state = State::StoppedAtStart;
|
|
|
|
}
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
emitError(type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
finished = true;
|
|
|
|
break;
|
2023-03-06 09:51:37 +00:00
|
|
|
} else if (result == Error::EndOfFile) {
|
2016-07-05 17:44:22 +00:00
|
|
|
finished = true;
|
|
|
|
break;
|
2023-03-06 09:51:37 +00:00
|
|
|
} else if (result == Error::Wait) {
|
2023-03-08 09:31:58 +00:00
|
|
|
waiting = (accumulated.size() < accumulateTill)
|
2023-03-06 09:51:37 +00:00
|
|
|
&& (accumulated.isEmpty() || !l->forceToBuffer());
|
2016-07-05 17:44:02 +00:00
|
|
|
if (waiting) {
|
2023-03-06 09:51:37 +00:00
|
|
|
l->saveDecodedSamples(&accumulated);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
break;
|
2023-03-06 09:51:37 +00:00
|
|
|
} else if (v::is<bytes::const_span>(result)) {
|
|
|
|
errAtStart = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QMutexLocker lock(internal::audioPlayerMutex());
|
|
|
|
if (!checkLoader(type)) {
|
|
|
|
clear(type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QMutexLocker lock(internal::audioPlayerMutex());
|
2017-01-24 21:24:39 +00:00
|
|
|
auto track = checkLoader(type);
|
|
|
|
if (!track) {
|
2016-07-05 17:44:02 +00:00
|
|
|
clear(type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
if (started || !accumulated.isEmpty() || updatedWithSpeed) {
|
2017-05-03 11:36:39 +00:00
|
|
|
Audio::AttachToDevice();
|
2019-02-22 14:28:10 +00:00
|
|
|
}
|
|
|
|
if (started) {
|
2023-03-08 09:31:58 +00:00
|
|
|
Assert(!updatedWithSpeed);
|
2017-01-24 21:24:39 +00:00
|
|
|
track->started();
|
|
|
|
if (!internal::audioCheckError()) {
|
|
|
|
setStoppedState(track, State::StoppedAtStart);
|
|
|
|
emitError(type);
|
|
|
|
return;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-12-10 08:52:38 +00:00
|
|
|
track->format = l->format();
|
2023-03-08 09:31:58 +00:00
|
|
|
track->state.frequency = l->samplesFrequency();
|
2017-12-10 08:52:38 +00:00
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
track->state.position = (positionMs * track->state.frequency)
|
|
|
|
/ 1000LL;
|
|
|
|
track->updateWithSpeedPosition();
|
|
|
|
track->withSpeed.bufferedPosition = track->withSpeed.position;
|
|
|
|
track->withSpeed.fadeStartPosition = track->withSpeed.position;
|
|
|
|
} else if (updatedWithSpeed) {
|
|
|
|
auto old = Mixer::Track();
|
|
|
|
old.stream = base::take(track->stream);
|
|
|
|
old.withSpeed = std::exchange(track->withSpeed, *updatedWithSpeed);
|
|
|
|
track->speed = setup.newSpeed;
|
|
|
|
track->reattach(type);
|
|
|
|
old.detach();
|
2017-01-24 21:24:39 +00:00
|
|
|
}
|
2023-03-06 09:51:37 +00:00
|
|
|
if (!accumulated.isEmpty()) {
|
2018-11-08 13:06:22 +00:00
|
|
|
track->ensureStreamCreated(type);
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
auto bufferIndex = track->getNotQueuedBufferIndex();
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
if (!internal::audioCheckError()) {
|
|
|
|
setStoppedState(track, State::StoppedAtError);
|
|
|
|
emitError(type);
|
|
|
|
return;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
if (bufferIndex < 0) { // No free buffers, wait.
|
2023-03-08 09:31:58 +00:00
|
|
|
track->waitingForBuffer = true;
|
2023-03-06 09:51:37 +00:00
|
|
|
l->saveDecodedSamples(&accumulated);
|
2017-01-24 21:24:39 +00:00
|
|
|
return;
|
2019-02-21 13:40:09 +00:00
|
|
|
} else if (l->forceToBuffer()) {
|
|
|
|
l->setForceToBuffer(false);
|
2017-01-24 21:24:39 +00:00
|
|
|
}
|
2023-03-08 09:31:58 +00:00
|
|
|
track->waitingForBuffer = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
track->withSpeed.buffered[bufferIndex] = accumulated;
|
|
|
|
track->withSpeed.samples[bufferIndex] = accumulatedCount;
|
|
|
|
track->withSpeed.bufferedLength += accumulatedCount;
|
2023-03-06 09:51:37 +00:00
|
|
|
alBufferData(
|
|
|
|
track->stream.buffers[bufferIndex],
|
|
|
|
track->format,
|
|
|
|
accumulated.constData(),
|
|
|
|
accumulated.size(),
|
2023-03-08 09:31:58 +00:00
|
|
|
track->state.frequency);
|
2023-03-06 09:51:37 +00:00
|
|
|
|
|
|
|
alSourceQueueBuffers(
|
|
|
|
track->stream.source,
|
|
|
|
1,
|
|
|
|
track->stream.buffers + bufferIndex);
|
2016-07-05 17:44:02 +00:00
|
|
|
|
|
|
|
if (!internal::audioCheckError()) {
|
2017-01-24 21:24:39 +00:00
|
|
|
setStoppedState(track, State::StoppedAtError);
|
2016-07-05 17:44:02 +00:00
|
|
|
emitError(type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (waiting) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
finished = true;
|
|
|
|
}
|
2019-02-22 14:28:10 +00:00
|
|
|
track->state.waitingForData = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
|
|
|
if (finished) {
|
2017-01-24 21:24:39 +00:00
|
|
|
track->loaded = true;
|
2023-03-08 09:31:58 +00:00
|
|
|
track->withSpeed.length = track->withSpeed.bufferedPosition
|
|
|
|
+ track->withSpeed.bufferedLength;
|
|
|
|
track->state.length = Mixer::Track::SpeedIndependentPosition(
|
|
|
|
track->withSpeed.length,
|
|
|
|
track->speed);
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
track->loading = false;
|
2019-02-22 14:28:10 +00:00
|
|
|
if (IsPausedOrPausing(track->state.state)
|
|
|
|
|| IsStoppedOrStopping(track->state.state)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ALint state = AL_INITIAL;
|
|
|
|
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
|
|
|
|
if (!internal::audioCheckError()) {
|
|
|
|
setStoppedState(track, State::StoppedAtError);
|
|
|
|
emitError(type);
|
|
|
|
return;
|
|
|
|
}
|
2017-01-24 21:24:39 +00:00
|
|
|
|
2019-02-22 14:28:10 +00:00
|
|
|
if (state == AL_PLAYING) {
|
|
|
|
return;
|
|
|
|
} else if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2019-02-22 14:28:10 +00:00
|
|
|
alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type));
|
|
|
|
if (!internal::audioCheckError()) {
|
|
|
|
setStoppedState(track, State::StoppedAtError);
|
|
|
|
emitError(type);
|
|
|
|
return;
|
|
|
|
}
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2019-02-22 14:28:10 +00:00
|
|
|
if (state == AL_STOPPED) {
|
2023-03-08 09:31:58 +00:00
|
|
|
alSourcei(
|
|
|
|
track->stream.source,
|
|
|
|
AL_SAMPLE_OFFSET,
|
|
|
|
qMax(track->withSpeed.position - track->withSpeed.bufferedPosition, 0LL));
|
2019-02-22 14:28:10 +00:00
|
|
|
if (!internal::audioCheckError()) {
|
2017-01-24 21:24:39 +00:00
|
|
|
setStoppedState(track, State::StoppedAtError);
|
2016-07-05 17:44:02 +00:00
|
|
|
emitError(type);
|
2019-02-22 14:28:10 +00:00
|
|
|
return;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-22 14:28:10 +00:00
|
|
|
alSourcePlay(track->stream.source);
|
|
|
|
if (!internal::audioCheckError()) {
|
|
|
|
setStoppedState(track, State::StoppedAtError);
|
|
|
|
emitError(type);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-03 18:22:42 +00:00
|
|
|
needToCheck();
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
Loaders::SetupLoaderResult Loaders::setupLoader(
|
2017-12-10 08:52:38 +00:00
|
|
|
const AudioMsgId &audio,
|
2019-02-19 06:57:53 +00:00
|
|
|
crl::time positionMs) {
|
2016-07-05 17:44:02 +00:00
|
|
|
QMutexLocker lock(internal::audioPlayerMutex());
|
2023-03-08 09:31:58 +00:00
|
|
|
if (!mixer()) {
|
|
|
|
return {};
|
|
|
|
}
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
auto track = mixer()->trackForType(audio.type());
|
|
|
|
if (!track || track->state.id != audio || !track->loading) {
|
2021-03-03 18:22:42 +00:00
|
|
|
error(audio);
|
2016-07-05 17:44:02 +00:00
|
|
|
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
|
2023-03-08 09:31:58 +00:00
|
|
|
return {};
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2016-07-05 17:44:22 +00:00
|
|
|
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_video == audio); break;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
if (l && (!isGoodId || !l->check(track->file, track->data))) {
|
2016-07-05 17:44:02 +00:00
|
|
|
clear(audio.type());
|
2016-07-05 17:44:22 +00:00
|
|
|
l = nullptr;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
auto SpeedDependentPosition = Mixer::Track::SpeedDependentPosition;
|
2016-07-05 17:44:02 +00:00
|
|
|
if (!l) {
|
2017-02-21 13:45:56 +00:00
|
|
|
std::unique_ptr<AudioPlayerLoader> *loader = nullptr;
|
2016-07-05 17:44:02 +00:00
|
|
|
switch (audio.type()) {
|
|
|
|
case AudioMsgId::Type::Voice: _audio = audio; loader = &_audioLoader; break;
|
|
|
|
case AudioMsgId::Type::Song: _song = audio; loader = &_songLoader; break;
|
2017-05-18 20:18:59 +00:00
|
|
|
case AudioMsgId::Type::Video: _video = audio; loader = &_videoLoader; break;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 21:03:25 +00:00
|
|
|
if (audio.externalPlayId()) {
|
|
|
|
if (!track->externalData) {
|
2017-05-18 20:18:59 +00:00
|
|
|
clear(audio.type());
|
2017-01-24 21:24:39 +00:00
|
|
|
track->state.state = State::StoppedAtError;
|
2021-03-03 18:22:42 +00:00
|
|
|
error(audio);
|
2016-07-05 17:44:22 +00:00
|
|
|
LOG(("Audio Error: video sound data not ready"));
|
2023-03-08 09:31:58 +00:00
|
|
|
return {};
|
2016-07-05 17:44:22 +00:00
|
|
|
}
|
2019-02-28 21:03:25 +00:00
|
|
|
*loader = std::make_unique<ChildFFMpegLoader>(
|
|
|
|
std::move(track->externalData));
|
2016-07-05 17:44:02 +00:00
|
|
|
} else {
|
2019-02-28 21:03:25 +00:00
|
|
|
*loader = std::make_unique<FFMpegLoader>(
|
|
|
|
track->file,
|
|
|
|
track->data,
|
|
|
|
bytes::vector());
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2017-05-18 20:18:59 +00:00
|
|
|
l = loader->get();
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2023-03-08 09:31:58 +00:00
|
|
|
track->speed = track->nextSpeed;
|
|
|
|
if (!l->open(positionMs, track->speed)) {
|
2017-01-24 21:24:39 +00:00
|
|
|
track->state.state = State::StoppedAtStart;
|
2023-03-08 09:31:58 +00:00
|
|
|
return { .errorAtStart = true };
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2023-03-08 09:31:58 +00:00
|
|
|
const auto duration = l->duration();
|
|
|
|
if (duration <= 0) {
|
2017-01-24 21:24:39 +00:00
|
|
|
track->state.state = State::StoppedAtStart;
|
2023-03-08 09:31:58 +00:00
|
|
|
return { .errorAtStart = true };
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2017-05-03 13:01:15 +00:00
|
|
|
track->state.frequency = l->samplesFrequency();
|
2023-03-08 09:31:58 +00:00
|
|
|
track->state.length = (duration * track->state.frequency) / 1000;
|
|
|
|
track->withSpeed.length = SpeedDependentPosition(
|
|
|
|
track->state.length,
|
|
|
|
track->speed);
|
|
|
|
return { .loader = l, .justStarted = true };
|
2023-03-17 17:18:31 +00:00
|
|
|
} else if (!EqualSpeeds(track->nextSpeed, track->speed)) {
|
2023-03-08 09:31:58 +00:00
|
|
|
return {
|
|
|
|
.loader = l,
|
|
|
|
.oldSpeed = track->speed,
|
|
|
|
.newSpeed = track->nextSpeed,
|
|
|
|
.fadeStartPosition = track->withSpeed.fadeStartPosition,
|
|
|
|
.position = track->withSpeed.fineTunedPosition,
|
|
|
|
.normalLength = track->state.length,
|
|
|
|
.frequency = track->state.frequency,
|
|
|
|
};
|
2017-01-24 21:24:39 +00:00
|
|
|
} else if (track->loaded) {
|
|
|
|
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
2023-03-08 09:31:58 +00:00
|
|
|
return {};
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2023-03-08 09:31:58 +00:00
|
|
|
return {
|
|
|
|
.loader = l,
|
|
|
|
.oldSpeed = track->speed,
|
|
|
|
.newSpeed = track->nextSpeed,
|
|
|
|
.position = track->withSpeed.fineTunedPosition,
|
|
|
|
.frequency = track->state.frequency,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Mixer::Track::WithSpeed Loaders::rebufferOnSpeedChange(
|
|
|
|
const SetupLoaderResult &setup) {
|
|
|
|
Expects(setup.oldSpeed > 0. && setup.newSpeed > 0.);
|
|
|
|
Expects(setup.loader != nullptr);
|
|
|
|
|
|
|
|
const auto speed = setup.newSpeed;
|
|
|
|
const auto change = setup.oldSpeed / speed;
|
|
|
|
const auto normalPosition = Mixer::Track::SpeedIndependentPosition(
|
|
|
|
setup.position,
|
|
|
|
setup.oldSpeed);
|
|
|
|
const auto newPosition = int64(base::SafeRound(setup.position * change));
|
|
|
|
auto result = Mixer::Track::WithSpeed{
|
|
|
|
.fineTunedPosition = newPosition,
|
|
|
|
.position = newPosition,
|
|
|
|
.length = Mixer::Track::SpeedDependentPosition(
|
|
|
|
setup.normalLength,
|
|
|
|
speed),
|
|
|
|
.fadeStartPosition = int64(
|
|
|
|
base::SafeRound(setup.fadeStartPosition * change)),
|
|
|
|
};
|
|
|
|
const auto l = setup.loader;
|
|
|
|
l->dropFramesTill(normalPosition);
|
|
|
|
const auto normalFrom = l->startReadingQueuedFrames(speed);
|
|
|
|
if (normalFrom < 0) {
|
|
|
|
result.bufferedPosition = newPosition;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.bufferedPosition = Mixer::Track::SpeedDependentPosition(
|
|
|
|
normalFrom,
|
|
|
|
speed);
|
|
|
|
for (auto i = 0; i != Mixer::Track::kBuffersCount; ++i) {
|
|
|
|
auto finished = false;
|
|
|
|
auto accumulated = QByteArray();
|
|
|
|
auto accumulatedCount = int64();
|
|
|
|
const auto sampleSize = l->sampleSize();
|
|
|
|
const auto accumulateTill = l->bytesPerBuffer();
|
|
|
|
while (accumulated.size() < accumulateTill) {
|
|
|
|
const auto result = l->readMore();
|
|
|
|
const auto sampleBytes = v::is<bytes::const_span>(result)
|
|
|
|
? v::get<bytes::const_span>(result)
|
|
|
|
: bytes::const_span();
|
|
|
|
if (!sampleBytes.empty()) {
|
|
|
|
accumulated.append(
|
|
|
|
reinterpret_cast<const char*>(sampleBytes.data()),
|
|
|
|
sampleBytes.size());
|
|
|
|
accumulatedCount += sampleBytes.size() / sampleSize;
|
|
|
|
continue;
|
|
|
|
} else if (result == AudioPlayerLoader::ReadError::Retry) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-12 03:24:53 +00:00
|
|
|
Assert(result == AudioPlayerLoader::ReadError::RetryNotQueued
|
|
|
|
|| result == AudioPlayerLoader::ReadError::EndOfFile);
|
2023-03-08 09:31:58 +00:00
|
|
|
finished = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!accumulated.isEmpty()) {
|
|
|
|
result.samples[i] = accumulatedCount;
|
|
|
|
result.bufferedLength += accumulatedCount;
|
|
|
|
result.buffered[i] = accumulated;
|
|
|
|
}
|
|
|
|
if (finished) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto limit = result.bufferedPosition + result.bufferedLength;
|
|
|
|
if (newPosition > limit) {
|
|
|
|
result.fineTunedPosition = limit;
|
|
|
|
result.position = limit;
|
|
|
|
}
|
|
|
|
if (limit > result.length) {
|
|
|
|
result.length = limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
Mixer::Track *Loaders::checkLoader(AudioMsgId::Type type) {
|
2017-01-19 08:24:43 +00:00
|
|
|
if (!mixer()) return nullptr;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
auto track = mixer()->trackForType(type);
|
|
|
|
auto isGoodId = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
AudioPlayerLoader *l = nullptr;
|
|
|
|
switch (type) {
|
2017-01-24 21:24:39 +00:00
|
|
|
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (track->state.id == _audio); break;
|
|
|
|
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (track->state.id == _song); break;
|
|
|
|
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (track->state.id == _video); break;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
2017-01-24 21:24:39 +00:00
|
|
|
if (!l || !track) return nullptr;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
if (!isGoodId || !track->loading || !l->check(track->file, track->data)) {
|
2016-07-05 17:44:02 +00:00
|
|
|
LOG(("Audio Error: playing changed while loading"));
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
return track;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
void Loaders::onCancel(const AudioMsgId &audio) {
|
2019-03-01 12:22:47 +00:00
|
|
|
Expects(audio.type() != AudioMsgId::Type::Unknown);
|
|
|
|
|
2016-07-05 17:44:02 +00:00
|
|
|
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());
|
2017-01-19 08:24:43 +00:00
|
|
|
if (!mixer()) return;
|
2016-07-05 17:44:02 +00:00
|
|
|
|
2017-01-24 21:24:39 +00:00
|
|
|
for (auto i = 0; i != kTogetherLimit; ++i) {
|
|
|
|
auto track = mixer()->trackForType(audio.type(), i);
|
|
|
|
if (track->state.id == audio) {
|
|
|
|
track->loading = false;
|
2016-07-05 17:44:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 08:24:43 +00:00
|
|
|
|
2019-02-28 21:03:25 +00:00
|
|
|
Loaders::~Loaders() = default;
|
|
|
|
|
2017-01-19 08:24:43 +00:00
|
|
|
} // namespace Player
|
|
|
|
} // namespace Media
|