mirror of
https://github.com/wwhai/generic-rtsp-yolov8-render.git
synced 2025-12-24 12:12:50 +08:00
dev: update
This commit is contained in:
@@ -25,6 +25,8 @@ extern "C"
|
||||
#include <SDL2/SDL.h>
|
||||
}
|
||||
#include "frame_queue.h"
|
||||
// 获取错误字符串的全局缓冲区实现
|
||||
const char *get_av_error(int errnum);
|
||||
// 函数用于复制AVFrame
|
||||
// 注意:使用完毕后需要释放
|
||||
// @param srcFrame 源帧
|
||||
|
||||
@@ -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
|
||||
|
||||
63
include/video_record_thread.h
Normal file
63
include/video_record_thread.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
186
src/video_record_thread.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user