4
社区成员




1. rtmp流的配置
bool Camera3::prepare_Push()
{
const char* rtmp_url = "rtmp://uhd8k.com.cn/WX/test";
// 输出的rtmp文件,创建输出格式上下文
if (avformat_alloc_output_context2(&rtmp_ctx, av_guess_format("flv", nullptr, nullptr), NULL, rtmp_url) < 0)
{
std::cout << "rtmp文件上下文创建失败" << "\n";
return false;
}
else
{
std::cout << "rtmp文件上下文创建成功" << "\n";
}
// 分配并初始化AVCodecParameters
AVCodecParameters * avcodec_context = avcodec_parameters_alloc();
if (!avcodec_context) {
std::cout << "无法分配 AVCodecParameters" << "\n";
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
avcodec_context->codec_id = AV_CODEC_ID_HEVC;
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO; // 表示视频类型
// 此值可能会影响到推流的数据帧格式——WX,2025.03.04
avcodec_context->format = AV_PIX_FMT_NV12; // 视频数据像素格式 (AV_PIX_FMT_YUV420P、AV_PIX_FMT_NV12)
avcodec_context->width = info.frameInfo.width; // 视频宽
avcodec_context->height = info.frameInfo.height; // 视频高
// 创建输出码流的AVStream(视频文件中每个视频(音频)流对应一个该结构体),也是初始化视频码流
rtmp_stream = avformat_new_stream(rtmp_ctx, nullptr);
if (!rtmp_stream)
{
std::cout << "推流的输出码流创建失败" << "\n";
// 释放编码器参数AVCodecParameters
avcodec_parameters_free(&avcodec_context);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
// 设置帧率
AVRational frame_rate = { frame_Rate, 1 };
rtmp_stream->time_base = { 1, 1000/* frame_rate.num*/ }; // 时间基——1s分成多少份(ffplay中的时间基为1000,即1k tbn)
rtmp_stream->avg_frame_rate = frame_rate; // 平均帧率
rtmp_stream->r_frame_rate = frame_rate; // 实际帧率
// 输出流编解码器参数分配
rtmp_stream->codecpar = avcodec_parameters_alloc();
if (!rtmp_stream->codecpar) {
std::cout << "申请编码器参数空间失败" << std::endl;
// 释放编码器参数AVCodecParameters
avcodec_parameters_free(&avcodec_context);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
if (avcodec_parameters_copy(rtmp_stream->codecpar, avcodec_context) < 0)
{
std::cout << "无法复制avcodec_context到输出流" << "\n";
// 释放输出流的编码器参数AVCodecParameters
avcodec_parameters_free(&rtmp_stream->codecpar);
// 释放编码器参数AVCodecParameters
avcodec_parameters_free(&avcodec_context);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
// 释放编码器参数AVCodecParameters,因为后续不会再用到此变量
avcodec_parameters_free(&avcodec_context);
rtmp_stream->codecpar->codec_tag = 0;
//输出一下格式------------------
std::cout << "准备完成了" << rtmp_stream->codecpar << "\n";
av_dump_format(rtmp_ctx, 0, rtmp_url, 1); // 打印输出
// 打开输出文件url(Open output file)
if (!(rtmp_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&rtmp_ctx->pb, rtmp_url, AVIO_FLAG_WRITE) < 0) // 打开输出文件
{
std::cout << "Could not open output file :" << rtmp_url << "\n";
// 释放输出流的编码器参数AVCodecParameters
avcodec_parameters_free(&rtmp_stream->codecpar);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
}
// // 添加以下两行,是为了避免推流结束后提示: Failed to update header with correct duration. Failed to update header with correct filesize.
AVDictionary* opts = nullptr;
av_dict_set(&opts, "flvflags", "no_duration_filesize", 0);
// 写文件头(Write file header)
if (avformat_write_header(rtmp_ctx, opts ? &opts : NULL) < 0) { // 将封装格式的信息写入文件头部位置
std::cout << "Error occurred when opening output file\n";
if (rtmp_ctx->pb) {
avio_closep(&rtmp_ctx->pb); // 关闭输出文件url
}
// 释放输出流的编码器参数AVCodecParameters
avcodec_parameters_free(&rtmp_stream->codecpar);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
return false;
}
return true;
}
2. 取出自定义编码器编号的帧,组成AVPacket
void Camera3::preparePacket()
{
while (encIsStart)
{
auto data = packetQueue.wait_for_get_and_pop(); // 获取到队首并出队
if (!data.has_value()) {
if (!encIsStart) break; // 超时且收到停止信号,退出
continue; // 超时但继续运行,继续等待
}
std::cout << "即将推入云端的帧: " << data.value()->getIndex() << std::endl;
std::unique_lock<std::mutex> lock(push_queue_mutex);
if (push_packet_queue.size() < 5) { // 队列积攒的比较少
auto start = std::chrono::high_resolution_clock::now();
// 申请推流数据包的空间
auto pushPacket = av_packet_alloc();
if (!pushPacket) {
std::cout << "分配 AVPacket 失败" << std::endl;
return;
}
// 获取到队首的数据帧和序号
auto& processPacket = data.value()->getData(); // 也可以 *data->getData()
auto packetIndex = data.value()->getIndex();
std::cout << "数据帧" << packetIndex << "即将被推入云端" << processPacket.size() << std::endl;
// 分配独立内存并复制数据
pushPacket->data = (uint8_t*)av_malloc(processPacket.size());
if (!pushPacket->data) {
av_packet_free(&pushPacket);
std::cout << "分配数据内存失败" << std::endl;
return;
}
memcpy(pushPacket->data, processPacket.data(), processPacket.size());
pushPacket->size = processPacket.size();
if (pushPacket->data[0] == 0x00 && pushPacket->data[1] == 0x00 && pushPacket->data[2] == 0x00 && pushPacket->data[3] == 0x01 && pushPacket->data[4] == 0x40)
{
pushPacket->flags = 1; // i帧
std::cout << "当前帧为I帧" << std::endl;
}
else
std::cout << "当前帧为P帧" << std::endl;
// 设置时间戳
pushPacket->pos = -1; // 所在流的位置,-1表示未知,由程序自行探测
pushPacket->stream_index = rtmp_stream->index; // 所在流的索引
pushPacket->pts = packetIndex; /*rtmp_sum++;frame_index;*/
pushPacket->dts = pushPacket->pts;
pushPacket->duration = 1; //帧持续的时间基——使用时间基,计算出每帧的持续时间(占多少个时间基)
// 转换时间戳到输出流的时间基——将 AVPacket 中的 pts、dts 和 duration 从一个时间基转换到另一个时间基。
av_packet_rescale_ts(pushPacket, { 1, frame_Rate }, rtmp_stream->time_base); // ——使用时间基,一次性转换 AVPacket 里的 pts、dts 和 duration 这几个时间戳字段(占多少个时间基)
// 准备好了一帧数据包,添加到队列中,以待后续可以直接写入rtmp流——改写法无需之后手动释放AVPacket
{
auto sp = std::shared_ptr<AVPacket>(
pushPacket,
[](AVPacket* p) {
av_freep(&p->data);
std::cout << "已释放 data 内存" << std::endl;
av_packet_free(&p);
std::cout << "已释放 AVPacket 结构体" << std::endl;
});
std::cout << "入队前的引用计数: " << sp.use_count() << " , " << data.value().use_count() << std::endl;
push_packet_queue.push(sp);
push_cond.notify_one();
}
// av_freep(&pushPacket->data);
// av_packet_free(&pushPacket);
auto end = std::chrono::high_resolution_clock::now();
auto queue_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "复制耗时: " << queue_time.count() << std::endl;
}
}
}
3. 执行推流写入
void Camera3::pushRtmp()
{
while (encIsStart) {
std::shared_ptr<AVPacket> pkt;
{
std::unique_lock<std::mutex> lock(push_queue_mutex);
bool ok = push_cond.wait_for(lock, std::chrono::milliseconds(100), [this] {return !encIsStart || !push_packet_queue.empty(); });
if (!encIsStart)
break; // 结束推流了
if (!ok)
continue; // 超时唤醒
pkt = push_packet_queue.front();
std::cout << "获取队首元素后 shared_ptr 引用计数: " << pkt.use_count() << std::endl;
std::cout << "队列大小:" << push_packet_queue.size();
push_packet_queue.pop();
}
if (pkt)
{
auto start = std::chrono::high_resolution_clock::now();
int ret = av_interleaved_write_frame(rtmp_ctx, pkt.get());
if(ret != 0){
char errbuf[1024] = {0};
av_strerror(ret, errbuf, sizeof(errbuf));
std::cout << "start当前帧写入失败,err: "<< errbuf <<"\n";
}
else
std::cout << "帧数据写入rtmp推流中" << std::endl;
auto end = std::chrono::high_resolution_clock::now();
auto queue_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "推流1帧耗时:" << queue_time.count() << std::endl;
}
std::cout << "推流操作后 shared_ptr 引用计数: " << pkt.use_count() << "\n" << std::endl;
}
// 停止推流
std::cout << "停止推流了" << std::endl;
// 清空AVpacket队列
for (int i = 0; i < push_packet_queue.size(); i++)
{
push_packet_queue.pop();
}
// // 释放掉AVPacket*
// av_packet_free(&pushPacket);
// 将封装格式的信息写入文件尾部位置
av_write_trailer(rtmp_ctx);
// 关闭输出文件url(Close output file)
if (rtmp_ctx && !(rtmp_ctx->oformat->flags & AVFMT_NOFILE)) {
if (rtmp_ctx->pb)
avio_closep(&rtmp_ctx->pb); // 关闭输出文件url
}
// 释放输出流的编码器参数AVCodecParameters
avcodec_parameters_free(&rtmp_stream->codecpar);
// 关闭输入流——感觉此代码不需要
avformat_close_input(&rtmp_ctx);
// 释放rtmp上下文
avformat_free_context(rtmp_ctx);
}
2和3均是在子线程中执行,发现执行过程中,"推流1帧耗时:",在前10s左右,其值在1ms左右,后面就逐渐升高到50ms,且不会降下来,一直找不到是什么原因.