From 616d08255c728be12fa585900f993d620ec95e10 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jul 2016 20:44:02 +0300 Subject: [PATCH] Moved audio to media/media_audio and divided to several modules. Basic video playback with sound support in mediaview added. --- Telegram/SourceFiles/app.cpp | 2 +- Telegram/SourceFiles/config.h | 4 +- Telegram/SourceFiles/core/basic_types.h | 2 +- Telegram/SourceFiles/history.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/layout.cpp | 2 +- Telegram/SourceFiles/localimageloader.cpp | 2 +- Telegram/SourceFiles/mainwidget.cpp | 16 +- .../{audio.cpp => media/media_audio.cpp} | 803 ++---------------- .../{audio.h => media/media_audio.h} | 98 +-- .../media/media_audio_ffmpeg_loader.cpp | 301 +++++++ .../media/media_audio_ffmpeg_loader.h | 102 +++ .../SourceFiles/media/media_audio_loader.cpp | 80 ++ .../SourceFiles/media/media_audio_loader.h | 62 ++ .../SourceFiles/media/media_audio_loaders.cpp | 423 +++++++++ .../SourceFiles/media/media_audio_loaders.h | 85 ++ .../media/media_child_ffmpeg_loader.cpp | 186 ++++ .../media/media_child_ffmpeg_loader.h | 93 ++ .../SourceFiles/media/media_clip_ffmpeg.cpp | 67 +- .../SourceFiles/media/media_clip_ffmpeg.h | 4 + .../SourceFiles/overview/overview_layout.cpp | 2 +- Telegram/SourceFiles/playerwidget.cpp | 2 +- Telegram/SourceFiles/playerwidget.h | 2 +- Telegram/SourceFiles/structs.cpp | 2 +- Telegram/SourceFiles/structs.h | 8 +- Telegram/SourceFiles/ui/twidget.h | 10 +- Telegram/Telegram.pro | 12 +- Telegram/Telegram.vcxproj | 87 +- Telegram/Telegram.vcxproj.filters | 63 +- Telegram/Telegram.xcodeproj/project.pbxproj | 20 +- Telegram/Telegram.xcodeproj/qt_preprocess.mak | 18 +- 31 files changed, 1684 insertions(+), 878 deletions(-) rename Telegram/SourceFiles/{audio.cpp => media/media_audio.cpp} (71%) rename Telegram/SourceFiles/{audio.h => media/media_audio.h} (79%) create mode 100644 Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h create mode 100644 Telegram/SourceFiles/media/media_audio_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_loader.h create mode 100644 Telegram/SourceFiles/media/media_audio_loaders.cpp create mode 100644 Telegram/SourceFiles/media/media_audio_loaders.h create mode 100644 Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp create mode 100644 Telegram/SourceFiles/media/media_child_ffmpeg_loader.h diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 9d4e551658..3c3dadca31 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "lang.h" #include "data/data_abstract_structure.h" #include "history/history_service_layout.h" -#include "audio.h" +#include "media/media_audio.h" #include "application.h" #include "fileuploader.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index c62b700b19..6defe63494 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -107,12 +107,12 @@ enum { AudioFadeDuration = 500, AudioVoiceMsgSkip = 400, // 200ms AudioVoiceMsgFade = 300, // 300ms - AudioPreloadSamples = 5 * 48000, // preload next part if less than 5 seconds remains + AudioPreloadSamples = 2 * 48000, // preload next part if less than 5 seconds remains AudioVoiceMsgFrequency = 48000, // 48 kHz AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo - AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers + AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs) AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index c4cc892e4c..6b748ae3fb 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -35,7 +35,7 @@ T *getPointerAndReset(T *&ptr) { template T createAndSwap(T &value) { - T result; + T result = T(); std::swap(result, value); return result; } diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 61b78c8e86..f0f0dfea3a 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -35,7 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" #include "apiwrap.h" #include "window/top_bar_widget.h" diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index c0c23cef07..8dd576800b 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -40,7 +40,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "passcodewidget.h" #include "mainwindow.h" #include "fileuploader.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" #include "apiwrap.h" #include "window/top_bar_widget.h" diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 2dcd973d7b..99da2c9a75 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -30,7 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" TextParseOptions _textNameOptions = { diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index cec36b9f44..ce407efbe6 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "localimageloader.h" #include "ui/filedialog.h" -#include "audio.h" +#include "media/media_audio.h" #include "boxes/photosendbox.h" #include "media/media_clip_reader.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 10688692c5..558d9f0920 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -47,7 +47,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/downloadpathbox.h" #include "localstorage.h" #include "shortcuts.h" -#include "audio.h" +#include "media/media_audio.h" StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) , _memento(std_::move(memento)) { @@ -1565,12 +1565,14 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } } - if (HistoryItem *item = App::histItemById(audioId.contextId())) { - Ui::repaintHistoryItem(item); - } - if (auto items = InlineBots::Layout::documentItems()) { - for (auto item : items->value(audioId.audio())) { - Ui::repaintInlineItem(item); + if (audioId.type() != AudioMsgId::Type::Video) { + if (auto item = App::histItemById(audioId.contextId())) { + Ui::repaintHistoryItem(item); + } + if (auto items = InlineBots::Layout::documentItems()) { + for (auto item : items->value(audioId.audio())) { + Ui::repaintInlineItem(item); + } } } } diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp similarity index 71% rename from Telegram/SourceFiles/audio.cpp rename to Telegram/SourceFiles/media/media_audio.cpp index dfdfd17f5c..52d424d401 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -19,8 +19,11 @@ 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.h" -#include "audio.h" +#include "media/media_audio_ffmpeg_loader.h" +#include "media/media_child_ffmpeg_loader.h" +#include "media/media_audio_loaders.h" #include #include @@ -29,11 +32,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include extern "C" { -#include -#include -#include -#include - #ifdef Q_OS_MAC #include @@ -274,13 +272,17 @@ void AudioPlayer::AudioMsg::clear() { if (alIsSource(source)) { alSourceStop(source); } - for (int32 i = 0; i < 3; ++i) { + for (int i = 0; i < 3; ++i) { if (samplesCount[i]) { - alSourceUnqueueBuffers(source, 1, buffers + i); + ALuint buffer = 0; + // This cleans some random queued buffer, not exactly the buffers[i]. + alSourceUnqueueBuffers(source, 1, &buffer); samplesCount[i] = 0; } } nextBuffer = 0; + + videoData = nullptr; } AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0), @@ -311,7 +313,7 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { AudioPlayer::~AudioPlayer() { { QMutexLocker lock(&playerMutex); - player = 0; + player = nullptr; } auto clearAudioMsg = [](AudioMsg *msg) { @@ -332,6 +334,8 @@ AudioPlayer::~AudioPlayer() { clearAudioMsg(dataForType(AudioMsgId::Type::Voice, i)); clearAudioMsg(dataForType(AudioMsgId::Type::Song, i)); } + clearAudioMsg(&_videoData); + _faderThread.quit(); _loaderThread.quit(); _faderThread.wait(); @@ -357,6 +361,7 @@ AudioPlayer::AudioMsg *AudioPlayer::dataForType(AudioMsgId::Type type, int index switch (type) { case AudioMsgId::Type::Voice: return &_audioData[index]; case AudioMsgId::Type::Song: return &_songData[index]; + case AudioMsgId::Type::Video: return &_videoData; } return nullptr; } @@ -369,6 +374,7 @@ int *AudioPlayer::currentIndex(AudioMsgId::Type type) { switch (type) { case AudioMsgId::Type::Voice: return &_audioCurrent; case AudioMsgId::Type::Song: return &_songCurrent; + case AudioMsgId::Type::Video: { static int videoIndex = 0; return &videoIndex; } } return nullptr; } @@ -422,7 +428,6 @@ bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { } void AudioPlayer::play(const AudioMsgId &audio, int64 position) { - bool fadedStart = false; auto type = audio.type(); AudioMsgId stopped; { @@ -479,6 +484,40 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { if (stopped) emit updated(stopped); } +void AudioPlayer::playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data) { + t_assert(audio.type() == AudioMsgId::Type::Video); + + auto type = audio.type(); + AudioMsgId stopped; + { + QMutexLocker lock(&playerMutex); + + auto current = dataForType(type); + t_assert(current != nullptr); + + fadedStop(AudioMsgId::Type::Song); + if (current->audio) { + fadedStop(type); + stopped = current->audio; + emit loaderOnCancel(current->audio); + } + emit faderOnTimer(); + current->clear(); + current->audio = audio; + current->videoData = std_::move(data); + _loader->startFromVideo(current->videoData->videoPlayId); + + current->state = AudioPlayerPlaying; + current->loading = true; + emit loaderOnStart(audio, position); + } + if (stopped) emit updated(stopped); +} + +void AudioPlayer::feedFromVideo(VideoSoundPart &&part) { + _loader->feedFromVideo(std_::move(part)); +} + bool AudioPlayer::checkCurrentALError(AudioMsgId::Type type) { if (_checkALError()) return true; @@ -633,6 +672,8 @@ void AudioPlayer::stopAndClear() { clearAndCancel(AudioMsgId::Type::Voice, index); clearAndCancel(AudioMsgId::Type::Song, index); } + _videoData.clear(); + _loader->stopFromVideo(); } } @@ -669,6 +710,27 @@ void AudioPlayer::resumeDevice() { _fader->resumeDevice(); } + +namespace internal { + +QMutex *audioPlayerMutex() { + return &playerMutex; +} + +float64 audioSuppressGain() { + return suppressAllGain; +} + +float64 audioSuppressSongGain() { + return suppressSongGain; +} + +bool audioCheckError() { + return _checkALError(); +} + +} // namespace internal + AudioCapture::AudioCapture() : _capture(new AudioCaptureInner(&_captureThread)) { connect(this, SIGNAL(captureOnStart()), _capture, SLOT(onStart())); connect(this, SIGNAL(captureOnStop(bool)), _capture, SLOT(onStop(bool))); @@ -699,7 +761,7 @@ bool AudioCapture::check() { } AudioCapture::~AudioCapture() { - capture = 0; + capture = nullptr; _captureThread.quit(); _captureThread.wait(); } @@ -789,6 +851,7 @@ void AudioPlayerFader::onTimer() { updatePlayback(AudioMsgId::Type::Voice, i, suppressAllGain, suppressAudioChanged); updatePlayback(AudioMsgId::Type::Song, i, suppressGainForMusic, suppressGainForMusicChanged); } + updatePlayback(AudioMsgId::Type::Video, 0, suppressGainForMusic, suppressGainForMusicChanged); _songVolumeChanged = false; @@ -972,710 +1035,6 @@ void AudioPlayerFader::resumeDevice() { } } -class AudioPlayerLoader { -public: - AudioPlayerLoader(const FileLocation &file, const QByteArray &data) : file(file), access(false), data(data), dataPos(0) { - } - virtual ~AudioPlayerLoader() { - if (access) { - file.accessDisable(); - access = false; - } - } - - bool check(const FileLocation &file, const QByteArray &data) { - return this->file == file && this->data.size() == data.size(); - } - - virtual bool open(qint64 position = 0) = 0; - virtual int64 duration() = 0; - virtual int32 frequency() = 0; - virtual int32 format() = 0; - virtual int readMore(QByteArray &result, int64 &samplesAdded) = 0; // < 0 - error, 0 - nothing read, > 0 - read something - -protected: - - FileLocation file; - bool access; - QByteArray data; - - QFile f; - int32 dataPos; - - bool openFile() { - if (data.isEmpty()) { - if (f.isOpen()) f.close(); - if (!access) { - if (!file.accessEnable()) { - LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); - return false; - } - access = true; - } - f.setFileName(file.name()); - if (!f.open(QIODevice::ReadOnly)) { - LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); - return false; - } - } - dataPos = 0; - return true; - } - -}; - -class AbstractFFMpegLoader : public AudioPlayerLoader { -public: - - AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) - , freq(AudioVoiceMsgFrequency) - , len(0) - , ioBuffer(0) - , ioContext(0) - , fmtContext(0) - , codec(0) - , streamId(0) - , _opened(false) { - } - - bool open(qint64 position = 0) { - if (!AudioPlayerLoader::openFile()) { - return false; - } - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - - ioBuffer = (uchar*)av_malloc(AVBlockSize); - if (data.isEmpty()) { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); - } else { - ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); - } - fmtContext = avformat_alloc_context(); - if (!fmtContext) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); - return false; - } - fmtContext->pb = ioContext; - - if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { - ioBuffer = 0; - - DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - _opened = true; - - if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { - DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); - if (streamId < 0) { - LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); - return false; - } - - freq = fmtContext->streams[streamId]->codec->sample_rate; - if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { - len = (fmtContext->duration * freq) / AV_TIME_BASE; - } else { - len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; - } - - return true; - } - - int64 duration() { - return len; - } - - int32 frequency() { - return freq; - } - - ~AbstractFFMpegLoader() { - if (ioContext) av_free(ioContext); - if (_opened) { - avformat_close_input(&fmtContext); - } else if (ioBuffer) { - av_free(ioBuffer); - } - if (fmtContext) avformat_free_context(fmtContext); - } - -protected: - - int32 freq; - int64 len; - - uchar *ioBuffer; - AVIOContext *ioContext; - AVFormatContext *fmtContext; - AVCodec *codec; - int32 streamId; - - bool _opened; - -private: - - static int _read_data(void *opaque, uint8_t *buf, int buf_size) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); - if (nbytes <= 0) { - return 0; - } - - memcpy(buf, l->data.constData() + l->dataPos, nbytes); - l->dataPos += nbytes; - return nbytes; - } - - static int64_t _seek_data(void *opaque, int64_t offset, int whence) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - int32 newPos = -1; - switch (whence) { - case SEEK_SET: newPos = offset; break; - case SEEK_CUR: newPos = l->dataPos + offset; break; - case SEEK_END: newPos = l->data.size() + offset; break; - } - if (newPos < 0 || newPos > l->data.size()) { - return -1; - } - l->dataPos = newPos; - return l->dataPos; - } - - static int _read_file(void *opaque, uint8_t *buf, int buf_size) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - return int(l->f.read((char*)(buf), buf_size)); - } - - static int64_t _seek_file(void *opaque, int64_t offset, int whence) { - AbstractFFMpegLoader *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; - case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; - case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; - } - return -1; - } -}; - -static const AVSampleFormat _toFormat = AV_SAMPLE_FMT_S16; -static const int64_t _toChannelLayout = AV_CH_LAYOUT_STEREO; -static const int32 _toChannels = 2; -class FFMpegLoader : public AbstractFFMpegLoader { -public: - - FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) - , sampleSize(2 * sizeof(uint16)) - , fmt(AL_FORMAT_STEREO16) - , srcRate(AudioVoiceMsgFrequency) - , dstRate(AudioVoiceMsgFrequency) - , maxResampleSamples(1024) - , dstSamplesData(0) - , codecContext(0) - , frame(0) - , swrContext(0) { - frame = av_frame_alloc(); - } - - bool open(qint64 position = 0) { - if (!AbstractFFMpegLoader::open(position)) { - return false; - } - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - - // Get a pointer to the codec context for the audio stream - av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { - LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - codecContext = fmtContext->streams[streamId]->codec; - - uint64_t layout = codecContext->channel_layout; - inputFormat = codecContext->sample_fmt; - switch (layout) { - case AV_CH_LAYOUT_MONO: - switch (inputFormat) { - case AV_SAMPLE_FMT_U8: - case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break; - case AV_SAMPLE_FMT_S16: - case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break; - default: - sampleSize = -1; // convert needed - break; - } - break; - case AV_CH_LAYOUT_STEREO: - switch (inputFormat) { - case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break; - case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break; - default: - sampleSize = -1; // convert needed - break; - } - break; - default: - sampleSize = -1; // convert needed - break; - } - if (freq != 44100 && freq != 48000) { - sampleSize = -1; // convert needed - } - - if (sampleSize < 0) { - swrContext = swr_alloc(); - if (!swrContext) { - LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); - return false; - } - int64_t src_ch_layout = layout, dst_ch_layout = _toChannelLayout; - srcRate = freq; - AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = _toFormat; - dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq; - - av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0); - av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0); - av_opt_set_sample_fmt(swrContext, "in_sample_fmt", src_sample_fmt, 0); - av_opt_set_int(swrContext, "out_channel_layout", dst_ch_layout, 0); - av_opt_set_int(swrContext, "out_sample_rate", dstRate, 0); - av_opt_set_sample_fmt(swrContext, "out_sample_fmt", dst_sample_fmt, 0); - - if ((res = swr_init(swrContext)) < 0) { - LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - sampleSize = _toChannels * sizeof(short); - freq = dstRate; - len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP); - fmt = AL_FORMAT_STEREO16; - - maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP); - if ((res = av_samples_alloc_array_and_samples(&dstSamplesData, 0, _toChannels, maxResampleSamples, _toFormat, 0)) < 0) { - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - if (position) { - int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num); - if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) { - if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) { - } - } - //if (dstSamplesData) { - // position = qRound(srcRate * (position / float64(dstRate))); - //} - } - - return true; - } - - int32 format() { - return fmt; - } - - int readMore(QByteArray &result, int64 &samplesAdded) { - int res; - if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { - if (res != AVERROR_EOF) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - } - return -1; - } - if (avpkt.stream_index == streamId) { - av_frame_unref(frame); - int got_frame = 0; - if ((res = avcodec_decode_audio4(codecContext, frame, &got_frame, &avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - if (res == AVERROR_INVALIDDATA) return 0; // try to skip bad packet - return -1; - } - - if (got_frame) { - if (dstSamplesData) { // convert needed - int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); - if (dstSamples > maxResampleSamples) { - maxResampleSamples = dstSamples; - av_free(dstSamplesData[0]); - - if ((res = av_samples_alloc(dstSamplesData, 0, _toChannels, maxResampleSamples, _toFormat, 1)) < 0) { - dstSamplesData[0] = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return -1; - } - } - if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - av_packet_unref(&avpkt); - return -1; - } - int32 resultLen = av_samples_get_buffer_size(0, _toChannels, res, _toFormat, 1); - result.append((const char*)dstSamplesData[0], resultLen); - samplesAdded += resultLen / sampleSize; - } else { - result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); - samplesAdded += frame->nb_samples; - } - } - } - av_packet_unref(&avpkt); - return 1; - } - - ~FFMpegLoader() { - if (codecContext) avcodec_close(codecContext); - if (swrContext) swr_free(&swrContext); - if (dstSamplesData) { - if (dstSamplesData[0]) { - av_freep(&dstSamplesData[0]); - } - av_freep(&dstSamplesData); - } - av_frame_free(&frame); - } - -protected: - int32 sampleSize; - -private: - - int32 fmt; - int32 srcRate, dstRate, maxResampleSamples; - uint8_t **dstSamplesData; - - AVCodecContext *codecContext; - AVPacket avpkt; - AVSampleFormat inputFormat; - AVFrame *frame; - - SwrContext *swrContext; - -}; - -AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) { - moveToThread(thread); -} - -AudioPlayerLoaders::~AudioPlayerLoaders() { - delete _audioLoader; - delete _songLoader; -} - -void AudioPlayerLoaders::onInit() { -} - -void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) { - auto type = audio.type(); - clear(type); - { - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - auto data = voice->dataForType(type); - if (!data) return; - - data->loading = true; - } - - loadData(audio, position); -} - -void AudioPlayerLoaders::clear(AudioMsgId::Type type) { - switch (type) { - case AudioMsgId::Type::Voice: clearAudio(); break; - case AudioMsgId::Type::Song: clearSong(); break; - } -} - -void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) { - m->state = state; - m->position = 0; -} - -void AudioPlayerLoaders::emitError(AudioMsgId::Type type) { - switch (type) { - case AudioMsgId::Type::Voice: emit error(clearAudio()); break; - case AudioMsgId::Type::Song: emit error(clearSong()); break; - } -} - -AudioMsgId AudioPlayerLoaders::clearAudio() { - AudioMsgId current = _audio; - _audio = AudioMsgId(); - delete _audioLoader; - _audioLoader = nullptr; - return current; -} - -AudioMsgId AudioPlayerLoaders::clearSong() { - AudioMsgId current = _song; - _song = AudioMsgId(); - delete _songLoader; - _songLoader = nullptr; - return current; -} - -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), finished = false, errAtStart = started; - - QByteArray result; - int64 samplesAdded = 0, frequency = l->frequency(), format = l->format(); - while (result.size() < AudioVoiceMsgBufferSize) { - int res = l->readMore(result, samplesAdded); - if (res < 0) { - if (errAtStart) { - { - QMutexLocker lock(&playerMutex); - AudioPlayer::AudioMsg *m = checkLoader(type); - if (m) m->state = AudioPlayerStoppedAtStart; - } - emitError(type); - return; - } - finished = true; - break; - } - if (res > 0) errAtStart = false; - - QMutexLocker lock(&playerMutex); - if (!checkLoader(type)) { - clear(type); - return; - } - } - - QMutexLocker lock(&playerMutex); - 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]) { - alSourceUnqueueBuffers(m->source, 1, m->buffers + i); - m->samplesCount[i] = 0; - } - } - m->nextBuffer = 0; - } - m->skipStart = position; - m->skipEnd = m->duration - position; - m->position = 0; - m->started = 0; - } - if (samplesAdded) { - 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 (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - - 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], format, result.constData(), result.size(), frequency); - alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer); - m->skipEnd -= samplesAdded; - - m->nextBuffer = (m->nextBuffer + 1) % 3; - - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - } else { - 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 (_checkALError()) { - if (state != AL_PLAYING) { - audioPlayer()->resumeDevice(); - - switch (type) { - case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, suppressAllGain); break; - case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, suppressSongGain * cSongVolume()); break; - } - if (!_checkALError()) { - setStoppedState(m, AudioPlayerStoppedAtError); - emitError(type); - return; - } - - alSourcePlay(m->source); - if (!_checkALError()) { - 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(&playerMutex); - 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; isGoodId = (_audio == audio); break; - case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (_song == audio); break; - } - - if (*l && (!isGoodId || !(*l)->check(data->file, data->data))) { - delete *l; - *l = nullptr; - switch (audio.type()) { - case AudioMsgId::Type::Voice: _audio = AudioMsgId(); break; - case AudioMsgId::Type::Song: _song = AudioMsgId(); break; - } - } - - if (!*l) { - switch (audio.type()) { - case AudioMsgId::Type::Voice: _audio = audio; break; - case AudioMsgId::Type::Song: _song = audio; break; - } - - *l = new FFMpegLoader(data->file, data->data); - - 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; isGoodId = (data->audio == _audio); break; - case AudioMsgId::Type::Song: l = &_songLoader; isGoodId = (data->audio == _song); 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; - } - - QMutexLocker lock(&playerMutex); - 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; - } - } -} - struct AudioCapturePrivate { AudioCapturePrivate() : device(0) @@ -2232,7 +1591,7 @@ public: FFMpegAttributesReader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { } - bool open(qint64 position = 0) { + bool open(qint64 position = 0) override { if (!AbstractFFMpegLoader::open()) { return false; } @@ -2287,7 +1646,7 @@ public: //} } - int32 format() { + int32 format() override { return 0; } @@ -2311,9 +1670,9 @@ public: return _coverFormat; } - int readMore(QByteArray &result, int64 &samplesAdded) { + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override { DEBUG_LOG(("Audio Read Error: should not call this")); - return -1; + return ReadResult::Error; } ~FFMpegAttributesReader() { @@ -2347,7 +1706,7 @@ public: FFMpegWaveformCounter(const FileLocation &file, const QByteArray &data) : FFMpegLoader(file, data) { } - bool open(qint64 position = 0) { + bool open(qint64 position = 0) override { if (!FFMpegLoader::open(position)) { return false; } @@ -2368,8 +1727,8 @@ public: buffer.resize(0); int64 samples = 0; - int res = readMore(buffer, samples); - if (res < 0) { + auto res = readMore(buffer, samples); + if (res == ReadResult::Error) { break; } if (buffer.isEmpty()) { diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/media/media_audio.h similarity index 79% rename from Telegram/SourceFiles/audio.h rename to Telegram/SourceFiles/media/media_audio.h index e0ff14e5a0..27a8e15a61 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -28,24 +28,27 @@ void audioPlayNotify(); void audioFinish(); enum AudioPlayerState { - AudioPlayerStopped = 0x01, - AudioPlayerStoppedAtEnd = 0x02, + AudioPlayerStopped = 0x01, + AudioPlayerStoppedAtEnd = 0x02, AudioPlayerStoppedAtError = 0x03, AudioPlayerStoppedAtStart = 0x04, - AudioPlayerStoppedMask = 0x07, + AudioPlayerStoppedMask = 0x07, - AudioPlayerStarting = 0x08, - AudioPlayerPlaying = 0x10, - AudioPlayerFinishing = 0x18, - AudioPlayerPausing = 0x20, - AudioPlayerPaused = 0x28, - AudioPlayerPausedAtEnd = 0x30, - AudioPlayerResuming = 0x38, + AudioPlayerStarting = 0x08, + AudioPlayerPlaying = 0x10, + AudioPlayerFinishing = 0x18, + AudioPlayerPausing = 0x20, + AudioPlayerPaused = 0x28, + AudioPlayerPausedAtEnd = 0x30, + AudioPlayerResuming = 0x38, }; class AudioPlayerFader; class AudioPlayerLoaders; +struct VideoSoundData; +struct VideoSoundPart; + class AudioPlayer : public QObject { Q_OBJECT @@ -58,6 +61,10 @@ public: void seek(int64 position); // type == AudioMsgId::Type::Song void stop(AudioMsgId::Type type); + // Video player audio stream interface. + void playFromVideo(const AudioMsgId &audio, int64 position, std_::unique_ptr &&data); + void feedFromVideo(VideoSoundPart &&part); + void stopAndClear(); void currentState(AudioMsgId *audio, AudioMsgId::Type type, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); @@ -115,6 +122,8 @@ private: int32 nextBuffer = 0; uint32 buffers[3] = { 0 }; int64 samplesCount[3] = { 0 }; + + std_::unique_ptr videoData; }; void currentState(AudioMsg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); @@ -131,6 +140,8 @@ private: int _songCurrent; AudioMsg _songData[AudioSimultaneousLimit]; + AudioMsg _videoData; + QMutex _mutex; friend class AudioPlayerFader; @@ -142,6 +153,15 @@ private: }; +namespace internal { + +QMutex *audioPlayerMutex(); +float64 audioSuppressGain(); +float64 audioSuppressSongGain(); +bool audioCheckError(); + +} // namespace internal + class AudioCaptureInner; class AudioCapture : public QObject { @@ -196,7 +216,7 @@ signals: void stopPauseDevice(); -public slots: + public slots: void onInit(); void onTimer(); @@ -211,10 +231,10 @@ public slots: private: enum { - EmitError = 0x01, - EmitStopped = 0x02, + EmitError = 0x01, + EmitStopped = 0x02, EmitPositionUpdated = 0x04, - EmitNeedToPreload = 0x08, + EmitNeedToPreload = 0x08, }; int32 updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); @@ -229,54 +249,6 @@ private: }; -class AudioPlayerLoader; -class AudioPlayerLoaders : public QObject { - Q_OBJECT - -public: - - AudioPlayerLoaders(QThread *thread); - ~AudioPlayerLoaders(); - -signals: - - void error(const AudioMsgId &audio); - void needToCheck(); - -public slots: - - void onInit(); - - void onStart(const AudioMsgId &audio, qint64 position); - void onLoad(const AudioMsgId &audio); - void onCancel(const AudioMsgId &audio); - -private: - - AudioMsgId _audio; - AudioPlayerLoader *_audioLoader; - - AudioMsgId _song; - AudioPlayerLoader *_songLoader; - - void emitError(AudioMsgId::Type type); - void clear(AudioMsgId::Type type); - void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); - AudioMsgId clearAudio(); - AudioMsgId clearSong(); - - enum SetupError { - SetupErrorAtStart = 0, - SetupErrorNotPlaying = 1, - SetupErrorLoadedFull = 2, - SetupNoErrorStarted = 3, - }; - void loadData(const AudioMsgId &audio, qint64 position); - AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); - AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); - -}; - struct AudioCapturePrivate; class AudioCaptureInner : public QObject { @@ -293,7 +265,7 @@ signals: void update(quint16 level, qint32 samples); void done(QByteArray data, VoiceWaveform waveform, qint32 samples); -public slots: + public slots: void onInit(); void onStart(); diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp new file mode 100644 index 0000000000..ab7a141562 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp @@ -0,0 +1,301 @@ +/* +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_ffmpeg_loader.h" + +constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; +constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; +constexpr int32 AudioToChannels = 2; + +bool AbstractFFMpegLoader::open(qint64 position) { + if (!AudioPlayerLoader::openFile()) { + return false; + } + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + ioBuffer = (uchar*)av_malloc(AVBlockSize); + if (data.isEmpty()) { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file); + } else { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data); + } + fmtContext = avformat_alloc_context(); + if (!fmtContext) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + fmtContext->pb = ioContext; + + if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { + ioBuffer = 0; + + DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); + if (streamId < 0) { + LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); + return false; + } + + freq = fmtContext->streams[streamId]->codec->sample_rate; + if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) { + len = (fmtContext->duration * freq) / AV_TIME_BASE; + } else { + len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den; + } + + return true; +} + +AbstractFFMpegLoader::~AbstractFFMpegLoader() { + if (ioContext) av_free(ioContext); + if (_opened) { + avformat_close_input(&fmtContext); + } else if (ioBuffer) { + av_free(ioBuffer); + } + if (fmtContext) avformat_free_context(fmtContext); +} + +int AbstractFFMpegLoader::_read_data(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); + if (nbytes <= 0) { + return 0; + } + + memcpy(buf, l->data.constData() + l->dataPos, nbytes); + l->dataPos += nbytes; + return nbytes; +} + +int64_t AbstractFFMpegLoader::_seek_data(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + int32 newPos = -1; + switch (whence) { + case SEEK_SET: newPos = offset; break; + case SEEK_CUR: newPos = l->dataPos + offset; break; + case SEEK_END: newPos = l->data.size() + offset; break; + } + if (newPos < 0 || newPos > l->data.size()) { + return -1; + } + l->dataPos = newPos; + return l->dataPos; +} + +int AbstractFFMpegLoader::_read_file(void *opaque, uint8_t *buf, int buf_size) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + return int(l->f.read((char*)(buf), buf_size)); +} + +int64_t AbstractFFMpegLoader::_seek_file(void *opaque, int64_t offset, int whence) { + AbstractFFMpegLoader *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; + case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; + case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; + } + return -1; +} + +FFMpegLoader::FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) { + frame = av_frame_alloc(); +} + +bool FFMpegLoader::open(qint64 position) { + if (!AbstractFFMpegLoader::open(position)) { + return false; + } + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + // Get a pointer to the codec context for the audio stream + av_opt_set_int(fmtContext->streams[streamId]->codec, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(fmtContext->streams[streamId]->codec, codec, 0)) < 0) { + LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + codecContext = fmtContext->streams[streamId]->codec; + + uint64_t layout = codecContext->channel_layout; + inputFormat = codecContext->sample_fmt; + switch (layout) { + case AV_CH_LAYOUT_MONO: + switch (inputFormat) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break; + default: + sampleSize = -1; // convert needed + break; + } + break; + case AV_CH_LAYOUT_STEREO: + switch (inputFormat) { + case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break; + case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break; + default: + sampleSize = -1; // convert needed + break; + } + break; + default: + sampleSize = -1; // convert needed + break; + } + if (freq != 44100 && freq != 48000) { + sampleSize = -1; // convert needed + } + + if (sampleSize < 0) { + swrContext = swr_alloc(); + if (!swrContext) { + LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout; + srcRate = freq; + AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = AudioToFormat; + dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq; + + av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0); + av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0); + av_opt_set_sample_fmt(swrContext, "in_sample_fmt", src_sample_fmt, 0); + av_opt_set_int(swrContext, "out_channel_layout", dst_ch_layout, 0); + av_opt_set_int(swrContext, "out_sample_rate", dstRate, 0); + av_opt_set_sample_fmt(swrContext, "out_sample_fmt", dst_sample_fmt, 0); + + if ((res = swr_init(swrContext)) < 0) { + LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + sampleSize = AudioToChannels * sizeof(short); + freq = dstRate; + len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP); + fmt = AL_FORMAT_STEREO16; + + maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP); + if ((res = av_samples_alloc_array_and_samples(&dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 0)) < 0) { + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + if (position) { + int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num); + if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) { + if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) { + } + } + //if (dstSamplesData) { + // position = qRound(srcRate * (position / float64(dstRate))); + //} + } + + return true; +} + +AudioPlayerLoader::ReadResult FFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { + int res; + if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { + if (res != AVERROR_EOF) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + } + return ReadResult::Error; + } + if (avpkt.stream_index == streamId) { + av_frame_unref(frame); + int got_frame = 0; + if ((res = avcodec_decode_audio4(codecContext, frame, &got_frame, &avpkt)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + if (res == AVERROR_INVALIDDATA) { + return ReadResult::NotYet; // try to skip bad packet + } + return ReadResult::Error; + } + + if (got_frame) { + if (dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP); + if (dstSamples > maxResampleSamples) { + maxResampleSamples = dstSamples; + av_free(dstSamplesData[0]); + + if ((res = av_samples_alloc(dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 1)) < 0) { + dstSamplesData[0] = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + return ReadResult::Error; + } + } + if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&avpkt); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)dstSamplesData[0], resultLen); + samplesAdded += resultLen / sampleSize; + } else { + result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize); + samplesAdded += frame->nb_samples; + } + } + } + av_packet_unref(&avpkt); + return ReadResult::Ok; +} + +FFMpegLoader::~FFMpegLoader() { + if (codecContext) avcodec_close(codecContext); + if (swrContext) swr_free(&swrContext); + if (dstSamplesData) { + if (dstSamplesData[0]) { + av_freep(&dstSamplesData[0]); + } + av_freep(&dstSamplesData); + } + av_frame_free(&frame); +} diff --git a/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h new file mode 100644 index 0000000000..02ff3d3a3c --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h @@ -0,0 +1,102 @@ +/* +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 +*/ +#pragma once + +#include "media/media_audio_loader.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +#include + +class AbstractFFMpegLoader : public AudioPlayerLoader { +public: + AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) { + } + + bool open(qint64 position = 0) override; + + int64 duration() override { + return len; + } + + int32 frequency() override { + return freq; + } + + ~AbstractFFMpegLoader(); + +protected: + int32 freq = AudioVoiceMsgFrequency; + int64 len = 0; + + uchar *ioBuffer = nullptr; + AVIOContext *ioContext = nullptr; + AVFormatContext *fmtContext = nullptr; + AVCodec *codec = nullptr; + int32 streamId = 0; + + bool _opened = false; + +private: + static int _read_data(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek_data(void *opaque, int64_t offset, int whence); + static int _read_file(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek_file(void *opaque, int64_t offset, int whence); + +}; + +class FFMpegLoader : public AbstractFFMpegLoader { +public: + FFMpegLoader(const FileLocation &file, const QByteArray &data); + + bool open(qint64 position = 0) override; + + int32 format() override { + return fmt; + } + + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; + + ~FFMpegLoader(); + +protected: + int32 sampleSize = 2 * sizeof(uint16); + +private: + int32 fmt = AL_FORMAT_STEREO16; + int32 srcRate = AudioVoiceMsgFrequency; + int32 dstRate = AudioVoiceMsgFrequency; + int32 maxResampleSamples = 1024; + uint8_t **dstSamplesData = nullptr; + + AVCodecContext *codecContext = nullptr; + AVPacket avpkt; + AVSampleFormat inputFormat; + AVFrame *frame = nullptr; + + SwrContext *swrContext = nullptr; + +}; diff --git a/Telegram/SourceFiles/media/media_audio_loader.cpp b/Telegram/SourceFiles/media/media_audio_loader.cpp new file mode 100644 index 0000000000..1f1866bf23 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loader.cpp @@ -0,0 +1,80 @@ +/* +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_loader.h" + +AudioPlayerLoader::AudioPlayerLoader(const FileLocation &file, const QByteArray &data) +: file(file) +, data(data) { +} + +AudioPlayerLoader::~AudioPlayerLoader() { + if (access) { + file.accessDisable(); + access = false; + } +} + +bool AudioPlayerLoader::check(const FileLocation &file, const QByteArray &data) { + return this->file == file && this->data.size() == data.size(); +} + +void AudioPlayerLoader::saveDecodedSamples(QByteArray *samples, int64 *samplesCount) { + t_assert(_savedSamplesCount == 0); + t_assert(_savedSamples.isEmpty()); + t_assert(!_holdsSavedSamples); + samples->swap(_savedSamples); + std::swap(*samplesCount, _savedSamplesCount); + _holdsSavedSamples = true; +} + +void AudioPlayerLoader::takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount) { + t_assert(*samplesCount == 0); + t_assert(samples->isEmpty()); + t_assert(_holdsSavedSamples); + samples->swap(_savedSamples); + std::swap(*samplesCount, _savedSamplesCount); + _holdsSavedSamples = false; +} + +bool AudioPlayerLoader::holdsSavedDecodedSamples() const { + return _holdsSavedSamples; +} + +bool AudioPlayerLoader::openFile() { + if (data.isEmpty()) { + if (f.isOpen()) f.close(); + if (!access) { + if (!file.accessEnable()) { + LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); + return false; + } + access = true; + } + f.setFileName(file.name()); + if (!f.open(QIODevice::ReadOnly)) { + LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString())); + return false; + } + } + dataPos = 0; + return true; +} diff --git a/Telegram/SourceFiles/media/media_audio_loader.h b/Telegram/SourceFiles/media/media_audio_loader.h new file mode 100644 index 0000000000..42d15a8e19 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loader.h @@ -0,0 +1,62 @@ +/* +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 +*/ +#pragma once + +class AudioPlayerLoader { +public: + AudioPlayerLoader(const FileLocation &file, const QByteArray &data); + virtual ~AudioPlayerLoader(); + + virtual bool check(const FileLocation &file, const QByteArray &data); + + virtual bool open(qint64 position = 0) = 0; + virtual int64 duration() = 0; + virtual int32 frequency() = 0; + virtual int32 format() = 0; + + enum class ReadResult { + Error, + NotYet, + Ok, + Wait, + }; + virtual ReadResult readMore(QByteArray &samples, int64 &samplesCount) = 0; + + void saveDecodedSamples(QByteArray *samples, int64 *samplesCount); + void takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount); + bool holdsSavedDecodedSamples() const; + +protected: + FileLocation file; + bool access = false; + QByteArray data; + + QFile f; + int32 dataPos = 0; + + bool openFile(); + +private: + QByteArray _savedSamples; + int64 _savedSamplesCount = 0; + bool _holdsSavedSamples = false; + +}; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp new file mode 100644 index 0000000000..c18e2e6e82 --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -0,0 +1,423 @@ +/* +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 *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(std_::move(data->videoData)); + l = _videoLoader.get(); + } else { + *loader = std_::make_unique(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; + } + } +} diff --git a/Telegram/SourceFiles/media/media_audio_loaders.h b/Telegram/SourceFiles/media/media_audio_loaders.h new file mode 100644 index 0000000000..f1893885aa --- /dev/null +++ b/Telegram/SourceFiles/media/media_audio_loaders.h @@ -0,0 +1,85 @@ +/* +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 +*/ +#pragma once + +#include "media/media_child_ffmpeg_loader.h" +#include "media/media_audio.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +class AudioPlayerLoader; +class ChildFFMpegLoader; +class AudioPlayerLoaders : public QObject { + Q_OBJECT + +public: + AudioPlayerLoaders(QThread *thread); + void startFromVideo(uint64 videoPlayId); + void stopFromVideo(); + void feedFromVideo(VideoSoundPart &&part); + ~AudioPlayerLoaders(); + +signals: + void error(const AudioMsgId &audio); + void needToCheck(); + +public slots: + void onInit(); + + void onStart(const AudioMsgId &audio, qint64 position); + void onLoad(const AudioMsgId &audio); + void onCancel(const AudioMsgId &audio); + + void onVideoSoundAdded(); + +private: + void clearFromVideoQueue(); + + AudioMsgId _audio, _song, _video; + std_::unique_ptr _audioLoader; + std_::unique_ptr _songLoader; + std_::unique_ptr _videoLoader; + + QMutex _fromVideoMutex; + uint64 _fromVideoPlayId; + QQueue _fromVideoQueue; + SingleDelayedCall _fromVideoNotify; + + void emitError(AudioMsgId::Type type); + AudioMsgId clear(AudioMsgId::Type type); + void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped); + + enum SetupError { + SetupErrorAtStart = 0, + SetupErrorNotPlaying = 1, + SetupErrorLoadedFull = 2, + SetupNoErrorStarted = 3, + }; + void loadData(const AudioMsgId &audio, qint64 position); + AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 position); + AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type); + +}; diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp new file mode 100644 index 0000000000..1f122002e0 --- /dev/null +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp @@ -0,0 +1,186 @@ +/* +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_child_ffmpeg_loader.h" + +constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16; +constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO; +constexpr int32 AudioToChannels = 2; + +VideoSoundData::~VideoSoundData() { + if (context) { + avcodec_close(context); + avcodec_free_context(&context); + context = nullptr; + } +} + +ChildFFMpegLoader::ChildFFMpegLoader(std_::unique_ptr &&data) : AudioPlayerLoader(FileLocation(), QByteArray()) +, _parentData(std_::move(data)) { + _frame = av_frame_alloc(); +} + +bool ChildFFMpegLoader::open(qint64 position) { + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + + uint64_t layout = _parentData->context->channel_layout; + _inputFormat = _parentData->context->sample_fmt; + switch (layout) { + case AV_CH_LAYOUT_MONO: + switch (_inputFormat) { + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: _format = AL_FORMAT_MONO8; _sampleSize = 1; break; + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: _format = AL_FORMAT_MONO16; _sampleSize = sizeof(uint16); break; + default: + _sampleSize = -1; // convert needed + break; + } + break; + case AV_CH_LAYOUT_STEREO: + switch (_inputFormat) { + case AV_SAMPLE_FMT_U8: _format = AL_FORMAT_STEREO8; _sampleSize = 2; break; + case AV_SAMPLE_FMT_S16: _format = AL_FORMAT_STEREO16; _sampleSize = 2 * sizeof(uint16); break; + default: + _sampleSize = -1; // convert needed + break; + } + break; + default: + _sampleSize = -1; // convert needed + break; + } + if (_parentData->frequency != 44100 && _parentData->frequency != 48000) { + _sampleSize = -1; // convert needed + } + + if (_sampleSize < 0) { + _swrContext = swr_alloc(); + if (!_swrContext) { + LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size())); + return false; + } + int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout; + _srcRate = _parentData->frequency; + AVSampleFormat src_sample_fmt = _inputFormat, dst_sample_fmt = AudioToFormat; + _dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? AudioVoiceMsgFrequency : _parentData->frequency; + + av_opt_set_int(_swrContext, "in_channel_layout", src_ch_layout, 0); + av_opt_set_int(_swrContext, "in_sample_rate", _srcRate, 0); + av_opt_set_sample_fmt(_swrContext, "in_sample_fmt", src_sample_fmt, 0); + av_opt_set_int(_swrContext, "out_channel_layout", dst_ch_layout, 0); + av_opt_set_int(_swrContext, "out_sample_rate", _dstRate, 0); + av_opt_set_sample_fmt(_swrContext, "out_sample_fmt", dst_sample_fmt, 0); + + if ((res = swr_init(_swrContext)) < 0) { + LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + _sampleSize = AudioToChannels * sizeof(short); + _parentData->frequency = _dstRate; + _parentData->length = av_rescale_rnd(_parentData->length, _dstRate, _srcRate, AV_ROUND_UP); + _format = AL_FORMAT_STEREO16; + + _maxResampleSamples = av_rescale_rnd(AVBlockSize / _sampleSize, _dstRate, _srcRate, AV_ROUND_UP); + if ((res = av_samples_alloc_array_and_samples(&_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 0)) < 0) { + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + + return true; +} + +AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) { + if (_queue.isEmpty()) { + return ReadResult::Wait; + } + + av_frame_unref(_frame); + int got_frame = 0; + int res = 0; + auto packet = _queue.dequeue(); + if ((res = avcodec_decode_audio4(_parentData->context, _frame, &got_frame, &packet)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to avcodec_decode_audio4() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + if (res == AVERROR_INVALIDDATA) { + return ReadResult::NotYet; // try to skip bad packet + } + return ReadResult::Error; + } + + if (got_frame) { + if (_dstSamplesData) { // convert needed + int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP); + if (dstSamples > _maxResampleSamples) { + _maxResampleSamples = dstSamples; + av_free(_dstSamplesData[0]); + + if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) { + _dstSamplesData[0] = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + return ReadResult::Error; + } + } + if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + av_packet_unref(&packet); + return ReadResult::Error; + } + int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1); + result.append((const char*)_dstSamplesData[0], resultLen); + samplesAdded += resultLen / _sampleSize; + } else { + result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize); + samplesAdded += _frame->nb_samples; + } + } + av_packet_unref(&packet); + return ReadResult::Ok; +} + +void ChildFFMpegLoader::enqueuePackets(QQueue &packets) { + _queue += std_::move(packets); + packets.clear(); +} + +ChildFFMpegLoader::~ChildFFMpegLoader() { + auto queue = createAndSwap(_queue); + for (auto &packet : queue) { + av_packet_unref(&packet); + } + if (_dstSamplesData) { + if (_dstSamplesData[0]) { + av_freep(&_dstSamplesData[0]); + } + av_freep(&_dstSamplesData); + } + av_frame_free(&_frame); +} diff --git a/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h new file mode 100644 index 0000000000..fff2058c93 --- /dev/null +++ b/Telegram/SourceFiles/media/media_child_ffmpeg_loader.h @@ -0,0 +1,93 @@ +/* +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 +*/ +#pragma once + +#include "media/media_audio_loader.h" + +extern "C" { +#include +#include +#include +#include +} // extern "C" + +#include + +struct VideoSoundData { + uint64 videoPlayId = 0; + AVCodecContext *context = nullptr; + int32 frequency = AudioVoiceMsgFrequency; + int64 length = 0; + ~VideoSoundData(); +}; + +struct VideoSoundPart { + AVPacket *packet = nullptr; + uint64 videoPlayId = 0; +}; + +class ChildFFMpegLoader : public AudioPlayerLoader { +public: + ChildFFMpegLoader(std_::unique_ptr &&data); + + bool open(qint64 position = 0) override; + + bool check(const FileLocation &file, const QByteArray &data) override { + return true; + } + + int32 format() override { + return _format; + } + + int64 duration() override { + return _parentData->length; + } + + int32 frequency() override { + return _parentData->frequency; + } + + ReadResult readMore(QByteArray &result, int64 &samplesAdded) override; + void enqueuePackets(QQueue &packets); + + uint64 playId() const { + return _parentData->videoPlayId; + } + + ~ChildFFMpegLoader(); + +private: + int32 _sampleSize = 2 * sizeof(uint16); + int32 _format = AL_FORMAT_STEREO16; + int32 _srcRate = AudioVoiceMsgFrequency; + int32 _dstRate = AudioVoiceMsgFrequency; + int32 _maxResampleSamples = 1024; + uint8_t **_dstSamplesData = nullptr; + + std_::unique_ptr _parentData; + AVSampleFormat _inputFormat; + AVFrame *_frame = nullptr; + + SwrContext *_swrContext = nullptr; + QQueue _queue; + +}; diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp index f509310d8e..cd20fd6d8a 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -21,6 +21,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "media/media_clip_ffmpeg.h" +#include "media/media_audio.h" +#include "media/media_child_ffmpeg_loader.h" + namespace Media { namespace Clip { namespace internal { @@ -104,7 +107,12 @@ bool FFMpegReaderImplementation::readNextFrame() { } if (eofReached) { + if (_mode == Mode::Normal) { + return false; + } + clearPacketQueue(); + if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits::min(), 0, std::numeric_limits::max(), 0)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) { if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) { @@ -119,6 +127,7 @@ bool FFMpegReaderImplementation::readNextFrame() { avcodec_flush_buffers(_codecContext); _hadFrame = false; _frameMs = 0; + _lastReadPacketMs = 0; } } @@ -162,6 +171,16 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q } } + // Read some future packets for audio stream. + if (_audioStreamId) { + while (_frameMs + 5000 > _lastReadPacketMs) { + auto packetResult = readPacket(); + if (packetResult != PacketResult::Ok) { + break; + } + } + } + av_frame_unref(_frame); return true; } @@ -171,6 +190,8 @@ int FFMpegReaderImplementation::nextFrameDelay() { } bool FFMpegReaderImplementation::start(Mode mode) { + _mode = mode; + initDevice(); if (!_device->open(QIODevice::ReadOnly)) { LOG(("Gif Error: Unable to open device %1").arg(logData())); @@ -207,12 +228,12 @@ bool FFMpegReaderImplementation::start(Mode mode) { } _packetNull.stream_index = _streamId; - // Get a pointer to the codec context for the audio stream + // Get a pointer to the codec context for the video stream _codecContext = _fmtContext->streams[_streamId]->codec; _codec = avcodec_find_decoder(_codecContext->codec_id); _audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0); - if (mode == Mode::OnlyGifv) { + if (_mode == Mode::OnlyGifv) { if (_audioStreamId >= 0) { // should be no audio stream return false; } @@ -222,7 +243,7 @@ bool FFMpegReaderImplementation::start(Mode mode) { if (_codecContext->codec_id != AV_CODEC_ID_H264) { return false; } - } else if (mode == Mode::Silent) { + } else if (_mode == Mode::Silent || !audioPlayer()) { _audioStreamId = -1; } av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); @@ -231,6 +252,35 @@ bool FFMpegReaderImplementation::start(Mode mode) { return false; } + if (_audioStreamId >= 0) { + // Get a pointer to the codec context for the audio stream + auto audioContextOriginal = _fmtContext->streams[_audioStreamId]->codec; + auto audioCodec = avcodec_find_decoder(audioContextOriginal->codec_id); + + AVCodecContext *audioContext = avcodec_alloc_context3(audioCodec); + if ((res = avcodec_copy_context(audioContext, audioContextOriginal)) != 0) { + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + av_opt_set_int(audioContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(audioContext, audioCodec, 0)) < 0) { + avcodec_free_context(&audioContext); + LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + auto soundData = std_::make_unique(); + soundData->context = audioContext; + soundData->frequency = audioContextOriginal->sample_rate; + if (_fmtContext->streams[_audioStreamId]->duration == AV_NOPTS_VALUE) { + soundData->length = (_fmtContext->duration * soundData->frequency) / AV_TIME_BASE; + } else { + soundData->length = (_fmtContext->streams[_audioStreamId]->duration * soundData->frequency * _fmtContext->streams[_audioStreamId]->time_base.num) / _fmtContext->streams[_audioStreamId]->time_base.den; + } + soundData->videoPlayId = _playId = rand_value(); + audioPlayer()->playFromVideo(AudioMsgId(AudioMsgId::Type::Video), 0, std_::move(soundData)); + } + return true; } @@ -281,6 +331,10 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( bool videoPacket = (packet.stream_index == _streamId); bool audioPacket = (_audioStreamId >= 0 && packet.stream_index == _audioStreamId); if (audioPacket || videoPacket) { + int64 packetPts = (packet.pts == AV_NOPTS_VALUE) ? packet.dts : packet.pts; + int64 packetMs = (packetPts * 1000LL * _fmtContext->streams[packet.stream_index]->time_base.num) / _fmtContext->streams[packet.stream_index]->time_base.den; + _lastReadPacketMs = packetMs; + //AVPacket packetForQueue; //av_init_packet(&packetForQueue); //if ((res = av_packet_ref(&packetForQueue, &packet)) < 0) { @@ -294,9 +348,10 @@ FFMpegReaderImplementation::PacketResult FFMpegReaderImplementation::readPacket( //_packetQueue.enqueue(packetForQueue); } else if (audioPacket) { // queue packet to audio player - // audioPlayer()->enqueuePacket(packet, &isEnough) - //av_packet_unref(&packetForQueue); - av_packet_unref(&packet); + VideoSoundPart part; + part.packet = &packet; + part.videoPlayId = _playId; + audioPlayer()->feedFromVideo(std_::move(part)); } } else { av_packet_unref(&packet); diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h index 78c5482825..02f5f18e6b 100644 --- a/Telegram/SourceFiles/media/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -62,6 +62,8 @@ private: static int _read(void *opaque, uint8_t *buf, int buf_size); static int64_t _seek(void *opaque, int64_t offset, int whence); + Mode _mode = Mode::Normal; + uchar *_ioBuffer = nullptr; AVIOContext *_ioContext = nullptr; AVFormatContext *_fmtContext = nullptr; @@ -74,6 +76,8 @@ private: bool _frameRead = false; int _audioStreamId = 0; + uint64 _playId = 0; + int64 _lastReadPacketMs = 0; QQueue _packetQueue; AVPacket _packetNull; // for final decoding diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 6c05b881f9..d2accd1619 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -31,7 +31,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "fileuploader.h" #include "mainwindow.h" #include "playerwidget.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" namespace Overview { diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp index 29f7941ce8..40e3ae804d 100644 --- a/Telegram/SourceFiles/playerwidget.cpp +++ b/Telegram/SourceFiles/playerwidget.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "mainwidget.h" #include "localstorage.h" -#include "audio.h" +#include "media/media_audio.h" PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) , _a_state(animation(this, &PlayerWidget::step_state)) diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h index 72aa9ba98f..c8e893483f 100644 --- a/Telegram/SourceFiles/playerwidget.h +++ b/Telegram/SourceFiles/playerwidget.h @@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "audio.h" +#include "media/media_audio.h" class PlayerWidget : public TWidget { Q_OBJECT diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 563af77fbb..8519bd95c6 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -32,7 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/filedialog.h" #include "apiwrap.h" #include "boxes/confirmbox.h" -#include "audio.h" +#include "media/media_audio.h" #include "localstorage.h" namespace { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 379fd0c182..3826aa5f09 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1241,10 +1241,12 @@ public: AudioMsgId() { } AudioMsgId(DocumentData *audio, const FullMsgId &msgId) : _audio(audio), _contextId(msgId) { - setType(); + setTypeFromAudio(); } AudioMsgId(DocumentData *audio, ChannelId channelId, MsgId msgId) : _audio(audio), _contextId(channelId, msgId) { - setType(); + setTypeFromAudio(); + } + AudioMsgId(Type type) : _type(type) { } Type type() const { @@ -1262,7 +1264,7 @@ public: } private: - void setType() { + void setTypeFromAudio() { if (_audio->voice()) { _type = Type::Voice; } else if (_audio->song()) { diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 2fe3a169e9..aa6701a439 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -265,23 +265,23 @@ class SingleDelayedCall : public QObject { Q_OBJECT public: - SingleDelayedCall(QObject *parent, const char *member) : QObject(parent), _pending(false), _member(member) { + SingleDelayedCall(QObject *parent, const char *member) : QObject(parent), _member(member) { } void call() { - if (!_pending) { - _pending = true; + if (!_pending.loadAcquire()) { + _pending.storeRelease(1); QMetaObject::invokeMethod(this, "makeDelayedCall", Qt::QueuedConnection); } } private slots: void makeDelayedCall() { - _pending = false; + _pending.storeRelease(0); QMetaObject::invokeMethod(parent(), _member); } private: - bool _pending; + QAtomicInt _pending = { 0 }; const char *_member; }; diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index 8651364b39..1fbe514a7f 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -104,7 +104,6 @@ SOURCES += \ ./SourceFiles/apiwrap.cpp \ ./SourceFiles/app.cpp \ ./SourceFiles/application.cpp \ - ./SourceFiles/audio.cpp \ ./SourceFiles/autoupdater.cpp \ ./SourceFiles/dialogswidget.cpp \ ./SourceFiles/dropdown.cpp \ @@ -172,6 +171,11 @@ SOURCES += \ ./SourceFiles/intro/intropwdcheck.cpp \ ./SourceFiles/intro/introsignup.cpp \ ./SourceFiles/intro/introstart.cpp \ + ./SourceFiles/media/media_audio.cpp \ + ./SourceFiles/media/media_clip_ffmpeg.cpp \ + ./SourceFiles/media/media_clip_implementation.cpp \ + ./SourceFiles/media/media_clip_qtgif.cpp \ + ./SourceFiles/media/media_clip_reader.cpp \ ./SourceFiles/mtproto/facade.cpp \ ./SourceFiles/mtproto/auth_key.cpp \ ./SourceFiles/mtproto/connection.cpp \ @@ -254,7 +258,6 @@ HEADERS += \ ./SourceFiles/apiwrap.h \ ./SourceFiles/app.h \ ./SourceFiles/application.h \ - ./SourceFiles/audio.h \ ./SourceFiles/autoupdater.h \ ./SourceFiles/config.h \ ./SourceFiles/countries.h \ @@ -328,6 +331,11 @@ HEADERS += \ ./SourceFiles/intro/intropwdcheck.h \ ./SourceFiles/intro/introsignup.h \ ./SourceFiles/intro/introstart.h \ + ./SourceFiles/media/media_audio.h \ + ./SourceFiles/media/media_clip_ffmpeg.h \ + ./SourceFiles/media/media_clip_implementation.h \ + ./SourceFiles/media/media_clip_qtgif.h \ + ./SourceFiles/media/media_clip_reader.h \ ./SourceFiles/mtproto/facade.h \ ./SourceFiles/mtproto/auth_key.h \ ./SourceFiles/mtproto/connection.h \ diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 532a644d2b..42dc178c9d 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -186,10 +186,6 @@ true true - - true - true - true true @@ -371,6 +367,14 @@ true true + + true + true + + + true + true + true true @@ -515,10 +519,6 @@ true true - - true - true - true true @@ -705,6 +705,14 @@ true true + + true + true + + + true + true + true true @@ -880,10 +888,6 @@ true true - - true - true - true true @@ -1070,6 +1074,14 @@ true true + + true + true + + + true + true + true true @@ -1204,7 +1216,6 @@ - @@ -1262,6 +1273,11 @@ + + + + + @@ -1538,6 +1554,37 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_audio_loaders.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_audio_loaders.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" + + @@ -2252,20 +2299,6 @@ $(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath) - - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) - Moc%27ing audio.h... - .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp - "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include" "-fstdafx.h" "-f../../SourceFiles/audio.h" - $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing usernamebox.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 64116e19b8..6993d85e15 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -447,18 +447,6 @@ GeneratedFiles\Release - - SourceFiles - - - GeneratedFiles\Deploy - - - GeneratedFiles\Debug - - - GeneratedFiles\Release - SourceFiles\boxes @@ -1368,6 +1356,39 @@ SourceFiles\media + + SourceFiles\media + + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + + + SourceFiles\media + + + GeneratedFiles\Deploy + + + SourceFiles\media + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + @@ -1628,6 +1649,15 @@ SourceFiles\media + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1705,9 +1735,6 @@ SourceFiles - - SourceFiles - SourceFiles\boxes @@ -1921,6 +1948,12 @@ SourceFiles\media + + SourceFiles\media + + + SourceFiles\media + diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 449edf2cf1..817d475511 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -163,8 +163,8 @@ 07C8FE101CB80890007A8702 /* toast.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C8FE0C1CB80890007A8702 /* toast.cpp */; }; 07C8FE121CB80915007A8702 /* moc_toast_manager.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07C8FE111CB80915007A8702 /* moc_toast_manager.cpp */; }; 07CAACD81AEA64F00058E508 /* AudioUnit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 07CAACD71AEA64F00058E508 /* AudioUnit.framework */; }; - 07D7034B19B8755A00C4EED2 /* audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D7034919B8755A00C4EED2 /* audio.cpp */; }; - 07D703BB19B88FB900C4EED2 /* moc_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */; }; + 07D7034B19B8755A00C4EED2 /* media_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D7034919B8755A00C4EED2 /* media_audio.cpp */; }; + 07D703BB19B88FB900C4EED2 /* moc_media_audio.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */; }; 07D7954A1B5544B200DE9598 /* qtpcre in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 07D795491B5544B200DE9598 /* qtpcre */; }; 07D7EABA1A597DD000838BA2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 07D7EABC1A597DD000838BA2 /* Localizable.strings */; }; 07D8509419F5C97E00623D75 /* core_types.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 07D8509219F5C97E00623D75 /* core_types.cpp */; }; @@ -603,9 +603,9 @@ 07C8FE111CB80915007A8702 /* moc_toast_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_toast_manager.cpp; path = GeneratedFiles/Debug/moc_toast_manager.cpp; sourceTree = SOURCE_ROOT; }; 07CAACD71AEA64F00058E508 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 07D518D41CD0E27600F5FF59 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = version.h; path = SourceFiles/core/version.h; sourceTree = SOURCE_ROOT; }; - 07D7034919B8755A00C4EED2 /* audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = audio.cpp; path = SourceFiles/audio.cpp; sourceTree = SOURCE_ROOT; }; - 07D7034A19B8755A00C4EED2 /* audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio.h; path = SourceFiles/audio.h; sourceTree = SOURCE_ROOT; }; - 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_audio.cpp; path = GeneratedFiles/Debug/moc_audio.cpp; sourceTree = SOURCE_ROOT; }; + 07D7034919B8755A00C4EED2 /* media_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = media_audio.cpp; path = SourceFiles/media/media_audio.cpp; sourceTree = SOURCE_ROOT; }; + 07D7034A19B8755A00C4EED2 /* media_audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = media_audio.h; path = SourceFiles/media/media_audio.h; sourceTree = SOURCE_ROOT; }; + 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_media_audio.cpp; path = GeneratedFiles/Debug/moc_media_audio.cpp; sourceTree = SOURCE_ROOT; }; 07D795491B5544B200DE9598 /* qtpcre */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = qtpcre; path = "$(QT_PATH)/lib/libqtpcre$(QT_LIBRARY_SUFFIX).a"; sourceTree = ""; }; 07D7EABB1A597DD000838BA2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Resources/langs/en.lproj/Localizable.strings; sourceTree = ""; }; 07D7EABD1A597DD200838BA2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Resources/langs/es.lproj/Localizable.strings; sourceTree = ""; }; @@ -1413,8 +1413,8 @@ C19DF71B273A4843553518F2 /* app.h */, C20F9DD8C7B031B8E20D5653 /* application.cpp */, 09FD01F2BD652EB838A296D8 /* application.h */, - 07D7034919B8755A00C4EED2 /* audio.cpp */, - 07D7034A19B8755A00C4EED2 /* audio.h */, + 07D7034919B8755A00C4EED2 /* media_audio.cpp */, + 07D7034A19B8755A00C4EED2 /* media_audio.h */, 07C7596D1B1F7E0000662169 /* autoupdater.cpp */, 07C7596E1B1F7E0000662169 /* autoupdater.h */, 206B4F5CBD5354BCE19FF32F /* countries.h */, @@ -1500,7 +1500,7 @@ A1479F94376F9732B57C69DB /* moc_animation.cpp */, 0764D55C1ABAD71B00FBFEED /* moc_apiwrap.cpp */, E181C525E21A16F2D4396CA7 /* moc_application.cpp */, - 07D703BA19B88FB900C4EED2 /* moc_audio.cpp */, + 07D703BA19B88FB900C4EED2 /* moc_media_audio.cpp */, 07DE92A91AA4928200A18F6F /* moc_autolockbox.cpp */, 07C759711B1F7E2800662169 /* moc_autoupdater.cpp */, 078A2FC91A811C5900CCC7A0 /* moc_backgroundbox.cpp */, @@ -2013,7 +2013,7 @@ EBE29731916DB43BF49FE7A4 /* aboutbox.cpp in Compile Sources */, 4426AF526AAD86D6F73CE36F /* addcontactbox.cpp in Compile Sources */, 0716C9561D0589A700797B22 /* profile_userpic_button.cpp in Compile Sources */, - 07D7034B19B8755A00C4EED2 /* audio.cpp in Compile Sources */, + 07D7034B19B8755A00C4EED2 /* media_audio.cpp in Compile Sources */, A0A6B97F7DBEC81004EC9461 /* confirmbox.cpp in Compile Sources */, 4FEA8F51B7BC7CAC71347A1A /* connectionbox.cpp in Compile Sources */, 07C7596F1B1F7E0000662169 /* autoupdater.cpp in Compile Sources */, @@ -2024,7 +2024,7 @@ 07C8FE0F1CB80890007A8702 /* toast_widget.cpp in Compile Sources */, 0716C9741D058C8600797B22 /* moc_profile_inner_widget.cpp in Compile Sources */, 3ABE4F9B2264F770D944106D /* emojibox.cpp in Compile Sources */, - 07D703BB19B88FB900C4EED2 /* moc_audio.cpp in Compile Sources */, + 07D703BB19B88FB900C4EED2 /* moc_media_audio.cpp in Compile Sources */, 77B998AC22A13EF3DDEE07AC /* photocropbox.cpp in Compile Sources */, F278C423357CA99797EA30AB /* photosendbox.cpp in Compile Sources */, E8D95529CED88F18818C9A8B /* introwidget.cpp in Compile Sources */, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index 0707e159e1..2cc42ee184 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -55,7 +55,6 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -99,6 +98,8 @@ compilers: GeneratedFiles/qrc_telegram.cpp\ GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -193,7 +194,6 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -237,6 +237,8 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_aboutbox.cpp\ GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -274,7 +276,6 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_animation.cpp\ GeneratedFiles/Debug/moc_apiwrap.cpp\ GeneratedFiles/Debug/moc_application.cpp\ - GeneratedFiles/Debug/moc_audio.cpp\ GeneratedFiles/Debug/moc_autolockbox.cpp\ GeneratedFiles/Debug/moc_autoupdater.cpp\ GeneratedFiles/Debug/moc_backgroundbox.cpp\ @@ -318,6 +319,8 @@ compiler_moc_header_clean: GeneratedFiles/Debug/moc_main_window_mac.cpp\ GeneratedFiles/Debug/moc_mainwidget.cpp\ GeneratedFiles/Debug/moc_mainwindow.cpp\ + GeneratedFiles/Debug/moc_media_audio.cpp\ + GeneratedFiles/Debug/moc_media_clip_reader.cpp\ GeneratedFiles/Debug/moc_mediaview.cpp\ GeneratedFiles/Debug/moc_overviewwidget.cpp\ GeneratedFiles/Debug/moc_passcodebox.cpp\ @@ -366,9 +369,6 @@ GeneratedFiles/Debug/moc_apiwrap.cpp: SourceFiles/apiwrap.h GeneratedFiles/Debug/moc_application.cpp: SourceFiles/application.h $(MOC_FILE) SourceFiles/application.h -o GeneratedFiles/Debug/moc_application.cpp -GeneratedFiles/Debug/moc_audio.cpp: SourceFiles/audio.h - $(MOC_FILE) SourceFiles/audio.h -o GeneratedFiles/Debug/moc_audio.cpp - GeneratedFiles/Debug/moc_autolockbox.cpp: SourceFiles/boxes/autolockbox.h $(MOC_FILE) SourceFiles/boxes/autolockbox.h -o GeneratedFiles/Debug/moc_autolockbox.cpp @@ -498,6 +498,12 @@ GeneratedFiles/Debug/moc_mainwidget.cpp: SourceFiles/mainwidget.h GeneratedFiles/Debug/moc_mainwindow.cpp: SourceFiles/mainwindow.h $(MOC_FILE) SourceFiles/mainwindow.h -o GeneratedFiles/Debug/moc_mainwindow.cpp +GeneratedFiles/Debug/moc_media_audio.cpp: SourceFiles/media/media_audio.h + $(MOC_FILE) SourceFiles/media/media_audio.h -o GeneratedFiles/Debug/moc_media_audio.cpp + +GeneratedFiles/Debug/moc_media_clip_reader.cpp: SourceFiles/media/media_clip_reader.h + $(MOC_FILE) SourceFiles/media/media_clip_reader.h -o GeneratedFiles/Debug/moc_media_clip_reader.cpp + GeneratedFiles/Debug/moc_mediaview.cpp: SourceFiles/mediaview.h $(MOC_FILE) SourceFiles/mediaview.h -o GeneratedFiles/Debug/moc_mediaview.cpp