⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 286628 in webkit


Ignore:
Timestamp:
Dec 7, 2021, 4:22:32 PM (5 years ago)
Author:
yoshiaki.jitsukawa@sony.com
Message:

Support Animated JPEG-XL images
https://bugs.webkit.org/show_bug.cgi?id=233545
<rdar://problem/86077143>

Reviewed by Don Olmstead.

Source/WebCore:

Implement relevant API, such as frameCount() and support a positive
frame index for frameBufferAtIndex().

Introduce a query to count frames because JPEG XL code stream itself doesn't have frame
count metadata and we need to scan the code stream to the last frame.

Also introduce the Size query instead of the onlySize parameter of the decode() function.

With the DecodeImage query, the decoder decodes a single frame. To
specify which frame to decode, we make use of the JxlDecoderRewind()
and JxlDecoderSkipFrames() functions of libjxl.

To omit rewind and skip as much as possible, we record the query to
m_lastQuery and use it in shouldRewind().

Test: fast/images/animated-jpegxl-loop-count.html

  • platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp:

(WebCore::JPEGXLImageDecoder::~JPEGXLImageDecoder):
(WebCore::JPEGXLImageDecoder::frameCount const):
(WebCore::JPEGXLImageDecoder::repetitionCount const):
(WebCore::JPEGXLImageDecoder::frameBufferAtIndex):
(WebCore::JPEGXLImageDecoder::clearFrameBufferCache):
(WebCore::JPEGXLImageDecoder::setFailed):
(WebCore::JPEGXLImageDecoder::tryDecodeSize):
(WebCore::JPEGXLImageDecoder::hasAlpha const):
(WebCore::JPEGXLImageDecoder::hasAnimation const):
(WebCore::JPEGXLImageDecoder::ensureDecoderInitialized):
(WebCore::JPEGXLImageDecoder::shouldRewind const):
(WebCore::JPEGXLImageDecoder::rewind):
(WebCore::JPEGXLImageDecoder::updateFrameCount):
(WebCore::JPEGXLImageDecoder::decode):
(WebCore::JPEGXLImageDecoder::processInput):
(WebCore::JPEGXLImageDecoder::imageOut):
(WebCore::JPEGXLImageDecoder::clear): Deleted.

  • platform/image-decoders/jpegxl/JPEGXLImageDecoder.h:

LayoutTests:

Add test for animated JPEG XL, based on animated-gif-loop-count.

  • TestExpectations:
  • fast/images/animated-jpegxl-loop-count-expected.html: Added.
  • fast/images/animated-jpegxl-loop-count.html: Added.
  • fast/images/resources/animated-red-green-blue-repeat-1.jxl: Added.
  • fast/images/resources/animated-red-green-blue-repeat-2.jxl: Added.
  • fast/images/resources/animated-red-green-blue-repeat-infinite.jxl: Added.
  • platform/glib/TestExpectations:
  • platform/wincairo/TestExpectations:
Location:
trunk
Files:
5 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r286627 r286628  
     12021-12-07  Yoshiaki Jitsukawa  <yoshiaki.jitsukawa@sony.com>
     2
     3        Support Animated JPEG-XL images
     4        https://bugs.webkit.org/show_bug.cgi?id=233545
     5        <rdar://problem/86077143>
     6
     7        Reviewed by Don Olmstead.
     8
     9         Add test for animated JPEG XL, based on animated-gif-loop-count.
     10
     11        * TestExpectations:
     12        * fast/images/animated-jpegxl-loop-count-expected.html: Added.
     13        * fast/images/animated-jpegxl-loop-count.html: Added.
     14        * fast/images/resources/animated-red-green-blue-repeat-1.jxl: Added.
     15        * fast/images/resources/animated-red-green-blue-repeat-2.jxl: Added.
     16        * fast/images/resources/animated-red-green-blue-repeat-infinite.jxl: Added.
     17        * platform/glib/TestExpectations:
     18        * platform/wincairo/TestExpectations:
     19
    1202021-12-07  Robert Jenner  <Jenner@apple.com>
    221
  • trunk/LayoutTests/TestExpectations

    r286593 r286628  
    136136fast/images/jpegxl-image-decoding.html [ Skip ]
    137137fast/images/jpegxl-as-image.html [ Skip ]
     138fast/images/animated-jpegxl-loop-count.html [ Skip ]
    138139
    139140# Only applicable on platforms with dark mode support
  • trunk/LayoutTests/platform/glib/TestExpectations

    r286586 r286628  
    108108fast/images/jpegxl-as-image.html [ Pass ]
    109109fast/images/jpegxl-image-decoding.html [ Pass ]
     110fast/images/animated-jpegxl-loop-count.html [ Pass ]
    110111
    111112# Some Apple ports don't support RTL scrollbars.
  • trunk/LayoutTests/platform/wincairo/TestExpectations

    r286451 r286628  
    273273fast/layers/prevent-hit-test-during-layout.html [ Skip ]
    274274
    275 # JPEG XL is disabled until WebKitRequirements is released with libjxl
    276 fast/images/jpegxl-as-image.html [ Skip ]
    277 fast/images/jpegxl-image-decoding.html [ Skip ]
     275# JPEG-XL Tests
     276fast/images/jpegxl-as-image.html [ Pass ]
     277fast/images/jpegxl-image-decoding.html [ Pass ]
     278fast/images/animated-jpegxl-loop-count.html [ Pass ]
    278279
    279280# DataTransferItems is not yet implemented
  • trunk/Source/WebCore/ChangeLog

    r286625 r286628  
     12021-12-07  Yoshiaki Jitsukawa  <yoshiaki.jitsukawa@sony.com>
     2
     3        Support Animated JPEG-XL images
     4        https://bugs.webkit.org/show_bug.cgi?id=233545
     5        <rdar://problem/86077143>
     6
     7        Reviewed by Don Olmstead.
     8
     9        Implement relevant API, such as frameCount() and support a positive
     10        frame index for frameBufferAtIndex().
     11
     12        Introduce a query to count frames because JPEG XL code stream itself doesn't have frame
     13        count metadata and we need to scan the code stream to the last frame.
     14
     15        Also introduce the Size query instead of the onlySize parameter of the decode() function.
     16
     17        With the DecodeImage query, the decoder decodes a single frame. To
     18        specify which frame to decode, we make use of the JxlDecoderRewind()
     19        and JxlDecoderSkipFrames() functions of libjxl.
     20
     21        To omit rewind and skip as much as possible, we record the query to
     22        m_lastQuery and use it in shouldRewind().
     23
     24        Test: fast/images/animated-jpegxl-loop-count.html
     25
     26        * platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp:
     27        (WebCore::JPEGXLImageDecoder::~JPEGXLImageDecoder):
     28        (WebCore::JPEGXLImageDecoder::frameCount const):
     29        (WebCore::JPEGXLImageDecoder::repetitionCount const):
     30        (WebCore::JPEGXLImageDecoder::frameBufferAtIndex):
     31        (WebCore::JPEGXLImageDecoder::clearFrameBufferCache):
     32        (WebCore::JPEGXLImageDecoder::setFailed):
     33        (WebCore::JPEGXLImageDecoder::tryDecodeSize):
     34        (WebCore::JPEGXLImageDecoder::hasAlpha const):
     35        (WebCore::JPEGXLImageDecoder::hasAnimation const):
     36        (WebCore::JPEGXLImageDecoder::ensureDecoderInitialized):
     37        (WebCore::JPEGXLImageDecoder::shouldRewind const):
     38        (WebCore::JPEGXLImageDecoder::rewind):
     39        (WebCore::JPEGXLImageDecoder::updateFrameCount):
     40        (WebCore::JPEGXLImageDecoder::decode):
     41        (WebCore::JPEGXLImageDecoder::processInput):
     42        (WebCore::JPEGXLImageDecoder::imageOut):
     43        (WebCore::JPEGXLImageDecoder::clear): Deleted.
     44        * platform/image-decoders/jpegxl/JPEGXLImageDecoder.h:
     45
    1462021-12-07  Cameron McCormack  <heycam@apple.com>
    247
  • trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp

    r286011 r286628  
    2929#if USE(JPEGXL)
    3030
     31#include <optional>
     32
    3133namespace WebCore {
    3234
     
    3840JPEGXLImageDecoder::~JPEGXLImageDecoder()
    3941{
    40     clear();
     42}
     43
     44size_t JPEGXLImageDecoder::frameCount() const
     45{
     46    if (!hasAnimation())
     47        return 1;
     48
     49    if (!m_isLastFrameHeaderReceived)
     50        const_cast<JPEGXLImageDecoder*>(this)->updateFrameCount();
     51
     52    return m_frameCount;
     53}
     54
     55RepetitionCount JPEGXLImageDecoder::repetitionCount() const
     56{
     57    if (hasAnimation()) {
     58        if (!m_basicInfo->animation.num_loops) {
     59            // If num_loops is zero, repeat infinitely.
     60            return RepetitionCountInfinite;
     61        }
     62        return m_basicInfo->animation.num_loops;
     63    }
     64    return RepetitionCountNone;
    4165}
    4266
    4367ScalableImageDecoderFrame* JPEGXLImageDecoder::frameBufferAtIndex(size_t index)
    4468{
    45     // TODO: To support animated JPEG XL in the future we need to handle second and subsequent frames.
    46     if (index)
     69    if (ScalableImageDecoder::encodedDataStatus() < EncodedDataStatus::SizeAvailable)
    4770        return nullptr;
     71
     72    if (index >= frameCount())
     73        index = frameCount() - 1;
    4874
    4975    if (m_frameBufferCache.isEmpty())
    5076        m_frameBufferCache.grow(1);
    5177
    52     auto& frame = m_frameBufferCache[0];
     78    auto& frame = m_frameBufferCache[index];
    5379    if (!frame.isComplete())
    54         decode(false, isAllDataReceived());
     80        decode(Query::DecodedImage, index, isAllDataReceived());
    5581    return &frame;
    5682}
    5783
    58 void JPEGXLImageDecoder::clear()
     84void JPEGXLImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
     85{
     86    if (m_frameBufferCache.isEmpty())
     87        return;
     88
     89    // Unlike the png and gif cases, we can always try to clear frames before "clearBeforeFrame" because
     90    // the dependenciy to the previous frame is handled by libjxl.
     91    const Vector<ScalableImageDecoderFrame>::iterator end(m_frameBufferCache.begin() + clearBeforeFrame);
     92
     93    for (Vector<ScalableImageDecoderFrame>::iterator i(m_frameBufferCache.begin()); i != end; ++i) {
     94        // If the frame is partial, we're still on the way to decode the frame and it's likely
     95        // we continue the decode, so we don't clear the frame.
     96        if (i->isPartial())
     97            continue;
     98
     99        i->clear();
     100    }
     101}
     102
     103bool JPEGXLImageDecoder::setFailed()
    59104{
    60105    m_decoder.reset();
     106    return ScalableImageDecoder::setFailed();
     107}
     108
     109void JPEGXLImageDecoder::tryDecodeSize(bool allDataReceived)
     110{
     111    if (m_basicInfo)
     112        return;
     113    decode(Query::Size, 0, allDataReceived);
     114}
     115
     116bool JPEGXLImageDecoder::hasAlpha() const
     117{
     118    return m_basicInfo && m_basicInfo->alpha_bits > 0;
     119}
     120
     121bool JPEGXLImageDecoder::hasAnimation() const
     122{
     123    return m_basicInfo && m_basicInfo->have_animation;
     124}
     125
     126void JPEGXLImageDecoder::ensureDecoderInitialized()
     127{
     128    if (failed())
     129        return;
     130
     131    if (m_decoder)
     132        return;
     133
     134    m_decoder = JxlDecoderMake(nullptr);
     135    if (!m_decoder) {
     136        setFailed();
     137        return;
     138    }
     139
     140    if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
     141        setFailed();
     142        return;
     143    }
     144
    61145    m_readOffset = 0;
    62 }
    63 
    64 bool JPEGXLImageDecoder::setFailed()
    65 {
    66     clear();
    67     return ScalableImageDecoder::setFailed();
    68 }
    69 
    70 void JPEGXLImageDecoder::decode(bool onlySize, bool allDataReceived)
     146    m_currentFrame = 0;
     147    m_lastQuery = Query::Size;
     148}
     149
     150bool JPEGXLImageDecoder::shouldRewind(Query query, size_t frameIndex) const
     151{
     152    if (m_lastQuery == Query::FrameCount) {
     153        // If the current query is not FrameCount, we've completed the previous FrameCount query
     154        // and the decoder has reached the EOF, so we need to rewind the decoder for the new query.
     155        // Otherwise we continue the FrameCount query, so we must not rewind the decoder.
     156        return query != Query::FrameCount;
     157    }
     158
     159    if (m_lastQuery == Query::Size) {
     160        // The decoder is at the stream header and doesn't need rewind.
     161        return false;
     162    }
     163
     164    // At this point we know m_lastQuery is Query::DecodedImage.
     165
     166    if (query != Query::DecodedImage)
     167        return true;
     168
     169    // There's two cases where we don't need to rewind:
     170    //   1. Previous decoding was interrupted with JXL_DEC_NEED_MORE_INPUT
     171    //      and trying to continue decoding the same frame.
     172    //   2. Previous decoding was completed (m_currentFrame was incremented) and starting a new frame.
     173    // In both cases frameIndex is equal to m_currentFrame.
     174    return frameIndex != m_currentFrame;
     175}
     176
     177void JPEGXLImageDecoder::rewind()
     178{
     179    JxlDecoderRewind(m_decoder.get());
     180    JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE);
     181    m_readOffset = 0;
     182    m_currentFrame = 0;
     183}
     184
     185void JPEGXLImageDecoder::updateFrameCount()
    71186{
    72187    if (failed())
    73188        return;
    74189
    75     if (!m_decoder) {
    76         clear();
    77         m_decoder = JxlDecoderMake(nullptr);
    78         if (!m_decoder) {
    79             setFailed();
    80             return;
    81         }
    82 
    83         if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
    84             setFailed();
    85             return;
    86         }
    87     }
     190    decode(Query::FrameCount, 0, isAllDataReceived());
     191
     192    if (m_frameCount != m_frameBufferCache.size())
     193        m_frameBufferCache.resize(m_frameCount);
     194}
     195
     196void JPEGXLImageDecoder::decode(Query query, size_t frameIndex, bool allDataReceived)
     197{
     198    ensureDecoderInitialized();
     199
     200    if (failed())
     201        return;
     202
     203    if (shouldRewind(query, frameIndex)) {
     204        rewind();
     205        // We care about the frameIndex only if the query is Query::DecodedImage.
     206        if (query == Query::DecodedImage && frameIndex) {
     207            JxlDecoderSkipFrames(m_decoder.get(), frameIndex);
     208            m_currentFrame = frameIndex;
     209        }
     210    }
     211
     212    m_lastQuery = query;
    88213
    89214    m_data->data();
     
    94219    }
    95220
    96     JxlDecoderStatus status = processInput(onlySize);
     221    JxlDecoderStatus status = processInput(query);
    97222    // We set the status as failed if the decoder reports an error or requires more data while all data has been received.
    98223    if (status == JXL_DEC_ERROR || (allDataReceived && status == JXL_DEC_NEED_MORE_INPUT)) {
     
    101226    }
    102227
    103     // We release the decoder when we finish the decoding.
    104     if (status == JXL_DEC_SUCCESS) {
    105         clear();
    106         return;
    107     }
    108 
    109     // Otherwise we get the decoder ready for subsequent data.
     228    if (query == Query::DecodedImage && status == JXL_DEC_FULL_IMAGE && m_isLastFrameHeaderReceived && m_currentFrame == m_frameCount) {
     229        // We free the decoder when the last frame is decoded.
     230        m_decoder.reset();
     231        return;
     232    }
     233
    110234    size_t remainingDataSize = JxlDecoderReleaseInput(m_decoder.get());
    111235    m_readOffset = dataSize - remainingDataSize;
    112236}
    113237
    114 JxlDecoderStatus JPEGXLImageDecoder::processInput(bool onlySize)
     238JxlDecoderStatus JPEGXLImageDecoder::processInput(Query query)
    115239{
    116240    while (true) {
    117241        auto status = JxlDecoderProcessInput(m_decoder.get());
     242
     243        // Return JXL_DEC_ERROR when we've reached EOF without receiving a frame marked as "is_last".
     244        if (status == JXL_DEC_SUCCESS && !m_isLastFrameHeaderReceived)
     245            return JXL_DEC_ERROR;
    118246
    119247        // JXL_DEC_ERROR and JXL_DEC_SUCCESS are terminal states. We also exit from the loop if more data is needed.
     
    122250
    123251        if (status == JXL_DEC_BASIC_INFO) {
    124             JxlBasicInfo basicInfo;
    125             if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS)
    126                 return JXL_DEC_ERROR;
    127 
    128             setSize(IntSize(basicInfo.xsize, basicInfo.ysize));
    129             if (onlySize)
     252            if (!m_basicInfo) {
     253                JxlBasicInfo basicInfo;
     254                if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS)
     255                    return JXL_DEC_ERROR;
     256
     257                m_basicInfo = basicInfo;
     258            }
     259
     260            if (query == Query::Size) {
     261                // setSize() must be called only if the query is Query::Size,
     262                // otherwise this would roll back the encoded data status from completed.
     263                setSize(IntSize(m_basicInfo->xsize, m_basicInfo->ysize));
    130264                return status;
     265            }
     266
    131267            continue;
    132268        }
     
    134270        if (status == JXL_DEC_FRAME) {
    135271            JxlPixelFormat format { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
     272            JxlFrameHeader frameHeader;
     273            if (JxlDecoderGetFrameHeader(m_decoder.get(), &frameHeader) != JXL_DEC_SUCCESS)
     274                return JXL_DEC_ERROR;
     275
     276            m_frameCount = std::max(m_frameCount, m_currentFrame + 1);
     277
     278            if (frameHeader.is_last)
     279                m_isLastFrameHeaderReceived = true;
     280
     281            if (query != Query::DecodedImage) {
     282                if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, [](void*, size_t, size_t, size_t, const void*) { }, nullptr) != JXL_DEC_SUCCESS)
     283                    return JXL_DEC_ERROR;
     284
     285                continue;
     286            }
     287
     288            if (m_currentFrame >= m_frameBufferCache.size())
     289                m_frameBufferCache.resize(m_frameCount + 1);
     290
     291            auto& buffer = m_frameBufferCache[m_currentFrame];
     292            if (buffer.isInvalid() && buffer.initialize(size(), m_premultiplyAlpha)) {
     293                buffer.setDecodingStatus(DecodingStatus::Partial);
     294                buffer.setHasAlpha(false);
     295                if (m_basicInfo && m_basicInfo->have_animation) {
     296                    buffer.setDuration(Seconds((double)frameHeader.duration * m_basicInfo->animation.tps_denominator / m_basicInfo->animation.tps_numerator));
     297                    buffer.setDisposalMethod(ScalableImageDecoderFrame::DisposalMethod::DoNotDispose);
     298                }
     299            }
     300
    136301            if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, imageOutCallback, this) != JXL_DEC_SUCCESS)
    137302                return JXL_DEC_ERROR;
     303
    138304            continue;
    139305        }
    140306
    141307        if (status == JXL_DEC_FULL_IMAGE) {
    142             if (m_frameBufferCache.isEmpty())
    143                 continue;
    144 
    145             auto& buffer = m_frameBufferCache[0];
    146             if (!buffer.isInvalid())
    147                 buffer.setDecodingStatus(DecodingStatus::Complete);
    148 
    149             // TODO: To support animated JPEG XL in the future we need to handle subsequent data.
    150             return JXL_DEC_SUCCESS;
     308            if (m_currentFrame < m_frameBufferCache.size()) {
     309                auto& buffer = m_frameBufferCache[m_currentFrame];
     310                if (!buffer.isInvalid())
     311                    buffer.setDecodingStatus(DecodingStatus::Complete);
     312            }
     313
     314            m_currentFrame++;
     315            if (query == Query::DecodedImage)
     316                return JXL_DEC_FULL_IMAGE;
     317
     318            ASSERT(query == Query::FrameCount);
     319            continue;
    151320        }
    152321    }
     
    160329void JPEGXLImageDecoder::imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels)
    161330{
    162     if (m_frameBufferCache.isEmpty())
    163         return;
    164 
    165     auto& buffer = m_frameBufferCache[0];
    166     if (buffer.isInvalid()) {
    167         if (!buffer.initialize(size(), m_premultiplyAlpha))
    168             return;
    169 
    170         buffer.setDecodingStatus(DecodingStatus::Partial);
    171         buffer.setHasAlpha(false);
    172     }
     331    if (m_currentFrame >= m_frameBufferCache.size())
     332        return;
     333
     334    auto& buffer = m_frameBufferCache[m_currentFrame];
     335    if (buffer.isInvalid())
     336        return;
    173337
    174338    uint32_t* row = buffer.backingStore()->pixelAt(x, y);
  • trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h

    r286011 r286628  
    4646    // ScalableImageDecoder
    4747    String filenameExtension() const override { return "jxl"_s; }
     48    size_t frameCount() const override;
     49    RepetitionCount repetitionCount() const override;
    4850    ScalableImageDecoderFrame* frameBufferAtIndex(size_t index) override;
     51    void clearFrameBufferCache(size_t clearBeforeFrame) override;
    4952
    5053    bool setFailed() override;
    5154
    5255private:
     56    enum class Query {
     57        // This query is used for tryDecodeSize().
     58        Size,
     59        // We define a query for frame count because JPEG XL doesn't have frame count information in its code stream
     60        // so we need to scan the code stream to get the frame count for animated JPEG XL.
     61        // JPEG XL container can have frame count metadata but currently libjxl doesn't support it.
     62        FrameCount,
     63        // Query to decode a single frame.
     64        DecodedImage,
     65    };
     66
    5367    JPEGXLImageDecoder(AlphaOption, GammaAndColorProfileOption);
    54     void tryDecodeSize(bool allDataReceived) override { decode(true, allDataReceived); }
     68    void tryDecodeSize(bool allDataReceived) override;
    5569
    56     void decode(bool onlySize, bool allDataReceived);
     70    bool hasAlpha() const;
     71    bool hasAnimation() const;
    5772
    58     void clear();
     73    void ensureDecoderInitialized();
     74    bool shouldRewind(Query , size_t frameIndex) const;
     75    void rewind();
     76    void updateFrameCount();
    5977
    60     JxlDecoderStatus processInput(bool onlySize);
    61 
    62     static void imageOutCallback(void* that, size_t x, size_t y, size_t numPixels, const void* pixels);
     78    void decode(Query, size_t frameIndex, bool allDataReceived);
     79    JxlDecoderStatus processInput(Query);
     80    static void imageOutCallback(void*, size_t x, size_t y, size_t numPixels, const void* pixels);
    6381    void imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels);
    6482
    6583    JxlDecoderPtr m_decoder;
    66     size_t m_readOffset = 0;
     84    size_t m_readOffset { 0 };
     85    std::optional<JxlBasicInfo> m_basicInfo;
     86
     87    Query m_lastQuery { Query::Size };
     88    size_t m_frameCount { 1 };
     89    size_t m_currentFrame { 0 };
     90
     91    bool m_isLastFrameHeaderReceived { false }; // If this is true, we know we don't need to update m_frameCount.
    6792};
    6893
Note: See TracChangeset for help on using the changeset viewer.