- Захват кадров с моей карты захвата DeckLink с помощью DeckLink SDK (1920 x 1080, 60 кадров в секунду, 8 бит на канал) кадры)
- Закодируйте их с помощью nvJPEG.
- Декодируйте их снова, также используя nvJPEG.
- Отобразите их с близким к нет заметной задержки (на данный момент я пробовал визуализировать их с помощью SDL и OpenCV)
Мне удалось создать решение, которое работает, но при выполнении декодирования результирующий поток имеет задержку, которая намного выше, чем Я ожидал бы примерно 2,2 секунды.
Ниже показано изображение источника и второго монитора, показывающее кадры, отображаемые с помощью sdl при вызове моей функции декодирования:

Когда я выполняю только кодирование и декодирование функция закомментирована, задержка составляет около 50 мс:

< /p>
Чтобы лучше понять, возможно ли это вообще в теории, я провел несколько исследований относительно скорости кодирования и декодирования.
Например, я попробовал запустить моя программа только с кодированием и записью файлов *.jpeg на диск. Я установил счетчик и захватил 3600 кадров, закодировал их и сохранил. При этом я достиг 59,52 кадров в секунду, что по сути соответствует частоте кадров, с которой карта захвата отправляет кадры (60 кадров в секунду).
Далее я использовал пример декодирования. который NVIDIA предоставляет в своем репозитории cuda-samples для чтения и декодирования 3600 кадров. Там я добился частоты кадров ~200 кадров в секунду.
Я также попробовал синхронизировать кодирование и декодирование, а также все время, необходимое от прибытия кадра до его рендеринга. .
Когда функция декодирования закомментирована, вывод выглядит следующим образом:
Time for encoding: 1.988256 ms
Time between frame arrival and display: 12.608000 ms
Time for encoding: 2.246624 ms
Time between frame arrival and display: 12.603800 ms
Time for encoding: 1.830784 ms
Time between frame arrival and display: 12.099000 ms
При вызове функции декодирования:
Time for encoding: 1.802432 ms
Time for decoding: 5.869888 ms
Time between frame arrival and display: 18.190200 ms
Time for encoding: 1.594944 ms
Time for decoding: 6.192608 ms
Time between frame arrival and display: 18.531900 ms
Time for encoding: 2.071168 ms
Time for decoding: 5.807488 ms
Time between frame arrival and display: 18.527200 ms
Вот это выглядит так: при декодировании добавляется только задержка ~6 мс по сравнению с только кодированием.
Однако при сравнении с наблюдаемой задержкой более 2 секунд что-то не складывается.
Значит, либо у меня неверный подход к расчету времени, либо есть некоторые проблемы с тем, как я обрабатываю декодирование и отображение.
Я думаю, что могут возникнуть проблемы с тем, как я распределяю и управляю памятью или вообще с тем, как я настраиваю свои обработчики и обратные вызовы.
Я также думал о создании другой кодировки и декодирования. сам буферизует, так что, например, входящие «сырые» кадры сначала записываются в буфер и процессы кодирования и декодирования от него отсоединяются. Однако я не уверен, принесет ли это мне преимущества с точки зрения производительности или вызовет только проблемы с таймингами и синхронизацией.
Есть какие-нибудь советы или подсказки?Ниже приведен код моей текущей программы:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
bool escapePressed = false;
int dev_malloc(void** p, size_t s) { return (int)cudaMalloc(p, s); }
int dev_free(void* p) { return (int)cudaFree(p); }
int host_malloc(void** p, size_t s, unsigned int f) { return (int)cudaHostAlloc(p, s, f); }
int host_free(void* p) { return (int)cudaFreeHost(p); }
class CUDAHandler {
public:
CUDAHandler(int width, int height, int quality) : width(width), height(height), quality(quality) {
checkCudaErrors(cudaGetDeviceProperties(&cuda_props, 0));
printf("Using GPU %d (%s, %d SMs, %d th/SM max, CC %d.%d, ECC %s)\n",
0, cuda_props.name, cuda_props.multiProcessorCount,
cuda_props.maxThreadsPerMultiProcessor, cuda_props.major, cuda_props.minor,
cuda_props.ECCEnabled ? "on" : "off");
nvjpegDevAllocator_t dev_allocator = { &dev_malloc, &dev_free };
nvjpegPinnedAllocator_t pinned_allocator = { &host_malloc, &host_free };
int flags = 0;
// nvJPEG handle and state
checkCudaErrors(nvjpegCreateEx(NVJPEG_BACKEND_DEFAULT, &dev_allocator, &pinned_allocator, flags, &nvjpeg_handle));
checkCudaErrors(nvjpegJpegStateCreate(nvjpeg_handle, &jpeg_state));
// Encoder
checkCudaErrors(nvjpegEncoderStateCreate(nvjpeg_handle, &encoder_state, cuda_stream));
checkCudaErrors(nvjpegEncoderParamsCreate(nvjpeg_handle, &encoder_params, cuda_stream));
checkCudaErrors(nvjpegEncoderParamsSetQuality(encoder_params, quality, cuda_stream));
checkCudaErrors(nvjpegEncoderParamsSetSamplingFactors(encoder_params, NVJPEG_CSS_444, cuda_stream));
checkCudaErrors(cudaStreamCreateWithFlags(&cuda_stream, cudaStreamNonBlocking));
dev_malloc(reinterpret_cast(&d_rgb), width * height * 3);
dev_malloc(reinterpret_cast(&d_decoded_rgb), width * height * 3);
source.channel[0] = d_rgb;
source.pitch[0] = width * 3;
output.channel[0] = d_decoded_rgb;
output.pitch[0] = width * 3;
}
~CUDAHandler() {
checkCudaErrors(nvjpegDestroy(nvjpeg_handle));
checkCudaErrors(nvjpegJpegStateDestroy(jpeg_state));
checkCudaErrors(cudaStreamDestroy(cuda_stream));
checkCudaErrors(nvjpegEncoderStateDestroy(encoder_state));
checkCudaErrors(nvjpegEncoderParamsDestroy(encoder_params));
checkCudaErrors(cudaFree(d_rgb));
checkCudaErrors(cudaFree(d_decoded_rgb));
}
void Encode(const unsigned char* rgb, vector& jpeg_image) {
checkCudaErrors(cudaStreamSynchronize(cuda_stream));
cudaEvent_t startEvent = NULL, stopEvent = NULL;
float loopTime = 0;
checkCudaErrors(cudaEventCreate(&startEvent, cudaEventBlockingSync));
checkCudaErrors(cudaEventCreate(&stopEvent, cudaEventBlockingSync));
checkCudaErrors(cudaEventRecord(startEvent, cuda_stream));
cudaMemcpy(d_rgb, rgb, width * height * 3, cudaMemcpyHostToDevice);
nvjpegEncodeImage(nvjpeg_handle, encoder_state, encoder_params, &source, NVJPEG_INPUT_RGBI, width, height, cuda_stream);
nvjpegEncodeRetrieveBitstream(nvjpeg_handle, encoder_state, NULL, &bitstream_length, cuda_stream);
jpeg_image.resize(bitstream_length);
nvjpegEncodeRetrieveBitstream(nvjpeg_handle, encoder_state, jpeg_image.data(), &bitstream_length, cuda_stream);
checkCudaErrors(cudaEventRecord(stopEvent, cuda_stream));
checkCudaErrors(cudaStreamSynchronize(cuda_stream));
cudaStreamSynchronize(cuda_stream);
checkCudaErrors(cudaEventElapsedTime(&loopTime, startEvent, stopEvent));
time = static_cast(loopTime);
printf("Time for encoding: %f ms\n", time);
}
void Decode(const unsigned char* jpeg_image, size_t jpeg_size, unsigned char* rgb) {
checkCudaErrors(cudaStreamSynchronize(cuda_stream));
cudaEvent_t startEvent = NULL, stopEvent = NULL;
float loopTime = 0;
checkCudaErrors(cudaEventCreate(&startEvent, cudaEventBlockingSync));
checkCudaErrors(cudaEventCreate(&stopEvent, cudaEventBlockingSync));
checkCudaErrors(cudaEventRecord(startEvent, cuda_stream));
checkCudaErrors(nvjpegDecode(nvjpeg_handle, jpeg_state, jpeg_image, jpeg_size, NVJPEG_OUTPUT_RGBI, &output, cuda_stream));
cudaMemcpy(rgb, d_decoded_rgb, width * height * 3, cudaMemcpyDeviceToHost);
checkCudaErrors(cudaEventRecord(stopEvent, cuda_stream));
checkCudaErrors(cudaEventSynchronize(stopEvent));
checkCudaErrors(cudaEventElapsedTime(&loopTime, startEvent, stopEvent));
time = static_cast(loopTime);
printf("Time for decoding: %f ms\n", time);
}
private:
int width, height, quality;
size_t bitstream_length;
nvjpegHandle_t nvjpeg_handle;
nvjpegJpegState_t jpeg_state;
cudaStream_t cuda_stream;
nvjpegEncoderState_t encoder_state;
nvjpegEncoderParams_t encoder_params;
nvjpegImage_t output;
nvjpegImage_t source;
unsigned char* d_rgb;
unsigned char* d_decoded_rgb;
cudaDeviceProp cuda_props;
double time;
};
class MyDeckLinkCallback : public IDeckLinkInputCallback {
public:
MyDeckLinkCallback(SDL_Renderer* renderer, SDL_Texture* texture, CUDAHandler* cudaHandler)
: renderer(renderer), texture(texture), cudaHandler(cudaHandler), refCount(1), input_rgb_image(width* height * 3), jpeg_image(width* height * 3), output_rgb_image(width* height * 3) {}
HRESULT VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket) override {
if (videoFrame) {
auto t1 = high_resolution_clock::now();
if (escapePressed) {
printf("Escape key pressed, exiting program.\n");
exit(EXIT_SUCCESS);
}
void* frameBytes;
videoFrame->GetBytes(&frameBytes);
ConvertARGBToRGB(static_cast(frameBytes), input_rgb_image.data(), width, height);
cudaHandler->Encode(input_rgb_image.data(), jpeg_image);
cudaHandler->Decode(jpeg_image.data(), jpeg_image.size(), output_rgb_image.data());
//SDL_UpdateTexture(texture, NULL, frameBytes, videoFrame->GetRowBytes());
SDL_UpdateTexture(texture, nullptr, output_rgb_image.data(), width * 3);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
auto t2 = high_resolution_clock::now();
duration ms_double = t2 - t1;
printf("Time between frame arrival and display: %f ms\n", ms_double.count());
}
return S_OK;
}
HRESULT VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode* newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags) override {
printf("Video format changed\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
if (ppvObject == NULL)
return E_POINTER;
if (riid == IID_IUnknown) {
*ppvObject = static_cast(this);
AddRef();
return S_OK;
}
if (riid == IID_IDeckLinkInputCallback) {
*ppvObject = static_cast(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override {
return InterlockedIncrement(&refCount);
}
ULONG STDMETHODCALLTYPE Release() override {
ULONG newRefCount = InterlockedDecrement(&refCount);
if (newRefCount == 0) {
delete this;
}
return newRefCount;
}
private:
SDL_Renderer* renderer;
SDL_Texture* texture;
CUDAHandler* cudaHandler;
ULONG refCount;
int width = 1920;
int height = 1080;
std::vector input_rgb_image;
std::vector jpeg_image;
std::vector output_rgb_image;
void ConvertARGBToRGB(const unsigned char* argb, unsigned char* rgb, int width, int height) {
cv::Mat argbImage(height, width, CV_8UC4, const_cast(argb));
cv::Mat rgbImage;
cv::cvtColor(argbImage, rgbImage, cv::COLOR_RGBA2RGB);
std::memcpy(rgb, rgbImage.data, width * height * 3);
}
};
int main(int argc, char* argv[]) {
CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("Failed to initialize SDL: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1920, 1080, SDL_WINDOW_SHOWN);
if (!window) {
printf("Failed to create SDL window: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
printf("Failed to create SDL renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGR24, SDL_TEXTUREACCESS_STREAMING, 1920, 1080);
if (!texture) {
printf("Failed to create SDL texture: %s\n", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
CUDAHandler cudaHandler(1920, 1080, 90);
IDeckLinkIterator* deckLinkIterator = NULL;
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
if (result != S_OK) {
printf("Failed to create DeckLink iterator instance\n");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
IDeckLink* deckLink = NULL;
if (deckLinkIterator->Next(&deckLink) == S_OK) {
IDeckLinkInput* deckLinkInput = NULL;
result = deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput);
if (result != S_OK) {
printf("Failed to query DeckLink input interface\n");
deckLinkIterator->Release();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
MyDeckLinkCallback* callback = new MyDeckLinkCallback(renderer, texture, &cudaHandler);
result = deckLinkInput->SetCallback(callback);
if (result != S_OK) {
printf("Failed to set DeckLink callback\n");
deckLinkInput->Release();
deckLinkIterator->Release();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
result = deckLinkInput->EnableVideoInput(bmdModeHD1080p6000, bmdFormat8BitBGRA, bmdVideoInputFlagDefault);
if (result != S_OK) {
printf("Failed to enable video input\n");
deckLinkInput->Release();
deckLinkIterator->Release();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
result = deckLinkInput->StartStreams();
if (result != S_OK) {
printf("Failed to start streams\n");
deckLinkInput->Release();
deckLinkIterator->Release();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
bool quit = false;
SDL_Event event;
while (!quit) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
quit = true;
}
else if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) {
escapePressed = true;
quit = true;
}
}
}
}
deckLinkInput->StopStreams();
deckLinkInput->Release();
deckLinkIterator->Release();
delete callback;
}
else {
printf("No DeckLink device found\n");
}
CoUninitialize();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Подробнее здесь: https://stackoverflow.com/questions/785 ... y-to-the-r