1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

fix(video): fix slanted video when video size is not divisible by 8

This commit is contained in:
initramfs 2016-04-25 10:39:57 -04:00 committed by initramfs
parent 3df6b990ae
commit 904495d2bf
No known key found for this signature in database
GPG Key ID: 78B8BDF87E9EF0AF
3 changed files with 213 additions and 59 deletions

View File

@ -77,7 +77,7 @@ void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
int bufSize = av_image_alloc(avframe->data, avframe->linesize,
width, height,
static_cast<AVPixelFormat>(AV_PIX_FMT_YUV420P), VideoFrame::frameAlignment);
static_cast<AVPixelFormat>(AV_PIX_FMT_YUV420P), VideoFrame::dataAlignment);
if(bufSize < 0){
av_frame_free(&avframe);

View File

@ -27,10 +27,11 @@ extern "C"{
VideoFrame::VideoFrame(AVFrame* sourceFrame, QRect dimensions, int pixFmt, std::function<void ()> destructCallback, bool freeSourceFrame)
: sourceDimensions(dimensions),
sourcePixelFormat(pixFmt),
sourceFrameKey(getFrameKey(dimensions.size(), pixFmt, sourceFrame->linesize[0])),
freeSourceFrame(freeSourceFrame),
destructCallback(destructCallback)
{
frameBuffer[pixFmt][dimensionsToKey(dimensions.size())] = sourceFrame;
frameBuffer[sourceFrameKey] = sourceFrame;
}
VideoFrame::VideoFrame(AVFrame* sourceFrame, std::function<void ()> destructCallback, bool freeSourceFrame)
@ -62,7 +63,7 @@ bool VideoFrame::isValid()
return retValue;
}
const AVFrame* VideoFrame::getAVFrame(const QSize& dimensions, const int pixelFormat)
const AVFrame* VideoFrame::getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned)
{
frameLock.lockForRead();
@ -73,7 +74,12 @@ const AVFrame* VideoFrame::getAVFrame(const QSize& dimensions, const int pixelFo
return nullptr;
}
AVFrame* ret = retrieveAVFrame(dimensions, pixelFormat);
if(frameSize.width() == 0 && frameSize.height() == 0)
{
frameSize = sourceDimensions.size();
}
AVFrame* ret = retrieveAVFrame(frameSize, pixelFormat, requireAligned);
if(ret)
{
@ -82,19 +88,19 @@ const AVFrame* VideoFrame::getAVFrame(const QSize& dimensions, const int pixelFo
}
// VideoFrame does not contain an AVFrame to spec, generate one here
ret = generateAVFrame(dimensions, pixelFormat);
ret = generateAVFrame(frameSize, pixelFormat, requireAligned);
/*
* We need to "upgrade" the lock to a write lock so we can update our frameBuffer map.
*
* It doesn't matter if another thread obtains the write lock before we finish since it is
* likely writing to somewhere else. Absolute worst-case scenario, we merely perform the
* generation process twice, and discard the old result.
* likely writing to somewhere else. Worst-case scenario, we merely perform the generation
* process twice, and discard the old result.
*/
frameLock.unlock();
frameLock.lockForWrite();
storeAVFrame(ret, dimensions, pixelFormat);
storeAVFrame(ret, frameSize, pixelFormat);
frameLock.unlock();
return ret;
@ -125,7 +131,7 @@ QImage VideoFrame::toQImage(QSize frameSize)
frameSize = sourceDimensions.size();
}
AVFrame* frame = retrieveAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_RGB24));
AVFrame* frame = retrieveAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_RGB24), false);
if(frame)
{
@ -136,14 +142,14 @@ QImage VideoFrame::toQImage(QSize frameSize)
}
// VideoFrame does not contain an AVFrame to spec, generate one here
frame = generateAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_RGB24));
frame = generateAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_RGB24), false);
/*
* We need to "upgrade" the lock to a write lock so we can update our frameBuffer map.
*
* It doesn't matter if another thread obtains the write lock before we finish since it is
* likely writing to somewhere else. Absolute worst-case scenario, we merely perform the
* generation process twice, and discard the old result.
* likely writing to somewhere else. Worst-case scenario, we merely perform the generation
* process twice, and discard the old result.
*/
frameLock.unlock();
frameLock.lockForWrite();
@ -172,7 +178,7 @@ vpx_image* VideoFrame::toVpxImage(QSize frameSize)
frameSize = sourceDimensions.size();
}
AVFrame* frame = retrieveAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_YUV420P));
AVFrame* frame = retrieveAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_YUV420P), true);
if(frame)
{
@ -196,14 +202,14 @@ vpx_image* VideoFrame::toVpxImage(QSize frameSize)
}
// VideoFrame does not contain an AVFrame to spec, generate one here
frame = generateAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_YUV420P));
frame = generateAVFrame(frameSize, static_cast<int>(AV_PIX_FMT_YUV420P), true);
/*
* We need to "upgrade" the lock to a write lock so we can update our frameBuffer map.
*
* It doesn't matter if another thread obtains the write lock before we finish since it is
* likely writing to somewhere else. Absolute worst-case scenario, we merely perform the
* generation process twice, and discard the old result.
* likely writing to somewhere else. Worst-case scenario, we merely perform the generation
* process twice, and discard the old result.
*/
frameLock.unlock();
frameLock.lockForWrite();
@ -229,11 +235,27 @@ vpx_image* VideoFrame::toVpxImage(QSize frameSize)
return ret;
}
AVFrame* VideoFrame::retrieveAVFrame(const QSize& dimensions, const int pixelFormat)
AVFrame* VideoFrame::retrieveAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned)
{
if(frameBuffer.contains(pixelFormat) && frameBuffer[pixelFormat].contains(dimensionsToKey(dimensions)))
if(!requireAligned)
{
return frameBuffer[pixelFormat][dimensionsToKey(dimensions)];
/*
* We attempt to obtain a unaligned frame first because an unaligned linesize corresponds
* to a data aligned frame.
*/
FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, false);
if(frameBuffer.count(frameKey) > 0)
{
return frameBuffer[frameKey];
}
}
FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, true);
if(frameBuffer.count(frameKey) > 0)
{
return frameBuffer[frameKey];
}
else
{
@ -241,7 +263,7 @@ AVFrame* VideoFrame::retrieveAVFrame(const QSize& dimensions, const int pixelFor
}
}
AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFormat)
AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned)
{
AVFrame* ret = av_frame_alloc();
@ -254,9 +276,25 @@ AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFor
ret->height = dimensions.height();
ret->format = pixelFormat;
int bufSize = av_image_alloc(ret->data, ret->linesize,
/*
* We generate a frame under data alignment only if the dimensions allow us to be frame aligned
* or if the caller doesn't require frame alignment
*/
int bufSize;
if(!requireAligned || (dimensions.width() % 8 == 0 && dimensions.height() % 8 == 0))
{
bufSize = av_image_alloc(ret->data, ret->linesize,
dimensions.width(), dimensions.height(),
static_cast<AVPixelFormat>(pixelFormat), frameAlignment);
static_cast<AVPixelFormat>(pixelFormat), dataAlignment);
}
else
{
bufSize = av_image_alloc(ret->data, ret->linesize,
dimensions.width(), dimensions.height(),
static_cast<AVPixelFormat>(pixelFormat), 1);
}
if(bufSize < 0){
av_frame_free(&ret);
@ -279,7 +317,7 @@ AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFor
return nullptr;
}
AVFrame* source = frameBuffer[sourcePixelFormat][dimensionsToKey(sourceDimensions.size())];
AVFrame* source = frameBuffer[sourceFrameKey];
sws_scale(swsCtx, source->data, source->linesize, 0, sourceDimensions.height(), ret->data, ret->linesize);
sws_freeContext(swsCtx);
@ -289,10 +327,12 @@ AVFrame* VideoFrame::generateAVFrame(const QSize& dimensions, const int pixelFor
void VideoFrame::storeAVFrame(AVFrame* frame, const QSize& dimensions, const int pixelFormat)
{
FrameBufferKey frameKey = getFrameKey(dimensions, pixelFormat, frame->linesize[0]);
// We check the prescence of the frame in case of double-computation
if(frameBuffer.contains(pixelFormat) && frameBuffer[pixelFormat].contains(dimensionsToKey(dimensions)))
if(frameBuffer.count(frameKey) > 0)
{
AVFrame* old_ret = frameBuffer[pixelFormat][dimensionsToKey(dimensions)];
AVFrame* old_ret = frameBuffer[frameKey];
// Free old frame
av_freep(&old_ret->data[0]);
@ -300,39 +340,38 @@ void VideoFrame::storeAVFrame(AVFrame* frame, const QSize& dimensions, const int
av_frame_free(&old_ret);
}
frameBuffer[pixelFormat].insert(dimensionsToKey(dimensions), frame);
frameBuffer[frameKey] = frame;
}
void VideoFrame::deleteFrameBuffer()
{
const quint64 sourceKey = dimensionsToKey(sourceDimensions.size());
for(auto pixFmtIter = frameBuffer.begin(); pixFmtIter != frameBuffer.end(); ++pixFmtIter)
for(auto frameIterator = frameBuffer.begin(); frameIterator != frameBuffer.end(); ++frameIterator)
{
const auto& pixFmtMap = pixFmtIter.value();
AVFrame* frame = frameIterator->second;
for(auto dimKeyIter = pixFmtMap.begin(); dimKeyIter != pixFmtMap.end(); ++dimKeyIter)
// Treat source frame and derived frames separately
if(sourceFrameKey == frameIterator->first)
{
AVFrame* frame = dimKeyIter.value();
// Treat source frame and derived frames separately
if(pixFmtIter.key() == sourcePixelFormat && dimKeyIter.key() == sourceKey)
{
if(freeSourceFrame)
{
av_freep(&frame->data[0]);
}
av_frame_unref(frame);
av_frame_free(&frame);
}
else
if(freeSourceFrame)
{
av_freep(&frame->data[0]);
av_frame_unref(frame);
av_frame_free(&frame);
}
av_frame_unref(frame);
av_frame_free(&frame);
}
else
{
av_freep(&frame->data[0]);
av_frame_unref(frame);
av_frame_free(&frame);
}
}
frameBuffer.clear();
}
VideoFrame::FrameBufferKey::FrameBufferKey(const int pixFmt, const int width, const int height, const bool lineAligned)
: frameWidth(width),
frameHeight(height),
pixelFormat(pixFmt),
linesizeAligned(lineAligned){}

View File

@ -20,7 +20,6 @@
#ifndef VIDEOFRAME_H
#define VIDEOFRAME_H
#include <QHash>
#include <QImage>
#include <QReadWriteLock>
#include <QRect>
@ -33,6 +32,7 @@ extern "C"{
#include <vpx/vpx_image.h>
#include <functional>
#include <memory>
#include <unordered_map>
/**
* @brief An ownernship and management class for AVFrames.
@ -44,6 +44,14 @@ extern "C"{
*
* Every function in this class is thread safe apart from concurrent construction and deletion of
* the object.
*
* This class uses the phrase "frame alignment" to specify the property that each frame's width is
* equal to it's maximum linesize. Note: this is NOT "data alignment" which specifies how allocated
* buffers are aligned in memory. Though internally the two are related, unless otherwise specified
* all instances of the term "alignment" exposed from public functions refer to frame alignment.
*
* Frame alignment is an important concept because ToxAV does not support frames with linesizes not
* directly equal to the width.
*/
class VideoFrame
{
@ -91,12 +99,14 @@ public:
* If a given frame does not exist, this function will perform appropriate conversions to
* return a frame that fulfills the given parameters.
*
* @param dimensions the dimensions of the frame.
* @param frameSize the dimensions of the frame to get. If frame size is 0, defaults to source
* frame size.
* @param pixelFormat the desired pixel format of the frame.
* @param requireAligned true if the returned frame must be frame aligned, false if not.
* @return a pointer to a AVFrame with the given parameters or nullptr if the VideoFrame is no
* longer valid.
*/
const AVFrame* getAVFrame(const QSize& dimensions, const int pixelFormat);
const AVFrame* getAVFrame(QSize frameSize, const int pixelFormat, const bool requireAligned);
/**
* @brief Releases all frames managed by this VideoFrame and invalidates it.
@ -151,21 +161,122 @@ public:
/**
* @brief Data alignment parameter used to populate AVFrame buffers.
*
* This field is public in effort to standardized the frame alignment parameter for all AVFrame
* This field is public in effort to standardized the data alignment parameter for all AVFrame
* allocations.
*
* It's currently set to 32-byte alignment for AVX2 support.
*/
static constexpr int frameAlignment = 32;
static constexpr int dataAlignment = 32;
private:
/**
* @brief A function to create a hashable key from a given QSize dimension.
* @param dimensions the dimensions to hash.
* @return a hashable unsigned 64-bit number representing the dimension.
* @brief A class representing a structure that stores frame properties to be used as the key
* value for a std::unordered_map.
*/
static inline quint64 dimensionsToKey(const QSize& dimensions)
class FrameBufferKey{
public:
/**
* @brief Constructs a new FrameBufferKey with the given attributes.
*
* @param width the width of the frame.
* @param height the height of the frame.
* @param pixFmt the pixel format of the frame.
* @param lineAligned whether the linesize matches the width of the image.
*/
FrameBufferKey(const int width, const int height, const int pixFmt, const bool lineAligned);
// Explictly state default constructor/destructor
FrameBufferKey(const FrameBufferKey&) = default;
FrameBufferKey(FrameBufferKey&&) = default;
~FrameBufferKey() = default;
// Assignment operators are disabled for the FrameBufferKey
const FrameBufferKey& operator=(const FrameBufferKey&) = delete;
const FrameBufferKey& operator=(FrameBufferKey&&) = delete;
/**
* @brief Comparison operator for FrameBufferKey.
*
* @param other instance to compare against.
* @return true if instances are equivilent, false otherwise.
*/
inline bool operator==(const FrameBufferKey& other) const
{
return pixelFormat == other.pixelFormat &&
frameWidth == other.frameWidth &&
frameHeight == other.frameHeight &&
linesizeAligned == other.linesizeAligned;
}
/**
* @brief Not equal to operator for FrameBufferKey.
*
* @param other instance to compare against
* @return true if instances are not equivilent, false otherwise.
*/
inline bool operator!=(const FrameBufferKey& other) const
{
return !operator==(other);
}
/**
* @brief Hash function for class.
*
* This function computes a hash value for use with std::unordered_map.
*
* @param key the given instance to compute hash value of.
* @return the hash of the given instance.
*/
static inline size_t hash(const FrameBufferKey& key)
{
std::hash<int> intHasher;
std::hash<bool> boolHasher;
// Use java-style hash function to combine fields
size_t ret = 47;
ret = 37 * ret + intHasher(key.frameWidth);
ret = 37 * ret + intHasher(key.frameHeight);
ret = 37 * ret + intHasher(key.pixelFormat);
ret = 37 * ret + boolHasher(key.linesizeAligned);
return ret;
}
public:
const int frameWidth;
const int frameHeight;
const int pixelFormat;
const bool linesizeAligned;
};
private:
/**
* @brief Generates a key object based on given parameters.
*
* @param frameSize the given size of the frame.
* @param pixFmt the pixel format of the frame.
* @param linesize the maximum linesize of the frame, may be larger than the width.
* @return a FrameBufferKey object representing the key for the frameBuffer map.
*/
static inline FrameBufferKey getFrameKey(const QSize& frameSize, const int pixFmt, const int linesize)
{
return (static_cast<quint64>(dimensions.width()) << 32) | dimensions.height();
return getFrameKey(frameSize, pixFmt, frameSize.width() == linesize);
}
/**
* @brief Generates a key object based on given parameters.
*
* @param frameSize the given size of the frame.
* @param pixFmt the pixel format of the frame.
* @param frameAligned true if the frame is aligned, false otherwise.
* @return a FrameBufferKey object representing the key for the frameBuffer map.
*/
static inline FrameBufferKey getFrameKey(const QSize& frameSize, const int pixFmt, const bool frameAligned)
{
return {frameSize.width(), frameSize.height(), pixFmt, frameAligned};
}
/**
@ -179,10 +290,11 @@ private:
*
* @param dimensions the dimensions of the frame.
* @param pixelFormat the desired pixel format of the frame.
* @param requireAligned true if the frame must be frame aligned, false otherwise.
* @return a pointer to a AVFrame with the given parameters or nullptr if no such frame was
* found.
*/
AVFrame* retrieveAVFrame(const QSize& dimensions, const int pixelFormat);
AVFrame* retrieveAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned);
/**
* @brief Generates an AVFrame based on the given specifications.
@ -191,9 +303,10 @@ private:
*
* @param dimensions the required dimensions for the frame.
* @param pixelFormat the required pixel format for the frame.
* @param requireAligned true if the generated frame needs to be frame aligned, false otherwise.
* @return an AVFrame with the given specifications.
*/
AVFrame* generateAVFrame(const QSize& dimensions, const int pixelFormat);
AVFrame* generateAVFrame(const QSize& dimensions, const int pixelFormat, const bool requireAligned);
/**
* @brief Stores a given AVFrame within the frameBuffer map.
@ -212,13 +325,15 @@ private:
* This function is not thread-safe and must be called from a thread-safe context.
*/
void deleteFrameBuffer();
private:
// Main framebuffer store
QHash<int, QHash<quint64, AVFrame*>> frameBuffer {};
std::unordered_map<FrameBufferKey, AVFrame*, std::function<decltype(FrameBufferKey::hash)>> frameBuffer {3, FrameBufferKey::hash};
// Source frame
const QRect sourceDimensions;
const int sourcePixelFormat;
const FrameBufferKey sourceFrameKey;
const bool freeSourceFrame;
// Destructor callback