haikuwebkit/PerformanceTests/DecoderTest/DecoderTest/main.mm

253 lines
9.7 KiB
Plaintext

/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import <CoreMedia/CMBufferQueue.h>
#import <CoreMedia/CMFormatDescription.h>
#import <CoreMedia/CMSampleBuffer.h>
#import <CoreServices/CoreServices.h>
#import <Foundation/Foundation.h>
#import <WebCore/ContentType.h>
#import <WebCore/MediaSample.h>
#import <WebCore/RuntimeEnabledFeatures.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/SourceBufferParserWebM.h>
#import <WebCore/UTIUtilities.h>
#import <WebCore/VP9UtilitiesCocoa.h>
#import <WebCore/WebCoreDecompressionSession.h>
#import <getopt.h>
#import <pal/avfoundation/MediaTimeAVFoundation.h>
#import <wtf/CPUTime.h>
#import <wtf/MonotonicTime.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/URL.h>
#import <wtf/WTFSemaphore.h>
#import <wtf/cf/TypeCastsCF.h>
using namespace WebCore;
static Function<void()>& updateOutputFunction()
{
static NeverDestroyed<Function<void()>> function;
return function;
}
int main(int argc, char* argv[])
{
int enableHardwareDecoder = true;
int rateLimit = false;
static struct option longopts[] = {
{ "hardware", no_argument, &enableHardwareDecoder, 1 },
{ "no-hardware", no_argument, &enableHardwareDecoder, 0 },
{ "rate-limit", no_argument, &rateLimit, 1 },
{ NULL, 0, NULL, 0 }
};
auto printUsage = [&] {
fprintf(stderr, "Usage: %s [options] <file>\n", argv[0]);
fprintf(stderr, "\tOptions:\n"
"\t\t--[no-]hardware # Enable or disable the hardware decoder\n"
"\t\t--rate-limit # Rate limit the decoder to decode samples at the natural frame rate\n"
);
};
char c;
while ((c = getopt_long(argc, argv, ":hn", longopts, NULL)) != -1) {
switch (c) {
case '0': break;
case '?':
fprintf(stderr, "Invalid argument -%c.\n", optopt);
printUsage();
return -1;
}
};
if (optind == argc || optind < argc - 1) {
printUsage();
return -1;
}
String filename = argv[optind];
@autoreleasepool {
WTF::initializeMainThread();
registerWebKitVP9Decoder();
registerSupplementalVP9Decoder();
RuntimeEnabledFeatures::sharedFeatures().setWebMParserEnabled(true);
auto mdItem = adoptCF(MDItemCreate(kCFAllocatorDefault, filename.createCFString().get()));
auto mdContentType = adoptCF(checked_cf_cast<CFStringRef>(MDItemCopyAttribute(mdItem.get(), kMDItemContentType)));
if (!mdContentType) {
fprintf(stderr, "Could not discover file type of file \"%s\"\n", filename.utf8().data());
return -1;
}
auto mimeType = MIMETypeFromUTI(mdContentType.get());
if (mimeType.isEmpty() && filename.endsWith(".webm"))
mimeType = "video/webm";
if (mimeType.isEmpty()) {
fprintf(stderr, "Could not discover file type of file \"%s\"\n", filename.utf8().data());
return -1;
}
auto buffer = SharedBuffer::createWithContentsOfFile(filename.utf8().data());
if (!buffer) {
fprintf(stderr, "Could not read file at \"%s\"\n", filename.utf8().data());
return -1;
}
fprintf(stdout, "Parsing \"%s\"...\n", filename.utf8().data());
auto parser = SourceBufferParser::create(ContentType(mimeType));
if (!buffer) {
fprintf(stderr, "Could not create parser for file of type \"%s\"\n", mimeType.utf8().data());
return -1;
}
parser->setDidEncounterErrorDuringParsingCallback([&] (uint64_t errorCode) {
fprintf(stderr, "Parser encountered error %llu, exiting", errorCode);
exit(-1);
});
CMBufferQueueRef bufferQueue = nullptr;
if (noErr != CMBufferQueueCreate(kCFAllocatorDefault, 0, CMBufferQueueGetCallbacksForUnsortedSampleBuffers(), &bufferQueue)) {
fprintf(stderr, "Could not create buffer queue, exting");
return -1;
}
bool firstSample = true;
Optional<CPUTime> startCPUTime;
MonotonicTime startTime;
uint64_t decodedSamples { 0 };
updateOutputFunction() = [&] {
auto endTime = MonotonicTime::now();
auto endCPUTime = CPUTime::get();
auto duration = endTime - startTime;
float fps = decodedSamples / duration.value();
fprintf(stdout, "Decoded %llu samples in %g seconds (%g fps)\n", decodedSamples, duration.value(), fps);
if (startCPUTime && endCPUTime) {
auto percentage = endCPUTime->percentageCPUUsageSince(*startCPUTime);
fprintf(stdout, "CPU Usage: %g %%, (%g %% per frame)\n", percentage, percentage / decodedSamples);
}
};
struct sigaction action { };
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = [] (int signal, siginfo_t*, void*) {
if (updateOutputFunction())
updateOutputFunction()();
if (signal == SIGINT)
exit(-1);
};
sigaction(SIGINFO, &action, nullptr);
sigaction(SIGINT, &action, nullptr);
WTF::Semaphore sampleSemaphore { 0 };
parser->setDidProvideMediaDataCallback([&] (Ref<MediaSample>&& sample, uint64_t, const String&) {
auto platformSample = sample->platformSample();
if (platformSample.type != PlatformSample::CMSampleBufferType)
return;
auto cmSampleBuffer = platformSample.sample.cmSampleBuffer;
auto formatDescription = CMSampleBufferGetFormatDescription(cmSampleBuffer);
if (CMFormatDescriptionGetMediaType(formatDescription) != kCMMediaType_Video)
return;
if (firstSample) {
auto size = sample->presentationSize();
startTime = MonotonicTime::now();
startCPUTime = CPUTime::get();
fprintf(stdout, "Size: %g x %g\n", size.width(), size.height());
firstSample = false;
}
CMBufferQueueEnqueue(bufferQueue, cmSampleBuffer);
sampleSemaphore.signal();
});
Semaphore finishedParsingSemaphore { 0 };
auto parserQueue = dispatch_queue_create("data parser queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(parserQueue, [&, buffer = WTFMove(buffer)] {
Vector<unsigned char> bytes;
bytes.resize(buffer->size());
memcpy(bytes.data(), buffer->data(), buffer->size());
parser->appendData(WTFMove(bytes));
dispatch_async(dispatch_get_main_queue(), [&] {
finishedParsingSemaphore.signal();
});
});
auto decompressionSession = WebCoreDecompressionSession::createOpenGL();
Semaphore finishedDecodingSemaphore { 0 };
auto decoderQueue = dispatch_queue_create("decoder queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(decoderQueue, [&] {
decompressionSession->setHardwareDecoderEnabled(enableHardwareDecoder);
do {
sampleSemaphore.wait();
if (CMBufferQueueIsEmpty(bufferQueue)) {
finishedDecodingSemaphore.signal();
return;
}
auto sample = adoptCF((CMSampleBufferRef)(const_cast<void*>(CMBufferQueueDequeueAndRetain(bufferQueue))));
auto decodeStartTime = MonotonicTime::now();
auto duration = PAL::toMediaTime(CMSampleBufferGetOutputDuration(sample.get()));
decompressionSession->decodeSampleSync(sample.get());
auto decodeEndTime = MonotonicTime::now();
auto remainingTime = Seconds(duration.toDouble()) - (decodeEndTime - decodeStartTime);
if (rateLimit && remainingTime > Seconds(0))
sleep(remainingTime);
++decodedSamples;
} while (true);
});
bool shouldKeepRunning = true;
auto monitorQueue = dispatch_queue_create("monitor queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(monitorQueue, [&] {
finishedParsingSemaphore.wait();
sampleSemaphore.signal();
finishedDecodingSemaphore.wait();
updateOutputFunction()();
shouldKeepRunning = false;
});
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]) { }
}
return 0;
}