目录
第一,javacv基础知识:
1.基础:
1、图像像素格式与图片封装格式
2、图像?视频帧?傻傻分不清楚
3、编码?封装?傻傻分不清楚
4、音/视频源
5、流媒体协议
6、流媒体服务
比如常见的srs(开源的rtmp流媒体服务,当然它支持rtmp/hls/http-flv的分发)和nginx(通过安装模块可以支持rtmp,hls,http-flv分发),除此之外的收费的和一些不太友好的开源流媒体服务就不一一介绍了。
第二:javaCV入门指南:调用FFmpeg原生API和JavaCV是如何封装了FFmpeg的音视频操作?(个人更感兴趣javacv的)
参考大神链接:https://blog.csdn.net/eguid_1/article/details/83663035
1、什么是JavaCPP
2.javaCPP直接调用FFmpeg的API
3、JavaCV是如何封装了FFmpeg的音视频操作?
1、帧抓取器(FrameGrabber)
2、帧录制器/推流器(FrameRecorder)
3、帧(Frame)
参考链接:https://blog.csdn.net/leeking888/article/details/77542661
翻译一下问题:
ated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[libx264 @ 00000000635b8f80] non-strictly-monotonic PTS
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[libx264 @ 00000000635b8f80] non-strictly-monotonic PTS
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[libx264 @ 00000000643328a0] frame I:15 Avg QP:13.91 size: 52452
[libx264 @ 00000000643328a0] frame P:115 Avg QP:17.78 size: 4696
[libx264 @ 00000000643328a0] frame B:223 Avg QP:17.87 size: 351
[libx264 @ 00000000643328a0] consecutive B-frames: 11.3% 11.3% 5.9% 71.4%
[libx264 @ 00000000643328a0] mb I I16..4: 11.3% 50.4% 38.3%
[libx264 @ 00000000643328a0] mb P I16..4: 0.4% 0.8% 0.3% P16..4: 30.4% 3.9% 6.0% 0.0% 0.0% skip:58.1%
[libx264 @ 00000000643328a0] mb B I16..4: 0.0% 0.0% 0.0% B16..8: 15.9% 0.7% 0.2% direct: 0.6% skip:82.6% L0:29.2% L1:69.7% BI: 1.2%
[libx264 @ 00000000643328a0] final ratefactor: 16.14
[libx264 @ 00000000643328a0] 8x8 transform intra:50.7% inter:52.8%
[libx264 @ 00000000643328a0] coded y,uvDC,uvAC intra: 80.3% 76.0% 64.0% inter: 7.9% 9.2% 3.9%
[libx264 @ 00000000643328a0] i16 v,h,dc,p: 38% 19% 8% 35%
[libx264 @ 00000000643328a0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 37% 16% 13% 4% 4% 7% 4% 5% 10%
[libx264 @ 00000000643328a0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 38% 18% 6% 4% 6% 10% 4% 5% 7%
[libx264 @ 00000000643328a0] i8c dc,h,v,p: 48% 16% 30% 6%
[libx264 @ 00000000643328a0] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 00000000643328a0] ref P L0: 88.2% 1.9% 7.4% 2.5%
[libx264 @ 00000000643328a0] ref B L0: 92.1% 6.9% 1.1%
[libx264 @ 00000000643328a0] ref B L1: 97.8% 2.2%
[libx264 @ 00000000643328a0] kb/s:399.21
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[libx264 @ 00000000635b8f80] non-strictly-monotonic PTS
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[h264 @ 0000000063439f60] error while decoding MB 20 12, bytestream -20
[h264 @ 0000000063439f60] concealing 161 DC, 161 AC, 161 MV errors in P frame
[swscaler @ 000000006344e840] deprecated pixel format used, make sure you did set range correctly
[libx264 @ 00000000635b8f80] frame I:12 Avg QP: 7.28 size: 37316
[libx264 @ 00000000635b8f80] frame P:69 Avg QP: 8.37 size: 10682
[libx264 @ 00000000635b8f80] frame B:203 Avg QP:13.32 size: 1705
[libx264 @ 00000000635b8f80] consecutive B-frames: 4.6% 0.0% 1.1% 94.4%
[libx264 @ 00000000635b8f80] mb I I16..4: 24.6% 36.8% 38.6%
[libx264 @ 00000000635b8f80] mb P I16..4: 1.7% 3.7% 3.7% P16..4: 36.5% 9.9% 14.5% 0.0% 0.0% skip:30.0%
[libx264 @ 00000000635b8f80] mb B I16..4: 0.0% 0.0% 0.0% B16..8: 27.8% 5.0% 2.0% direct: 2.9% skip:62.2% L0:47.2% L1:50.2% BI: 2.6%
[libx264 @ 00000000635b8f80] final ratefactor: 10.96
[libx264 @ 00000000635b8f80] 8x8 transform intra:38.4% inter:33.4%
[libx264 @ 00000000635b8f80] coded y,uvDC,uvAC intra: 83.3% 0.0% 0.0% inter: 15.8% 0.0% 0.0%
[libx264 @ 00000000635b8f80] i16 v,h完成了读流的方法
翻译后:
使用了像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[libx264@00000000 635b8f80]非严格单调pts
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[libx264@00000000 635b8f80]非严格单调pts
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[libx264@00000000 643328a0]帧I:15平均qp:13.91大小:52452
[libx264@00000000 643328a0]帧P:115平均qp:17.78大小:4696
[libx264@00000000 643328a0]帧B:223平均qp:17.87大小:351
[libx264@00000000 643328a0]连续B帧:11.3%11.3%5.9%71.4%
[libx264@00000000 643328a0]mb i i16..4:11.3%50.4%38.3%
[libx264@00000000 643328a0]mb p i16..4:0.4%0.8%0.3%p16..4:30.4%3.9%6.0%0.0%0.0%跳过:58.1%
[libx264@00000000 643328a0]mb b i16..4:0.0%0.0%0.0%b16..8:15.9%0.7%0.2%直接:0.6%跳过:82.6%l0:29.2%l1:69.7%bi:1.2%
[libx264@00000000 643328a0]最终费率系数:16.14
[libx264@00000000 643328a0]8x8转换内部:50.7%内部:52.8%
[libx264@00000000 643328a0]编码Y,uvdc,uvac内部:80.3%76.0%64.0%内部:7.9%9.2%3.9%
[libx264@00000000 643328a0]I16 V,H,DC,P:38%19%8%35%
[libx264@00000000 643328a0]i8 V,H,DC,DDL,DDR,VR,HD,VL,HU:37%16%13%4%4%7%4%5%10%
[libx264@00000000 643328a0]I4 V,H,DC,DDL,DDR,VR,HD,VL,HU:38%18%6%4%6%10%4%5%7%
[libx264@00000000 643328a0]i8c直流、H、V、P:48%16%30%6%
[libx264@00000000 643328a0]加权P帧:y:0.0%uv:0.0%
[libx264@00000000 643328a0]参考P l0:88.2%1.9%7.4%2.5%
[libx264@00000000 643328a0]参考B l0:92.1%6.9%1.1%
[libx264@00000000 643328a0]参考B l1:97.8%2.2%
[libx264@00000000 643328a0]kb/s:399.21
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[libx264@00000000 635b8f80]非严格单调pts
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[h264@00000000 63439f60]解码mb 20 12时出错,字节串-20
[H264@00000000 63439F60]隐藏161直流、161交流、161毫伏P帧错误
[swscaler@00000000 6344E840]不推荐使用像素格式,请确保正确设置了范围。
[libx264@00000000 635b8f80]帧I:12平均qp:7.28大小:37316
[libx264@00000000 635b8f80]帧P:69平均qp:8.37大小:10682
[libx264@00000000 635b8f80]帧B:203平均qp:13.32大小:1705
[libx264@00000000 635b8f80]连续B帧:4.6%0.0%1.1%94.4%
[libx264@00000000 635b8f80]MB I 16..4:24.6%36.8%38.6%
[libx264@00000000 635b8f80]mb p i16..4:1.7%3.7%3.7%p16..4:36.5%9.9%14.5%0.0%0.0%跳过:30.0%
[libx264@00000000 635b8f80]mb b i16..4:0.0%0.0%0.0%b16..8:27.8%5.0%2.0%直接:2.9%跳过:62.2%l0:47.2%l1:50.2%bi:2.6%
[libx264@00000000 635b8f80]最终利率系数:10.96
[libx264@00000000 635b8f80]8x8转换内部:38.4%内部:33.4%
[libx264@00000000 635b8f80]编码Y,uvdc,uvac内部:83.3%0.0%0.0%内部:15.8%0.0%0.0%
[libx264@00000000 635b8f80]i16 v,h完成了读取流的方法
分析:
不推荐使用像素格式,请确保正确设置了范围
发现问题后特地去拜读了大神分享的内容,以下是用到的部分知识。
参考链接:https://blog.csdn.net/eguid_1/article/details/82875343
以上全是些空话,我们无非就是要用javaCV采集视频和音频,给这些音视频编解码,然后是用什么封装格式封装这些音视频数据,以及用什么协议传输,可能还要对视频里的图像进一步进行处理(这个属于图像处理范畴),流程大致如此(音频方面了解不多,大家见谅):
拉流(采集)--->图像像素数据/音频数据<---->编/解码 <---->音/视频帧<---->解封装/封装---->推流
举例:编解码过程(以hevc编码的rtsp转rtmp/flv为例,无音频数据):
rtsp流---
拉流解复用--->h265(hevc)---解码--->yuv像素数据---编码--->h264---封装推流--->rtmp/flv
图像像素格式(简称像素格式),一般指的是没有经过编码的按照原始像素排列的数据。
举个栗子,一个完整图像的像素排列一般是这样的(以4*4像素的rgb像素格式为例):
rgbrgbrgbrgb
当然我们存储的时候一般使用一维数组来存这些数据,所以排列顺序就变成这样:rgbrgbrgbrgb.......以此类推。
图片封装格式指的我们日常见到的png,jpg,bmp,gif等等图片格式,其中bmp是无损格式,里面的数据格式就是图片头信息加上rgb排列的像素数据,png/jpg这些都是有损压缩格式,但是压缩比还是很高的,为什么要压缩下面会讲到。
图像像素数据指的是yuv、rgb,rbga,bgr,gbra等图像像素格式,经过编码后才是视频帧。比如我们常见的h264编码,编码其实就是对图像像素数据的压缩,(以rgb为例,假如当前图像像素尺寸为1920*1080,,每种颜色用一个字节表示,也就是说每个像素点有红绿蓝三色共3字节,图像有1920*1080个像素点,也就是说这张图像大小为1920*1080*3字节,显然数据太大了),可以这样理解,h264编码本质上就是一种图像数据压缩算法。
补充:视频帧中常常提到的I帧,B帧和P帧指的是什么?i帧也叫关键帧,实际上就是一张完整的静态图像,而B帧和P帧只是用来记录画面的运动矢量等非图像数据,B/P帧都需要依赖i帧才能够正确解码出完整图像(有损的图像画面)。在实际应用中各种视频源中很少使用B帧,原因是虽然使用大量B帧可以提高压缩率,但也会消耗更多的硬件性能,所以大多数情况下的视频源都以i帧(关键帧)和大量P帧为主。
另外在直播应用中i帧间隔会很低,这样能够更快的显示首帧画面(B/P帧需要i帧才能够解码),但是这样也增加了传输的数据量,因为一个i帧通常会很大。
编码上面已经讲了,是一种压缩算法;那么封装格式又是什么呢,封装格式就是我们日常见到的视频文件了,比如mp4,avi,mkv,flv等等等,按照每种封装格式的规范把视频帧和音频按照一定顺序存起来就成我们日常看到的视频文件了,这些封装格式一般都会包含一些头/尾标识和一些视频描述信息,这样播放器读取视频文件的时候就知道该怎么播放这些视频文件了(可以把封装格式理解成收纳箱,上面贴着小纸条说明里面放了哪些东西)。
压缩图片格式也可以参考视频编码格式,原理都一样,都是对图像数据做有损/无损压缩。
音/视频源可以是视频文件、音频文件,流媒体源,设备等等。
比如我们要看电脑或手机摄像头视频,就得采集设备的图像数据(从源设备采集到的是像素数据,一般是bgr或者rgb像素数据)如果是某些厂商的商用摄像机,可能会支持rtsp/rtmp协议,要采集声音呢,就得采集录音/话筒设备里面的数据(一般是pcm采样数据)
rtsp协议栈,rtmp协议栈,hls,http-flv(理论上讲这个flv不能算是流媒体协议,它只是个无限大的flv文件)等等。
例如rtmp,对编码后的音视频帧,要对其进行封装成flv进行传输。
补充:说到底这些协议原理上依然是建立在tcp/udp基础上的应用层传输协议。
支持音视频存储分发的服务都可以叫流媒体服务。
处理音视频流媒体的前置基本知识,基本知识包含了像素格式、编解码格式、封装格式、网络协议以及一些音视频专业名词,专业名词不会赘述,自行搜索即可。
javaCV的官方文档:
JavaCV是计算机视觉领域的开发人员(OpenCV、FFmpeg、libdc1394、PGR FlyCapture、OpenKinect、li.lsense、CL PS3 Eye Driver、videoInput、ARToolKitPlus、flandmark、Leptonica和Tesseract)常用库的JavaCPP预置的包装器,并提供实用的程序类使它们的功能更容易在Java平台上使用,包括Android。
JavaCV还提供了硬件加速的全屏图像显示(CanvasFrame和GLCanvasFrame)、在多核(并行)上并行执行代码的简便方法、照相机和投影机的用户友好的几何和颜色校准(GeometricCalibrator,ProCamometricCalibrato)r,ProCamColorCalibrator),特征点的检测和匹配(ObjectFinder),一组用于实现投影仪-照相机系统的直接图像对齐的类(主要是GNImageAligner、ProjectiveTransformer、ProjectiveColorTransformer、ProCamTransformer和ReflectanceInitializer),一个blob分析包(BLUB),以及JavaCV类中的各种功能。其中一些类还具有OpenCL和OpenGL的对应类,它们的名称以CL结尾或以GL开始,即:JavaCVCL、GLCanvasFrame等。
要了解如何使用API,因为文档目前缺乏,请参考下面的示例用法部分以及示例程序,包括两个用于Android(FACEPREVIEW.Java和ReordActudio.java)的示例程序,它们也在示例目录中找到。您可能还发现参考ProCamCalib和ProCamTracker的源代码以及从OpenCV2 Cookbook和相关联的wiki页面移植的示例很有用。
大家知道FFmpeg是C语言中著名的音视频库(注意,不是c++。使用c++调用ffmpeg库的性能损失与Java方式调用损耗相差并不大)。
JavaCV利用JavaCPP在FFmpeg和Java之间构建了桥梁,我们通过这个桥梁可以方便的调用FFmpeg,当然这并不是没有损失的,性能损失暂且不提,最主要问题在于调用ffmpeg之于jvm是native方法,所以通过ffmpeg创建的结构体实例与常量、方法等等都是使用堆外内存,都需要像C那样手动的释放这些资源(jvm并不会帮你回收这部分),以此来保证不会发生内存溢出/泄露等风险。
Javapp在Java内部提供了对本地C++的高效访问,这与一些C/C++编译器与汇编语言交互的方式不同。不需要发明新的语言,比如SWIG、SIP、C++、CLI、Cython或Rython。相反,类似于CPpyy为Python所做的努力,它利用了Java和C++之间的语法和语义相似性。在引擎盖下,它使用JNI,因此除了Java、SE和RoboVM(指令)之外,它还适用于Java SE的所有实现...
详细描述请参考:https://github.com/bytedeco/javacpp
我们通过《视频拉流解码成YUVJ420P,并保存为jpg图片》作为实例来阐述,实例地址:
https://blog.csdn.net/eguid_1/article/details/81369055
这部分内容主要是如何调用FFmpeg的API,本系列作为JavaCV入门不会讲解FFmpeg的具体用法,如果想要深入学习FFmpeg部分,可以选择通过查看FFmpeg的API手册ffmpeg.org,或者访问雷霄骅的博客详细学习FFmpeg的使用。
JavaCV通过JavaCPP调用了FFmpeg,并且对FFmpeg复杂的操作进行了封装,把视频处理分成了两大类:“帧抓取器”(FrameGrabber)和“帧录制器”(又叫“帧推流器”,FrameRecorder)以及用于存放音视频帧的Frame(FrameFilter暂且不提)。
整体结构如下:
视频源---->帧抓取器(FrameGabber) ---->抓取视频帧(Frame)---->帧录制器(FrameRecorder)---->推流/录制---->流媒体服务/录像文件
封装了FFmpeg的检索流信息,自动猜测视频解码格式,音视频解码等具体API,并把解码完的像素数据(可配置像素格式)或音频数据保存到Frame中返回。
封装了FFmpeg的音视频编码操作和封装操作,把传参过来的Frame中的数据取出并进行编码、封装、发送等操作流程。
用于存放音视频帧(图像像素和音频采样数据,如果没有配置FrameGrabber的像素格式和音频格式,那么默认解码后的视频格式是yuv420j,音频则是pcm采样数据)
查找到此,感谢大神 eguid ,初步怀疑可能是像素或者内存的原因。
-----帧的问题
获取frame:
/**
* 转流器 最新版
*
* @param inputFile
* @param outputFile
* @throws Exception
* @throws org.bytedeco.javacv.FrameRecorder.Exception
* @throws InterruptedException
*/
public static void recordPush4(String inputFile, String outputFile, int v_rs) throws Exception, org.bytedeco.javacv.FrameRecorder.Exception, InterruptedException {
Loader.load(opencv_objdetect.class);
long startTime = 0;
//帧抓取器(FrameGrabber)
FrameGrabber grabber = FFmpegFrameGrabber.createDefault(inputFile);
try {
grabber.start();
} catch (Exception e) {
try {
grabber.restart();
} catch (Exception e1) {
throw e;
}
}
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
Frame grabframe = grabber.grab();
opencv_core.IplImage grabbedImage = null;
if (grabframe != null) {
System.out.println("取到第一帧");
grabbedImage = converter.convert(grabframe);
} else {
System.out.println("没有取到第一帧");
}
//帧录制器/推流器(FrameRecorder)
//如果想要保存图片,可以使用 opencv_imgcodecs.cvSaveImage("hello.jpg", grabbedImage);来保存图片
FrameRecorder recorder;
try {
recorder = FrameRecorder.createDefault(outputFile, 1280, 720);
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
throw e;
}
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // avcodec.AV_CODEC_ID_H264
recorder.setFormat("flv");
recorder.setFrameRate(v_rs);
recorder.setGopSize(v_rs);
System.out.println("准备开始推流...");
try {
recorder.start();
} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
try {
System.out.println("录制器启动失败,正在重新启动...");
if (recorder != null) {
System.out.println("尝试关闭录制器");
recorder.stop();
System.out.println("尝试重新开启录制器");
recorder.start();
}
} catch (org.bytedeco.javacv.FrameRecorder.Exception e1) {
throw e;
}
}
System.out.println("开始推流");
//CanvasFrame frame = new CanvasFrame("camera", CanvasFrame.getDefaultGamma() / grabber.getGamma());
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.setAlwaysOnTop(true);
while ( //frame.isVisible() &&
(grabframe = grabber.grab()) != null) {
System.out.println("推流..."+inputFile);
//frame.showImage(grabframe);
grabbedImage = converter.convert(grabframe);
Frame rotatedFrame = converter.convert(grabbedImage);
if (startTime == 0) {
startTime = System.currentTimeMillis();
}
recorder.setTimestamp(1000 * (System.currentTimeMillis() - startTime));//时间戳
if (rotatedFrame != null) {
recorder.record(rotatedFrame);
}
Thread.sleep(40);
}
//frame.dispose();
recorder.stop();
recorder.release();
grabber.stop();
System.exit(2);
}
最后:nginx一个摄像头只能推一次,不能每次用推一次,也不能多个页面同时推一个。
多个摄像头,只需要在启动时用listener监听,线程依次启动即可。