J'ai une caméra IP sur mon réseau LAN en streaming vidéo en utilisant RTSP. J'ai pu capturer et afficher avec succès en utilisant la commande ffplay:
ffplay rtsp://admin:[email protected]:7070
(avec authentification)
Je voudrais donc obtenir la même chose en utilisant la programmation en C/C++ en utilisant la bibliothèque ffmpeg
. Je suppose que cela doit être possible.
Alors laissez-moi formuler deux questions simples:
Comment recevoir le flux dans un programme C/C++ utilisant la bibliothèque FFMPEG? (fournissez simplement une URL/tutoriel, car Google n'était pas utile)
Comment afficher la vidéo reçue? (idem ici, une bonne URL pour me diriger).
Pour les flux rtsp, ce qui suit fonctionne pour moi (après avoir reçu des images, j'enregistre le résultat dans un fichier 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);
}
FWIW, j'ai mis à jour le code fourni par @technique pour qu'il fonctionne avec les bibliothèques de FFMPEG 3.2.2 que j'ai. Espérons que cela aide quelqu'un googler cela. Il y a quelques petits changements qui pourraient être déroutants pour les personnes qui tombent sur cette réponse.
#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);
}
Il peut être compilé par quelque chose à l'effet de:
g++ -w my_streamer.cpp -o my_streamer $(pkg-config --cflags --libs libavformat libswscale)
J'inclus le -w
car il y a beaucoup d'avertissements de dépréciation pour ces fonctions dans la bibliothèque. Vous aurez besoin d'avoir pkg-config ainsi que les bibliothèques libavformat et libswscale installées.
FFmpeg peut ouvrir des flux rtsp directement, tout comme ouvrir des fichiers vidéo locaux. Here est un code de didacticiel mis à jour avec la version la plus récente de FFmpeg.
vérifiez votre fichier ./configure. Peut-être sa compilation pour armlinux
pas pour le x86
. C’est pourquoi vous obtenez une référence non définie à 'avcodec_register_all'
Voici la solution: -
$ cd ffmpeg
$export LD_RUN_PATH=/usr/local/lib:/usr/lib:/lib
$ ./configure
$ make && make install
Et après cela, compilez votre application.