Ticket #9556: thumbs_v1.patch

File thumbs_v1.patch, 13.3 KB (added by Jim Stichnoth, 7 years ago)

Here is a prototype implementation of the concept that uses the existing edit screen.

  • mythtv/libs/libmythtv/deletemap.cpp

    diff --git a/mythtv/libs/libmythtv/deletemap.cpp b/mythtv/libs/libmythtv/deletemap.cpp
    index 86fb101..7130ab5 100644
    a b  
    11#include <cmath>
    22#include <stdint.h>
     3#include <QCoreApplication>
     4#include <QMutexLocker>
    35
    46#include "mythlogging.h"
    57#include "mythcontext.h"
    68#include "osd.h"
    79#include "deletemap.h"
    810#include "mythplayer.h"
     11#include "mythuiimage.h"
    912
    1013#define LOC     QString("DelMap: ")
    1114#define EDIT_CHECK do { \
     
    1518    } \
    1619} while(0)
    1720
     21class NextThumbnailEvent : public QEvent
     22{
     23public:
     24    NextThumbnailEvent(ThumbnailCache *c) : QEvent(kEventType), m_cache(c) {}
     25    static Type kEventType;
     26    ThumbnailCache *m_cache;
     27};
     28
     29QEvent::Type NextThumbnailEvent::kEventType =
     30    (QEvent::Type) QEvent::registerEventType();
     31
     32ThumbnailCache::ThumbnailCache(PlayerContext *origCtx,
     33                               const QVector<ThumbnailLayout> &images) :
     34    MThread("Thumbnail loader")
     35{
     36    m_ctx = NULL;
     37    m_origCtx = origCtx;
     38    m_images = images;
     39    m_isEventOutstanding = false;
     40    m_totalDataBytes = 0;
     41    m_maxDataBytes = gCoreContext->GetNumSetting("UIImageCacheSize", 20)
     42        * 1024 * 1024 * 8;
     43    m_keepRunning = true;
     44    m_timestamp = 0;
     45}
     46
     47ThumbnailCache::~ThumbnailCache(void)
     48{
     49    m_keepRunning = false;
     50    wait();
     51    if (m_ctx)
     52        delete m_ctx;
     53    QHash<uint64_t, ThumbnailCacheEntry>::iterator iter = m_cache.begin();
     54    for (; iter != m_cache.end(); ++iter)
     55    {
     56        delete (*iter).m_image;
     57        delete (*iter).m_data;
     58    }
     59}
     60
     61void ThumbnailCache::run(void)
     62{
     63    RunProlog();
     64
     65    m_ctx = new PlayerContext("Cutlist editor thumbnail");
     66    m_rbuf = RingBuffer::Create(m_origCtx->buffer->GetFilename(), false);
     67    m_ctx->SetRingBuffer(m_rbuf);
     68    m_ctx->SetPlayingInfo(m_origCtx->playingInfo);
     69    m_player =
     70        new MythPlayer((PlayerFlags)(kAudioMuted | kVideoIsNull
     71                                     /* | kDecodeAllowGPU*/));
     72    m_ctx->SetPlayer(m_player);
     73    m_ctx->player->SetPlayerInfo(NULL, NULL, true, m_ctx);
     74
     75    while (m_keepRunning)
     76    {
     77        bool isEmpty;
     78        uint64_t target = 0;
     79        {
     80            QMutexLocker l(&m_cacheLock);
     81            isEmpty = m_loadQueue.isEmpty();
     82            if (!isEmpty)
     83                target = m_loadQueue.front();
     84        }
     85        if (!isEmpty)
     86        {
     87            int bufflen, vw, vh;
     88            float ar;
     89            char *grab =
     90                m_ctx->player->GetScreenGrabAtFrame(target, true, bufflen,
     91                                                    vw, vh, ar);
     92            {
     93                QMutexLocker l(&m_cacheLock);
     94                int length = vw * vh * 4;
     95                m_totalDataBytes += length;
     96                QImage *qi = new QImage((unsigned char *)grab, vw, vh,
     97                                        QImage::Format_RGB32);
     98                m_cache[target] = ThumbnailCacheEntry(qi, (unsigned char *)grab,
     99                                                      length, m_timestamp++);
     100                m_loadQueue.removeAll(target);
     101                isEmpty = m_loadQueue.isEmpty();
     102            }
     103            LOG(VB_PLAYBACK, LOG_DEBUG,
     104                QString("Grabbed frame %1, %2x%3").arg(target).arg(vw).arg(vh));
     105        }
     106        if (isEmpty)
     107            usleep(1000);
     108    }
     109    RunEpilog();
     110}
     111
     112void ThumbnailCache::customEvent(QEvent *event)
     113{
     114    if (event->type() == NextThumbnailEvent::kEventType)
     115    {
     116        NextThumbnailEvent *nte = dynamic_cast<NextThumbnailEvent*>(event);
     117        nte->m_cache->m_isEventOutstanding = false;
     118        nte->m_cache->Load();
     119    }
     120}
     121
     122void ThumbnailCache::Load(void)
     123{
     124    if (m_isEventOutstanding)
     125        return;
     126    bool moreToLoad = false;
     127    {
     128        QMutexLocker l(&m_cacheLock);
     129        // Load all images that are cached.
     130        for (int i = 0; i < m_images.size(); ++i)
     131        {
     132            if (!m_displayed.contains(i))
     133            {
     134                int offset = m_seekamount * m_images[i].m_steps;
     135                uint64_t target = m_baseFrame + offset;
     136                if (offset < 0 && target > m_baseFrame)
     137                    target = 0; // handle underflow
     138                if (m_cache.contains(target))
     139                {
     140                    MythUIImage *imageType = m_images[i].m_image;
     141                    MythImage *mi = new MythImage(imageType->GetPainter());
     142                    QImage *qi = m_cache[target].m_image;
     143                    m_cache[target].m_timestamp = m_timestamp++;
     144                    mi->Assign(*qi);
     145                    imageType->SetImage(mi);
     146                    m_displayed += i;
     147                }
     148                else
     149                {
     150                    moreToLoad = true;
     151                    if (!m_loadQueue.contains(target))
     152                        m_loadQueue += target;
     153                }
     154            }
     155        }
     156    }
     157    if (moreToLoad)
     158    {
     159        QCoreApplication::postEvent(this, new NextThumbnailEvent(this));
     160    }
     161}
     162
     163void ThumbnailCache::Clear(uint64_t frame, int seekamount)
     164{
     165    QMutexLocker l(&m_cacheLock);
     166    for (int i = 0 ; i < m_images.size(); ++i)
     167        m_images[i].m_image->Reset();
     168    m_displayed.clear();
     169    m_baseFrame = frame;
     170    m_seekamount = seekamount;
     171    while (m_totalDataBytes > m_maxDataBytes && !m_cache.isEmpty())
     172    {
     173        // find and delete the oldest image
     174        uint64_t oldestIndex = -1;
     175        uint64_t oldestTimestamp = m_timestamp;
     176        QHash<uint64_t, ThumbnailCacheEntry>::iterator iter = m_cache.begin();
     177        while (m_totalDataBytes > m_maxDataBytes && iter != m_cache.end())
     178        {
     179            uint64_t key = iter.key();
     180            ++iter;
     181            uint64_t timestamp = m_cache[key].m_timestamp;
     182            if (timestamp < oldestTimestamp)
     183            {
     184                oldestIndex = key;
     185                oldestTimestamp = timestamp;
     186            }
     187        }
     188        if (oldestIndex == (uint64_t)-1) // this shouldn't happen
     189            break;
     190        uint64_t key = oldestIndex;
     191        ThumbnailCacheEntry entry = m_cache.take(key);
     192        delete entry.m_image;
     193        delete entry.m_data;
     194        m_totalDataBytes -= entry.m_dataLength;
     195        LOG(VB_PLAYBACK, LOG_DEBUG,
     196            QString("Expired thumbnail frame=%1 size=%2 new cache size=%3")
     197            .arg(key).arg(entry.m_dataLength).arg(m_totalDataBytes));
     198    }
     199    m_loadQueue.clear();
     200}
     201
     202////////////////////////////////////////////////////////////////
     203
    18204DeleteMapUndoEntry::DeleteMapUndoEntry(frm_dir_map_t dm, QString msg) :
    19205    deleteMap(dm), message(msg) { }
    20206
    void DeleteMap::UpdateOSD(uint64_t frame, uint64_t total, double frame_rate, 
    196382        osd->SetRegions("osd_program_editor", m_deleteMap, total);
    197383    m_changed = false;
    198384    m_cachedTotalForOSD = total;
     385
     386    // First-time creation of thumbnail loader/cacher
     387    if (!m_thumbnails)
     388    {
     389        MythScreenType *win = osd->GetWindow("osd_program_editor");
     390        QVector<ThumbnailLayout> images;
     391        if (win)
     392        {
     393            QList<MythUIType *> *children = win->GetAllChildren();
     394            if (children)
     395            {
     396                QList<MythUIType *>::const_iterator iter =
     397                    children->constBegin();
     398                for (; iter != children->constEnd(); ++iter)
     399                {
     400                    static const char *prefix = "thumbnail_";
     401                    QString name = (*iter)->objectName();
     402                    if (!name.startsWith(prefix))
     403                        continue;
     404                    MythUIImage *imageType = dynamic_cast<MythUIImage*> (*iter);
     405                    if (!imageType)
     406                        continue;
     407                    bool ok = false;
     408                    float steps = name.mid(strlen(prefix)).toFloat(&ok);
     409                    if (!ok)
     410                        continue;
     411                    images +=
     412                        ThumbnailLayout(imageType, steps,
     413                                        imageType->GetArea().toQRect().size());
     414                }
     415            }
     416        }
     417
     418        m_thumbnails = new ThumbnailCache(m_ctx, images);
     419        // Start helper thread only if the theme contains thumbnail
     420        // objects.
     421        if (!images.isEmpty())
     422            m_thumbnails->start();
     423    }
     424
     425    m_thumbnails->Clear(m_ctx->player->GetFramesPlayed(), m_seekamount);
     426    m_thumbnails->Load();
     427}
     428
     429DeleteMap::~DeleteMap(void)
     430{
     431    if (m_thumbnails)
     432        delete m_thumbnails;
    199433}
    200434
    201435/// Set the edit mode and optionally hide the edit mode OSD.
  • mythtv/libs/libmythtv/deletemap.h

    diff --git a/mythtv/libs/libmythtv/deletemap.h b/mythtv/libs/libmythtv/deletemap.h
    index fb75d71..30626b5 100644
    a b  
    55#include "programinfo.h"
    66#include "playercontext.h"
    77#include "mythtvexp.h"
     8#include "mthread.h"
    89
    910class DeleteMap;
    1011
    typedef struct DeleteMapUndoEntry 
    1617    DeleteMapUndoEntry(void);
    1718} DeleteMapUndoEntry;
    1819
     20class ThumbnailCacheEntry
     21{
     22public:
     23    ThumbnailCacheEntry(QImage *image, unsigned char *data,
     24                        int dataLength, uint64_t timestamp) :
     25        m_image(image), m_data(data), m_dataLength(dataLength),
     26        m_timestamp(timestamp) {}
     27    ThumbnailCacheEntry(void) : m_image(NULL), m_data(NULL), m_dataLength(0),
     28            m_timestamp(0) {}
     29
     30    QImage *m_image;
     31    unsigned char *m_data;
     32    int m_dataLength;
     33    uint64_t m_timestamp;
     34};
     35
     36class ThumbnailLayout
     37{
     38public:
     39    ThumbnailLayout(MythUIImage *image, float steps, QSize size) :
     40        m_image(image), m_steps(steps), m_size(size) {}
     41    ThumbnailLayout(void) : m_image(NULL), m_steps(0), m_size(QSize()) {}
     42
     43    MythUIImage *m_image;
     44    float m_steps;
     45    QSize m_size;
     46};
     47
     48class ThumbnailCache : public QObject, public MThread
     49{
     50    Q_OBJECT
     51public:
     52    ThumbnailCache(PlayerContext *origCtx,
     53                   const QVector<ThumbnailLayout> &images);
     54    ~ThumbnailCache(void);
     55
     56    virtual void run(void);
     57
     58    virtual void customEvent(QEvent *);
     59    void Load(void);
     60    void Clear(uint64_t frame, int seekamount);
     61
     62    // Fields used by the lookahead thread
     63    PlayerContext *m_ctx;
     64    RingBuffer *m_rbuf;
     65    MythPlayer *m_player;
     66    volatile bool m_keepRunning;
     67
     68    QMutex m_cacheLock;
     69    // m_cacheLock protects the following fields:
     70
     71    // An ordered queue of images to load, indexed by frame number
     72    QList<uint64_t> m_loadQueue;
     73
     74    // Maps frame number to <QImage, image data, image data size, timestamp>
     75    QHash<uint64_t, ThumbnailCacheEntry> m_cache;
     76
     77    // <image, steps from current frame>
     78    QVector<ThumbnailLayout> m_images;
     79
     80    // which image indexes are finished displaying
     81    QSet<int> m_displayed;
     82
     83    // used for the LRU cache
     84    uint64_t m_timestamp;
     85    int m_totalDataBytes;
     86
     87    // End of fields protected by m_cacheLock
     88
     89    bool m_isEventOutstanding;
     90    uint64_t m_baseFrame;
     91    int m_seekamount;
     92    int m_maxDataBytes;
     93    PlayerContext *m_origCtx;
     94};
     95
    1996class MTV_PUBLIC DeleteMap
    2097{
    2198  public:
    class MTV_PUBLIC DeleteMap 
    23100                 m_nextCutStartIsValid(false),
    24101                 m_nextCutStart(0), m_changed(true),
    25102                 m_seekamountpos(4), m_seekamount(30),
    26                  m_ctx(0), m_cachedTotalForOSD(0), m_undoStackPointer(-1)
     103                 m_ctx(NULL), m_cachedTotalForOSD(0), m_undoStackPointer(-1),
     104                 m_thumbnails(NULL)
    27105    {
    28106        Push("");
    29107    }
     108    ~DeleteMap();
    30109
    31110    void SetPlayerContext(PlayerContext *ctx) { m_ctx = ctx; }
    32111    bool HandleAction(QString &action, uint64_t frame, uint64_t played,
    class MTV_PUBLIC DeleteMap 
    108187    // Invariant: m_undoStack[m_undoStackPointer].deleteMap == m_deleteMap
    109188    QVector<DeleteMapUndoEntry> m_undoStack;
    110189    int m_undoStackPointer;
     190
     191    ThumbnailCache *m_thumbnails;
    111192};
    112193
    113194#endif // DELETEMAP_H
  • mythtv/libs/libmythtv/mythplayer.cpp

    diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
    index ead1600..3e936a3 100644
    a b char *MythPlayer::GetScreenGrabAtFrame(uint64_t frameNum, bool absolute, 
    41434143    memset(&orig,   0, sizeof(AVPicture));
    41444144    memset(&retbuf, 0, sizeof(AVPicture));
    41454145
    4146     if (OpenFile(0) < 0)
     4146    bool alreadyOpen = videoOutput;
     4147
     4148    if (!alreadyOpen && OpenFile(0) < 0)
    41474149    {
    41484150        LOG(VB_GENERAL, LOG_ERR, LOC + "Could not open file for preview.");
    41494151        return NULL;
    char *MythPlayer::GetScreenGrabAtFrame(uint64_t frameNum, bool absolute, 
    41664168        return (char*) outputbuf;
    41674169    }
    41684170
    4169     if (!InitVideo())
     4171    if (!alreadyOpen && !InitVideo())
    41704172    {
    41714173        LOG(VB_GENERAL, LOG_ERR, LOC +
    41724174            "Unable to initialize video for screen grab.");
    char *MythPlayer::GetScreenGrabAtFrame(uint64_t frameNum, bool absolute, 
    41744176    }
    41754177
    41764178    ClearAfterSeek();
    4177     if (!decoderThread)
     4179    if (!alreadyOpen && !decoderThread)
    41784180        DecoderStart(true /*start paused*/);
    41794181    SeekForScreenGrab(number, frameNum, absolute);
    41804182    int tries = 0;
    void MythPlayer::SeekForScreenGrab(uint64_t &number, uint64_t frameNum, 
    43054307    if (hasFullPositionMap)
    43064308    {
    43074309        DiscardVideoFrame(videoOutput->GetLastDecodedFrame());
    4308         DoFastForward(number);
     4310        DoJumpToFrame(number);
    43094311    }
    43104312}
    43114313