使用 FFMPEG 库接收 RTSP 流

Receiving RTSP stream using FFMPEG library

本文关键字:RTSP FFMPEG 使用      更新时间:2023-10-16

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

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

(带身份验证)

因此,我想使用C/C++编程ffmpeg库来实现相同的目标。我想这一定是可能的。

因此,让我提出两个简单的问题:

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

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

对于 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 << " 255n";
                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);
}

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
                        << " 255n";
                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)。

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

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

这是解决方案:-

$ cd ffmpeg 
$export LD_RUN_PATH=/usr/local/lib:/usr/lib:/lib 
$ ./configure
$ make && make install

然后编译您的应用程序。