From 246b4cb9d116d2c1d2907928948ef9c9ea01d307 Mon Sep 17 00:00:00 2001 From: Isaac Connor <isaac@zoneminder.com> Date: Mon, 24 Jun 2019 17:22:59 -0400 Subject: [PATCH] working hwdecode --- src/zm_ffmpeg.cpp | 11 +++ src/zm_ffmpeg.h | 1 + src/zm_ffmpeg_camera.cpp | 193 ++++++++++++++++++++++++++++++++------- src/zm_ffmpeg_camera.h | 4 +- zoneminder-config.cmake | 2 +- 5 files changed, 175 insertions(+), 36 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 98509f92f..1dbf3be3b 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -284,6 +284,17 @@ static void zm_log_fps(double d, const char *postfix) { } } +void zm_dump_video_frame(const AVFrame *frame, const char *text) { + Debug(1, "%s: format %d %s %dx%d linesize:%d pts: %" PRId64, + text, + frame->format, + av_get_pix_fmt_name((AVPixelFormat)frame->format), + frame->width, + frame->height, + frame->linesize, + frame->pts + ); +} void zm_dump_frame(const AVFrame *frame,const char *text) { Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" " duration %" PRId64 diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 7c4414db0..e3b8314f8 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -300,6 +300,7 @@ void zm_dump_codec(const AVCodecContext *codec); void zm_dump_codecpar(const AVCodecParameters *par); #endif void zm_dump_frame(const AVFrame *frame, const char *text="Frame"); +void zm_dump_video_frame(const AVFrame *frame, const char *text="Frame"); #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #define zm_av_packet_unref( packet ) av_packet_unref( packet ) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index c02259561..98dfd0e4e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -42,6 +42,22 @@ extern "C" { #endif +static enum AVPixelFormat hw_pix_fmt; +static enum AVPixelFormat get_hw_format( + AVCodecContext *ctx, + const enum AVPixelFormat *pix_fmts +) { + const enum AVPixelFormat *p; + + for ( p = pix_fmts; *p != -1; p++ ) { + if ( *p == hw_pix_fmt ) + return *p; + } + + Error("Failed to get HW surface format."); + return AV_PIX_FMT_NONE; +} + #if HAVE_AVUTIL_HWCONTEXT_H static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) { while (*pix_fmts != AV_PIX_FMT_NONE) { @@ -76,7 +92,7 @@ static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat pix_fmts++; } - Error( "The QSV pixel format not offered in get_format()"); + Error("The QSV pixel format not offered in get_format()"); return AV_PIX_FMT_NONE; } @@ -122,8 +138,8 @@ FfmpegCamera::FfmpegCamera( hwaccel = false; #if HAVE_AVUTIL_HWCONTEXT_H decode = { NULL }; - hwFrame = NULL; #endif + hwFrame = NULL; mFormatContext = NULL; mVideoStreamId = -1; @@ -250,6 +266,7 @@ int FfmpegCamera::Capture( Image &image ) { zm_av_packet_unref( &packet ); continue; } + Debug(1, "transfering from hardware"); ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0); if (ret < 0) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); @@ -259,6 +276,8 @@ int FfmpegCamera::Capture( Image &image ) { } } else { #endif + + ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame ); if ( ret < 0 ) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); @@ -305,7 +324,7 @@ int FfmpegCamera::Capture( Image &image ) { #endif #if HAVE_LIBSWSCALE - if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0 ) { + if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) <= 0 ) { Error("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); return -1; } @@ -331,7 +350,6 @@ int FfmpegCamera::PostCapture() { int FfmpegCamera::OpenFfmpeg() { - int ret; have_video_keyframe = false; @@ -339,7 +357,6 @@ int FfmpegCamera::OpenFfmpeg() { // Open the input, not necessarily a file #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - Debug ( 1, "Calling av_open_input_file" ); if ( av_open_input_file( &mFormatContext, mPath.c_str(), NULL, 0, NULL ) != 0 ) #else // Handle options @@ -360,7 +377,7 @@ int FfmpegCamera::OpenFfmpeg() { } else if ( method == "rtpUni" ) { ret = av_dict_set(&opts, "rtsp_transport", "udp", 0); } else { - Warning("Unknown method (%s)", method.c_str() ); + Warning("Unknown method (%s)", method.c_str()); } //#av_dict_set(&opts, "timeout", "10000000", 0); // in microseconds. @@ -518,6 +535,23 @@ int FfmpegCamera::OpenFfmpeg() { } } // end if h264 #endif + + AVHWAccel *first_hwaccel = av_hwaccel_next(NULL); + AVHWAccel *temp_hwaccel = first_hwaccel; + AVHWAccel *h264 = NULL; + const char * h264_name = "h264_vaapi"; + while ( temp_hwaccel != NULL ) { + Debug(1,"%s ", temp_hwaccel->name); + if ( strcmp(temp_hwaccel->name, h264_name) == 0 ) { + h264=temp_hwaccel; + } + temp_hwaccel = av_hwaccel_next(temp_hwaccel); + + if ( temp_hwaccel == first_hwaccel ) { + break; + } + } + if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == NULL ) { Debug(1, "Failed to find decoder (h264_mmal)" ); @@ -530,32 +564,75 @@ int FfmpegCamera::OpenFfmpeg() { // Try and get the codec from the codec context Error("Can't find codec for video stream from %s", mPath.c_str()); return -1; + } + + Debug(1, "Video Found decoder %s", mVideoCodec->name); + zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); + + enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE; + while ( (type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE ) + Debug(1, "%s", av_hwdevice_get_type_name(type)); + + const char *hw_name = "vaapi"; + type = av_hwdevice_find_type_by_name(hw_name); + if ( type == AV_HWDEVICE_TYPE_NONE ) { + Debug(1,"Device type %s is not supported.", hw_name); } else { - Debug(1, "Video Found decoder %s", mVideoCodec->name); - zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); - // Open the codec -#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - ret = avcodec_open(mVideoCodecContext, mVideoCodec); -#else - ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); -#endif - AVDictionaryEntry *e = NULL; - while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { - Warning( "Option %s not recognized by ffmpeg", e->key); - } - if ( ret < 0 ) { - Error("Unable to open codec for video stream from %s", mPath.c_str()); - av_dict_free(&opts); + Debug(1, "Found hwdevice %s", av_hwdevice_get_type_name(type)); + } + + // Get h_pix_fmt + for ( int i = 0;; i++ ) { + const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i); + if ( !config ) { + Debug(1, "Decoder %s does not support device type %s.", + mVideoCodec->name, av_hwdevice_get_type_name(type)); return -1; } - zm_dump_codec(mVideoCodecContext); + if ( (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX ) + && (config->device_type == type) + ) { + hw_pix_fmt = config->pix_fmt; + break; + } + } // end foreach hwconfig + + mVideoCodecContext->get_format = get_hw_format; + + Debug(1, "Creating hwdevice"); + if ((ret = av_hwdevice_ctx_create(&hw_device_ctx, type, NULL, NULL, 0)) < 0) { + Error("Failed to create specified HW device."); + return -1; } + Debug(1, "Created hwdevice"); + mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); + hwaccel = true; + hwFrame = zm_av_frame_alloc(); + + // Open the codec +#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) + ret = avcodec_open(mVideoCodecContext, mVideoCodec); +#else + ret = avcodec_open2(mVideoCodecContext, mVideoCodec, &opts); +#endif + e = NULL; + while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + Warning( "Option %s not recognized by ffmpeg", e->key); + } + if ( ret < 0 ) { + Error("Unable to open codec for video stream from %s", mPath.c_str()); + av_dict_free(&opts); + return -1; + } + zm_dump_codec(mVideoCodecContext); if ( mVideoCodecContext->hwaccel != NULL ) { Debug(1, "HWACCEL in use"); } else { Debug(1, "HWACCEL not in use"); } + + if ( mAudioStreamId >= 0 ) { if ( (mAudioCodec = avcodec_find_decoder( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -578,13 +655,13 @@ int FfmpegCamera::OpenFfmpeg() { zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - Debug ( 1, "Calling avcodec_open" ); + Debug(1, "Calling avcodec_open"); if ( avcodec_open(mAudioCodecContext, mAudioCodec) < 0 ) { #else - Debug ( 1, "Calling avcodec_open2" ); + Debug(1, "Calling avcodec_open2" ); if ( avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0 ) { #endif - Error( "Unable to open codec for audio stream from %s", mPath.c_str() ); + Error("Unable to open codec for audio stream from %s", mPath.c_str() ); return -1; } zm_dump_codec(mAudioCodecContext); @@ -629,6 +706,7 @@ int FfmpegCamera::OpenFfmpeg() { return -1; } +# if 0 mConvertContext = sws_getContext( mVideoCodecContext->width, mVideoCodecContext->height, @@ -640,6 +718,7 @@ int FfmpegCamera::OpenFfmpeg() { Error( "Unable to create conversion context for %s", mPath.c_str() ); return -1; } +#endif #else // HAVE_LIBSWSCALE Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE @@ -885,7 +964,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( packetqueue->packet_count(mVideoStreamId) >= monitor->GetImageBufferCount() ) { Warning("ImageBufferCount %d is too small. Needs to be at least %d. Either increase it or decrease time between keyframes", monitor->GetImageBufferCount(), - packetqueue->packet_count(mVideoStreamId)+1 ); + packetqueue->packet_count(mVideoStreamId)+1 + ); } packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); @@ -931,6 +1011,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event } #if HAVE_AVUTIL_HWCONTEXT_H if ( hwaccel ) { + Debug(1, "Using hwaccel to decode"); ret = avcodec_receive_frame(mVideoCodecContext, hwFrame); if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); @@ -939,7 +1020,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event continue; } ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0); - if (ret < 0) { + if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); Error("Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf); zm_av_packet_unref(&packet); @@ -947,6 +1028,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event } } else { #endif + Debug(1, "Decodingaccel to decode"); + ret = avcodec_receive_frame(mVideoCodecContext, mRawFrame); if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); @@ -961,6 +1044,24 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event continue; } if ( error_count > 0 ) error_count --; + zm_dump_video_frame(mRawFrame); + if ( mRawFrame->format == hw_pix_fmt ) { + /* retrieve data from GPU to CPU */ + ret = av_hwframe_transfer_data(hwFrame, mRawFrame, 0); + if ( ret < 0 ) { + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + Error("Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf); + zm_av_packet_unref(&packet); + continue; + } + Debug(1,"Success transfering"); + zm_dump_video_frame(hwFrame); + + hwFrame->pts = mRawFrame->pts; + input_frame = hwFrame; + } else { + input_frame = mRawFrame; + } #if HAVE_AVUTIL_HWCONTEXT_H } @@ -968,7 +1069,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event frameComplete = 1; # else - ret = zm_avcodec_decode_video(mVideoCodecContext, mRawFrame, &frameComplete, &packet); + ret = zm_avcodec_decode_video(mVideoCodecContext, input_frame, &frameComplete, &packet); if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); Error("Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf); @@ -978,7 +1079,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event #endif if ( frameComplete ) { - Debug( 4, "Got frame %d", frameCount ); + Debug(4, "Got frame %d", frameCount); uint8_t* directbuffer; @@ -986,7 +1087,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); if ( directbuffer == NULL ) { Error("Failed requesting writeable buffer for the captured image."); - zm_av_packet_unref( &packet ); + zm_av_packet_unref(&packet); return -1; } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) @@ -994,10 +1095,34 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event #else avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); #endif - if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, - 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { - Error("Unable to convert raw format %u to target format %u at frame %d", - mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); + Debug(1,"swscale target format: %c%c%c%c %c%c%c%c", + (imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff), + (mVideoCodecContext->pix_fmt)&0xff, + ((mVideoCodecContext->pix_fmt>>8)&0xff), + ((mVideoCodecContext->pix_fmt>>16)&0xff), + ((mVideoCodecContext->pix_fmt>>24)&0xff) + ); + if ( ! mConvertContext ) { + mConvertContext = sws_getContext( + input_frame->width, + input_frame->height, + (AVPixelFormat)input_frame->format, + width, height, + imagePixFormat, SWS_BICUBIC, NULL, + NULL, NULL); + if ( mConvertContext == NULL ) { + Error( "Unable to create conversion context for %s", mPath.c_str() ); + return -1; + } + } + + if ( sws_scale(mConvertContext, input_frame->data, input_frame->linesize, + 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) <= 0 ) { + Error("Unable to convert raw format %u to target format %u at frame %d codec %u ", + input_frame->format, + imagePixFormat, frameCount, + mVideoCodecContext->pix_fmt + ); return -1; } diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index a5a5550a9..626269f51 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -55,12 +55,14 @@ class FfmpegCamera : public Camera { AVFrame *mRawFrame; AVFrame *mFrame; _AVPIXELFORMAT imagePixFormat; + AVFrame *input_frame; // Use to point to mRawFrame or hwFrame; bool hwaccel; -#if HAVE_AVUTIL_HWCONTEXT_H AVFrame *hwFrame; +#if HAVE_AVUTIL_HWCONTEXT_H DecodeContext decode; #endif + AVBufferRef *hw_device_ctx = NULL; // Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero. int64_t audio_last_pts; diff --git a/zoneminder-config.cmake b/zoneminder-config.cmake index e088e68dc..46cf28d46 100644 --- a/zoneminder-config.cmake +++ b/zoneminder-config.cmake @@ -51,7 +51,7 @@ #cmakedefine HAVE_LIBAVUTIL 1 #cmakedefine HAVE_LIBAVUTIL_AVUTIL_H 1 #cmakedefine HAVE_LIBAVUTIL_MATHEMATICS_H 1 -#cmakedefine HAVE_LIBAVUTIL_HWCONTEXT_H 0 +#cmakedefine HAVE_LIBAVUTIL_HWCONTEXT_H 1 #cmakedefine HAVE_LIBSWSCALE 1 #cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1 #cmakedefine HAVE_LIBSWRESAMPLE 1