Skip to content

Commit 7cca01c

Browse files
authored
Hardware decoding of frames (#13)
Allow user to initialize FrameDecoder with a HW device type selected. Performs decoding (but not pixel format conversion and scaling) on the hardware device. Signed-off-by: Emerson Knapp <emerson.b.knapp@gmail.com>
1 parent 633a889 commit 7cca01c

File tree

4 files changed

+126
-6
lines changed

4 files changed

+126
-6
lines changed

broll/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ if(BUILD_TESTING)
7878
ament_lint_auto_find_test_dependencies()
7979
endif()
8080

81+
ament_export_dependencies(
82+
sensor_msgs
83+
)
8184
ament_export_include_directories(
8285
"include/${PROJECT_NAME}"
8386
)

broll/include/broll/frame_decoder.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ class FrameDecoder
3838
/// @param target_fmt Pixel format to convert to, if necessary.
3939
/// AV_PIX_FMT_NONE guarantees no conversion.
4040
/// @param scale Scale applied to image dimensions (by multiplication, 0.5 is half size)
41+
/// @param hw_device_type Attempt to use hardware decoding device of this type.
42+
/// AV_HWDEVICE_TYPE_NONE uses software decoding only.
4143
/// @param dbg_print Print info about each decoded frame to stdout
44+
/// @throws std::invalid_argument if hw_device_type is not supported
4245
FrameDecoder(
4346
AVCodecID codec_id,
4447
AVPixelFormat target_fmt = AV_PIX_FMT_NONE,
4548
double scale = 1.0f,
49+
AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE,
4650
bool dbg_print = false);
4751
virtual ~FrameDecoder();
4852

@@ -79,6 +83,11 @@ class FrameDecoder
7983
/// available in HEVC decoder internals, so we would need to use libx265 directly to detect.
8084
void startSkippingPFrames();
8185

86+
/// @brief Function to help AVCodecContext pick a pixel format that matches hwPixFmt_.
87+
///
88+
/// Only used if hardware decoding is enabled.
89+
static AVPixelFormat getHardwarePixelFormat(AVCodecContext * ctx, const AVPixelFormat * pix_fmts);
90+
8291
AVPacket * packet_ = nullptr;
8392
const AVCodec * codec_ = nullptr;
8493
AVCodecContext * codecCtx_ = nullptr;
@@ -87,6 +96,17 @@ class FrameDecoder
8796
AVFrame * decodedFrame_ = nullptr;
8897
AVFrame * convertedFrame_ = nullptr;
8998

99+
// Hardware decoding extras
100+
// Pixel format reported for an on-hardware DMA frame, such as "cuda", which isn't
101+
// a real pixel format but how the frame reports that it isn't in CPU memory.
102+
AVPixelFormat hwPixFmt_ = AV_PIX_FMT_NONE;
103+
// Pixel format to convert to when transferring the frame from hardware into CPU memory.
104+
// Hardware devices offer several options, but not all that we might want so this
105+
// is the pre-conversion format before sws.
106+
AVPixelFormat hwSoftwarePixFmt_ = AV_PIX_FMT_NONE;
107+
AVBufferRef * hwDeviceCtx_ = nullptr;
108+
AVFrame * hwFrame_ = nullptr;
109+
90110
float scale_ = 1.0f;
91111
uint scaled_width_ = 0u;
92112
uint scaled_height_ = 0u;

broll/src/frame_decoder.cpp

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5654
namespace 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("\tCodec %s ID %d", codec_->name, codec_->id);
308+
BROLL_LOG_INFO("\tCodec %d ('%s')", codec_->id, codec_->name);
309+
BROLL_LOG_INFO(
310+
"\tCodec 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("\tTarget 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

rosbag2_storage_broll/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ if(BUILD_TESTING)
7878
ament_lint_auto_find_test_dependencies()
7979
endif()
8080

81+
ament_export_dependencies(
82+
rosbag2_transport
83+
)
8184
ament_export_libraries(
8285
bag_utils
8386
)

0 commit comments

Comments
 (0)