使用 FFMPEG 库接收 RTSP 流

Posted

技术标签:

【中文标题】使用 FFMPEG 库接收 RTSP 流【英文标题】:Receiving RTSP stream using FFMPEG library 【发布时间】:2012-05-29 17:16:15 【问题描述】:

我的 LAN 上有一个使用 RTSP 流式传输视频的 IPCamera。我已经能够使用 ffplay 命令成功捕获并显示它:

ffplay rtsp://admin:123456@192.168.2.50:7070 

(有认证)

所以我想使用 ffmpeg 库在 C/C++ 中进行编程来实现相同的目的。我想这一定是可能的。

所以让我提出两个简单的问题:

    如何使用 FFMPEG 库在 C/C++ 程序中接收流? (只需提供一些 URL/教程,因为 google 没有帮助)

    如何显示收到的视频? (这里也一样,一些很好的 URL 可以指导我)。

【问题讨论】:

【参考方案1】:

FWIW,我已经更新了@technique 提供的代码,以使用我从 FFMPEG 3.2.2 获得的库。希望这有助于有人谷歌搜索。有一些小的变化可能会让那些偶然发现这个答案的人感到困惑。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <sstream>

extern "C" 
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libswscale/swscale.h>


int main(int argc, char** argv) 

    // Open the initial context variables that are needed
    SwsContext *img_convert_ctx;
    AVFormatContext* format_ctx = avformat_alloc_context();
    AVCodecContext* codec_ctx = NULL;
    int video_stream_index;

    // Register everything
    av_register_all();
    avformat_network_init();

    //open RTSP
    if (avformat_open_input(&format_ctx, "rtsp://134.169.178.187:8554/h264.3gp",
            NULL, NULL) != 0) 
        return EXIT_FAILURE;
    

    if (avformat_find_stream_info(format_ctx, NULL) < 0) 
        return EXIT_FAILURE;
    

    //search video stream
    for (int i = 0; i < format_ctx->nb_streams; i++) 
        if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            video_stream_index = i;
    

    AVPacket packet;
    av_init_packet(&packet);

    //open output file
    AVFormatContext* output_ctx = avformat_alloc_context();

    AVStream* stream = NULL;
    int cnt = 0;

    //start reading packets from stream and write them to file
    av_read_play(format_ctx);    //play RTSP

    // Get the codec
    AVCodec *codec = NULL;
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) 
        exit(1);
    

    // Add this to allocate the context by codec
    codec_ctx = avcodec_alloc_context3(codec);

    avcodec_get_context_defaults3(codec_ctx, codec);
    avcodec_copy_context(codec_ctx, format_ctx->streams[video_stream_index]->codec);
    std::ofstream output_file;

    if (avcodec_open2(codec_ctx, codec, NULL) < 0)
        exit(1);

    img_convert_ctx = sws_getContext(codec_ctx->width, codec_ctx->height,
            codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_RGB24,
            SWS_BICUBIC, NULL, NULL, NULL);

    int size = avpicture_get_size(AV_PIX_FMT_YUV420P, codec_ctx->width,
            codec_ctx->height);
    uint8_t* picture_buffer = (uint8_t*) (av_malloc(size));
    AVFrame* picture = av_frame_alloc();
    AVFrame* picture_rgb = av_frame_alloc();
    int size2 = avpicture_get_size(AV_PIX_FMT_RGB24, codec_ctx->width,
            codec_ctx->height);
    uint8_t* picture_buffer_2 = (uint8_t*) (av_malloc(size2));
    avpicture_fill((AVPicture *) picture, picture_buffer, AV_PIX_FMT_YUV420P,
            codec_ctx->width, codec_ctx->height);
    avpicture_fill((AVPicture *) picture_rgb, picture_buffer_2, AV_PIX_FMT_RGB24,
            codec_ctx->width, codec_ctx->height);

    while (av_read_frame(format_ctx, &packet) >= 0 && cnt < 1000)  //read ~ 1000 frames

        std::cout << "1 Frame: " << cnt << std::endl;
        if (packet.stream_index == video_stream_index)     //packet is video
            std::cout << "2 Is Video" << std::endl;
            if (stream == NULL)     //create stream in file
                std::cout << "3 create stream" << std::endl;
                stream = avformat_new_stream(output_ctx,
                        format_ctx->streams[video_stream_index]->codec->codec);
                avcodec_copy_context(stream->codec,
                        format_ctx->streams[video_stream_index]->codec);
                stream->sample_aspect_ratio =
                        format_ctx->streams[video_stream_index]->codec->sample_aspect_ratio;
            
            int check = 0;
            packet.stream_index = stream->id;
            std::cout << "4 decoding" << std::endl;
            int result = avcodec_decode_video2(codec_ctx, picture, &check, &packet);
            std::cout << "Bytes decoded " << result << " check " << check
                    << std::endl;
            if (cnt > 100)    //cnt < 0)
                    
                sws_scale(img_convert_ctx, picture->data, picture->linesize, 0,
                        codec_ctx->height, picture_rgb->data, picture_rgb->linesize);
                std::stringstream file_name;
                file_name << "test" << cnt << ".ppm";
                output_file.open(file_name.str().c_str());
                output_file << "P3 " << codec_ctx->width << " " << codec_ctx->height
                        << " 255\n";
                for (int y = 0; y < codec_ctx->height; y++) 
                    for (int x = 0; x < codec_ctx->width * 3; x++)
                        output_file
                                << (int) (picture_rgb->data[0]
                                        + y * picture_rgb->linesize[0])[x] << " ";
                
                output_file.close();
            
            cnt++;
        
        av_free_packet(&packet);
        av_init_packet(&packet);
    
    av_free(picture);
    av_free(picture_rgb);
    av_free(picture_buffer);
    av_free(picture_buffer_2);

    av_read_pause(format_ctx);
    avio_close(output_ctx->pb);
    avformat_free_context(output_ctx);

    return (EXIT_SUCCESS);

它可以被编译成这样的效果:

g++ -w my_streamer.cpp -o my_streamer $(pkg-config --cflags --libs  libavcodec libavformat libswscale libavutil)

我将-w 包括在内,因为库中的这些函数有很多弃用警告。您需要安装 pkg-config 以及 libavcodec libavformat libswscale 和 libavutil 库。 (h/t @BobCheng 缺少图书馆)。

【讨论】:

自从您的更新发生了一些变化。请考虑添加新版本。谢谢:-) @FolkertvanHeusden 我只使用 3.2.2(而且我已经有一段时间不需要这样做了),所以很遗憾我没有了解任何新功能或更改的最新信息 :( 次要编辑:您需要 libavcodec 和 libavutil 才能成功编译:g++ -w my_streamer.cpp -o my_streamer $(pkg-config --cflags --libs libavcodec libavformat libswscale libavutil) 欣赏它@BobCheng - 不确定我的设置是否允许我出于某种原因不包括那些但仍然有效。我已将其添加到任何人复制/粘贴的答案中【参考方案2】:

FFmpeg 可以像打开本地视频文件一样直接打开 rtsp 流。 Here 是使用最新版本的 FFmpeg 更新的教程代码。

【讨论】:

【参考方案3】:

检查您的 ./configure 文件。可能是为armlinux 而不是为x86 编译。这就是为什么你得到未定义的引用 'avcodec_register_all'

这里是解决方案:-

$ cd ffmpeg 

$export LD_RUN_PATH=/usr/local/lib:/usr/lib:/lib 

$ ./configure

$ make && make install

然后编译你的应用程序。

【讨论】:

【参考方案4】:

对于 rtsp 流,以下内容对我有用(接收帧后我将结果保存到 ppm 文件):

    #include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <sstream>

extern "C"

    #include <avcodec.h>
    #include <avformat.h>
    #include <avio.h>
    #include <swscale.h>


void log_callback(void *ptr, int level, const char *fmt, va_list vargs)

   static char message[8192];
   const char *module = NULL;

    if (ptr)
    
        AVClass *avc = *(AVClass**) ptr;
        module = avc->item_name(ptr);
    
   vsnprintf_s(message, sizeof(message), fmt, vargs);

   std::cout << "LOG: " << message << std::endl;



int main(int argc, char** argv) 

    SwsContext *img_convert_ctx;
    AVFormatContext* context = avformat_alloc_context();
    AVCodecContext* ccontext = avcodec_alloc_context();
    int video_stream_index;

    av_register_all();
    avformat_network_init();
    //av_log_set_callback(&log_callback);

    //open rtsp
    if(avformat_open_input(&context, "rtsp://134.169.178.187:8554/h264.3gp",NULL,NULL) != 0)
        return EXIT_FAILURE;
    

    if(avformat_find_stream_info(context,NULL) < 0)
        return EXIT_FAILURE;
    

    //search video stream
    for(int i =0;i<context->nb_streams;i++)
        if(context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            video_stream_index = i;
    

    AVPacket packet;
    av_init_packet(&packet);

    //open output file
    //AVOutputFormat* fmt = av_guess_format(NULL,"test2.mp4",NULL);
    AVFormatContext* oc = avformat_alloc_context();
    //oc->oformat = fmt;
    //avio_open2(&oc->pb, "test.mp4", AVIO_FLAG_WRITE,NULL,NULL);

    AVStream* stream=NULL;
    int cnt = 0;
    //start reading packets from stream and write them to file
    av_read_play(context);//play RTSP

    AVCodec *codec = NULL;
    codec = avcodec_find_decoder(CODEC_ID_H264);
    if (!codec) exit(1);

    avcodec_get_context_defaults3(ccontext, codec);
    avcodec_copy_context(ccontext,context->streams[video_stream_index]->codec);
    std::ofstream myfile;

    if (avcodec_open(ccontext, codec) < 0) exit(1);

    img_convert_ctx = sws_getContext(ccontext->width, ccontext->height, ccontext->pix_fmt, ccontext->width, ccontext->height,
                            PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);

    int size = avpicture_get_size(PIX_FMT_YUV420P, ccontext->width, ccontext->height);
    uint8_t* picture_buf = (uint8_t*)(av_malloc(size));
    AVFrame* pic = avcodec_alloc_frame();
    AVFrame* picrgb = avcodec_alloc_frame();
    int size2 = avpicture_get_size(PIX_FMT_RGB24, ccontext->width, ccontext->height);
    uint8_t* picture_buf2 = (uint8_t*)(av_malloc(size2));
    avpicture_fill((AVPicture *) pic, picture_buf, PIX_FMT_YUV420P, ccontext->width, ccontext->height);
    avpicture_fill((AVPicture *) picrgb, picture_buf2, PIX_FMT_RGB24, ccontext->width, ccontext->height);

    while(av_read_frame(context,&packet)>=0 && cnt <1000)
    //read 100 frames

        std::cout << "1 Frame: " << cnt << std::endl;
        if(packet.stream_index == video_stream_index)//packet is video
            std::cout << "2 Is Video" << std::endl;
            if(stream == NULL)
            //create stream in file
                std::cout << "3 create stream" << std::endl;
                stream = avformat_new_stream(oc,context->streams[video_stream_index]->codec->codec);
                avcodec_copy_context(stream->codec,context->streams[video_stream_index]->codec);
                stream->sample_aspect_ratio = context->streams[video_stream_index]->codec->sample_aspect_ratio;
            
            int check = 0;
            packet.stream_index = stream->id;
            std::cout << "4 decoding" << std::endl;
            int result = avcodec_decode_video2(ccontext, pic, &check, &packet);
            std::cout << "Bytes decoded " << result << " check " << check << std::endl;
            if(cnt > 100)//cnt < 0)
            
                sws_scale(img_convert_ctx, pic->data, pic->linesize, 0, ccontext->height, picrgb->data, picrgb->linesize);
                std::stringstream name;
                name << "test" << cnt << ".ppm";
                myfile.open(name.str());
                myfile << "P3 " << ccontext->width << " " << ccontext->height << " 255\n";
                for(int y = 0; y < ccontext->height; y++)
                
                    for(int x = 0; x < ccontext->width * 3; x++)
                        myfile << (int)(picrgb->data[0] + y * picrgb->linesize[0])[x] << " ";
                
                myfile.close();
            
            cnt++;
        
        av_free_packet(&packet);
        av_init_packet(&packet);
    
    av_free(pic);
    av_free(picrgb);
    av_free(picture_buf);
    av_free(picture_buf2);

    av_read_pause(context);
    avio_close(oc->pb);
    avformat_free_context(oc);

    return (EXIT_SUCCESS);

【讨论】:

感谢您的回复。我按照these 的说明安装了ffmpeg 和x264。安装成功,库安装在我的ubuntu系统的/usr/lib下。当我试图编译你的代码(来自/home/bhanu/main.cpp)时,我遇到了一些与适当标志有关的问题,我不断收到链接器错误。我会发布它们,请指导我错误是什么/在哪里? 我使用的命令是g++ main.cpp -lavcodec -lavdevice -lavfilter -lavformat -lavutil -logg -lrtmp -lswscale -lx264 -lpthread -lvorbis -L/usr/lib,我得到以下输出main.cpp: In function 'int main(int, char**)': /home/bhanu/work_environment/softwares/ffmpeg/libavformat/allformats.c:49: undefined reference to 'avcodec_register_all' /usr/lib/libavformat.a(oggparsevorbis.o): In function 'vorbis_packet': collect2: ld returned 1 exit status 和一些类似的错误。完整的编译器输出请参考链接here。 cnt &lt;1000) //read 100 frames 看起来代码读取了 1000 帧而不是... 我为使用 FFMPEG 解码 RTSP 流期间生成的错误日志创建了一个要点。 gist.github.com/anonymous/64b3df25b7f103c0da54 知道如何解决上述错误日志。显示的图片模糊。 @technique 这对我很有帮助,我添加了一个更新版本作为新答案,它与 ffmpeg 3.2.2 的库一起使用,以防有人通过 Google 找到它。他们更改了一些函数和枚举,这些函数和枚举一开始让我陷入了困境。

以上是关于使用 FFMPEG 库接收 RTSP 流的主要内容,如果未能解决你的问题,请参考以下文章

如何通过python实现H.264视频推流与接收

求救 怎样使用ffmpeg函数接收rtsp 网络数据

使用 ffmpeg 将低延迟 RTSP 视频流式传输到 android

无法从 IP cam 接收 RTSP 实时流

调用Live555接收RTSP直播流,转换为Http Live Streaming(iOS直播)协议

使用 VLC 从其他计算机上的 ffmpeg 接收 rtp (opus) 流