android实现rtsp服务器的一些参考资料

1 https://github.com/fyhertz/libstreaming
支持rtsp、rtp协议,H264编码,主要是基于这个库进行开发
需要先了解RTSP协议、RTP协议和怎么通过RTP协议来传输H264的每一帧
RTSP协议介绍
RTP协议介绍
h264基础及rtp分包解包

libstreaming的目录
net
└── majorkernelpanic
└── streaming
├── audio 音频相关
│ ├── AACStream.java
│ ├── AMRNBStream.java
│ ├── AudioQuality.java
│ └── AudioStream.java
├── exceptions
│ ├── CameraInUseException.java
│ ├── ConfNotSupportedException.java
│ ├── InvalidSurfaceException.java
│ └── StorageUnavailableException.java
├── gl openGL相关的,当前没用到
│ ├── SurfaceManager.java
│ ├── SurfaceView.java
│ └── TextureManager.java
├── hw 用于获取编码器等信息
│ ├── CodecManager.java
│ ├── EncoderDebugger.java 获取sps和pps,NV21Convertor的初始化
│ └── NV21Convertor.java 将NV21数据转换成yuv420p
├── MediaStream.java 媒体流,视频流和音频流的父类
├── mp4 没用到
│ ├── MP4Config.java
│ └── MP4Parser.java
├── rtcp RTCP相关
│ └── SenderReport.java
├── rtp
│ ├── AACADTSPacketizer.java
│ ├── AACLATMPacketizer.java
│ ├── AbstractPacketizer.java
│ ├── AMRNBPacketizer.java
│ ├── H263Packetizer.java
│ ├── H264Packetizer.java H264数据打包
│ ├── MediaCodecInputStream.java 将h264编码器的输出包装成一个流
│ └── RtpSocket.java 发送打包好的H264数据
├── rtsp
│ ├── RtcpDeinterleaver.java
│ ├── RtspClient.java
│ ├── RtspServer.java RTSP服务器的实现
│ └── UriParser.java 解析RTSP的url工具
├── SessionBuilder.java 用于配置视频音频编码格式、分辨率等等
├── Session.java
├── Stream.java
└── video
├── CodecManager.java
├── H263Stream.java
├── H264Stream.java H264视频流,将摄像头采集的数据编码成H264码流
├── VideoQuality.java
└── VideoStream.java H264Stream的父类

2 https://github.com/fyhertz/spydroid-ipcamera
3 https://github.com/hypeapps/Endoscope
以上两个是基于libstreaming的应用,可以用来参考

4 https://github.com/EasyDSS/EasyIPCamera
EasyIPCamera是一个需要商业授权的应用,开源的代码包含了摄像头数据H264软硬编码部分,可以作为参考

4 https://github.com/Bilibili/ijkplayer
原生的mediaplayer支持rtsp协议,但是播放延时太长,
这里使用了ijkplayer,默认不支持rtsp协议,所以需要自己编译so库
参考编译ijkplayer,并添加rtsp、rtmp支持,解决无法播放、unknown、延迟问题

5 android开发,通过摄像头实时采集视频并使用MediaCodec硬编码为H264
6 java、android可用的rtp封包解包h264
以上两篇文章可以了解怎么对摄像头采集视频后硬编码为H264,并且怎么用rtp封装H264

7 大致流程
(1) 启动RtspServer,等待客户端连接 (RtspServer.java)
(2) 连接成功后,打开相机,采集图像 (VideoStream.java)
(3) 将采集的数据使用MediaCodec编码为H264 (H264Stream.java)
(4) 取出H264数据,根据rtp协议和NALU格式,打包数据,然后发送 (H264Packetizer.java、MediaCodecInputStream.java、RtpSocket.java)

8 libstreaming的坑
(1)分辨率大于640时报错
在libstreaming/src/main/java/net/majorkernelpanic/streaming/video/H264Stream.java注释掉相关代码,
这里分辨率宽度大于640时,会从MediaRecorder里读取H264数据,并且会报错。我们需要用的是MediaCodec来直接硬编码,
所以这个判断直接注释掉

if (mQuality.resX>=640) {
    // Using the MediaCodec API with the buffer method for high resolutions is too slow
    Log.e(TAG,"Using the MediaCodec API with the buffer method for high resolutions is too slow");
    //mMode = MODE_MEDIARECORDER_API;
}

在libstreaming/src/main/java/net/majorkernelpanic/streaming/video/VideoStream.java里
注释掉determineClosestSupportedResolutiom,这个方法实现有问题,会导致设置的分辨率和实际camera使用的分辨率不一致,
导致后面buffer大小运算上有问题。如果我们使用的分辨率时camera支持的,也不需要去校正,所以直接注释掉

//mQuality = VideoQuality.determineClosestSupportedResolution(parameters, mQuality);

(2) 画面旋转90度
libstreaming/src/main/java/net/majorkernelpanic/streaming/video/VideoStream.java
由于后摄像头模组是旋转了90度的,所以需要自己讲图像数据旋转90回来,因此如果设置的预览分辨率时640x480,
则创建MediaFormat时,将宽高对调,设置480x640

//mediaFormat = MediaFormat.createVideoFormat("video/avc", mQuality.resX, mQuality.resY);
mediaFormat = MediaFormat.createVideoFormat("video/avc", mQuality.resY, mQuality.resX);

在使用convertor将NV21(YUV420sp)格式转换成YUV420p时先将数据进行旋转,这里用的是EasyIPCamera里的一个方法Util.yuvRotate
Android Camera 默认的预览格式是NV21(YUV420sp)

扫描二维码关注公众号,回复: 475440 查看本文章
Camera.CameraInfo camInfo = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraId, camInfo);
int cameraRotationOffset = camInfo.orientation;
if (cameraRotationOffset == 90) {
    Camera.Size previewSize = camera.getParameters().getPreviewSize();
    Util.yuvRotate(data, 1, previewSize.width, previewSize.height, 90); //将onPreviewFrame的数据旋转90度
    convertor.convert(data, inputBuffers[bufferIndex]); //将NV21格式转换成编码器需要需要yuv420p
}

数据旋转后,在客户端播放会花屏,需要使用解码器读取的sps和pps而不是EncoderDebugger里得到的
在libstreaming/src/main/java/net/majorkernelpanic/streaming/rtp/H264Packetizer.java里,
将type为7或8的NALU里直接读取整个数据并保留下来,在读取到type为5的关键帧时,将sps和pps放在数据的最前面。

    //将sps pps保存到stapa1里
    if (type == 7 || type == 8) {
        Log.v(TAG,"SPS or PPS present in the stream.");
        byte[] buf = new byte[1300];
        len = fill(buf, 0,  naluLength-1);
        stapa1 = new byte[len + 5];
        System.arraycopy(header, 0, stapa1, 0, 5);
        System.arraycopy(buf, 0, stapa1, 5, len);
        return;
    }
    //如果是type为5的关键帧,则吧sps和pps放置到图像数据前面
    if (type == 5 && stapa1!= null) {
        buffer = socket.requestBuffer();
        socket.markNextPacket();
        socket.updateTimestamp(ts);
        System.arraycopy(stapa1, 0, buffer, rtphl, stapa1.length);
        super.send(rtphl+stapa1.length);
    }

(3)客户端起播慢
虽然在创建解码器的时候,设置了1秒1个关键帧,但是从log里看,并没有生效,并且关键帧间隔40秒才有一个,所以需要在更新解码器的参数

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

在读到数据帧后,更新PARAMETER_KEY_REQUEST_SYNC_FRAME为1

if (Build.VERSION.SDK_INT >= 23) {
    if (System.currentTimeMillis()-timestamp>=1000) {
        timestamp=System.currentTimeMillis();
        Bundle params = new Bundle();
        params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 1);
        mMediaCodec.setParameters(params);
    }
}

参考Android MediaCodec在6.0系统上I帧间隔的问题
同时在EasyIPCamera也是这样设置的

9 yuv格式
NV12,NV21,YV12,I420都属于YUV420,但是YUV420 又分为YUV420P,YUV420SP,P与SP区别就是,前者YUV420P UV顺序存储,
而YUV420SP则是UV交错存储,这是最大的区别
具体的yuv排序就是这样的:
I420: YYYYYYYY UUVV ->YUV420P
YV12: YYYYYYYY VVUU ->YUV420P
NV12: YYYYYYYY UVUV ->YUV420SP
NV21: YYYYYYYY VUVU ->YUV420SP
那么H264编码,需要把android 相机采集的NV21数据转换成YUV420P,
因为H264编码器设置的色彩格式是COLOR_FormatYUV420Planar(19)
而摄像头采集的是NV21(yuv420sp),所以这里必须要处理色彩格式转换。

10 其他一些参考文档
https://juejin.im/post/5a127deef265da431b6cd009
https://www.cnblogs.com/raomengyang/p/6288019.html
https://bigflake.com/mediacodec/
https://blog.csdn.net/zxccxzzxz/article/details/53982849
https://blog.csdn.net/leixiaohua1020/article/details/50534150
http://blog.csdn.net/leixiaohua1020/article/details/50534369
http://blog.csdn.net/leixiaohua1020/article/details/50535230
https://blog.csdn.net/leixiaohua1020/
https://zh.wikipedia.org/wiki/%E5%8D%B3%E6%99%82%E4%B8%B2%E6%B5%81%E5%8D%94%E5%AE%9A
https://zh.wikipedia.org/wiki/%E5%AE%9E%E6%97%B6%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE
https://github.com/EasyDarwin/Course/tree/master/%E6%B5%81%E5%AA%92%E4%BD%93%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE(RTSP%20RTP%20SDP)%E8%AF%A6%E8%A7%A3
https://github.com/EasyDarwin/Course/tree/master/%E6%B5%81%E5%AA%92%E4%BD%93%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E8%BF%9B%E9%98%B6(RTSP%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8)

猜你喜欢

转载自blog.csdn.net/abc_1234d/article/details/80229526