Skip to content

Add automatic writing encoded video streams to disk when streaming from RTSP sources using cudacodec #3098

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

Merged
merged 12 commits into from
Dec 12, 2021

Conversation

cudawarped
Copy link
Contributor

@cudawarped cudawarped commented Nov 3, 2021

Update cudacoded VideoReader to;

  1. automatically write the raw encoded RTSP video streams to video files if desired,
  2. use less GPU memory by more closely mirroring the Nvidia samples, and
  3. decode mpeg4 files.

This pull request relies on the changes in opencv/opencv#20978.

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
  • The PR is proposed to proper branch
  • There is reference to original bug report and related work
  • There is accuracy test, performance test and test data in opencv_extra repository, if applicable
    Patch to opencv_extra has the same branch name.
  • The feature is well documented and sample code can be built with the project CMake
force_builders=Custom
buildworker:Custom=linux-4,linux-6
build_image:Custom=ubuntu-cuda:16.04

James Bowley and others added 10 commits June 11, 2019 09:57
…luding checks to ensure codec used in input video file is supported on the current device.
# Conflicts:
#	modules/cudacodec/include/opencv2/cudacodec.hpp
#	modules/cudacodec/src/video_decoder.cpp
1) automatically write the raw encoded video stream to a video file and,
2) use less GPU memory by more closely mirroring the Nvidia samples.  Specifically querying the decoder for the number of decode surfaces (h265 commonly uses 4) instead of always using 20 and not using adaptive deinterlacing when the video sequence is progressive.  Additional updates to mirror the Nvidia sample include initializing the decoder so that HandleVideoSequence() gets called every time before the decoder is initialized, ensuring all the parameters for the decoder are provided by nvcudec.
Added facility to decode AV1, not tested as VideoCapture doesn't return a valid fourcc for this.
Add facility to decode MPEG4 video - requires modification to VideoCapture see pull request.
…les to that they play in vlc.

Notes: VideoCapture - returns mpeg as the codec for mpeg4 files, so files written as .m4v from mpeg4 sources cannot currently be decoded.  This is also true for AV1 sources where cap.get(CAP_PROP_FOURCC) returns 0.
Added mpeg4 test file which can be decoded when VideoCapture adds the extra_data.
Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

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

Be careful with creation of GOD classes.
Reader should read, not write.

Please take a look on comments below before merge.

file, especially from container formats avi, mp4 etc. If the filename provided is invalid, cannot be opened
or written to, the first call to nextFrame() after calling this function will return false.
*/
CV_WRAP virtual void writeToFile(const std::string filename, const bool autoDetectExt = false) = 0;
Copy link
Member

Choose a reason for hiding this comment

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

const std::string filename

const reference

Here and below


FFMPEG is used to read videos. User can implement own demultiplexing with cudacodec::RawVideoSource
*/
CV_EXPORTS_W Ptr<VideoReader> createVideoReader(const String& filename);
CV_EXPORTS_W Ptr<VideoReader> createVideoReader(const String& filename, const String filenameToWrite = "", const bool autoDetectExt = false);
Copy link
Member

Choose a reason for hiding this comment

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

String filenameToWrite

Main problem of this approach is missing unicode support.


I would create dedicated API for that:

  • keep existed API "as is"
  • add new API like proposed (with _W)
  • add extra new API with std::ostream&& or std::ofstream&& (note, move ctor is available only)

Copy link
Contributor Author

@cudawarped cudawarped Nov 29, 2021

Choose a reason for hiding this comment

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

Main problem of this approach is missing unicode support.

Ahh I see. How can we support unicode in the new api (VideoReader_W) for const String& filename?

add extra new API with std::ostream&& or std::ofstream&& (note, move ctor is available only)

If possible I would like to be able to automatically detect the extension before creating the file so that the file can be played back without having to be renamed after writing has finished.

If this is not performed internally then this would involve creating a VideoCapture object before creating the new class just to detect the codec and then using that info to determine the file extension.

VideoCapture cap("rtsp://...")
std::string ext = FourCCToExtension(cap.get(CAP_PROP_FOURCC))
std::ofstream fileOut = (fileName + ext);
cap.release(); // release the stream so we can connect to it again below
cv::Ptr<cv::cudacodec::VideoReader_W> reader_w = cv::cudacodec::createVideoReader_W(inputFile, fileOut);

I would like to avoid that can you suggest and alternative?

Copy link
Member

Choose a reason for hiding this comment

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

How can we support unicode in the new api (VideoReader_W) for const String& filename

String may handle UTF-8 strings only (it is std::string , BTW). There is no wchar_t or std::wstring in OpenCV.
Main question is about support on target platform (e.g., support of UTF-8 in std::ofstream) - Win 10 brings some support for UTF-8 but it should be pre-configured (also this configuration may break legacy apps).


alternative

Capture to temporary file and then rename() with correct extension on user side.
But it also looks weird, so I don't have strong suggestion or recommendation here.

@cudawarped
Copy link
Contributor Author

Thank you for taking a look.

Be careful with creation of GOD classes.
Reader should read, not write.

I am not sure how to achieve my objective without creating a GOD class. I want to be able to stream and decode from an RTSP source at the same time as archiving the footage. This seems like a common use case for IP camera's, e.g. run DNN over live decoded footage and archive footage for training later or to simply see why the DNN failed.
With two classes I would need two streams which is what I am trying to avoid due to unecessary network overhead and lack of support from IP camera's (usually each stream is a different resolution if multiple streams are supported).
Can you suggest a way to achieve this without placing the read and write functionality in the same class?

@alalek
Copy link
Member

alalek commented Nov 29, 2021

I can suggest .retrieve() approach to fetch decoded frames + RAW encoded packets (0-N per frame) and write them by user code.
But there is no strong requirements for that in opencv_contrib.

open() with enabling of RAW stream
std::ofstream ostream = ...;
... retrieve and white "codec_extradata" ...
int rawIdxBase = (int)cudacodec.get(PROP_RAW_PACKAGES_BASE_INDEX);
...
if (cudacodec.grab())
{
    cudacodec.retrive(frame, 0);  // regular decoded frame data
    int N = (int)cudacodec.get(PROP_NUMBER_OF_RAW_PACKAGES_FOR_CURRENT_FRAME);  // [0-N] range
    for (int i = 0; i < N; i++)
    {
        cudacodec.retrieve(raw_buffer, rawIdxBase + i);
        ostream.write(raw_buffer);
    }

    ... process frame ...
}
else
{
    ... do we have RAW packages in case of end of stream? ...
}

@cudawarped
Copy link
Contributor Author

I can suggest .retrieve() approach to fetch decoded frames + RAW encoded packets (0-N per frame) and write them by user code.
But there is no strong requirements for that in opencv_contrib.

I have added retrieve, grab etc. to enable the raw packets to be retrieved from the VideoReader class. I understand that there may not be a strong requirement for this, however these additions should not interfere with the current functionality when rawMode is disabled.

The only "issue" I can see is that there is no straight forward way to get the packets corresponding to the current frame because the decoder is fed encoded data as fast as possible. As a result when it outputs a decoded frame (HandlePictureDisplay) there is no guarantee that that frame corresponds to all the packets that were fed in. The best we can do is get all the packets since the last call to grab() or the creation of the VideoReader object, which is fine for file writing purposes.

@cudawarped cudawarped requested a review from alalek December 8, 2021 17:44
Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

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

Well done! Thank you for contribution 👍

Comment on lines 115 to +116

int StartCodeLen(unsigned char* data, const int sz) {
Copy link
Member

Choose a reason for hiding this comment

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

static to keep code local

@alalek alalek merged commit 1cecd2c into opencv:4.x Dec 12, 2021
@cudawarped
Copy link
Contributor Author

Thanks for all you help alalek, I will add static when cudacodec needs modifying again to account for depreciation of CUvideosource.

@alalek alalek mentioned this pull request Dec 30, 2021
@alalek alalek mentioned this pull request Feb 22, 2022
@cudawarped cudawarped mentioned this pull request Aug 26, 2022
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants