Skip to content
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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

bsobhani
Copy link

ADCore changes for areaDetector/ADSupport#44

Copy link
Contributor

@ericonr ericonr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing code has a lot of leftover whitespace changes, commented out code, and debugging printfs. Are you planning on cleaning these up after initial review?

Comment on lines +232 to +238
# Sets the quantizer (qmin and qmax) to value written to PV.
# One can effectively adjust quality this way, with 1 being the least lossy, and higher numbers lossier
record(ao, "$(P)$(R)QMinMax")
{
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR=0),$(TIMEOUT=1))QMINMAX")
}
Copy link
Contributor

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 and qmin separately?

Copy link
Author

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.

Copy link
Contributor

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.

Comment on lines +163 to +174
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 VC_LIB is required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.


NDArrayInfo_t info;
input->getInfo(&info);
int outputSize = LZ4_compressBound((int)info.totalBytes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is LZ4_compressBound the function that should be used here?

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But LZ4_compressBound is also specific to LZ4 compression. That it works for video compression is more a coincidence than something that should be relied upon. That's why I think passing the buffer size to the compression function would at least allow us to catch issues with buffer size and avoid memory corruption. FFMpeg might also provide a function to help us determine the necessary buffer size, based on the configured bit-rate and quantization.

Comment on lines +707 to +722
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are implemented only for the case where HAVE_VC isn't defined.

Copy link
Contributor

@ericonr ericonr Mar 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that set_gop_size and set_q_min_max are defined in the ADSupport functions. They are operating on global status; is it safe to operate on them during compression, for example? And this means we can't have more than one compression stream on the same IOC.

Is that part of the reason why the compress function can't run with unlock() called before it?

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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.

Comment on lines +986 to +987
createParam(NDCodecGOPSizeString, asynParamInt32, &NDCodecGOPSize);
createParam(NDCodecQMinMaxString, asynParamInt32, &NDCodecQMinMax);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't following the existing formatting.

Comment on lines +16 to +17
#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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't following the existing formatting either.

@bsobhani
Copy link
Author

bsobhani commented Mar 6, 2024

Erico, thank you for the feedback. I have responded to some of your comments. When I have time I will take a more thorough look at the pull request and probably make some additional commits.

@bsobhani
Copy link
Author

I did some testing and was able to confirm a couple of things: 1) If I create a mutex to prevent video compression parameters from being written while compressing then I can safely leave H264_compress unlocked in ADCore. 2) I tried running two video streams in the same IOC and confirm there were issues - if I make each instance of NDPluginCodec have its own video compression context pointer and pass this pointer to the video compression functions then the issues seem to go away.

I have made changes locally that seem to resolve these things, but not ready to push yet. Next week I should have some more time to work on this and then I will push my changes.

@bsobhani
Copy link
Author

bsobhani commented Apr 1, 2024

I have pushed changes which fix those two major issues. I plan to clean some things up and address some of the other smaller issues later.

Also, I seem to have done something to mutilate something in this pull request. All I was trying to do was fix a typo in my email address for a single commit. But in the process it seems to have changed the hashes of all the commits in the repo, which I think may be why it thinks there are 994 new commits. I will think about what to do about this.

Edit: I believe I have unmutilated the pull request. I will work on the other issues next (e.g. remove the use of the LZ4 bound function which creates a non-useful dependency on the LZ4 plugin, and look into framerate/bitrate/timebase), and will comment when I am finished.

@MarkRivers MarkRivers marked this pull request as draft November 18, 2024 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants