ffmpeg学习(14)流媒体应用(1)拉流 您所在的位置:网站首页 点积和叉积 ffmpeg学习(14)流媒体应用(1)拉流

ffmpeg学习(14)流媒体应用(1)拉流

2023-04-10 03:13| 来源: 网络整理| 查看: 265

前面基本介绍了整个ffmepg的编解码相关的内容,本文介绍ffmpeg在流媒体上的应用,主要是流媒体的发送和接收。通常流媒体应用协议有有rtmp、rtsp,以及其他的流媒体协议如http、udp、rtp等。直接使用ffmpeg.exe命令行工具介绍见文章FFmpeg发送流媒体的命令(UDP,RTP,RTMP)。

本文简单介绍流媒体接收的功能(拉流),将一个流媒体的裸流保存为文件,并比较不同流媒体协议的处理区别。

后文。。。。。。链接。。ffmpeg学习(15)流媒体应用(2)推流。。。。。将介绍流媒体推送的功能(推流),将本地文件、直播流推送到流媒体服务器,并比较不同流媒体协议的处理区别。

简单示例代码

指定一个流媒体地址,将解析的图像AVPacket保存到文件,保存文件时未使用封装。

#include #ifdef __cplusplus extern "C" { #endif #include "libavformat/avformat.h" #include "libavutil/time.h" #ifdef __cplusplus } #endif int main() { int ret; // 打开输入 const char* input_file = "rtmp://58.200.131.2:1935/livetv/cctv1"; //const char* input_file = "http://192.168.3.86:9091/videos/record/XUNI0000000001-20201026_064506.flv"; AVFormatContext* input_fmt_ctx = NULL; // 必须设置NULL if((ret = avformat_open_input(&input_fmt_ctx, input_file, NULL, NULL)) av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n"); return ret; } ret = av_find_best_stream(input_fmt_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0); if(ret av_log(NULL, AV_LOG_ERROR, "Cannot open file\n"); return -1; } uint64_t frame_index=0; AVPacket *pkt = av_packet_alloc(); while(1) { if(ret = av_read_frame(input_fmt_ctx, pkt) stream_index == stream_index_video) { #ifdef NEED_MP4TOANNEXB_FILTER if(av_bsf_send_packet(bsf_ctx, pkt) == 0) { while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) { fwrite(pkt->data, 1, pkt->size, fp); } } #else fwrite(pkt->data, 1, pkt->size, fp); #endif printf("save frame: %llu , time: %lld\n", ++frame_index, av_gettime()/1000); // ms } av_packet_unref(pkt); } // 关闭输入 avformat_close_input(&input_fmt_ctx); av_packet_free(&pkt); fclose(fp); #ifdef NEED_MP4TOANNEXB_FILTER av_bsf_free(&bsf_ctx); #endif return 0; } (1)裸流处理

前面已经介绍多次,图像压缩编码数据有两种格式,区别在于有无起始码start code。如果解析的packet是含有起始码,那么可以直接进行保存。否则需要使用h264_mp4toannexb_filter过滤处理后处理。 使用时建议给 AVBSFContext 配置待处理的视频流参数,否则可能处理失败。

avcodec_parameters_copy(bsf_ctx->par_in, input_fmt_ctx->streams[sream_index_video]->codecpar); (2)输入不同影响保存速度

示例代码中,如果输入是一个直播流如rtmp地址,在解析码流时ffmpeg将以一定时间间隔(对应帧率)获取每一帧数据,之后便保存每一帧。 在这里插入图片描述 但是,当输入是非直播的网络文件如http地址,那么在解析码流时ffmpeg将以最高的解析速度获取数据包,直观感受就是以最快的下载速度存储。如截图,输入帧率为31、时长1.5s的视频,10ms就保存结束。 在这里插入图片描述 这种情况下,需要考虑磁盘io的性能。同样,在后文推流本地文件时需要给一定帧间隔延时,使流媒体服务器能有能力处理。

示例代码(保存封装,不涉及编解码)

整个代码流程类似Demux过程,参考 ffmpeg学习(9)音视频文件demuxer 将解析的压缩编码数据直接保存在封装文件中,不涉及编解码和参数修改。

#include #ifdef __cplusplus extern "C" { #endif #include "libavformat/avformat.h" #include "libavutil/time.h" #ifdef __cplusplus } #endif #include bool running = true; void sig_handle(int signo) { running = false; } int main() { signal(SIGINT, sig_handle); int ret; AVFormatContext* input_fmt_ctx = NULL; AVFormatContext* output_fmt_ctx = NULL; // 输入输出 const char* input_file = "rtmp://58.200.131.2:1935/livetv/cctv1"; //const char* input_file = "http://192.168.3.86:9091/videos/record/XUNI0000000001-20201026_064506.flv"; if((ret = avformat_open_input(&input_fmt_ctx, input_file, NULL, NULL)) av_log(NULL, AV_LOG_ERROR, "Cannot alloc out avformat\n"); return ret; } // 分析流信息 if((ret = avformat_find_stream_info(input_fmt_ctx, NULL)) AVStream *in_stream = input_fmt_ctx->streams[i]; AVStream *out_stream = avformat_new_stream(output_fmt_ctx, NULL); if(!out_stream) { av_log(NULL, AV_LOG_ERROR, "Cannot add stream\n"); return AVERROR_UNKNOWN; } ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); out_stream->codecpar->codec_tag = 0; if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) stream_index_video = i; else if(in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) stream_index_audio = i; } // 输出封装处理 if(output_fmt_ctx && !(output_fmt_ctx->flags & AVFMT_NOFILE)) { ret = avio_open(&output_fmt_ctx->pb, output_file, AVIO_FLAG_WRITE); if(ret av_log(NULL, AV_LOG_ERROR, "Cannot write header\n"); return ret; } // 打印输出信息 av_dump_format(output_fmt_ctx, 0, output_file, 1); //#define NEED_MP4TOANNEXB_FILTER #ifdef NEED_MP4TOANNEXB_FILTER const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb"); AVBSFContext *bsf_ctx; av_bsf_alloc(bsf, &bsf_ctx); // 这里必须添加,否则AVPacket处理可能无效 avcodec_parameters_copy(bsf_ctx->par_in, input_fmt_ctx->streams[stream_index_video]->codecpar); av_bsf_init(bsf_ctx); #endif uint64_t frame_index=0; AVPacket *pkt = av_packet_alloc(); int stream_index; while(running) { if(ret = av_read_frame(input_fmt_ctx, pkt) stream_index; av_packet_rescale_ts(pkt, input_fmt_ctx->streams[stream_index]->time_base, output_fmt_ctx->streams[stream_index]->time_base ); #ifdef NEED_MP4TOANNEXB_FILTER if(av_bsf_send_packet(bsf_ctx, pkt) == 0) { while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) { //fwrite(pkt->data, 1, pkt->size, fp); av_interleaved_write_frame(output_fmt_ctx, pkt); } } #else av_interleaved_write_frame(output_fmt_ctx, pkt); #endif //Print to Screen if(stream_index == stream_index_video) printf("save frame: %llu , time: %lld\n", ++frame_index, av_gettime() / 1000); // ms av_packet_unref(pkt); } //Write file trailer if((ret = av_write_trailer(output_fmt_ctx)) avio_close(output_fmt_ctx->pb); } avformat_free_context(output_fmt_ctx); #ifdef NEED_MP4TOANNEXB_FILTER av_bsf_free(&bsf_ctx); #endif return 0; }

注意以下两点:

(1) 程序关闭机制

因为保存封装文件到本地,需要正确写文件头和文件尾。直播流可能一直存在,提前退出保证手动关闭程序正确执行所有代码,因此加入了signal处理以退出while循环(否则可能影响封装信息)。

(2) 时间戳的转换

因为重新编解码,参数未调整,不处理时间戳转换,保存封装文件也是能正常播放的,但看到是封装信息可能会混乱,参考ffmpeg 时基timebase、时间戳pts/dts、延时控制delay。

分别尝试保存为三种封装格式,使用MediaInfo查看信息如下 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有