RED5流媒体服务器,是Java开源的实现RTMP协议的服务器。有关RTMP协议,网上有很多的介绍。现在着重介绍一下,客户端连接RED5服务器的开发流程。

RED5有一个client包red-client.jar实现了对客户端的封装,可以用来连接RED5服务器,发布数据。

RED5 client连接成功服务器后,第一步需要创建一个流:createStream,这是,服务端会创建一个streamId返回到客户端,第二步需要publish了,发布成功后,即可发布数据(流数据)。

主要的流程是:

1、FFmpeg(或者其它推流客户端)--(流推送)--->RED5服务器、接收到流后,再连接另一台RED5

2、当接收到流后,作为客户端进行(流转发)至另一台--->RED5服务器

下面,主要说下客户端到服务端的开发修改原因RED5的部分:

客户端修改red-client.jar:

BaseRTMPClientHandler类,主要添加一个方法,暴露出客户端的一个connectionconsumer,

public ConnectionConsumer getConnectionConsumerByStreamId(int streamId) {return streamDataMap.get(streamId).connConsumer;}

这个方法的主要目的是:拿到客户端的消费者,注册到服务端的消费者队列中。客户端的代码修改就这么多。

服务端修改,主要是修改red-server-common.jar的内容:

StreamService类的publish方法,这个方法对应的是:客户端的publish,这时候会创建一个IClientBroadcastStream类,这个类的主要作用就是可以对流进行分发:分发到web-client的连接,把流保存成flv等,在这个publish方法中,主要是为了获取到RED5服务端的connection(这个conn主要是上面的流推送时的conn,也就是红色部分),这个connection中包含的有streamId(区别自己开发的streamId)。

ClientBroadcastStream类中,添加一个方法:

/** 获取live pipe add by zjk*/public IPipe getLivePipe() {return this.livePipe;}

这个方法,主要是对外提供一个获取pipe的接口,这个pipe是一个管道,可以在这个管道上注册connectionconsumer(可以把 BaseRTMPClientHandler类中的connectionconsumer注册进来,这样只要服务端来了流信息,就可以自动转发到另一台服务器)。

RTMPProtocolDecoder类的decodeStreamMetadata这个方法,主要是解析onMetaData(FLV格式的元数据信息)的,这里想要说的是,发送的数据格式是以“@setDataFrame”开始的,可以在ClientBroadcastStream中拿到metaData的信息,不过,需要封装成以“@setDataFrame”开头格式的数据,要不,RED5不能解析只以onMeta开头的数据。如果不能封装的话,可以在decodeStreamMetadata方法中添加一些逻辑或者修改它的方法参数,保存到客户端(自己开发的)里面,以streamId(这个Id可以在decodeMessage方法中拿到)作为区分。
至此,RED5的原有部分已经修改完成,剩下的设计工作,则需要根据业务设计。

FFmpeg推送流到RED5服务器的流程大致如下:

RTMP连接客户端到red5服务端,其实跟FFmpeg是一样的流程,只不过,这时不需要保存一些额外的信息。

发布成功后,第一步需要做的是:发送解码数据StreamCodecInfo,如果不发送,则很有可能会出现只有声音,没有画面的情况,代码如下:

ClientBroadcastStream stream = (ClientBroadcastStream)conn.getStreamById(RTMPClient.serverStreamId);ConnectionConsumer consumer = rtmpClient.getConnectionConsumerByStreamId(rtmpClient.getStreamId());IStreamCodecInfo codecInfo = stream.getCodecInfo();if (codecInfo instanceof StreamCodecInfo) {StreamCodecInfo info = (StreamCodecInfo) codecInfo;// handle video codec with configurationIVideoStreamCodec videoCodec = info.getVideoCodec();log.debug("Video codec: {}", videoCodec);if (videoCodec != null) {// check for decoder configuration to sendIoBuffer config = videoCodec.getDecoderConfiguration();if (config != null) {log.debug("Decoder configuration is available for {}", videoCodec.getName());//log.debug("Dump:\n{}", Hex.encodeHex(config.array()));VideoData conf = new VideoData(config.asReadOnlyBuffer());log.trace("Configuration ts: {}", conf.getTimestamp());RTMPMessage confMsg = RTMPMessage.build(conf);try {log.debug("Pushing video decoder configuration");consumer.pushMessage(null,confMsg);} finally {conf.release();}}// check for a keyframe to sendIoBuffer keyFrame = videoCodec.getKeyframe();if (keyFrame != null) {log.debug("Keyframe is available");VideoData video = new VideoData(keyFrame.asReadOnlyBuffer());log.trace("Keyframe ts: {}", video.getTimestamp());//log.debug("Dump:\n{}", Hex.encodeHex(keyFrame.array()));RTMPMessage videoMsg = RTMPMessage.build(video);try {log.debug("Pushing keyframe");consumer.pushMessage(null,videoMsg);} finally {video.release();}}} else {log.debug("No video decoder configuration available");}// handle audio codec with configurationIAudioStreamCodec audioCodec = info.getAudioCodec();log.debug("Audio codec: {}", audioCodec);if (audioCodec != null) {// check for decoder configuration to sendIoBuffer config = audioCodec.getDecoderConfiguration();if (config != null) {log.debug("Decoder configuration is available for {}", audioCodec.getName());//log.debug("Dump:\n{}", Hex.encodeHex(config.array()));AudioData conf = new AudioData(config.asReadOnlyBuffer());log.trace("Configuration ts: {}", conf.getTimestamp());RTMPMessage confMsg = RTMPMessage.build(conf);try {log.debug("Pushing audio decoder configuration");consumer.pushMessage(null,confMsg);} finally {conf.release();}}} else {log.debug("No audio decoder configuration available");}}

上面的数据发送完成后,再发送onMetaData数据:

rtmpClient.publishStreamData(RTMPMessage.build(stream.getMetaData()));

或者

private void sendOnMetaData() {logger.error("========="+RTMPClient.ioBuffer);Notify metaNotify = new Notify();
//        metaNotify.setTimestamp(0);
//        IoBuffer data = IoBuffer.allocate(1024);
//        try {
//            Charset charset = Charset.defaultCharset();
//            CharsetEncoder encoder = charset.newEncoder();
//            data.putString("@setDataFrame", encoder);
//            data.putString("onMetaData", encoder);
//            data.put(RTMPClient.notify.getData());
//        } catch (Exception e) {
//            logger.error("Put the string error.",e);
//        }
<span style="white-space:pre">  </span>//这个ioBuffer就是保存的metaData数据metaNotify.setData(RTMPClient.ioBuffer);publishStreamData(streamId,RTMPMessage.build(metaNotify));}

发送完成后,即可注册到pipe上:

stream.getLivePipe().subscribe(consumer,null);

上面的consumer是RTMP客户端的consumer。

在pushMessage方法中,会有一次发送chunkSize的包,这个主要是协调服务端跟客户端包的大小。RED5的代码默认值是1024,可以修改。

ConnectionConsumer

private int chunkSize = 128; //TODO: Not sure of the best value here

至此,开发完成,至于细节问题,则可以详细的设计。
RED5的版本是:1.0.5,而这个版本的IO包中,FLVWriter类的305行,有一个bug,在保存成流文件是,经常出错,

原来代码是:

tag.getBody().mark();// get input dataInput metadata = new Input(tag.getBody());// initialize type so that readString knows what to dometadata.readDataType();String metaType = metadata.readString(String.class);log.debug("Metadata tag type: {}", metaType);tag.getBody().reset();if (!"onCuePoint".equals(metaType)) {// store any incoming onMetaData tags until we close the file, allow onCuePoint tags to continuemetaTags.put(System.currentTimeMillis(), tag);return true;}

修改后的代码是:

byte dataType = tag.getDataType();/*** when tag is ImmutableTag which is in red5-server-common.jar, tag.getBody().reset() will throw InvalidMarkException* because ImmutableTag.getBody() returns a new IoBuffer instance everytime.*/IoBuffer tagBody = tag.getBody();// if we're writing non-meta tags do seeking and tag size updateif (dataType != ITag.TYPE_METADATA) {// get the current file offsetlong fileOffset = dataFile.getFilePointer();log.debug("Current file offset: {} expected offset: {}", fileOffset, prevBytesWritten);if (fileOffset < prevBytesWritten) {log.debug("Seeking to expected offset");// it's necessary to seek to the length of the file// so that we can append new tagsdataFile.seek(prevBytesWritten);log.debug("New file position: {}", dataFile.getChannel().position());}} else {tagBody.mark();// get input dataInput metadata = new Input(tagBody);// initialize type so that readString knows what to dometadata.readDataType();String metaType = metadata.readString(String.class);log.debug("Metadata tag type: {}", metaType);try{tagBody.reset();} catch (InvalidMarkException e) {//TDJ: this error is probably caused by the setter of limit on readString methodlog.debug("Exception reseting position of buffer: " + e.getMessage(), e);}if (!"onCuePoint".equals(metaType)) {// store any incoming onMetaData tags until we close the file, allow onCuePoint tags to continuemetaTags.put(System.currentTimeMillis(), tag);return true;}}// set a var holding the entire tag size including the previous tag lengthint totalTagSize = TAG_HEADER_LENGTH + bodySize + 4;// resizedataFile.setLength(dataFile.length() + totalTagSize);// create a buffer for this tagByteBuffer tagBuffer = ByteBuffer.allocate(totalTagSize);// get the timestampint timestamp = tag.getTimestamp() + timeOffset;// allow for empty tag bodiesbyte[] bodyBuf = null;if (bodySize > 0) {// create an array big enoughbodyBuf = new byte[bodySize];// put the bytes into the arraytagBody.get(bodyBuf);

不过,在我修改这个bug后,看它的1.0.6版本,发现,已经修改了此bug。

还有一种方式是:通过client连接一个red5,再连接另一个red5。用client播放一个red5的流,client在监听到流数据后,即可发布到另一台服务器,即可实现流的转发。这个是RED5的client包中已经实现的效果。StreamRelay.java中的方法。具体的可以查看red5的源码包中StreamRelay类。

RED5流媒体服务器作为客户端转发流至另一个RED5服务器相关推荐

  1. wincc终端和服务器滞后,wincc做服务器和客户端,Simatic Shell中看不到服务器计算机...

    如题,wincc做服务器和客户端,Simatic Shell中看不到服务器计算机,网络连接是没问题的,计算机也属于相同的工作组,想请教下如何解决? 钻石用户推荐最佳答案 1.为什么 WinCC 客户端 ...

  2. php开发ftp服务器搭建教程,在Linux中搭建一个FTP服务器

    在Linux中搭建一个ftp服务器,以供两个工作小组保管文件使用.禁用匿名.第一个小组使用ftp账号:ftp1,工作目录在:/var/ftp/ftp1:第二个小组使用ftp2,工作目录在:/var/f ...

  3. java服务器和linux_在Linux下开一个Java服务器(使用CatServer Pro)

    引言 Linux开服具有快速,高效,性能等特点,而Windows虽然简单,但是不具备Linux良好的性能. 本教程就说明一下简单的Linux开服方式(@需要教程的人,如果你学会后,请无偿帮助更多的人. ...

  4. android服务器怎么做的,[Android]Android 制作一个HTTP服务器应用

    上传文件 开始想用apache的开源库获取文件,但是失败了,要么文件不全,要么就完全为空,还是自己写. 文件上传请求头的部分内容 contentType:multipart/form-data; bo ...

  5. c++简单实现http协议服务器和客户端

    C++ 简单实现HTTP GET/POST 请求 HTTP(超文本传输协议)是一种客户端与服务端的传输协议,最早用于浏览器和服务器之间的通信,后来因为其使用灵活.方便等特点,广泛用于客户端与服务端的通 ...

  6. 贸易时代的文档(二)--地图服务器和客户端移动功能【邹志兵】

    一.需求分析 (一)问题定义 1.概述 <贸易时代>游戏是体现欧洲15.16世纪航海大发现时代航海历史的游戏,游戏主要体现在贸易,探险和海盗这三个主题上.游戏主要体现在贸易这个环节上,突出 ...

  7. vertx访问html,Vert.x HTTP 服务器与客户端

    编写HTTP 服务器与客户端 Vert.x让编写非阻塞的HTTP 服务器与客户端变得非常轻松. 创建HTTP 服务器 缺省状况: HttpServer server = vertx.createHtt ...

  8. Redis源码剖析(一)服务器与客户端交互流程

    Redis中的C/S模型 Redis底层还是基于网络请求的,对于单机数据库而言,网络请求仅仅是在一台机器上交互,即服务器客户端都在一台计算机上 当在终端输入redis-serve时,便启动了一个Red ...

  9. grpc服务器和客户端互传数据

    一.客户端给服务器传数据 1.data.proto syntax = 'proto3'; // 服务定义 service data{// 函数定义 data_request参数 data_reply返 ...

最新文章

  1. 值得推荐的好书——评《亮剑.NET.图解C#开发实战》
  2. C++ dll 类型与 C#类型对应关系
  3. Microsoft .NET Pet Shop 4
  4. (三)虚拟化技术重点笔记与总结
  5. 高速的二舍八入三七作五_有没有发现,高速收费都是5的倍数,这是为什么?怎么判断的?...
  6. 【牛客 - 373A】翻硬币问题(博弈,结论,分析)
  7. onclick的值传给php,php – 从onclick事件将HTML属性传递给jQuery函数
  8. SAP License:作业类型作为成本对象
  9. 非常适合新手的jq/zepto源码分析05
  10. 一只青蛙跳向三个台阶_9. 变态跳台阶
  11. 硬编码是什么意思_饰品上那些编码和数字你都知道是什么意思吗?
  12. 织梦 php模板修改,织梦专题模板修改.doc
  13. win10安装打印机驱动的方法,电脑打印机驱动安装教程
  14. Blender快捷键、技巧和软件配置
  15. 有没有一种让人欲罢不能的学习方法?
  16. 书单 | 7月畅销新书情报,看谁是最大黑马
  17. webSphere介绍
  18. leetcode 739 解法思路
  19. 小智机器人型号_小智类人型机器人
  20. 计算机职称落户,2019有这些中级职称就可以在上海落户啦!(国家职业资格)

热门文章

  1. 盘点七大接地气的翅片管式换热器设计软件
  2. PMP证书如何续证?PMP证书续证步骤和注意事项
  3. 炫酷!200 行 Python 代码实现马赛克拼图!
  4. 读取MDL文件与骨骼控制
  5. Java数据库编程(JDBC)-入门笔记
  6. 惠普M329打印机更换副厂硒鼓后提示墨粉不足并无法打印
  7. 系统分析师真题2019试卷相关概念二
  8. 埃里克贝里奇_【双语分享】为什么科技需要人文学科?
  9. [Julia语言]使用Chudnovsky 算法快速计算圆周率 Pi (π) 值
  10. 贝塞尔曲线公式,我是你爸爸