first commit

This commit is contained in:
陈赣
2026-06-03 12:43:14 +08:00
commit ba76cfae28
608 changed files with 120791 additions and 0 deletions

482
nodes/ffio/ff_des.cpp Normal file
View File

@@ -0,0 +1,482 @@
#ifdef VP_WITH_FFMPEG
#include <iostream>
#include <sstream>
#include "ff_des.h"
namespace vp_nodes {
ff_des::ff_des(int channel_index): m_channel_index(channel_index) {
}
ff_des::~ff_des() {
inner_close();
}
void ff_des::enmux_run() {
auto ret = 0;
while (m_enmux_running) {
ff_av_packet_ptr ff_packet = nullptr;
m_enmux_semaphore.wait();
{
std::lock_guard<std::mutex> g(m_enmux_packets_m);
ff_packet = m_enmux_packets_q.front();
m_enmux_packets_q.pop();
}
if (!ff_packet) {
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][enmux_run] get exit flag.",
get_channel_index()));
break;
} else {
/* set parameters for packets */
av_packet_rescale_ts(ff_packet.get(), m_enc_ctx->time_base, m_ofmt_ctx->streams[m_inner_stream_index]->time_base);
ff_packet->stream_index = m_inner_stream_index;
ret = av_interleaved_write_frame(m_ofmt_ctx, ff_packet.get());
}
if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][enmux_run] av_interleaved_write_frame failed. ret: %d",
get_channel_index(), ret));
break;
}
}
/* write the trailer for output stream */
ret = av_write_trailer(m_ofmt_ctx);
if (ret != 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][enmux_run] av_write_trailer failed. ret: %d",
get_channel_index(), ret));
}
/* set enmux running flag to false in case of abnormal exit */
m_enmux_running = false;
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][enmux_run] enmux thread exits.",
get_channel_index()));
}
void ff_des::encode_run() {
while (m_encode_running) {
ff_av_frame_ptr ff_frame;
m_encode_semaphore.wait();
{
std::lock_guard<std::mutex> g(m_encode_frames_m);
ff_frame = m_encode_frames_q.front();
m_encode_frames_q.pop();
}
auto ret = 0;
if (!ff_frame) {
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][encode_run] get exit flag.",
get_channel_index()));
break;
}
ff_frame->pts = m_enc_ctx->frame_number;
ret = avcodec_send_frame(m_enc_ctx, ff_frame.get());
if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][encode_run] avcodec_send_frame failed. ret: %d",
get_channel_index(), ret));
break;
}
while (ret >= 0) {
auto ff_packet = alloc_ff_av_packet();
ret = avcodec_receive_packet(m_enc_ctx, ff_packet.get());
if (ret == AVERROR(EAGAIN)) {
break;
} else if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][encode_run] avcodec_receive_packet failed. ret: %d",
get_channel_index(), ret));
ff_frame = nullptr;
break;
}
bool notify = true;
{
std::lock_guard<std::mutex> g(m_enmux_packets_m);
m_enmux_packets_q.push(ff_packet);
if (m_enmux_packets_q.size() > m_enmux_packets_q_max_size) {
m_enmux_packets_q.pop();
notify = false;
VP_WARN(vp_utils::string_format("[ffio/ff_des][%d][encode_run] exceed m_enmux_packets_q_max_size(%d), discard the front in queue.",
get_channel_index(), m_enmux_packets_q_max_size));
}
}
if (notify) {
m_enmux_semaphore.signal();
}
}
if (!ff_frame) {
break;
}
}
/* set encode running flag to false in case of abnormal exit */
m_encode_running = false;
{
// send exit flag to notify enmux thread
std::lock_guard<std::mutex> g(m_enmux_packets_m);
m_enmux_packets_q.push(nullptr);
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][encode_run] send exit flag to enmux thread.",
get_channel_index()));
}
m_enmux_semaphore.signal();
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][encode_run] encode thread exits.",
get_channel_index()));
}
bool ff_des::
open(const std::string& uri,
int width, int height, int fps, int bitrate, int max_b_frames,
const std::string& encoder_name,
AVPixelFormat sw_pix_fmt,
AVHWDeviceType hw_type,
AVPixelFormat hw_pix_fmt) {
inner_close();
auto uri_valid = false;
if (!uri_valid) {
auto uri_parts = vp_utils::string_split(uri, '.'); // file
if (std::find(m_supported_files.begin(), m_supported_files.end(), uri_parts[uri_parts.size() - 1]) != m_supported_files.end()) {
uri_valid = true;
m_live_stream = false;
}
}
if (!uri_valid) {
auto uri_parts = vp_utils::string_split(uri, ':'); // live stream
if (std::find(m_supported_protocols.begin(), m_supported_protocols.end(), uri_parts[0]) != m_supported_protocols.end()) {
uri_valid = true;
m_live_stream = true;
}
}
if (!uri_valid) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][open] uri invalid! uri MUST start with `rtsp/rtmp/..`(network streams) or end with `mp4/mkv/..`(file streams).",
get_channel_index()));
return false;
}
return inner_open(uri, width, height, fps, bitrate, max_b_frames, encoder_name, sw_pix_fmt, hw_type, hw_pix_fmt);
}
void ff_des::close() {
inner_close();
}
bool ff_des::is_opened() const {
return m_encode_running && m_enmux_running;
}
void ff_des::inner_close() {
/* set running flags to false */
m_encode_running = false;
m_enmux_running = false;
inner_exit_signal();
/* waiting for threads exist */
if (m_encode_th != nullptr && m_encode_th->joinable()) {
m_encode_th->join();
}
if (m_enmux_th != nullptr && m_enmux_th->joinable()) {
m_enmux_th->join();
}
/* free format & codec context */
if (m_ofmt_ctx) {
if (m_ofmt_ctx->pb) {
avio_closep(&m_ofmt_ctx->pb);
}
avformat_free_context(m_ofmt_ctx);
}
if (m_enc_ctx) {
avcodec_free_context(&m_enc_ctx);
}
/* clear queue */
{
// clear enmux_packets_q
std::lock_guard<std::mutex> g(m_enmux_packets_m);
m_enmux_packets_q = {};
m_enmux_semaphore.reset();
}
{
// clear encode_frames_q
std::lock_guard<std::mutex> g(m_encode_frames_m);
m_encode_frames_q = {};
m_encode_semaphore.reset();
}
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][inner_close] close successfully.",
get_channel_index()));
}
std::string ff_des::print_summary() {
return "";
}
bool ff_des::
inner_open(const std::string& uri,
int width, int height, int fps, int bitrate, int max_b_frames,
const std::string& encoder_name,
AVPixelFormat sw_pix_fmt,
AVHWDeviceType hw_type,
AVPixelFormat hw_pix_fmt) {
auto ret = 0;
/* allocate the output media context */
ret = avformat_alloc_output_context2(&m_ofmt_ctx, NULL, NULL, uri.c_str());
if (ret < 0) {
VP_WARN(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not deduce output format from uri, try to use FLV.",
get_channel_index()));
ret = avformat_alloc_output_context2(&m_ofmt_ctx, NULL, "flv", uri.c_str());
}
if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not deduce output format from uri(used FLV).",
get_channel_index()));
return false;
}
auto ofmt = m_ofmt_ctx->oformat;
/* find encoder for the output stream */
const AVCodec* enc = nullptr;
if (encoder_name.empty()) {
enc = avcodec_find_encoder(ofmt->video_codec); // get default encoder by AVCodecID
} else {
enc = avcodec_find_encoder_by_name(encoder_name.c_str()); // get by encoder name
}
if (!enc) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not find proper encoder for output stream.",
get_channel_index()));
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
/* allocate a codec context for the encoder */
m_enc_ctx = avcodec_alloc_context3(enc);
if (!m_enc_ctx) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not allocate context for encoder(%s).",
get_channel_index(), enc->name));
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
/* set parameters for encoder */
m_enc_ctx->framerate = (AVRational) {fps, 1};
m_enc_ctx->time_base = av_inv_q(m_enc_ctx->framerate);
m_enc_ctx->pix_fmt = sw_pix_fmt;
m_enc_ctx->width = width;
m_enc_ctx->height = height;
m_enc_ctx->bit_rate = bitrate * 1024;
m_enc_ctx->max_b_frames = max_b_frames;
/* check if need init hwaccels context for encoder */
if (hw_type != AVHWDeviceType::AV_HWDEVICE_TYPE_NONE) {
m_enc_ctx->pix_fmt = hw_pix_fmt;
if ((ret = hw_encoder_init(m_enc_ctx, hw_type, sw_pix_fmt)) < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] hw_encoder_init failed. ret: %d",
get_channel_index(), ret));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
}
/* open the encoder */
if ((ret = avcodec_open2(m_enc_ctx, enc, NULL)) < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not open encoder. ret: %d",
get_channel_index(), ret));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
/* create new stream for output */
auto out_stream = avformat_new_stream(m_ofmt_ctx, NULL);
if (!out_stream) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] could not create new stream for output uri.",
get_channel_index()));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
m_inner_stream_index = out_stream->index;
out_stream->time_base = m_enc_ctx->time_base;
ret = avcodec_parameters_from_context(out_stream->codecpar, m_enc_ctx);
if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] failed to copy codec parameters from encode context. ret: %d",
get_channel_index(), ret));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
if (!(m_ofmt_ctx->flags & AVFMT_NOFILE)) {
ret = avio_open(&m_ofmt_ctx->pb, uri.c_str(), AVIO_FLAG_WRITE);
m_ofmt_ctx->flush_packets = 1;
m_ofmt_ctx->flags |= AVFMT_FLAG_FLUSH_PACKETS;
if (ret < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] avio_open failed on output uri(%s). ret: %d",
get_channel_index(), uri.c_str(), ret));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
}
/* write the stream header */
if ((ret = avformat_write_header(m_ofmt_ctx, NULL)) < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][inner_open] avformat_write_header failed. ret: %d",
get_channel_index(), ret));
avcodec_free_context(&m_enc_ctx);
avformat_free_context(m_ofmt_ctx);
m_ofmt_ctx = NULL;
return false;
}
/* initialize video & ff_des properties */
m_hw_type_name = hw_type != AV_HWDEVICE_TYPE_NONE ? std::string(av_hwdevice_get_type_name(hw_type)) : "none";
m_encoder_name = encoder_name.empty() ? std::string(m_enc_ctx->codec->name) : encoder_name;
m_uri = uri;
m_fps = fps;
m_width = width;
m_height = height;
m_bitrate = bitrate;
m_codec_name = std::string(avcodec_get_name(out_stream->codecpar->codec_id));;
m_pixel_format = std::string(av_get_pix_fmt_name(static_cast<AVPixelFormat>(out_stream->codecpar->format)));
m_max_b_frames = max_b_frames;
/* collect summary notify caller */
auto summary = print_summary();
/* go! */
m_enmux_running = true;
m_encode_running = true;
m_enmux_th = std::make_shared<std::thread>(&ff_des::enmux_run, this);
m_encode_th = std::make_shared<std::thread>(&ff_des::encode_run, this);
VP_INFO(vp_utils::string_format("[ffio/ff_des][%d][inner_open] open successfully.",
get_channel_index()));
return true;
}
bool ff_des::write(const ff_av_frame_ptr& frame) {
if (!is_opened()) {
return false;
}
bool notify = true;
{
std::lock_guard<std::mutex> g(m_encode_frames_m);
m_encode_frames_q.push(frame);
if (m_encode_frames_q.size() > m_encode_frames_q_max_size) {
m_encode_frames_q.pop();
notify = false;
VP_WARN(vp_utils::string_format("[ffio/ff_des][%d][write] exceed m_encode_frames_q_max_size(%d), discard the front in queue.",
get_channel_index(), m_encode_frames_q_max_size));
}
}
if (notify) {
m_encode_semaphore.signal();
}
return true;
}
ff_des& ff_des::operator<<(const ff_av_frame_ptr& frame) {
write(frame);
return *this;
}
void ff_des::inner_exit_signal() {
{
// send exit flag to notify encode thread
std::lock_guard<std::mutex> g(m_encode_frames_m);
m_encode_frames_q.push(nullptr);
}
m_encode_semaphore.signal();
}
int ff_des::get_video_fps() const {
return m_fps;
}
int ff_des::get_video_width() const {
return m_width;
}
int ff_des::get_video_height() const {
return m_height;
}
long ff_des::get_video_bitrate() const {
return m_bitrate;
}
std::string ff_des::get_video_codec_name() const {
return m_codec_name;
}
std::string ff_des::get_video_pixel_format_name() const {
return m_pixel_format;
}
bool ff_des::is_live_stream() const {
return m_live_stream;
}
std::string ff_des::get_hw_type_name() const {
return m_hw_type_name;
}
std::string ff_des::get_encoder_name() const {
return m_encoder_name;
}
std::string ff_des::get_uri() const {
return m_uri;
}
int ff_des::get_channel_index() const {
return m_channel_index;
}
const AVCodecContext* ff_des::get_encode_ctx() const {
return m_enc_ctx;
}
int ff_des::
hw_encoder_init(AVCodecContext* enc_ctx,
const enum AVHWDeviceType hw_type,
const enum AVPixelFormat sw_pix_fmt) {
AVBufferRef* hw_device_ctx = nullptr;
AVBufferRef* hw_frames_ref = nullptr;
AVHWFramesContext* frames_ctx = nullptr;
int err = 0;
if ((err = av_hwdevice_ctx_create(&hw_device_ctx, hw_type,
NULL, NULL, 0)) < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][hw_encoder_init] failed to create specified(%s) HW device context for encoder.",
get_channel_index(), av_hwdevice_get_type_name(hw_type)));
return err;
}
if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][hw_encoder_init] failed to create specified(%s) HW frame context for encoder.",
get_channel_index(), av_hwdevice_get_type_name(hw_type)));
return err;
}
frames_ctx = (AVHWFramesContext* )(hw_frames_ref->data);
frames_ctx->format = enc_ctx->pix_fmt;
frames_ctx->sw_format = sw_pix_fmt;
frames_ctx->width = enc_ctx->width;
frames_ctx->height = enc_ctx->height;
frames_ctx->initial_pool_size = 20;
if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) {
VP_ERROR(vp_utils::string_format("[ffio/ff_des][%d][hw_encoder_init] failed to initialize specified(%s) HW frame context for encoder.",
get_channel_index(), av_hwdevice_get_type_name(hw_type)));
av_buffer_unref(&hw_frames_ref);
av_buffer_unref(&hw_device_ctx);
return err;
}
enc_ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref);
if (!enc_ctx->hw_frames_ctx)
err = AVERROR(ENOMEM);
av_buffer_unref(&hw_frames_ref);
av_buffer_unref(&hw_device_ctx);
return err;
}
}
#endif