dev: update

This commit is contained in:
wwhai
2025-02-07 00:46:50 +08:00
parent 7054019f09
commit b0bce76c2b
6 changed files with 301 additions and 174 deletions

View File

@@ -25,6 +25,8 @@ extern "C"
#include <SDL2/SDL.h>
}
#include "frame_queue.h"
// 获取错误字符串的全局缓冲区实现
const char *get_av_error(int errnum);
// 函数用于复制AVFrame
// 注意:使用完毕后需要释放
// @param srcFrame 源帧

View File

@@ -39,8 +39,7 @@ typedef struct
AVStream *video_stream;
AVCodecContext *codec_ctx;
} RtmpStreamContext;
// 获取错误字符串的全局缓冲区实现
const char *get_av_error(int errnum);
/// @brief
/// @param ctx
/// @param output_url

View File

@@ -0,0 +1,63 @@
// 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 __VIDEO_RECORD_H__
#define __VIDEO_RECORD_H__
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavutil/error.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
}
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "frame_queue.h" // 自定义队列头文件
#include "thread_args.h"
typedef struct
{
AVFormatContext *output_ctx;
AVStream *video_stream;
AVCodecContext *codec_ctx;
} Mp4StreamContext;
/// @brief
/// @param ctx
/// @param output_url
/// @param width
/// @param height
/// @param fps
/// @return
int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, int height, int fps);
/// @brief
/// @param ctx
/// @param frame
void save_mp4(Mp4StreamContext *ctx, AVFrame *frame);
/// @brief
/// @param arg
/// @return
void *push_mp4_handler_thread(void *arg);
#endif

View File

@@ -14,6 +14,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "libav_utils.h"
#include "frame_queue.h"
#include "stdio.h"
const char *get_av_error(int errnum)
{
static char str[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, str, sizeof(str));
return str;
}
// 函数用于复制AVFrame
AVFrame *CopyAVFrame(AVFrame *srcFrame)
{
@@ -23,9 +30,7 @@ AVFrame *CopyAVFrame(AVFrame *srcFrame)
fprintf(stderr, "Could not allocate frame\n");
return NULL;
}
dstFrame->format = srcFrame->format;
dstFrame->width = srcFrame->width;
dstFrame->height = srcFrame->height;
int ret = av_frame_get_buffer(dstFrame, 32);
if (ret < 0)
{
@@ -40,141 +45,32 @@ AVFrame *CopyAVFrame(AVFrame *srcFrame)
av_frame_free(&dstFrame);
return NULL;
}
if (av_frame_copy_props(dstFrame, srcFrame) < 0)
{
fprintf(stderr, "Could not copy frame properties\n");
av_frame_free(&dstFrame);
return NULL;
}
return dstFrame;
}
// 保存图像到文件,格式为png不要用Libav的库直接提取Avframe的Data
int CaptureImage(AVFrame *srcFrame, const char *file_path)
{
if (!srcFrame || !file_path)
{
fprintf(stderr, "Invalid input to CaptureImage\n");
fprintf(stderr, "Invalid input parameters\n");
return -1;
}
// Initialize variables
const AVCodec *jpegCodec = NULL;
AVCodecContext *codecCtx = NULL;
AVFrame *rgbFrame = NULL;
struct SwsContext *swsCtx = NULL;
FILE *file = NULL;
int ret = 0;
int rgbBufferSize = 0;
uint8_t *rgbBuffer;
// Allocate packet for JPEG encoding
AVPacket pkt;
pkt.data = NULL;
pkt.size = 0;
// Find the JPEG codec
jpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (!jpegCodec)
{
fprintf(stderr, "JPEG codec not found\n");
return -1;
}
// Allocate codec context
codecCtx = avcodec_alloc_context3(jpegCodec);
if (!codecCtx)
{
fprintf(stderr, "Could not allocate codec context\n");
return -1;
}
// Set codec parameters
codecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
codecCtx->width = srcFrame->width;
codecCtx->height = srcFrame->height;
codecCtx->time_base = (AVRational){1, 25};
// Open codec
if ((ret = avcodec_open2(codecCtx, jpegCodec, NULL)) < 0)
{
char buffer[64] = {0};
char *error = av_make_error_string(buffer, 64, ret);
fprintf(stderr, "Could not open codec: %s\n", error);
goto cleanup;
}
// Allocate frame for RGB conversion
rgbFrame = av_frame_alloc();
if (!rgbFrame)
{
fprintf(stderr, "Could not allocate RGB frame\n");
ret = -1;
goto cleanup;
}
// Set up RGB frame
rgbBufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, srcFrame->width, srcFrame->height, 1);
rgbBuffer = (uint8_t *)av_malloc(rgbBufferSize);
if (!rgbBuffer)
{
fprintf(stderr, "Could not allocate RGB buffer\n");
ret = -1;
goto cleanup;
}
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbBuffer, AV_PIX_FMT_RGB24, srcFrame->width, srcFrame->height, 1);
// Set up SwsContext for RGB conversion
swsCtx = sws_getContext(srcFrame->width, srcFrame->height, (enum AVPixelFormat)srcFrame->format,
srcFrame->width, srcFrame->height, AV_PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
if (!swsCtx)
{
fprintf(stderr, "Could not create SwsContext\n");
ret = -1;
goto cleanup;
}
// Convert frame to RGB
sws_scale(swsCtx, (const uint8_t *const *)srcFrame->data, srcFrame->linesize, 0, srcFrame->height,
rgbFrame->data, rgbFrame->linesize);
// Send frame to encoder
if ((ret = avcodec_send_frame(codecCtx, rgbFrame)) < 0)
{
char buffer[64] = {0};
char *error = av_make_error_string(buffer, 64, ret);
fprintf(stderr, "Error sending frame to encoder: %s\n", error);
goto cleanup;
}
// Receive encoded packet
ret = avcodec_receive_packet(codecCtx, &pkt);
if (ret < 0)
{
char buffer[64] = {0};
char *error = av_make_error_string(buffer, 64, ret);
fprintf(stderr, "Error receiving packet from encoder: %s\n", error);
goto cleanup;
}
// Write packet to file
file = fopen(file_path, "wb");
FILE *file = fopen(file_path, "wb");
if (!file)
{
fprintf(stderr, "Could not open file for writing: %s\n", file_path);
ret = -1;
goto cleanup;
fprintf(stderr, "Could not open file: %s\n", file_path);
return -1;
}
fwrite(pkt.data, 1, pkt.size, file);
// Success
ret = 0;
cleanup:
if (file)
fclose(file);
if (rgbFrame)
av_frame_free(&rgbFrame);
if (codecCtx)
avcodec_free_context(&codecCtx);
if (swsCtx)
sws_freeContext(swsCtx);
av_packet_unref(&pkt);
return ret;
// 写入文件头
fwrite(srcFrame->data[0], 1, srcFrame->linesize[0] * srcFrame->height, file);
fclose(file);
return 0;
}
/**

View File

@@ -14,14 +14,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "push_stream_thread.h"
const char *get_av_error(int errnum)
{
static char str[AV_ERROR_MAX_STRING_SIZE];
av_strerror(errnum, str, sizeof(str));
return str;
}
#include "libav_utils.h"
// 初始化 RTMP 流上下文
int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width, int height, int fps)
{
// 输出参数
@@ -61,8 +55,8 @@ int init_rtmp_stream(RtmpStreamContext *ctx, const char *output_url, int width,
ctx->codec_ctx->gop_size = 12;
// H.264高级配置
av_opt_set(ctx->codec_ctx->priv_data, "preset", "fast", 0);
av_opt_set(ctx->codec_ctx->priv_data, "tune", "zerolatency", 0);
// av_opt_set(ctx->codec_ctx->priv_data, "preset", "fast", 0);
// av_opt_set(ctx->codec_ctx->priv_data, "tune", "zerolatency", 0);
// 创建输出流
ctx->video_stream = avformat_new_stream(ctx->output_ctx, NULL);
@@ -103,7 +97,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(stderr, "Failed to write header: %s\n", get_av_error(ret));
fprintf(stderr, "Failed to write header:%d, %s\n", ret, get_av_error(ret));
return -1;
}
@@ -134,7 +128,6 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
return;
}
// 接收编码后的数据包
// 接收编码后的数据包
while (1)
{
@@ -149,45 +142,33 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
fprintf(stderr, "Error encoding frame: %s\n", get_av_error(ret));
break;
}
// 输出 frame index
fprintf(stderr, "Packet index: %d\n", pkt->stream_index);
fprintf(stderr, "Codec time base: %d/%d\n", ctx->codec_ctx->time_base.num, ctx->codec_ctx->time_base.den);
fprintf(stderr, "Video stream time base: %d/%d\n", ctx->video_stream->time_base.num, ctx->video_stream->time_base.den);
fprintf(stderr, "BEFORE ====== PTS: %ld, DTS: %ld, Duration: %ld\n", pkt->pts, pkt->dts, pkt->duration);
// 确保时间戳被正确设置
if (pkt->pts == AV_NOPTS_VALUE)
{
// 如果编码器没有产生正确的 PTS我们可以手动设置
pkt->pts = frame->pts;
}
if (pkt->dts == AV_NOPTS_VALUE)
{
pkt->dts = frame->pts;
}
// 手动计算并设置数据包的持续时间
if (pkt->duration == 0)
{
// 根据帧率计算持续时间
AVRational time_base = ctx->codec_ctx->time_base;
int fps = (int)(1 / av_q2d(time_base));
pkt->duration = av_rescale_q(1, (AVRational){1, fps}, time_base);
}
// 设置数据包属性
// 调整时间戳
pkt->pts = av_rescale_q_rnd(pkt->pts, ctx->codec_ctx->time_base, ctx->video_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt->dts = av_rescale_q_rnd(pkt->dts, ctx->codec_ctx->time_base, ctx->video_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
int fps = 25;
AVRational codec_time_base = ctx->codec_ctx->time_base;
pkt->duration = av_rescale_q(1, (AVRational){1, fps}, codec_time_base);
pkt->pos = -1;
pkt->stream_index = ctx->video_stream->index;
// 重新缩放时间戳
av_packet_rescale_ts(pkt, ctx->codec_ctx->time_base, ctx->video_stream->time_base);
fprintf(stderr, "AFTER ====== PTS: %ld, DTS: %ld, Duration: %ld\n", pkt->pts, pkt->dts, pkt->duration);
// 打印数据包信息
// fprintf(stdout, "Encoded packet information:\n");
// fprintf(stdout, " size: %d\n", pkt->size);
// fprintf(stdout, " pts: %" PRId64 "\n", pkt->pts);
// fprintf(stdout, " dts: %" PRId64 "\n", pkt->dts);
// fprintf(stdout, " duration: %" PRId64 "\n", pkt->duration);
// 发送数据包
// 检查数据包大小
if (pkt->size <= 0)
{
fprintf(stderr, "Packet size is invalid: %d\n", pkt->size);
return;
}
ret = av_interleaved_write_frame(ctx->output_ctx, pkt);
if (ret < 0)
{
fprintf(stderr, "Error writing packet: %s\n", get_av_error(ret));
fprintf(stderr, "Error writing packet: %d, %s\n", ret, get_av_error(ret));
}
// 释放数据包
@@ -201,7 +182,7 @@ void push_stream(RtmpStreamContext *ctx, AVFrame *frame)
void *push_rtmp_handler_thread(void *arg)
{
ThreadArgs *args = (ThreadArgs *)arg;
const char *output_url = "rtmp://192.168.10.5:1935/live/tlive001";
const char *output_url = "rtmp://192.168.10.8:1935/live/tlive001";
RtmpStreamContext ctx;
memset(&ctx, 0, sizeof(RtmpStreamContext));

186
src/video_record_thread.cc Normal file
View File

@@ -0,0 +1,186 @@
// 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_record_thread.h"
#include <unistd.h>
#include "libav_utils.h"
// 初始化 MP4 流上下文
int init_mp4_stream(Mp4StreamContext *ctx, const char *output_url, int width, int height, int fps)
{
int ret;
// 分配输出格式上下文
avformat_alloc_output_context2(&ctx->output_ctx, NULL, "mp4", output_url);
if (!ctx->output_ctx)
{
fprintf(stderr, "Could not create output context\n");
return -1;
}
// 查找视频编码器
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
return -1;
}
// 分配编码器上下文
ctx->codec_ctx = avcodec_alloc_context3(codec);
if (!ctx->codec_ctx)
{
fprintf(stderr, "Could not allocate codec context\n");
return -1;
}
// 设置编码器参数
ctx->codec_ctx->codec_id = AV_CODEC_ID_H264;
ctx->codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
ctx->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
ctx->codec_ctx->width = width;
ctx->codec_ctx->height = height;
ctx->codec_ctx->time_base = (AVRational){1, fps};
ctx->codec_ctx->framerate = (AVRational){fps, 1};
// 打开编码器
ret = avcodec_open2(ctx->codec_ctx, codec, NULL);
if (ret < 0)
{
fprintf(stderr, "Could not open codec: %s\n", get_av_error(ret));
return ret;
}
// 创建视频流
ctx->video_stream = avformat_new_stream(ctx->output_ctx, NULL);
if (!ctx->video_stream)
{
fprintf(stderr, "Could not create new stream\n");
return -1;
}
ctx->video_stream->time_base = ctx->codec_ctx->time_base;
ret = avcodec_parameters_from_context(ctx->video_stream->codecpar, ctx->codec_ctx);
if (ret < 0)
{
fprintf(stderr, "Could not copy codec parameters: %s\n", get_av_error(ret));
return ret;
}
// 打开输出文件
if (!(ctx->output_ctx->oformat->flags & AVFMT_NOFILE))
{
ret = avio_open(&ctx->output_ctx->pb, output_url, AVIO_FLAG_WRITE);
if (ret < 0)
{
fprintf(stderr, "Could not open output file: %s\n", get_av_error(ret));
return ret;
}
}
// 写入文件头
ret = avformat_write_header(ctx->output_ctx, NULL);
if (ret < 0)
{
fprintf(stderr, "Error occurred when opening output file: %s\n", get_av_error(ret));
return ret;
}
return 0;
}
// 保存一帧视频到 MP4 文件
void save_mp4(Mp4StreamContext *ctx, AVFrame *frame)
{
int ret;
AVPacket *pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stderr, "Could not allocate packet\n");
return;
}
// 发送帧到编码器
ret = avcodec_send_frame(ctx->codec_ctx, frame);
if (ret < 0)
{
fprintf(stderr, "Error sending a frame for encoding: %s\n", get_av_error(ret));
av_packet_free(&pkt);
return;
}
// 从编码器接收数据包
while (ret >= 0)
{
ret = avcodec_receive_packet(ctx->codec_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
{
fprintf(stderr, "Error during encoding: %s\n", get_av_error(ret));
break;
}
// 调整时间戳
av_packet_rescale_ts(pkt, ctx->codec_ctx->time_base, ctx->video_stream->time_base);
pkt->stream_index = ctx->video_stream->index;
// 写入数据包到文件
ret = av_interleaved_write_frame(ctx->output_ctx, pkt);
if (ret < 0)
{
fprintf(stderr, "Error muxing packet: %s\n", get_av_error(ret));
break;
}
av_packet_unref(pkt);
}
av_packet_free(&pkt);
}
// 推送 MP4 处理线程函数
void *push_mp4_handler_thread(void *arg)
{
ThreadArgs *thread_args = (ThreadArgs *)arg;
Mp4StreamContext ctx;
memset(&ctx, 0, sizeof(Mp4StreamContext));
// 初始化输出流
if (init_mp4_stream(&ctx, "./1.mp4", 1920, 1080, 25) < 0)
{
fprintf(stderr, "Failed to initialize Mp4 stream\n");
return NULL;
}
// Main processing loop
while (1)
{
QueueItem item;
memset(&item, 0, sizeof(QueueItem));
if (dequeue(thread_args->origin_frame_queue, &item))
{
if (item.type == ONLY_FRAME && item.data)
{
AVFrame *frame = (AVFrame *)item.data;
save_mp4(&ctx, frame);
av_frame_free(&frame);
}
}
}
av_write_trailer(ctx.output_ctx);
avcodec_free_context(&ctx.codec_ctx);
avio_closep(&ctx.output_ctx->pb);
avformat_free_context(ctx.output_ctx);
return NULL;
}