当前位置: 首页 > news >正文

Gstreamer中,使用mp4或者flv作为视频源去推流RTP等视频流时,需要先解码在编码才能正常

前言:

        在Gstreamer中,视频源可以有很多,在很多时候,我们为了测试,会使用MP4等文件作为视频源进行测试,但是发现无论是我自己测试,还是很多网上的命令,都需要先对mp4的h264数据解码以后,再编码为h264,然后再封装和推流,这个就感觉很离谱,今天算是差不多搞明白了。以下是测试步骤:

测试流程:

使用h264文件:

推流:

gst-launch-1.0 -v filesrc location=1080p_60fps.h264 ! h264parse ! rtph264pay ! udpsink host=127.0.0.1 port=8000

播放:

gst-launch-1.0 udpsrc port=8000 ! application/x-rtp, media=video, clock-rate=90000, encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! glimagesink name=vsink sync=false

这个是正常的。

使用MP4文件:

解封装以后进行推流:

gst-launch-1.0 -v filesrc location=./1080p_60fps_h264.mp4 ! qtdemux ! h264parse ! rtph264pay ! udpsink host=127.0.0.1 port=8000

发现不报错,但是无法播放。

正常:

gst-launch-1.0 -v filesrc location=./1080p_60fps_h264.mp4 ! qtdemux ! avdec_h264 ! x264enc ! rtph264pay ! udpsink host=127.0.0.1 port=8000

发现需要先解码再编码才可以,这样的话,不是无形之间增加了资源消耗吗?

原因:

        偶然之间,发现了一个知识点:

        H264有两种封装模式,一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中,第二种是mp4模式,没有startcode,SPS和PPS以及其他信息封装在container中,每一个frame前面4个字节是这个frame的长度。很多解码器只支持annexb这种模式,因此需要将Mp4做转换,flv也是一样。

        所以,这里的关键是有没有SPS和PPS信息。mp4和flv的h264文件中,SPS和PPS数据不是在每一帧前面的,而且还没有四个字节的startcode:

        左边是正常的,右边是不正常的。

        为了验证这个思路是否正确,我使用了TS视频流进行测试:

验证

        根据之前的原因分析,理论上,ts流就可以不用先解码再编码。事实证明确实如此:

使用ts作为视频源进行推流:

gst-launch-1.0 -v filesrc location=./believe.ts ! tsdemux ! rtph264pay ! udpsink host=127.0.0.1 port=8000

播放:

gst-launch-1.0 udpsrc port=8000 ! application/x-rtp, media=video, clock-rate=90000, encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! glimagesink name=vsink sync=false

发现可以正常播放。

解决办法:

方法一:

        在mp4和flv作为视频源的时候,需要先解码再编码。

        此方法具有泛用性。

方法二:

        在推流的时候,想办法加入SPS和PPS数据。比如后面找到的一种方法:将rtph264pay config-interval=-1 只要这个参数不是0就行,使用gst-inspect-1.0 rtph264pay命令查看,这个参数的意思如下:

进行测试:

gst-launch-1.0 -v filesrc location=./1080p_60fps_h264.mp4 ! qtdemux ! rtph264pay config-interval=-1 ! udpsink host=127.0.0.1 port=8000

发现可以不用先解码再编码就可以实现了。

但是这种情况,并不适用于所有情况,比如SRT协议,还是需要先解码再编码:

gst-launch-1.0 -v filesrc location=./1080p_60fps_h264.mp4 ! qtdemux name=demux demux.video_0 ! h264parse ! avdec_h264 ! x264enc ! mpegtsmux ! srtsink uri=srt://192.168.1.125:8088?mode=listener

方法三:

        使用ffmpeg的h264_mp4toannexb 过滤器。

        参考代码如下,这个代码只是将mp4中的h264文件,转换为了annexb模式的h264,此代码并未实现RTP的封装和推流,需要以后完善。

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}/*
AvCodecContext->extradata[]中为nalu长度
*   codec_extradata:
*   1, 64, 0, 1f, ff, e1, [0, 18], 67, 64, 0, 1f, ac, c8, 60, 78, 1b, 7e,
*   78, 40, 0, 0, fa, 40, 0, 3a, 98, 3, c6, c, 66, 80,
*   1, [0, 5],68, e9, 78, bc, b0, 0,
*///ffmpeg -i 2018.mp4 -codec copy -bsf:h264_mp4toannexb -f h264 tmp.h264
//ffmpeg 从mp4上提取H264的nalu h
int main(int argc, char **argv)
{AVFormatContext *ifmt_ctx = NULL;int             videoindex = -1;AVPacket        *pkt = NULL;int             ret = -1;int             file_end = 0; // 文件是否读取结束if(argc < 3){printf("usage inputfile outfile\n");return -1;}FILE *outfp=fopen(argv[2],"wb");printf("in:%s out:%s\n", argv[1], argv[2]);// 分配解复用器的内存,使用avformat_close_input释放ifmt_ctx = avformat_alloc_context();if (!ifmt_ctx){printf("[error] Could not allocate context.\n");return -1;}// 根据url打开码流,并选择匹配的解复用器ret = avformat_open_input(&ifmt_ctx,argv[1], NULL, NULL);if(ret != 0){printf("[error]avformat_open_input: %s\n", av_get_err(ret));return -1;}// 读取媒体文件的部分数据包以获取码流信息ret = avformat_find_stream_info(ifmt_ctx, NULL);if(ret < 0){printf("[error]avformat_find_stream_info: %s\n", av_get_err(ret));avformat_close_input(&ifmt_ctx);return -1;}// 查找出哪个码流是video/audio/subtitlesvideoindex = -1;// 推荐的方式videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(videoindex == -1){printf("Didn't find a video stream.\n");avformat_close_input(&ifmt_ctx);return -1;}// 分配数据包pkt = av_packet_alloc();av_init_packet(pkt);// 1 获取相应的比特流过滤器//FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。// FLV封装时,可以把多个NALU放在一个VIDEO TAG中,结构为4B NALU长度+NALU1+4B NALU长度+NALU2+...,// 需要做的处理把4B长度换成00000001或者000001const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");AVBSFContext *bsf_ctx = NULL;// 2 初始化过滤器上下文av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;// 3 添加解码器属性avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);av_bsf_init(bsf_ctx);file_end = 0;while (0 == file_end){if((ret = av_read_frame(ifmt_ctx, pkt)) < 0){// 没有更多包可读file_end = 1;printf("read file end: ret:%d\n", ret);}if(ret == 0 && pkt->stream_index == videoindex){int input_size = pkt->size;int out_pkt_count = 0;if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间{av_packet_unref(pkt);   // 你不用了就把资源释放掉continue;       // 继续送}av_packet_unref(pkt);   // 释放资源while(av_bsf_receive_packet(bsf_ctx, pkt) == 0){out_pkt_count++;// printf("fwrite size:%d\n", pkt->size);size_t size = fwrite(pkt->data, 1, pkt->size, outfp);if(size != pkt->size){printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);}av_packet_unref(pkt);}if(out_pkt_count >= 2){printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n",input_size, out_pkt_count);}}else{if(ret == 0)av_packet_unref(pkt);        // 释放内存}}if(outfp)fclose(outfp);if(bsf_ctx)av_bsf_free(&bsf_ctx);if(pkt)av_packet_free(&pkt);if(ifmt_ctx)avformat_close_input(&ifmt_ctx);printf("finish\n");return 0;
}

相关文章:

  • uniapp view设置当前view之外的点击事件
  • 类与对象—python
  • Anaconda教程
  • Kubernetes服务发布基础
  • LeetCode 149. 直线上最多的点数
  • LaTeX 编辑器-TeXstudio
  • 【计算机网络最全知识点问答】第二章 物理层
  • gitlab-runner集成CI/CD完整项目部署
  • 凤凰模拟器V6中无人机如何设置“有头模式”
  • 科研绘图系列:R语言多个AUC曲线图(multiple AUC curves)
  • Linux终端简介
  • WordPress LearnPress插件 SQL注入复现(CVE-2024-8522)
  • 网络编程,端口号,网络字节序,udp
  • 如何创建网络白名单
  • [数据结构]无头单向非循环链表的实现与应用
  • Angular 响应式表单 基础例子
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • Gradle 5.0 正式版发布
  • iOS 系统授权开发
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • spark本地环境的搭建到运行第一个spark程序
  • SQLServer之创建数据库快照
  • swift基础之_对象 实例方法 对象方法。
  • vue的全局变量和全局拦截请求器
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 每天一个设计模式之命令模式
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 使用parted解决大于2T的磁盘分区
  • 正则学习笔记
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​马来语翻译中文去哪比较好?
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • ‌[AI问答] Auto-sklearn‌ 与 scikit-learn 区别
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • #传输# #传输数据判断#
  • $.proxy和$.extend
  • (1)Android开发优化---------UI优化
  • (2022 CVPR) Unbiased Teacher v2
  • (LeetCode 49)Anagrams
  • (二十四)Flask之flask-session组件
  • (九十四)函数和二维数组
  • (六)c52学习之旅-独立按键
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (万字长文)Spring的核心知识尽揽其中
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (已解决)vscode如何选择python解释器
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .apk文件,IIS不支持下载解决
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .NET Core跨平台微服务学习资源
  • .NET IoC 容器(三)Autofac