Skip to content

Commit a4e739b

Browse files
committed
Merge branch 'uvgV3CRTP_integration'
2 parents ac5f900 + 0094a05 commit a4e739b

File tree

10 files changed

+236
-8
lines changed

10 files changed

+236
-8
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "src/app/dependencies/uvgV3CRTP"]
2+
path = src/app/dependencies/uvgV3CRTP
3+
url = https://github.com/ultravideo/uvgV3CRTP.git

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ set(TMC2_EP_UPDATE_DISCONNECTED ON)
1616

1717
option(BUILD_SHARED_LIBS "Build using shared uvgVPCCenc library" ON)
1818

19+
# Control if uvgVPCCenc should be build with RTP support
20+
option(ENABLE_V3CRTP "Enable RTP support" OFF)
21+
1922
project(uvgVPCCenc
2023
VERSION ${uvgVPCCenc_VER}
2124
DESCRIPTION ${uvgVPCCenc_DESCR}

CMakePresets.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"ENABLE_TESTING": false,
2020
"CHECK_PC_MD5": false,
2121
"ENABLE_CI_TESTING": false,
22-
"ENABLE_MY_TESTING": false
22+
"ENABLE_MY_TESTING": false,
23+
"ENABLE_V3CRTP": false
2324
}
2425
},
2526
{

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ cmake --build --preset=Release
5555
By default, the build application is located in ```_build/Release/src/app/```.
5656
Refer to "Using uvgVPCCenc" section to learn how to use the library, and attached application.
5757

58+
#### Compile with V3C RTP support
59+
To enable sending over RTP run cmake with
60+
```
61+
cmake --preset=Release -DENABLE_V3CRTP=ON
62+
```
63+
5864
### Test uvgVPCCenc
5965
To test the encoder, please use following commands:
6066
```
@@ -98,6 +104,13 @@ The application option ```--uvgvpcc``` accept a string containing uvgVPCCenc par
98104

99105
The list of uvgVPCCenc parameters that can be modified with the ```--uvgvpcc``` option is defined in the ```parameterMap``` initialized in [parameters.cpp](src/lib/utils/parameters.cpp).
100106

107+
#### V3C RTP Example:
108+
```
109+
uvgVPCCenc -i <path_to_ply> -n 10 --dst-address=127.0.0.1 --dst-port=8890
110+
```
111+
112+
Send encoded bitstream over RTP. Destination can be set using ```--dst-address``` and ```--dst-port```. A separate RTP media stream is created for each type of V3C unit and the streames are multiplexed to the same port using SSRC (hardcoded to be ```vuh_unit_type + 1```).
113+
101114
---
102115

103116

@@ -113,6 +126,8 @@ The list of uvgVPCCenc parameters that can be modified with the ```--uvgvpcc```
113126
--uvgvpcc <params> Encoder configuration parameters (see next section)
114127
--help Show this help message
115128
--version Show version information
129+
--dst-address <IP> Destination IP address for an rtp stream (when compiled with V3C RTP support)";
130+
--dst-port <number> Destination port for an rtp stream (when compiled with V3C RTP support)";
116131
```
117132
---
118133

src/app/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
message(DEBUG "./src/app/cmake")
22

3+
# Take care of external dependencies (e.g. uvgV3CRTP)
4+
include(dependencies/FindDependencies.cmake)
5+
36
add_library(miniplyLibrary STATIC
47
extras/miniply.cpp
58
)
@@ -21,5 +24,12 @@ target_compile_options(getoptLibrary PRIVATE -fPIC)
2124

2225
target_link_libraries(uvgVPCCenc uvgvpccenc)
2326

27+
# Add uvgV3CRTP to link targets if enabled
28+
if(ENABLE_V3CRTP)
29+
target_link_libraries(uvgVPCCenc uvgv3crtp)
30+
# Include define to uvgVPCCenc so uvgV3CRTP code is included
31+
target_compile_definitions(uvgVPCCenc PRIVATE ENABLE_V3CRTP)
32+
endif()
33+
2434
# Define installation destinations
2535
install(TARGETS uvgVPCCenc DESTINATION bin)

src/app/cli.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ namespace {
5151

5252
// NOLINTNEXTLINE(cert-err58-cpp)
5353
const std::string short_options = "i:g:l:n:o:s:t:b:d:";
54-
const std::array<struct option, 11> long_options{{{"input", required_argument, nullptr, 'i'},
54+
const std::array<struct option, 13> long_options{{{"input", required_argument, nullptr, 'i'},
5555
{"output", required_argument, nullptr, 'o'},
5656
{"frames", required_argument, nullptr, 'n'},
5757
{"start-frame", required_argument, nullptr, 's'},
@@ -61,7 +61,9 @@ const std::array<struct option, 11> long_options{{{"input", required_argument, n
6161
{"loop-input", required_argument, nullptr, 'l'},
6262
{"dummy-run", required_argument, nullptr, 'd'},
6363
{"help", no_argument, nullptr, 0},
64-
{"version", no_argument, nullptr, 0}}};
64+
{"version", no_argument, nullptr, 0},
65+
{"dst-address", required_argument, nullptr, 0},
66+
{"dst-port", required_argument, nullptr, 0}}};
6567

6668
/**
6769
* \brief Try to detect voxel size from file name automatically
@@ -180,6 +182,10 @@ bool opts_parse(cli::opts_t& opts, const int& argc, const std::span<const char*
180182
} else if (name == "help") {
181183
cli::print_help();
182184
return true;
185+
} else if (name == "dst-address") {
186+
opts.dstAddress = optarg; // TODO: Check that the address is valid
187+
} else if (name == "dst-port") {
188+
opts.dstPort = static_cast<uint16_t>(std::stoi(optarg));
183189
}
184190
}
185191

@@ -189,8 +195,13 @@ bool opts_parse(cli::opts_t& opts, const int& argc, const std::span<const char*
189195
}
190196

191197
// Check that the required files were defined
192-
if (opts.inputPath.empty() || opts.outputPath.empty()) {
193-
throw std::runtime_error("Input error: Input or output path is empty\n");
198+
if (opts.inputPath.empty()) {
199+
throw std::runtime_error("Input error: Input path is empty\n");
200+
}
201+
202+
// Check that at least one output method is defined
203+
if (opts.outputPath.empty() && opts.dstAddress.empty()) {
204+
throw std::runtime_error("Input error: At least one output should be specified (e.g. 'output' or 'dst-address')\n");
194205
}
195206

196207
if (opts.inputGeoPrecision == 0) {
@@ -254,7 +265,10 @@ void print_help(void) {
254265
std::cout << " --uvgvpcc <params> Encoder configuration parameters\n";
255266
std::cout << " --help Show this help message\n";
256267
std::cout << " --version Show version information\n";
257-
268+
#if defined(ENABLE_V3CRTP)
269+
std::cout << " --dst-address <IP> Destination IP address for an rtp stream\n";
270+
std::cout << " --dst-port <number> Destination port for an rtp stream\n";
271+
#endif
258272
std::cout << "\nDescription:\n";
259273
std::cout << " This tool encodes point cloud video frames using the uvgVPCCenc codec\n";
260274
std::cout << " with specified parameters.\n";

src/app/cli.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <limits>
4040
#include <string>
4141
#include <span>
42+
#include <cstdint>
4243

4344
namespace cli {
4445

@@ -65,6 +66,10 @@ struct opts_t {
6566
size_t nbLoops = 1;
6667
/** \brief If dummyRun is true, config is verified but no encoding is done */
6768
bool dummyRun = false;
69+
/** \brief Destination address for rtp streams */
70+
std::string dstAddress{};
71+
/** \brief Destination port for rtp streams */
72+
uint16_t dstPort = 0;
6873
};
6974

7075
bool opts_parse(cli::opts_t& opts, const int& argc, const std::span<const char* const>& args);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
if(ENABLE_V3CRTP)
2+
# Check if uvgV3CRTP is already available
3+
find_package(uvgV3CRTP 0.6.0 QUIET) #TODO: update min version to a version with necessary bug fixes etc.
4+
5+
# Try pkgConfig as well
6+
find_package(PkgConfig QUIET)
7+
if(PkgConfig_FOUND)
8+
if(NOT UVGV3CRTP_FOUND)
9+
pkg_search_module(UVGV3CRTP uvgv3crtp>=0.6.0 uvgV3CRTP>=0.6.0)
10+
endif()
11+
endif()
12+
13+
# If we did not find uvgV3CRTP build it from source_group
14+
if(UVGV3CRTP_FOUND)
15+
message(STATUS "Found uvgV3CRTP")
16+
else()
17+
message(STATUS "uvgV3CRTP not found, building from source...")
18+
19+
#include(FetchContent)
20+
find_package(Git REQUIRED)
21+
22+
option(GIT_SUBMODULE "Check submodules during build" ON)
23+
if(GIT_SUBMODULE)
24+
message(STATUS "Update submodule")
25+
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
26+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
27+
RESULT_VARIABLE GIT_SUBMOD_RESULT)
28+
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
29+
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodule")
30+
endif()
31+
endif()
32+
33+
# Add subdir and specify relevant options
34+
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
35+
36+
set(UVGV3CRTP_DISABLE_TESTS ON CACHE BOOL "" FORCE)
37+
set(UVGV3CRTP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE)
38+
set(UVGV3CRTP_DISABLE_INSTALL OFF CACHE BOOL "" FORCE)
39+
40+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/dependencies/uvgV3CRTP)
41+
42+
include_directories(${uvgv3crtp_SOURCE_DIR}/include)
43+
link_directories(${uvgv3crtp_SOURCE_DIR})
44+
45+
unset(BUILD_SHARED_LIBS)
46+
47+
endif()
48+
endif()
49+
50+
# include and link directories for dependencies.
51+
# These are needed when the compilation happens a second time and the library
52+
# is found so no compilation happens. Not needed in every case, but a nice backup
53+
include_directories(${CMAKE_BINARY_DIR}/include)
54+
link_directories(${CMAKE_CURRENT_BINARY_DIR}/lib)

src/app/dependencies/uvgV3CRTP

Submodule uvgV3CRTP added at fbf5bd9

src/app/uvgVPCCencAppExample.cpp

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
#include "uvgvpcc/log.hpp"
6161
#include "uvgvpcc/uvgvpcc.hpp"
6262

63+
#if defined(ENABLE_V3CRTP)
64+
#include <uvgv3crtp/version.h>
65+
#include <uvgv3crtp/v3c_api.h>
66+
#endif
67+
6368
namespace {
6469

6570
// Semaphores for synchronization.
@@ -257,6 +262,7 @@ void file_writer(uvgvpcc_enc::API::v3c_unit_stream* chunks, const std::string& o
257262
if (chunk.data == nullptr && chunk.len == 0) {
258263
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION", "All chunks written to file.\n");
259264
file.close();
265+
chunks->io_mutex.unlock();
260266
break;
261267
}
262268
if (chunk.data != nullptr) {
@@ -282,6 +288,118 @@ void file_writer(uvgvpcc_enc::API::v3c_unit_stream* chunks, const std::string& o
282288
}
283289
}
284290

291+
/// @brief Application thread for sending output over RTP.
292+
/// @param chunks
293+
/// @param output_path
294+
void v3c_sender(uvgvpcc_enc::API::v3c_unit_stream* chunks, const std::string dst_address, const uint16_t dst_port) {
295+
#if defined(ENABLE_V3CRTP)
296+
297+
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION", "Using uvgV3CRTP lib version " + uvgV3CRTP::get_version() + "\n");
298+
299+
// ******** Initialize sample stream with input bitstream ***********
300+
//
301+
uvgV3CRTP::V3C_State<uvgV3CRTP::V3C_Sender> state(uvgV3CRTP::INIT_FLAGS::VPS | uvgV3CRTP::INIT_FLAGS::AD | uvgV3CRTP::INIT_FLAGS::OVD |
302+
uvgV3CRTP::INIT_FLAGS::GVD | uvgV3CRTP::INIT_FLAGS::AVD,
303+
dst_address.c_str(), dst_port // Receiver address and port
304+
); // Create a new state in a sender configuration
305+
state.init_sample_stream(FORCED_V3C_SIZE_PRECISION); // Use the forced precision for now
306+
//
307+
// ******************************************************************
308+
if (state.get_error_flag() != uvgV3CRTP::ERROR_TYPE::OK) {
309+
throw std::runtime_error(std::string("V3C Sender : Error initializing state (message: ") + state.get_error_msg() + ")");
310+
}
311+
312+
// ******** Send sample stream **********
313+
//
314+
// TODO: Currently whole bitstream is stored, but should clear the sample stream at certain points
315+
// For rate limiting sending
316+
auto last_sleep_time = std::chrono::high_resolution_clock::now();
317+
while (state.get_error_flag() == uvgV3CRTP::ERROR_TYPE::OK) {
318+
319+
// Get chunks and add them to state for sending
320+
chunks->available_chunks.acquire();
321+
chunks->io_mutex.lock();
322+
323+
const uvgvpcc_enc::API::v3c_chunk& chunk = chunks->v3c_chunks.front();
324+
if (chunk.data == nullptr && chunk.len == 0) {
325+
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION", "All chunks sent.\n");
326+
chunks->io_mutex.unlock();
327+
328+
break;
329+
}
330+
331+
// Add new data to state
332+
size_t len = chunk.len;
333+
std::ptrdiff_t ptr = 0; // Keep track of ptr in the V3C unit stream
334+
for (const uint64_t current_size : chunk.v3c_unit_sizes) {
335+
// Add the V3C unit to existing sample stream
336+
// NOLINTNEXTLINE(concurrency-mt-unsafe)
337+
state.append_to_sample_stream(std::next(chunk.data.get(), ptr), static_cast<size_t>(current_size));
338+
ptr += static_cast<std::ptrdiff_t>(current_size);
339+
}
340+
341+
chunks->v3c_chunks.pop();
342+
chunks->io_mutex.unlock();
343+
344+
// Send newly added data
345+
while (state.get_error_flag() == uvgV3CRTP::ERROR_TYPE::OK) {
346+
// Send gof if full
347+
if(state.cur_gof_is_full()) {
348+
uvgV3CRTP::send_gof(&state);
349+
state.next_gof();
350+
351+
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION",
352+
"Sent one gof " + std::to_string(len) + " bytes.\n");
353+
}
354+
else
355+
{
356+
//TODO: track which unit has been sent
357+
// for (uint8_t id = 0; id < uvgV3CRTP::NUM_V3C_UNIT_TYPES; id++) {
358+
// if (!state.cur_gof_has_unit(uvgV3CRTP::V3C_UNIT_TYPE(id))) continue;
359+
360+
// // TODO: send side-channel info
361+
362+
// uvgV3CRTP::send_unit(&state, uvgV3CRTP::V3C_UNIT_TYPE(id));
363+
//}
364+
//state.next_gof();
365+
break;
366+
}
367+
// Get difference from last sleep and sleep if needed
368+
const auto elapsed_time = std::chrono::high_resolution_clock::now() - last_sleep_time;
369+
std::this_thread::sleep_for(std::chrono::nanoseconds((1000000000 / uvgV3CRTP::SEND_FRAME_RATE)) - elapsed_time);
370+
last_sleep_time = std::chrono::high_resolution_clock::now(); // Update last send time
371+
}
372+
373+
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION",
374+
"Processed V3C chunk of size " + std::to_string(len) + " bytes.\n");
375+
376+
if (state.get_error_flag() == uvgV3CRTP::ERROR_TYPE::EOS) {
377+
state.reset_error_flag(); //More chunks are added later so reset EOS
378+
}
379+
}
380+
381+
// ******** Print info about sample stream **********
382+
//
383+
// Print state and bitstream info
384+
//state.print_state(false);
385+
386+
//std::cout << "Bitstream info: " << std::endl;
387+
//state.print_bitstream_info();
388+
389+
size_t len = 0;
390+
auto info = std::unique_ptr<char, decltype(&free)>(state.get_bitstream_info_string(uvgV3CRTP::INFO_FMT::LOGGING, &len), &free);
391+
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::TRACE>("APPLICATION", "Bitstream info string: \n" + std::string(info.get(), len) + "\n");
392+
//
393+
// **************************************
394+
395+
if (state.get_error_flag() != uvgV3CRTP::ERROR_TYPE::OK && state.get_error_flag() != uvgV3CRTP::ERROR_TYPE::EOS) {
396+
throw std::runtime_error(std::string("V3C Sender : Sending error (message: ") + state.get_error_msg() + ")");
397+
}
398+
#else
399+
throw std::runtime_error("V3C RTP not enabled, re-run cmake with '-DENABLE_V3CRTP=ON'.");
400+
#endif
401+
}
402+
285403
/// @brief Simple application wrapper taking a command string as input to set multiple encoder parameters.
286404
/// @param parametersCommand
287405
void setParameters(const std::string& parametersCommand) {
@@ -375,7 +493,10 @@ int main(const int argc, const char* const argv[]) {
375493

376494
uvgvpcc_enc::API::v3c_unit_stream output; // Each v3c chunk gets appended to the V3C unit stream as they are encoded
377495
std::thread file_writer_thread;
378-
file_writer_thread = std::thread(file_writer, &output, appParameters.outputPath);
496+
std::thread v3c_sender_thread;
497+
498+
if (!appParameters.outputPath.empty()) file_writer_thread = std::thread(file_writer, &output, appParameters.outputPath);
499+
if (!appParameters.dstAddress.empty()) v3c_sender_thread = std::thread(v3c_sender, &output, appParameters.dstAddress, appParameters.dstPort);
379500

380501
// Main loop of the application, feeding one frame to the encoder at each iteration
381502
for (;;) {
@@ -410,7 +531,8 @@ int main(const int argc, const char* const argv[]) {
410531
output.io_mutex.unlock();
411532
output.available_chunks.release();
412533

413-
file_writer_thread.join();
534+
if(file_writer_thread.joinable()) file_writer_thread.join();
535+
if(v3c_sender_thread.joinable()) v3c_sender_thread.join();
414536

415537
uvgvpcc_enc::Logger::log<uvgvpcc_enc::LogLevel::INFO>("APPLICATION", "Encoded " + std::to_string(frameRead) + " frames.\n");
416538

0 commit comments

Comments
 (0)