| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "media/audio/media_audio_track.h"
- #include "media/audio/media_audio_ffmpeg_loader.h"
- #include "media/audio/media_audio.h"
- #include "core/application.h"
- #include "core/core_settings.h"
- #include "core/file_location.h"
- #include <al.h>
- #include <alc.h>
- namespace Media {
- namespace Audio {
- namespace {
- constexpr auto kMaxFileSize = 10 * 1024 * 1024;
- constexpr auto kDetachDeviceTimeout = crl::time(500); // destroy the audio device after 500ms of silence
- constexpr auto kTrackUpdateTimeout = crl::time(100);
- 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);
- return source;
- }
- ALuint CreateBuffer() {
- auto buffer = ALuint(0);
- alGenBuffers(1, &buffer);
- return buffer;
- }
- } // namespace
- Track::Track(not_null<Instance*> instance) : _instance(instance) {
- _instance->registerTrack(this);
- }
- void Track::samplePeakEach(crl::time peakDuration) {
- _peakDurationMs = peakDuration;
- }
- void Track::fillFromData(bytes::vector &&data) {
- FFMpegLoader loader(Core::FileLocation(), QByteArray(), std::move(data));
- auto position = qint64(0);
- if (!loader.open(position)) {
- _failed = true;
- return;
- }
- auto format = loader.format();
- _peakEachPosition = _peakDurationMs ? ((loader.samplesFrequency() * _peakDurationMs) / 1000) : 0;
- const auto samplesCount = (loader.duration() * loader.samplesFrequency()) / 1000;
- const auto peaksCount = _peakEachPosition ? (samplesCount / _peakEachPosition) : 0;
- _peaks.reserve(peaksCount);
- auto peakValue = uint16(0);
- auto peakSamples = 0;
- auto peakEachSample = (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) ? (_peakEachPosition * 2) : _peakEachPosition;
- _peakValueMin = 0x7FFF;
- _peakValueMax = 0;
- auto peakCallback = [this, &peakValue, &peakSamples, peakEachSample](uint16 sample) {
- accumulate_max(peakValue, sample);
- if (++peakSamples >= peakEachSample) {
- peakSamples -= peakEachSample;
- _peaks.push_back(peakValue);
- accumulate_max(_peakValueMax, peakValue);
- accumulate_min(_peakValueMin, peakValue);
- peakValue = 0;
- }
- };
- do {
- using Error = AudioPlayerLoader::ReadError;
- const auto result = loader.readMore();
- Assert(result != Error::Wait && result != Error::RetryNotQueued);
- if (result == Error::Retry) {
- continue;
- } else if (result == Error::EndOfFile) {
- break;
- } else if (result == Error::Other || result == Error::Wait) {
- _failed = true;
- break;
- }
- Assert(v::is<bytes::const_span>(result));
- const auto sampleBytes = v::get<bytes::const_span>(result);
- Assert(!sampleBytes.empty());
- _samplesCount += sampleBytes.size() / loader.sampleSize();
- _samples.insert(_samples.end(), sampleBytes.data(), sampleBytes.data() + sampleBytes.size());
- if (peaksCount) {
- if (format == AL_FORMAT_MONO8 || format == AL_FORMAT_STEREO8) {
- Media::Audio::IterateSamples<uchar>(sampleBytes, peakCallback);
- } else if (format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16) {
- Media::Audio::IterateSamples<int16>(sampleBytes, peakCallback);
- }
- }
- } while (true);
- _alFormat = loader.format();
- _sampleRate = loader.samplesFrequency();
- _lengthMs = loader.duration();
- }
- void Track::fillFromFile(const Core::FileLocation &location) {
- if (location.accessEnable()) {
- fillFromFile(location.name());
- location.accessDisable();
- } else {
- LOG(("Track Error: Could not enable access to file '%1'.").arg(location.name()));
- _failed = true;
- }
- }
- void Track::fillFromFile(const QString &filePath) {
- QFile f(filePath);
- if (f.open(QIODevice::ReadOnly)) {
- auto size = f.size();
- if (size > 0 && size <= kMaxFileSize) {
- auto bytes = bytes::vector(size);
- if (f.read(reinterpret_cast<char*>(bytes.data()), bytes.size()) == bytes.size()) {
- fillFromData(std::move(bytes));
- } else {
- LOG(("Track Error: Could not read %1 bytes from file '%2'.").arg(bytes.size()).arg(filePath));
- _failed = true;
- }
- } else {
- LOG(("Track Error: Bad file '%1' size: %2.").arg(filePath).arg(size));
- _failed = true;
- }
- } else {
- LOG(("Track Error: Could not open file '%1'.").arg(filePath));
- _failed = true;
- }
- }
- void Track::playWithLooping(bool looping) {
- _active = true;
- if (failed() || _samples.empty()) {
- finish();
- return;
- }
- ensureSourceCreated();
- alSourceStop(_alSource);
- _looping = looping;
- alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0);
- alSourcef(_alSource, AL_GAIN, _volume);
- alSourcePlay(_alSource);
- _instance->trackStarted(this);
- }
- void Track::finish() {
- if (_active) {
- _active = false;
- _instance->trackFinished(this);
- }
- _alPosition = 0;
- }
- void Track::ensureSourceCreated() {
- if (alIsSource(_alSource)) {
- return;
- }
- {
- QMutexLocker lock(Player::internal::audioPlayerMutex());
- if (!AttachToDevice()) {
- _failed = true;
- return;
- }
- }
- _alSource = CreateSource();
- _alBuffer = CreateBuffer();
- alBufferData(_alBuffer, _alFormat, _samples.data(), _samples.size(), _sampleRate);
- alSourcei(_alSource, AL_BUFFER, _alBuffer);
- }
- void Track::updateState() {
- if (!isActive() || !alIsSource(_alSource)) {
- return;
- }
- _stateUpdatedAt = crl::now();
- auto state = ALint(0);
- alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
- if (state != AL_PLAYING) {
- finish();
- } else {
- auto currentPosition = ALint(0);
- alGetSourcei(_alSource, AL_SAMPLE_OFFSET, ¤tPosition);
- _alPosition = currentPosition;
- }
- }
- float64 Track::getPeakValue(crl::time when) const {
- if (!isActive() || !_samplesCount || _peaks.empty() || _peakValueMin == _peakValueMax) {
- return 0.;
- }
- auto sampleIndex = (_alPosition + ((when - _stateUpdatedAt) * _sampleRate / 1000));
- while (sampleIndex < 0) {
- sampleIndex += _samplesCount;
- }
- sampleIndex = sampleIndex % _samplesCount;
- auto peakIndex = (sampleIndex / _peakEachPosition) % _peaks.size();
- return (_peaks[peakIndex] - _peakValueMin) / float64(_peakValueMax - _peakValueMin);
- }
- void Track::detachFromDevice() {
- if (alIsSource(_alSource)) {
- updateState();
- alSourceStop(_alSource);
- alSourcei(_alSource, AL_BUFFER, AL_NONE);
- alDeleteBuffers(1, &_alBuffer);
- alDeleteSources(1, &_alSource);
- }
- _alBuffer = 0;
- _alSource = 0;
- }
- void Track::reattachToDevice() {
- if (!isActive() || alIsSource(_alSource)) {
- return;
- }
- ensureSourceCreated();
- alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0);
- alSourcei(_alSource, AL_SAMPLE_OFFSET, static_cast<ALint>(_alPosition));
- alSourcePlay(_alSource);
- }
- Track::~Track() {
- detachFromDevice();
- _instance->unregisterTrack(this);
- }
- Instance::Instance()
- : _playbackDeviceId(
- &Core::App().mediaDevices(),
- Webrtc::DeviceType::Playback,
- Webrtc::DeviceIdOrDefault(
- Core::App().settings().playbackDeviceIdValue()))
- , _captureDeviceId(
- &Core::App().mediaDevices(),
- Webrtc::DeviceType::Capture,
- Webrtc::DeviceIdOrDefault(
- Core::App().settings().captureDeviceIdValue())) {
- _updateTimer.setCallback([this] {
- auto hasActive = false;
- for (auto track : _tracks) {
- track->updateState();
- if (track->isActive()) {
- hasActive = true;
- }
- }
- if (hasActive) {
- Audio::StopDetachIfNotUsedSafe();
- }
- });
- _detachFromDeviceTimer.setCallback([=] {
- _detachFromDeviceForce = false;
- Player::internal::DetachFromDevice(this);
- });
- _playbackDeviceId.changes(
- ) | rpl::start_with_next([=](Webrtc::DeviceResolvedId id) {
- if (Player::internal::DetachIfDeviceChanged(this, id)) {
- _detachFromDeviceForce = false;
- }
- }, _lifetime);
- }
- Webrtc::DeviceResolvedId Instance::playbackDeviceId() const {
- return _playbackDeviceId.threadSafeCurrent();
- }
- Webrtc::DeviceResolvedId Instance::captureDeviceId() const {
- return _captureDeviceId.current();
- }
- std::unique_ptr<Track> Instance::createTrack() {
- return std::make_unique<Track>(this);
- }
- Instance::~Instance() {
- Expects(_tracks.empty());
- }
- void Instance::registerTrack(Track *track) {
- _tracks.insert(track);
- }
- void Instance::unregisterTrack(Track *track) {
- _tracks.erase(track);
- }
- void Instance::trackStarted(Track *track) {
- stopDetachIfNotUsed();
- if (!_updateTimer.isActive()) {
- _updateTimer.callEach(kTrackUpdateTimeout);
- }
- }
- void Instance::trackFinished(Track *track) {
- if (!hasActiveTracks()) {
- _updateTimer.cancel();
- scheduleDetachIfNotUsed();
- }
- }
- void Instance::detachTracks() {
- for (auto track : _tracks) {
- track->detachFromDevice();
- }
- }
- void Instance::reattachTracks() {
- if (!IsAttachedToDevice()) {
- return;
- }
- for (auto track : _tracks) {
- track->reattachToDevice();
- }
- }
- bool Instance::hasActiveTracks() const {
- for (auto track : _tracks) {
- if (track->isActive()) {
- return true;
- }
- }
- return false;
- }
- void Instance::scheduleDetachFromDevice() {
- _detachFromDeviceForce = true;
- scheduleDetachIfNotUsed();
- }
- void Instance::scheduleDetachIfNotUsed() {
- if (!_detachFromDeviceTimer.isActive()) {
- _detachFromDeviceTimer.callOnce(kDetachDeviceTimeout);
- }
- }
- void Instance::stopDetachIfNotUsed() {
- if (!_detachFromDeviceForce) {
- _detachFromDeviceTimer.cancel();
- }
- }
- Instance &Current() {
- return Core::App().audio();
- }
- } // namespace Audio
- } // namespace Media
|