342 lines
10 KiB
C
342 lines
10 KiB
C
// Copyright 2020 Joe Drago. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// #define WIN32_MEMORY_LEAK_DETECTION
|
|
#ifdef WIN32_MEMORY_LEAK_DETECTION
|
|
#define _CRTDBG_MAP_ALLOC
|
|
#include <crtdbg.h>
|
|
#endif
|
|
|
|
#include "avif/avif.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32)
|
|
|
|
#include <windows.h>
|
|
|
|
typedef struct NextFilenameData
|
|
{
|
|
int didFirstFile;
|
|
HANDLE handle;
|
|
WIN32_FIND_DATA wfd;
|
|
} NextFilenameData;
|
|
|
|
static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd)
|
|
{
|
|
for (;;) {
|
|
if (nfd->didFirstFile) {
|
|
if (FindNextFile(nfd->handle, &nfd->wfd) == 0) {
|
|
// No more files
|
|
break;
|
|
}
|
|
} else {
|
|
char filenameBuffer[2048];
|
|
snprintf(filenameBuffer, sizeof(filenameBuffer), "%s\\*", parentDir);
|
|
filenameBuffer[sizeof(filenameBuffer) - 1] = 0;
|
|
nfd->handle = FindFirstFile(filenameBuffer, &nfd->wfd);
|
|
if (nfd->handle == INVALID_HANDLE_VALUE) {
|
|
return NULL;
|
|
}
|
|
nfd->didFirstFile = 1;
|
|
}
|
|
|
|
// If we get here, we should have a valid wfd
|
|
const char * dot = strrchr(nfd->wfd.cFileName, '.');
|
|
if (dot) {
|
|
++dot;
|
|
if (!strcmp(dot, extension)) {
|
|
return nfd->wfd.cFileName;
|
|
}
|
|
}
|
|
}
|
|
|
|
FindClose(nfd->handle);
|
|
nfd->handle = INVALID_HANDLE_VALUE;
|
|
nfd->didFirstFile = 0;
|
|
return NULL;
|
|
}
|
|
|
|
#else
|
|
#include <dirent.h>
|
|
typedef struct NextFilenameData
|
|
{
|
|
DIR * dir;
|
|
} NextFilenameData;
|
|
|
|
static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd)
|
|
{
|
|
if (!nfd->dir) {
|
|
nfd->dir = opendir(parentDir);
|
|
if (!nfd->dir) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
struct dirent * entry;
|
|
while ((entry = readdir(nfd->dir)) != NULL) {
|
|
const char * dot = strrchr(entry->d_name, '.');
|
|
if (dot) {
|
|
++dot;
|
|
if (!strcmp(dot, extension)) {
|
|
return entry->d_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(nfd->dir);
|
|
nfd->dir = NULL;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
typedef struct avifIOTestReader
|
|
{
|
|
avifIO io;
|
|
avifROData rodata;
|
|
size_t availableBytes;
|
|
} avifIOTestReader;
|
|
|
|
static avifResult avifIOTestReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out)
|
|
{
|
|
// printf("avifIOTestReaderRead offset %" PRIu64 " size %zu\n", offset, size);
|
|
|
|
if (readFlags != 0) {
|
|
// Unsupported readFlags
|
|
return AVIF_RESULT_IO_ERROR;
|
|
}
|
|
|
|
avifIOTestReader * reader = (avifIOTestReader *)io;
|
|
|
|
// Sanitize/clamp incoming request
|
|
if (offset > reader->rodata.size) {
|
|
// The offset is past the end of the buffer.
|
|
return AVIF_RESULT_IO_ERROR;
|
|
}
|
|
if (offset == reader->rodata.size) {
|
|
// The parser is *exactly* at EOF: return a 0-size pointer to any valid buffer
|
|
offset = 0;
|
|
size = 0;
|
|
}
|
|
uint64_t availableSize = reader->rodata.size - offset;
|
|
if (size > availableSize) {
|
|
size = (size_t)availableSize;
|
|
}
|
|
|
|
if (offset > reader->availableBytes) {
|
|
return AVIF_RESULT_WAITING_ON_IO;
|
|
}
|
|
if (size > (reader->availableBytes - offset)) {
|
|
return AVIF_RESULT_WAITING_ON_IO;
|
|
}
|
|
|
|
out->data = reader->rodata.data + offset;
|
|
out->size = size;
|
|
return AVIF_RESULT_OK;
|
|
}
|
|
|
|
static void avifIOTestReaderDestroy(struct avifIO * io)
|
|
{
|
|
avifFree(io);
|
|
}
|
|
|
|
static avifIOTestReader * avifIOCreateTestReader(const uint8_t * data, size_t size)
|
|
{
|
|
avifIOTestReader * reader = avifAlloc(sizeof(avifIOTestReader));
|
|
memset(reader, 0, sizeof(avifIOTestReader));
|
|
reader->io.destroy = avifIOTestReaderDestroy;
|
|
reader->io.read = avifIOTestReaderRead;
|
|
reader->io.sizeHint = size;
|
|
reader->io.persistent = AVIF_TRUE;
|
|
reader->rodata.data = data;
|
|
reader->rodata.size = size;
|
|
return reader;
|
|
}
|
|
|
|
#define FILENAME_MAX_LENGTH 2047
|
|
|
|
static int runIOTests(const char * dataDir)
|
|
{
|
|
printf("AVIF Test Suite: Running IO Tests...\n");
|
|
|
|
static const char * ioSuffix = "/io/";
|
|
|
|
char ioDir[FILENAME_MAX_LENGTH + 1];
|
|
size_t dataDirLen = strlen(dataDir);
|
|
size_t ioSuffixLen = strlen(ioSuffix);
|
|
|
|
if ((dataDirLen + ioSuffixLen) > FILENAME_MAX_LENGTH) {
|
|
printf("Path too long: %s\n", dataDir);
|
|
return 1;
|
|
}
|
|
strcpy(ioDir, dataDir);
|
|
strcat(ioDir, ioSuffix);
|
|
size_t ioDirLen = strlen(ioDir);
|
|
|
|
int retCode = 0;
|
|
|
|
NextFilenameData nfd;
|
|
memset(&nfd, 0, sizeof(nfd));
|
|
avifRWData fileBuffer = AVIF_DATA_EMPTY;
|
|
const char * filename = nextFilename(ioDir, "avif", &nfd);
|
|
for (; filename != NULL; filename = nextFilename(ioDir, "avif", &nfd)) {
|
|
char fullFilename[FILENAME_MAX_LENGTH + 1];
|
|
size_t filenameLen = strlen(filename);
|
|
if ((ioDirLen + filenameLen) > FILENAME_MAX_LENGTH) {
|
|
printf("Path too long: %s\n", filename);
|
|
retCode = 1;
|
|
break;
|
|
}
|
|
strcpy(fullFilename, ioDir);
|
|
strcat(fullFilename, filename);
|
|
|
|
FILE * f = fopen(fullFilename, "rb");
|
|
if (!f) {
|
|
printf("Can't open for read: %s\n", filename);
|
|
retCode = 1;
|
|
break;
|
|
}
|
|
fseek(f, 0, SEEK_END);
|
|
size_t fileSize = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (avifRWDataRealloc(&fileBuffer, fileSize) != AVIF_RESULT_OK) {
|
|
printf("Out of memory when allocating buffer to read file: %s\n", filename);
|
|
fclose(f);
|
|
retCode = 1;
|
|
break;
|
|
}
|
|
if (fread(fileBuffer.data, 1, fileSize, f) != fileSize) {
|
|
printf("Can't read entire file: %s\n", filename);
|
|
fclose(f);
|
|
retCode = 1;
|
|
break;
|
|
}
|
|
fclose(f);
|
|
|
|
avifDecoder * decoder = avifDecoderCreate();
|
|
avifIOTestReader * io = avifIOCreateTestReader(fileBuffer.data, fileBuffer.size);
|
|
avifDecoderSetIO(decoder, (avifIO *)io);
|
|
|
|
for (int pass = 0; pass < 4; ++pass) {
|
|
io->io.persistent = ((pass % 2) == 0);
|
|
decoder->ignoreExif = decoder->ignoreXMP = (pass < 2);
|
|
|
|
// Slowly pretend to have streamed-in / downloaded more and more bytes
|
|
avifResult parseResult = AVIF_RESULT_UNKNOWN_ERROR;
|
|
for (io->availableBytes = 0; io->availableBytes <= io->io.sizeHint; ++io->availableBytes) {
|
|
parseResult = avifDecoderParse(decoder);
|
|
if (parseResult == AVIF_RESULT_WAITING_ON_IO) {
|
|
continue;
|
|
}
|
|
if (parseResult != AVIF_RESULT_OK) {
|
|
retCode = 1;
|
|
}
|
|
|
|
printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] parse returned: %s\n",
|
|
filename,
|
|
io->availableBytes,
|
|
io->io.sizeHint,
|
|
io->io.persistent ? "Persistent" : "NonPersistent",
|
|
decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
|
|
avifResultToString(parseResult));
|
|
break;
|
|
}
|
|
|
|
if (parseResult == AVIF_RESULT_OK) {
|
|
for (; io->availableBytes <= io->io.sizeHint; ++io->availableBytes) {
|
|
avifExtent extent;
|
|
avifResult extentResult = avifDecoderNthImageMaxExtent(decoder, 0, &extent);
|
|
if (extentResult != AVIF_RESULT_OK) {
|
|
retCode = 1;
|
|
|
|
printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] maxExtent returned: %s\n",
|
|
filename,
|
|
io->availableBytes,
|
|
io->io.sizeHint,
|
|
io->io.persistent ? "Persistent" : "NonPersistent",
|
|
decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
|
|
avifResultToString(extentResult));
|
|
} else {
|
|
avifResult nextImageResult = avifDecoderNextImage(decoder);
|
|
if (nextImageResult == AVIF_RESULT_WAITING_ON_IO) {
|
|
continue;
|
|
}
|
|
if (nextImageResult != AVIF_RESULT_OK) {
|
|
retCode = 1;
|
|
}
|
|
|
|
printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] nextImage [MaxExtent off %" PRIu64 ", size %zu] returned: %s\n",
|
|
filename,
|
|
io->availableBytes,
|
|
io->io.sizeHint,
|
|
io->io.persistent ? "Persistent" : "NonPersistent",
|
|
decoder->ignoreExif ? "IgnoreMetadata" : "Metadata",
|
|
extent.offset,
|
|
extent.size,
|
|
avifResultToString(nextImageResult));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
avifDecoderDestroy(decoder);
|
|
}
|
|
|
|
avifRWDataFree(&fileBuffer);
|
|
return retCode;
|
|
}
|
|
|
|
static void syntax(void)
|
|
{
|
|
fprintf(stderr, "Syntax: aviftest dataDir\n");
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
const char * dataDir = NULL;
|
|
|
|
#ifdef WIN32_MEMORY_LEAK_DETECTION
|
|
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
|
// _CrtSetBreakAlloc(2906);
|
|
#endif
|
|
|
|
// Parse cmdline
|
|
for (int i = 1; i < argc; ++i) {
|
|
char * arg = argv[i];
|
|
if (!strcmp(arg, "--io-only")) {
|
|
fprintf(stderr, "WARNING: --io-only is deprecated; ignoring.\n");
|
|
} else if (dataDir == NULL) {
|
|
dataDir = arg;
|
|
} else {
|
|
fprintf(stderr, "Too many positional arguments: %s\n", arg);
|
|
syntax();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Verify all required args were set
|
|
if (dataDir == NULL) {
|
|
fprintf(stderr, "dataDir is required, bailing out.\n");
|
|
syntax();
|
|
return 1;
|
|
}
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
char codecVersions[256];
|
|
avifCodecVersions(codecVersions);
|
|
printf("Codec Versions: %s\n", codecVersions);
|
|
printf("Test Data Dir : %s\n", dataDir);
|
|
|
|
int retCode = runIOTests(dataDir);
|
|
if (retCode == 0) {
|
|
printf("AVIF Test Suite: Complete.\n");
|
|
} else {
|
|
printf("AVIF Test Suite: Failed.\n");
|
|
}
|
|
return retCode;
|
|
}
|