-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
adding video compression (h264) #499
base: master
Are you sure you want to change the base?
Changes from all commits
f90b86e
aee675e
560dda6
5b4623e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -160,6 +160,20 @@ ifeq ($(WITH_BLOSC),YES) | |
endif | ||
endif | ||
|
||
ifeq ($(WITH_VC),YES) | ||
ifeq ($(VC_EXTERNAL),NO) | ||
PROD_LIBS += videoCompression | ||
else | ||
ifdef VC_LIB | ||
VC_DIR = $(VC_LIB) | ||
PROD_LIBS += videoCompression | ||
else | ||
PROD_SYS_LIBS += videoCompression | ||
endif | ||
endif | ||
endif | ||
Comment on lines
+163
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this is relevant here and for your PR in ADSupport. This videoCompression library seems to only be implemented in ADSupport. Is there a chance it could even be provided externally? On a similar vein, if it isn't ever external, I don't think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. |
||
|
||
|
||
ifeq ($(WITH_SZIP),YES) | ||
ifeq ($(SZIP_EXTERNAL),NO) | ||
PROD_LIBS += szip | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -491,7 +491,6 @@ NDArray *compressLZ4(NDArray *input, NDCodecStatus_t *status, char *errorMessage | |
return output; | ||
} | ||
|
||
|
||
NDArray *decompressLZ4(NDArray *input, NDCodecStatus_t *status, char *errorMessage) | ||
{ | ||
// Sanity check | ||
|
@@ -635,6 +634,97 @@ NDArray *decompressBSLZ4(NDArray *input, NDCodecStatus_t *status, char *errorMes | |
|
||
#endif // ifdef HAVE_BITSHUFFLE | ||
|
||
#ifdef HAVE_VC | ||
#include <video_compression.h> | ||
//extern int H264_compress_default(const char*, char*, int, int, int); | ||
NDArray *compressH264(void** vc_context, NDArray *input, NDCodecStatus_t *status, char *errorMessage) | ||
{ | ||
//printf("inside compressH264\n"); | ||
if (!input->codec.empty()) { | ||
sprintf(errorMessage, "Array is already compressed"); | ||
*status = NDCODEC_WARNING; | ||
return NULL; | ||
} | ||
|
||
switch (input->dataType) { | ||
case NDInt8: | ||
case NDUInt8: | ||
//case NDInt16: | ||
//case NDUInt16: | ||
break; | ||
default: | ||
sprintf(errorMessage, "H264 only supports 8-bit data"); | ||
*status = NDCODEC_ERROR; | ||
//goto failure; | ||
return NULL; | ||
} | ||
|
||
|
||
|
||
NDArrayInfo_t info; | ||
input->getInfo(&info); | ||
int outputSize = LZ4_compressBound((int)info.totalBytes); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LZ4_compressBound appears to be a simple function that calculates a number slightly larger than the value that it is given. I found this sufficient when testing this plugin, but I will think about if another calculation (or perhaps simply using info.totalBytes) would be more suitable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But |
||
NDArray *output = allocArray(input, -1, outputSize); | ||
|
||
if (!output) { | ||
sprintf(errorMessage, "Failed to allocate H264 output array"); | ||
*status = NDCODEC_ERROR; | ||
return NULL; | ||
} | ||
|
||
//int compSize = LZ4_compress_default((const char*)input->pData, (char*)output->pData, (int)info.totalBytes, outputSize); | ||
int x_size = input->dims[0].size; | ||
int y_size = input->dims[1].size; | ||
|
||
//printf("x_size %d y_size %d\n", x_size, y_size); | ||
//printf("before H264 compress default\n"); | ||
//int compSize = H264_compress_default((const char*)input->pData, (char*)output->pData, (int)x_size, (int)y_size, outputSize); | ||
//int compSize = H264_compress((const char*)input->pData, (char*)output->pData, (int)x_size, (int)y_size); | ||
int compSize = H264_compress(vc_context, (const char*)input->pData, (char*)output->pData, (int)x_size, (int)y_size); | ||
|
||
if (compSize <= 0) { | ||
output->release(); | ||
sprintf(errorMessage, "Internal H264 error"); | ||
*status = NDCODEC_ERROR; | ||
return NULL; | ||
} | ||
|
||
output->codec.name = codecName[NDCODEC_H264]; | ||
output->compressedSize = compSize; | ||
//printf("before return output\n"); | ||
|
||
return output; | ||
} | ||
|
||
#else | ||
|
||
NDArray *compressH264(void** vc_context, NDArray *input, NDCodecStatus_t *status, char *errorMessage) | ||
{ | ||
sprintf(errorMessage, "No H264 support"); | ||
*status = NDCODEC_ERROR; | ||
return NULL; | ||
} | ||
|
||
NDArray *decompressH264(NDArray *input, NDCodecStatus_t *status, char *errorMessage) | ||
{ | ||
sprintf(errorMessage, "No H264 support"); | ||
*status = NDCODEC_ERROR; | ||
return NULL; | ||
} | ||
|
||
|
||
void set_gop_size(int value){ | ||
printf("unable to set gop size to %d\n", value); | ||
} | ||
|
||
void set_q_min_max(int value){ | ||
printf("unable to set q min max to %d\n", value); | ||
} | ||
Comment on lines
+708
to
+722
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These functions are implemented only for the case where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see now that Is that part of the reason why the compress function can't run with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I think a global variable is not ideal because I believe it is possible to have two separate cameras in a single IOC, and hence multiple instances of the plugin. I did not think of this. In practice, there is usually only camera per IOC. I don't think it is likely that the global variable is the reason for the unlock issues since I did my testing with only one instance of the plugin. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Even with a single instance of the plugin, but without the additional locks you added, if you write into the QMinMax or GOPSize PVs while compression is happening, you will reset the codec state while the struct is being used. That's not safe. These functions should be implemented in NDPluginCodec, which can track the current encoding state, and block writes to these values if encoding is ongoing. |
||
|
||
#endif | ||
|
||
|
||
|
||
/** Callback function that is called by the NDArray driver with new NDArray data. | ||
* Does JPEG or Blosc compression on the array. | ||
* If compression is None or fails the input array is passed on without | ||
|
@@ -717,6 +807,13 @@ void NDPluginCodec::processCallbacks(NDArray *pArray) | |
break; | ||
} | ||
|
||
case NDCODEC_H264: { | ||
unlock(); | ||
result = compressH264(&vc_context, pArray, &codecStatus, errorMessage); | ||
lock(); | ||
break; | ||
} | ||
|
||
} | ||
|
||
if (result && result != pArray) { | ||
|
@@ -807,8 +904,17 @@ asynStatus NDPluginCodec::writeInt32(asynUser *pasynUser, epicsInt32 value) | |
value = 1; | ||
} else if (function < FIRST_NDCODEC_PARAM) { | ||
status = NDPluginDriver::writeInt32(pasynUser, value); | ||
} else if (function == NDCodecGOPSize) { | ||
printf("setting GOP Size...\n"); | ||
set_gop_size(vc_context, value); | ||
} else if (function == NDCodecQMinMax) { | ||
//lock(); | ||
printf("setting qmin and qmax...\n"); | ||
set_q_min_max(vc_context, value); | ||
//unlock(); | ||
} | ||
|
||
|
||
/* Set the parameter in the parameter library. */ | ||
status = (asynStatus) setIntegerParam(function, value); | ||
|
||
|
@@ -877,6 +983,8 @@ NDPluginCodec::NDPluginCodec(const char *portName, int queueSize, int blockingCa | |
createParam(NDCodecBloscCLevelString, asynParamInt32, &NDCodecBloscCLevel); | ||
createParam(NDCodecBloscShuffleString, asynParamInt32, &NDCodecBloscShuffle); | ||
createParam(NDCodecBloscNumThreadsString, asynParamInt32, &NDCodecBloscNumThreads); | ||
createParam(NDCodecGOPSizeString, asynParamInt32, &NDCodecGOPSize); | ||
createParam(NDCodecQMinMaxString, asynParamInt32, &NDCodecQMinMax); | ||
Comment on lines
+986
to
+987
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't following the existing formatting. |
||
|
||
/* Set the plugin type string */ | ||
setStringParam(NDPluginDriverPluginType, "NDPluginCodec"); | ||
|
@@ -894,6 +1002,8 @@ NDPluginCodec::NDPluginCodec(const char *portName, int queueSize, int blockingCa | |
// This plugin currently ignores this setting and always does callbacks, so make the setting reflect the behavior | ||
setIntegerParam(NDArrayCallbacks, 1); | ||
|
||
void* vc_context=0; | ||
|
||
/* Try to connect to the array port */ | ||
connectToArrayPort(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
#define NDCodecBloscCLevelString "BLOSC_CLEVEL" /* (int r/w) Blosc compression level */ | ||
#define NDCodecBloscShuffleString "BLOSC_SHUFFLE" /* (bool r/w) Should Blosc apply shuffling? */ | ||
#define NDCodecBloscNumThreadsString "BLOSC_NUMTHREADS" /* (int r/w) Number of threads to be used by Blosc */ | ||
#define NDCodecGOPSizeString "GOP_SIZE" /* (int r/w) Group of pictures size for H264 */ | ||
#define NDCodecQMinMaxString "QMINMAX" /* (int r/w) Sets min and max values for quantizer for H264 */ | ||
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't following the existing formatting either. |
||
|
||
/** Compress/decompress NDArrays according to available codecs. | ||
* This plugin is a source of NDArray callbacks, passing the (possibly | ||
|
@@ -74,6 +76,7 @@ class NDPLUGIN_API NDPluginCodec : public NDPluginDriver { | |
/* These methods override the virtual methods in the base class */ | ||
void processCallbacks(NDArray *pArray); | ||
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value); | ||
void* vc_context; | ||
|
||
protected: | ||
int NDCodecMode; | ||
|
@@ -87,6 +90,8 @@ class NDPLUGIN_API NDPluginCodec : public NDPluginDriver { | |
int NDCodecBloscCLevel; | ||
int NDCodecBloscShuffle; | ||
int NDCodecBloscNumThreads; | ||
int NDCodecGOPSize; | ||
int NDCodecQMinMax; | ||
|
||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not allow users to set
qmax
andqmin
separately?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is my understanding that QMin and QMax set a range for acceptable "Q" (which stands for quantizer and roughly corresponds to quality), and then the codec selects a "Q" within this range based on various other factors. When I was testing this I found it useful to have a single PV that changes the compression quality, rather than separate min/max that must be set every time. It would be more complete to have separate QMin/QMax PVs, and then perhaps a PV that controls a "QCenter" which shifts QMin and QMax by the same amount as a convenience, although there is a little bit of complexity in implementing "QCenter", and I tried to keep things simple for the initial implementation. I agree that in principle QMin and QMax should be exposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, that's a more complicated interface.