-
rtmp推流
2020-08-14 10:15:06湖南卫视的rtmp是rtmp://58.200.131.2:1935/livetv/hunantv,可以在浏览器中直接输入地址,选择potplayer打开即可观看直播湖南卫视的rtmp是rtmp://58.200.131.2:1935/livetv/hunantv,可以在浏览器中直接输入地址,选择potplayer打开即可观看直播
-
RTMP推流RTSP视频
2018-10-14 11:55:18RTMP推流 -
RTMP推流组件EasyRTMP实现内网摄像头RTSP拉流转码RTMP推流到RTMP服务器之Android版屏幕推流方法及其注意点
2019-12-11 14:09:39Real Time Messaging Protocol(RTMP)即...EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP推流功能组件,经过多年客户实战和线上运行打造,支持RTMP推送断线重连、环形缓冲、智能丢帧、网络事件回调,支...Real Time Messaging Protocol(RTMP)即实时消息传输协议,是 Adobe 公司开发的一个基于 TCP 的应用层协议,目前国内的视频云服务都是以 RTMP 为主要推流协议。
关于RTMP推流组件
EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP推流功能组件,经过多年客户实战和线上运行打造,支持RTMP推送断线重连、环形缓冲、智能丢帧、网络事件回调,支持Windows、Linux、ARM、Android、iOS平台,支持市面上绝大部分的RTMP流媒体服务器。
EasyRTMP-Android介绍屏幕推流及其注意点
解决问题
1、录屏
VirtualDisplay类代表一个虚拟显示器,需要调用DisplayManager 类的 createVirtualDisplay()方法,将虚拟显示器的内容渲染在一个Surface控件上,当进程终止时虚拟显示器会被自动的释放,并且所有的Window都会被强制移除。当不再使用时,需要调用release() 方法来释放资源。
1)获取MediaProjectionManager实例
mMpmngr = (MediaProjectionManager) getApplicationContext() .getSystemService(MEDIA_PROJECTION_SERVICE);
2)在Android 6.0后,Android需要动态获取权限,若没有权限,则不启用该service:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { return; } }
3)MediaProjection是Android5.0后才开放的屏幕采集接口,通过系统级服务MediaProjectionManager进行管理。
if (mMpj == null) { mMpj = mMpmngr.getMediaProjection(StreamActivity.mResultCode, StreamActivity.mResultIntent); StreamActivity.mResultCode = 0; StreamActivity.mResultIntent = null; }
4)通过MediaProjection对象的createVirtualDisplay方法,拿到VirtureDisplay对象,拿这个对象的时候,需要把Surface对象传进去,Surface是由MediaCodec获得。
mVirtualDisplay = mMpj.createVirtualDisplay( "record_screen", windowWidth, windowHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mSurface, null, null);
2、编码及推流
使用Android硬编码MediaCodec,将VirtualDisplay获取到的视频数据编码后通过EasyRTMP推送出去。
1)初始化MediaCodec,同时获取Surface对象,并启动编码器
mMediaCodec = MediaCodec.createByCodecName(ci.mName); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", windowWidth, windowHeight); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); mediaFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 20000000); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 获取Surface对象 mSurface = mMediaCodec.createInputSurface(); mMediaCodec.start();
2)音频编码的工作直接使用AudioStream,
final AudioStream audioStream = AudioStream.getInstance(EasyApplication.getEasyApplication(), SPUtil.getEnableAudio(EasyApplication.getEasyApplication()));
3)启动编码线程,不停的将获取的音频帧和视频帧,编码并推流。详情见代码注释:
// 启动线程, mPushThread.start(); // 初始化推流器 mEasyPusher = new EasyRTMP(mHevc ? EasyRTMP.VIDEO_CODEC_H265 : EasyRTMP.VIDEO_CODEC_H264, RTMP_KEY); // 视频的硬编码 ByteBuffer outputBuffer; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex); } else { outputBuffer = outputBuffers[outputBufferIndex]; } outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); try { boolean sync = false; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { sync = (bufferInfo.flags&MediaCodec.BUFFER_FLAG_SYNC_FRAME)!=0; if (!sync) { byte[] temp = new byte[bufferInfo.size]; outputBuffer.get(temp); mPpsSps = temp; continue; } else { mPpsSps = new byte[0]; } } sync |= (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; int len = mPpsSps.length + bufferInfo.size; if (len > h264.length) { h264 = new byte[len]; } // 将编码后的视频数据发送出去 if (sync) { System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length); outputBuffer.get(h264, mPpsSps.length, bufferInfo.size); mEasyPusher.push(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 2); } else { outputBuffer.get(h264, 0, bufferInfo.size); mEasyPusher.push(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1); }
-
Android摄像头RTMP推流
2018-12-31 22:11:11使用FFMPEG的RTMP推流修改而来,可以读取摄像头和音频推流至流媒体服务器,适合做视频直播的新手参考. 使用FFMPEG的RTMP推流修改而来,可以读取摄像头和音频推流至流媒体服务器,适合做视频直播的新手参考. -
ffmpeg+EasyDSS流媒体服务器实现稳定的rtmp推流直播
2017-07-08 09:43:57本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/74783269需求在做EasyDSS开发时,总是在测试推流...以及其他RTMP推流工具。但是,别忘了,还有ffmpeg这个神器。ffmpeg可以获需求
在做EasyDSS开发时,总是在测试推流效果。
当然,可以使用EasyRTMP进行推流测试。
有时候,我们想使用OBS进行推流测试,也可以,这很好。
以及其他RTMP推流工具。
但是,别忘了,还有ffmpeg这个神器。ffmpeg可以获取各种视频流,并推送给EasyDSS RTMP流媒体服务器。
比如,拉取摄像机RTSP流,推送给EasyDSS,命令在此
ffmpeg -i rtsp://username:password@ip:port/xxxxxxx -vcodec copy -acodec copy -f flv -y rtmp://easydssip:easydssport/live/test
当然,这个命令限于摄像机提供的就为H.264+AAC的码流,若不是
则将
-vcodec copy
改为-vcodec libx264
,-acodec copy
改为-acodec aac
若是,我们想用ffmpeg读取文件进行RTMP推送呢,当然,您可以选择OBS,但是,毕竟,ffmpeg比OBS更轻量,命令在此
ffmpeg -i test.h264 -vcodec copy -acodec copy -f flv -y rtmp://easydssip:easydssport/live/test
-vcodec和-acodec按需更改同拉取RTSP流。
这样可以满足我们推送文件到EasyDSS的愿望。
但是。。。。。
ffmpeg推流的效果经常不如人意,经常出现播放画面慢、音视频不同步、HLS直播不稳定等各种问题?
解决
当然有解决办法
ffmpeg -re -stream_loop -1 -i test.h264 -vcodec copy -acodec copy -f flv -y rtmp://easydssip:easydssport/live/test
加了一个
-re
这是干啥的?
我们明白了,-re表示重新调整时间戳,这样就能够将各种文件、RTSP源、RTMP源的不均匀时间戳全部进行ffmpeg的重新调整,再进行rtmp推流,保证直播的平滑和hls切片的均匀。
另外,在拉取RTSP流时,尽量加上 -rtsp_transport tcp 的选项,保证数据源的稳定和不丢包!
EasyDSS流媒体服务器推荐
EasyDSS商用流媒体服务器提供一站式的转码、点播、直播、时移回放服务,极大地简化了开发和集成的工作,并且EasyDSS支持多种特性,完全能够满足企业视频信息化建设方面的需求:
①多屏播放:支持Flash、HTML5播放,兼容Windows、Android、iOS、Mac等操作系统。
②自由组合:EasyDSS软件产品之间无缝对接,也可将EasyDSS流媒体服务器软件与其他第三方平台对接,组合灵活自由。
③支持云架构:支持阿里云、腾讯云、华为云、青云、七牛云存储等各大云服务商,支持云架构,部署更灵活。
④与CDN无缝对接:EasyDSS软件产品支持与网宿、帝联、蓝汛等CDN无缝对接。
⑤二次开发简单:提供编程语言无关化的RESTful API接口,只要了解JS、HTML、JAVA、ASP.NET、PHP、ASP等开发语言中的任意一种,就能运用EasyDSS提供的RESTful API进行二次开发。
⑥简单易用:图形化操作,模块清晰,流程简单,极易上手。
EasyDarwin如何支持点播和RTMP/HLS直播?EasyDSS!
更多EasyDSS高级功能:www.easydss.com
更多流媒体音视频资源
EasyDarwin开源流媒体服务器:www.EasyDarwin.org
EasyDSS高性能互联网直播服务:www.EasyDSS.com
EasyNVR安防视频可视化服务:www.EasyNVR.com
EasyNVS视频综合管理平台:www.EasyNVS.com
EasyNTS云组网:www.EasyNTS.com
EasyGBS国标GB/T28181服务器:www.EasyGBS.com
EasyRTC视频会议解决方案:www.EasyRTC.cn
Copyright © TSINGSEE.com Team 2012-2019
-
RTMP推流器demo
2020-03-13 04:40:35rtmp推流器 很小的一个推流器 直接加到项目就可以使用 很不错 推荐一下 -
ffmpeg rtmp推流源码
2019-08-07 09:34:03一份可以直接将本地视频推送到服务器的推流源代码, 是学习推流的很好的资料, 代码基于ffmpeg rtmp推流, 采用nginx+rtmp module搭建本地流媒体服务器 -
安卓rtmp推流工具
2019-05-10 11:00:41安卓app,rtmp推流工具, -
linux环境rtmp推流
2018-10-18 14:29:33linux环境,基于rtmp推流源代码,源文件可以下载参考学习 -
【RTMP推流】利用FFMPEG进行USB摄像头数据采集硬件编码后进行 RTMP推流
2020-04-25 15:52:42在三月份接到了这样一个任务,需要通过USB摄像头采集数据之后,放入6818进行硬件编码后,再通过FFMPEG进行RTMP推流。因为对于ffmpeg并不是非常的了解,加上中间偷了一段时间的懒,直到最近才完成初步的工作。 在这里...相关前期准备:
1.RMTP推流服务器建立
2.S5P6818平台硬件编码
3.FFMPEG USB摄像头数据采集在三月份接到了这样一个任务,需要通过USB摄像头采集数据之后,放入6818进行硬件编码后,再通过FFMPEG进行RTMP推流。因为对于ffmpeg并不是非常的了解,加上中间偷了一段时间的懒,直到最近才完成初步的工作。
在这里为了方便直接使用了一些QT的东西,并且通过修改Makefile兼容了一些平台编译的问题。
我这里提前移植QT4.8.6和FFMPEG4.0.2(一)FFMPEG进行RTMP推流相关代码
ffmpeg.cpp
#include "ffmpeg.h" #include "Vpu.h" extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavdevice/avdevice.h" } //#define FFMPEG_MJPEG //#define FFMPEG_H264 #define FFMPEG_YUV #define TIMEMS qPrintable(QTime::currentTime().toString("HH:mm:ss zzz")) static double r2d(AVRational r) { return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den; } ffmpeg::ffmpeg(QWidget *parent) : QThread(parent) { width=640; height=480; Fps=30; } ffmpeg::~ffmpeg() { } void ffmpeg::YUYV_to_YUV420P(char * image_in, char* image_out, int inwidth, int inheight) { AVFrame *frmyuyv = av_frame_alloc(); AVFrame *frm420p = av_frame_alloc(); av_image_fill_arrays(frmyuyv->data, frmyuyv->linesize, (uint8_t*)image_in, AV_PIX_FMT_YUYV422, inwidth, inheight, 16); av_image_fill_arrays(frm420p->data, frm420p->linesize, (uint8_t*)image_out, AV_PIX_FMT_YUV420P, inwidth, inheight, 16); struct SwsContext *sws = sws_getContext(inwidth, inheight, AV_PIX_FMT_YUYV422, inwidth,inheight, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); int ret = sws_scale(sws, frmyuyv->data, frmyuyv->linesize, 0, inheight, frm420p->data, frm420p->linesize); image_out=(char*)frm420p->data; av_frame_free(&frmyuyv); av_frame_free(&frm420p); sws_freeContext(sws); } int ffmpeg::GetSpsPpsFromH264(uint8_t* buf, int len) { int i = 0; for (i = 0; i < len; i++) { if (buf[i+0] == 0x00 && buf[i + 1] == 0x00 && buf[i + 2] == 0x00 && buf[i + 3] == 0x01 && buf[i + 4] == 0x06) { break; } } if (i == len) { printf("GetSpsPpsFromH264 error..."); return 0; } printf("h264(i=%d):", i); for (int j = 0; j < i; j++) { printf("%x ", buf[j]); } return i; } bool ffmpeg::isIdrFrame2(uint8_t* buf, int len) { switch (buf[0] & 0x1f) { case 7: // SPS return true; case 8: // PPS return true; case 5: return true; case 1: return false; default: return false; break; } return false; } bool ffmpeg::isIdrFrame1(uint8_t* buf, int size) { int last = 0; for (int i = 2; i <= size; ++i) { if (i == size) { if (last) { bool ret = isIdrFrame2(buf + last, i - last); if (ret) { return true; } } } else if (buf[i - 2] == 0x00 && buf[i - 1] == 0x00 && buf[i] == 0x01) { if (last) { int size = i - last - 3; if (buf[i - 3]) ++size; bool ret = isIdrFrame2(buf + last, size); if (ret) { return true; } } last = i + 1; } } return false; } int ffmpeg::RtmpInit(void* spspps_date, int spspps_datalen) { RtmpULR="rtmp://192.168.2.101:1935/live/livestream"; int ret = 0; AVStream *out_stream; AVCodecParameters *out_codecpar; avformat_network_init(); avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", NULL);// out_filename); if (!ofmt_ctx) { fprintf(stderr, "Could not create output context\n"); ret = AVERROR_UNKNOWN; } ofmt = ofmt_ctx->oformat; out_stream = avformat_new_stream(ofmt_ctx, NULL); if (!out_stream) { fprintf(stderr, "Failed allocating output stream\n"); ret = AVERROR_UNKNOWN; } stream_index = out_stream->index; //因为输入是内存读出来的一帧帧的H264数据,所以没有输入的codecpar信息,必须手动添加输出的codecpar out_codecpar = out_stream->codecpar; out_codecpar->codec_type = AVMEDIA_TYPE_VIDEO; out_codecpar->codec_id = AV_CODEC_ID_H264; out_codecpar->bit_rate = 400000; out_codecpar->width = width; out_codecpar->height = height; out_codecpar->codec_tag = 0; out_codecpar->format = AV_PIX_FMT_YUV420P; //必须添加extradata(H264第一帧的sps和pps数据),否则无法生成带有AVCDecoderConfigurationRecord信息的FLV //unsigned char sps_pps[26] = { 0x00, 0x00, 0x01, 0x67, 0x4d, 0x00, 0x1f, 0x9d, 0xa8, 0x14, 0x01, 0x6e, 0x9b, 0x80, 0x80, 0x80, 0x81, 0x00, 0x00, 0x00, 0x01, 0x68, 0xee, 0x3c, 0x80 }; out_codecpar->extradata_size = spspps_datalen; out_codecpar->extradata = (uint8_t*)av_malloc(spspps_datalen + AV_INPUT_BUFFER_PADDING_SIZE); if (out_codecpar->extradata == NULL) { printf("could not av_malloc the video params extradata!\n"); } memcpy(out_codecpar->extradata, spspps_date, spspps_datalen); av_dump_format(ofmt_ctx, 0, RtmpULR, 1); if (!(ofmt->flags & AVFMT_NOFILE)) { ret = avio_open(&ofmt_ctx->pb, RtmpULR, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "Could not open output file '%s'", RtmpULR); } } AVDictionary *opts = NULL; av_dict_set(&opts, "flvflags", "add_keyframe_index", 0); ret = avformat_write_header(ofmt_ctx, &opts); av_dict_free(&opts); if (ret < 0) { fprintf(stderr, "Error occurred when opening output file\n"); } waitI = 1; return 0; } // void ffmpeg::VideoWrite(void* data, int datalen) // { // int ret = 0, isI = 0; // AVRational r = { 10, 1 }; // AVPacket pkt; // out_stream = ofmt_ctx->streams[videoStreamIndex]; // av_init_packet(&pkt); // isI = isIdrFrame1((uint8_t*)data, datalen); // pkt.flags |= isI ? AV_PKT_FLAG_KEY : 0; // pkt.stream_index = avDePacket->stream_index; // pkt.data = (uint8_t*)data; // pkt.size = datalen; // //AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。 // AVRational time_base1 = in_stream->time_base; // printf("time_base1:{%d,%d}",in_stream->time_base.num,in_stream->time_base.den); // //计算两帧之间的时间 // int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(r); // //配置参数 // pkt.pts = (double)((framIndex+1)*calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE); // printf("{%d %d %d,%d}\n",framIndex,calc_duration, pkt.pts,av_q2d(time_base1)); // pkt.dts =pkt.pts ; // pkt.duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE); // AVRational time_base = in_stream->time_base; // AVRational time_base_q = { 1,AV_TIME_BASE }; // //计算视频播放时间 // int64_t pts_time = av_rescale_q(pkt.pts, time_base, time_base_q); // //计算实际视频的播放时间 // int64_t now_time = av_gettime() - start_time; // printf("pts_time:%d\n", pts_time); // printf("now_time:%d\n", now_time); // AVRational avr = in_stream->time_base; // // printf("avr.num:%d, avr.den:%d, pkt.dts:%ld, pkt.pts:%ld, pts_time:%ld\n", // // avr.num, avr.den, pkt.dts, pkt.pts, pts_time); // if (pts_time > now_time) // { // //睡眠一段时间(目的是让当前视频记录的播放时间与实际时间同步) // printf("pts_time:%d\n", pts_time); // printf("now_time:%d\n", now_time); // av_usleep((unsigned int)(pts_time - now_time)); // } // //计算延时后,重新指定时间戳 // pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); // pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); // pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); // //字节流的位置,-1 表示不知道字节流位置,由程序自行探测 // pkt.pos = -1; // printf("avr.num:%d, avr.den:%d, pkt.dts:%ld, pkt.pts:%ld, pts_time:%ld\n", // avr.num, avr.den, pkt.dts, pkt.pts, pts_time); // ret = av_interleaved_write_frame(ofmt_ctx, &pkt); // if (ret < 0) // { // printf("发送数据包出错\n"); // } // av_free_packet(&pkt); // } void ffmpeg::VideoWrite(void* data, int datalen) { int ret = 0, isI = 0; AVPacket pkt; out_stream = ofmt_ctx->streams[stream_index]; int calc_duration; av_init_packet(&pkt); isI = isIdrFrame1((uint8_t*)data, datalen); pkt.flags |= isI ? AV_PKT_FLAG_KEY : 0; pkt.stream_index = out_stream->index; pkt.data = (uint8_t*)data; pkt.size = datalen; //wait I frame if (waitI) { if (0 == (pkt.flags & AV_PKT_FLAG_KEY)) return; else waitI = 0; } AVRational time_base1=ifmt_ctx->streams[stream_index]->time_base; //Duration between 2 frames (us) calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[stream_index]->r_frame_rate)-2000; //Parameters pkt.pts=((framIndex+1)*calc_duration)/(av_q2d(time_base1)*AV_TIME_BASE); pkt.dts=pkt.pts; pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE); pkt.pos=-1; // printf("instream{%d,%d}\n",ifmt_ctx->streams[stream_index]->time_base.num,ifmt_ctx->streams[stream_index]->time_base.den); // printf("outstream{%d,%d}\n",ofmt_ctx->streams[stream_index]->time_base.num,ofmt_ctx->streams[stream_index]->time_base.den); printf("DURATION :%d\n",calc_duration); printf("PTS DTS :%d %d\n",pkt.pts,pkt.dts); //计算延时后,重新指定时间戳 pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.duration = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); //字节流的位置,-1 表示不知道字节流位置,由程序自行探测 pkt.pos = -1; gettimeofday(&stamp, NULL); int now_time = 1000*1000*(stamp.tv_sec)+stamp.tv_usec-start_time; printf("start_time now_time:%d %d\n",start_time, now_time); if (pkt.dts > now_time) av_usleep(pkt.dts - now_time); ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "Error muxing packet\n"); } av_packet_unref(&pkt); } void ffmpeg::RtmpUnit(void) { if (ofmt_ctx) av_write_trailer(ofmt_ctx); /* close output */ if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) avio_closep(&ofmt_ctx->pb); if (ofmt_ctx) { avformat_free_context(ofmt_ctx); ofmt_ctx = NULL; } } // void ffmpeg::YUYV_to_NV12( char * image_in, char* image_out, int inwidth, int inheight) // { // /* 计算循环次数,YUYV 一个像素点占2个字节*/ // int pixNUM = inwidth * inheight; // unsigned int cycleNum = 1; // /*单帧图像中 NV12格式的输出图像 Y分量 和 UV 分量的起始地址,并初始化*/ // char *y = image_out; // char *uv = image_out + pixNUM ; // char *start = image_in; // unsigned int i =0; // int j =0,k =0; // /*处理Y分量*/ // for(i= 0; i<cycleNum ;i++) // { // int index =0; // for(j =0; j< pixNUM*2; j=j+2) //YUYV单行中每两个字节一个Y分量 // { // *(y+index) = *(start + j); // index ++; // } // start = image_in + pixNUM*2*i; // y= y + pixNUM*3/2; // } // /**处理UV分量**/ // start = image_in; // for(i= 0; i<cycleNum ;i++) // { // int uv_index = 0; // for(j=0; j< inheight; j =j+2) // 隔行, 我选择保留偶数行 // { // for(k = j*inwidth*2+1; k< inwidth*2*(j+1); k=k+4) //YUYV单行中每四个字节含有一对UV分量 // { // *(uv+ uv_index) = *(start + k); // *(uv +uv_index+1) = *(start +k +2); // uv_index += 2; // } // } // start = image_in + pixNUM*2*i; // uv =uv + pixNUM*3/2; // } // } void ffmpeg::YUV420PtoNV12(unsigned char* Src, unsigned char* Dst,int Width,int Height){ unsigned char* SrcU = Src + Width * Height; unsigned char* SrcV = SrcU + Width * Height / 4 ; memcpy(Dst, Src, Width * Height); unsigned char* DstU = Dst + Width * Height; for(int i = 0 ; i < Width * Height / 4 ; i++ ){ ( *DstU++) = ( *SrcU++); ( *DstU++) = ( *SrcV++); } } /* 功能:初始化video mjpeg to yuv * 1 video * 2 解码 * 参数:无 * 返回值:成功返回零,失败返回-1 */ int ffmpeg::initDecodeVideo() { MJPEGPath=fopen("out.mpg","wb"); H264Path = fopen(outputFilename, "wb"); YUVPath = fopen("out.yuv","wb"); NV12Path= fopen("out.nv12","wb"); //为解封装上下文开辟空间 ifmt_ctx = avformat_alloc_context(); framIndex=0; /*1、注册*/ avcodec_register_all(); avdevice_register_all(); qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION; /*2、连接视频源*/ AVInputFormat *inputFmt = av_find_input_format("video4linux2"); AVDictionary *options = NULL; //打开输入视频流,进行解封装 av_dict_set(&options, "framerate", "30", 0); char videosize[9]; sprintf(videosize,"%dx%d",width,height); av_dict_set(&options, "video_size", videosize, 0); #ifdef FFMPEG_MJPEG av_dict_set(&options, "input_format", "mjpeg", 0); #endif #ifdef FFMPEG_YUV av_dict_set(&options, "input_format", "yuyv422", 0); #endif int result = avformat_open_input(&ifmt_ctx, inputFilename, inputFmt, &options); if (result < 0) { qDebug() << TIMEMS << "open input error" << inputFilename; return false; } //释放设置参数 if(options != NULL) { av_dict_free(&options); } //获取流信息 result = avformat_find_stream_info(ifmt_ctx, NULL); if (result < 0) { qDebug() << TIMEMS << "find stream info error"; return false; } avDePacket = av_packet_alloc(); avDeFrameYuv = av_frame_alloc(); videoStreamIndex = -1; videoStreamIndex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &deCodec, 0); if (videoStreamIndex < 0) { qDebug() << TIMEMS << "find video stream index error"; return false; } printf("videoindex:%d\n", videoStreamIndex); //从输入封装上下文获取输入视频流 in_stream = ifmt_ctx->streams[videoStreamIndex]; if (!in_stream) { printf("Failed get input stream\n"); return false; } //获取视频流解码器上下文 deCodecCtx = in_stream->codec; //获取分辨率大小 videoWidth = in_stream->codec->width; videoHeight = in_stream->codec->height; //如果没有获取到宽高则返回 if (videoWidth == 0 || videoHeight == 0) { qDebug() << TIMEMS << "find width height error"; qDebug() <<"WIDTH"<<videoWidth<<":"<<"HEIGHT"<<videoHeight; return false; } //获取视频流的帧率 fps,要对0进行过滤,除数不能为0,有些时候获取到的是0 int num = in_stream->codec->framerate.num; int den = in_stream->codec->framerate.den; if (num != 0 && den != 0) { videoFps = num / den ; } QString videoInfo = QString("视频流信息 -> 索引: %1 格式: %2 时长: %3 秒 fps: %4 分辨率: %5*%6 instream->time_base:%7 %8 in_stream->r_frame_rate: %9 %10") .arg(videoStreamIndex).arg(ifmt_ctx->iformat->name) .arg((ifmt_ctx->duration) / 1000000).arg(videoFps).arg(videoWidth).arg(videoHeight).arg(in_stream->time_base.num).arg(in_stream->time_base.den).arg(in_stream->r_frame_rate.num).arg(in_stream->r_frame_rate.den); qDebug() << TIMEMS << videoInfo; //打开视频解码器 result = avcodec_open2(deCodecCtx, deCodec, NULL); if (result < 0) { qDebug() << TIMEMS << "open video codec error"; return false; } AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P; #ifdef FFMPEG_YUV srcFormat = AV_PIX_FMT_YUYV422; #endif return 0; } int ffmpeg::playVideo() { int length; int got_packet; initDecodeVideo(); vpu Vpu(width,height,Fps); //WRITE HERDER fwrite( Vpu.seqBuffer, 1, Vpu.size, H264Path ); RtmpInit(Vpu.seqBuffer, Vpu.size); nv12=(char*)malloc(width*height*3/2); yuv420p=(char*)malloc(width*height*3/2); gettimeofday(&stamp, NULL); start_time=(stamp.tv_sec)*1000*1000+stamp.tv_usec; while(true) { if (av_read_frame(ifmt_ctx, avDePacket) >= 0) { YUYV_to_YUV420P(( char*)avDePacket->data,( char*)yuv420p,width,height); h264=Vpu.DecodeNV12_To_H264(yuv420p,&length); VideoWrite(h264, length); // fwrite(avDePacket->data,avDePacket->size,1,YUVPath); // fwrite(h264,length,1,H264Path); // fwrite(yuv420p,width*height*3/2,1,NV12Path); // printf("[H264 PACKET LENGTH]=%d\n",length); // qDebug()<< "解码到第" << framIndex << "帧"; // qDebug()<<"PCAKET SIZE="<<avDePacket->size; // qDebug() << TIMEMS; av_packet_unref(avDePacket); av_freep(avDePacket); } framIndex++; } RtmpUnit(); avformat_free_context(ifmt_ctx); qDebug() << TIMEMS << "stop ffmpeg thread"; } void ffmpeg::run() { playVideo(); }
ffmpeg.h
#ifndef FFMPEG_H #define FFMPEG_H #include <QMainWindow> #include <QMutex> #include <QDateTime> #include <QFile> #include <QThread> #include <QDebug> #include <stdio.h> #include <string> #include <iostream> #include <chrono> #include <vector> //引入ffmpeg头文件 extern "C" { #include "libavutil/opt.h" #include "libavutil/time.h" #include "libavutil/frame.h" #include "libavutil/pixdesc.h" #include "libavutil/avassert.h" #include "libavutil/imgutils.h" #include "libavutil/ffversion.h" #include "libavcodec/avcodec.h" #include "libswscale/swscale.h" #include "libavdevice/avdevice.h" #include "libavformat/avformat.h" #include "libavfilter/avfilter.h" #include "libavutil/hwcontext.h" #include "libavutil/avutil.h" #include "libavutil/opt.h" } class ffmpeg : public QThread { Q_OBJECT public: explicit ffmpeg(QWidget *parent = NULL); ~ffmpeg(); char *outputFilename; char *inputFilename; struct timeval stamp; int start_time; protected: void run(); signals: //收到图片信号 void receiveImage(const QImage &image); private: AVFormatContext *fmtCtx = NULL;; int framIndex; uint8_t *buffer; //存储解码后图片buffer AVFrame *avDeFrameYuv; //解码帧对象YUV AVCodec *deCodec = NULL; //解码器 AVCodec *pCodecH264; //编码器 AVPacket *avDePacket; //解码包对象 AVPacket avpkt; int frameFinish = 0; int stream_index; FILE* MJPEGPath; FILE* YUVPath; FILE* H264Path; FILE* NV12Path; int WIDTH,HEIGHT,FRAME; int videoStreamIndex; //视频流索引 //输入输出视频流 AVStream *out_stream; AVStream *in_stream; //输入视频流 AVCodecContext *c= NULL; AVCodecContext *deCodecCtx; //解码器上下文 int videoWidth; //视频宽度 int videoHeight; //视频高度 int videoFps; uint8_t * outbuf; int outbuf_size; int got_packet_ptr; char* nv12; char *h264; char* yuv420p; char* RtmpULR; AVFormatContext *ifmt_ctx; AVOutputFormat *ofmt = NULL; AVFormatContext *ofmt_ctx = NULL; int waitI,rtmpisinit; int ptsInc=0; int width; int height; int Fps; private: void YUV420PtoNV12(unsigned char *Src, unsigned char* Dst,int Width,int Height); void YUYV_to_YUV420P( char * image_in, char* image_out, int width, int height); int initDecodeVideo(); int playVideo(); int RtmpInit(void* spspps_date, int spspps_datale); int GetSpsPpsFromH264(uint8_t* buf, int len); bool isIdrFrame2(uint8_t* buf, int len); bool isIdrFrame1(uint8_t* buf, int size); void VideoWrite(void* data, int datalen); void RtmpUnit(void); }; #endif // FFMPEG_H
(二)运行中遇到的问题
其他初始化的想关的准备中并没有遇到严重的问题。但是在推流的过程中遇到了三个非常严重的问题。
(1)时间戳
在其他的代码中经常会使用av_gettime()获取时间戳,但是非常奇怪的是,我在使用相关函数的时候并不能成功获取时间戳。最后不得已采用
gettimeofday(&stamp, NULL);
int now_time = 1000 * 1000 * (stamp.tv_sec)+stamp.tv_usec-start_time;
获取相关的时间戳。(2)推流包参数设置的问题
打印数据发现,输入和输出具有完全不同的时间基,需要通过一个av_rescale_q_rnd函数重新计算时间PTS DTS参数
(3)推流和播放无法同步
这是一个非常麻烦的问题,到目前都没有找到非常好的解决方法。也就是实际时间与播放时间有1到2秒的时间差,无法同步。这个应该不是代码的原因。即使源码推流好像也存在这样的问题。
我想到了一个这样的办法,通过稍微减小两帧之间的时间可以让这样的问题得到缓解。但是,也有一个问题就是每过一段时间会稍微卡一下。void ffmpeg::VideoWrite(void* data, int datalen) { int ret = 0, isI = 0; AVPacket pkt; out_stream = ofmt_ctx->streams[stream_index]; int calc_duration; av_init_packet(&pkt); isI = isIdrFrame1((uint8_t*)data, datalen); pkt.flags |= isI ? AV_PKT_FLAG_KEY : 0; pkt.stream_index = out_stream->index; pkt.data = (uint8_t*)data; pkt.size = datalen; //wait I frame if (waitI) { if (0 == (pkt.flags & AV_PKT_FLAG_KEY)) return; else waitI = 0; } AVRational time_base1=ifmt_ctx->streams[stream_index]->time_base; //Duration between 2 frames (us) //通过减少两帧之间的时间缓解时间差的问题 calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[stream_index]->r_frame_rate)-2000; //Parameters pkt.pts=((framIndex+1)*calc_duration)/(av_q2d(time_base1)*AV_TIME_BASE); pkt.dts=pkt.pts; pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE); pkt.pos=-1; // printf("instream{%d,%d}\n",ifmt_ctx->streams[stream_index]->time_base.num,ifmt_ctx->streams[stream_index]->time_base.den); // printf("outstream{%d,%d}\n",ofmt_ctx->streams[stream_index]->time_base.num,ofmt_ctx->streams[stream_index]->time_base.den); printf("DURATION :%d\n",calc_duration); printf("PTS DTS :%d %d\n",pkt.pts,pkt.dts); //计算延时后,重新指定时间戳 pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); pkt.duration = (int)av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); //字节流的位置,-1 表示不知道字节流位置,由程序自行探测 pkt.pos = -1; gettimeofday(&stamp, NULL); int now_time = 1000*1000*(stamp.tv_sec)+stamp.tv_usec-start_time; printf("start_time now_time:%d %d\n",start_time, now_time); if (pkt.dts > now_time) av_usleep(pkt.dts - now_time); ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "Error muxing packet\n"); } av_packet_unref(&pkt); }
-
RTMP推流组件EasyRTMP实现内网摄像头RTSP拉流转码RTMP推流到RTMP服务器之Android版如何快速接入文档
2019-12-11 15:31:55背景分析 RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写,该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMP/RTMPS/RTMPE等多种变种。...RTMP推流,就是将直播内容推送到服... -
无人机RTMP推流直播及无人机GBR24视频流推流直播解决方案
2019-04-11 17:54:00如果无人机自身支持RTMP推流,可以直接采用RTMP直接推流的方式;也可以采用LivePush,与定制GBR24到H.264结合的方式;可以使用稳定的RTMP推流服务器LiveQing 或是 其他运营商提供的CDN. -
rtmp推流抓包,Flash Media Live Encoder 推流给Adobe服务器
2018-11-23 09:34:42rtmp推流抓包,Flash Media Live Encoder 推流给Adobe服务器的抓包。 -
windows rtmp推流器
2017-05-23 19:11:28本事例主要采用ffmpeg、libaac、libx264、librtmp实现的windows系统下的音视频设备检测、数据采集、编码(AAC/X264)、RTMP推流直播。 -
【硬件设备】海康RTMP推流摄像头推流到阿里云配置手册
2020-09-30 09:30:30TSINGSEE青犀视频和海康联合研发的RTMP推流摄像头上线已经有一段时间了,RTMP推流摄像头无需布置电源线、交换机,即插即用,支持萤石云等平台,可以随时随地查看监控中的店铺、公司动态,且支持全新Smart H265编码,... -
一步一步搭建视频直播RTMP推流流媒体服务
2019-06-17 17:07:31第一步:准备工具 OBS推流工具下载及配置可以参见:OBS推流工具 ##第二步:安装流媒体服务 免费获得试用安装包,加入QQ群 615081503 群文件里有试用安装包,极速安装,下载...点击复制获得推流地址: rtmp://192.16... -
python利用ffmpeg进行rtmp推流直播
2019-07-04 23:44:06思路: opencv读取视频 —> 将视频分割为帧 —> 将每一帧进行需求加工后 —> 将此帧写入pipe管道 —> 利用ffmpeg进行推流...利用这个特点 让ffmpeg读取处理后的图像帧并进行rtmp推流即可 代码 ... -
RTMP推流器,RTMP(HLS)秒开播放器
2019-11-03 16:24:46RTMP 推流器,RTMP(HLS)秒开播放器,跨平台(Win,IOS,Android)开源代码 -
ffmpeg rtmp推流代码示例
2020-01-31 21:22:11rtmp推流,得有rtmp服务器,可以参考这篇博客搭建rtmp服务器 ===》》》ubuntu搭建rtmp服务器,如果自己有rtmp服务器,可以不用看。 rtmp推流需要用flv格式, 本篇博客demo是本地flv文件rtmp推流. 主要流程如下: ... -
【硬件设备】海康RTMP推流摄像头推流到腾讯云配置手册
2020-09-30 09:16:25每天都有很多新老用户咨询我关于RTMP推流摄像头的问题,推流摄像头的配置手册我之前已经写过(RTMP推流摄像头如何接入EasyDSS视频平台),已经持有RTMP推流摄像头的可以按照此手册进行配置。 由于部分用户使用的... -
【腾讯云】海康RTMP推流摄像头推流到腾讯云配置手册
2020-10-20 18:04:16每天都有很多新老用户咨询我关于RTMP推流摄像头的问题,推流摄像头的配置手册我之前已经写过(RTMP推流摄像头如何接入EasyDSS视频平台),已经持有RTMP推流摄像头的可以按照此手册进行配置。 由于部分用户使用的... -
海康定制RTMP推流摄像头使用手册.pdf
2020-08-06 10:02:24TSINGSEE青犀团队定制的海康RTMP推流摄像头是新上线定制产品,能够直接将摄像头视频流推到流媒体平台,本资源讲述了摄像头配置及推流方法。 -
【阿里云+硬件配置操作】海康RTMP推流摄像头推流到阿里云配置手册
2020-10-20 18:05:59TSINGSEE青犀视频和海康联合研发的RTMP推流摄像头上线已经有一段时间了,RTMP推流摄像头无需布置电源线、交换机,即插即用,支持萤石云等平台,可以随时随地查看监控中的店铺、公司动态,且支持全新Smart H265编码,... -
视频点播RTMP推流直播流媒体服务二次开发集成接口
2019-03-02 13:54:34LiveQing流媒体服务器软件,提供一站式的转码、点播、直播、时移回放服务,极大地简化了开发和集成的工作。 其中,点播功能主要包含:上传、转码、分发。...提供播放鉴权、推流鉴权等安全保证。提供用户及相关权限... -
Window环境下 海康视频RTMP推流方法
2018-11-03 23:12:03Window环境下 海康视频RTMP推流方法,使用到的工具。希望能够帮到大家。
-
视觉SLAM技术学习笔记(一)基础知识以及SLAM的应用
-
Android开发中UI优化方案整理 (UI渲染背景知识,系统做的优化,工程师优化方案,优化工具)
-
前端性能优化
-
psasp自带全算例7[1].0
-
方法上加上@Transactional事务注解后,阿里编码规约显示警告
-
(新)备战2021软考系统集成学习套餐
-
mysql-5.7.29-winx64.zip
-
转行做IT-第1章 计算机基础
-
【数据分析-随到随学】数据分析建模和预测
-
西安航空学院软工毕业实训
-
21年新接口自动化测试视频postman教程 零基础接口测试
-
第二讲 Python的环境搭建
-
最新人教版五年级下册语文第五单元基础过关知识整理.doc
-
21年新MybatisPlus+Swagger3.x+SpringBo
-
EN 50498:2010.pdf
-
Mysql 8.0修改密码
-
XLSReadWritell 6.00.26 for Tokyo 10.2
-
vue后台管理系统.
-
【leetcode】1232. 缀点成线(check-if-it-is-a-straight-line)(数学)[简单]
-
【数据分析-随到随学】SPSS调查问卷统计分析