Получение RTSP-потока с использованием библиотеки FFMPEG

У меня есть IP-камера в моей локальной сети для потоковой передачи видео с использованием RTSP. Мне удалось захватить и отобразить его успешно с помощью команды ffplay:

ffplay rtsp://admin:[email protected]:7070 

(с аутентификацией)

Поэтому я хотел бы добиться того же, используя программирование на C/C++, используя библиотеку ffmpeg. Я думаю, это должно быть возможно.

Итак, позвольте мне сформулировать два простых вопроса:

  1. Как получить поток в программе C/C++, используя библиотеку FFMPEG? (просто предоставьте какой-нибудь URL/учебник, так как Google не помог)

  2. Как отобразить полученное видео? (то же самое здесь, хороший URL, чтобы направить меня).


person Bhanu Challa    schedule 23.05.2012    source источник


Ответы (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);
}
person technique    schedule 15.06.2012
comment
Спасибо за ответ. Я установил ffmpeg и x264, следуя этим инструкциям. Установка прошла успешно, и библиотеки установлены в /usr/lib моей системы Ubuntu. И когда я пытаюсь скомпилировать ваш код (из /home/bhanu/main.cpp), используя Но я сталкиваюсь с некоторыми проблемами с соответствующими флагами, я продолжаю получать ошибки компоновщика. Я опубликую их, пожалуйста, подскажите, в чем/где может быть ошибка? - person Bhanu Challa; 13.07.2012
comment
Я использую команду 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 и несколько подобных ошибок. Пожалуйста, перейдите по ссылке здесь для полного вывода компилятора. - person Bhanu Challa; 13.07.2012
comment
cnt <1000) {//read 100 frames похоже, вместо этого код считывает 1000 кадров... - person Pimgd; 19.08.2014
comment
Я создал суть для журналов ошибок, которые генерируются при декодировании потока RTSP с помощью FFMPEG. gist.github.com/anonymous/64b3df25b7f103c0da54 Любая идея, как разрешить указанные выше журналы ошибок. Отображаемое изображение размыто. - person Tariq; 27.06.2015
comment
@technique Это было полезно для меня, и я добавил обновленную версию в качестве нового ответа, который работает с библиотеками с ffmpeg 3.2.2 на случай, если кто-нибудь найдет это через Google. Они изменили некоторые функции и перечисления, которые сначала поставили меня в тупик. - person syntheticgio; 11.01.2017
comment
безопасна ли утечка памяти? - person hagor; 27.03.2018
comment
@hagor Я не видел проблем с памятью с подобным кодом. - person syntheticgio; 09.05.2018

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 для отсутствующих библиотек).

person syntheticgio    schedule 11.01.2017
comment
после вашего обновления кое-что изменилось. Пожалуйста, рассмотрите возможность добавления новой версии. Спасибо :-) - person Folkert van Heusden; 10.08.2017
comment
@FolkertvanHeusden Я работаю только с 3.2.2 (и мне не приходилось делать даже этого какое-то время), поэтому, к сожалению, я не в курсе каких-либо новых функций или изменений :( - person syntheticgio; 09.05.2018
comment
Незначительное редактирование: для успешной компиляции вам понадобятся libavcodec и libavutil: g++ -w my_streamer.cpp -o my_streamer $(pkg-config --cflags --libs libavcodec libavformat libswscale libavutil) - person Bob Cheng; 06.01.2021
comment
Ценю это @BobCheng - не уверен, что моя установка позволяла мне не включать их по какой-то причине, но все же работала. Я добавил это в ответ для всех, кто копирует/вставляет - person syntheticgio; 07.01.2021

FFmpeg может напрямую открывать потоки rtsp так же, как открывать локальные видеофайлы. Вот приведен код руководства, который обновляется самой последней версией FFmpeg.

person Qin Peixi    schedule 18.01.2014

проверьте файл ./configure. Может быть, он компилируется для armlinux, а не для x86. вот почему вы получаете неопределенную ссылку на 'avcodec_register_all'

Вот решение: -

$ cd ffmpeg 

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

$ ./configure

$ make && make install

И после этого скомпилируйте ваше приложение.

person Prashant    schedule 13.12.2012