{"id":241,"date":"2025-04-30T16:40:03","date_gmt":"2025-04-30T08:40:03","guid":{"rendered":"https:\/\/blog.superfyx.top\/?p=241"},"modified":"2025-04-30T16:40:03","modified_gmt":"2025-04-30T08:40:03","slug":"%e5%9f%ba%e4%ba%8effmpeg%e5%92%8copencv%e7%9a%84rtmp%e6%b5%81%e6%92%ad%e6%94%be%e5%99%a8%e5%bc%80%e5%8f%91%e5%ae%9e%e5%bd%95","status":"publish","type":"post","link":"https:\/\/blog.superfyx.top\/?p=241","title":{"rendered":"\u57fa\u4e8eFFmpeg\u548cOpenCV\u7684RTMP\u6d41\u64ad\u653e\u5668\u5f00\u53d1\u5b9e\u5f55"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">1. \u9879\u76ee\u80cc\u666f<\/h3>\n\n\n\n<p>\u672c\u9879\u76ee\u65e8\u5728\u5b9e\u73b0\u4e00\u4e2a\u9ad8\u6548\u7684RTMP\u89c6\u9891\u6d41\u64ad\u653e\u5668\uff0c\u652f\u63011080P\u6d41\u7545\u64ad\u653e\uff0c\u91c7\u7528C++\u5f00\u53d1\uff0c\u5e95\u5c42\u4f9d\u8d56FFmpeg\u8fdb\u884c\u89e3\u7801\uff0cOpenCV\u8fdb\u884c\u56fe\u50cf\u663e\u793a\u3002\u9879\u76ee\u652f\u6301\u591a\u7ebf\u7a0b\u89e3\u7801\u4e0e\u5e27\u7f13\u51b2\uff0c\u9002\u5408\u65e0\u4eba\u673a\u3001\u76d1\u63a7\u7b49\u5b9e\u65f6\u89c6\u9891\u573a\u666f\u3002<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u5f00\u53d1\u73af\u5883<\/h3>\n\n\n\n<ul>\n<li>\u64cd\u4f5c\u7cfb\u7edf\uff1aWindows 10\/11<\/li>\n\n\n\n<li>\u7f16\u8bd1\u5668\uff1aVSCode<\/li>\n\n\n\n<li>\u5305\u7ba1\u7406\uff1avcpkg<\/li>\n\n\n\n<li>\u4e3b\u8981\u4f9d\u8d56\uff1a<\/li>\n\n\n\n<li>FFmpeg<\/li>\n\n\n\n<li>OpenCV<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u73af\u5883\u642d\u5efa<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">3.1 \u5b89\u88c5vcpkg<\/h4>\n\n\n\n<p>powershell<\/p>\n\n\n\n<pre class=\"wp-block-code has-tertiary-background-color has-background\"><code>git clone https:\/\/github.com\/microsoft\/vcpkg.git\r\ncd vcpkg\r\n.\\bootstrap-vcpkg.bat<\/code><\/pre>\n\n\n\n<p>3.2 \u5b89\u88c5\u4f9d\u8d56\u5e93<\/p>\n\n\n\n<pre class=\"wp-block-code has-tertiary-background-color has-background\"><code># \u5b89\u88c5FFmpeg\u548cOpenCV\r\n.\\vcpkg install ffmpeg:x64-windows\r\n.\\vcpkg install opencv4:x64-windows<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">4. CMake\u9879\u76ee\u914d\u7f6e<\/h4>\n\n\n\n<p>CMakeLists.txt&nbsp;\u793a\u4f8b\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code has-tertiary-background-color has-background has-small-font-size\"><code>cmake_minimum_required(VERSION 3.10)\nproject(ffmpeg_rtmp_demo)\n\nset(CMAKE_CXX_STANDARD 11)\n\n# FFmpeg\u548cOpenCV\u8def\u5f84\uff08\u6839\u636evcpkg\u5b89\u88c5\u8def\u5f84\u8c03\u6574\uff09\ninclude_directories(\"E:\/greensoft\/vcpkg\/installed\/x64-windows\/include\")\nlink_directories(\"E:\/greensoft\/vcpkg\/installed\/x64-windows\/lib\")\n\nfind_package(OpenCV REQUIRED)\ninclude_directories(${OpenCV_INCLUDE_DIRS})\n\nadd_executable(ffmpeg_rtmp_demo main.cpp)\n\ntarget_link_libraries(ffmpeg_rtmp_demo PRIVATE\n    avformat\n    avcodec\n    avutil\n    swscale\n    swresample\n    ${OpenCV_LIBS}\n)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading has-large-font-size\">5. \u4ee3\u7801\u5b9e\u73b0<\/h4>\n\n\n\n<p>5.1 \u4ee3\u7801\u6d41\u7a0b<\/p>\n\n\n\n<ul>\n<li>\u4f7f\u7528FFmpeg\u89e3\u7801RTMP\u6d41\uff0cOpenCV\u663e\u793a<\/li>\n\n\n\n<li>\u89e3\u7801\u4e0e\u663e\u793a\u5206\u7ebf\u7a0b\uff0c\u91c7\u7528\u751f\u4ea7\u8005-\u6d88\u8d39\u8005\u961f\u5217\u7f13\u51b2<\/li>\n\n\n\n<li>\u652f\u6301\u5e27\u7387\u7edf\u8ba1\u3001PTS\u540c\u6b65\u663e\u793a\uff0c\u63d0\u5347\u6d41\u7545\u5ea6<\/li>\n\n\n\n<li>\u53ef\u8c03\u8282\u7f13\u51b2\u533a\u5927\u5c0f\uff0c\u517c\u987e\u5ef6\u8fdf\u4e0e\u6d41\u7545<\/li>\n<\/ul>\n\n\n\n<p class=\"has-medium-font-size\">5.2\u00a0\u5173\u952e\u4ee3\u7801\u7ed3\u6784<\/p>\n\n\n\n<p>main.cpp\u00a0\u4e3b\u8981\u7ed3\u6784\u5982\u4e0b\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code has-tertiary-background-color has-background has-small-font-size\"><code>#include &lt;iostream>\n#include &lt;opencv2\/opencv.hpp>\n#include &lt;queue>\n#include &lt;thread>\n#include &lt;mutex>\nextern \"C\" {\n#include &lt;libavformat\/avformat.h>\n#include &lt;libavcodec\/avcodec.h>\n#include &lt;libswscale\/swscale.h>\n#include &lt;libavutil\/imgutils.h>\n#include &lt;libavutil\/time.h>\n}\n\n\/\/ \u5e27\u7ed3\u6784\u4f53\nstruct Frame {\n    cv::Mat image;\n    int64_t pts;\n};\n\n\/\/ \u5168\u5c40\u53d8\u91cf\nstd::queue&lt;Frame> frameQueue;\nstd::mutex mtx;\nbool running = true;\nconst size_t MAX_QUEUE_SIZE = 20;\n\n\/\/ \u89e3\u7801\u7ebf\u7a0b\nvoid decode_thread(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, SwsContext* sws_ctx, int video_stream_index, uint8_t* bgr_buffer, int linesize) {\n    AVPacket* pkt = av_packet_alloc();\n    AVFrame* frame = av_frame_alloc();\n    AVFrame* bgr_frame = av_frame_alloc();\n    av_image_fill_arrays(bgr_frame->data, bgr_frame->linesize, bgr_buffer, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);\n\n    while (running &amp;&amp; av_read_frame(fmt_ctx, pkt) >= 0) {\n        if (pkt->stream_index == video_stream_index) {\n            if (avcodec_send_packet(codec_ctx, pkt) == 0) {\n                while (avcodec_receive_frame(codec_ctx, frame) == 0) {\n                    while (running &amp;&amp; frameQueue.size() >= MAX_QUEUE_SIZE) {\n                        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n                    }\n                    if (!running) break;\n                    sws_scale(sws_ctx, frame->data, frame->linesize, 0, codec_ctx->height, bgr_frame->data, bgr_frame->linesize);\n                    int64_t pts = frame->pts;\n                    if (pts == AV_NOPTS_VALUE) pts = frame->best_effort_timestamp;\n                    Frame f;\n                    f.image = cv::Mat(codec_ctx->height, codec_ctx->width, CV_8UC3, bgr_frame->data&#91;0], bgr_frame->linesize&#91;0]).clone();\n                    f.pts = pts;\n                    std::lock_guard&lt;std::mutex> lock(mtx);\n                    frameQueue.push(f);\n                }\n            }\n        }\n        av_packet_unref(pkt);\n    }\n    av_frame_free(&amp;frame);\n    av_frame_free(&amp;bgr_frame);\n    av_packet_free(&amp;pkt);\n}\n\nint main() {\n    avformat_network_init();\n    const char* url = \"rtmp:\/\/192.168.1.1\/test\/drone\";\n    AVFormatContext* fmt_ctx = nullptr;\n\n    \/\/ \u7f51\u7edc\u53c2\u6570\u53ef\u8c03\n    AVDictionary* options = nullptr;\n    av_dict_set(&amp;options, \"rtsp_transport\", \"tcp\", 0);\n    av_dict_set(&amp;options, \"stimeout\", \"3000000\", 0);\n    av_dict_set(&amp;options, \"buffer_size\", \"4096000\", 0);\n    av_dict_set(&amp;options, \"max_delay\", \"1000000\", 0);\n    av_dict_set(&amp;options, \"fflags\", \"nobuffer\", 0);\n    av_dict_set(&amp;options, \"tune\", \"zerolatency\", 0);\n\n    if (avformat_open_input(&amp;fmt_ctx, url, nullptr, &amp;options) &lt; 0) return -1;\n    av_dict_free(&amp;options);\n    if (avformat_find_stream_info(fmt_ctx, nullptr) &lt; 0) return -1;\n\n    int video_stream_index = -1;\n    for (unsigned i = 0; i &lt; fmt_ctx->nb_streams; ++i) {\n        if (fmt_ctx->streams&#91;i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {\n            video_stream_index = i; break;\n        }\n    }\n    if (video_stream_index == -1) return -1;\n\n    AVCodecParameters* codecpar = fmt_ctx->streams&#91;video_stream_index]->codecpar;\n    const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);\n    if (!codec) return -1;\n    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);\n    avcodec_parameters_to_context(codec_ctx, codecpar);\n\n    \/\/ \u89e3\u7801\u5668\u53c2\u6570\n    codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;\n    codec_ctx->flags2 |= AV_CODEC_FLAG2_FAST;\n    codec_ctx->thread_count = 4;\n    codec_ctx->thread_type = FF_THREAD_FRAME;\n\n    if (avcodec_open2(codec_ctx, codec, nullptr) &lt; 0) return -1;\n\n    SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,\n                                         codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24,\n                                         SWS_BILINEAR, nullptr, nullptr, nullptr);\n\n    int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);\n    uint8_t* bgr_buffer = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));\n\n    std::thread decode_t(decode_thread, fmt_ctx, codec_ctx, sws_ctx, video_stream_index, bgr_buffer, num_bytes);\n\n    cv::namedWindow(\"RTMP Stream\", cv::WINDOW_NORMAL);\n    cv::resizeWindow(\"RTMP Stream\", codec_ctx->width, codec_ctx->height);\n\n    \/\/ \u5e27\u540c\u6b65\u663e\u793a\n    AVRational time_base = fmt_ctx->streams&#91;video_stream_index]->time_base;\n    int64_t start_pts = -1;\n    int64_t start_time_us = 0;\n    while (running) {\n        Frame frame;\n        bool has_frame = false;\n        {\n            std::lock_guard&lt;std::mutex> lock(mtx);\n            if (!frameQueue.empty()) {\n                frame = frameQueue.front();\n                frameQueue.pop();\n                has_frame = true;\n            }\n        }\n        if (has_frame) {\n            if (start_pts == -1) {\n                start_pts = frame.pts;\n                start_time_us = av_gettime();\n            }\n            int64_t pts_time = (int64_t)((frame.pts - start_pts) * av_q2d(time_base) * 1000000) + start_time_us;\n            int64_t now = av_gettime();\n            if (pts_time > now) {\n                std::this_thread::sleep_for(std::chrono::microseconds(pts_time - now));\n            } else if (now - pts_time > 300000) {\n                continue;\n            }\n            cv::imshow(\"RTMP Stream\", frame.image);\n        }\n        if (cv::waitKey(1) == 'q') {\n            running = false;\n            break;\n        }\n    }\n    decode_t.join();\n    cv::destroyAllWindows();\n    av_free(bgr_buffer);\n    sws_freeContext(sws_ctx);\n    avcodec_free_context(&amp;codec_ctx);\n    avformat_close_input(&amp;fmt_ctx);\n    avformat_network_deinit();\n    return 0;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">6.\u5e38\u89c1\u95ee\u9898\u4e0e\u4f18\u5316<\/h4>\n\n\n\n<ul>\n<li>\u5934\u6587\u4ef6\u627e\u4e0d\u5230\uff1a\u786e\u4fdd&nbsp;includePath\u3001CMakeLists.txt&nbsp;\u914d\u7f6e\u6b63\u786e\uff0cvcpkg\u96c6\u6210\u5230VS\u3002<\/li>\n\n\n\n<li>\u4e2d\u6587\u4e71\u7801\/\u7f16\u7801\u8b66\u544a\uff1a\u6240\u6709\u6e90\u6587\u4ef6\u4fdd\u5b58\u4e3a UTF-16 \u7f16\u7801\u3002<\/li>\n\n\n\n<li>\u5ef6\u8fdf\u4e0e\u5361\u987f\uff1a\u53ef\u8c03\u8282 MAX_QUEUE_SIZE\u3001\u7f51\u7edc\u53c2\u6570\u3001\u89e3\u7801\u7ebf\u7a0b\u6570\uff0c\u5e73\u8861\u6d41\u7545\u4e0e\u5b9e\u65f6\u6027\u3002<\/li>\n\n\n\n<li>1080P\u5361\u987f\uff1a\u9002\u5f53\u589e\u5927\u7f13\u51b2\u3001\u63d0\u5347\u89e3\u7801\u7ebf\u7a0b\u6570\uff0c\u6216\u7528\u66f4\u9ad8\u6027\u80fd\u7684CPU\u3002<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<p><canvas width=\"0\" height=\"90\"><\/canvas><canvas width=\"0\" height=\"90\"><\/canvas><\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. \u9879\u76ee\u80cc\u666f \u672c\u9879\u76ee\u65e8\u5728\u5b9e\u73b0\u4e00\u4e2a\u9ad8\u6548\u7684RTMP\u89c6\u9891\u6d41\u64ad\u653e\u5668\uff0c\u652f\u63011080P\u6d41\u7545\u64ad\u653e\uff0c\u91c7\u7528C++\u5f00\u53d1\uff0c\u5e95\u5c42\u4f9d\u8d56 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1,26],"tags":[20,19],"_links":{"self":[{"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/posts\/241"}],"collection":[{"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=241"}],"version-history":[{"count":3,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/posts\/241\/revisions"}],"predecessor-version":[{"id":244,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=\/wp\/v2\/posts\/241\/revisions\/244"}],"wp:attachment":[{"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=241"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=241"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.superfyx.top\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=241"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}