/* 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. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "audio.h" #include #include #include #include namespace { ALCdevice *audioDevice = 0; ALCcontext *audioContext = 0; ALuint notifySource = 0; ALuint notifyBuffer = 0; QMutex voicemsgsMutex; VoiceMessages *voicemsgs = 0; } bool _checkALCError() { ALenum errCode; if ((errCode = alcGetError(audioDevice)) != ALC_NO_ERROR) { LOG(("Audio Error: (alc) %1").arg((const char *)alcGetString(audioDevice, errCode))); return false; } return true; } bool _checkALError() { ALenum errCode; if ((errCode = alGetError()) != AL_NO_ERROR) { LOG(("Audio Error: (al) %1").arg((const char *)alGetString(errCode))); return false; } return true; } void audioInit() { if (audioDevice) return; audioDevice = alcOpenDevice(NULL); if (!audioDevice) { LOG(("Audio Error: default sound device not present.")); return; } ALCint attributes[] = { ALC_STEREO_SOURCES, 8, 0 }; audioContext = alcCreateContext(audioDevice, attributes); alcMakeContextCurrent(audioContext); if (!_checkALCError()) return audioFinish(); 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); alGenSources(1, ¬ifySource); alSourcef(notifySource, AL_PITCH, 1.f); alSourcef(notifySource, AL_GAIN, 1.f); alSource3f(notifySource, AL_POSITION, 0, 0, 0); alSource3f(notifySource, AL_VELOCITY, 0, 0, 0); alSourcei(notifySource, AL_LOOPING, 0); alGenBuffers(1, ¬ifyBuffer); if (!_checkALError()) return audioFinish(); QFile notify(st::newMsgSound); if (!notify.open(QIODevice::ReadOnly)) return audioFinish(); QByteArray blob = notify.readAll(); const char *data = blob.constData(); if (blob.size() < 44) return audioFinish(); if (*((const uint32*)(data + 0)) != 0x46464952) return audioFinish(); // ChunkID - "RIFF" if (*((const uint32*)(data + 4)) != uint32(blob.size() - 8)) return audioFinish(); // ChunkSize if (*((const uint32*)(data + 8)) != 0x45564157) return audioFinish(); // Format - "WAVE" if (*((const uint32*)(data + 12)) != 0x20746d66) return audioFinish(); // Subchunk1ID - "fmt " uint32 subchunk1Size = *((const uint32*)(data + 16)), extra = subchunk1Size - 16; if (subchunk1Size < 16 || (extra && extra < 2)) return audioFinish(); if (*((const uint16*)(data + 20)) != 1) return audioFinish(); // AudioFormat - PCM (1) uint16 numChannels = *((const uint16*)(data + 22)); if (numChannels != 1 && numChannels != 2) return audioFinish(); uint32 sampleRate = *((const uint32*)(data + 24)); uint32 byteRate = *((const uint32*)(data + 28)); uint16 blockAlign = *((const uint16*)(data + 32)); uint16 bitsPerSample = *((const uint16*)(data + 34)); if (bitsPerSample % 8) return audioFinish(); uint16 bytesPerSample = bitsPerSample / 8; if (bytesPerSample != 1 && bytesPerSample != 2) return audioFinish(); if (blockAlign != numChannels * bytesPerSample) return audioFinish(); if (byteRate != sampleRate * blockAlign) return audioFinish(); if (extra) { uint16 extraSize = *((const uint16*)(data + 36)); if (uint32(extraSize + 2) != extra) return audioFinish(); if (uint32(blob.size()) < 44 + extra) return audioFinish(); } if (*((const uint32*)(data + extra + 36)) != 0x61746164) return audioFinish(); // Subchunk2ID - "data" uint32 subchunk2Size = *((const uint32*)(data + extra + 40)); if (subchunk2Size % (numChannels * bytesPerSample)) return audioFinish(); uint32 numSamples = subchunk2Size / (numChannels * bytesPerSample); if (uint32(blob.size()) < 44 + extra + subchunk2Size) return audioFinish(); data += 44 + extra; ALenum format = 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; } if (!format) return audioFinish(); alBufferData(notifyBuffer, format, data, subchunk2Size, sampleRate); alSourcei(notifySource, AL_BUFFER, notifyBuffer); if (!_checkALError()) return audioFinish(); voicemsgs = new VoiceMessages(); } bool audioWorks() { return !!voicemsgs; } void audioPlayNotify() { if (!audioWorks()) return; alSourcePlay(notifySource); } void audioFinish() { if (voicemsgs) { delete voicemsgs; } alSourceStop(notifySource); if (alIsBuffer(notifyBuffer)) { alDeleteBuffers(1, ¬ifyBuffer); notifyBuffer = 0; } if (alIsSource(notifySource)) { alDeleteSources(1, ¬ifySource); notifySource = 0; } if (audioContext) { alcMakeContextCurrent(NULL); alcDestroyContext(audioContext); audioContext = 0; } if (audioDevice) { alcCloseDevice(audioDevice); audioDevice = 0; } } VoiceMessages::VoiceMessages() : _current(0), _fader(new VoiceMessagesFader(&_faderThread)), _loader(new VoiceMessagesLoader(&_loaderThread)) { connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer())); connect(this, SIGNAL(loaderOnStart(AudioData*)), _loader, SLOT(onStart(AudioData*))); connect(this, SIGNAL(loaderOnCancel(AudioData*)), _loader, SLOT(onCancel(AudioData*))); connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit())); connect(&_loaderThread, SIGNAL(started()), _loader, SLOT(onInit())); connect(&_faderThread, SIGNAL(finished()), _fader, SLOT(deleteLater())); connect(&_loaderThread, SIGNAL(finished()), _loader, SLOT(deleteLater())); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); connect(_loader, SIGNAL(error(AudioData*)), this, SLOT(onError(AudioData*))); connect(_fader, SIGNAL(needToPreload(AudioData*)), _loader, SLOT(onLoad(AudioData*))); connect(_fader, SIGNAL(playPositionUpdated(AudioData*)), this, SIGNAL(updated(AudioData*))); connect(_fader, SIGNAL(audioStopped(AudioData*)), this, SIGNAL(stopped(AudioData*))); connect(_fader, SIGNAL(error(AudioData*)), this, SLOT(onError(AudioData*))); _loaderThread.start(); _faderThread.start(); } VoiceMessages::~VoiceMessages() { { QMutexLocker lock(&voicemsgsMutex); voicemsgs = 0; } for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { alSourceStop(_data[i].source); if (alIsBuffer(_data[i].buffers[0])) { alDeleteBuffers(3, _data[i].buffers); for (int32 j = 0; j < 3; ++j) { _data[i].buffers[j] = _data[i].samplesCount[j] = 0; } } if (alIsSource(_data[i].source)) { alDeleteSources(1, &_data[i].source); _data[i].source = 0; } } _faderThread.quit(); _loaderThread.quit(); _faderThread.wait(); _loaderThread.wait(); } void VoiceMessages::onError(AudioData *audio) { emit stopped(audio); } bool VoiceMessages::updateCurrentStarted(int32 pos) { if (pos < 0) { if (alIsSource(_data[_current].source)) { alGetSourcei(_data[_current].source, AL_SAMPLE_OFFSET, &pos); } else { pos = 0; } } if (!_checkALError()) { _data[_current].state = VoiceMessageStopped; onError(_data[_current].audio); return false; } _data[_current].started = _data[_current].position = pos + _data[_current].skipStart; return true; } void VoiceMessages::play(AudioData *audio) { QMutexLocker lock(&voicemsgsMutex); bool startNow = true; if (_data[_current].audio != audio) { switch (_data[_current].state) { case VoiceMessageStarting: case VoiceMessageResuming: case VoiceMessagePlaying: _data[_current].state = VoiceMessageFinishing; updateCurrentStarted(); startNow = false; break; case VoiceMessagePausing: _data[_current].state = VoiceMessageFinishing; startNow = false; break; case VoiceMessagePaused: _data[_current].state = VoiceMessageStopped; break; } if (_data[_current].audio) { emit loaderOnCancel(_data[_current].audio); emit faderOnTimer(); } } int32 index = 0; for (; index < AudioVoiceMsgSimultaneously; ++index) { if (_data[index].audio == audio) { _current = index; break; } } if (index == AudioVoiceMsgSimultaneously && ++_current >= AudioVoiceMsgSimultaneously) { _current -= AudioVoiceMsgSimultaneously; } _data[_current].audio = audio; _data[_current].fname = audio->already(true); _data[_current].data = audio->data; if (_data[_current].fname.isEmpty() && _data[_current].data.isEmpty()) { _data[_current].state = VoiceMessageStopped; onError(audio); } else if (updateCurrentStarted(0)) { _data[_current].state = startNow ? VoiceMessagePlaying : VoiceMessageStarting; _data[_current].loading = true; emit loaderOnStart(audio); } } void VoiceMessages::pauseresume() { QMutexLocker lock(&voicemsgsMutex); switch (_data[_current].state) { case VoiceMessagePausing: case VoiceMessagePaused: if (_data[_current].state == VoiceMessagePaused) { updateCurrentStarted(); } _data[_current].state = VoiceMessageResuming; alSourcePlay(_data[_current].source); break; case VoiceMessageStarting: case VoiceMessageResuming: case VoiceMessagePlaying: _data[_current].state = VoiceMessagePausing; updateCurrentStarted(); break; case VoiceMessageFinishing: _data[_current].state = VoiceMessagePausing; break; } emit faderOnTimer(); } void VoiceMessages::currentState(AudioData **audio, VoiceMessageState *state, int64 *position, int64 *duration) { QMutexLocker lock(&voicemsgsMutex); if (audio) *audio = _data[_current].audio; if (state) *state = _data[_current].state; if (position) *position = _data[_current].position; if (duration) *duration = _data[_current].duration; } VoiceMessages *audioVoice() { return voicemsgs; } VoiceMessagesFader::VoiceMessagesFader(QThread *thread) : _timer(this) { moveToThread(thread); _timer.moveToThread(thread); _timer.setSingleShot(true); connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer())); } void VoiceMessagesFader::onInit() { } void VoiceMessagesFader::onTimer() { bool hasFading = false, hasPlaying = false; QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { VoiceMessages::Msg &m(voice->_data[i]); if (m.state == VoiceMessageStopped || m.state == VoiceMessagePaused || !m.source) continue; bool playing = false, fading = false; ALint pos = 0; ALint state = AL_INITIAL; alGetSourcei(m.source, AL_SAMPLE_OFFSET, &pos); alGetSourcei(m.source, AL_SOURCE_STATE, &state); if (!_checkALError()) { m.state = VoiceMessageStopped; emit error(m.audio); } else { switch (m.state) { case VoiceMessageFinishing: case VoiceMessagePausing: case VoiceMessageStarting: case VoiceMessageResuming: fading = true; break; case VoiceMessagePlaying: playing = true; break; } if (fading && (state == AL_PLAYING || !m.loading)) { if (state != AL_PLAYING) { fading = false; if (m.source) { alSourcef(m.source, AL_GAIN, 1); alSourceStop(m.source); } m.state = VoiceMessageStopped; emit audioStopped(m.audio); } else if (1000 * (pos + m.skipStart - m.started) >= AudioFadeDuration * AudioVoiceMsgFrequency) { fading = false; alSourcef(m.source, AL_GAIN, 1); switch (m.state) { case VoiceMessageFinishing: alSourceStop(m.source); m.state = VoiceMessageStopped; break; case VoiceMessagePausing: alSourcePause(m.source); m.state = VoiceMessagePaused; break; case VoiceMessageStarting: case VoiceMessageResuming: m.state = VoiceMessagePlaying; playing = true; break; } } else { float64 newGain = 1000. * (pos + m.skipStart - m.started) / (AudioFadeDuration * AudioVoiceMsgFrequency); if (m.state == VoiceMessagePausing || m.state == VoiceMessageFinishing) { newGain = 1. - newGain; } if (newGain < 0) { int a = 0, b; b = a; } alSourcef(m.source, AL_GAIN, newGain); LOG(("Now volume is: %1").arg(newGain)); } } else if (playing && (state == AL_PLAYING || !m.loading)) { if (state != AL_PLAYING) { playing = false; if (m.source) { alSourceStop(m.source); alSourcef(m.source, AL_GAIN, 1); } m.state = VoiceMessageStopped; emit audioStopped(m.audio); } } if (pos + m.skipStart - m.position >= AudioCheckPositionDelta) { m.position = pos + m.skipStart; emit playPositionUpdated(m.audio); } if (!m.loading && m.skipEnd > 0 && m.position + AudioPreloadSamples + m.skipEnd > m.duration) { m.loading = true; emit needToPreload(m.audio); } if (playing) hasPlaying = true; if (fading) hasFading = true; } } if (hasFading) { _timer.start(AudioFadeTimeout); } else if (hasPlaying) { _timer.start(AudioCheckPositionTimeout); } } struct VoiceMessagesLoader::Loader { QString fname; QByteArray data; OggOpusFile *file; ogg_int64_t pcm_offset; ogg_int64_t pcm_print_offset; int prev_li; Loader() : file(0), pcm_offset(0), pcm_print_offset(0), prev_li(-1) { } }; VoiceMessagesLoader::VoiceMessagesLoader(QThread *thread) { moveToThread(thread); } VoiceMessagesLoader::~VoiceMessagesLoader() { for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e; ++i) { delete i.value(); } _loaders.clear(); } void VoiceMessagesLoader::onInit() { } void VoiceMessagesLoader::onStart(AudioData *audio) { Loaders::iterator i = _loaders.find(audio); if (i != _loaders.end()) { delete (*i); _loaders.erase(i); } onLoad(audio); } void VoiceMessagesLoader::loadError(Loaders::iterator i) { emit error(i.key()); delete (*i); _loaders.erase(i); } void VoiceMessagesLoader::onLoad(AudioData *audio) { bool started = false; int32 audioindex = -1; Loader *l = 0; Loaders::iterator j = _loaders.end(); { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { VoiceMessages::Msg &m(voice->_data[i]); if (m.audio != audio || !m.loading) continue; audioindex = i; j = _loaders.find(audio); if (j != _loaders.end() && (j.value()->fname != m.fname || j.value()->data.size() != m.data.size())) { delete j.value(); _loaders.erase(j); j = _loaders.end(); } if (j == _loaders.end()) { l = (j = _loaders.insert(audio, new Loader())).value(); l->fname = m.fname; l->data = m.data; int ret; if (m.data.isEmpty()) { l->file = op_open_file(m.fname.toUtf8().constData(), &ret); } else { l->file = op_open_memory((const unsigned char*)m.data.constData(), m.data.size(), &ret); } if (!l->file) { LOG(("Audio Error: op_open_file failed for '%1', data size '%2', error code %3").arg(m.fname).arg(m.data.size()).arg(ret)); m.state = VoiceMessageStopped; return loadError(j); } ogg_int64_t duration = op_pcm_total(l->file, -1); if (duration < 0) { LOG(("Audio Error: op_pcm_total failed to get full duration for '%1', data size '%2', error code %3").arg(m.fname).arg(m.data.size()).arg(duration)); m.state = VoiceMessageStopped; return loadError(j); } m.duration = duration; m.skipStart = 0; m.skipEnd = duration; m.position = 0; m.started = 0; started = true; } else { if (!m.skipEnd) continue; l = j.value(); } break; } } if (j == _loaders.end()) { LOG(("Audio Error: trying to load part of audio, that is not playing at the moment")); emit error(audio); return; } if (started) { l->pcm_offset = op_pcm_tell(l->file); l->pcm_print_offset = l->pcm_offset - AudioVoiceMsgFrequency; } bool finished = false; DEBUG_LOG(("Audio Info: reading buffer for file '%1', data size '%2', current pcm_offset %3").arg(l->fname).arg(l->data.size()).arg(l->pcm_offset)); QByteArray result; int64 samplesAdded = 0; while (result.size() < AudioVoiceMsgBufferSize) { opus_int16 pcm[AudioVoiceMsgFrequency * AudioVoiceMsgChannels]; int ret = op_read_stereo(l->file, pcm, sizeof(pcm) / sizeof(*pcm)); if (ret < 0) { { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (voice) { VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio == audio) { m.state = VoiceMessageStopped; } } } LOG(("Audio Error: op_read_stereo failed, error code %1").arg(ret)); return loadError(j); } int li = op_current_link(l->file); if (li != l->prev_li) { const OpusHead *head = op_head(l->file, li); const OpusTags *tags = op_tags(l->file, li); for (int32 ci = 0; ci < tags->comments; ++ci) { const char *comment = tags->user_comments[ci]; if (opus_tagncompare("METADATA_BLOCK_PICTURE", 22, comment) == 0) { OpusPictureTag pic; int err = opus_picture_tag_parse(&pic, comment); if (err >= 0) { opus_picture_tag_clear(&pic); } } } if (!op_seekable(l->file)) { l->pcm_offset = op_pcm_tell(l->file) - ret; } } if (li != l->prev_li || l->pcm_offset >= l->pcm_print_offset + AudioVoiceMsgFrequency) { l->pcm_print_offset = l->pcm_offset; } l->pcm_offset = op_pcm_tell(l->file); if (!ret) { DEBUG_LOG(("Audio Info: read completed")); finished = true; break; } result.append((const char*)pcm, sizeof(*pcm) * ret * AudioVoiceMsgChannels); l->prev_li = li; samplesAdded += ret; { QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio != audio || !m.loading || m.fname != l->fname || m.data.size() != l->data.size()) { LOG(("Audio Error: playing changed while loading")); m.state = VoiceMessageStopped; return loadError(j); } } } QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; VoiceMessages::Msg &m(voice->_data[audioindex]); if (m.audio != audio || !m.loading || m.fname != l->fname || m.data.size() != l->data.size()) { LOG(("Audio Error: playing changed while loading")); m.state = VoiceMessageStopped; return loadError(j); } if (started) { if (m.source) { alSourceStop(m.source); for (int32 i = 0; i < 3; ++i) { if (m.samplesCount[i]) { alSourceUnqueueBuffers(m.source, 1, m.buffers + i); m.samplesCount[i] = 0; } } m.nextBuffer = 0; } } if (samplesAdded) { if (!m.source) { alGenSources(1, &m.source); alSourcef(m.source, AL_PITCH, 1.f); alSourcef(m.source, AL_GAIN, 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 (!_checkALError()) { m.state = VoiceMessageStopped; return loadError(j); } if (m.samplesCount[m.nextBuffer]) { alSourceUnqueueBuffers(m.source, 1, m.buffers + m.nextBuffer); m.skipStart += m.samplesCount[m.nextBuffer]; } m.samplesCount[m.nextBuffer] = samplesAdded; alBufferData(m.buffers[m.nextBuffer], AL_FORMAT_STEREO16, result.constData(), result.size(), AudioVoiceMsgFrequency); alSourceQueueBuffers(m.source, 1, m.buffers + m.nextBuffer); m.skipEnd -= samplesAdded; m.nextBuffer = (m.nextBuffer + 1) % 3; if (!_checkALError()) { m.state = VoiceMessageStopped; return loadError(j); } } else { finished = true; } if (finished) { m.skipEnd = 0; m.duration = m.skipStart + m.samplesCount[0] + m.samplesCount[1] + m.samplesCount[2]; } m.loading = false; if (m.state == VoiceMessageResuming || m.state == VoiceMessagePlaying || m.state == VoiceMessageStarting) { ALint state = AL_INITIAL; alGetSourcei(m.source, AL_SOURCE_STATE, &state); if (_checkALError()) { if (state != AL_PLAYING) { alSourcePlay(m.source); emit needToCheck(); } } } } void VoiceMessagesLoader::onCancel(AudioData *audio) { Loaders::iterator i = _loaders.find(audio); if (i != _loaders.end()) { delete (*i); _loaders.erase(i); } QMutexLocker lock(&voicemsgsMutex); VoiceMessages *voice = audioVoice(); if (!voice) return; for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { VoiceMessages::Msg &m(voice->_data[i]); if (m.audio == audio) { m.loading = false; } } }