@@ -44,13 +44,11 @@ static AVFrame * allocPicture(enum AVPixelFormat pix_fmt, int width, int height)
4444 int ret = av_frame_get_buffer (picture, 0 );
4545 if (ret < 0 ) {
4646 fprintf (stderr, " Could not allocate frame data.\n " );
47- exit ( 1 ) ;
47+ return nullptr ;
4848 }
4949 return picture;
5050}
5151
52- static const int64_t NS_TO_S = 1000000000 ;
53-
5452} // namespace
5553
5654namespace broll
@@ -60,6 +58,7 @@ FrameDecoder::FrameDecoder(
6058 AVCodecID codec_id,
6159 AVPixelFormat target_fmt,
6260 double scale,
61+ AVHWDeviceType hw_device_type,
6362 bool dbg_print)
6463: targetPixFmt_(target_fmt),
6564 scale_ (scale),
@@ -91,6 +90,61 @@ FrameDecoder::FrameDecoder(
9190 assert (false && " failed to copy codec params to codec context" );
9291 }
9392
93+ if (hw_device_type != AV_HWDEVICE_TYPE_NONE) {
94+ // Read HW configs 0->N until finding the one we're looking for or it returns nullptr
95+ for (size_t i = 0 ;; i++) {
96+ const AVCodecHWConfig * config = avcodec_get_hw_config (codec_, i);
97+ if (!config) {
98+ BROLL_LOG_ERROR (
99+ " Decoder %s does not support device type %s.\n " ,
100+ codec_->name , av_hwdevice_get_type_name (hw_device_type));
101+ throw std::runtime_error (" Unsupported hardware device type" );
102+ }
103+ if (
104+ config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
105+ config->device_type == hw_device_type)
106+ {
107+ hwPixFmt_ = config->pix_fmt ;
108+ break ;
109+ }
110+ }
111+
112+ BROLL_LOG_INFO (
113+ " Hardware decoding enabled. Pixel format \" %s\" " ,
114+ av_get_pix_fmt_name (hwPixFmt_));
115+ codecCtx_->get_format = FrameDecoder::getHardwarePixelFormat;
116+ int err = av_hwdevice_ctx_create (&hwDeviceCtx_, hw_device_type, nullptr , nullptr , 0 );
117+ if (err < 0 ) {
118+ assert (false && " Failed to initialize hardware decoder." );
119+ }
120+ codecCtx_->hw_device_ctx = av_buffer_ref (hwDeviceCtx_);
121+ {
122+ // Check what software-side pixel formats the hardware transfer supports.
123+ // Print all for visibility, but select just the first one.
124+ // This should probably be configurable, to let user target best pre-sws fmt.
125+ AVHWFramesConstraints * hw_frames_const = av_hwdevice_get_hwframe_constraints (
126+ hwDeviceCtx_, nullptr );
127+ assert (hw_frames_const && " Couldn't retrieve hardware device frame constraints." );
128+ hwSoftwarePixFmt_ = AV_PIX_FMT_NONE;
129+ for (AVPixelFormat * p = hw_frames_const->valid_sw_formats ; *p != AV_PIX_FMT_NONE; p++) {
130+ if (sws_isSupportedInput (*p)) {
131+ BROLL_LOG_INFO (
132+ " Supported hardware-to-software pixel format: \" %s\" " ,
133+ av_get_pix_fmt_name (*p));
134+ if (hwSoftwarePixFmt_ == AV_PIX_FMT_NONE) {
135+ hwSoftwarePixFmt_ = *p;
136+ }
137+ }
138+ }
139+ av_hwframe_constraints_free (&hw_frames_const);
140+ BROLL_LOG_INFO (" Selected hw/sw pixel format: \" %s\" " , av_get_pix_fmt_name (hwSoftwarePixFmt_));
141+ }
142+
143+ BROLL_LOG_INFO (" Succeeded to init hardware decoder" );
144+ hwFrame_ = av_frame_alloc ();
145+ assert (hwFrame_ && " failed to alloc hardwareFrame" );
146+ }
147+
94148 if (avcodec_open2 (codecCtx_, codec_, nullptr ) < 0 ) {
95149 assert (false && " failed to open codec through avcodec_open2" );
96150 }
@@ -122,7 +176,13 @@ bool FrameDecoder::decodeFrame(const AVPacket & packet_in, AVFrame & frame_out)
122176 BROLL_LOG_ERROR (" avcodec_send_packet failed: %s" , errStr);
123177 return false ;
124178 }
125- const int recv_frame_resp = avcodec_receive_frame (codecCtx_, &frame_out);
179+
180+ int recv_frame_resp;
181+ if (hwFrame_) {
182+ recv_frame_resp = avcodec_receive_frame (codecCtx_, hwFrame_);
183+ } else {
184+ recv_frame_resp = avcodec_receive_frame (codecCtx_, &frame_out);
185+ }
126186 if (recv_frame_resp == AVERROR (EAGAIN)) {
127187 BROLL_LOG_DEBUG (" avcodec_receive_frame returned EAGAIN" );
128188 return false ;
@@ -137,6 +197,20 @@ bool FrameDecoder::decodeFrame(const AVPacket & packet_in, AVFrame & frame_out)
137197 return false ;
138198 }
139199
200+ if (hwFrame_) {
201+ if (hwFrame_->format != hwPixFmt_) {
202+ BROLL_LOG_ERROR (" Received hardware frame was not in expected pixel format." );
203+ return false ;
204+ }
205+ // Move the frame from hardware device memory to CPU memory.
206+ // Setting the pixel format on frame_out prompts the transfer to convert to a specific format.
207+ frame_out.format = hwSoftwarePixFmt_;
208+ if (av_hwframe_transfer_data (&frame_out, hwFrame_, 0 ) < 0 ) {
209+ BROLL_LOG_ERROR (" Error transferring the data to system memory" );
210+ return false ;
211+ }
212+ }
213+
140214#if LIBAVCODEC_VERSION_MAJOR >= 60
141215 int64_t frame_num = codecCtx_->frame_num ;
142216 bool is_key_frame = frame_out.flags & AV_FRAME_FLAG_KEY;
@@ -231,13 +305,18 @@ bool FrameDecoder::decode(const AVPacket & in, sensor_msgs::msg::Image & out)
231305 BROLL_LOG_INFO (
232306 " Frame Decoder initialized: resolution in %d x %d, resolution out %d x %d" ,
233307 width, height, scaled_width_, scaled_height_);
234- BROLL_LOG_INFO (" \t Codec %s ID %d" , codec_->name , codec_->id );
308+ BROLL_LOG_INFO (" \t Codec %d ('%s')" , codec_->id , codec_->name );
309+ BROLL_LOG_INFO (
310+ " \t Codec pixfmt '%s', decoded pixfmt '%s'" ,
311+ av_get_pix_fmt_name (codecCtx_->pix_fmt ),
312+ av_get_pix_fmt_name (static_cast <AVPixelFormat>(decodedFrame_->format )));
313+ BROLL_LOG_INFO (" \t Target pixfmt '%s'" , av_get_pix_fmt_name (targetPixFmt_));
235314
236315 convertedFrame_ = allocPicture (targetPixFmt_, scaled_width_, scaled_height_);
237316 assert (convertedFrame_ && " failed to alloc convertedFrame" );
238317
239318 bool set_extended_color_range = false ;
240- AVPixelFormat sws_pix_fmt = codecCtx_-> pix_fmt ;
319+ AVPixelFormat sws_pix_fmt = static_cast <AVPixelFormat>(decodedFrame_-> format ) ;
241320 switch (sws_pix_fmt) {
242321 case AV_PIX_FMT_YUVJ420P:
243322 sws_pix_fmt = AV_PIX_FMT_YUV420P;
@@ -299,4 +378,19 @@ bool FrameDecoder::decode(
299378 return res;
300379}
301380
381+ AVPixelFormat FrameDecoder::getHardwarePixelFormat (
382+ AVCodecContext * ctx, const AVPixelFormat * pix_fmts)
383+ {
384+ const AVPixelFormat * p;
385+ const AVPixelFormat hwPixFmt = static_cast <FrameDecoder *>(ctx->opaque )->hwPixFmt_ ;
386+ for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
387+ if (*p == hwPixFmt) {
388+ return *p;
389+ }
390+ }
391+ BROLL_LOG_ERROR (" Failed to get HW surface format." );
392+ return AV_PIX_FMT_NONE;
393+ }
394+
395+
302396} // namespace broll
0 commit comments