VLC RTSP视频播放终极解决方案
来源:互联网 发布:nginx php 500错误 编辑:程序博客网 时间:2024/05/17 18:17
VLC播放RTSP视频流遇到的问题
摄像头是RTSP协议的,需要在Android端实时显示摄像头视频流,这里采用了开源的VLC播放器,可能会有如下需求:
一、有截屏的需求
二、有屏幕录制的需求
三、视频本来是横的,但是现在要竖屏显示,如何旋转视频,另外旋转后视频会拉伸,因此需要截取一段显示
四、显示的视频可能需要做额外处理,比如识别出人脸后框出来
先说说直接用VLC播放器的SDK会遇到的问题,利用SDK显示视频通常是如下写法:
private MediaPlayer mMediaPlayer;private LibVLC mVlc;void createPlayer(String url, int width, int height) { ArrayList<String> options = new ArrayList<>(); options.add("--aout=opensles"); options.add("--audio-time-stretch"); options.add("-vvv"); mVlc = new LibVLC(context, options); mMediaPlayer = new MediaPlayer(mVlc); IVLCVout vout = mMediaPlayer.getVLCVout(); vout.setVideoView(textureView); vout.attachViews(); vout.setWindowSize(width, height); Media m = new Media(mVlc, Uri.parse(url)); int cache = 1000; m.addOption(":network-caching=" + cache); m.addOption(":file-caching=" + cache); m.addOption(":live-cacheing=" + cache); m.addOption(":sout-mux-caching=" + cache); m.addOption(":codec=mediacodec,iomx,all"); mMediaPlayer.setMedia(m); mMediaPlayer.play();}public void releasePlayer() { mMediaPlayer.setVideoCallback(null, null); mMediaPlayer.stop(); IVLCVout vout = mMediaPlayer.getVLCVout(); vout.detachViews(); mVlc.release(); mVlc = null;}
这里值得一提的是调setVideoView设置视频输出,可以是TextureView,也可以是SurfaceView,也可以是SurfaceTexture。我尝试过使用SurfaceTexture,然后当Frame Available时再从SurfaceTexture绘制到Window Surface上,结果显示出来的是一团糟,原因尚未查明。
另外为了避免视频播放时卡顿,最好加上各种Cache。注意Cache设得太大会增加延时。
接下来说说这种方式的局限:
1,对于截屏的需求,如果采用的SurfaceView,是无法getDrawingCache的。采用TextureView的话系统提供了接口获取截屏Bitmap的。尝试过采用SurfaceTexture绘制再glReadPixels获取RGBA这种办法失败了,最终的图像是混沌的。
2,对于视频录制,除非能拿到每一帧视频数据,否则无解,如果能从SurfaceTexture上拷出数据就行了,但是实践中发现拷出来的图像是混沌的,原因未明。
3,对于横竖屏切换,假如视频是横的,手机分辨率是1920 * 1080,如果要竖屏显示,需要对视频进行旋转,对于TextureView可以采用setTransform(Matrix),为了避免视频拉伸需要截取一部分来显示,但是默认截取是从左到右或从上到下的,假如我要截取视频中间的部分就不行了。
4,对于额外处理的需求最靠谱的办法还是拿到视频流,离线渲染完成后再显示。
综上,解决一切问题的核心就是拿到视频流。网上关于截屏和视频录制的方案都是抄来抄去的,VLC的native层本来是有截屏和视频录制功能的,只是没开放给Java层,所以自己加几行代码开放出来重新编译一下就OK了。但是仍然没解决根本问题:拿到视频流。
为了解决这个问题,我们只能翻vlc的代码。首先给vlc-android的代码同步下来,然后编译一遍,建议在linux下编,过程中会遇到各种各样的问题,google并解决之。编译完后会在libvlc目录下生成一堆so文件,包括jni目录中的libc++_shared.so, libvlc.so, libvlcjni.so,还有private_libs目录中的libiomx.so和libanw.xo,另外还会output一个aar文件,我们直接用这个aar文件就好了,里面已经给so都打包了。
接下来正式看vlc的代码了,libvlc是重点,这个相当于一个中间层,是封装了给Android端用的。里面最终还是调用底层的vlc框架,我们就不用关注了。libvlc里有两个文件是重点,一个是libvlcjni.c,一个是libvlcjni-mediaplayer.c。
先看看libvlcjni-media_player.h头文件,里面介绍了一些关键的接口,注释非常详细,需要仔细阅读,获取视频流的答案就在里面。就是这两个函数:
LIBVLC_APIvoid libvlc_video_set_callbacks( libvlc_media_player_t *mp, libvlc_video_lock_cb lock, libvlc_video_unlock_cb unlock, libvlc_video_display_cb display, void *opaque );LIBVLC_APIvoid libvlc_video_set_format( libvlc_media_player_t *mp, const char *chroma, unsigned width, unsigned height, unsigned pitch );
为了获取视频流,首先要调用libvlc_video_set_format设置视频流编码格式和宽高,然后调用libvlc_video_set_callbacks设置回调,里面有三个回调,我们用到的是lock和display,在lock中传入buffer,解码后的视频流会写到该buffer中,然后在display中将buffer回调到java层。
胜利的曙光依稀就在眼前,我们在libvlcjni-mediaplayer.c中插入以下代码:
voidJava_org_videolan_libvlc_MediaPlayer_nativeSetVideoFormat(JNIEnv *env, jobject thiz, jstring format, jint width, jint height, jint pitch) { vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz); if (!p_obj) return; const char *formatStr = (*env)->GetStringUTFChars(env, format, NULL); libvlc_video_set_format(p_obj->u.p_mp, formatStr, width, height, pitch); (*env)->ReleaseStringUTFChars(env, format, formatStr);}struct myfield { jclass mediaPlayerClazz; jmethodID onDisplayCallback; jobject thiz; void *buffer;} myfield;static void *lock(void *data, void ** p_pixels) { *p_pixels = myfield.buffer; return NULL;}static void unlock(void *data, void *id, void * const * p_pixels) {}static pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;static void display(void *data, void *id) { JavaVM *jvm = fields.jvm; JNIEnv *env; int stat = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2); if (stat == JNI_EDETACHED) { if ((*jvm)->AttachCurrentThread(jvm, (void **) &env, NULL) != 0) { return; } } else if (stat == JNI_OK) { // } else if (stat == JNI_EVERSION) { return; } pthread_mutex_lock(&myMutex); if (myfield.thiz != NULL) { (*env)->CallVoidMethod(env, myfield.thiz, myfield.onDisplayCallback); } pthread_mutex_unlock(&myMutex); (*jvm)->DetachCurrentThread(jvm);}voidJava_org_videolan_libvlc_MediaPlayer_nativeSetVideoBuffer(JNIEnv *env, jobject thiz, jobject buffer) { vlcjni_object *p_obj = VLCJniObject_getInstance(env, thiz); libvlc_media_player_t *mp = p_obj->u.p_mp; if (!mp) { return; } if (buffer == NULL) { (*env)->DeleteGlobalRef(env, myfield.mediaPlayerClazz); pthread_mutex_lock(&myMutex); (*env)->DeleteGlobalRef(env, myfield.thiz); myfield.thiz = NULL; pthread_mutex_unlock(&myMutex); return; } myfield.mediaPlayerClazz = (*env)->FindClass(env, "org/videolan/libvlc/MediaPlayer"); myfield.mediaPlayerClazz = (jclass) (*env)->NewGlobalRef(env, myfield.mediaPlayerClazz); myfield.onDisplayCallback = (*env)->GetMethodID(env, myfield.mediaPlayerClazz, "onDisplay", "()V"); myfield.thiz = (*env)->NewGlobalRef(env, thiz); myfield.buffer = (*env)->GetDirectBufferAddress(env, buffer); libvlc_video_set_callbacks(mp, lock, NULL, display, NULL);}
要注意的是这里用到了fields.jvm,是在libvlcjni.c中的Jni_OnLoad时保存的全局JavaVM,要在struct fields中添加成员jvm。此外org.videolan.libvlc.MediaPlayer.java中添加代码如下:
public void setVideoFormat(String format, int width, int height, int pitch) { nativeSetVideoFormat(format, width, height, pitch);}private native void nativeSetVideoFormat(String format, int width, int height, int pitch);private ByteBuffer mBuffer;private MediaPlayCallback mCallback;public void setVideoCallback(ByteBuffer buffer, MediaPlayCallback callback) { mBuffer = buffer; mCallback = callback; nativeSetVideoBuffer(buffer);}private native void nativeSetVideoBuffer(ByteBuffer buffer);private void onDisplay() { if (mCallback != null) { mCallback.onDisplay(mBuffer); }}
MediaPlayerCallback.java定义如下,直接返回了视频流buffer。
public interface MediaPlayCallback { public void onDisplay(ByteBuffer buffer);}
这个视频流是RGBA的,我们可以用OpenGL来渲染。如果直接转成Bitmap再显示性能就堪忧了。
接下来再来说说以上jni中要注意的一些问题,
一,内存泄露,这里setVideoCallback时会从java层传下来buffer和MediaPlayer对象,其中MediaPlayer由于之后在Display回调中要用到,因此NewGlobalRef保存下来,这里如果没有释放的话,当
MediaPlayer重建后,如手机横竖屏切换或多次退出进来,之前的MediaPlayer会一直被JNI层持有,包括MediaPlayer中的Buffer就泄露了,这个Buffer通常都不小。所以释放MediaPlayer时
要解除jni层的引用。
二,回调在子线程,Display回调是在子线程的,这里需要获取子线程的JNIEnv,需要AttachCurrentThread,调用完后在Detach。另外考虑到线程同步,要加上锁。
三,这里的Buffer是Java层传下来的,考虑到性能,buffer是通过ByteBuffer.allocDirect创建的,这样可以直接被native层使用,通过GetDirectBufferAddress获取到buffer的地址。
当数据更新完后通知Java层直接读就好了,不用多余的拷贝。
最后再来谈谈文章开始提到的四个需求,
一,对于截屏,拿到了视频的Buffer数据后,可以通过如下方式生成Bitmap,然后保存文件
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);bitmap.copyPixelsFromBuffer(buffer);
二,对于录屏,可以参考我的如下项目中的视频录制部分
https://github.com/dingjikerbo/Android-Camera
三,关于横竖屏切换及视频裁剪,同样可参考我的项目
https://github.com/dingjikerbo/Android-Camera
大致思路是通过OpenGL渲染来处理视频,先渲染到Offscreen Surface上,然后再Blit到Window Surface上显示,Blit时可以指定要裁剪的区域。
四,关于视频的额外处理,如滤镜或者人脸识别同样可以参考我的Android-Camera项目
Demo项目地址:https://github.com/dingjikerbo/Android-RTSP
我的博客
- VLC RTSP视频播放终极解决方案
- Vlc播放rtsp视频
- Vlc播放rtsp视频
- Vlc播放rtsp视频
- VLC播放RTSP视频延迟问题
- 使用vlc sdk播放rtsp视频流
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题 (转)
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- VLC播放RTSP视频延迟问题
- IOS开发之VLC播放器播放RTSP视频流
- VLC播放RTSP视频流(360浏览器可用)
- Ubuntu 16.04 vlc和ffmpeg播放rtsp视频
- html的jQuery判断格式和从文本框获取信息放入表格
- 卷积神经网络应用:基于Tensorflow的CNN/CRF图像分割技术
- 关于同步异步、阻塞非阻塞的一些回答(简明扼要,准确精辟)
- 单源最短路(dijkstra)
- 横向滑动
- VLC RTSP视频播放终极解决方案
- JavaScript 隐式转换
- 1、JQuery——选择器
- C++ 对象模型--1小窥
- vue中关于computed的理解及其其他扩展
- 简述mybatis和hibernate的区别
- s5pv210-Linux驱动之USB鼠标
- 开始使用ABP.CORE模板 (ASP.NET Core with Angular)
- 【C++】多态机制的剖析!!!