encoder: Replace ffmpeg::avframe_queue with integrated implementation

This commit is contained in:
Michael Fabian 'Xaymar' Dirks
2019-10-03 19:21:54 +02:00
parent 4836f9dda6
commit d0dc4be985
3 changed files with 162 additions and 100 deletions
+141 -90
View File
@@ -612,11 +612,6 @@ void obsffmpeg::encoder::initialize_sw(obs_data_t* settings)
<< (_swscale.is_source_full_range() ? "full" : "partial") << " range.";
throw std::runtime_error(sstr.str());
}
// Create Frame queue
_frame_queue.set_pixel_format(_context->pix_fmt);
_frame_queue.set_resolution(_context->width, _context->height);
_frame_queue.precache(2);
}
}
@@ -655,6 +650,61 @@ void obsffmpeg::encoder::initialize_hw(obs_data_t* settings)
throw std::runtime_error("Failed to initialize AVHWFramesContext.");
}
void obsffmpeg::encoder::push_free_frame(std::shared_ptr<AVFrame> frame)
{
auto now = std::chrono::high_resolution_clock::now();
if (_free_frames.size() > 0) {
if ((now - _free_frames_last_used) < std::chrono::seconds(1)) {
_free_frames.push(frame);
}
} else {
_free_frames.push(frame);
_free_frames_last_used = std::chrono::high_resolution_clock::now();
}
}
std::shared_ptr<AVFrame> obsffmpeg::encoder::pop_free_frame()
{
std::shared_ptr<AVFrame> frame;
if (_free_frames.size() > 0) {
// Re-use existing frames first.
frame = _free_frames.top();
_free_frames.pop();
} else {
if (_hwinst) {
frame = _hwinst->allocate_frame(_context->hw_frames_ctx);
} else {
frame = std::shared_ptr<AVFrame>(av_frame_alloc(), [](AVFrame* frame) {
av_frame_unref(frame);
av_frame_free(&frame);
});
frame->width = _context->width;
frame->height = _context->height;
frame->format = _context->pix_fmt;
int res = av_frame_get_buffer(frame.get(), 32);
if (res < 0) {
throw std::runtime_error(ffmpeg::tools::get_error_description(res));
}
}
}
return frame;
}
void obsffmpeg::encoder::push_used_frame(std::shared_ptr<AVFrame> frame)
{
_used_frames.push(frame);
}
std::shared_ptr<AVFrame> obsffmpeg::encoder::pop_used_frame()
{
auto frame = _used_frames.front();
_used_frames.pop();
return frame;
}
obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode)
: _self(encoder), _lag_in_frames(0), _count_send_frames(0), _have_first_frame(false)
{
@@ -700,14 +750,27 @@ obsffmpeg::encoder::encoder(obs_data_t* settings, obs_encoder_t* encoder, bool i
// Log Encoder info
PLOG_INFO("[%s] Initializing...", _codec->name);
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(),
_swscale.get_source_height(), ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()),
_swscale.is_source_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", _codec->name, _swscale.get_target_width(),
_swscale.get_target_height(), ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()),
_swscale.is_target_full_range() ? "Full" : "Partial");
if (_hwinst) {
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _context->width, _context->height,
ffmpeg::tools::get_pixel_format_name(_context->sw_pix_fmt),
ffmpeg::tools::get_color_space_name(_context->colorspace),
_swscale.is_source_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", _codec->name, _context->width, _context->height,
ffmpeg::tools::get_pixel_format_name(_context->sw_pix_fmt),
ffmpeg::tools::get_color_space_name(_context->colorspace),
_swscale.is_target_full_range() ? "Full" : "Partial");
} else {
PLOG_INFO("[%s] Video Input: %ldx%ld %s %s %s", _codec->name, _swscale.get_source_width(),
_swscale.get_source_height(),
ffmpeg::tools::get_pixel_format_name(_swscale.get_source_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_source_colorspace()),
_swscale.is_source_full_range() ? "Full" : "Partial");
PLOG_INFO("[%s] Video Output: %ldx%ld %s %s %s", _codec->name, _swscale.get_target_width(),
_swscale.get_target_height(),
ffmpeg::tools::get_pixel_format_name(_swscale.get_target_format()),
ffmpeg::tools::get_color_space_name(_swscale.get_target_colorspace()),
_swscale.is_target_full_range() ? "Full" : "Partial");
}
PLOG_INFO("[%s] Framerate: %ld/%ld (%f FPS)", _codec->name, _context->time_base.den, _context->time_base.num,
static_cast<double_t>(_context->time_base.den) / static_cast<double_t>(_context->time_base.num));
PLOG_INFO("[%s] Custom Settings: %s", _codec->name, obs_data_get_string(settings, ST_FFMPEG_CUSTOMSETTINGS));
@@ -744,8 +807,6 @@ obsffmpeg::encoder::~encoder()
av_packet_unref(&_current_packet);
_frame_queue.clear();
_frame_queue_used.clear();
_swscale.finalize();
}
@@ -895,8 +956,9 @@ static inline void copy_data(encoder_frame* frame, AVFrame* vframe)
bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* packet, bool* received_packet)
{
std::shared_ptr<AVFrame> vframe = pop_free_frame(); // Retrieve an empty frame.
// Convert frame.
std::shared_ptr<AVFrame> vframe = _frame_queue.pop(); // Retrieve an empty frame.
{
#ifdef _DEBUG
ScopeProfiler profile("convert");
@@ -929,10 +991,6 @@ bool obsffmpeg::encoder::video_encode(encoder_frame* frame, encoder_packet* pack
if (!encode_avframe(vframe, packet, received_packet))
return false;
if (vframe != nullptr) {
_frame_queue.push(vframe);
}
return true;
}
@@ -945,12 +1003,8 @@ bool obsffmpeg::encoder::video_encode_texture(uint32_t handle, int64_t pts, uint
return false;
}
std::shared_ptr<AVFrame> frame{av_frame_alloc(), [](void* ptr) {
av_frame_unref(reinterpret_cast<AVFrame*>(ptr));
av_frame_free(reinterpret_cast<AVFrame**>(&ptr));
}};
std::shared_ptr<AVFrame> vframe = _hwinst->avframe_from_obs(_context->hw_frames_ctx, handle, lock_key, next_lock_key);
std::shared_ptr<AVFrame> vframe = pop_free_frame();
_hwinst->copy_from_obs(_context->hw_frames_ctx, handle, lock_key, next_lock_key, vframe);
vframe->color_range = _context->color_range;
vframe->colorspace = _context->colorspace;
@@ -971,82 +1025,76 @@ int obsffmpeg::encoder::receive_packet(bool* received_packet, struct encoder_pac
av_packet_unref(&_current_packet);
int res = avcodec_receive_packet(_context, &_current_packet);
if (res == 0) {
if (!_have_first_frame) {
if (_codec->id == AV_CODEC_ID_H264) {
uint8_t* tmp_packet;
uint8_t* tmp_header;
uint8_t* tmp_sei;
size_t sz_packet, sz_header, sz_sei;
obs_extract_avc_headers(_current_packet.data, _current_packet.size, &tmp_packet,
&sz_packet, &tmp_header, &sz_header, &tmp_sei, &sz_sei);
if (sz_header) {
_extra_data.resize(sz_header);
std::memcpy(_extra_data.data(), tmp_header, sz_header);
}
if (sz_sei) {
_sei_data.resize(sz_sei);
std::memcpy(_sei_data.data(), tmp_sei, sz_sei);
}
// Not required, we only need the Extra Data and SEI Data anyway.
//std::memcpy(_current_packet.data, tmp_packet, sz_packet);
//_current_packet.size = static_cast<int>(sz_packet);
bfree(tmp_packet);
bfree(tmp_header);
bfree(tmp_sei);
} else if (_codec->id == AV_CODEC_ID_HEVC) {
obsffmpeg::codecs::hevc::extract_header_sei(_current_packet.data, _current_packet.size,
_extra_data, _sei_data);
} else if (_context->extradata != nullptr) {
_extra_data.resize(_context->extradata_size);
std::memcpy(_extra_data.data(), _context->extradata, _context->extradata_size);
}
_have_first_frame = true;
}
// Allow Handler Post-Processing
if (_handler)
_handler->process_avpacket(_current_packet, _codec, _context);
packet->type = OBS_ENCODER_VIDEO;
packet->pts = _current_packet.pts;
packet->dts = _current_packet.dts;
packet->data = _current_packet.data;
packet->size = _current_packet.size;
packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY);
packet->drop_priority = packet->keyframe ? 0 : 1;
*received_packet = true;
{
std::shared_ptr<AVFrame> uframe = _frame_queue_used.pop_only();
_frame_queue.push(uframe);
}
if (res != 0) {
return res;
}
if (!_have_first_frame) {
if (_codec->id == AV_CODEC_ID_H264) {
uint8_t* tmp_packet;
uint8_t* tmp_header;
uint8_t* tmp_sei;
size_t sz_packet, sz_header, sz_sei;
obs_extract_avc_headers(_current_packet.data, _current_packet.size, &tmp_packet, &sz_packet,
&tmp_header, &sz_header, &tmp_sei, &sz_sei);
if (sz_header) {
_extra_data.resize(sz_header);
std::memcpy(_extra_data.data(), tmp_header, sz_header);
}
if (sz_sei) {
_sei_data.resize(sz_sei);
std::memcpy(_sei_data.data(), tmp_sei, sz_sei);
}
// Not required, we only need the Extra Data and SEI Data anyway.
//std::memcpy(_current_packet.data, tmp_packet, sz_packet);
//_current_packet.size = static_cast<int>(sz_packet);
bfree(tmp_packet);
bfree(tmp_header);
bfree(tmp_sei);
} else if (_codec->id == AV_CODEC_ID_HEVC) {
obsffmpeg::codecs::hevc::extract_header_sei(_current_packet.data, _current_packet.size,
_extra_data, _sei_data);
} else if (_context->extradata != nullptr) {
_extra_data.resize(_context->extradata_size);
std::memcpy(_extra_data.data(), _context->extradata, _context->extradata_size);
}
_have_first_frame = true;
}
// Allow Handler Post-Processing
if (_handler)
_handler->process_avpacket(_current_packet, _codec, _context);
packet->type = OBS_ENCODER_VIDEO;
packet->pts = _current_packet.pts;
packet->dts = _current_packet.dts;
packet->data = _current_packet.data;
packet->size = _current_packet.size;
packet->keyframe = !!(_current_packet.flags & AV_PKT_FLAG_KEY);
packet->drop_priority = packet->keyframe ? 0 : 1;
*received_packet = true;
push_free_frame(pop_used_frame());
return res;
}
int obsffmpeg::encoder::send_frame(std::shared_ptr<AVFrame> const frame)
{
int res = avcodec_send_frame(_context, frame.get());
switch (res) {
case 0:
if (!_hwapi)
_frame_queue_used.push(frame);
_count_send_frames++;
case AVERROR(EAGAIN):
case AVERROR(EOF):
break;
if (res == 0) {
push_used_frame(frame);
}
return res;
}
bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame>& frame, encoder_packet* packet, bool* received_packet)
bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame> frame, encoder_packet* packet, bool* received_packet)
{
#ifdef _DEBUG
ScopeProfiler profile("loop");
@@ -1127,5 +1175,8 @@ bool obsffmpeg::encoder::encode_avframe(std::shared_ptr<AVFrame>& frame, encoder
}
}
if (!sent_frame)
push_free_frame(frame);
return true;
}
+18 -8
View File
@@ -23,6 +23,8 @@
#include <condition_variable>
#include <mutex>
#include <queue>
#include <stack>
#include <thread>
#include <vector>
#include "ffmpeg/avframe-queue.hpp"
@@ -81,19 +83,16 @@ namespace obsffmpeg {
obs_encoder_t* _self;
encoder_factory* _factory;
const AVCodec* _codec;
AVCodecContext* _context;
AVHWFramesContext* _hwcontext;
const AVCodec* _codec;
AVCodecContext* _context;
std::shared_ptr<obsffmpeg::ui::handler> _handler;
std::shared_ptr<obsffmpeg::hwapi::base> _hwapi;
std::shared_ptr<obsffmpeg::hwapi::instance> _hwinst;
ffmpeg::avframe_queue _frame_queue;
ffmpeg::avframe_queue _frame_queue_used;
ffmpeg::swscale _swscale;
AVPacket _current_packet;
ffmpeg::swscale _swscale;
AVPacket _current_packet;
int64_t _lag_in_frames;
int64_t _count_send_frames;
@@ -103,9 +102,20 @@ namespace obsffmpeg {
std::vector<uint8_t> _extra_data;
std::vector<uint8_t> _sei_data;
// Frame Stack and Queue
std::stack<std::shared_ptr<AVFrame>> _free_frames;
std::queue<std::shared_ptr<AVFrame>> _used_frames;
std::chrono::high_resolution_clock::time_point _free_frames_last_used;
void initialize_sw(obs_data_t* settings);
void initialize_hw(obs_data_t* settings);
void push_free_frame(std::shared_ptr<AVFrame> frame);
std::shared_ptr<AVFrame> pop_free_frame();
void push_used_frame(std::shared_ptr<AVFrame> frame);
std::shared_ptr<AVFrame> pop_used_frame();
public:
encoder(obs_data_t* settings, obs_encoder_t* encoder, bool is_texture_encode = false);
virtual ~encoder();
@@ -139,7 +149,7 @@ namespace obsffmpeg {
int send_frame(std::shared_ptr<AVFrame> frame);
bool encode_avframe(std::shared_ptr<AVFrame>& frame, struct encoder_packet* packet,
bool encode_avframe(std::shared_ptr<AVFrame> frame, struct encoder_packet* packet,
bool* received_packet);
};
} // namespace obsffmpeg
+3 -2
View File
@@ -23,9 +23,9 @@
#include <cinttypes>
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <memory>
extern "C" {
#pragma warning(push)
@@ -60,7 +60,8 @@ namespace obsffmpeg {
virtual void copy_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key,
uint64_t* next_lock_key, std::shared_ptr<AVFrame> frame) = 0;
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle, uint64_t lock_key,
virtual std::shared_ptr<AVFrame> avframe_from_obs(AVBufferRef* frames, uint32_t handle,
uint64_t lock_key,
uint64_t* next_lock_key) = 0;
};
} // namespace hwapi