Unverified Commit 27a10811 authored by Yuting Jiang's avatar Yuting Jiang Committed by GitHub
Browse files

Benchmarks: micro benchmark - source code for evaluating NVDEC decoding performance (#560)



**Description**
source code for evaluating NVDEC decoding performance.

---------
Co-authored-by: default avataryukirora <yuting.jiang@microsoft.com>
parent 6c0205ce
...@@ -11,7 +11,7 @@ pool: ...@@ -11,7 +11,7 @@ pool:
container: container:
image: nvcr.io/nvidia/pytorch:20.12-py3 image: nvcr.io/nvidia/pytorch:20.12-py3
options: '-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker' options: '-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v /usr/bin/sudo:/usr/bin/sudo -v /usr/lib/sudo/:/usr/lib/sudo/'
steps: steps:
- script: | - script: |
...@@ -21,6 +21,8 @@ steps: ...@@ -21,6 +21,8 @@ steps:
python3 -m pip install --upgrade pip setuptools==65.7 python3 -m pip install --upgrade pip setuptools==65.7
python3 -m pip install .[test,nvworker] python3 -m pip install .[test,nvworker]
make postinstall make postinstall
sudo DEBIAN_FRONTEND=noninteractive apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswresample-dev
displayName: Install dependencies displayName: Install dependencies
- script: | - script: |
python3 setup.py lint python3 setup.py lint
...@@ -31,7 +33,7 @@ steps: ...@@ -31,7 +33,7 @@ steps:
- script: | - script: |
SB_MICRO_PATH=$PWD python3 setup.py test SB_MICRO_PATH=$PWD python3 setup.py test
displayName: Run unit tests displayName: Run unit tests
timeoutInMinutes: 15 timeoutInMinutes: 30
- script: | - script: |
bash <(curl -s https://codecov.io/bash) -cF cuda-unit-test bash <(curl -s https://codecov.io/bash) -cF cuda-unit-test
displayName: Report coverage results displayName: Report coverage results
......
...@@ -49,6 +49,10 @@ jobs: ...@@ -49,6 +49,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Dependency
run: |
DEBIAN_FRONTEND=noninteractive apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswresample-dev sudo
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v2
with: with:
......
...@@ -9,9 +9,6 @@ __pycache__/ ...@@ -9,9 +9,6 @@ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# C extensions
*.so
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/
......
...@@ -26,13 +26,18 @@ RUN apt-get update && \ ...@@ -26,13 +26,18 @@ RUN apt-get update && \
build-essential \ build-essential \
curl \ curl \
dmidecode \ dmidecode \
ffmpeg \
git \ git \
iproute2 \ iproute2 \
jq \ jq \
libaio-dev \ libaio-dev \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libcap2 \ libcap2 \
libnuma-dev \ libnuma-dev \
libpci-dev \ libpci-dev \
libswresample-dev \
libtinfo5 \ libtinfo5 \
libtool \ libtool \
lshw \ lshw \
......
...@@ -25,14 +25,19 @@ RUN apt-get update && \ ...@@ -25,14 +25,19 @@ RUN apt-get update && \
build-essential \ build-essential \
curl \ curl \
dmidecode \ dmidecode \
ffmpeg \
git \ git \
iproute2 \ iproute2 \
jq \ jq \
libaio-dev \ libaio-dev \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
libboost-program-options-dev \ libboost-program-options-dev \
libcap2 \ libcap2 \
libnuma-dev \ libnuma-dev \
libpci-dev \ libpci-dev \
libswresample-dev \
libtinfo5 \ libtinfo5 \
libtool \ libtool \
lshw \ lshw \
......
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
#include <algorithm>
#include <chrono>
#include <cuda.h>
#include <cudaProfiler.h>
#include <fstream>
#include <iostream>
#include <memory>
#include <numeric>
#include <stdio.h>
#include <string.h>
#include <string>
#include <thread>
#include "../Utils/FFmpegDemuxer.h"
#include "../Utils/NvCodecUtils.h"
#include "OptimizedNvDecoder.h"
#include "ThreadPoolUtils.h"
// Define logger which need in third party utils
simplelogger::Logger *logger = simplelogger::LoggerFactory::CreateConsoleLogger();
// Define the codec map
std::map<std::string, cudaVideoCodec_enum> codecMap = {
{"mpeg1", cudaVideoCodec_MPEG1}, {"mpeg2", cudaVideoCodec_MPEG2}, {"mpeg4", cudaVideoCodec_MPEG4},
{"vc1", cudaVideoCodec_VC1}, {"h264", cudaVideoCodec_H264}, {"jpeg", cudaVideoCodec_JPEG},
{"h264_svc", cudaVideoCodec_H264_SVC}, {"h264_mvc", cudaVideoCodec_H264_MVC}, {"hevc", cudaVideoCodec_HEVC},
{"vp8", cudaVideoCodec_VP8}, {"vp9", cudaVideoCodec_VP9}, {"av1", cudaVideoCodec_AV1}};
/**
* @brief Function to decode video file using OptimizedNvDecoder interface
* @param pDec - Handle to OptimizedNvDecoder
* @param demuxer - Pointer to an FFmpegDemuxer instance
* @param pnFrame - Variable to record the number of frames decoded
* @param ex - Stores current exception in case of failure
*/
void DecProc(OptimizedNvDecoder *pDec, const char *szInFilePath, int *pnFrame, std::exception_ptr &ex) {
try {
std::unique_ptr<FFmpegDemuxer> demuxer(new FFmpegDemuxer(szInFilePath));
int nVideoBytes = 0, nFrameReturned = 0, nFrame = 0;
uint8_t *pVideo = NULL, *pFrame = NULL;
do {
// Demux video from file using FFmpegDemuxer
demuxer->Demux(&pVideo, &nVideoBytes);
// Decode the video frame from demuxed packet
nFrameReturned = pDec->Decode(pVideo, nVideoBytes);
if (!nFrame && nFrameReturned)
LOG(INFO) << pDec->GetVideoInfo();
nFrame += nFrameReturned;
} while (nVideoBytes);
*pnFrame = nFrame;
} catch (std::exception &) {
ex = std::current_exception();
}
}
/**
* @brief Function to show help message and exit
*/
void ShowHelpAndExit(const char *szBadOption = NULL) {
std::ostringstream oss;
bool bThrowError = false;
if (szBadOption) {
bThrowError = true;
oss << "Error parsing \"" << szBadOption << "\"" << std::endl;
}
oss << "Options:" << std::endl
<< "-i Input file path. No default value. One of -i and -multi_input is required." << std::endl
<< "-o Output file path of raw data. No default value. Optional." << std::endl
<< "-gpu Ordinal of GPU to use. Default 0. Optional." << std::endl
<< "-thread Number of decoding thread. Default 5. Optional." << std::endl
<< "-total Number of total video to test. Default 100. Optional." << std::endl
<< "-single (No value) Use single cuda context for every thread. Default is multi-context, one context "
"per thread."
<< std::endl
<< "-host (No value) Copy frame to host memory .Default is device memory)" << std::endl
<< "-multi_input The file path which lists the path of multiple video in each line." << std::endl
<< "-codec The codec of video to test. Default H264." << std::endl;
if (bThrowError) {
throw std::invalid_argument(oss.str());
} else {
std::cout << oss.str();
exit(0);
}
}
/**
* @brief Function to parse commandline arguments
*/
void ParseCommandLine(int argc, char *argv[], char *szInputFileName, int &iGpu, int &nThread, int &nTotalVideo,
bool &bSingle, bool &bHost, std::string &inputFilesListPath, std::string &outputFile,
cudaVideoCodec &codec) {
for (int i = 1; i < argc; i++) {
if (!_stricmp(argv[i], "-h")) {
ShowHelpAndExit();
}
if (!_stricmp(argv[i], "-i")) {
if (++i == argc) {
ShowHelpAndExit("-i");
}
sprintf(szInputFileName, "%s", argv[i]);
continue;
}
if (!_stricmp(argv[i], "-o")) {
if (++i == argc) {
ShowHelpAndExit("-o");
}
outputFile = std::string(argv[i]);
continue;
}
if (!_stricmp(argv[i], "-gpu")) {
if (++i == argc) {
ShowHelpAndExit("-gpu");
}
iGpu = atoi(argv[i]);
continue;
}
if (!_stricmp(argv[i], "-thread")) {
if (++i == argc) {
ShowHelpAndExit("-thread");
}
nThread = atoi(argv[i]);
continue;
}
if (!_stricmp(argv[i], "-total")) {
if (++i == argc) {
ShowHelpAndExit("-total");
}
nTotalVideo = atoi(argv[i]);
continue;
}
if (!_stricmp(argv[i], "-multi_input")) {
if (++i == argc) {
ShowHelpAndExit("-multi_input");
}
inputFilesListPath = std::string(argv[i]);
continue;
}
if (!_stricmp(argv[i], "-single")) {
bSingle = true;
continue;
}
if (!_stricmp(argv[i], "-host")) {
bHost = true;
continue;
}
if (!_stricmp(argv[i], "-codec")) {
if (++i == argc) {
ShowHelpAndExit("-codec");
}
std::string codecName = std::string(argv[i]);
std::transform(codecName.begin(), codecName.end(), codecName.begin(),
[](unsigned char c) { return std::tolower(c); });
if (codecMap.find(codecName) != codecMap.end()) {
codec = codecMap[codecName];
} else {
std::cout << "Codec name not found in the map." << std::endl;
exit(1);
}
continue;
}
ShowHelpAndExit(argv[i]);
}
}
/**
* @brief Function to create cuda context and initialize decoder
*/
OptimizedNvDecoder *InitOptimizedNvDecoder(int i, const CUdevice &cuDevice, CUcontext &cuContext, bool bSingle,
bool bHost, cudaVideoCodec codec, CUVIDDECODECAPS decodecaps) {
if (!bSingle) {
ck(cuCtxCreate(&cuContext, 0, cuDevice));
}
OptimizedNvDecoder *sessionObject = new OptimizedNvDecoder(cuContext, !bHost, codec, decodecaps);
sessionObject->setDecoderSessionID(i);
return sessionObject;
}
/**
* @brief Function to decode a video in a thread and measure the latency
*/
double DecodeVideo(size_t i, const std::vector<OptimizedNvDecoder *> &vDec, const char *szInFilePath, int *pnFrame,
std::exception_ptr &ex) {
try {
OptimizedNvDecoder *pDec = vDec[i];
auto start = std::chrono::high_resolution_clock::now();
DecProc(pDec, szInFilePath, pnFrame, ex);
auto end = std::chrono::high_resolution_clock::now();
auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "Decode finished --"
<< " duration:" << elapsedTime << " frames:" << *pnFrame << std::endl;
return elapsedTime / 1000.0f;
} catch (const std::exception &e) {
std::cerr << "Exception in deocding: " << e.what() << std::endl;
return 0;
}
}
/**
* @brief Function to read the video paths from a file
*/
std::vector<std::string> ReadMultipleVideoFiles(const std::string &filepath) {
std::ifstream file(filepath);
if (!file) {
std::cerr << "Error opening the file." << std::endl;
exit(1);
}
std::string line;
std::vector<std::string> tokens;
while (std::getline(file, line)) {
tokens.push_back(line);
}
file.close();
return tokens;
}
/**
* @brief Function to get the decoder capability
*/
void GetDefaultDecoderCaps(CUVIDDECODECAPS &decodecaps, cudaVideoCodec codec) {
memset(&decodecaps, 0, sizeof(decodecaps));
decodecaps.eCodecType = codec;
decodecaps.eChromaFormat = cudaVideoChromaFormat_420;
decodecaps.nBitDepthMinus8 = 0;
NVDEC_API_CALL(cuvidGetDecoderCaps(&decodecaps));
}
/**
* @brief Function to initialize the cuda device, cuda context, query the decoder capability and create decoder for
* each thread
*/
void InitializeContext(std::vector<OptimizedNvDecoder *> &vDec, int iGpu, int nThread, bool bSingle, bool bHost,
cudaVideoCodec codec) {
ck(cuInit(0));
int nGpu = 0;
ck(cuDeviceGetCount(&nGpu));
if (iGpu < 0 || iGpu >= nGpu) {
std::cout << "GPU ordinal out of range. Should be within [" << 0 << ", " << nGpu - 1 << "]" << std::endl;
exit(1);
}
CUdevice cuDevice = 0;
ck(cuDeviceGet(&cuDevice, iGpu));
char szDeviceName[80];
ck(cuDeviceGetName(szDeviceName, sizeof(szDeviceName), cuDevice));
std::cout << "GPU in use: " << szDeviceName << std::endl;
CUcontext cuContext = NULL;
ck(cuCtxCreate(&cuContext, 0, cuDevice));
CUVIDDECODECAPS decodecaps;
GetDefaultDecoderCaps(decodecaps, codec);
ThreadPool threadPool(nThread);
std::vector<std::future<OptimizedNvDecoder *>> futures;
for (int i = 0; i < nThread; i++) {
futures.push_back(
threadPool.enqueue(InitOptimizedNvDecoder, cuDevice, cuContext, bSingle, bHost, codec, decodecaps));
}
for (auto &future : futures) {
vDec.push_back(future.get()); // Retrieve the results from each task
}
}
/**
* @brief Function to write the latency and FPS data of each video to a file
*/
void WriteRawData(std::vector<OptimizedNvDecoder *> &vDec, int nThread, const std::vector<double> &data,
std::vector<int> &frames, std::string filename) {
// Open the output file stream
std::ofstream outputFile(filename);
outputFile << "Frame Latency" << std::endl;
for (int i = 0; i < nThread; i++) {
for (const auto &tuple : vDec[i]->GetFrameLatency()) {
int frame = std::get<0>(tuple);
double latency = std::get<1>(tuple);
outputFile << "Frame: " << frame << ", Latency: " << latency << std::endl;
}
}
outputFile << "Video Latency" << std::endl;
for (int i = 0; i < data.size(); i++) {
outputFile << data[i] << std::endl;
}
outputFile << "Video FPS" << std::endl;
for (int i = 0; i < data.size(); i++) {
outputFile << frames[i] / data[i] << std::endl;
}
// Close the file stream
outputFile.close();
}
/**
* @brief Function to calculate the statistical metrics
*/
std::tuple<double, double, double, double, double, double, double, double>
CalMetrics(const std::vector<double> &originData) {
std::vector<double> data = originData;
double sum = std::accumulate(data.begin(), data.end(), 0.0);
double mean = sum / data.size();
double min = *std::min_element(data.begin(), data.end());
double max = *std::max_element(data.begin(), data.end());
std::sort(data.begin(), data.end());
double p50 = data[data.size() / 2];
double p90 = data[static_cast<size_t>(data.size() * 0.9)];
double p95 = data[static_cast<size_t>(data.size() * 0.95)];
double p99 = data[static_cast<size_t>(data.size() * 0.99)];
return std::make_tuple(sum, mean, min, max, p50, p90, p95, p99);
}
/**
* @brief Function to generate the total file list for the given total number of videos.
* If the number of videos is less than the total number of videos, the list will be repeated.
* If the number of videos is greater than the total number of videos, the list will be truncated.
*/
std::vector<std::string> GenerateTotalFileList(const std::string &inputFilesListPath, int nTotalVideo,
const char *szInFilePath) {
std::vector<std::string> files;
if (inputFilesListPath.size() != 0) {
auto videofiles = ReadMultipleVideoFiles(inputFilesListPath);
int smallerSize = videofiles.size();
if (nTotalVideo > smallerSize) {
int numIterations = nTotalVideo / smallerSize;
for (int i = 0; i < numIterations; i++) {
files.insert(files.end(), videofiles.begin(), videofiles.end());
}
int remainingElements = nTotalVideo - (numIterations * smallerSize);
files.insert(files.end(), videofiles.begin(), videofiles.begin() + remainingElements);
} else {
files = std::vector<std::string>(videofiles.begin(), videofiles.begin() + nTotalVideo);
}
std::cout << "Multifile mode - " << nTotalVideo << "videos will be decoded" << std::endl;
} else {
for (int i = 0; i < nTotalVideo; i++) {
files.push_back(std::string(szInFilePath));
}
}
return files;
}
/**
* @brief Function to run the decoding tasks in parallel with thread pool to decode all the videos and record the total
* latency and the total number of frames
*/
float run(std::vector<OptimizedNvDecoder *> &vDec, int nThread, std::vector<std::string> &files,
std::vector<int> &vnFrame, std::vector<std::exception_ptr> &vExceptionPtrs, int *nTotalFrames,
std::vector<double> &vnLatency, std::vector<double> &frLatency, std::vector<double> &vnFPS) {
std::vector<std::future<double>> decodeLatencyFutures;
ThreadPool threadPool(nThread);
// Enqueue the video decoding task into thread pool
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < files.size(); i++) {
auto filePath = files[i].c_str();
CheckInputFile(filePath);
decodeLatencyFutures.push_back(
threadPool.enqueue(DecodeVideo, vDec, filePath, &vnFrame[i], std::ref(vExceptionPtrs[i])));
}
// Wait until decoding tasks finished
for (int i = 0; i < files.size(); i++) {
auto decodeLatency = decodeLatencyFutures[i].get();
vnLatency.push_back(decodeLatency);
*nTotalFrames += vnFrame[i];
}
auto elapsedTime =
(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start)
.count()) /
1000.0f;
for (int i = 0; i < nThread; i++) {
for (const auto &tuple : vDec[i]->GetFrameLatency()) {
int frame = std::get<0>(tuple);
double latency = std::get<1>(tuple);
if (frame > 0) {
frLatency.push_back(latency / frame);
}
}
}
for (int i = 0; i < vnLatency.size(); i++) {
if (vnLatency[i] != 0) {
vnFPS.push_back(vnFrame[i] / vnLatency[i]);
}
}
// Record the total time
return elapsedTime;
}
int main(int argc, char **argv) {
char szInFilePath[256] = "";
int iGpu = 0;
int nThread = 5;
int nTotalVideo = 100;
bool bSingle = false;
bool bHost = false;
std::string inputFilesListPath = "";
std::string outputFilePath = "";
std::vector<std::exception_ptr> vExceptionPtrs(nTotalVideo);
cudaVideoCodec codec = cudaVideoCodec_H264;
try {
// Parse the command line arguments
ParseCommandLine(argc, argv, szInFilePath, iGpu, nThread, nTotalVideo, bSingle, bHost, inputFilesListPath,
outputFilePath, codec);
auto files = GenerateTotalFileList(inputFilesListPath, nTotalVideo, szInFilePath);
// Initialize and prepare the decoder context for each thread
std::vector<OptimizedNvDecoder *> vDec;
InitializeContext(vDec, iGpu, nThread, bSingle, bHost, codec);
// Decode all video with thread pool
std::vector<int> vnFrame(nTotalVideo);
int nTotalFrames = 0;
std::vector<double> vnLatency;
std::vector<double> frLatency;
std::vector<double> videoFPS;
auto elapsedTime =
run(vDec, nThread, files, vnFrame, vExceptionPtrs, &nTotalFrames, vnLatency, frLatency, videoFPS);
// Calculate and output the raw data into file and metrics into stdout
double sum, mean, min, max, p50, p90, p95, p99;
std::tie(sum, mean, min, max, p50, p90, p95, p99) = CalMetrics(vnLatency);
std::cout << "Total Frames Decoded=" << nTotalFrames << " FPS=" << nTotalFrames / elapsedTime << std::endl;
std::cout << "Mean Latency for each video=" << mean * 1000 << " P50 Latency=" << p50 * 1000
<< " P90 Latency=" << p90 * 1000 << " P95 Latency=" << p95 * 1000 << " P99 Latency=" << p99 * 1000
<< "ms" << std::endl;
std::tie(sum, mean, min, max, p50, p90, p95, p99) = CalMetrics(videoFPS);
std::cout << "Mean FPS for each video=" << mean << " P50 FPS=" << p50 << " P90 FPS=" << p90
<< " P95 FPS=" << p95 << " P99 FPS=" << p99 << std::endl;
std::tie(sum, mean, min, max, p50, p90, p95, p99) = CalMetrics(frLatency);
std::cout << "Mean Latency for each frame=" << mean * 1000 << " P50 Latency=" << p50 * 1000
<< " P90 Latency=" << p90 * 1000 << " P95 Latency=" << p95 * 1000 << " P99 Latency=" << p99 * 1000
<< "ms" << std::endl;
if (outputFilePath.size() != 0) {
WriteRawData(vDec, nThread, vnLatency, vnFrame, outputFilePath);
}
// Deinitialization
for (int i = 0; i < nThread; i++) {
delete (vDec[i]);
}
for (int i = 0; i < nThread; i++) {
if (vExceptionPtrs[i]) {
std::rethrow_exception(vExceptionPtrs[i]);
}
}
} catch (const std::exception &ex) {
std::cout << ex.what();
exit(1);
}
return 0;
}
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cmake_minimum_required(VERSION 3.18)
project(cuda_decode_performance)
find_package(CUDA QUIET)
if(CUDA_FOUND)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(THIRD_PARTY_SAMPLE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../third_party/Video_Codec_SDK/Samples)
set(NVCODEC_PUBLIC_INTERFACE_DIR ${THIRD_PARTY_SAMPLE_DIR}/../Interface)
set(NVCODEC_UTILS_DIR ${THIRD_PARTY_SAMPLE_DIR}/Utils)
set(NV_CODEC_DIR ${THIRD_PARTY_SAMPLE_DIR}/NvCodec)
set(NV_DEC_DIR ${THIRD_PARTY_SAMPLE_DIR}/NvCodec/NvDecoder)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_package(PkgConfig REQUIRED)
pkg_check_modules(PC_AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
pkg_check_modules(PC_AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
pkg_check_modules(PC_AVUTIL REQUIRED IMPORTED_TARGET libavutil)
pkg_check_modules(PC_SWRESAMPLE REQUIRED IMPORTED_TARGET libswresample)
set(NV_FFMPEG_HDRS ${PC_AVCODEC_INCLUDE_DIRS})
find_library(AVCODEC_LIBRARY NAMES avcodec
HINTS
${PC_AVCODEC_LIBDIR}
${PC_AVCODEC_LIBRARY_DIRS}
)
find_library(AVFORMAT_LIBRARY NAMES avformat
HINTS
${PC_AVFORMAT_LIBDIR}
${PC_AVFORMAT_LIBRARY_DIRS}
)
find_library(AVUTIL_LIBRARY NAMES avutil
HINTS
${PC_AVUTIL_LIBDIR}
${PC_AVUTIL_LIBRARY_DIRS}
)
find_library(SWRESAMPLE_LIBRARY NAMES swresample
HINTS
${PC_SWRESAMPLE_LIBDIR}
${PC_SWRESAMPLE_LIBRARY_DIRS}
)
set(AVCODEC_LIB ${AVCODEC_LIBRARY})
set(AVFORMAT_LIB ${AVFORMAT_LIBRARY})
set(AVUTIL_LIB ${AVUTIL_LIBRARY})
set(SWRESAMPLE_LIB ${SWRESAMPLE_LIBRARY})
endif()
set(APP_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/AppDecPerf.cpp
)
set(NV_DEC_SOURCES
${NV_DEC_DIR}/NvDecoder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/OptimizedNvDecoder.cpp
)
set(NV_DEC_HDRS
${NV_DEC_DIR}/NvDecoder.h
${NVCODEC_PUBLIC_INTERFACE_DIR}/cuviddec.h
${NVCODEC_PUBLIC_INTERFACE_DIR}/nvcuvid.h
${NVCODEC_UTILS_DIR}/NvCodecUtils.h
${NVCODEC_UTILS_DIR}/FFmpegDemuxer.h
${CMAKE_CURRENT_SOURCE_DIR}/ThreadPoolUtils.h
${CMAKE_CURRENT_SOURCE_DIR}/OptimizedNvDecoder.h
)
source_group( "headers" FILES ${NV_DEC_HDRS} )
source_group( "sources" FILES ${APP_SOURCES} ${NV_DEC_SOURCES})
set(CMAKE_LIBRARY_PATH "${CUDA_TOOLKIT_ROOT_DIR}/lib64/stubs;${CUDA_TOOLKIT_ROOT_DIR}/lib/stubs;${CUDA_TOOLKIT_ROOT_DIR}/lib64;${CUDA_TOOLKIT_ROOT_DIR}/lib;${CMAKE_LIBRARY_PATH}")
find_package(CUDA)
set(CUDA_HOST_COMPILER ${CMAKE_CXX_COMPILER})
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS};-gencode arch=compute_50,code=\"sm_50,compute_50\")
if ( CMAKE_COMPILER_IS_GNUCC )
if(NOT "${CUDA_NVCC_FLAGS}" MATCHES "-std=c\\+\\+11" )
list(APPEND CUDA_NVCC_FLAGS -std=c++11)
endif()
endif()
# Check if the file exists
if (NOT EXISTS "/usr/local/lib/libnvcuvid.so" )
execute_process(
COMMAND sudo ln -s /usr/lib/x86_64-linux-gnu/libnvcuvid.so.1 /usr/local/lib/libnvcuvid.so
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR "Failed to create symbolic link for nvcuvid lib: ${result}")
endif()
endif ()
find_library(CUVID_LIB nvcuvid
HINTS
"/usr/local/lib/"
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../third_party/Video_Codec_SDK/Lib/linux/stubs/x86_64/"
)
cuda_add_executable(${PROJECT_NAME} ${APP_SOURCES} ${NV_DEC_SOURCES} ${NV_DEC_HDRS})
set_target_properties(${PROJECT_NAME} PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
target_include_directories(${PROJECT_NAME} PUBLIC ${CUDA_INCLUDE_DIRS}
${NVCODEC_PUBLIC_INTERFACE_DIR}
${NVCODEC_UTILS_DIR}
${NV_CODEC_DIR}
${NV_APPDEC_COMMON_DIR}
${NV_FFMPEG_HDRS}
${THIRD_PARTY_SAMPLE_DIR}
)
target_link_libraries(${PROJECT_NAME} ${CUDA_CUDA_LIBRARY} ${CMAKE_DL_LIBS} ${CUVID_LIB} ${AVCODEC_LIB}
${AVFORMAT_LIB} ${AVUTIL_LIB} ${SWRESAMPLE_LIB})
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin LIBRARY DESTINATION lib)
endif()
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
#include <cmath>
#include "OptimizedNvDecoder.h"
int OptimizedNvDecoder::Decode(const uint8_t *pData, int nSize, int nFlags, int64_t nTimestamp) {
m_nDecodedFrame = 0;
m_nDecodedFrameReturned = 0;
CUVIDSOURCEDATAPACKET packet = {0};
packet.payload = pData;
packet.payload_size = nSize;
packet.flags = nFlags | CUVID_PKT_TIMESTAMP;
packet.timestamp = nTimestamp;
if (!pData || nSize == 0) {
packet.flags |= CUVID_PKT_ENDOFSTREAM;
}
auto start = std::chrono::high_resolution_clock::now();
NVDEC_API_CALL(cuvidParseVideoData(m_hParser, &packet));
int64_t elapsedTime =
std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start)
.count();
frameLatency.push_back(std::make_tuple(m_nDecodedFrame, elapsedTime / 1000.0f / 1000.0f));
return m_nDecodedFrame;
}
OptimizedNvDecoder::OptimizedNvDecoder(CUcontext &cuContext, bool bUseDeviceFrame, cudaVideoCodec eCodec,
CUVIDDECODECAPS decodecaps, bool bLowLatency, bool bDeviceFramePitched,
const Rect *pCropRect, const Dim *pResizeDim, bool extract_user_SEI_Message,
int maxWidth, int maxHeight, unsigned int clkRate, bool force_zero_latency) {
m_cuContext = cuContext;
m_bUseDeviceFrame = bUseDeviceFrame;
m_eCodec = eCodec;
m_bDeviceFramePitched = bDeviceFramePitched;
m_bExtractSEIMessage = extract_user_SEI_Message;
m_nMaxWidth = maxWidth;
m_nMaxHeight = maxHeight;
m_bForce_zero_latency = force_zero_latency;
if (pCropRect)
m_cropRect = *pCropRect;
if (pResizeDim)
m_resizeDim = *pResizeDim;
CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext));
NVDEC_API_CALL(cuvidCtxLockCreate(&m_ctxLock, cuContext));
ck(cuStreamCreate(&m_cuvidStream, CU_STREAM_DEFAULT));
decoderSessionID = 0;
if (m_bExtractSEIMessage) {
m_fpSEI = fopen("sei_message.txt", "wb");
m_pCurrSEIMessage = new CUVIDSEIMESSAGEINFO;
memset(&m_SEIMessagesDisplayOrder, 0, sizeof(m_SEIMessagesDisplayOrder));
}
CUVIDPARSERPARAMS videoParserParameters = {};
videoParserParameters.CodecType = eCodec;
videoParserParameters.ulMaxNumDecodeSurfaces = 1;
videoParserParameters.ulClockRate = clkRate;
videoParserParameters.ulMaxDisplayDelay = bLowLatency ? 0 : 1;
videoParserParameters.pUserData = this;
videoParserParameters.pfnSequenceCallback = HandleVideoSequenceProc;
videoParserParameters.pfnDecodePicture = HandlePictureDecodeProc;
videoParserParameters.pfnDisplayPicture = m_bForce_zero_latency ? NULL : HandlePictureDisplayProc;
videoParserParameters.pfnGetOperatingPoint = HandleOperatingPointProc;
videoParserParameters.pfnGetSEIMsg = m_bExtractSEIMessage ? HandleSEIMessagesProc : NULL;
NVDEC_API_CALL(cuvidCreateVideoParser(&m_hParser, &videoParserParameters));
// reuse the decodecaps queried before
m_decodecaps = decodecaps;
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
}
int OptimizedNvDecoder::HandleVideoSequence(CUVIDEOFORMAT *pVideoFormat) {
START_TIMER
m_videoInfo.str("");
m_videoInfo.clear();
m_videoInfo << "Video Input Information" << std::endl
<< "\tCodec : " << GetVideoCodecString(pVideoFormat->codec) << std::endl
<< "\tFrame rate : " << pVideoFormat->frame_rate.numerator << "/"
<< pVideoFormat->frame_rate.denominator << " = "
<< 1.0 * pVideoFormat->frame_rate.numerator / pVideoFormat->frame_rate.denominator << " fps"
<< std::endl
<< "\tSequence : " << (pVideoFormat->progressive_sequence ? "Progressive" : "Interlaced")
<< std::endl
<< "\tCoded size : [" << pVideoFormat->coded_width << ", " << pVideoFormat->coded_height << "]"
<< std::endl
<< "\tDisplay area : [" << pVideoFormat->display_area.left << ", " << pVideoFormat->display_area.top
<< ", " << pVideoFormat->display_area.right << ", " << pVideoFormat->display_area.bottom << "]"
<< std::endl
<< "\tChroma : " << GetVideoChromaFormatString(pVideoFormat->chroma_format) << std::endl
<< "\tBit depth : " << pVideoFormat->bit_depth_luma_minus8 + 8;
m_videoInfo << std::endl;
int nDecodeSurface = pVideoFormat->min_num_decode_surfaces;
// re-call the cuvidGetDecoderCaps when the video codeoc and format change
if (m_decodecaps.eCodecType != pVideoFormat->codec || m_decodecaps.eChromaFormat != pVideoFormat->chroma_format ||
m_decodecaps.nBitDepthMinus8 != pVideoFormat->bit_depth_luma_minus8) {
m_decodecaps.eCodecType = pVideoFormat->codec;
m_decodecaps.eChromaFormat = pVideoFormat->chroma_format;
m_decodecaps.nBitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8;
CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext));
NVDEC_API_CALL(cuvidGetDecoderCaps(&m_decodecaps));
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
}
if (!m_decodecaps.bIsSupported) {
NVDEC_THROW_ERROR("Codec not supported on this GPU", CUDA_ERROR_NOT_SUPPORTED);
return nDecodeSurface;
}
if ((pVideoFormat->coded_width > m_decodecaps.nMaxWidth) ||
(pVideoFormat->coded_height > m_decodecaps.nMaxHeight)) {
std::ostringstream errorString;
errorString << std::endl
<< "Resolution : " << pVideoFormat->coded_width << "x" << pVideoFormat->coded_height
<< std::endl
<< "Max Supported (wxh) : " << m_decodecaps.nMaxWidth << "x" << m_decodecaps.nMaxHeight << std::endl
<< "Resolution not supported on this GPU";
const std::string cErr = errorString.str();
NVDEC_THROW_ERROR(cErr, CUDA_ERROR_NOT_SUPPORTED);
return nDecodeSurface;
}
if ((pVideoFormat->coded_width >> 4) * (pVideoFormat->coded_height >> 4) > m_decodecaps.nMaxMBCount) {
std::ostringstream errorString;
errorString << std::endl
<< "MBCount : " << (pVideoFormat->coded_width >> 4) * (pVideoFormat->coded_height >> 4)
<< std::endl
<< "Max Supported mbcnt : " << m_decodecaps.nMaxMBCount << std::endl
<< "MBCount not supported on this GPU";
NVDEC_THROW_ERROR(errorString.str(), CUDA_ERROR_NOT_SUPPORTED);
return nDecodeSurface;
}
if (m_nWidth && m_nLumaHeight && m_nChromaHeight) {
// cuvidCreateDecoder() has been called before, and now there's possible config change
return ReconfigureDecoder(pVideoFormat);
}
// eCodec has been set in the constructor (for parser). Here it's set again for potential correction
m_eCodec = pVideoFormat->codec;
m_eChromaFormat = pVideoFormat->chroma_format;
m_nBitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8;
m_nBPP = m_nBitDepthMinus8 > 0 ? 2 : 1;
// Set the output surface format same as chroma format
if (m_eChromaFormat == cudaVideoChromaFormat_420 || cudaVideoChromaFormat_Monochrome)
m_eOutputFormat =
pVideoFormat->bit_depth_luma_minus8 ? cudaVideoSurfaceFormat_P016 : cudaVideoSurfaceFormat_NV12;
else if (m_eChromaFormat == cudaVideoChromaFormat_444)
m_eOutputFormat =
pVideoFormat->bit_depth_luma_minus8 ? cudaVideoSurfaceFormat_YUV444_16Bit : cudaVideoSurfaceFormat_YUV444;
else if (m_eChromaFormat == cudaVideoChromaFormat_422)
m_eOutputFormat = cudaVideoSurfaceFormat_NV12; // no 4:2:2 output format supported yet so make 420 default
// Check if output format supported. If not, check falback options
if (!(m_decodecaps.nOutputFormatMask & (1 << m_eOutputFormat))) {
if (m_decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_NV12))
m_eOutputFormat = cudaVideoSurfaceFormat_NV12;
else if (m_decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_P016))
m_eOutputFormat = cudaVideoSurfaceFormat_P016;
else if (m_decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_YUV444))
m_eOutputFormat = cudaVideoSurfaceFormat_YUV444;
else if (m_decodecaps.nOutputFormatMask & (1 << cudaVideoSurfaceFormat_YUV444_16Bit))
m_eOutputFormat = cudaVideoSurfaceFormat_YUV444_16Bit;
else
NVDEC_THROW_ERROR("No supported output format found", CUDA_ERROR_NOT_SUPPORTED);
}
m_videoFormat = *pVideoFormat;
CUVIDDECODECREATEINFO videoDecodeCreateInfo = {0};
videoDecodeCreateInfo.CodecType = pVideoFormat->codec;
videoDecodeCreateInfo.ChromaFormat = pVideoFormat->chroma_format;
videoDecodeCreateInfo.OutputFormat = m_eOutputFormat;
videoDecodeCreateInfo.bitDepthMinus8 = pVideoFormat->bit_depth_luma_minus8;
if (pVideoFormat->progressive_sequence)
videoDecodeCreateInfo.DeinterlaceMode = cudaVideoDeinterlaceMode_Weave;
else
videoDecodeCreateInfo.DeinterlaceMode = cudaVideoDeinterlaceMode_Adaptive;
videoDecodeCreateInfo.ulNumOutputSurfaces = 2;
// With PreferCUVID, JPEG is still decoded by CUDA while video is decoded by NVDEC hardware
videoDecodeCreateInfo.ulCreationFlags = cudaVideoCreate_PreferCUVID;
videoDecodeCreateInfo.ulNumDecodeSurfaces = nDecodeSurface;
videoDecodeCreateInfo.vidLock = m_ctxLock;
videoDecodeCreateInfo.ulWidth = pVideoFormat->coded_width;
videoDecodeCreateInfo.ulHeight = pVideoFormat->coded_height;
// AV1 has max width/height of sequence in sequence header
if (pVideoFormat->codec == cudaVideoCodec_AV1 && pVideoFormat->seqhdr_data_length > 0) {
CUVIDEOFORMATEX *vidFormatEx = (CUVIDEOFORMATEX *)pVideoFormat;
if (m_nMaxWidth < pVideoFormat->coded_width) {
m_nMaxWidth = vidFormatEx->av1.max_width;
}
if (m_nMaxHeight < pVideoFormat->coded_height) {
m_nMaxHeight = vidFormatEx->av1.max_height;
}
}
if (m_nMaxWidth < (int)pVideoFormat->coded_width)
m_nMaxWidth = pVideoFormat->coded_width;
if (m_nMaxHeight < (int)pVideoFormat->coded_height)
m_nMaxHeight = pVideoFormat->coded_height;
videoDecodeCreateInfo.ulMaxWidth = m_nMaxWidth;
videoDecodeCreateInfo.ulMaxHeight = m_nMaxHeight;
if (!(m_cropRect.r && m_cropRect.b) && !(m_resizeDim.w && m_resizeDim.h)) {
m_nWidth = pVideoFormat->display_area.right - pVideoFormat->display_area.left;
m_nLumaHeight = pVideoFormat->display_area.bottom - pVideoFormat->display_area.top;
videoDecodeCreateInfo.ulTargetWidth = pVideoFormat->coded_width;
videoDecodeCreateInfo.ulTargetHeight = pVideoFormat->coded_height;
} else {
if (m_resizeDim.w && m_resizeDim.h) {
videoDecodeCreateInfo.display_area.left = pVideoFormat->display_area.left;
videoDecodeCreateInfo.display_area.top = pVideoFormat->display_area.top;
videoDecodeCreateInfo.display_area.right = pVideoFormat->display_area.right;
videoDecodeCreateInfo.display_area.bottom = pVideoFormat->display_area.bottom;
m_nWidth = m_resizeDim.w;
m_nLumaHeight = m_resizeDim.h;
}
if (m_cropRect.r && m_cropRect.b) {
videoDecodeCreateInfo.display_area.left = m_cropRect.l;
videoDecodeCreateInfo.display_area.top = m_cropRect.t;
videoDecodeCreateInfo.display_area.right = m_cropRect.r;
videoDecodeCreateInfo.display_area.bottom = m_cropRect.b;
m_nWidth = m_cropRect.r - m_cropRect.l;
m_nLumaHeight = m_cropRect.b - m_cropRect.t;
}
videoDecodeCreateInfo.ulTargetWidth = m_nWidth;
videoDecodeCreateInfo.ulTargetHeight = m_nLumaHeight;
}
m_nChromaHeight = (int)(ceil(m_nLumaHeight * GetChromaHeightFactor(m_eOutputFormat)));
m_nNumChromaPlanes = GetChromaPlaneCount(m_eOutputFormat);
m_nSurfaceHeight = videoDecodeCreateInfo.ulTargetHeight;
m_nSurfaceWidth = videoDecodeCreateInfo.ulTargetWidth;
m_displayRect.b = videoDecodeCreateInfo.display_area.bottom;
m_displayRect.t = videoDecodeCreateInfo.display_area.top;
m_displayRect.l = videoDecodeCreateInfo.display_area.left;
m_displayRect.r = videoDecodeCreateInfo.display_area.right;
m_videoInfo << "Video Decoding Params:" << std::endl
<< "\tNum Surfaces : " << videoDecodeCreateInfo.ulNumDecodeSurfaces << std::endl
<< "\tCrop : [" << videoDecodeCreateInfo.display_area.left << ", "
<< videoDecodeCreateInfo.display_area.top << ", " << videoDecodeCreateInfo.display_area.right << ", "
<< videoDecodeCreateInfo.display_area.bottom << "]" << std::endl
<< "\tResize : " << videoDecodeCreateInfo.ulTargetWidth << "x"
<< videoDecodeCreateInfo.ulTargetHeight << std::endl
<< "\tDeinterlace : "
<< std::vector<const char *>{"Weave", "Bob", "Adaptive"}[videoDecodeCreateInfo.DeinterlaceMode];
m_videoInfo << std::endl;
CUDA_DRVAPI_CALL(cuCtxPushCurrent(m_cuContext));
NVDEC_API_CALL(cuvidCreateDecoder(&m_hDecoder, &videoDecodeCreateInfo));
CUDA_DRVAPI_CALL(cuCtxPopCurrent(NULL));
STOP_TIMER("Session Initialization Time: ");
NvDecoder::addDecoderSessionOverHead(getDecoderSessionID(), elapsedTime);
return nDecodeSurface;
}
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
#include "NvDecoder/NvDecoder.h"
// This class is derived from NvDecoder class and is used to optimize the cuvidGetDecoderCaps overhead
class OptimizedNvDecoder : public NvDecoder {
public:
OptimizedNvDecoder() {}
/**
* @brief This function is used to initialize the decoder session.
* Application must call this function to initialize the decoder, before
* starting to decode any frames.
* The only difference from the original function is to add a new member m_decodecaps.
* Other part is the same as the original function, refer to NvDecoder.cpp in NVIDIA Video Codec SDK.
*/
OptimizedNvDecoder(CUcontext &cuContext, bool bUseDeviceFrame, cudaVideoCodec eCodec, CUVIDDECODECAPS decodecaps,
bool bLowLatency = false, bool bDeviceFramePitched = false, const Rect *pCropRect = NULL,
const Dim *pResizeDim = NULL, bool extract_user_SEI_Message = false, int maxWidth = 0,
int maxHeight = 0, unsigned int clkRate = 1000, bool force_zero_latency = false);
/**
* @brief This function is to overwrite the origin Decode function to record the latency on frame level.
*/
int Decode(const uint8_t *pData, int nSize, int nFlags = 0, int64_t nTimestamp = 0);
/**
* @brief This function is used to Get the frameLatency vector
*/
std::vector<std::tuple<int, double>> &GetFrameLatency() { return frameLatency; }
protected:
/**
* @brief Callback function to be registered for getting a callback when decoding of sequence starts
*/
static int CUDAAPI HandleVideoSequenceProc(void *pUserData, CUVIDEOFORMAT *pVideoFormat) {
if (pUserData == nullptr) {
throw std::runtime_error("pUserData is nullptr");
}
return ((OptimizedNvDecoder *)pUserData)->HandleVideoSequence(pVideoFormat);
}
/**
* @brief Define the new handler when decoding of sequence starts.
* The only change is to re-query decoder caps when the video codec or format change
* Other part is the same as the original function, refer to NvDecoder.cpp in NVIDIA Video Codec SDK.
*/
int HandleVideoSequence(CUVIDEOFORMAT *pVideoFormat);
CUVIDDECODECAPS m_decodecaps;
std::vector<std::tuple<int, double>> frameLatency;
};
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
// ThreadPool is a simple thread pool implementation that supports enqueueing the task with the index of thread to use
// and custom arguments like task(thread_index, *args).
class ThreadPool {
public:
/**
* @brief Construct a new ThreadPool object with the given number of threads.
*/
ThreadPool(size_t numThreads) {
for (size_t i = 0; i < numThreads; ++i) {
threads.emplace_back(&ThreadPool::worker, this, i);
}
}
/**
* @brief Destroy the ThreadPool object and join all threads.
*/
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(mutex);
stop = true;
}
cv.notify_all();
for (auto &thread : threads) {
thread.join();
}
}
/**
* @brief TaskWrapper is a wrapper of the task with the index of thread to use and custom arguments like
* task(thread_index, *args).
*/
template <typename R, typename F, typename... Args> struct TaskWrapper {
std::shared_ptr<std::packaged_task<R(size_t)>> task;
template <typename Callable, typename... CallableArgs> TaskWrapper(Callable &&f, CallableArgs &&...args) {
task = std::make_shared<std::packaged_task<R(size_t)>>(
[f, args...](size_t threadIdx) mutable { return f(threadIdx, args...); });
}
void operator()(size_t threadIdx) { (*task)(threadIdx); }
};
/**
* @brief Enqueue enqueues the task with custom arguments and return the results of task when finished.
*/
template <typename F, typename... Args>
auto enqueue(F &&f, Args &&...args) -> std::future<typename std::result_of<F(size_t, Args...)>::type> {
using ReturnType = typename std::result_of<F(size_t, Args...)>::type;
TaskWrapper<ReturnType, F, Args...> wrapper(std::forward<F>(f), std::forward<Args>(args)...);
std::future<ReturnType> res = wrapper.task->get_future();
{
std::unique_lock<std::mutex> lock(mutex);
tasks.emplace(std::move(wrapper));
}
cv.notify_one();
return res;
}
private:
/**
* @brief The worker function that dequeues the task and executes it for each thread index.
*/
void worker(size_t threadIdx) {
while (true) {
std::function<void(size_t)> task;
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) {
return;
}
task = tasks.front();
tasks.pop();
}
task(threadIdx);
}
}
std::vector<std::thread> threads;
std::queue<std::function<void(size_t)>> tasks;
std::mutex mutex;
std::condition_variable cv;
bool stop = false;
};
This diff is collapsed.
This diff is collapsed.
/*
* This copyright notice applies to this header file only:
*
* Copyright (c) 2010-2023 NVIDIA Corporation
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the software, and to permit persons to whom the
* software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
/* Explicitly include bsf.h when building against FFmpeg 4.3 (libavcodec 58.45.100) or later for backward compatibility
*/
#if LIBAVCODEC_VERSION_INT >= 3824484
#include <libavcodec/bsf.h>
#endif
}
#include "NvCodecUtils.h"
#include "nvcuvid.h"
//---------------------------------------------------------------------------
//! \file FFmpegDemuxer.h
//! \brief Provides functionality for stream demuxing
//!
//! This header file is used by Decode/Transcode apps to demux input video clips before decoding frames from it.
//---------------------------------------------------------------------------
/**
* @brief libavformat wrapper class. Retrieves the elementary encoded stream from the container format.
*/
class FFmpegDemuxer {
private:
AVFormatContext *fmtc = NULL;
AVIOContext *avioc = NULL;
AVPacket *pkt = NULL; /*!< AVPacket stores compressed data typically exported by demuxers and then passed as input
to decoders */
AVPacket *pktFiltered = NULL;
AVBSFContext *bsfc = NULL;
int iVideoStream;
bool bMp4H264, bMp4HEVC, bMp4MPEG4;
AVCodecID eVideoCodec;
AVPixelFormat eChromaFormat;
int nWidth, nHeight, nBitDepth, nBPP, nChromaHeight;
double timeBase = 0.0;
int64_t userTimeScale = 0;
uint8_t *pDataWithHeader = NULL;
unsigned int frameCount = 0;
public:
class DataProvider {
public:
virtual ~DataProvider() {}
virtual int GetData(uint8_t *pBuf, int nBuf) = 0;
};
private:
/**
* @brief Private constructor to initialize libavformat resources.
* @param fmtc - Pointer to AVFormatContext allocated inside avformat_open_input()
*/
FFmpegDemuxer(AVFormatContext *fmtc, int64_t timeScale = 1000 /*Hz*/) : fmtc(fmtc) {
if (!fmtc) {
LOG(ERROR) << "No AVFormatContext provided.";
return;
}
// Allocate the AVPackets and initialize to default values
pkt = av_packet_alloc();
pktFiltered = av_packet_alloc();
if (!pkt || !pktFiltered) {
LOG(ERROR) << "AVPacket allocation failed";
return;
}
LOG(INFO) << "Media format: " << fmtc->iformat->long_name << " (" << fmtc->iformat->name << ")";
ck(avformat_find_stream_info(fmtc, NULL));
iVideoStream = av_find_best_stream(fmtc, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (iVideoStream < 0) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__ << " "
<< "Could not find stream in input file";
av_packet_free(&pkt);
av_packet_free(&pktFiltered);
return;
}
// fmtc->streams[iVideoStream]->need_parsing = AVSTREAM_PARSE_NONE;
eVideoCodec = fmtc->streams[iVideoStream]->codecpar->codec_id;
nWidth = fmtc->streams[iVideoStream]->codecpar->width;
nHeight = fmtc->streams[iVideoStream]->codecpar->height;
eChromaFormat = (AVPixelFormat)fmtc->streams[iVideoStream]->codecpar->format;
AVRational rTimeBase = fmtc->streams[iVideoStream]->time_base;
timeBase = av_q2d(rTimeBase);
userTimeScale = timeScale;
// Set bit depth, chroma height, bits per pixel based on eChromaFormat of input
switch (eChromaFormat) {
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_GRAY10LE: // monochrome is treated as 420 with chroma filled with 0x0
nBitDepth = 10;
nChromaHeight = (nHeight + 1) >> 1;
nBPP = 2;
break;
case AV_PIX_FMT_YUV420P12LE:
nBitDepth = 12;
nChromaHeight = (nHeight + 1) >> 1;
nBPP = 2;
break;
case AV_PIX_FMT_YUV444P10LE:
nBitDepth = 10;
nChromaHeight = nHeight << 1;
nBPP = 2;
break;
case AV_PIX_FMT_YUV444P12LE:
nBitDepth = 12;
nChromaHeight = nHeight << 1;
nBPP = 2;
break;
case AV_PIX_FMT_YUV444P:
nBitDepth = 8;
nChromaHeight = nHeight << 1;
nBPP = 1;
break;
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
case AV_PIX_FMT_YUVJ422P: // jpeg decoder output is subsampled to NV12 for 422/444 so treat it as 420
case AV_PIX_FMT_YUVJ444P: // jpeg decoder output is subsampled to NV12 for 422/444 so treat it as 420
case AV_PIX_FMT_GRAY8: // monochrome is treated as 420 with chroma filled with 0x0
nBitDepth = 8;
nChromaHeight = (nHeight + 1) >> 1;
nBPP = 1;
break;
default:
LOG(WARNING) << "ChromaFormat not recognized. Assuming 420";
eChromaFormat = AV_PIX_FMT_YUV420P;
nBitDepth = 8;
nChromaHeight = (nHeight + 1) >> 1;
nBPP = 1;
}
bMp4H264 = eVideoCodec == AV_CODEC_ID_H264 && (!strcmp(fmtc->iformat->long_name, "QuickTime / MOV") ||
!strcmp(fmtc->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(fmtc->iformat->long_name, "Matroska / WebM"));
bMp4HEVC = eVideoCodec == AV_CODEC_ID_HEVC && (!strcmp(fmtc->iformat->long_name, "QuickTime / MOV") ||
!strcmp(fmtc->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(fmtc->iformat->long_name, "Matroska / WebM"));
bMp4MPEG4 = eVideoCodec == AV_CODEC_ID_MPEG4 && (!strcmp(fmtc->iformat->long_name, "QuickTime / MOV") ||
!strcmp(fmtc->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(fmtc->iformat->long_name, "Matroska / WebM"));
// Initialize bitstream filter and its required resources
if (bMp4H264) {
const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
if (!bsf) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__ << " "
<< "av_bsf_get_by_name() failed";
av_packet_free(&pkt);
av_packet_free(&pktFiltered);
return;
}
ck(av_bsf_alloc(bsf, &bsfc));
avcodec_parameters_copy(bsfc->par_in, fmtc->streams[iVideoStream]->codecpar);
ck(av_bsf_init(bsfc));
}
if (bMp4HEVC) {
const AVBitStreamFilter *bsf = av_bsf_get_by_name("hevc_mp4toannexb");
if (!bsf) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__ << " "
<< "av_bsf_get_by_name() failed";
av_packet_free(&pkt);
av_packet_free(&pktFiltered);
return;
}
ck(av_bsf_alloc(bsf, &bsfc));
avcodec_parameters_copy(bsfc->par_in, fmtc->streams[iVideoStream]->codecpar);
ck(av_bsf_init(bsfc));
}
}
AVFormatContext *CreateFormatContext(DataProvider *pDataProvider) {
AVFormatContext *ctx = NULL;
if (!(ctx = avformat_alloc_context())) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__;
return NULL;
}
uint8_t *avioc_buffer = NULL;
int avioc_buffer_size = 8 * 1024 * 1024;
avioc_buffer = (uint8_t *)av_malloc(avioc_buffer_size);
if (!avioc_buffer) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__;
return NULL;
}
avioc = avio_alloc_context(avioc_buffer, avioc_buffer_size, 0, pDataProvider, &ReadPacket, NULL, NULL);
if (!avioc) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__;
return NULL;
}
ctx->pb = avioc;
ck(avformat_open_input(&ctx, NULL, NULL, NULL));
return ctx;
}
/**
* @brief Allocate and return AVFormatContext*.
* @param szFilePath - Filepath pointing to input stream.
* @return Pointer to AVFormatContext
*/
AVFormatContext *CreateFormatContext(const char *szFilePath) {
avformat_network_init();
AVFormatContext *ctx = NULL;
ck(avformat_open_input(&ctx, szFilePath, NULL, NULL));
return ctx;
}
public:
FFmpegDemuxer(const char *szFilePath, int64_t timescale = 1000 /*Hz*/)
: FFmpegDemuxer(CreateFormatContext(szFilePath), timescale) {}
FFmpegDemuxer(DataProvider *pDataProvider) : FFmpegDemuxer(CreateFormatContext(pDataProvider)) { avioc = fmtc->pb; }
~FFmpegDemuxer() {
if (!fmtc) {
return;
}
if (pkt) {
av_packet_free(&pkt);
}
if (pktFiltered) {
av_packet_free(&pktFiltered);
}
if (bsfc) {
av_bsf_free(&bsfc);
}
avformat_close_input(&fmtc);
if (avioc) {
av_freep(&avioc->buffer);
av_freep(&avioc);
}
if (pDataWithHeader) {
av_free(pDataWithHeader);
}
}
AVCodecID GetVideoCodec() { return eVideoCodec; }
AVPixelFormat GetChromaFormat() { return eChromaFormat; }
int GetWidth() { return nWidth; }
int GetHeight() { return nHeight; }
int GetBitDepth() { return nBitDepth; }
int GetFrameSize() { return nWidth * (nHeight + nChromaHeight) * nBPP; }
bool Demux(uint8_t **ppVideo, int *pnVideoBytes, int64_t *pts = NULL) {
if (!fmtc) {
return false;
}
*pnVideoBytes = 0;
if (pkt->data) {
av_packet_unref(pkt);
}
int e = 0;
while ((e = av_read_frame(fmtc, pkt)) >= 0 && pkt->stream_index != iVideoStream) {
av_packet_unref(pkt);
}
if (e < 0) {
return false;
}
if (bMp4H264 || bMp4HEVC) {
if (pktFiltered->data) {
av_packet_unref(pktFiltered);
}
ck(av_bsf_send_packet(bsfc, pkt));
ck(av_bsf_receive_packet(bsfc, pktFiltered));
*ppVideo = pktFiltered->data;
*pnVideoBytes = pktFiltered->size;
if (pts)
*pts = (int64_t)(pktFiltered->pts * userTimeScale * timeBase);
} else {
if (bMp4MPEG4 && (frameCount == 0)) {
int extraDataSize = fmtc->streams[iVideoStream]->codecpar->extradata_size;
if (extraDataSize > 0) {
// extradata contains start codes 00 00 01. Subtract its size
pDataWithHeader = (uint8_t *)av_malloc(extraDataSize + pkt->size - 3 * sizeof(uint8_t));
if (!pDataWithHeader) {
LOG(ERROR) << "FFmpeg error: " << __FILE__ << " " << __LINE__;
return false;
}
memcpy(pDataWithHeader, fmtc->streams[iVideoStream]->codecpar->extradata, extraDataSize);
memcpy(pDataWithHeader + extraDataSize, pkt->data + 3, pkt->size - 3 * sizeof(uint8_t));
*ppVideo = pDataWithHeader;
*pnVideoBytes = extraDataSize + pkt->size - 3 * sizeof(uint8_t);
}
} else {
*ppVideo = pkt->data;
*pnVideoBytes = pkt->size;
}
if (pts)
*pts = (int64_t)(pkt->pts * userTimeScale * timeBase);
}
frameCount++;
return true;
}
static int ReadPacket(void *opaque, uint8_t *pBuf, int nBuf) {
return ((DataProvider *)opaque)->GetData(pBuf, nBuf);
}
};
inline cudaVideoCodec FFmpeg2NvCodecId(AVCodecID id) {
switch (id) {
case AV_CODEC_ID_MPEG1VIDEO:
return cudaVideoCodec_MPEG1;
case AV_CODEC_ID_MPEG2VIDEO:
return cudaVideoCodec_MPEG2;
case AV_CODEC_ID_MPEG4:
return cudaVideoCodec_MPEG4;
case AV_CODEC_ID_WMV3:
case AV_CODEC_ID_VC1:
return cudaVideoCodec_VC1;
case AV_CODEC_ID_H264:
return cudaVideoCodec_H264;
case AV_CODEC_ID_HEVC:
return cudaVideoCodec_HEVC;
case AV_CODEC_ID_VP8:
return cudaVideoCodec_VP8;
case AV_CODEC_ID_VP9:
return cudaVideoCodec_VP9;
case AV_CODEC_ID_MJPEG:
return cudaVideoCodec_JPEG;
case AV_CODEC_ID_AV1:
return cudaVideoCodec_AV1;
default:
return cudaVideoCodec_NumCodecs;
}
}
/*
* This copyright notice applies to this header file only:
*
* Copyright (c) 2010-2023 NVIDIA Corporation
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the software, and to permit persons to whom the
* software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <mutex>
#include <thread>
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
};
#include "Logger.h"
using namespace std;
extern simplelogger::Logger *logger;
static string AvErrorToString(int av_error_code) {
const auto buf_size = 1024U;
char *err_string = (char *)calloc(buf_size, sizeof(*err_string));
if (!err_string) {
return string();
}
if (0 != av_strerror(av_error_code, err_string, buf_size - 1)) {
free(err_string);
stringstream ss;
ss << "Unknown error with code " << av_error_code;
return ss.str();
}
string str(err_string);
free(err_string);
return str;
}
class FFmpegStreamer {
private:
AVFormatContext *oc = NULL;
AVStream *vs = NULL;
int nFps = 0;
public:
FFmpegStreamer(AVCodecID eCodecId, int nWidth, int nHeight, int nFps, const char *szInFilePath) : nFps(nFps) {
avformat_network_init();
int ret = 0;
if ((eCodecId == AV_CODEC_ID_H264) || (eCodecId == AV_CODEC_ID_HEVC))
ret = avformat_alloc_output_context2(&oc, NULL, "mpegts", NULL);
else if (eCodecId == AV_CODEC_ID_AV1)
ret = avformat_alloc_output_context2(&oc, NULL, "ivf", NULL);
if (ret < 0) {
LOG(ERROR) << "FFmpeg: failed to allocate an AVFormatContext. Error message: " << AvErrorToString(ret);
return;
}
oc->url = av_strdup(szInFilePath);
LOG(INFO) << "Streaming destination: " << oc->url;
// Add video stream to oc
vs = avformat_new_stream(oc, NULL);
if (!vs) {
LOG(ERROR) << "FFMPEG: Could not alloc video stream";
return;
}
vs->id = 0;
// Set video parameters
AVCodecParameters *vpar = vs->codecpar;
vpar->codec_id = eCodecId;
vpar->codec_type = AVMEDIA_TYPE_VIDEO;
vpar->width = nWidth;
vpar->height = nHeight;
// Everything is ready. Now open the output stream.
if (avio_open(&oc->pb, oc->url, AVIO_FLAG_WRITE) < 0) {
LOG(ERROR) << "FFMPEG: Could not open " << oc->url;
return;
}
// Write the container header
if (avformat_write_header(oc, NULL)) {
LOG(ERROR) << "FFMPEG: avformat_write_header error!";
return;
}
}
~FFmpegStreamer() {
if (oc) {
av_write_trailer(oc);
avio_close(oc->pb);
avformat_free_context(oc);
}
}
bool Stream(uint8_t *pData, int nBytes, int nPts) {
AVPacket *pkt = av_packet_alloc();
if (!pkt) {
LOG(ERROR) << "AVPacket allocation failed !";
return false;
}
pkt->pts = av_rescale_q(nPts++, AVRational{1, nFps}, vs->time_base);
// No B-frames
pkt->dts = pkt->pts;
pkt->stream_index = vs->index;
pkt->data = pData;
pkt->size = nBytes;
if (!memcmp(pData, "\x00\x00\x00\x01\x67", 5)) {
pkt->flags |= AV_PKT_FLAG_KEY;
}
// Write the compressed frame into the output
int ret = av_write_frame(oc, pkt);
av_write_frame(oc, NULL);
if (ret < 0) {
LOG(ERROR) << "FFMPEG: Error while writing video frame";
}
av_packet_free(&pkt);
return true;
}
};
/*
* This copyright notice applies to this header file only:
*
* Copyright (c) 2010-2023 NVIDIA Corporation
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the software, and to permit persons to whom the
* software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")
#undef ERROR
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#define SOCKET int
#define INVALID_SOCKET -1
#endif
enum LogLevel { TRACE, INFO, WARNING, ERROR, FATAL };
namespace simplelogger {
class Logger {
public:
Logger(LogLevel level, bool bPrintTimeStamp) : level(level), bPrintTimeStamp(bPrintTimeStamp) {}
virtual ~Logger() {}
virtual std::ostream &GetStream() = 0;
virtual void FlushStream() {}
bool ShouldLogFor(LogLevel l) { return l >= level; }
char *GetLead(LogLevel l, const char *szFile, int nLine, const char *szFunc) {
if (l < TRACE || l > FATAL) {
sprintf(szLead, "[?????] ");
return szLead;
}
const char *szLevels[] = {"TRACE", "INFO", "WARN", "ERROR", "FATAL"};
if (bPrintTimeStamp) {
time_t t = time(NULL);
struct tm *ptm = localtime(&t);
sprintf(szLead, "[%-5s][%02d:%02d:%02d] ", szLevels[l], ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
} else {
sprintf(szLead, "[%-5s] ", szLevels[l]);
}
return szLead;
}
void EnterCriticalSection() { mtx.lock(); }
void LeaveCriticalSection() { mtx.unlock(); }
private:
LogLevel level;
char szLead[80];
bool bPrintTimeStamp;
std::mutex mtx;
};
class LoggerFactory {
public:
static Logger *CreateFileLogger(std::string strFilePath, LogLevel level = INFO, bool bPrintTimeStamp = true) {
return new FileLogger(strFilePath, level, bPrintTimeStamp);
}
static Logger *CreateConsoleLogger(LogLevel level = INFO, bool bPrintTimeStamp = true) {
return new ConsoleLogger(level, bPrintTimeStamp);
}
static Logger *CreateUdpLogger(char *szHost, unsigned uPort, LogLevel level = INFO, bool bPrintTimeStamp = true) {
return new UdpLogger(szHost, uPort, level, bPrintTimeStamp);
}
private:
LoggerFactory() {}
class FileLogger : public Logger {
public:
FileLogger(std::string strFilePath, LogLevel level, bool bPrintTimeStamp) : Logger(level, bPrintTimeStamp) {
pFileOut = new std::ofstream();
pFileOut->open(strFilePath.c_str());
}
~FileLogger() { pFileOut->close(); }
std::ostream &GetStream() { return *pFileOut; }
private:
std::ofstream *pFileOut;
};
class ConsoleLogger : public Logger {
public:
ConsoleLogger(LogLevel level, bool bPrintTimeStamp) : Logger(level, bPrintTimeStamp) {}
std::ostream &GetStream() { return std::cout; }
};
class UdpLogger : public Logger {
private:
class UdpOstream : public std::ostream {
public:
UdpOstream(char *szHost, unsigned short uPort) : std::ostream(&sb), socket(INVALID_SOCKET) {
#ifdef _WIN32
WSADATA w;
if (WSAStartup(0x0101, &w) != 0) {
fprintf(stderr, "WSAStartup() failed.\n");
return;
}
#endif
socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket == INVALID_SOCKET) {
#ifdef _WIN32
WSACleanup();
#endif
fprintf(stderr, "socket() failed.\n");
return;
}
#ifdef _WIN32
unsigned int b1, b2, b3, b4;
sscanf(szHost, "%u.%u.%u.%u", &b1, &b2, &b3, &b4);
struct in_addr addr = {(unsigned char)b1, (unsigned char)b2, (unsigned char)b3, (unsigned char)b4};
#else
struct in_addr addr = {inet_addr(szHost)};
#endif
struct sockaddr_in s = {AF_INET, htons(uPort), addr};
server = s;
}
~UdpOstream() throw() {
if (socket == INVALID_SOCKET) {
return;
}
#ifdef _WIN32
closesocket(socket);
WSACleanup();
#else
close(socket);
#endif
}
void Flush() {
if (sendto(socket, sb.str().c_str(), (int)sb.str().length() + 1, 0, (struct sockaddr *)&server,
(int)sizeof(sockaddr_in)) == -1) {
fprintf(stderr, "sendto() failed.\n");
}
sb.str("");
}
private:
std::stringbuf sb;
SOCKET socket;
struct sockaddr_in server;
};
public:
UdpLogger(char *szHost, unsigned uPort, LogLevel level, bool bPrintTimeStamp)
: Logger(level, bPrintTimeStamp), udpOut(szHost, (unsigned short)uPort) {}
UdpOstream &GetStream() { return udpOut; }
virtual void FlushStream() { udpOut.Flush(); }
private:
UdpOstream udpOut;
};
};
class LogTransaction {
public:
LogTransaction(Logger *pLogger, LogLevel level, const char *szFile, const int nLine, const char *szFunc)
: pLogger(pLogger), level(level) {
if (!pLogger) {
std::cout << "[-----] ";
return;
}
if (!pLogger->ShouldLogFor(level)) {
return;
}
pLogger->EnterCriticalSection();
pLogger->GetStream() << pLogger->GetLead(level, szFile, nLine, szFunc);
}
~LogTransaction() {
if (!pLogger) {
std::cout << std::endl;
return;
}
if (!pLogger->ShouldLogFor(level)) {
return;
}
pLogger->GetStream() << std::endl;
pLogger->FlushStream();
pLogger->LeaveCriticalSection();
if (level == FATAL) {
exit(1);
}
}
std::ostream &GetStream() {
if (!pLogger) {
return std::cout;
}
if (!pLogger->ShouldLogFor(level)) {
return ossNull;
}
return pLogger->GetStream();
}
private:
Logger *pLogger;
LogLevel level;
std::ostringstream ossNull;
};
} // namespace simplelogger
extern simplelogger::Logger *logger;
#define LOG(level) simplelogger::LogTransaction(logger, level, __FILE__, __LINE__, __FUNCTION__).GetStream()
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment