dev: reformat logger

This commit is contained in:
wwhai
2025-02-16 19:00:50 +08:00
parent b21df21359
commit c570005cb6
19 changed files with 336 additions and 252 deletions
+46
View File
@@ -0,0 +1,46 @@
// Copyright (C) 2025 wwhai
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#ifndef LOGGER_H
#define LOGGER_H
#include <stdio.h>
#include <time.h>
// 日志等级枚举
typedef enum
{
LOG_TRACE,
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR,
LOG_FATAL
} LogLevel;
// 设置日志等级
void set_log_level(LogLevel level);
// 日志记录函数,增加文件和行号参数
void log_message(LogLevel level, const char *file, int line, const char *format, ...);
// 定义宏,方便调用时自动传入文件和行号
#define log_trace(format, ...) log_message(LOG_TRACE, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_debug(format, ...) log_message(LOG_DEBUG, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_info(format, ...) log_message(LOG_INFO, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_warn(format, ...) log_message(LOG_WARN, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_error(format, ...) log_message(LOG_ERROR, __FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_fatal(format, ...) log_message(LOG_FATAL, __FILE__, __LINE__, format, ##__VA_ARGS__)
#endif // LOGGER_H
-84
View File
@@ -55,87 +55,3 @@ make clean
```sh
docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 ossrs/srs:5
```
## YoloV8类型
```
0 => person
1 => bicycle
2 => car
3 => motorcycle
4 => airplane
5 => bus
6 => train
7 => truck
8 => boat
9 => traffic light
10 => fire hydrant
11 => stop sign
12 => parking meter
13 => bench
14 => bird
15 => cat
16 => dog
17 => horse
18 => sheep
19 => cow
20 => elephant
21 => bear
22 => zebra
23 => giraffe
24 => backpack
25 => umbrella
26 => handbag
27 => tie
28 => suitcase
29 => frisbee
30 => skis
31 => snowboard
32 => sports ball
33 => kite
34 => baseball bat
35 => baseball glove
36 => skateboard
37 => surfboard
38 => tennis racket
39 => bottle
40 => wine glass
41 => cup
42 => fork
43 => knife
44 => spoon
45 => bowl
46 => banana
47 => apple
48 => sandwich
49 => orange
50 => broccoli
51 => carrot
52 => hot dog
53 => pizza
54 => donut
55 => cake
56 => chair
57 => couch
58 => potted plant
59 => bed
60 => dining table
61 => toilet
62 => tv
63 => laptop
64 => mouse
65 => remote
66 => keyboard
67 => cell phone
68 => microwave
69 => oven
70 => toaster
71 => sink
72 => refrigerator
73 => book
74 => clock
75 => vase
76 => scissors
77 => teddy bear
78 => hair drier
79 => toothbrush
```
+4 -4
View File
@@ -14,7 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "coco_class.h"
#include "logger.h"
// 定义存储名称的数组
const char *coco_names[80];
void init_coco_names()
@@ -102,12 +102,12 @@ void init_coco_names()
}
void print_coco_names()
{
fprintf(stdout, "===========coco names============ \n");
log_info( "===========coco names============ ");
for (int i = 0; i < 80; i++)
{
printf(" %d => %s\n", i, coco_names[i]);
log_info( " %d => %s", i, coco_names[i]);
}
fprintf(stdout, "================================= \n");
log_info( "================================= ");
}
const char *get_coco_name(int id)
+1 -3
View File
@@ -18,6 +18,7 @@
#include <pthread.h>
#include <unistd.h>
#include "context.h"
#include "logger.h"
// 创建 Context 结构体
Context *CreateContext()
@@ -25,18 +26,15 @@ Context *CreateContext()
Context *ctx = (Context *)malloc(sizeof(Context));
if (ctx == NULL)
{
perror("malloc");
return NULL;
}
if (pthread_mutex_init(&ctx->mtx, NULL) != 0)
{
perror("pthread_mutex_init");
free(ctx);
return NULL;
}
if (pthread_cond_init(&ctx->cond, NULL) != 0)
{
perror("pthread_cond_init");
pthread_mutex_destroy(&ctx->mtx);
free(ctx);
return NULL;
+2 -2
View File
@@ -22,7 +22,7 @@
#include "opencv_utils.h"
#include "warning_timer.h"
#include "timestamp_utils.h"
#include "logger.h"
void *frame_detection_thread(void *arg)
{
const ThreadArgs *args = (ThreadArgs *)arg;
@@ -30,7 +30,7 @@ void *frame_detection_thread(void *arg)
cv::dnn::Net net;
if (Init_CV_ONNX_DNN_Yolov8(modelPath, &net) != 0)
{
printf("Error: Failed to initialize the YOLOv8 ONNX DNN model.\n");
log_info( "Error: Failed to initialize the YOLOv8 ONNX DNN model.");
pthread_exit(NULL);
return NULL;
}
+3 -1
View File
@@ -13,6 +13,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "frame_queue.h"
#include "logger.h"
void free_queue_node(QueueItem *item)
{
if (item != NULL)
@@ -64,7 +66,7 @@ int enqueue(FrameQueue *q, QueueItem item)
QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
if (newNode == NULL)
{
perror("malloc failed");
log_error( "malloc failed");
pthread_mutex_unlock(&q->lock);
exit(EXIT_FAILURE);
}
+5 -4
View File
@@ -14,12 +14,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "http_api.h"
#include "logger.h"
// 回调函数,用于处理响应数据
size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
fprintf(stdout, "=== http write callback === %.*s\n", (int)realsize, (char *)contents);
log_info( "=== http write callback === %.*s", (int)realsize, (char *)contents);
return realsize;
}
void post_recognized_type(const char *url, int type, const char *device_uuid)
@@ -31,7 +32,7 @@ void post_recognized_type(const char *url, int type, const char *device_uuid)
tm_info = localtime(&now);
strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", tm_info);
snprintf(json_body, sizeof(json_body), "{\"type\": %d, \"ts\": \"%s\", \"device_uuid\": \"%s\"}", type, ts, device_uuid);
fprintf(stdout, "====== post_recognized_type json_body ======\n");
fprintf(stdout, "POST: %s, Body: %s\n", url, json_body);
fprintf(stdout, "============================================\n");
log_info( "====== post_recognized_type json_body ======");
log_info( "POST: %s, Body: %s", url, json_body);
log_info( "============================================");
}
+9 -8
View File
@@ -17,6 +17,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <libavutil/imgutils.h>
#include "logger.h"
// 保存AVFrame图像到文件,格式为png
const char *get_av_error(int errnum)
{
@@ -69,7 +70,7 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
{
if (!frame || !filename)
{
fprintf(stderr, "Invalid input parameters\n");
log_info( "Invalid input parameters");
return;
}
@@ -77,18 +78,18 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
int height = frame->height;
if (width <= 0 || height <= 0)
{
fprintf(stderr, "Invalid frame dimensions: %d*%d\n", width, height);
log_info( "Invalid frame dimensions: %d*%d", width, height);
return;
}
AVPixelFormat input_format = (AVPixelFormat)frame->format;
AVPixelFormat output_format = AV_PIX_FMT_BGR24;
AVPixelFormat output_format = AV_PIX_FMT_YUV420P;
// 检查像素格式是否支持
const AVPixFmtDescriptor *input_desc = av_pix_fmt_desc_get(input_format);
const AVPixFmtDescriptor *output_desc = av_pix_fmt_desc_get(output_format);
if (!input_desc || !output_desc)
{
fprintf(stderr, "Unsupported pixel format\n");
log_info( "Unsupported pixel format");
return;
}
@@ -96,7 +97,7 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
FILE *file = fopen(filename, "wb");
if (!file)
{
fprintf(stderr, "Failed to open file: %s\n", filename);
log_info( "Failed to open file: %s", filename);
return;
}
@@ -153,7 +154,7 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
SWS_BILINEAR, NULL, NULL, NULL);
if (!sws_ctx)
{
fprintf(stderr, "Failed to create SwsContext\n");
log_info( "Failed to create SwsContext");
fclose(file);
return;
}
@@ -162,7 +163,7 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
AVFrame *bgr_frame = av_frame_alloc();
if (!bgr_frame)
{
fprintf(stderr, "Failed to allocate AVFrame\n");
log_info( "Failed to allocate AVFrame");
sws_freeContext(sws_ctx);
fclose(file);
return;
@@ -173,7 +174,7 @@ void save_frame_as_bmp(AVFrame *frame, const char *filename)
uint8_t *buffer = (uint8_t *)av_malloc(num_bytes * sizeof(uint8_t));
if (!buffer)
{
fprintf(stderr, "Failed to allocate buffer\n");
log_info( "Failed to allocate buffer");
av_frame_free(&bgr_frame);
sws_freeContext(sws_ctx);
fclose(file);
+101
View File
@@ -0,0 +1,101 @@
// Copyright (C) 2025 wwhai
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "logger.h"
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
// 全局日志等级
static LogLevel current_log_level = LOG_INFO;
// 颜色代码
const char *color_codes[] = {
"\x1B[90m", // 灰色 (TRACE)
"\x1B[36m", // 青色 (DEBUG)
"\x1B[32m", // 绿色 (INFO)
"\x1B[33m", // 黄色 (WARN)
"\x1B[31m", // 红色 (ERROR)
"\x1B[41m" // 红底白字 (FATAL)
};
const char *reset_color = "\x1B[0m";
// 日志等级名称
const char *log_level_names[] = {
"TRACE",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL"};
// 设置日志等级
void set_log_level(LogLevel level)
{
current_log_level = level;
}
// 获取当前时间字符串
char *get_current_time()
{
time_t rawtime;
struct tm *timeinfo;
char *time_str = (char *)malloc(26);
if (time_str == NULL)
{
return NULL;
}
time(&rawtime);
timeinfo = localtime(&rawtime);
if (timeinfo == NULL || strftime(time_str, 26, "%Y-%m-%d %H:%M:%S", timeinfo) == 0)
{
free(time_str);
return NULL;
}
return time_str;
}
// 日志记录函数,增加文件和行号参数
void log_message(LogLevel level, const char *file, int line, const char *format, ...)
{
if (level < current_log_level)
{
return;
}
char *time_str = get_current_time();
va_list args;
// 打印时间、文件、行号和日志等级
if (time_str != NULL)
{
fprintf(stdout, "%s%s [%s] %s:%d - ", color_codes[level], time_str, log_level_names[level], file, line);
free(time_str);
}
else
{
fprintf(stdout, "%sUnknownTime [%s] %s:%d - ", color_codes[level], log_level_names[level], file, line);
}
// 打印日志消息
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
// 重置颜色
fprintf(stdout, "%s\n", reset_color);
}
+14 -19
View File
@@ -28,7 +28,7 @@
#include "push_stream_thread.h"
#include "warning_timer.h"
#include <curl/curl.h>
#include "logger.h"
// 全局上下文指针数组
Context *contexts[4];
@@ -42,7 +42,7 @@ void handle_signal(int sig)
CancelContext(contexts[i]);
}
}
fprintf(stdout, "Received signal %d, exiting...\n", sig);
log_info("Received signal %d, exiting...", sig);
exit(0);
}
@@ -52,7 +52,7 @@ int create_thread(pthread_t *thread, void *(*start_routine)(void *), void *arg)
int ret = pthread_create(thread, NULL, start_routine, arg);
if (ret != 0)
{
perror("Failed to create thread");
log_error( "Failed to create thread");
return -1;
}
return 0;
@@ -89,31 +89,32 @@ void destroy_frame_queues(FrameQueue *queues, int num_queues)
int main(int argc, char *argv[])
{
set_log_level(LOG_DEBUG);
// 检查命令行参数数量
if (argc < 3)
{
fprintf(stderr, "Usage: %s <camera_URL> <PUSH_URL>\n", argv[0]);
log_info("Usage: %s <camera_URL> <PUSH_URL>", argv[0]);
return EXIT_FAILURE;
}
// 设置信号处理函数
if (signal(SIGINT, handle_signal) == SIG_ERR)
{
perror("Failed to set signal handler for SIGINT");
log_error( "Failed to set signal handler for SIGINT");
return EXIT_FAILURE;
}
// 初始化告警计时器
if (warning_timer_init(10000, 10, event_triggered) != 0)
{
fprintf(stderr, "Failed to initialize warning timer\n");
log_info("Failed to initialize warning timer");
return EXIT_FAILURE;
}
// 初始化cURL库
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
{
fprintf(stderr, "Failed to initialize cURL library\n");
log_info("Failed to initialize cURL library");
warning_timer_stop();
return EXIT_FAILURE;
}
@@ -127,7 +128,7 @@ int main(int argc, char *argv[])
contexts[i] = CreateContext();
if (!contexts[i])
{
fprintf(stderr, "Failed to create context %d\n", i);
log_info("Failed to create context %d", i);
destroy_contexts();
curl_global_cleanup();
warning_timer_stop();
@@ -162,23 +163,17 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
}
fprintf(stdout, "Main thread waiting for threads to finish...\n");
log_info("Main thread waiting for threads to finish...");
// 分离线程
for (int i = 1; i < 4; i++)
// 等待所有线程结束
for (int i = 0; i < 4; i++)
{
if (pthread_detach(threads[i]) != 0)
if (pthread_join(threads[i], NULL) != 0)
{
perror("Failed to detach thread");
log_error( "Failed to join thread");
}
}
// 等待后台线程结束
if (pthread_join(threads[0], NULL) != 0)
{
perror("Failed to join background thread");
}
// 清理资源
destroy_contexts();
destroy_frame_queues(queues, num_queues);
+5 -15
View File
@@ -15,6 +15,7 @@
#include "opencv_dnn_module.h"
#include "opencv_utils.h"
#include <iostream>
#include "logger.h"
#include "coco_class.h"
// 初始化YOLOv8模型
int Init_CV_ONNX_DNN_Yolov8(const char *model_path, cv::dnn::Net *net)
@@ -47,7 +48,7 @@ int Infer_CV_ONNX_DNN_Yolov8(cv::dnn::Net *net, cv::Mat frame, std::vector<Box>
{
if (!net)
{
fprintf(stdout, "Error: Net pointer is null.\n");
log_info( "Error: Net pointer is null.");
return -1;
}
// 准备输入; YOLOV8图片尺寸需要压缩为640*640
@@ -65,7 +66,7 @@ int Infer_CV_ONNX_DNN_Yolov8(cv::dnn::Net *net, cv::Mat frame, std::vector<Box>
// 检查推理结果
if (outs.empty())
{
fprintf(stdout, "Error: No output from the network.\n");
log_info( "Error: No output from the network.");
return -1;
}
std::vector<DnnResult> results = postprocess(frame, outs, 0.25, 0.5);
@@ -92,17 +93,6 @@ int Infer_CV_ONNX_DNN_Yolov8(cv::dnn::Net *net, cv::Mat frame, std::vector<Box>
// 释放模型资源
int Release_CV_ONNX_DNN_Yolov8(cv::dnn::Net *net)
{
try
{
if (net)
{
net->~Net();
}
return 0; // 成功
}
catch (const std::exception &e)
{
fprintf(stdout, "Exception during model release: %s\n", e.what());
return -1;
}
log_info( "Releasing YOLOv8 model...");
return 0;
}
+3 -2
View File
@@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "opencv_utils.h"
#include "logger.h"
// 将 AVFrame 转换为 OpenCV 的 cv::Mat
cv::Mat AVFrameToCVMat(AVFrame *frame)
@@ -47,7 +48,7 @@ cv::Mat AVFrameToCVMat(AVFrame *frame)
}
else
{
fprintf(stdout, "Unsupported pixel format: %s\n", av_get_pix_fmt_name(pix_fmt));
log_info( "Unsupported pixel format: %s", av_get_pix_fmt_name(pix_fmt));
return cv::Mat();
}
@@ -108,7 +109,7 @@ std::vector<DnnResult> postprocess(cv::Mat &frame, const std::vector<cv::Mat> &o
for (int idx : indices)
{
cv::Rect box = boxes[idx];
// printf("box: %d, %d, %d, %d, %f, %d\n", box.x, box.y, box.width, box.height, confidences[idx], classIds[idx]);
// log_message(LOG_INFO"box: %d, %d, %d, %d, %f, %d", box.x, box.y, box.width, box.height, confidences[idx], classIds[idx]);
boxes_result.push_back({box.x, box.y, box.width, box.height, confidences[idx], classIds[idx]});
}
return boxes_result;
+16 -15
View File
@@ -28,6 +28,7 @@ extern "C"
#include "libav_utils.h"
#include "push_stream_thread.h"
#include "video_record_thread.h"
#include "logger.h"
// 克隆帧并加入队列
void clone_and_enqueue(AVFrame *src_frame, FrameQueue *queue)
{
@@ -49,7 +50,7 @@ void handle_error(const char *message, int ret, AVFormatContext **fmt_ctx, AVPac
{
message = "Unknown error";
}
fprintf(stderr, "%s (%s).\n", message, get_av_error(ret));
log_info( "%s (%s).", message, get_av_error(ret));
if (codec_ctx != nullptr && *codec_ctx != nullptr)
{
avcodec_free_context(codec_ctx);
@@ -107,9 +108,9 @@ void *pull_stream_handler_thread(void *arg)
handle_error("Error: No video stream found", AVERROR_STREAM_NOT_FOUND, &fmt_ctx, &origin_packet, &codec_ctx);
}
// Print stream information
fprintf(stdout, "=========input_stream_url======== \n");
log_info( "=========input_stream_url======== ");
av_dump_format(fmt_ctx, 0, args->input_stream_url, 0);
fprintf(stdout, "================================= \n");
log_info( "================================= ");
// Allocate AVPacket for reading frames
origin_packet = av_packet_alloc();
if (!origin_packet)
@@ -146,14 +147,14 @@ void *pull_stream_handler_thread(void *arg)
AVStream *stream = fmt_ctx->streams[i];
char pix_fmt_str[16];
av_get_pix_fmt_string(pix_fmt_str, sizeof(pix_fmt_str), (AVPixelFormat)stream->codecpar->format);
fprintf(stdout, "======= Stream Info =====\n");
printf("Stream %d:\n", i);
printf(" pixel_format: %s\n", pix_fmt_str);
printf(" codec_type: %s\n", av_get_media_type_string(stream->codecpar->codec_type));
printf(" codec_name: %s\n", avcodec_get_name(stream->codecpar->codec_id));
fprintf(stdout, "=========================\n");
log_info( "======= Stream Info =====");
log_info( "Stream %d:", i);
log_info( " pixel_format: %s", pix_fmt_str);
log_info( " codec_type: %s", av_get_media_type_string(stream->codecpar->codec_type));
log_info( " codec_name: %s", avcodec_get_name(stream->codecpar->codec_id));
log_info( "=========================");
}
fprintf(stdout, "Stream handler thread started. Pull stream: %s\n", args->input_stream_url);
log_info( "Stream handler thread started. Pull stream: %s", args->input_stream_url);
Context *record_mp4_thread_ctx = CreateContext();
Context *push_stream_thread_ctx = CreateContext();
if (!record_mp4_thread_ctx || !push_stream_thread_ctx)
@@ -221,14 +222,14 @@ void *pull_stream_handler_thread(void *arg)
ret = avcodec_send_packet(codec_ctx, origin_packet);
if (ret < 0)
{
fprintf(stdout, "Error: Failed to send packet to decoder (%s).\n", get_av_error(ret));
log_info( "Error: Failed to send packet to decoder (%s).", get_av_error(ret));
av_packet_unref(origin_packet);
continue;
}
AVFrame *origin_frame = av_frame_alloc();
if (!origin_frame)
{
fprintf(stdout, "Error: Failed to allocate frame(%s).\n", get_av_error(AVERROR(ENOMEM)));
log_info( "Error: Failed to allocate frame(%s).", get_av_error(AVERROR(ENOMEM)));
av_packet_unref(origin_packet);
continue;
}
@@ -236,14 +237,14 @@ void *pull_stream_handler_thread(void *arg)
ret = avcodec_receive_frame(codec_ctx, origin_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
printf("No frame received from decoder.\n");
log_info( "No frame received from decoder.");
av_frame_free(&origin_frame);
av_packet_unref(origin_packet);
continue;
}
else if (ret < 0)
{
fprintf(stdout, "Error: Failed to receive frame from decoder (%s).\n", get_av_error(ret));
log_info( "Error: Failed to receive frame from decoder (%s).", get_av_error(ret));
av_frame_free(&origin_frame);
av_packet_unref(origin_packet);
continue;
@@ -260,7 +261,7 @@ void *pull_stream_handler_thread(void *arg)
}
av_packet_unref(origin_packet);
}
fprintf(stdout, "Stream handler thread stopped\n");
log_info( "push_stream_thread ended.");
// 取消上下文并销毁互斥锁
if (push_stream_thread_ctx)
{
+20 -19
View File
@@ -15,36 +15,37 @@
#include "push_stream_thread.h"
#include "libav_utils.h"
#include "logger.h"
// 初始化 RTMP 流上下文
int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width, int height, int fps)
{
if (!ctx || !output_url)
{
fprintf(stdout, "Invalid input parameters for init_rtmp_stream\n");
log_info( "Invalid input parameters for init_rtmp_stream");
return -1;
}
// 输出参数
fprintf(stdout, "init_rtmp_stream === output_url=%s,width=%d,height=%d,fps=%d\n",
output_url, width, height, fps);
log_info( "init_rtmp_stream === output_url=%s,width=%d,height=%d,fps=%d",
output_url, width, height, fps);
// 创建输出上下文
int ret = avformat_alloc_output_context2(&ctx->output_ctx, NULL, "flv", output_url);
if (ret < 0 || !ctx->output_ctx)
{
fprintf(stdout, "Failed to create output context: %s\n", get_av_error(ret));
log_info( "Failed to create output context: %s", get_av_error(ret));
return -1;
}
// 查找编码器
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
fprintf(stdout, "H.264 encoder not found\n");
log_info( "H.264 encoder not found");
goto cleanup_output_context;
}
// 创建编码器上下文
ctx->codec_ctx = avcodec_alloc_context3(codec);
if (!ctx->codec_ctx)
{
fprintf(stdout, "Failed to allocate codec context\n");
log_info( "Failed to allocate codec context");
goto cleanup_output_context;
}
// 从输入流复制编解码器参数到编码器上下文
@@ -53,7 +54,7 @@ int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width,
ret = avcodec_parameters_to_context(ctx->codec_ctx, ctx->input_stream->codecpar);
if (ret < 0)
{
fprintf(stdout, "Failed to copy codec parameters from input stream: %s\n", get_av_error(ret));
log_info( "Failed to copy codec parameters from input stream: %s", get_av_error(ret));
goto cleanup_codec_context;
}
}
@@ -70,21 +71,21 @@ int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width,
ctx->video_stream = avformat_new_stream(ctx->output_ctx, NULL);
if (!ctx->video_stream)
{
fprintf(stdout, "Failed to create video stream\n");
log_info( "Failed to create video stream");
goto cleanup_codec_context;
}
// 关联编码器参数到输出流
ret = avcodec_parameters_from_context(ctx->video_stream->codecpar, ctx->codec_ctx);
if (ret < 0)
{
fprintf(stdout, "Failed to copy codec parameters to output stream: %s\n", get_av_error(ret));
log_info( "Failed to copy codec parameters to output stream: %s", get_av_error(ret));
goto cleanup_codec_context;
}
// 打开编码器
ret = avcodec_open2(ctx->codec_ctx, codec, NULL);
if (ret < 0)
{
fprintf(stdout, "Failed to open codec: %s\n", get_av_error(ret));
log_info( "Failed to open codec: %s", get_av_error(ret));
goto cleanup_codec_context;
}
// 打开网络输出
@@ -93,7 +94,7 @@ int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width,
ret = avio_open(&ctx->output_ctx->pb, output_url, AVIO_FLAG_WRITE);
if (ret < 0)
{
fprintf(stdout, "Failed to open output URL: %s\n", get_av_error(ret));
log_info( "Failed to open output URL: %s", get_av_error(ret));
goto cleanup_codec_context;
}
}
@@ -101,7 +102,7 @@ int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width,
ret = avformat_write_header(ctx->output_ctx, NULL);
if (ret < 0)
{
fprintf(stdout, "Failed to write header: %d, %s\n", ret, get_av_error(ret));
log_info( "Failed to write header: %d, %s", ret, get_av_error(ret));
goto cleanup_io;
}
return 0;
@@ -121,7 +122,7 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
{
if (!ctx || !frame)
{
fprintf(stdout, "Invalid input parameters: RtmpStreamContext or AVFrame is NULL\n");
log_info( "Invalid input parameters: RtmpStreamContext or AVFrame is NULL");
return;
}
int ret = 0;
@@ -129,13 +130,13 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
ret = avcodec_send_frame(ctx->codec_ctx, frame);
if (ret < 0)
{
fprintf(stdout, "Error sending frame: %s\n", get_av_error(ret));
log_info( "Error sending frame: %s", get_av_error(ret));
return;
}
AVPacket *pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stdout, "Error allocating AVPacket\n");
log_info( "Error allocating AVPacket");
return;
}
// 接收编码后的数据包
@@ -149,7 +150,7 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
}
else if (ret < 0)
{
fprintf(stdout, "Error encoding frame: %s\n", get_av_error(ret));
log_info( "Error encoding frame: %s", get_av_error(ret));
break;
}
@@ -157,7 +158,7 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
ret = av_interleaved_write_frame(ctx->output_ctx, pkt);
if (ret < 0)
{
fprintf(stdout, "Error writing packet: %d, %s\n", ret, get_av_error(ret));
log_info( "Error writing packet: %d, %s", ret, get_av_error(ret));
}
// 释放数据包
av_packet_unref(pkt);
@@ -176,7 +177,7 @@ void *push_rtmp_handler_thread(void *arg)
// 初始化输出流
if (init_rtmp_stream(&ctx, args->output_stream_url, 1920, 1080, 25) < 0)
{
fprintf(stdout, "Failed to initialize RTMP stream\n");
log_info( "Failed to initialize RTMP stream");
return NULL;
}
@@ -196,7 +197,7 @@ void *push_rtmp_handler_thread(void *arg)
}
}
}
log_info( "push_rtmp_handler_thread exit");
av_write_trailer(ctx.output_ctx);
avcodec_free_context(&ctx.codec_ctx);
avio_closep(&ctx.output_ctx->pb);
+8 -7
View File
@@ -15,6 +15,7 @@
#include "sdl_utils.h"
#include "frame_queue.h"
#include "logger.h"
void NV12ToRGB(uint8_t *y_plane, uint8_t *uv_plane, int width,
int height, int y_pitch, int uv_pitch, uint8_t *rgb_buffer)
{
@@ -59,7 +60,7 @@ void SDLDisplayNV12Frame(SDL_Renderer *renderer, SDL_Texture *texture, AVFrame *
frame->width <= 0 ||
frame->height <= 0)
{
fprintf(stdout, "frame is null or invalid\n");
log_info( "frame is null or invalid");
return;
}
@@ -72,7 +73,7 @@ void SDLDisplayNV12Frame(SDL_Renderer *renderer, SDL_Texture *texture, AVFrame *
uint8_t *rgb_buffer = (uint8_t *)malloc(width * height * 3);
if (!rgb_buffer)
{
fprintf(stdout, "Failed to allocate RGB buffer\n");
log_info( "Failed to allocate RGB buffer");
return;
}
// 转换 NV12 为 RGB
@@ -80,7 +81,7 @@ void SDLDisplayNV12Frame(SDL_Renderer *renderer, SDL_Texture *texture, AVFrame *
// 更新纹理
if (SDL_UpdateTexture(texture, NULL, rgb_buffer, width * 3) < 0)
{
fprintf(stdout, "Failed to update texture: %s\n", SDL_GetError());
log_info( "Failed to update texture: %s", SDL_GetError());
free(rgb_buffer);
return;
}
@@ -103,14 +104,14 @@ void SDLDrawText(SDL_Renderer *renderer, SDL_Texture *texture, TTF_Font *font, c
SDL_Surface *textSurface = TTF_RenderText_Solid(font, text, textColor);
if (textSurface == NULL)
{
fprintf(stdout, "Failed to render text: %s\n", TTF_GetError());
log_info( "Failed to render text: %s", TTF_GetError());
return;
}
// 创建纹理
SDL_Texture *textTexture = SDL_CreateTextureFromSurface(renderer, textSurface);
if (textTexture == NULL)
{
fprintf(stdout, "Failed to create texture from surface: %s\n", SDL_GetError());
log_info( "Failed to create texture from surface: %s", SDL_GetError());
SDL_FreeSurface(textSurface);
return;
}
@@ -128,7 +129,7 @@ void SDLDrawLabel(SDL_Renderer *renderer, TTF_Font *font, const char *text, int
SDL_Surface *textSurface = TTF_RenderText_Solid(font, text, textColor);
if (!textSurface)
{
printf("Unable to render text surface! SDL_ttf Error: %s\n", TTF_GetError());
log_info( "Unable to render text surface! SDL_ttf Error: %s", TTF_GetError());
return;
}
// 创建一个矩形作为背景
@@ -143,7 +144,7 @@ void SDLDrawLabel(SDL_Renderer *renderer, TTF_Font *font, const char *text, int
if (!textTexture)
{
printf("Unable to create texture from rendered text! SDL_Error: %s\n", SDL_GetError());
log_info( "Unable to create texture from rendered text! SDL_Error: %s", SDL_GetError());
return;
}
+4 -3
View File
@@ -14,9 +14,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "thread_args.h"
#include "logger.h"
void dump_thread_args(ThreadArgs *args)
{
fprintf(stdout, "=== dump_thread_args ===\n");
fprintf(stdout, "input_stream_url=%s\n", args->input_stream_url);
fprintf(stdout, "output_stream_url=%s\n", args->output_stream_url);
log_info( "=== dump_thread_args ===");
log_info( "input_stream_url=%s", args->input_stream_url);
log_info( "output_stream_url=%s", args->output_stream_url);
}
+24 -23
View File
@@ -15,36 +15,37 @@
#include "video_record_thread.h"
#include "libav_utils.h"
#include "logger.h"
// 初始化 RTMP 流上下文
int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, int height, int fps)
{
if (!ctx || !output_url)
{
fprintf(stdout, "Invalid input parameters for init_rtmp_stream\n");
log_info( "Invalid input parameters for init_rtmp_stream");
return -1;
}
// 输出参数
fprintf(stdout, "init_rtmp_stream === output_url=%s,width=%d,height=%d,fps=%d\n",
output_url, width, height, fps);
log_info( "init_rtmp_stream === output_url=%s,width=%d,height=%d,fps=%d",
output_url, width, height, fps);
// 创建输出上下文
int ret = avformat_alloc_output_context2(&ctx->output_ctx, NULL, "flv", output_url);
if (ret < 0 || !ctx->output_ctx)
{
fprintf(stdout, "Failed to create output context: %s\n", get_av_error(ret));
log_info( "Failed to create output context: %s", get_av_error(ret));
return -1;
}
// 查找编码器
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
fprintf(stdout, "H.264 encoder not found\n");
log_info( "H.264 encoder not found");
goto cleanup_output_context;
}
// 创建编码器上下文
ctx->codec_ctx = avcodec_alloc_context3(codec);
if (!ctx->codec_ctx)
{
fprintf(stdout, "Failed to allocate codec context\n");
log_info( "Failed to allocate codec context");
goto cleanup_output_context;
}
// 从输入流复制编解码器参数到编码器上下文
@@ -53,7 +54,7 @@ int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, in
ret = avcodec_parameters_to_context(ctx->codec_ctx, ctx->input_stream->codecpar);
if (ret < 0)
{
fprintf(stdout, "Failed to copy codec parameters from input stream: %s\n", get_av_error(ret));
log_info( "Failed to copy codec parameters from input stream: %s", get_av_error(ret));
goto cleanup_codec_context;
}
}
@@ -70,33 +71,33 @@ int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, in
ctx->video_stream = avformat_new_stream(ctx->output_ctx, NULL);
if (!ctx->video_stream)
{
fprintf(stdout, "Failed to create video stream\n");
log_info( "Failed to create video stream");
goto cleanup_codec_context;
}
// 关联编码器参数到输出流
ret = avcodec_parameters_from_context(ctx->video_stream->codecpar, ctx->codec_ctx);
if (ret < 0)
{
fprintf(stdout, "Failed to copy codec parameters to output stream: %s\n", get_av_error(ret));
log_info( "Failed to copy codec parameters to output stream: %s", get_av_error(ret));
goto cleanup_codec_context;
}
// 打开编码器
ret = avcodec_open2(ctx->codec_ctx, codec, NULL);
if (ret < 0)
{
fprintf(stdout, "Failed to open codec: %s\n", get_av_error(ret));
log_info( "Failed to open codec: %s", get_av_error(ret));
goto cleanup_codec_context;
}
fprintf(stdout, "=== av_dump_format output.mp4 === \n");
log_info( "=== av_dump_format output.mp4 === ");
av_dump_format(ctx->output_ctx, 0, "output.mp4", 1);
fprintf(stdout, "================================= \n");
log_info( "================================= ");
// 打开网络输出
if (!(ctx->output_ctx->oformat->flags & AVFMT_NOFILE))
{
ret = avio_open(&ctx->output_ctx->pb, output_url, AVIO_FLAG_WRITE);
if (ret < 0)
{
fprintf(stdout, "Failed to open output URL: %s\n", get_av_error(ret));
log_info( "Failed to open output URL: %s", get_av_error(ret));
goto cleanup_codec_context;
}
}
@@ -104,7 +105,7 @@ int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, in
ret = avformat_write_header(ctx->output_ctx, NULL);
if (ret < 0)
{
fprintf(stdout, "Failed to write header: %d, %s\n", ret, get_av_error(ret));
log_info( "Failed to write header: %d, %s", ret, get_av_error(ret));
goto cleanup_io;
}
return 0;
@@ -124,7 +125,7 @@ void save_mp4(Mp4StreamContext *ctx, AVFrame *frame)
{
if (!ctx || !frame)
{
fprintf(stdout, "Invalid input parameters: RtmpStreamContext or AVFrame is NULL\n");
log_info( "Invalid input parameters: RtmpStreamContext or AVFrame is NULL");
return;
}
int ret = 0;
@@ -132,13 +133,13 @@ void save_mp4(Mp4StreamContext *ctx, AVFrame *frame)
ret = avcodec_send_frame(ctx->codec_ctx, frame);
if (ret < 0)
{
fprintf(stdout, "Error sending frame: %s\n", get_av_error(ret));
log_info( "Error sending frame: %s", get_av_error(ret));
return;
}
AVPacket *pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stdout, "Error allocating AVPacket\n");
log_info( "Error allocating AVPacket");
return;
}
// 接收编码后的数据包
@@ -152,7 +153,7 @@ void save_mp4(Mp4StreamContext *ctx, AVFrame *frame)
}
else if (ret < 0)
{
fprintf(stdout, "Error encoding frame: %s\n", get_av_error(ret));
log_info( "Error encoding frame: %s", get_av_error(ret));
break;
}
av_packet_rescale_ts(pkt, ctx->input_stream->time_base, ctx->video_stream->time_base);
@@ -160,7 +161,7 @@ void save_mp4(Mp4StreamContext *ctx, AVFrame *frame)
ret = av_interleaved_write_frame(ctx->output_ctx, pkt);
if (ret < 0)
{
fprintf(stdout, "Error writing packet: %d, %s\n", ret, get_av_error(ret));
log_info( "Error writing packet: %d, %s", ret, get_av_error(ret));
}
// 释放数据包
av_packet_unref(pkt);
@@ -177,14 +178,14 @@ void *save_mp4_handler_thread(void *arg)
ctx.input_stream = args->input_stream;
// 初始化输出流
fprintf(stdout, "Start save mp4 record thread\n");
log_info( "Start save mp4 record thread");
// 获取当前时间戳作为文件名
time_t current_time = time(NULL);
char file_name[100];
strftime(file_name, sizeof(file_name), "./local_%Y%m%d_%H%M%S.mp4", localtime(&current_time));
if (init_mp4_stream(&ctx, file_name, 1920, 1080, 25) < 0)
{
fprintf(stdout, "Failed to initialize RTMP stream\n");
log_info( "Failed to initialize RTMP stream");
return NULL;
}
@@ -223,7 +224,7 @@ void *save_mp4_handler_thread(void *arg)
ctx.input_stream = args->input_stream;
if (init_mp4_stream(&ctx, file_name, 1920, 1080, 25) < 0)
{
fprintf(stdout, "Failed to initialize new MP4 stream\n");
log_info( "Failed to initialize new MP4 stream");
return NULL;
}
start_time = time(NULL);
@@ -234,7 +235,7 @@ void *save_mp4_handler_thread(void *arg)
}
}
fprintf(stdout, "Stop save mp4 record thread\n");
log_info( "Stop save mp4 record thread");
av_write_trailer(ctx.output_ctx);
avio_closep(&ctx.output_ctx->pb);
avcodec_free_context(&ctx.codec_ctx);
+65 -38
View File
@@ -1,72 +1,74 @@
// Copyright (C) 2025 wwhai
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "video_renderer.h"
#define TARGET_FPS 30 // 目标帧率
#include "logger.h"
#define TARGET_FPS 25 // 目标帧率
#define FRAME_TIME (1000 / TARGET_FPS) // 每帧目标时间 (毫秒)
void *video_renderer_thread(void *arg)
{
// 检查输入参数是否为空
if (arg == NULL)
{
log_info( "Error: Input argument is NULL.");
return NULL;
}
ThreadArgs *args = (ThreadArgs *)arg;
// 初始化 SDL 和 TTF
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
fprintf(stdout, "SDL_Init Error: %s\n", SDL_GetError());
log_info( "SDL_Init Error: %s", SDL_GetError());
return NULL;
}
if (TTF_Init() == -1)
{
fprintf(stdout, "SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
log_info( "SDL_ttf could not initialize! TTF_Error: %s", TTF_GetError());
SDL_Quit();
return NULL;
}
// 创建窗口
SDL_Window *window = SDL_CreateWindow("VIDEO-PLAYER",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
1920, 1080, SDL_WINDOW_SHOWN);
if (!window)
{
fprintf(stdout, "SDL_CreateWindow Error: %s\n", SDL_GetError());
log_info( "SDL_CreateWindow Error: %s", SDL_GetError());
TTF_Quit();
SDL_Quit();
return NULL;
}
// 创建渲染器
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer)
{
fprintf(stdout, "SDL_CreateRenderer Error: %s\n", SDL_GetError());
log_info( "SDL_CreateRenderer Error: %s", SDL_GetError());
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return NULL;
}
// 创建纹理
SDL_Texture *texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
1920, 1080);
if (!texture)
{
fprintf(stdout, "SDL_CreateTexture Error: %s\n", SDL_GetError());
log_info( "SDL_CreateTexture Error: %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
pthread_exit(NULL);
return NULL;
}
// 加载字体
TTF_Font *font = TTF_OpenFont("mono.ttf", 18);
if (font == NULL)
{
fprintf(stdout, "Failed to load font! TTF_Error: %s\n", TTF_GetError());
log_info( "Failed to load font! TTF_Error: %s", TTF_GetError());
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
@@ -81,14 +83,16 @@ void *video_renderer_thread(void *arg)
Uint64 lastFrameTime = SDL_GetPerformanceCounter();
Uint64 performanceFrequency = SDL_GetPerformanceFrequency();
SDL_Event event;
// loop
// 主循环
while (1)
{
Uint32 frameStart = SDL_GetTicks();
// 检查上下文是否被取消
if (args->ctx->is_cancelled)
{
goto END;
break;
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
@@ -109,12 +113,13 @@ void *video_renderer_thread(void *arg)
newFrame->data[2], newFrame->linesize[2]);
if (ret < 0)
{
fprintf(stdout, "SDL_UpdateYUVTexture failed: %s\n", SDL_GetError());
log_info( "SDL_UpdateYUVTexture failed: %s", SDL_GetError());
}
av_frame_free(&newFrame);
}
}
SDL_RenderCopy(renderer, texture, NULL, NULL);
// 处理检测结果队列
QueueItem boxes_item;
if (async_dequeue(args->box_queue, &boxes_item))
@@ -123,12 +128,17 @@ void *video_renderer_thread(void *arg)
{
for (int i = 0; i < boxes_item.box_count; ++i)
{
SDLDrawBox(renderer, font, boxes_item.Boxes[i].label,
boxes_item.Boxes[i].x, boxes_item.Boxes[i].y,
boxes_item.Boxes[i].w, boxes_item.Boxes[i].h, 1);
// 检查边界框数据是否有效
if (boxes_item.Boxes != NULL)
{
SDLDrawBox(renderer, font, boxes_item.Boxes[i].label,
boxes_item.Boxes[i].x, boxes_item.Boxes[i].y,
boxes_item.Boxes[i].w, boxes_item.Boxes[i].h, 1);
}
}
}
}
// 计算FPS并显示
frameCount++;
currentFrameTime = SDL_GetPerformanceCounter();
@@ -159,25 +169,42 @@ void *video_renderer_thread(void *arg)
{
if (event.type == SDL_QUIT)
{
goto END;
break;
}
else if (event.type == SDL_KEYDOWN)
{
if (event.key.keysym.sym == SDLK_ESCAPE)
{
goto END;
break;
}
}
}
if (event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE)
{
break;
}
}
END:
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_CloseFont(font);
// 资源释放
if (texture)
{
SDL_DestroyTexture(texture);
}
if (renderer)
{
SDL_DestroyRenderer(renderer);
}
if (window)
{
SDL_DestroyWindow(window);
}
if (font)
{
TTF_CloseFont(font);
}
TTF_Quit();
SDL_Quit();
pthread_exit(NULL);
log_info( "video_renderer_thread exit");
return NULL;
}
}
+6 -5
View File
@@ -21,6 +21,7 @@
#include <unistd.h>
#include "http_api.h"
#include "libav_utils.h"
#include "logger.h"
// 全局变量
static uint32_t interval_ms;
static uint32_t threshold;
@@ -34,8 +35,8 @@ static AVFrame *last_frame;
//
void print_warning_info(WarningInfo *info)
{
fprintf(stdout, "Warning triggered! Count: %u, Interval: %ums, Type: %s, Timestamp: %d\n",
info->warning_count, info->interval_ms, info->coco_types, info->latest_warning_timestamp);
log_info( "Warning triggered! Count: %u, Interval: %ums, Type: %s, Timestamp: %d",
info->warning_count, info->interval_ms, info->coco_types, info->latest_warning_timestamp);
}
// 计时器线程函数
static void *timer_thread_func(void *arg)
@@ -84,10 +85,10 @@ int warning_timer_init(uint32_t interval_ms_param, uint32_t threshold_param, voi
if (pthread_create(&timer_thread, NULL, timer_thread_func, NULL) != 0)
{
perror("Failed to create timer thread");
log_error( "Failed to create timer thread");
exit(EXIT_FAILURE);
}
fprintf(stdout, "Warning timer initialized!\n");
log_info( "Warning timer initialized!");
return 0;
}
@@ -105,7 +106,7 @@ void warning_timer_stop()
{
running = 0;
pthread_join(timer_thread, NULL);
fprintf(stdout, "Warning timer stopped!\n");
log_info( "Warning timer stopped!");
}
// 触发事件的回调函数
void event_triggered(WarningInfo *info)