diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 256421f30..9d4e55165 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2462,11 +2462,11 @@ namespace { return ::sharedContactItems; } - void regGifItem(ClipReader *reader, HistoryItem *item) { + void regGifItem(Media::Clip::Reader *reader, HistoryItem *item) { ::gifItems.insert(reader, item); } - void unregGifItem(ClipReader *reader) { + void unregGifItem(Media::Clip::Reader *reader) { ::gifItems.remove(reader); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index d2e06ce10..1c80ba848 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -39,7 +39,7 @@ typedef QHash PhotoItems; typedef QHash DocumentItems; typedef QHash WebPageItems; typedef QHash SharedContactItems; -typedef QHash GifItems; +typedef QHash GifItems; typedef QHash PhotosData; typedef QHash DocumentsData; @@ -257,8 +257,8 @@ namespace App { const SharedContactItems &sharedContactItems(); QString phoneFromSharedContact(int32 userId); - void regGifItem(ClipReader *reader, HistoryItem *item); - void unregGifItem(ClipReader *reader); + void regGifItem(Media::Clip::Reader *reader, HistoryItem *item); + void unregGifItem(Media::Clip::Reader *reader); void stopGifItems(); void regMuted(PeerData *peer, int32 changeIn); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index ae585def9..ded75552a 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "history/history_service_layout.h" #include "data/data_drafts.h" +#include "media/media_clip_reader.h" #include "lang.h" #include "mainwidget.h" #include "application.h" @@ -2906,15 +2907,17 @@ void HistoryItem::setUnreadBarFreezed() { } } -void HistoryItem::clipCallback(ClipReaderNotification notification) { +void HistoryItem::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + HistoryMedia *media = getMedia(); if (!media) return; - ClipReader *reader = media ? media->getClipReader() : 0; + Reader *reader = media ? media->getClipReader() : 0; if (!reader) return; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { bool stopped = false; if (reader->paused()) { if (MainWidget *m = App::main()) { @@ -2933,7 +2936,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) { } } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (!reader->currentDisplayed()) { Ui::repaintHistoryItem(this); } @@ -4526,13 +4529,13 @@ void HistoryGif::initDimensions() { bool bubble = _parent->hasBubble(); int32 tw = 0, th = 0; - if (gif() && _gif->state() == ClipError) { + if (gif() && _gif->state() == Media::Clip::State::Error) { if (!_gif->autoplay()) { Ui::showLayer(new InformBox(lang(lng_gif_error))); } App::unregGifItem(_gif); delete _gif; - _gif = BadClipReader; + _gif = Media::Clip::BadReader; } if (gif() && _gif->ready()) { @@ -4637,7 +4640,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading(); bool selected = (selection == FullSelection); - if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { + if (loaded && !gif() && _gif != Media::Clip::BadReader && cAutoPlayGif()) { Ui::autoplayMediaInlineAsync(_parent->fullId()); } @@ -4684,7 +4687,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6 App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) { + if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) { float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); @@ -4848,7 +4851,7 @@ bool HistoryGif::playInline(bool autoplay) { if (!cAutoPlayGif()) { App::stopGifItems(); } - _gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback)); + _gif = new Media::Clip::Reader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback)); App::regGifItem(_gif, _parent); if (gif()) _gif->setAutoplay(); } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index a38257e02..8f867bb09 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1412,7 +1412,7 @@ public: return _text.isEmpty() && !_media; } - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); virtual ~HistoryItem(); @@ -1656,7 +1656,7 @@ public: virtual DocumentData *getDocument() { return nullptr; } - virtual ClipReader *getClipReader() { + virtual Media::Clip::Reader *getClipReader() { return nullptr; } @@ -2140,7 +2140,7 @@ public: DocumentData *getDocument() override { return _data; } - ClipReader *getClipReader() override { + Media::Clip::Reader *getClipReader() override { return gif(); } @@ -2189,12 +2189,12 @@ private: int32 _thumbw, _thumbh; Text _caption; - ClipReader *_gif; - ClipReader *gif() { - return (_gif == BadClipReader) ? nullptr : _gif; + Media::Clip::Reader *_gif; + Media::Clip::Reader *gif() { + return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } - const ClipReader *gif() const { - return (_gif == BadClipReader) ? nullptr : _gif; + const Media::Clip::Reader *gif() const { + return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } void setStatusSize(int32 newSize) const; @@ -2377,7 +2377,7 @@ public: DocumentData *getDocument() override { return _attach ? _attach->getDocument() : 0; } - ClipReader *getClipReader() override { + Media::Clip::Reader *getClipReader() override { return _attach ? _attach->getClipReader() : 0; } bool playInline(bool autoplay) override { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index c3fa01dd6..5b9f7d89b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "inline_bots/inline_bot_result.h" +#include "media/media_clip_reader.h" #include "localstorage.h" #include "mainwidget.h" #include "lang.h" @@ -131,9 +132,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons document->automaticLoad(nullptr); bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading(); - if (loaded && !gif() && _gif != BadClipReader) { + if (loaded && !gif() && _gif != Media::Clip::BadReader) { Gif *that = const_cast(this); - that->_gif = new ClipReader(document->location(), document->data(), func(that, &Gif::clipCallback)); + that->_gif = new Media::Clip::Reader(document->location(), document->data(), func(that, &Gif::clipCallback)); if (gif()) _gif->setAutoplay(); } @@ -162,7 +163,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons } } - if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) { + if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) { float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1; if (_animation && _animation->_a_over.animating(context->ms)) { float64 over = _animation->_a_over.current(); @@ -326,13 +327,14 @@ void Gif::step_radial(uint64 ms, bool timer) { } } -void Gif::clipCallback(ClipReaderNotification notification) { +void Gif::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { if (gif()) { - if (_gif->state() == ClipError) { + if (_gif->state() == State::Error) { delete _gif; - _gif = BadClipReader; + _gif = BadReader; getShownDocument()->forget(); } else if (_gif->ready() && !_gif->started()) { int32 height = st::inlineMediaHeight; @@ -348,7 +350,7 @@ void Gif::clipCallback(ClipReaderNotification notification) { update(); } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (gif() && !_gif->currentDisplayed()) { update(); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 7095f8828..85a8ccc08 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -92,10 +92,10 @@ private: return ~StateFlags(flag); } - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; ClickHandlerPtr _delete; bool gif() const { - return (!_gif || _gif == BadClipReader) ? false : true; + return (!_gif || _gif == Media::Clip::BadReader) ? false : true; } mutable QPixmap _thumb; void prepareThumb(int32 width, int32 height, const QSize &frame) const; @@ -104,7 +104,7 @@ private: bool isRadialAnimation(uint64 ms) const; void step_radial(uint64 ms, bool timer); - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); struct AnimationData { AnimationData(AnimationCallbacks &&callbacks) diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 54d061526..08f840da6 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "lang.h" +#include "media/media_clip_reader.h" #include "layerwidget.h" #include "application.h" #include "mainwindow.h" @@ -346,9 +347,9 @@ QPixmap MediaPreviewWidget::currentImage() const { } else { _document->automaticLoad(nullptr); if (_document->loaded()) { - if (!_gif && _gif != BadClipReader) { - MediaPreviewWidget *that = const_cast(this); - that->_gif = new ClipReader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback)); + if (!_gif && _gif != Media::Clip::BadReader) { + auto that = const_cast(this); + that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback)); if (gif()) _gif->setAutoplay(); } } @@ -385,12 +386,13 @@ QPixmap MediaPreviewWidget::currentImage() const { return _cache; } -void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) { +void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; switch (notification) { - case ClipReaderReinit: { - if (gif() && _gif->state() == ClipError) { + case NotificationReinit: { + if (gif() && _gif->state() == State::Error) { delete _gif; - _gif = BadClipReader; + _gif = BadReader; } if (gif() && _gif->ready() && !_gif->started()) { @@ -401,7 +403,7 @@ void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) { update(); } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (gif() && !_gif->currentDisplayed()) { update(); } diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h index e1d408f1e..c5ba79eb7 100644 --- a/Telegram/SourceFiles/layerwidget.h +++ b/Telegram/SourceFiles/layerwidget.h @@ -135,12 +135,12 @@ private: Animation _a_shown; DocumentData *_document = nullptr; PhotoData *_photo = nullptr; - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; bool gif() const { - return (!_gif || _gif == BadClipReader) ? false : true; + return (!_gif || _gif == Media::Clip::BadReader) ? false : true; } - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); enum CacheStatus { CacheNotLoaded, diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index f484ccb9b..cec36b9f4 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "audio.h" #include "boxes/photosendbox.h" +#include "media/media_clip_reader.h" #include "mainwidget.h" #include "mainwindow.h" #include "lang.h" @@ -330,7 +331,7 @@ void FileLoadTask::process() { } if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) { QImage cover; - MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover); + MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover); if (animatedAttribute.type() == mtpc_documentAttributeVideo) { int32 cw = cover.width(), ch = cover.height(); if (cw < 20 * ch && ch < 20 * cw) { diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp new file mode 100644 index 000000000..d10568f70 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.cpp @@ -0,0 +1,302 @@ +/* +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_clip_ffmpeg.h" + +namespace Media { +namespace Clip { +namespace internal { + +FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { + _frame = av_frame_alloc(); + av_init_packet(&_avpkt); + _avpkt.data = NULL; + _avpkt.size = 0; +} + +bool FFMpegReaderImplementation::readNextFrame() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + + int res; + while (true) { + if (_avpkt.size > 0) { // previous packet not finished + res = 0; + } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { + if (res != AVERROR_EOF || !_hadFrame) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + + bool finished = (res < 0); + if (finished) { + _avpkt.data = NULL; + _avpkt.size = 0; + } else { + rememberPacket(); + } + + int32 got_frame = 0; + int32 decoded = _avpkt.size; + if (_avpkt.stream_index == _streamId) { + if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + + if (res == AVERROR_INVALIDDATA) { // try to skip bad packet + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + + if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file + return false; + } + freePacket(); + _avpkt.data = NULL; + _avpkt.size = 0; + continue; + } + if (res > 0) decoded = res; + } else if (_audioStreamId >= 0 && _avpkt.stream_index == _audioStreamId) { + freePacket(); + continue; + } + if (!finished) { + _avpkt.data += decoded; + _avpkt.size -= decoded; + if (_avpkt.size <= 0) freePacket(); + } + + if (got_frame) { + int64 duration = av_frame_get_pkt_duration(_frame); + int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; + int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + _currentFrameDelay = _nextFrameDelay; + if (_frameMs + _currentFrameDelay < frameMs) { + _currentFrameDelay = int32(frameMs - _frameMs); + } + if (duration == AV_NOPTS_VALUE) { + _nextFrameDelay = 0; + } else { + _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; + } + _frameMs = frameMs; + + _hadFrame = _frameRead = true; + return true; + } + + if (finished) { + 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) { + if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + } + } + } + avcodec_flush_buffers(_codecContext); + _hadFrame = false; + _frameMs = 0; + } + } + + return false; +} + +bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(_frameRead); + _frameRead = false; + + if (!_width || !_height) { + _width = _frame->width; + _height = _frame->height; + if (!_width || !_height) { + LOG(("Gif Error: Bad frame size %1").arg(logData())); + return false; + } + } + + QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); + if (to.isNull() || to.size() != toSize) { + to = QImage(toSize, QImage::Format_ARGB32); + } + hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); + if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { + int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); + uchar *s = _frame->data[0], *d = to.bits(); + for (int32 i = 0, l = _frame->height; i < l; ++i) { + memcpy(d + i * dbpl, s + i * sbpl, bpl); + } + } else { + if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { + _swsSize = toSize; + _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); + } + uint8_t * toData[1] = { to.bits() }; + int toLinesize[1] = { to.bytesPerLine() }, res; + if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { + LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); + return false; + } + } + + av_frame_unref(_frame); + return true; +} + +int FFMpegReaderImplementation::nextFrameDelay() { + return _currentFrameDelay; +} + +bool FFMpegReaderImplementation::start(bool onlyGifv) { + initDevice(); + if (!_device->open(QIODevice::ReadOnly)) { + LOG(("Gif Error: Unable to open device %1").arg(logData())); + return false; + } + _ioBuffer = (uchar*)av_malloc(AVBlockSize); + _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); + _fmtContext = avformat_alloc_context(); + if (!_fmtContext) { + LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); + return false; + } + _fmtContext->pb = _ioContext; + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { + _ioBuffer = 0; + + LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { + LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); + if (_streamId < 0) { + LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); + return false; + } + + // Get a pointer to the codec context for the audio 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 (onlyGifv) { + if (_audioStreamId >= 0) { // should be no audio stream + return false; + } + if (dataSize() > AnimationInMemory) { + return false; + } + if (_codecContext->codec_id != AV_CODEC_ID_H264) { + return false; + } + } + av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); + if ((res = avcodec_open2(_codecContext, _codec, 0)) < 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; + } + + return true; +} + +QString FFMpegReaderImplementation::logData() const { + return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); +} + +int FFMpegReaderImplementation::duration() const { + if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; + return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; +} + +FFMpegReaderImplementation::~FFMpegReaderImplementation() { + if (_frameRead) { + av_frame_unref(_frame); + _frameRead = false; + } + if (_ioContext) av_free(_ioContext); + if (_codecContext) avcodec_close(_codecContext); + if (_swsContext) sws_freeContext(_swsContext); + if (_opened) { + avformat_close_input(&_fmtContext); + } else if (_ioBuffer) { + av_free(_ioBuffer); + } + if (_fmtContext) avformat_free_context(_fmtContext); + av_frame_free(&_frame); + freePacket(); +} + +void FFMpegReaderImplementation::rememberPacket() { + if (!_packetWas) { + _packetSize = _avpkt.size; + _packetData = _avpkt.data; + _packetWas = true; + } +} + +void FFMpegReaderImplementation::freePacket() { + if (_packetWas) { + _avpkt.size = _packetSize; + _avpkt.data = _packetData; + _packetWas = false; + av_packet_unref(&_avpkt); + } +} + +int FFMpegReaderImplementation::_read(void *opaque, uint8_t *buf, int buf_size) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + return int(l->_device->read((char*)(buf), buf_size)); +} + +int64_t FFMpegReaderImplementation::_seek(void *opaque, int64_t offset, int whence) { + FFMpegReaderImplementation *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; + case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; + case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; + } + return -1; +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/media_clip_ffmpeg.h new file mode 100644 index 000000000..3850ebf49 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_ffmpeg.h @@ -0,0 +1,89 @@ +/* +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 + +extern "C" { +#include +#include +#include +#include +} + +#include "media/media_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +class FFMpegReaderImplementation : public ReaderImplementation { +public: + + FFMpegReaderImplementation(FileLocation *location, QByteArray *data); + + bool readNextFrame() override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int nextFrameDelay() override; + bool start(bool onlyGifv) override; + + int duration() const; + QString logData() const; + + ~FFMpegReaderImplementation(); + +private: + void rememberPacket(); + void freePacket(); + + static int _read(void *opaque, uint8_t *buf, int buf_size); + static int64_t _seek(void *opaque, int64_t offset, int whence); + + uchar *_ioBuffer = nullptr; + AVIOContext *_ioContext = nullptr; + AVFormatContext *_fmtContext = nullptr; + AVCodec *_codec = nullptr; + AVCodecContext *_codecContext = nullptr; + int _streamId = 0; + AVFrame *_frame = nullptr; + bool _opened = false; + bool _hadFrame = false; + bool _frameRead = false; + + int _audioStreamId = 0; + + AVPacket _avpkt; + int _packetSize = 0; + uint8_t *_packetData = nullptr; + bool _packetWas = false; + + int _width = 0; + int _height = 0; + SwsContext *_swsContext = nullptr; + QSize _swsSize; + + int64 _frameMs = 0; + int _nextFrameDelay = 0; + int _currentFrameDelay = 0; + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_implementation.cpp b/Telegram/SourceFiles/media/media_clip_implementation.cpp new file mode 100644 index 000000000..9526074cb --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_implementation.cpp @@ -0,0 +1,43 @@ +/* +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_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +void ReaderImplementation::initDevice() { + if (_data->isEmpty()) { + if (_file.isOpen()) _file.close(); + _file.setFileName(_location->name()); + _dataSize = _file.size(); + } else { + if (_buffer.isOpen()) _buffer.close(); + _buffer.setBuffer(_data); + _dataSize = _data->size(); + } + _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_implementation.h b/Telegram/SourceFiles/media/media_clip_implementation.h new file mode 100644 index 000000000..30f34c4bc --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_implementation.h @@ -0,0 +1,60 @@ +/* +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 FileLocation; + +namespace Media { +namespace Clip { +namespace internal { + +class ReaderImplementation { +public: + + ReaderImplementation(FileLocation *location, QByteArray *data) + : _location(location) + , _data(data) { + } + virtual bool readNextFrame() = 0; + virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; + virtual int nextFrameDelay() = 0; + virtual bool start(bool onlyGifv) = 0; + virtual ~ReaderImplementation() { + } + int64 dataSize() const { + return _dataSize; + } + +protected: + FileLocation *_location; + QByteArray *_data; + QFile _file; + QBuffer _buffer; + QIODevice *_device = nullptr; + int64 _dataSize = 0; + + void initDevice(); + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.cpp b/Telegram/SourceFiles/media/media_clip_qtgif.cpp new file mode 100644 index 000000000..2ec76997c --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_qtgif.cpp @@ -0,0 +1,106 @@ +/* +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_clip_qtgif.h" + +namespace Media { +namespace Clip { +namespace internal { + +QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) { +} + +bool QtGifReaderImplementation::readNextFrame() { + if (_reader) _frameDelay = _reader->nextImageDelay(); + if (_framesLeft < 1 && !jumpToStart()) { + return false; + } + + _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it + if (!_reader->read(&_frame) || _frame.isNull()) { + return false; + } + --_framesLeft; + return true; +} + +bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { + t_assert(!_frame.isNull()); + if (size.isEmpty() || size == _frame.size()) { + int32 w = _frame.width(), h = _frame.height(); + if (to.width() == w && to.height() == h && to.format() == _frame.format()) { + if (to.byteCount() != _frame.byteCount()) { + int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine()); + for (int i = 0; i < h; ++i) { + memcpy(to.scanLine(i), _frame.constScanLine(i), bpl); + } + } else { + memcpy(to.bits(), _frame.constBits(), _frame.byteCount()); + } + } else { + to = _frame.copy(); + } + } else { + to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + hasAlpha = _frame.hasAlphaChannel(); + _frame = QImage(); + return true; +} + +int QtGifReaderImplementation::nextFrameDelay() { + return _frameDelay; +} + +bool QtGifReaderImplementation::start(bool onlyGifv) { + if (onlyGifv) return false; + return jumpToStart(); +} + +QtGifReaderImplementation::~QtGifReaderImplementation() { + deleteAndMark(_reader); +} + +bool QtGifReaderImplementation::jumpToStart() { + if (_reader && _reader->jumpToImage(0)) { + _framesLeft = _reader->imageCount(); + return true; + } + + delete _reader; + initDevice(); + _reader = new QImageReader(_device); +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + _reader->setAutoTransform(true); +#endif + if (!_reader->canRead() || !_reader->supportsAnimation()) { + return false; + } + _framesLeft = _reader->imageCount(); + if (_framesLeft < 1) { + return false; + } + return true; +} + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_qtgif.h b/Telegram/SourceFiles/media/media_clip_qtgif.h new file mode 100644 index 000000000..5e8efa063 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_qtgif.h @@ -0,0 +1,53 @@ +/* +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_clip_implementation.h" + +namespace Media { +namespace Clip { +namespace internal { + +class QtGifReaderImplementation : public ReaderImplementation { +public: + + QtGifReaderImplementation(FileLocation *location, QByteArray *data); + + bool readNextFrame() override; + bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override; + int nextFrameDelay() override; + bool start(bool onlyGifv) override; + + ~QtGifReaderImplementation(); + +private: + bool jumpToStart(); + + QImageReader *_reader = nullptr; + int _framesLeft = 0; + int _frameDelay = 0; + QImage _frame; + +}; + +} // namespace internal +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp new file mode 100644 index 000000000..ca96eb418 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -0,0 +1,731 @@ +/* +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_clip_reader.h" + +extern "C" { +#include +#include +#include +#include +} + +#include "media/media_clip_ffmpeg.h" +#include "media/media_clip_qtgif.h" +#include "mainwidget.h" +#include "mainwindow.h" + +namespace Media { +namespace Clip { +namespace { + +QVector threads; +QVector managers; + +QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { + bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); + bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); + if (badSize || needOuter || hasAlpha || request.rounded) { + int32 factor(request.factor); + bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); + if (newcache) { + cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(factor); + } + { + Painter p(&cache); + if (newcache) { + if (request.framew < request.outerw) { + p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); + p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); + } + if (request.frameh < request.outerh) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); + } + } + if (hasAlpha) { + p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); + } + QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); + if (badSize) { + p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to(position, QSize(request.framew / factor, request.frameh / factor)); + QRect from(0, 0, original.width(), original.height()); + p.drawImage(to, original, from, Qt::ColorOnly); + } else { + p.drawImage(position, original); + } + } + if (request.rounded) { + imageRound(cache); + } + return QPixmap::fromImage(cache, Qt::ColorOnly); + } + return QPixmap::fromImage(original, Qt::ColorOnly); +} + +} // namespace + +Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode) +: _callback(std_::move(callback)) +, _mode(mode) { + if (threads.size() < ClipThreadsCount) { + _threadIndex = threads.size(); + threads.push_back(new QThread()); + managers.push_back(new Manager(threads.back())); + threads.back()->start(); + } else { + _threadIndex = int32(rand_value() % threads.size()); + int32 loadLevel = 0x7FFFFFFF; + for (int32 i = 0, l = threads.size(); i < l; ++i) { + int32 level = managers.at(i)->loadLevel(); + if (level < loadLevel) { + _threadIndex = i; + loadLevel = level; + } + } + } + managers.at(_threadIndex)->append(this, location, data); +} + +Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForRequestStep) { + i = 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = (step / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep) { + i = 0; + } else if (step == WaitingForRequestStep) { + if (index) *index = 0; + return 0; + } else if (step == WaitingForFirstFrameStep) { + i = 0; + } else { + i = ((step + 2) / 2) % 3; + } + if (index) *index = i; + return _frames + i; +} + +Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) const { + int32 step = _step.loadAcquire(), i; + if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { + if (index) *index = 0; + return 0; + } + i = ((step + 4) / 2) % 3; + if (index) *index = i; + return _frames + i; +} + +void Reader::moveToNextShow() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + } else if (step == WaitingForRequestStep) { + _step.storeRelease(WaitingForFirstFrameStep); + } else if (step == WaitingForFirstFrameStep) { + } else if (!(step % 2)) { + _step.storeRelease(step + 1); + } +} + +void Reader::moveToNextWrite() const { + int32 step = _step.loadAcquire(); + if (step == WaitingForDimensionsStep) { + _step.storeRelease(WaitingForRequestStep); + } else if (step == WaitingForRequestStep) { + } else if (step == WaitingForFirstFrameStep) { + _step.storeRelease(0); + } else if (step % 2) { + _step.storeRelease((step + 1) % 6); + } +} + +void Reader::callback(Reader *reader, int32 threadIndex, Notification notification) { + // check if reader is not deleted already + if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader)) { + reader->_callback.call(notification); + } +} + +void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { + if (managers.size() <= _threadIndex) error(); + if (_state == State::Error) return; + + if (_step.loadAcquire() == WaitingForRequestStep) { + int factor = cIntRetinaFactor(); + FrameRequest request; + request.factor = factor; + request.framew = framew * factor; + request.frameh = frameh * factor; + request.outerw = outerw * factor; + request.outerh = outerh * factor; + request.rounded = rounded; + _frames[0].request = _frames[1].request = _frames[2].request = request; + moveToNextShow(); + managers.at(_threadIndex)->start(this); + } +} + +QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { + Frame *frame = frameToShow(); + t_assert(frame != 0); + + if (ms) { + frame->displayed.storeRelease(1); + if (_paused.loadAcquire()) { + _paused.storeRelease(0); + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->update(this); + } + } + } else { + frame->displayed.storeRelease(-1); // displayed, but should be paused + } + + int32 factor(cIntRetinaFactor()); + if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { + moveToNextShow(); + return frame->pix; + } + + frame->request.framew = framew * factor; + frame->request.frameh = frameh * factor; + frame->request.outerw = outerw * factor; + frame->request.outerh = outerh * factor; + + QImage cacheForResize; + frame->original.setDevicePixelRatio(factor); + frame->pix = QPixmap(); + frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); + + Frame *other = frameToWriteNext(true); + if (other) other->request = frame->request; + + moveToNextShow(); + + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->update(this); + } + + return frame->pix; +} + +bool Reader::ready() const { + if (_width && _height) return true; + + Frame *frame = frameToShow(); + if (frame) { + _width = frame->original.width(); + _height = frame->original.height(); + return true; + } + return false; +} + +int32 Reader::width() const { + return _width; +} + +int32 Reader::height() const { + return _height; +} + +State Reader::state() const { + return _state; +} + +void Reader::stop() { + if (managers.size() <= _threadIndex) error(); + if (_state != State::Error) { + managers.at(_threadIndex)->stop(this); + _width = _height = 0; + } +} + +void Reader::error() { + _state = State::Error; +} + +Reader::~Reader() { + stop(); +} + +class ReaderPrivate { +public: + + ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) { + if (_data.isEmpty() && !_location->accessEnable()) { + error(); + return; + } + _accessed = true; + } + + ProcessResult start(uint64 ms) { + if (!_implementation && !init()) { + return error(); + } + if (frame() && frame()->original.isNull()) { + if (!_implementation->readNextFrame()) { + return error(); + } + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { + return error(); + } + _width = frame()->original.width(); + _height = frame()->original.height(); + return ProcessResult::Started; + } + return ProcessResult::Wait; + } + + ProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit + if (_state == State::Error) return ProcessResult::Error; + + if (!_request.valid()) { + return start(ms); + } + + if (!_paused && ms >= _nextFrameWhen) { + return ProcessResult::Repaint; + } + return ProcessResult::Wait; + } + + ProcessResult finishProcess(uint64 ms) { + if (!readNextFrame()) { + return error(); + } + if (ms >= _nextFrameWhen && !readNextFrame(true)) { + return error(); + } + if (!renderFrame()) { + return error(); + } + return ProcessResult::CopyFrame; + } + + uint64 nextFrameDelay() { + int32 delay = _implementation->nextFrameDelay(); + return qMax(delay, 5); + } + + bool readNextFrame(bool keepup = false) { + if (!_implementation->readNextFrame()) { + return false; + } + _nextFrameWhen += nextFrameDelay(); + if (keepup) { + _nextFrameWhen = qMax(_nextFrameWhen, getms()); + } + return true; + } + + bool renderFrame() { + t_assert(frame() != 0 && _request.valid()); + if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { + return false; + } + frame()->original.setDevicePixelRatio(_request.factor); + frame()->pix = QPixmap(); + frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); + frame()->when = _nextFrameWhen; + return true; + } + + bool init() { + if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { + QFile f(_location->name()); + if (f.open(QIODevice::ReadOnly)) { + _data = f.readAll(); + if (f.error() != QFile::NoError) { + _data = QByteArray(); + } + } + } + + _implementation = new internal::FFMpegReaderImplementation(_location, &_data); +// _implementation = new QtGifReaderImplementation(_location, &_data); + return _implementation->start(false); + } + + ProcessResult error() { + stop(); + _state = State::Error; + return ProcessResult::Error; + } + + void stop() { + delete _implementation; + _implementation = 0; + + if (_location) { + if (_accessed) { + _location->accessDisable(); + } + delete _location; + _location = 0; + } + _accessed = false; + } + + ~ReaderPrivate() { + stop(); + deleteAndMark(_location); + deleteAndMark(_implementation); + _data.clear(); + } + +private: + + Reader *_interface; + State _state = State::Reading; + + QByteArray _data; + FileLocation *_location; + bool _accessed = false; + + QBuffer _buffer; + internal::ReaderImplementation *_implementation = nullptr; + + FrameRequest _request; + struct Frame { + QPixmap pix; + QImage original, cache; + bool alpha = true; + uint64 when = 0; + }; + Frame _frames[3]; + int _frame = 0; + Frame *frame() { + return _frames + _frame; + } + + int _width = 0; + int _height = 0; + + uint64 _nextFrameWhen = 0; + + bool _paused = false; + + friend class Manager; + +}; + +Manager::Manager(QThread *thread) : _processingInThread(0), _needReProcess(false) { + moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(thread, SIGNAL(finished()), this, SLOT(finish())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + + _timer.setSingleShot(true); + _timer.moveToThread(thread); + connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); + + anim::registerClipManager(this); +} + +void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) { + reader->_private = new ReaderPrivate(reader, location, data); + _loadLevel.fetchAndAddRelaxed(AverageGifSize); + update(reader); +} + +void Manager::start(Reader *reader) { + update(reader); +} + +void Manager::update(Reader *reader) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator i = _readerPointers.constFind(reader); + if (i == _readerPointers.cend()) { + lock.unlock(); + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, MutableAtomicInt(1)); + } else { + i->v.storeRelease(1); + } + emit processDelayed(); +} + +void Manager::stop(Reader *reader) { + if (!carries(reader)) return; + + QWriteLocker lock(&_readerPointersMutex); + _readerPointers.remove(reader); + emit processDelayed(); +} + +bool Manager::carries(Reader *reader) const { + QReadLocker lock(&_readerPointersMutex); + return _readerPointers.contains(reader); +} + +Manager::ReaderPointers::iterator Manager::unsafeFindReaderPointer(ReaderPrivate *reader) { + ReaderPointers::iterator it = _readerPointers.find(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); +} + +Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(ReaderPrivate *reader) const { + ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); + + // could be a new reader which was realloced in the same address + return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); +} + +bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (result == ProcessResult::Error) { + if (it != _readerPointers.cend()) { + it.key()->error(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + + lock.unlock(); + QWriteLocker lock(&_readerPointersMutex); + ReaderPointers::iterator i = unsafeFindReaderPointer(reader); + if (i != _readerPointers.cend()) _readerPointers.erase(i); + } + return false; + } + if (it == _readerPointers.cend()) { + return false; + } + + if (result == ProcessResult::Started) { + _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); + } + if (!reader->_paused && result == ProcessResult::Repaint) { + int32 ishowing, iprevious; + Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); + t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); + if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown + if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { + reader->_paused = true; + it.key()->_paused.storeRelease(1); + result = ProcessResult::Paused; + } + } + } + if (result == ProcessResult::Started || result == ProcessResult::CopyFrame) { + t_assert(reader->_frame >= 0); + Reader::Frame *frame = it.key()->_frames + reader->_frame; + frame->clear(); + frame->pix = reader->frame()->pix; + frame->original = reader->frame()->original; + frame->displayed.storeRelease(0); + if (result == ProcessResult::Started) { + reader->_nextFrameWhen = ms; + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + } + } else if (result == ProcessResult::Paused) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationReinit); + } else if (result == ProcessResult::Repaint) { + it.key()->moveToNextWrite(); + emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint); + } + return true; +} + +Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) { + if (!handleProcessResult(reader, result, ms)) { + _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); + delete reader; + return ResultHandleRemove; + } + + _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (_processingInThread->isInterruptionRequested()) { + return ResultHandleStop; + } + + if (result == ProcessResult::Repaint) { + { + QReadLocker lock(&_readerPointersMutex); + ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); + if (it != _readerPointers.cend()) { + int32 index = 0; + Reader *r = it.key(); + Reader::Frame *frame = it.key()->frameToWrite(&index); + if (frame) { + frame->clear(); + } else { + t_assert(!reader->_request.valid()); + } + reader->_frame = index; + } + } + return handleResult(reader, reader->finishProcess(ms), ms); + } + + return ResultHandleContinue; +} + +void Manager::process() { + if (_processingInThread) { + _needReProcess = true; + return; + } + + _timer.stop(); + _processingInThread = thread(); + + uint64 ms = getms(), minms = ms + 86400 * 1000ULL; + { + QReadLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + if (it->v.loadAcquire()) { + Readers::iterator i = _readers.find(it.key()->_private); + if (i == _readers.cend()) { + _readers.insert(it.key()->_private, 0); + } else { + i.value() = ms; + if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { + i.key()->_paused = false; + } + } + Reader::Frame *frame = it.key()->frameToWrite(); + if (frame) it.key()->_private->_request = frame->request; + it->v.storeRelease(0); + } + } + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + ReaderPrivate *reader = i.key(); + if (i.value() <= ms) { + ResultHandleState state = handleResult(reader, reader->process(ms), ms); + if (state == ResultHandleRemove) { + i = _readers.erase(i); + continue; + } else if (state == ResultHandleStop) { + _processingInThread = 0; + return; + } + ms = getms(); + i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); + } + if (!reader->_paused && i.value() < minms) { + minms = i.value(); + } + ++i; + } + + ms = getms(); + if (_needReProcess || minms <= ms) { + _needReProcess = false; + _timer.start(1); + } else { + _timer.start(minms - ms); + } + + _processingInThread = 0; +} + +void Manager::finish() { + _timer.stop(); + clear(); +} + +void Manager::clear() { + { + QWriteLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { + it.key()->_private = 0; + } + _readerPointers.clear(); + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { + delete i.key(); + } + _readers.clear(); +} + +Manager::~Manager() { + clear(); +} + +MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover) { + FileLocation localloc(StorageFilePartial, fname); + QByteArray localdata(data); + + auto reader = std_::make_unique(&localloc, &localdata); + if (reader->start(true)) { + bool hasAlpha = false; + if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { + if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { + if (hasAlpha) { + QImage cacheForResize; + FrameRequest request; + request.framew = request.outerw = cover.width(); + request.frameh = request.outerh = cover.height(); + request.factor = 1; + cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); + } + int duration = reader->duration(); + return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); + } + } + } + return MTP_documentAttributeFilename(MTP_string(fname)); +} + +void Finish() { + if (!threads.isEmpty()) { + for (int32 i = 0, l = threads.size(); i < l; ++i) { + threads.at(i)->quit(); + DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); + threads.at(i)->wait(); + delete managers.at(i); + delete threads.at(i); + } + threads.clear(); + managers.clear(); + } +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h new file mode 100644 index 000000000..a0b160e70 --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -0,0 +1,222 @@ +/* +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 FileLocation; + +namespace Media { +namespace Clip { + +enum class State { + Reading, + Error, +}; + +struct FrameRequest { + bool valid() const { + return factor > 0; + } + int factor = 0; + int framew = 0; + int frameh = 0; + int outerw = 0; + int outerh = 0; + bool rounded = false; +}; + +enum ReaderSteps { + WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size + WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request + WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay +}; + +class ReaderPrivate; +class Reader { +public: + + using Callback = Function; + enum class Mode { + Gif, + Video, + }; + + Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif); + static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted + + void setAutoplay() { + _autoplay = true; + } + bool autoplay() const { + return _autoplay; + } + + void start(int framew, int frameh, int outerw, int outerh, bool rounded); + QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms); + QPixmap frameOriginal() const { + Frame *frame = frameToShow(); + if (!frame) return QPixmap(); + QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); + result.detach(); + return result; + } + bool currentDisplayed() const { + Frame *frame = frameToShow(); + return frame ? (frame->displayed.loadAcquire() != 0) : true; + } + bool paused() const { + return _paused.loadAcquire(); + } + int threadIndex() const { + return _threadIndex; + } + + int width() const; + int height() const; + + State state() const; + bool started() const { + int step = _step.loadAcquire(); + return (step == WaitingForFirstFrameStep) || (step >= 0); + } + bool ready() const; + + void stop(); + void error(); + + ~Reader(); + +private: + + Callback _callback; + Mode _mode; + + State _state = State::Reading; + + mutable int _width = 0; + mutable int _height = 0; + + // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 + mutable QAtomicInt _step = WaitingForDimensionsStep; + struct Frame { + Frame() : displayed(false) { + } + void clear() { + pix = QPixmap(); + original = QImage(); + } + QPixmap pix; + QImage original; + FrameRequest request; + QAtomicInt displayed; + }; + mutable Frame _frames[3]; + Frame *frameToShow(int *index = 0) const; // 0 means not ready + Frame *frameToWrite(int *index = 0) const; // 0 means not ready + Frame *frameToWriteNext(bool check, int *index = 0) const; + void moveToNextShow() const; + void moveToNextWrite() const; + + QAtomicInt _paused = 0; + int32 _threadIndex; + + bool _autoplay = false; + + friend class Manager; + + ReaderPrivate *_private = nullptr; + +}; + +enum class ProcessResult { + Error, + Started, + Paused, + Repaint, + CopyFrame, + Wait, +}; + +class Manager : public QObject { + Q_OBJECT + +public: + + Manager(QThread *thread); + int32 loadLevel() const { + return _loadLevel.load(); + } + void append(Reader *reader, const FileLocation &location, const QByteArray &data); + void start(Reader *reader); + void update(Reader *reader); + void stop(Reader *reader); + bool carries(Reader *reader) const; + ~Manager(); + +signals: + void processDelayed(); + + void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); + +public slots: + void process(); + void finish(); + +private: + + void clear(); + + QAtomicInt _loadLevel; + struct MutableAtomicInt { + MutableAtomicInt(int value) : v(value) { + } + mutable QAtomicInt v; + }; + typedef QMap ReaderPointers; + ReaderPointers _readerPointers; + mutable QReadWriteLock _readerPointersMutex; + + ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const; + ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader); + + bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms); + + enum ResultHandleState { + ResultHandleRemove, + ResultHandleStop, + ResultHandleContinue, + }; + ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms); + + typedef QMap Readers; + Readers _readers; + + QTimer _timer; + QThread *_processingInThread; + bool _needReProcess; + +}; + +MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover); + +void Finish(); + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 9f3d29fab..dae547c5f 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -26,52 +26,55 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "application.h" #include "ui/filedialog.h" +#include "media/media_clip_reader.h" namespace { - class SaveMsgClickHandler : public ClickHandler { - public: - SaveMsgClickHandler(MediaView *view) : _view(view) { - } +class SaveMsgClickHandler : public ClickHandler { +public: - void onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton) { - _view->showSaveMsgFile(); - } - } - - private: - - MediaView *_view; - }; - - TextParseOptions _captionTextOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - TextParseOptions _captionBotOptions = { - TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir - }; - - bool typeHasMediaOverview(MediaOverviewType type) { - switch (type) { - case OverviewPhotos: - case OverviewVideos: - case OverviewMusicFiles: - case OverviewFiles: - case OverviewVoiceFiles: - case OverviewLinks: return true; - default: break; - } - return false; + SaveMsgClickHandler(MediaView *view) : _view(view) { } + + void onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton) { + _view->showSaveMsgFile(); + } + } + +private: + + MediaView *_view; +}; + +TextParseOptions _captionTextOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; +TextParseOptions _captionBotOptions = { + TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +bool typeHasMediaOverview(MediaOverviewType type) { + switch (type) { + case OverviewPhotos: + case OverviewVideos: + case OverviewMusicFiles: + case OverviewFiles: + case OverviewVoiceFiles: + case OverviewLinks: return true; + default: break; + } + return false; } +} // namespace + MediaView::MediaView() : TWidget(App::wnd()) , _animStarted(getms()) , _docDownload(this, lang(lng_media_download), st::mvDocLink) @@ -219,7 +222,7 @@ bool MediaView::gifShown() const { _gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false); const_cast(this)->_current = QPixmap(); } - return _gif->state() != ClipError; + return _gif->state() != Media::Clip::State::Error; } return false; } @@ -479,13 +482,13 @@ void MediaView::step_radial(uint64 ms, bool timer) { if (timer && _radial.animating()) { update(radialRect()); } - if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation())) { - if (!_doc->data().isEmpty() && _doc->isAnimation()) { + if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) { + if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); } else { const FileLocation &location(_doc->location(true)); if (location.accessEnable()) { - if (_doc->isAnimation() || QImageReader(location.name()).canRead()) { + if (_doc->isAnimation() || _doc->isVideo() || QImageReader(location.name()).canRead()) { displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid)); } location.accessDisable(); @@ -654,13 +657,15 @@ void MediaView::onDocClick() { } } -void MediaView::clipCallback(ClipReaderNotification notification) { +void MediaView::clipCallback(Media::Clip::Notification notification) { + using namespace Media::Clip; + if (!_gif) return; switch (notification) { - case ClipReaderReinit: { + case NotificationReinit: { if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) { - if (_gif->state() == ClipError) { + if (_gif->state() == State::Error) { _current = QPixmap(); } displayDocument(_doc, item); @@ -669,7 +674,7 @@ void MediaView::clipCallback(ClipReaderNotification notification) { } } break; - case ClipReaderRepaint: { + case NotificationRepaint: { if (!_gif->currentDisplayed()) { update(_x, _y, _w, _h); } @@ -1027,7 +1032,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) { } void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL - if (!doc || !doc->isAnimation() || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { + if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) { stopGif(); } _doc = doc; @@ -1049,20 +1054,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty _doc->automaticLoad(item); const FileLocation &location(_doc->location(true)); - if (!_doc->data().isEmpty() && _doc->isAnimation()) { + if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) { if (!_gif) { if (_doc->dimensions.width() && _doc->dimensions.height()) { _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } - _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); } } else if (location.accessEnable()) { - if (_doc->isAnimation()) { + if (_doc->isAnimation() || _doc->isVideo()) { if (!_gif) { if (_doc->dimensions.width() && _doc->dimensions.height()) { _current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height()); } - _gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback)); + _gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback)); } } else { if (QImageReader(location.name()).canRead()) { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index ae07799ac..904e4bd41 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -71,7 +71,7 @@ public: void activateControls(); void onDocClick(); - void clipCallback(ClipReaderNotification notification); + void clipCallback(Media::Clip::Notification notification); PeerData *ui_getPeerForMouseAction(); ~MediaView(); @@ -169,7 +169,7 @@ private: bool _pressed = false; int32 _dragging = 0; QPixmap _current; - ClipReader *_gif = nullptr; + Media::Clip::Reader *_gif = nullptr; int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full bool fileShown() const; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 4b3e006d7..b5c7eb34f 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -947,9 +947,10 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } bool playVoice = data->voice() && audioPlayer(); bool playMusic = data->song() && audioPlayer(); + bool playVideo = data->isVideo() && audioPlayer(); bool playAnimation = data->isAnimation() && item && item->getMedia(); const FileLocation &location(data->location(true)); - if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playAnimation))) { + if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { if (playVoice) { AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; @@ -975,6 +976,16 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { audioPlayer()->play(song); if (App::main()) App::main()->documentPlayProgress(song); } + } else if (playVideo) { + if (!data->data().isEmpty()) { + App::wnd()->showDocument(data, item); + } else if (location.accessEnable()) { + App::wnd()->showDocument(data, item); + location.accessDisable(); + } else { + psOpenFile(location.name()); + } + if (App::main()) App::main()->mediaMarkRead(data); } else if (data->voice() || data->isVideo()) { psOpenFile(location.name()); if (App::main()) App::main()->mediaMarkRead(data); diff --git a/Telegram/SourceFiles/ui/animation.cpp b/Telegram/SourceFiles/ui/animation.cpp index 03b194fe9..8fcc98cce 100644 --- a/Telegram/SourceFiles/ui/animation.cpp +++ b/Telegram/SourceFiles/ui/animation.cpp @@ -19,99 +19,85 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" - #include "animation.h" -extern "C" { -#include -#include -#include -#include -} - -#include "mainwidget.h" -#include "mainwindow.h" +#include "media/media_clip_reader.h" namespace { - AnimationManager *_manager = 0; - QVector _clipThreads; - QVector _clipManagers; -}; + +AnimationManager *_manager = nullptr; + +} // namespace namespace anim { - float64 linear(const float64 &delta, const float64 &dt) { - return delta * dt; - } +float64 linear(const float64 &delta, const float64 &dt) { + return delta * dt; +} - float64 sineInOut(const float64 &delta, const float64 &dt) { - return -(delta / 2) * (cos(M_PI * dt) - 1); - } +float64 sineInOut(const float64 &delta, const float64 &dt) { + return -(delta / 2) * (cos(M_PI * dt) - 1); +} - float64 halfSine(const float64 &delta, const float64 &dt) { - return delta * sin(M_PI * dt / 2); - } +float64 halfSine(const float64 &delta, const float64 &dt) { + return delta * sin(M_PI * dt / 2); +} - float64 easeOutBack(const float64 &delta, const float64 &dt) { - static const float64 s = 1.70158; +float64 easeOutBack(const float64 &delta, const float64 &dt) { + static const float64 s = 1.70158; - const float64 t = dt - 1; - return delta * (t * t * ((s + 1) * t + s) + 1); - } + const float64 t = dt - 1; + return delta * (t * t * ((s + 1) * t + s) + 1); +} - float64 easeInCirc(const float64 &delta, const float64 &dt) { - return -delta * (sqrt(1 - dt * dt) - 1); - } +float64 easeInCirc(const float64 &delta, const float64 &dt) { + return -delta * (sqrt(1 - dt * dt) - 1); +} - float64 easeOutCirc(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1; - return delta * sqrt(1 - t * t); - } +float64 easeOutCirc(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1; + return delta * sqrt(1 - t * t); +} - float64 easeInCubic(const float64 &delta, const float64 &dt) { - return delta * dt * dt * dt; - } +float64 easeInCubic(const float64 &delta, const float64 &dt) { + return delta * dt * dt * dt; +} - float64 easeOutCubic(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1; - return delta * (t * t * t + 1); - } +float64 easeOutCubic(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1; + return delta * (t * t * t + 1); +} - float64 easeInQuint(const float64 &delta, const float64 &dt) { - const float64 t2 = dt * dt; - return delta * t2 * t2 * dt; - } +float64 easeInQuint(const float64 &delta, const float64 &dt) { + const float64 t2 = dt * dt; + return delta * t2 * t2 * dt; +} - float64 easeOutQuint(const float64 &delta, const float64 &dt) { - const float64 t = dt - 1, t2 = t * t; - return delta * (t2 * t2 * t + 1); - } +float64 easeOutQuint(const float64 &delta, const float64 &dt) { + const float64 t = dt - 1, t2 = t * t; + return delta * (t2 * t2 * t + 1); +} - void startManager() { - stopManager(); +void startManager() { + stopManager(); - _manager = new AnimationManager(); - - } - - void stopManager() { - delete _manager; - _manager = 0; - if (!_clipThreads.isEmpty()) { - for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { - _clipThreads.at(i)->quit(); - DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i)); - _clipThreads.at(i)->wait(); - delete _clipManagers.at(i); - delete _clipThreads.at(i); - } - _clipThreads.clear(); - _clipManagers.clear(); - } - } + _manager = new AnimationManager(); } +void stopManager() { + delete _manager; + _manager = nullptr; + + Media::Clip::Finish(); +} + +void registerClipManager(Media::Clip::Manager *manager) { + manager->connect(manager, SIGNAL(callback(Media::Clip::Reader*,qint32,qint32)), _manager, SLOT(clipCallback(Media::Clip::Reader*,qint32,qint32))); +} + +} // anim + void Animation::start() { if (!_manager) return; @@ -190,1137 +176,6 @@ void AnimationManager::timeout() { } } -void AnimationManager::clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification) { - ClipReader::callback(reader, threadIndex, ClipReaderNotification(notification)); -} - -QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) { - bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); - bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); - if (badSize || needOuter || hasAlpha || request.rounded) { - int32 factor(request.factor); - bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh); - if (newcache) { - cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); - cache.setDevicePixelRatio(factor); - } - { - Painter p(&cache); - if (newcache) { - if (request.framew < request.outerw) { - p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black); - p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black); - } - if (request.frameh < request.outerh) { - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black); - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black); - } - } - if (hasAlpha) { - p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white); - } - QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); - if (badSize) { - p.setRenderHint(QPainter::SmoothPixmapTransform); - QRect to(position, QSize(request.framew / factor, request.frameh / factor)); - QRect from(0, 0, original.width(), original.height()); - p.drawImage(to, original, from, Qt::ColorOnly); - } else { - p.drawImage(position, original); - } - } - if (request.rounded) { - imageRound(cache); - } - return QPixmap::fromImage(cache, Qt::ColorOnly); - } - return QPixmap::fromImage(original, Qt::ColorOnly); -} - -ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback) -: _callback(std_::move(callback)) -, _state(ClipReading) -, _width(0) -, _height(0) -, _step(WaitingForDimensionsStep) -, _paused(0) -, _autoplay(false) -, _private(0) { - if (_clipThreads.size() < ClipThreadsCount) { - _threadIndex = _clipThreads.size(); - _clipThreads.push_back(new QThread()); - _clipManagers.push_back(new ClipReadManager(_clipThreads.back())); - _clipThreads.back()->start(); - } else { - _threadIndex = int32(rand_value() % _clipThreads.size()); - int32 loadLevel = 0x7FFFFFFF; - for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { - int32 level = _clipManagers.at(i)->loadLevel(); - if (level < loadLevel) { - _threadIndex = i; - loadLevel = level; - } - } - } - _clipManagers.at(_threadIndex)->append(this, location, data); -} - -ClipReader::Frame *ClipReader::frameToShow(int32 *index) const { // 0 means not ready - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep) { - if (index) *index = 0; - return 0; - } else if (step == WaitingForRequestStep) { - i = 0; - } else if (step == WaitingForFirstFrameStep) { - i = 0; - } else { - i = (step / 2) % 3; - } - if (index) *index = i; - return _frames + i; -} - -ClipReader::Frame *ClipReader::frameToWrite(int32 *index) const { // 0 means not ready - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep) { - i = 0; - } else if (step == WaitingForRequestStep) { - if (index) *index = 0; - return 0; - } else if (step == WaitingForFirstFrameStep) { - i = 0; - } else { - i = ((step + 2) / 2) % 3; - } - if (index) *index = i; - return _frames + i; -} - -ClipReader::Frame *ClipReader::frameToWriteNext(bool checkNotWriting, int32 *index) const { - int32 step = _step.loadAcquire(), i; - if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) { - if (index) *index = 0; - return 0; - } - i = ((step + 4) / 2) % 3; - if (index) *index = i; - return _frames + i; -} - -void ClipReader::moveToNextShow() const { - int32 step = _step.loadAcquire(); - if (step == WaitingForDimensionsStep) { - } else if (step == WaitingForRequestStep) { - _step.storeRelease(WaitingForFirstFrameStep); - } else if (step == WaitingForFirstFrameStep) { - } else if (!(step % 2)) { - _step.storeRelease(step + 1); - } -} - -void ClipReader::moveToNextWrite() const { - int32 step = _step.loadAcquire(); - if (step == WaitingForDimensionsStep) { - _step.storeRelease(WaitingForRequestStep); - } else if (step == WaitingForRequestStep) { - } else if (step == WaitingForFirstFrameStep) { - _step.storeRelease(0); - } else if (step % 2) { - _step.storeRelease((step + 1) % 6); - } -} - -void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { - // check if reader is not deleted already - if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { - reader->_callback.call(notification); - } -} - -void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { - if (_clipManagers.size() <= _threadIndex) error(); - if (_state == ClipError) return; - - if (_step.loadAcquire() == WaitingForRequestStep) { - int32 factor(cIntRetinaFactor()); - ClipFrameRequest request; - request.factor = factor; - request.framew = framew * factor; - request.frameh = frameh * factor; - request.outerw = outerw * factor; - request.outerh = outerh * factor; - request.rounded = rounded; - _frames[0].request = _frames[1].request = _frames[2].request = request; - moveToNextShow(); - _clipManagers.at(_threadIndex)->start(this); - } -} - -QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) { - Frame *frame = frameToShow(); - t_assert(frame != 0); - - if (ms) { - frame->displayed.storeRelease(1); - if (_paused.loadAcquire()) { - _paused.storeRelease(0); - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->update(this); - } - } - } else { - frame->displayed.storeRelease(-1); // displayed, but should be paused - } - - int32 factor(cIntRetinaFactor()); - if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) { - moveToNextShow(); - return frame->pix; - } - - frame->request.framew = framew * factor; - frame->request.frameh = frameh * factor; - frame->request.outerw = outerw * factor; - frame->request.outerh = outerh * factor; - - QImage cacheForResize; - frame->original.setDevicePixelRatio(factor); - frame->pix = QPixmap(); - frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize); - - Frame *other = frameToWriteNext(true); - if (other) other->request = frame->request; - - moveToNextShow(); - - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->update(this); - } - - return frame->pix; -} - -bool ClipReader::ready() const { - if (_width && _height) return true; - - Frame *frame = frameToShow(); - if (frame) { - _width = frame->original.width(); - _height = frame->original.height(); - return true; - } - return false; -} - -int32 ClipReader::width() const { - return _width; -} - -int32 ClipReader::height() const { - return _height; -} - -ClipState ClipReader::state() const { - return _state; -} - -void ClipReader::stop() { - if (_clipManagers.size() <= _threadIndex) error(); - if (_state != ClipError) { - _clipManagers.at(_threadIndex)->stop(this); - _width = _height = 0; - } -} - -void ClipReader::error() { - _state = ClipError; -} - -ClipReader::~ClipReader() { - stop(); -} - -class ClipReaderImplementation { -public: - - ClipReaderImplementation(FileLocation *location, QByteArray *data) - : _location(location) - , _data(data) - , _device(0) - , _dataSize(0) { - } - virtual bool readNextFrame() = 0; - virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; - virtual int32 nextFrameDelay() = 0; - virtual bool start(bool onlyGifv) = 0; - virtual ~ClipReaderImplementation() { - } - int64 dataSize() const { - return _dataSize; - } - -protected: - FileLocation *_location; - QByteArray *_data; - QFile _file; - QBuffer _buffer; - QIODevice *_device; - int64 _dataSize; - - void initDevice() { - if (_data->isEmpty()) { - if (_file.isOpen()) _file.close(); - _file.setFileName(_location->name()); - _dataSize = _file.size(); - } else { - if (_buffer.isOpen()) _buffer.close(); - _buffer.setBuffer(_data); - _dataSize = _data->size(); - } - _device = _data->isEmpty() ? static_cast(&_file) : static_cast(&_buffer); - } - -}; - -class QtGifReaderImplementation : public ClipReaderImplementation{ -public: - - QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) - , _reader(0) - , _framesLeft(0) - , _frameDelay(0) { - } - - bool readNextFrame() { - if (_reader) _frameDelay = _reader->nextImageDelay(); - if (_framesLeft < 1 && !jumpToStart()) { - return false; - } - - _frame = QImage(); // QGifHandler always reads first to internal QImage and returns it - if (!_reader->read(&_frame) || _frame.isNull()) { - return false; - } - --_framesLeft; - return true; - } - - bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { - t_assert(!_frame.isNull()); - if (size.isEmpty() || size == _frame.size()) { - int32 w = _frame.width(), h = _frame.height(); - if (to.width() == w && to.height() == h && to.format() == _frame.format()) { - if (to.byteCount() != _frame.byteCount()) { - int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine()); - for (int i = 0; i < h; ++i) { - memcpy(to.scanLine(i), _frame.constScanLine(i), bpl); - } - } else { - memcpy(to.bits(), _frame.constBits(), _frame.byteCount()); - } - } else { - to = _frame.copy(); - } - } else { - to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - hasAlpha = _frame.hasAlphaChannel(); - _frame = QImage(); - return true; - } - - int32 nextFrameDelay() { - return _frameDelay; - } - - bool start(bool onlyGifv) { - if (onlyGifv) return false; - return jumpToStart(); - } - - ~QtGifReaderImplementation() { - deleteAndMark(_reader); - } - -private: - QImageReader *_reader; - int32 _framesLeft, _frameDelay; - QImage _frame; - - bool jumpToStart() { - if (_reader && _reader->jumpToImage(0)) { - _framesLeft = _reader->imageCount(); - return true; - } - - delete _reader; - initDevice(); - _reader = new QImageReader(_device); -#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) - _reader->setAutoTransform(true); -#endif - if (!_reader->canRead() || !_reader->supportsAnimation()) { - return false; - } - _framesLeft = _reader->imageCount(); - if (_framesLeft < 1) { - return false; - } - return true; - } - -}; - -class FFMpegReaderImplementation : public ClipReaderImplementation { -public: - - FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ClipReaderImplementation(location, data) - , _ioBuffer(0) - , _ioContext(0) - , _fmtContext(0) - , _codec(0) - , _codecContext(0) - , _streamId(0) - , _frame(0) - , _opened(false) - , _hadFrame(false) - , _frameRead(false) - , _packetSize(0) - , _packetData(0) - , _packetWas(false) - , _width(0) - , _height(0) - , _swsContext(0) - , _frameMs(0) - , _nextFrameDelay(0) - , _currentFrameDelay(0) { - _frame = av_frame_alloc(); - av_init_packet(&_avpkt); - _avpkt.data = NULL; - _avpkt.size = 0; - } - - bool readNextFrame() { - if (_frameRead) { - av_frame_unref(_frame); - _frameRead = false; - } - - int res; - while (true) { - if (_avpkt.size > 0) { // previous packet not finished - res = 0; - } else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) { - if (res != AVERROR_EOF || !_hadFrame) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - - bool finished = (res < 0); - if (finished) { - _avpkt.data = NULL; - _avpkt.size = 0; - } else { - rememberPacket(); - } - - int32 got_frame = 0; - int32 decoded = _avpkt.size; - if (_avpkt.stream_index == _streamId) { - if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - - if (res == AVERROR_INVALIDDATA) { // try to skip bad packet - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; - } - - if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file - return false; - } - freePacket(); - _avpkt.data = NULL; - _avpkt.size = 0; - continue; - } - if (res > 0) decoded = res; - } - if (!finished) { - _avpkt.data += decoded; - _avpkt.size -= decoded; - if (_avpkt.size <= 0) freePacket(); - } - - if (got_frame) { - int64 duration = av_frame_get_pkt_duration(_frame); - int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts; - int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - _currentFrameDelay = _nextFrameDelay; - if (_frameMs + _currentFrameDelay < frameMs) { - _currentFrameDelay = int32(frameMs - _frameMs); - } - if (duration == AV_NOPTS_VALUE) { - _nextFrameDelay = 0; - } else { - _nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - _frameMs = frameMs; - - _hadFrame = _frameRead = true; - return true; - } - - if (finished) { - 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) { - if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) { - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - } - } - } - avcodec_flush_buffers(_codecContext); - _hadFrame = false; - _frameMs = 0; - } - } - - return false; - } - - bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) { - t_assert(_frameRead); - _frameRead = false; - - if (!_width || !_height) { - _width = _frame->width; - _height = _frame->height; - if (!_width || !_height) { - LOG(("Gif Error: Bad frame size %1").arg(logData())); - return false; - } - } - - QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); - if (to.isNull() || to.size() != toSize) { - to = QImage(toSize, QImage::Format_ARGB32); - } - hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA)); - if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) { - int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); - uchar *s = _frame->data[0], *d = to.bits(); - for (int32 i = 0, l = _frame->height; i < l; ++i) { - memcpy(d + i * dbpl, s + i * sbpl, bpl); - } - } else { - if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { - _swsSize = toSize; - _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); - } - uint8_t * toData[1] = { to.bits() }; - int toLinesize[1] = { to.bytesPerLine() }, res; - if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { - LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); - return false; - } - } - - av_frame_unref(_frame); - return true; - } - - int32 nextFrameDelay() { - return _currentFrameDelay; - } - - QString logData() const { - return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size()); - } - - bool start(bool onlyGifv) { - initDevice(); - if (!_device->open(QIODevice::ReadOnly)) { - LOG(("Gif Error: Unable to open device %1").arg(logData())); - return false; - } - _ioBuffer = (uchar*)av_malloc(AVBlockSize); - _ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek); - _fmtContext = avformat_alloc_context(); - if (!_fmtContext) { - LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData())); - return false; - } - _fmtContext->pb = _ioContext; - - int res = 0; - char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; - if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) { - _ioBuffer = 0; - - LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - _opened = true; - - if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) { - LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); - return false; - } - - _streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0); - if (_streamId < 0) { - LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId))); - return false; - } - - // Get a pointer to the codec context for the audio stream - _codecContext = _fmtContext->streams[_streamId]->codec; - _codec = avcodec_find_decoder(_codecContext->codec_id); - - if (onlyGifv) { - if (av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) >= 0) { // should be no audio stream - return false; - } - if (dataSize() > AnimationInMemory) { - return false; - } - if (_codecContext->codec_id != AV_CODEC_ID_H264) { - return false; - } - } - av_opt_set_int(_codecContext, "refcounted_frames", 1, 0); - if ((res = avcodec_open2(_codecContext, _codec, 0)) < 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; - } - - return true; - } - - int32 duration() const { - if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0; - return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den; - } - - ~FFMpegReaderImplementation() { - if (_frameRead) { - av_frame_unref(_frame); - _frameRead = false; - } - if (_ioContext) av_free(_ioContext); - if (_codecContext) avcodec_close(_codecContext); - if (_swsContext) sws_freeContext(_swsContext); - if (_opened) { - avformat_close_input(&_fmtContext); - } else if (_ioBuffer) { - av_free(_ioBuffer); - } - if (_fmtContext) avformat_free_context(_fmtContext); - av_frame_free(&_frame); - freePacket(); - } - -private: - uchar *_ioBuffer; - AVIOContext *_ioContext; - AVFormatContext *_fmtContext; - AVCodec *_codec; - AVCodecContext *_codecContext; - int32 _streamId; - AVFrame *_frame; - bool _opened, _hadFrame, _frameRead; - - AVPacket _avpkt; - int _packetSize; - uint8_t *_packetData; - bool _packetWas; - void rememberPacket() { - if (!_packetWas) { - _packetSize = _avpkt.size; - _packetData = _avpkt.data; - _packetWas = true; - } - } - void freePacket() { - if (_packetWas) { - _avpkt.size = _packetSize; - _avpkt.data = _packetData; - _packetWas = false; - av_packet_unref(&_avpkt); - } - } - - int32 _width, _height; - SwsContext *_swsContext; - QSize _swsSize; - - int64 _frameMs; - int32 _nextFrameDelay, _currentFrameDelay; - - static int _read(void *opaque, uint8_t *buf, int buf_size) { - FFMpegReaderImplementation *l = reinterpret_cast(opaque); - return int(l->_device->read((char*)(buf), buf_size)); - } - - static int64_t _seek(void *opaque, int64_t offset, int whence) { - FFMpegReaderImplementation *l = reinterpret_cast(opaque); - - switch (whence) { - case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1; - case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1; - case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1; - } - return -1; - } - -}; - -class ClipReaderPrivate { -public: - - ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) - , _state(ClipReading) - , _data(data) - , _location(_data.isEmpty() ? new FileLocation(location) : 0) - , _accessed(false) - , _implementation(0) - , _frame(0) - , _width(0) - , _height(0) - , _nextFrameWhen(0) - , _paused(false) { - if (_data.isEmpty() && !_location->accessEnable()) { - error(); - return; - } - _accessed = true; - } - - ClipProcessResult start(uint64 ms) { - if (!_implementation && !init()) { - return error(); - } - if (frame() && frame()->original.isNull()) { - if (!_implementation->readNextFrame()) { - return error(); - } - if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) { - return error(); - } - _width = frame()->original.width(); - _height = frame()->original.height(); - return ClipProcessStarted; - } - return ClipProcessWait; - } - - ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit - if (_state == ClipError) return ClipProcessError; - - if (!_request.valid()) { - return start(ms); - } - - if (!_paused && ms >= _nextFrameWhen) { - return ClipProcessRepaint; - } - return ClipProcessWait; - } - - ClipProcessResult finishProcess(uint64 ms) { - if (!readNextFrame()) { - return error(); - } - if (ms >= _nextFrameWhen && !readNextFrame(true)) { - return error(); - } - if (!renderFrame()) { - return error(); - } - return ClipProcessCopyFrame; - } - - uint64 nextFrameDelay() { - int32 delay = _implementation->nextFrameDelay(); - return qMax(delay, 5); - } - - bool readNextFrame(bool keepup = false) { - if (!_implementation->readNextFrame()) { - return false; - } - _nextFrameWhen += nextFrameDelay(); - if (keepup) { - _nextFrameWhen = qMax(_nextFrameWhen, getms()); - } - return true; - } - - bool renderFrame() { - t_assert(frame() != 0 && _request.valid()); - if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) { - return false; - } - frame()->original.setDevicePixelRatio(_request.factor); - frame()->pix = QPixmap(); - frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache); - frame()->when = _nextFrameWhen; - return true; - } - - bool init() { - if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { - QFile f(_location->name()); - if (f.open(QIODevice::ReadOnly)) { - _data = f.readAll(); - if (f.error() != QFile::NoError) { - _data = QByteArray(); - } - } - } - - _implementation = new FFMpegReaderImplementation(_location, &_data); -// _implementation = new QtGifReaderImplementation(_location, &_data); - return _implementation->start(false); - } - - ClipProcessResult error() { - stop(); - _state = ClipError; - return ClipProcessError; - } - - void stop() { - delete _implementation; - _implementation = 0; - - if (_location) { - if (_accessed) { - _location->accessDisable(); - } - delete _location; - _location = 0; - } - _accessed = false; - } - - ~ClipReaderPrivate() { - stop(); - deleteAndMark(_location); - deleteAndMark(_implementation); - _data.clear(); - } - -private: - - ClipReader *_interface; - ClipState _state; - - QByteArray _data; - FileLocation *_location; - bool _accessed; - - QBuffer _buffer; - ClipReaderImplementation *_implementation; - - ClipFrameRequest _request; - struct Frame { - Frame() : alpha(true), when(0) { - } - QPixmap pix; - QImage original, cache; - bool alpha; - uint64 when; - }; - Frame _frames[3]; - int32 _frame; - Frame *frame() { - return _frames + _frame; - } - - int32 _width, _height; - - uint64 _nextFrameWhen; - - bool _paused; - - friend class ClipReadManager; - -}; - -ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0), _needReProcess(false) { - moveToThread(thread); - connect(thread, SIGNAL(started()), this, SLOT(process())); - connect(thread, SIGNAL(finished()), this, SLOT(finish())); - connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); - - _timer.setSingleShot(true); - _timer.moveToThread(thread); - connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); - - connect(this, SIGNAL(callback(ClipReader*,qint32,qint32)), _manager, SLOT(clipCallback(ClipReader*,qint32,qint32))); -} - -void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { - reader->_private = new ClipReaderPrivate(reader, location, data); - _loadLevel.fetchAndAddRelaxed(AverageGifSize); - update(reader); -} - -void ClipReadManager::start(ClipReader *reader) { - update(reader); -} - -void ClipReadManager::update(ClipReader *reader) { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator i = _readerPointers.constFind(reader); - if (i == _readerPointers.cend()) { - lock.unlock(); - - QWriteLocker lock(&_readerPointersMutex); - _readerPointers.insert(reader, MutableAtomicInt(1)); - } else { - i->v.storeRelease(1); - } - emit processDelayed(); -} - -void ClipReadManager::stop(ClipReader *reader) { - if (!carries(reader)) return; - - QWriteLocker lock(&_readerPointersMutex); - _readerPointers.remove(reader); - emit processDelayed(); -} - -bool ClipReadManager::carries(ClipReader *reader) const { - QReadLocker lock(&_readerPointersMutex); - return _readerPointers.contains(reader); -} - -ClipReadManager::ReaderPointers::iterator ClipReadManager::unsafeFindReaderPointer(ClipReaderPrivate *reader) { - ReaderPointers::iterator it = _readerPointers.find(reader->_interface); - - // could be a new reader which was realloced in the same address - return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end(); -} - -ClipReadManager::ReaderPointers::const_iterator ClipReadManager::constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const { - ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface); - - // could be a new reader which was realloced in the same address - return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend(); -} - -bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); - if (result == ClipProcessError) { - if (it != _readerPointers.cend()) { - it.key()->error(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - - lock.unlock(); - QWriteLocker lock(&_readerPointersMutex); - ReaderPointers::iterator i = unsafeFindReaderPointer(reader); - if (i != _readerPointers.cend()) _readerPointers.erase(i); - } - return false; - } - if (it == _readerPointers.cend()) { - return false; - } - - if (result == ClipProcessStarted) { - _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize); - } - if (!reader->_paused && result == ClipProcessRepaint) { - int32 ishowing, iprevious; - ClipReader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious); - t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0); - if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown - if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) { - reader->_paused = true; - it.key()->_paused.storeRelease(1); - result = ClipProcessPaused; - } - } - } - if (result == ClipProcessStarted || result == ClipProcessCopyFrame) { - t_assert(reader->_frame >= 0); - ClipReader::Frame *frame = it.key()->_frames + reader->_frame; - frame->clear(); - frame->pix = reader->frame()->pix; - frame->original = reader->frame()->original; - frame->displayed.storeRelease(0); - if (result == ClipProcessStarted) { - reader->_nextFrameWhen = ms; - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - } - } else if (result == ClipProcessPaused) { - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderReinit); - } else if (result == ClipProcessRepaint) { - it.key()->moveToNextWrite(); - emit callback(it.key(), it.key()->threadIndex(), ClipReaderRepaint); - } - return true; -} - -ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { - if (!handleProcessResult(reader, result, ms)) { - _loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize)); - delete reader; - return ResultHandleRemove; - } - - _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); - if (_processingInThread->isInterruptionRequested()) { - return ResultHandleStop; - } - - if (result == ClipProcessRepaint) { - { - QReadLocker lock(&_readerPointersMutex); - ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader); - if (it != _readerPointers.cend()) { - int32 index = 0; - ClipReader *r = it.key(); - ClipReader::Frame *frame = it.key()->frameToWrite(&index); - if (frame) { - frame->clear(); - } else { - t_assert(!reader->_request.valid()); - } - reader->_frame = index; - } - } - return handleResult(reader, reader->finishProcess(ms), ms); - } - - return ResultHandleContinue; -} - -void ClipReadManager::process() { - if (_processingInThread) { - _needReProcess = true; - return; - } - - _timer.stop(); - _processingInThread = thread(); - - uint64 ms = getms(), minms = ms + 86400 * 1000ULL; - { - QReadLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - if (it->v.loadAcquire()) { - Readers::iterator i = _readers.find(it.key()->_private); - if (i == _readers.cend()) { - _readers.insert(it.key()->_private, 0); - } else { - i.value() = ms; - if (i.key()->_paused && !it.key()->_paused.loadAcquire()) { - i.key()->_paused = false; - } - } - ClipReader::Frame *frame = it.key()->frameToWrite(); - if (frame) it.key()->_private->_request = frame->request; - it->v.storeRelease(0); - } - } - } - - for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { - ClipReaderPrivate *reader = i.key(); - if (i.value() <= ms) { - ResultHandleState state = handleResult(reader, reader->process(ms), ms); - if (state == ResultHandleRemove) { - i = _readers.erase(i); - continue; - } else if (state == ResultHandleStop) { - _processingInThread = 0; - return; - } - ms = getms(); - i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL); - } - if (!reader->_paused && i.value() < minms) { - minms = i.value(); - } - ++i; - } - - ms = getms(); - if (_needReProcess || minms <= ms) { - _needReProcess = false; - _timer.start(1); - } else { - _timer.start(minms - ms); - } - - _processingInThread = 0; -} - -void ClipReadManager::finish() { - _timer.stop(); - clear(); -} - -void ClipReadManager::clear() { - { - QWriteLocker lock(&_readerPointersMutex); - for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) { - it.key()->_private = 0; - } - _readerPointers.clear(); - } - - for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) { - delete i.key(); - } - _readers.clear(); -} - -ClipReadManager::~ClipReadManager() { - clear(); -} - -MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover) { - FileLocation localloc(StorageFilePartial, fname); - QByteArray localdata(data); - - FFMpegReaderImplementation *reader = new FFMpegReaderImplementation(&localloc, &localdata); - if (reader->start(true)) { - bool hasAlpha = false; - if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) { - if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) { - if (hasAlpha) { - QImage cacheForResize; - ClipFrameRequest request; - request.framew = request.outerw = cover.width(); - request.frameh = request.outerh = cover.height(); - request.factor = 1; - cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage(); - } - int32 duration = reader->duration(); - delete reader; - return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height())); - } - } - } - delete reader; - return MTP_documentAttributeFilename(MTP_string(fname)); +void AnimationManager::clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification) { + Media::Clip::Reader::callback(reader, threadIndex, Media::Clip::Notification(notification)); } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index ff1a944ed..3482ef0e9 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -24,6 +24,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include #include +namespace Media { +namespace Clip { + +class Reader; +static Reader * const BadReader = SharedMemoryLocation(); + +class Manager; + +enum Notification { + NotificationReinit, + NotificationRepaint, +}; + +} // namespace Clip +} // namespace Media + namespace anim { typedef float64 (*transition)(const float64 &delta, const float64 &dt); @@ -193,6 +209,7 @@ namespace anim { void startManager(); void stopManager(); + void registerClipManager(Media::Clip::Manager *manager); }; @@ -480,8 +497,6 @@ if ((animation).isNull()) { \ ENSURE_ANIMATION(animation, updateCallback, from); \ (animation).start((to), (duration), (transition)) -class ClipReader; - class AnimationManager : public QObject { Q_OBJECT @@ -494,7 +509,7 @@ public: public slots: void timeout(); - void clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification); + void clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification); private: typedef QMap AnimatingObjects; @@ -503,198 +518,3 @@ private: bool _iterating; }; - -class FileLocation; - -enum ClipState { - ClipReading, - ClipError, -}; - -struct ClipFrameRequest { - ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) { - } - bool valid() const { - return factor > 0; - } - int32 factor; - int32 framew, frameh; - int32 outerw, outerh; - bool rounded; -}; - -enum ClipReaderNotification { - ClipReaderReinit, - ClipReaderRepaint, -}; - -enum ClipReaderSteps { - WaitingForDimensionsStep = -3, // before ClipReaderPrivate read the first image and got the original frame size - WaitingForRequestStep = -2, // before ClipReader got the original frame size and prepared the frame request - WaitingForFirstFrameStep = -1, // before ClipReaderPrivate got the frame request and started waiting for the 1-2 delay -}; - -class ClipReaderPrivate; -class ClipReader { -public: - - using Callback = Function; - - ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback); - static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted - - void setAutoplay() { - _autoplay = true; - } - bool autoplay() const { - return _autoplay; - } - - void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); - QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms); - QPixmap frameOriginal() const { - Frame *frame = frameToShow(); - if (!frame) return QPixmap(); - QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap()); - result.detach(); - return result; - } - bool currentDisplayed() const { - Frame *frame = frameToShow(); - return frame ? (frame->displayed.loadAcquire() != 0) : true; - } - bool paused() const { - return _paused.loadAcquire(); - } - int32 threadIndex() const { - return _threadIndex; - } - - int32 width() const; - int32 height() const; - - ClipState state() const; - bool started() const { - int32 step = _step.loadAcquire(); - return (step == WaitingForFirstFrameStep) || (step >= 0); - } - bool ready() const; - - void stop(); - void error(); - - ~ClipReader(); - -private: - - Callback _callback; - - ClipState _state; - - mutable int32 _width, _height; - - mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3 - struct Frame { - Frame() : displayed(false) { - } - void clear() { - pix = QPixmap(); - original = QImage(); - } - QPixmap pix; - QImage original; - ClipFrameRequest request; - QAtomicInt displayed; - }; - mutable Frame _frames[3]; - Frame *frameToShow(int32 *index = 0) const; // 0 means not ready - Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready - Frame *frameToWriteNext(bool check, int32 *index = 0) const; - void moveToNextShow() const; - void moveToNextWrite() const; - - QAtomicInt _paused; - int32 _threadIndex; - - bool _autoplay; - - friend class ClipReadManager; - - ClipReaderPrivate *_private; - -}; - -static ClipReader * const BadClipReader = SharedMemoryLocation(); - -enum ClipProcessResult { - ClipProcessError, - ClipProcessStarted, - ClipProcessPaused, - ClipProcessRepaint, - ClipProcessCopyFrame, - ClipProcessWait, -}; - -class ClipReadManager : public QObject { - Q_OBJECT - -public: - - ClipReadManager(QThread *thread); - int32 loadLevel() const { - return _loadLevel.load(); - } - void append(ClipReader *reader, const FileLocation &location, const QByteArray &data); - void start(ClipReader *reader); - void update(ClipReader *reader); - void stop(ClipReader *reader); - bool carries(ClipReader *reader) const; - ~ClipReadManager(); - -signals: - - void processDelayed(); - - void callback(ClipReader *reader, qint32 threadIndex, qint32 notification); - -public slots: - - void process(); - void finish(); - -private: - - void clear(); - - QAtomicInt _loadLevel; - struct MutableAtomicInt { - MutableAtomicInt(int value) : v(value) { - } - mutable QAtomicInt v; - }; - typedef QMap ReaderPointers; - ReaderPointers _readerPointers; - mutable QReadWriteLock _readerPointersMutex; - - ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const; - ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader); - - bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); - - enum ResultHandleState { - ResultHandleRemove, - ResultHandleStop, - ResultHandleContinue, - }; - ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); - - typedef QMap Readers; - Readers _readers; - - QTimer _timer; - QThread *_processingInThread; - bool _needReProcess; - -}; - -MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover); diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 5b8fc9a82..532a644d2 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -371,6 +371,10 @@ true true + + true + true + true true @@ -701,6 +705,10 @@ true true + + true + true + true true @@ -1062,6 +1070,10 @@ true true + + true + true + true true @@ -1250,6 +1262,10 @@ + + + + @@ -1508,6 +1524,23 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing media_clip_reader.h... + .\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 -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_clip_reader.h... + .\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 -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_clip_reader.h... + .\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) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 2d1a1f393..64116e19b 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -112,6 +112,9 @@ {385d4cd5-f702-41b7-9e39-707d16b118d5} + + {a281888a-8b70-4e95-9b03-ebcb02837df4} + @@ -1344,6 +1347,27 @@ SourceFiles\platform\linux + + GeneratedFiles\Deploy + + + GeneratedFiles\Debug + + + GeneratedFiles\Release + + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1595,6 +1619,15 @@ SourceFiles\platform\linux + + SourceFiles\media + + + SourceFiles\media + + + SourceFiles\media + @@ -1885,6 +1918,9 @@ SourceFiles\ui + + SourceFiles\media +