1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 00:03:03 +04:00

Merge pull request #24363 from cudawarped:videoio_ffmpeg_add_stream_encapsulation

videoio: Add raw encoded video stream muxing to cv::VideoWriter with CAP_FFMPEG #24363

Allow raw encoded video streams (e.g. h264[5]) to be encapsulated by `cv::VideoWriter` to video containers (e.g. mp4/mkv).

Operates in a similar way to https://github.com/opencv/opencv/pull/15290 where encapsulation is enabled by setting the `VideoWriterProperties::VIDEOWRITER_PROP_RAW_VIDEO` flag when constructing `cv::VideoWriter` e.g.
```
VideoWriter container(fileNameOut, api, fourcc, fps, { width, height }, { VideoWriterProperties::VIDEOWRITER_PROP_RAW_VIDEO, 1 });
```
and each raw encoded frame is passed as single row of a `CV_8U` `cv::Mat`.

The main reason for this PR is to allow `cudacodec::VideoWriter` to output its encoded streams to a suitable container, see https://github.com/opencv/opencv_contrib/pull/3569.

### Pull Request Readiness Checklist

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

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
cudawarped
2023-10-25 13:21:01 +03:00
committed by GitHub
parent a3b3a589f9
commit 38bc519e4a
4 changed files with 358 additions and 84 deletions
+149 -82
View File
@@ -1447,7 +1447,8 @@ bool CvCapture_FFMPEG::processRawPacket()
#else
AVCodecContext* ctx = ic->streams[video_stream]->codec;
int err = av_bitstream_filter_filter(bsfc, ctx, NULL, &packet_filtered.data,
&packet_filtered.size, packet.data, packet.size, packet_filtered.flags & AV_PKT_FLAG_KEY);
&packet_filtered.size, packet.data, packet.size, packet.flags & AV_PKT_FLAG_KEY);
if (packet.flags & AV_PKT_FLAG_KEY) packet_filtered.flags |= AV_PKT_FLAG_KEY;
if (err < 0)
{
CV_WARN("Packet filtering failed");
@@ -2069,6 +2070,7 @@ struct CvVideoWriter_FFMPEG
bool writeFrame( const unsigned char* data, int step, int width, int height, int cn, int origin );
bool writeHWFrame(cv::InputArray input);
double getProperty(int propId) const;
bool setProperty(int, double);
void init();
@@ -2092,6 +2094,9 @@ struct CvVideoWriter_FFMPEG
VideoAccelerationType va_type;
int hw_device;
int use_opencl;
bool encode_video;
int idr_period;
bool key_frame;
};
static const char * icvFFMPEGErrStr(int err)
@@ -2157,6 +2162,9 @@ void CvVideoWriter_FFMPEG::init()
hw_device = -1;
use_opencl = 0;
ok = false;
encode_video = true;
idr_period = 0;
key_frame = false;
}
/**
@@ -2202,7 +2210,7 @@ static AVCodecContext * icv_configure_video_stream_FFMPEG(AVFormatContext *oc,
AVStream *st,
const AVCodec* codec,
int w, int h, int bitrate,
double fps, AVPixelFormat pixel_format, int fourcc)
double fps, AVPixelFormat pixel_format, int fourcc, AVCodecID codec_id)
{
#ifdef CV_FFMPEG_CODECPAR
AVCodecContext *c = avcodec_alloc_context3(codec);
@@ -2213,9 +2221,7 @@ static AVCodecContext * icv_configure_video_stream_FFMPEG(AVFormatContext *oc,
int frame_rate, frame_rate_base;
c->codec_id = codec->id;
c->codec_type = AVMEDIA_TYPE_VIDEO;
c->codec_tag = fourcc;
c->codec_id = codec ? codec->id : codec_id;
#ifndef CV_FFMPEG_CODECPAR
// Set per-codec defaults
@@ -2225,6 +2231,9 @@ static AVCodecContext * icv_configure_video_stream_FFMPEG(AVFormatContext *oc,
c->codec_id = c_id;
#endif
c->codec_type = AVMEDIA_TYPE_VIDEO;
c->codec_tag = fourcc;
/* put sample parameters */
int64_t lbit_rate = (int64_t)bitrate;
lbit_rate += (bitrate / 2);
@@ -2323,6 +2332,29 @@ static AVCodecContext * icv_configure_video_stream_FFMPEG(AVFormatContext *oc,
static const int OPENCV_NO_FRAMES_WRITTEN_CODE = 1000;
static int icv_av_encapsulate_video_FFMPEG(AVFormatContext* oc, AVStream* video_st, AVCodecContext* c,
uint8_t* data, int sz, const int frame_idx, const bool key_frame)
{
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(57, 0, 0)
AVPacket pkt_;
av_init_packet(&pkt_);
AVPacket* pkt = &pkt_;
#else
AVPacket* pkt = av_packet_alloc();
#endif
if(key_frame)
pkt->flags |= PKT_FLAG_KEY;
pkt->pts = frame_idx;
pkt->size = sz;
pkt->data = data;
av_packet_rescale_ts(pkt, c->time_base, video_st->time_base);
int ret = av_write_frame(oc, pkt);
#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(57, 0, 0)
av_packet_free(&pkt);
#endif
return ret;
}
static int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st, AVCodecContext * c,
uint8_t *, uint32_t,
AVFrame * picture, int frame_idx)
@@ -2404,6 +2436,14 @@ static int icv_av_write_frame_FFMPEG( AVFormatContext * oc, AVStream * video_st,
/// write a frame with FFMPEG
bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int width, int height, int cn, int origin )
{
if (!encode_video) {
CV_Assert(cn == 1 && ((width > 0 && height == 1) || (width == 1 && height > 0 && step == 1)));
const bool set_key_frame = key_frame ? key_frame : idr_period ? frame_idx % idr_period == 0 : 1;
bool ret = icv_av_encapsulate_video_FFMPEG(oc, video_st, context, (uint8_t*)data, width, frame_idx, set_key_frame);
frame_idx++;
return ret;
}
// check parameters
if (input_pix_fmt == AV_PIX_FMT_BGR24) {
if (cn != 3) {
@@ -2592,6 +2632,21 @@ double CvVideoWriter_FFMPEG::getProperty(int propId) const
return 0;
}
bool CvVideoWriter_FFMPEG::setProperty(int property_id, double value)
{
if (!video_st) return false;
switch (property_id)
{
case VIDEOWRITER_PROP_KEY_FLAG:
key_frame = static_cast<bool>(value);
break;
default:
return false;
}
return true;
}
/// close video output stream and free associated memory
void CvVideoWriter_FFMPEG::close()
{
@@ -2601,17 +2656,19 @@ void CvVideoWriter_FFMPEG::close()
// TODO -- do we need to account for latency here?
/* write the trailer, if any */
if (picture && ok && oc)
if ((!encode_video || picture) && ok && oc)
{
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(57, 0, 0)
if (!(oc->oformat->flags & AVFMT_RAWPICTURE))
#endif
{
for(;;)
{
int ret = icv_av_write_frame_FFMPEG( oc, video_st, context, outbuf, outbuf_size, NULL, frame_idx);
if( ret == OPENCV_NO_FRAMES_WRITTEN_CODE || ret < 0 )
break;
if (encode_video) {
for (;;)
{
int ret = icv_av_write_frame_FFMPEG(oc, video_st, context, outbuf, outbuf_size, NULL, frame_idx);
if (ret == OPENCV_NO_FRAMES_WRITTEN_CODE || ret < 0)
break;
}
}
}
av_write_trailer(oc);
@@ -2720,6 +2777,8 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
close();
encode_video = !params.get(VIDEOWRITER_PROP_RAW_VIDEO, false);
idr_period = params.get(VIDEOWRITER_PROP_KEY_INTERVAL, 0);
const bool is_color = params.get(VIDEOWRITER_PROP_IS_COLOR, true);
const int depth = params.get(VIDEOWRITER_PROP_DEPTH, CV_8U);
const bool is_supported = depth == CV_8U || (depth == CV_16U && !is_color);
@@ -2770,13 +2829,15 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
if(fps <= 0)
return false;
// we allow frames of odd width or height, but in this case we truncate
// the rightmost column/the bottom row. Probably, this should be handled more elegantly,
// but some internal functions inside FFMPEG swscale require even width/height.
width &= -2;
height &= -2;
if( width <= 0 || height <= 0 )
return false;
if (encode_video) {
// we allow frames of odd width or height, but in this case we truncate
// the rightmost column/the bottom row. Probably, this should be handled more elegantly,
// but some internal functions inside FFMPEG swscale require even width/height.
width &= -2;
height &= -2;
if (width <= 0 || height <= 0)
return false;
}
/* auto detect the output format from the name and fourcc code. */
@@ -3027,41 +3088,46 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
HWAccelIterator accel_iter(va_type, true/*isEncoder*/, dict);
while (accel_iter.good())
{
AVPixelFormat hw_format = AV_PIX_FMT_NONE;
AVHWDeviceType hw_type = AV_HWDEVICE_TYPE_NONE;
#else
do {
#endif
if (encode_video) {
#if USE_AV_HW_CODECS
accel_iter.parse_next();
AVHWDeviceType hw_type = accel_iter.hw_type();
codec = NULL;
AVPixelFormat hw_format = AV_PIX_FMT_NONE;
if (hw_device_ctx)
av_buffer_unref(&hw_device_ctx);
if (hw_type != AV_HWDEVICE_TYPE_NONE)
{
codec = hw_find_codec(codec_id, hw_type, av_codec_is_encoder, accel_iter.disabled_codecs().c_str(), &hw_format);
accel_iter.parse_next();
hw_type = accel_iter.hw_type();
codec = NULL;
hw_format = AV_PIX_FMT_NONE;
if (hw_device_ctx)
av_buffer_unref(&hw_device_ctx);
if (hw_type != AV_HWDEVICE_TYPE_NONE)
{
codec = hw_find_codec(codec_id, hw_type, av_codec_is_encoder, accel_iter.disabled_codecs().c_str(), &hw_format);
if (!codec)
continue;
hw_device_ctx = hw_create_device(hw_type, hw_device, accel_iter.device_subname(), use_opencl != 0);
if (!hw_device_ctx)
continue;
}
else if (hw_type == AV_HWDEVICE_TYPE_NONE)
#endif
{
codec = avcodec_find_encoder(codec_id);
if (!codec) {
CV_LOG_ERROR(NULL, "Could not find encoder for codec_id=" << (int)codec_id << ", error: "
<< icvFFMPEGErrStr(AVERROR_ENCODER_NOT_FOUND));
}
}
if (!codec)
continue;
}
hw_device_ctx = hw_create_device(hw_type, hw_device, accel_iter.device_subname(), use_opencl != 0);
if (!hw_device_ctx)
continue;
}
else if (hw_type == AV_HWDEVICE_TYPE_NONE)
#endif
{
codec = avcodec_find_encoder(codec_id);
if (!codec) {
CV_LOG_ERROR(NULL, "Could not find encoder for codec_id=" << (int)codec_id << ", error: "
<< icvFFMPEGErrStr(AVERROR_ENCODER_NOT_FOUND));
}
}
if (!codec)
continue;
#if USE_AV_HW_CODECS
AVPixelFormat format = (hw_format != AV_PIX_FMT_NONE) ? hw_format : codec_pix_fmt;
AVPixelFormat format = (hw_format != AV_PIX_FMT_NONE) ? hw_format : codec_pix_fmt;
#else
AVPixelFormat format = codec_pix_fmt;
AVPixelFormat format = codec_pix_fmt;
#endif
#ifdef CV_FFMPEG_CODECPAR
@@ -3069,7 +3135,7 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
#endif
context = icv_configure_video_stream_FFMPEG(oc, video_st, codec,
width, height, (int) (bitrate + 0.5),
fps, format, fourcc);
fps, format, fourcc, codec_id);
if (!context)
{
continue;
@@ -3082,17 +3148,18 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
av_dump_format(oc, 0, filename, 1);
#endif
#endif
if (encode_video) {
#if USE_AV_HW_CODECS
if (hw_device_ctx) {
context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
if (hw_format != AV_PIX_FMT_NONE) {
context->hw_frames_ctx = hw_create_frames(NULL, hw_device_ctx, width, height, hw_format);
if (!context->hw_frames_ctx)
continue;
if (hw_device_ctx) {
context->hw_device_ctx = av_buffer_ref(hw_device_ctx);
if (hw_format != AV_PIX_FMT_NONE) {
context->hw_frames_ctx = hw_create_frames(NULL, hw_device_ctx, width, height, hw_format);
if (!context->hw_frames_ctx)
continue;
}
}
}
#endif
}
int64_t lbit_rate = (int64_t) context->bit_rate;
lbit_rate += (int64_t)(bitrate / 2);
@@ -3101,7 +3168,7 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
context->bit_rate = (int) lbit_rate;
/* open the codec */
err = avcodec_open2(context, codec, NULL);
err = !encode_video ? 0 : avcodec_open2(context, codec, NULL);
if (err >= 0) {
#if USE_AV_HW_CODECS
va_type = hw_type_to_va_type(hw_type);
@@ -3137,43 +3204,43 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
avcodec_parameters_from_context(video_st->codecpar, context);
#endif
outbuf = NULL;
if (encode_video) {
outbuf = NULL;
#if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(57, 0, 0)
if (!(oc->oformat->flags & AVFMT_RAWPICTURE))
if (!(oc->oformat->flags & AVFMT_RAWPICTURE))
#endif
{
/* allocate output buffer */
/* assume we will never get codec output with more than 4 bytes per pixel... */
outbuf_size = width*height*4;
outbuf = (uint8_t *) av_malloc(outbuf_size);
}
{
/* allocate output buffer */
/* assume we will never get codec output with more than 4 bytes per pixel... */
outbuf_size = width * height * 4;
outbuf = (uint8_t*)av_malloc(outbuf_size);
}
bool need_color_convert;
AVPixelFormat sw_pix_fmt = context->pix_fmt;
bool need_color_convert;
AVPixelFormat sw_pix_fmt = context->pix_fmt;
#if USE_AV_HW_CODECS
if (context->hw_frames_ctx)
sw_pix_fmt = ((AVHWFramesContext*)context->hw_frames_ctx->data)->sw_format;
if (context->hw_frames_ctx)
sw_pix_fmt = ((AVHWFramesContext*)context->hw_frames_ctx->data)->sw_format;
#endif
need_color_convert = (sw_pix_fmt != input_pix_fmt);
need_color_convert = (sw_pix_fmt != input_pix_fmt);
/* allocate the encoded raw picture */
picture = icv_alloc_picture_FFMPEG(sw_pix_fmt, context->width, context->height, need_color_convert);
if (!picture) {
return false;
}
/* if the output format is not our input format, then a temporary
picture of the input format is needed too. It is then converted
to the required output format */
input_picture = NULL;
if ( need_color_convert ) {
input_picture = icv_alloc_picture_FFMPEG(input_pix_fmt, context->width, context->height, false);
if (!input_picture) {
/* allocate the encoded raw picture */
picture = icv_alloc_picture_FFMPEG(sw_pix_fmt, context->width, context->height, need_color_convert);
if (!picture) {
return false;
}
/* if the output format is not our input format, then a temporary
picture of the input format is needed too. It is then converted
to the required output format */
input_picture = NULL;
if (need_color_convert) {
input_picture = icv_alloc_picture_FFMPEG(input_pix_fmt, context->width, context->height, false);
if (!input_picture) {
return false;
}
}
}
/* open the output file, if needed */