Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,7 @@ python3 tests/video_test_framework_encode.py --test "encode_h264_*"
```

For complete documentation, command-line options, and advanced usage examples, see **[tests/README.md](tests/README.md)**.

## Contributing

We welcome contributions! Please see [CONTRIBUTING](CONTRIBUTING) for guidelines on commit messages, pull requests, and testing requirements.
11 changes: 7 additions & 4 deletions common/include/VkVSCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,17 @@ inline const char* string_VkResult_Extended(VkResult result) {
}

// Helper function to check if a VkResult indicates video profile/feature not supported
Comment thread
dabrain34 marked this conversation as resolved.
// Returns true if the error indicates the hardware/driver doesn't support the requested
// video codec profile or feature (as opposed to an actual runtime error)
inline bool IsVideoProfileNotSupportedError(VkResult result) {
// Returns true for video-specific KHR errors (profile, format, codec, std version)
// and general Vulkan capability errors (format, feature, driver, extension)
inline bool IsVideoUnsupportedResult(VkResult result) {
return result == VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR ||
result == VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR ||
result == VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR ||
result == VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR ||
result == VK_ERROR_FORMAT_NOT_SUPPORTED ||
result == VK_ERROR_FEATURE_NOT_PRESENT;
result == VK_ERROR_FEATURE_NOT_PRESENT ||
result == VK_ERROR_INCOMPATIBLE_DRIVER ||
result == VK_ERROR_EXTENSION_NOT_PRESENT;
}

#endif // __cplusplus
Expand Down
5 changes: 3 additions & 2 deletions common/libs/VkCodecUtils/VkVideoFrameOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ class VkVideoFrameOutput : public VkVideoRefCountBase {
*
* @param pFrame Pointer to the decoded frame
* @param vkDevCtx Vulkan device context
* @return size_t Number of bytes written, (size_t)-1 on error
* @param bytesWritten Optional pointer to receive number of bytes written
* @return bool true on success, false on error
*/
virtual size_t OutputFrame(VulkanDecodedFrame* pFrame, const VulkanDeviceContext* vkDevCtx) = 0;
virtual bool OutputFrame(VulkanDecodedFrame* pFrame, const VulkanDeviceContext* vkDevCtx, size_t* bytesWritten = nullptr) = 0;

protected:
VkVideoFrameOutput() = default;
Expand Down
111 changes: 80 additions & 31 deletions common/libs/VkCodecUtils/VkVideoFrameToFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,14 @@ class VkVideoFrameToFileImpl : public VkVideoFrameOutput {
return ret;
}

virtual size_t OutputFrame(VulkanDecodedFrame* pFrame, const VulkanDeviceContext* vkDevCtx) override {
virtual bool OutputFrame(VulkanDecodedFrame* pFrame, const VulkanDeviceContext* vkDevCtx, size_t* bytesWritten = nullptr) override {
if (bytesWritten) {
*bytesWritten = 0;
}

if (!IsFileStreamValid()) {
return (size_t)-1;
std::cerr << "ERROR: Output file stream is not valid\n";
Comment thread
dabrain34 marked this conversation as resolved.
return false;
}

assert(pFrame != nullptr);
Expand All @@ -218,6 +223,10 @@ class VkVideoFrameToFileImpl : public VkVideoFrameOutput {
const VkMpFormatInfo* mpInfo = YcbcrVkFormatInfo(format);
size_t usedBufferSize = ConvertFrameToNv12(vkDevCtx, pFrame->displayWidth, pFrame->displayHeight,
imageResource, pOutputBuffer, mpInfo);
if (usedBufferSize == 0) {
std::cerr << "ERROR: Failed to convert frame to NV12. Output size is 0\n";
return false;
}

if (m_outputcrcPerFrame && m_crcOutputFile) {
fprintf(m_crcOutputFile, "CRC Frame[%" PRId64 "]:", pFrame->displayOrder);
Expand All @@ -239,9 +248,9 @@ class VkVideoFrameToFileImpl : public VkVideoFrameOutput {
}

if (m_outputy4m) {
return WriteFrameToFileY4M(0, usedBufferSize, pFrame->displayWidth, pFrame->displayHeight, mpInfo);
return WriteFrameToFileY4M(0, usedBufferSize, pFrame->displayWidth, pFrame->displayHeight, mpInfo, bytesWritten);
} else {
return WriteDataToFile(0, usedBufferSize);
return WriteDataToFile(0, usedBufferSize, bytesWritten);
}
}

Expand Down Expand Up @@ -269,53 +278,93 @@ class VkVideoFrameToFileImpl : public VkVideoFrameOutput {
return IsFileStreamValid();
}

size_t WriteDataToFile(size_t offset, size_t size) {
return fwrite(m_pLinearMemory + offset, size, 1, m_outputFile);
bool WriteDataToFile(size_t offset, size_t size, size_t* bytesWritten) {
size_t totalBytesWritten = 0;
while (totalBytesWritten < size) {
size_t remainingBytes = size - totalBytesWritten;
size_t written = fwrite(m_pLinearMemory + offset + totalBytesWritten, 1, remainingBytes, m_outputFile);
if (ferror(m_outputFile)) {
fprintf(stderr, "ERROR: fwrite failed (wrote %zu of %zu bytes)\n",
totalBytesWritten + written, size);
if (bytesWritten) {
*bytesWritten = totalBytesWritten + written;
}
return false;
}
if (written == 0) {
fprintf(stderr, "ERROR: fwrite wrote 0 bytes unexpectedly (wrote %zu of %zu bytes)\n",
totalBytesWritten, size);
if (bytesWritten) {
*bytesWritten = totalBytesWritten;
}
return false;
}
totalBytesWritten += written;
}
if (bytesWritten) {
*bytesWritten = totalBytesWritten;
}
return true;
}

size_t GetMaxFrameSize() {
return m_allocationSize;
}

size_t WriteFrameToFileY4M(size_t offset, size_t size, size_t width, size_t height,
const VkMpFormatInfo* mpInfo) {
bool WriteFrameToFileY4M(size_t offset, size_t size, size_t width, size_t height,
const VkMpFormatInfo* mpInfo, size_t* bytesWritten) {

auto setErrorAndReturn = [&bytesWritten]() {
Comment thread
dabrain34 marked this conversation as resolved.
if (bytesWritten) {
*bytesWritten = 0;
}
return false;
};

if (mpInfo == nullptr) {
fprintf(stderr, "ERROR: mpInfo is required for Y4M output\n");
return setErrorAndReturn();;
}

if (m_firstFrame != false) {
m_firstFrame = false;
fprintf(m_outputFile, "YUV4MPEG2 ");
fprintf(m_outputFile, "W%i H%i ", (int)width, (int)height);
m_height = height;
m_width = width;
fprintf(m_outputFile, "F24:1 ");
fprintf(m_outputFile, "Ip ");
fprintf(m_outputFile, "A1:1 ");
if (mpInfo->planesLayout.secondaryPlaneSubsampledX == false) {
fprintf(m_outputFile, "C444");
} else {
fprintf(m_outputFile, "C420");
}

if (mpInfo->planesLayout.bpp != YCBCRA_8BPP) {
fprintf(m_outputFile, "p16");
}
const char* chromaFormat = mpInfo->planesLayout.secondaryPlaneSubsampledX ? "C420" : "C444";
const char* bitDepth = (mpInfo->planesLayout.bpp != YCBCRA_8BPP) ? "p16" : "";

fprintf(m_outputFile, "\n");
if (fprintf(m_outputFile, "YUV4MPEG2 W%i H%i F24:1 Ip A1:1 %s%s\n",
(int)width, (int)height, chromaFormat, bitDepth) < 0) {
fprintf(stderr, "ERROR: fprintf failed writing Y4M header\n");
return setErrorAndReturn();
}
}

fprintf(m_outputFile, "FRAME");
if ((m_width != width) || (m_height != height)) {
fprintf(m_outputFile, " ");
fprintf(m_outputFile, "W%i H%i", (int)width, (int)height);
if (fprintf(m_outputFile, "FRAME W%i H%i\n", (int)width, (int)height) < 0) {
fprintf(stderr, "ERROR: fprintf failed writing Y4M frame header\n");
return setErrorAndReturn();
}
m_height = height;
m_width = width;
} else {
if (fprintf(m_outputFile, "FRAME\n") < 0) {
fprintf(stderr, "ERROR: fprintf failed writing Y4M frame marker\n");
return setErrorAndReturn();
}
}

fprintf(m_outputFile, "\n");
return WriteDataToFile(offset, size);
return WriteDataToFile(offset, size, bytesWritten);
}

size_t ConvertFrameToNv12(const VulkanDeviceContext* vkDevCtx, int32_t frameWidth, int32_t frameHeight,
VkSharedBaseObj<VkImageResource>& imageResource,
uint8_t* pOutBuffer, const VkMpFormatInfo* mpInfo) {
if (mpInfo == nullptr) {
fprintf(stderr, "ERROR: mpInfo is required for NV12 conversion. Unable to convert frame, return 0.\n");
return 0;
}
size_t outputBufferSize = 0;
VkDevice device = imageResource->GetDevice();
VkImage srcImage = imageResource->GetImage();
Expand All @@ -331,21 +380,21 @@ class VkVideoFrameToFileImpl : public VkVideoFrameOutput {
int32_t secondaryPlaneHeight = frameHeight;
int32_t imageHeight = frameHeight;
bool isUnnormalizedRgba = false;
if (mpInfo && (mpInfo->planesLayout.layout == YCBCR_SINGLE_PLANE_UNNORMALIZED) && !(mpInfo->planesLayout.disjoint)) {
if ((mpInfo->planesLayout.layout == YCBCR_SINGLE_PLANE_UNNORMALIZED) && !(mpInfo->planesLayout.disjoint)) {
isUnnormalizedRgba = true;
}

if (mpInfo && mpInfo->planesLayout.secondaryPlaneSubsampledX) {
if (mpInfo->planesLayout.secondaryPlaneSubsampledX) {
secondaryPlaneWidth = (secondaryPlaneWidth + 1) / 2;
}
if (mpInfo && mpInfo->planesLayout.secondaryPlaneSubsampledY) {
if (mpInfo->planesLayout.secondaryPlaneSubsampledY) {
secondaryPlaneHeight = (secondaryPlaneHeight + 1) / 2;
}

VkImageSubresource subResource = {};
VkSubresourceLayout layouts[3] = {};

if (mpInfo && !isUnnormalizedRgba) {
if (!isUnnormalizedRgba) {
switch (mpInfo->planesLayout.layout) {
case YCBCR_SINGLE_PLANE_UNNORMALIZED:
case YCBCR_SINGLE_PLANE_INTERLEAVED:
Expand Down
25 changes: 17 additions & 8 deletions common/libs/VkCodecUtils/VkVideoQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
#include <stdint.h>
#include "VkCodecUtils/VkVideoRefCountBase.h"

/**
* @enum VkVideoQueueResult
* @brief Result codes for VkVideoQueue::GetNextFrame operations.
*/
enum class VkVideoQueueResult {
NewFrame,
NoFrame,
EndOfStream,
Error
};

/**
* @class VkVideoQueue
* @brief Interface for retrieving frames from a Vulkan-based video queue.
Expand Down Expand Up @@ -137,17 +148,15 @@ class VkVideoQueue : public VkVideoRefCountBase {
* information about the newly decoded frame, if available.
* If no frame is currently available or it was the end of the stream,
* there will be no data filled in pNewFrame.
* @param[out] endOfStream Set to `true` if the end of the stream has been reached (i.e., no more
* frames will ever be available), or `false` otherwise.
*
* @return
* - `1` if a decoded frame is successfully retrieved.
* - `0` if no frame is currently available (but not an error, you may need to parse more data or
* wait for pending frames to be reordered because of the B-frames are present).
* - `-1` if an error occurs or if the end of the stream is reached. In either case, decoding should
* be terminated or reset accordingly.
* - `VkVideoQueueResult::NewFrame` if a decoded frame is successfully retrieved.
* - `VkVideoQueueResult::NoFrame` if no frame is currently available (need more parsing
* or B-frame reordering).
* - `VkVideoQueueResult::EndOfStream` if the end of stream has been reached.
* - `VkVideoQueueResult::Error` if an error occurs during frame retrieval.
*/
virtual int32_t GetNextFrame(FrameDataType* pNewFrame, bool* endOfStream) = 0;
virtual VkVideoQueueResult GetNextFrame(FrameDataType* pNewFrame) = 0;

/**
* @brief Release a previously retrieved decoded frame.
Expand Down
9 changes: 4 additions & 5 deletions common/libs/VkCodecUtils/VulkanFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,17 +359,16 @@ bool VulkanFrame<FrameDataType>::OnFrame( int32_t renderIndex,

pLastDecodedFrame->Reset();

bool endOfStream = false;
int32_t numVideoFrames = 0;

numVideoFrames = m_videoQueue->GetNextFrame(pLastDecodedFrame, &endOfStream);
if (endOfStream && (numVideoFrames < 0)) {
VkVideoQueueResult result = m_videoQueue->GetNextFrame(pLastDecodedFrame);
if (result == VkVideoQueueResult::EndOfStream || result == VkVideoQueueResult::Error) {
continueLoop = false;
bool displayTimeNow = true;
float fps = GetFrameRateFps(displayTimeNow);
if (displayTimeNow) {
std::cout << "\t\tFrame " << m_frameCount << ", FPS: " << fps << std::endl;
}
} else if (dumpDebug && result == VkVideoQueueResult::NoFrame) {
std::cout << "No frame available, waiting for more data" << std::endl;
}
}

Expand Down
15 changes: 9 additions & 6 deletions common/libs/VkCodecUtils/VulkanVideoDisplayQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class VulkanVideoDisplayQueue : public VkVideoQueue<FrameDataType> {

virtual uint32_t GetProfileIdc() const { return 0; }
virtual VkExtent3D GetVideoExtent() const;
virtual int32_t GetNextFrame(FrameDataType* pFrame, bool* endOfStream);
virtual VkVideoQueueResult GetNextFrame(FrameDataType* pFrame);
virtual int32_t ReleaseFrame(FrameDataType* pDisplayedFrame);

static VkSharedBaseObj<VulkanVideoDisplayQueue>& invalidVulkanVideoDisplayQueue;
Expand Down Expand Up @@ -175,20 +175,23 @@ int32_t VulkanVideoDisplayQueue<FrameDataType>::EnqueueFrame(FrameDataType* pFra
}

template<class FrameDataType>
int32_t VulkanVideoDisplayQueue<FrameDataType>::GetNextFrame(FrameDataType* pFrame, bool* endOfStream)
VkVideoQueueResult VulkanVideoDisplayQueue<FrameDataType>::GetNextFrame(FrameDataType* pFrame)
{
if (m_exitQueueRequested) {
m_queue.SetFlushAndExit();
m_queueIsEnabled = false;
}

*endOfStream = !m_queue.WaitAndPop(*pFrame) && !m_queueIsEnabled;
bool gotFrame = m_queue.WaitAndPop(*pFrame);

if (*endOfStream) {
return -1;
if (!gotFrame) {
if (!m_queueIsEnabled) {
return VkVideoQueueResult::EndOfStream;
}
return VkVideoQueueResult::NoFrame;
}

return 1;
return VkVideoQueueResult::NewFrame;
}

template<class FrameDataType>
Expand Down
Loading