av_interleaved_write_frame()写入耗时逐渐增大(从1ms -> 50ms)

三幕之秋 2025-04-09 14:43:44

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,且不会降下来,一直找不到是什么原因.

...全文
55 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

4

社区成员

发帖
与我相关
我的任务
社区描述
本社区旨在为FFMPEG交流使用
音视频ffmpeg视频编解码 技术论坛(原bbs)
社区管理员
  • 亭台六七座
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

本社区旨在FFMPEG学习交流

试试用AI创作助手写篇文章吧