This commit is contained in:
cxh 2025-10-17 15:58:54 +08:00
parent f2c8cbfd9a
commit 23718bb584

View File

@ -109,7 +109,9 @@ void RTMPManager::stream_loop(Camera cam, StreamContext *ctx)
while (ctx->running) while (ctx->running)
{ {
if (!device_exists(cam.device)) // 检查设备是否存在
struct stat st;
if (stat(cam.device.c_str(), &st) != 0)
{ {
ctx->status.running = false; ctx->status.running = false;
ctx->status.last_error = "Device not found: " + cam.device; ctx->status.last_error = "Device not found: " + cam.device;
@ -118,101 +120,76 @@ void RTMPManager::stream_loop(Camera cam, StreamContext *ctx)
continue; continue;
} }
// 创建 pipeline
GstElement *pipeline = create_pipeline(cam); GstElement *pipeline = create_pipeline(cam);
if (!pipeline) if (!pipeline)
{ {
ctx->status.running = false; ctx->status.running = false;
ctx->status.last_error = "Failed to create pipeline"; ctx->status.last_error = "Pipeline creation failed";
LOG_ERROR("[RTMP] " + key + " - " + ctx->status.last_error);
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(3));
continue; continue;
} }
GstBus *bus = gst_element_get_bus(pipeline); GstBus *bus = gst_element_get_bus(pipeline);
gst_element_set_state(pipeline, GST_STATE_PLAYING); gst_element_set_state(pipeline, GST_STATE_PLAYING);
LOG_INFO("[RTMP] Starting stream: " + key);
// 等 pipeline 真正进入 PLAYING 状态
GstStateChangeReturn ret = gst_element_get_state(pipeline, nullptr, nullptr, 3 * GST_SECOND);
if (ret != GST_STATE_CHANGE_SUCCESS)
{
ctx->status.running = false;
ctx->status.last_error = "Pipeline failed to reach PLAYING";
LOG_ERROR("[RTMP] " + key + " failed to start (maybe no signal)");
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(bus);
gst_object_unref(pipeline);
std::this_thread::sleep_for(std::chrono::seconds(3));
continue;
}
// 等首帧
bool first_frame = false;
auto start_time = std::chrono::steady_clock::now();
while (!first_frame && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(3))
{
GstMessage *msg = gst_bus_timed_pop_filtered(bus, 300 * GST_MSECOND,
(GstMessageType)(GST_MESSAGE_ELEMENT | GST_MESSAGE_ERROR));
if (!msg) continue;
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT)
{
const GstStructure *s = gst_message_get_structure(msg);
if (s && g_str_has_prefix(gst_structure_get_name(s), "fpsdisplaysink"))
{
first_frame = true;
break;
}
}
else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR)
{
first_frame = false;
break;
}
gst_message_unref(msg);
}
if (!first_frame)
{
ctx->status.running = false;
ctx->status.last_error = "No frames detected";
LOG_ERROR("[RTMP] " + key + " - No frames detected (no video signal)");
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(bus);
gst_object_unref(pipeline);
std::this_thread::sleep_for(std::chrono::seconds(3));
continue;
}
// 进入正常推流状态
ctx->status.running = true; ctx->status.running = true;
ctx->status.last_error.clear(); ctx->status.last_error.clear();
LOG_INFO("[RTMP] Started stream for " + key);
bool got_frame = false;
bool need_restart = false; bool need_restart = false;
auto start_time = std::chrono::steady_clock::now();
while (ctx->running) while (ctx->running)
{ {
GstMessage *msg = gst_bus_timed_pop_filtered(bus, 500 * GST_MSECOND, GstMessage *msg = gst_bus_timed_pop_filtered(
(GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); bus, 200 * GST_MSECOND, (GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_ELEMENT));
// 检查帧超时3 秒内没有检测到帧
auto elapsed =
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count();
if (!got_frame && elapsed > 3)
{
ctx->status.running = false;
ctx->status.last_error = "No frames detected (no video signal)";
LOG_ERROR("[RTMP] " + key + " - " + ctx->status.last_error);
need_restart = true;
break;
}
if (!msg) continue; if (!msg) continue;
switch (GST_MESSAGE_TYPE(msg)) switch (GST_MESSAGE_TYPE(msg))
{ {
case GST_MESSAGE_ELEMENT:
// 检测 fpsdisplaysink 输出帧
if (gst_message_has_name(msg, "fpsprobe"))
{
got_frame = true;
}
break;
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
{ {
GError *err = nullptr; GError *err = nullptr;
gst_message_parse_error(msg, &err, nullptr); gst_message_parse_error(msg, &err, nullptr);
ctx->status.running = false; ctx->status.running = false;
ctx->status.last_error = err ? err->message : "Unknown GStreamer error"; ctx->status.last_error = err ? std::string(err->message) : "GStreamer error";
LOG_ERROR("[RTMP] " + key + " stream error: " + ctx->status.last_error); LOG_ERROR("[RTMP] " + key + " stream error: " + ctx->status.last_error);
if (err) g_error_free(err); if (err) g_error_free(err);
need_restart = true; need_restart = true;
break; break;
} }
case GST_MESSAGE_EOS: case GST_MESSAGE_EOS:
ctx->status.running = false; ctx->status.running = false;
ctx->status.last_error = "End of stream"; ctx->status.last_error = "End of stream (EOS)";
LOG_WARN("[RTMP] " + key + " reached EOS");
need_restart = true; need_restart = true;
break; break;
default: default:
break; break;
} }
@ -221,8 +198,9 @@ void RTMPManager::stream_loop(Camera cam, StreamContext *ctx)
if (need_restart) break; if (need_restart) break;
} }
// 清理资源
gst_element_set_state(pipeline, GST_STATE_NULL); gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(bus); if (bus) gst_object_unref(bus);
gst_object_unref(pipeline); gst_object_unref(pipeline);
if (ctx->running) if (ctx->running)
@ -233,6 +211,7 @@ void RTMPManager::stream_loop(Camera cam, StreamContext *ctx)
} }
ctx->status.running = false; ctx->status.running = false;
LOG_INFO("[RTMP] Stream thread exited for " + key);
} }
// ========== 启停与状态 ========== // ========== 启停与状态 ==========
@ -251,20 +230,34 @@ void RTMPManager::start_all()
} }
} }
void RTMPManager::stop_all() void RTMPManager::start_all()
{ {
LOG_INFO("[RTMP] Starting all record streams...");
std::lock_guard<std::mutex> lock(streams_mutex); std::lock_guard<std::mutex> lock(streams_mutex);
for (auto &kv : streams) kv.second->running.store(false);
for (auto &kv : streams)
if (kv.second->thread.joinable()) kv.second->thread.join();
streams.clear();
}
bool RTMPManager::is_streaming(const std::string &cam_name) int delay_ms = 0;
{ for (auto &cam : g_app_config.cameras)
std::lock_guard<std::mutex> lock(streams_mutex); {
auto it = streams.find(make_key(cam_name)); auto key = make_key(cam.name);
return (it != streams.end() && it->second->status.running); if (streams.find(key) != streams.end())
{
LOG_INFO("[RTMP] Stream already running: " + key);
continue;
}
auto ctx = std::make_unique<StreamContext>();
ctx->running.store(true);
ctx->thread = std::thread(
[cam, ptr = ctx.get(), delay_ms]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
stream_loop(cam, ptr);
});
streams.emplace(key, std::move(ctx));
delay_ms += 200; // 每路错开 200ms
}
} }
std::string RTMPManager::get_stream_url(const std::string &cam_name) std::string RTMPManager::get_stream_url(const std::string &cam_name)