你如何在 Android LibVLC 中全屏显示 RTSP 流?

How do you Fullscreen RTSP Stream in Android LibVLC?

我正在使用 mrmaffen 的 VLC-ANDROID-SDK 开发一个 RTSP 流媒体应用程序。 https://github.com/mrmaffen/vlc-android-sdk

我在让它工作方面取得了很大的成功,而且 运行 非常好,但我遇到的问题似乎无法解决,那就是让它全屏显示视频源在 SurfaceView 上,甚至只是在 SurfaceView 的中心。

这是我得到的:

http://s1378.photobucket.com/user/Jo_Han_Solo/media/Screenshot_20171214-125504_zps437k1kw2.png.html?filters[user]=146993343&filters[recent]=1&sort=1&o=1

黑色 window 是屏幕的总大小,我希望该视频填满屏幕并希望始终从中心填充,但我不知道该怎么做。

有人遇到过类似的事情并且知道如何解决吗?

我有点解决了这个问题,但有点狡猾,还远未完成,但考虑到缺乏关于该主题的知识和信息,我认为这可能暂时对某人有所帮助。

  1. 查找屏幕尺寸。
  2. 设置最终的 IVLCOut 以包含屏幕尺寸。
  3. 将 setScale 调整为 "fullscreen" 视频流。

解释每个任务:

  1. 设置你的全局变量:

    public class SingleStreamView extends AppCompatActivity implements 
    IVLCVout.Callback {
    
    public int mHeight;
    public int mWidth;
    

其次,在 onCreate 任务中找到您设备的屏幕尺寸:

    DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        mHeight = displayMetrics.heightPixels;
        mWidth = displayMetrics.widthPixels;

2。 然后进入您的 "CreatePlayer" 活动并设置视频输出:

    // Set up video output
        final IVLCVout vout = mMediaPlayer.getVLCVout();
        vout.setVideoView(mSurface);
        vout.setWindowSize(mWidth,mHeight);
        vout.addCallback(this);
        vout.attachViews();

使它在我的表面居中的获胜线是 "vout.setWindowSize(mWidth,mHeight);"

然后我简单地使用 setscale 选项 "fullscreen" 视频。也就是说,这是一种 hack 的做法,我想尝试找出一种获取编解码器信息的方法,以便动态设置视频的比例和这样会自动将每个尺寸的视频流全屏显示到任何尺寸的屏幕,但目前这适用于已知的视频流分辨率,它会自动调整到您的 phone.

的屏幕尺寸

无论哪种方式,我发现对于 Samsung Galaxy s8,640x480p RTSP 流的良好比例因子是 1.8。编码如下:

        Media m = new Media(libvlc, Uri.parse(RTSP_ADDRESS));
        m.setHWDecoderEnabled(true,false);
        m.addOption(":network-caching=100");
        m.addOption(":clock-jitter=0");
        m.addOption(":clock-synchro=0");
        m.addOption(":fullscreen");
        mMediaPlayer.setMedia(m);
        mMediaPlayer.setAspectRatio("16:9");
        mMediaPlayer.setScale(1.8f);
        mMediaPlayer.play();

你在哪里"mMediaPlayer.setScale(1.8f);"

希望这对某人有所帮助!

您的解决方案似乎很有趣,但是我面临着同样的问题,我似乎无法用您的方法解决(还)。

到目前为止我得到的截图可以在以下位置看到: https://photos.app.goo.gl/9nKo22Mkc2SZq4SK9

我还想在 Samsung-XCover4(720x1280 像素)和最小分辨率为 320x480 的设备上以 landscape/portrait 模式(垂直)居中 rtsp 视频流。我希望 运行 的最低 Android SDK 版本是 API-22 (Android 5.1.1)。 我让(嵌入式)VLC 播放器工作的 libvlc 代码基于 'de.mrmaffen:libvlc-android:2.1.12@aar'.

鉴于上述 'requirements',您可以在屏幕截图中看到以下行为。前两个屏幕截图在 Samsung-XCover4 (720x1280) 上,您可以在其中看到 device-orientation=landscape 剪辑视频并且不缩放它,而第三个和第四个屏幕截图显示相同的视频流不跟随SURFACE_BEST_FIT 方法(请参阅下面的代码以获取解释)在具有小分辨率的设备上。 我很想看到一个 updateVideoSurfaces 来处理设备方向的变化,或者至少在启动时显示整个视频。

我的 VLC 视频播放器的布局(垂直 LinearLayout 的一部分)如下:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="0.3"
    android:layout_marginBottom="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/video_surface_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:foregroundGravity="clip_horizontal|clip_vertical"
        tools:ignore="true">

        <ViewStub
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout="@layout/surface_view"
            android:id="@+id/surface_stub" />

        <ViewStub
            android:layout_width="1dp"
            android:layout_height="1dp"
            android:layout="@layout/surface_view"
            android:id="@+id/subtitles_surface_stub" />

        <ViewStub
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout="@layout/texture_view"
            android:id="@+id/texture_stub" />

    </FrameLayout>

</LinearLayout>

我从 de.mrmaffen 获得的示例代码使用了一个 updateVideoSurfaces(见下文 java-代码),它使用了一些 SURFACE_XX 方法,在我看来它涵盖了所有场景不同的设备方向和分辨率。

出于某种原因,我无法弄清楚为什么这不起作用,我怀疑我为播放器(FrameLayout/ViewStub)使用的布局可能会导致问题。

我想知道您是否可以阐明一些方向,以确保视频流 auto-scale/center 在任何设备 orientation/resolution。

我使用的播放器代码如下:

package com.testing.vlc2player;

import ...

public class VLC2PlayerActivity extends AppCompatActivity implements IVLCVout.OnNewVideoLayoutListener,
        IVLCVout.Callback {

    private static final Logger log = LoggerFactory.getLogger(VLC2PlayerActivity.class);

    private static final boolean USE_SURFACE_VIEW = true;
    private static final boolean ENABLE_SUBTITLES = false;
    private static final int SURFACE_BEST_FIT = 0;
    private static final int SURFACE_FIT_SCREEN = 1;
    private static final int SURFACE_FILL = 2;
    private static final int SURFACE_16_9 = 3;
    private static final int SURFACE_4_3 = 4;
    private static final int SURFACE_ORIGINAL = 5;
    private static final int CURRENT_SIZE = SURFACE_BEST_FIT;

    private FrameLayout mVideoSurfaceFrame = null;
    private SurfaceView mVideoSurface = null;
    private SurfaceView mSubtitlesSurface = null;
    private TextureView mVideoTexture = null;
    private View mVideoView = null;

    private final Handler mHandler = new Handler();
    private View.OnLayoutChangeListener mOnLayoutChangeListener = null;

    private LibVLC mLibVLC = null;
    private MediaPlayer mMediaPlayer = null;
    private int mVideoHeight = 0;
    private int mVideoWidth = 0;
    private int mVideoVisibleHeight = 0;
    private int mVideoVisibleWidth = 0;
    private int mVideoSarNum = 0;
    private int mVideoSarDen = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_player);

        setupVLCLayout();
    }

    private void setupVLCLayout() {
        log.debug("...");
        final ArrayList<String> args = new ArrayList<>();
        args.add("-vvv");
        mLibVLC = new LibVLC(this, args);
        mMediaPlayer = new MediaPlayer(mLibVLC);

        mVideoSurfaceFrame = findViewById(R.id.video_surface_frame);
        if (USE_SURFACE_VIEW) {
            ViewStub stub = findViewById(R.id.surface_stub);
            mVideoSurface = (SurfaceView) stub.inflate();
            if (ENABLE_SUBTITLES) {
                stub = findViewById(R.id.subtitles_surface_stub);
                mSubtitlesSurface = (SurfaceView) stub.inflate();
                mSubtitlesSurface.setZOrderMediaOverlay(true);
                mSubtitlesSurface.getHolder().setFormat(PixelFormat.TRANSLUCENT);
            }
            mVideoView = mVideoSurface;
        } else {
            ViewStub stub = findViewById(R.id.texture_stub);
            mVideoTexture = (TextureView) stub.inflate();
            mVideoView = mVideoTexture;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMediaPlayer.release();
        mLibVLC.release();
    }

    @Override
    protected void onStart() {
        super.onStart();
        final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
        if (mVideoSurface != null) {
            vlcVout.setVideoView(mVideoSurface);
            if (mSubtitlesSurface != null) {
                vlcVout.setSubtitlesView(mSubtitlesSurface);
            }
        } else {
            vlcVout.setVideoView(mVideoTexture);
        }
        vlcVout.attachViews(this);

        String url = getString(R.string.videoURL);
        Uri uri = Uri.parse(url);
        final Media media = new Media(mLibVLC, uri);
        mMediaPlayer.setMedia(media);
        media.release();
        mMediaPlayer.play();

        if (mOnLayoutChangeListener == null) {
            mOnLayoutChangeListener = new View.OnLayoutChangeListener() {
                private final Runnable mRunnable = new Runnable() {
                    @Override
                    public void run() {
                        updateVideoSurfaces();
                    }
                };
                @Override
                public void onLayoutChange(View v, int left, int top, int right,
                                           int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
                        mHandler.removeCallbacks(mRunnable);
                        mHandler.post(mRunnable);
                    }
                }
            };
        }
        mVideoSurfaceFrame.addOnLayoutChangeListener(mOnLayoutChangeListener);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mOnLayoutChangeListener != null) {
            mVideoSurfaceFrame.removeOnLayoutChangeListener(mOnLayoutChangeListener);
            mOnLayoutChangeListener = null;
        }
        mMediaPlayer.stop();
        mMediaPlayer.getVLCVout().detachViews();
    }

    private void changeMediaPlayerLayout(int displayW, int displayH) {
        log.debug("displayW={}, displayH={}", displayW, displayH);
        /* Change the video placement using the MediaPlayer API */
        int dispWd = displayW;
        int dispHt = displayH;
        dispWd = mVideoSurface.getWidth();  //Note: we do NOT want to use the entire display!
        dispHt = mVideoSurface.getHeight();
        switch (CURRENT_SIZE) {
            case SURFACE_BEST_FIT:
                mMediaPlayer.setAspectRatio(null);
                mMediaPlayer.setScale(0);
                break;
            case SURFACE_FIT_SCREEN:
            case SURFACE_FILL: {
                Media.VideoTrack vtrack = mMediaPlayer.getCurrentVideoTrack();
                if (vtrack == null) {
                    return;
                }
                final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom
                        || vtrack.orientation == Media.VideoTrack.Orientation.RightTop;
                if (CURRENT_SIZE == SURFACE_FIT_SCREEN) {
                    int videoW = vtrack.width;
                    int videoH = vtrack.height;
                    if (videoSwapped) {
                        int swap = videoW;
                        videoW = videoH;
                        videoH = swap;
                    }
                    if (vtrack.sarNum != vtrack.sarDen) {
                        videoW = videoW * vtrack.sarNum / vtrack.sarDen;
                    }
                    float ar = videoW / (float) videoH;
                    float dar = dispWd / (float) dispHt;
                    //noinspection unused
                    float scale;
                    if (dar >= ar) {
                        scale = dispWd / (float) videoW; /* horizontal */
                    } else {
                        scale = dispHt / (float) videoH; /* vertical */
                    }
                    log.debug("scale={}", scale);
                    mMediaPlayer.setScale(scale);
                    mMediaPlayer.setAspectRatio(null);
                } else {
                    mMediaPlayer.setScale(0);
                    mMediaPlayer.setAspectRatio(!videoSwapped ? ""+dispWd+":"+dispHt
                            : ""+dispHt+":"+dispWd);
                }
                break;
            }
            case SURFACE_16_9:
                mMediaPlayer.setAspectRatio("16:9");
                mMediaPlayer.setScale(0);
                break;
            case SURFACE_4_3:
                mMediaPlayer.setAspectRatio("4:3");
                mMediaPlayer.setScale(0);
                break;
            case SURFACE_ORIGINAL:
                mMediaPlayer.setAspectRatio(null);
                mMediaPlayer.setScale(1);
                break;
        }
    }

    private void updateVideoSurfaces() {
        log.debug("...");
        int sw = getWindow().getDecorView().getWidth();
        int sh = getWindow().getDecorView().getHeight();
        // sanity check
        if (sw * sh == 0) {
            log.error("Invalid surface size");
            return;
        }

        mMediaPlayer.getVLCVout().setWindowSize(sw, sh);

        ViewGroup.LayoutParams lp = mVideoView.getLayoutParams();
        if (mVideoWidth * mVideoHeight == 0) {
            /* Case of OpenGL vouts: handles the placement of the video using MediaPlayer API */
            lp.width  = ViewGroup.LayoutParams.MATCH_PARENT;
            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
            mVideoView.setLayoutParams(lp);
            lp = mVideoSurfaceFrame.getLayoutParams();
            lp.width  = ViewGroup.LayoutParams.MATCH_PARENT;
            lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
            mVideoSurfaceFrame.setLayoutParams(lp);
            changeMediaPlayerLayout(sw, sh);
            return;
        }

        if (lp.width == lp.height && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
            /* We handle the placement of the video using Android View LayoutParams */
            mMediaPlayer.setAspectRatio(null);
            mMediaPlayer.setScale(0);
        }

        double dw = sw, dh = sh;
        final boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

        if (sw > sh && isPortrait || sw < sh && !isPortrait) {
            dw = sh;
            dh = sw;
        }

        // compute the aspect ratio
        double ar, vw;
        if (mVideoSarDen == mVideoSarNum) {
            /* No indication about the density, assuming 1:1 */
            vw = mVideoVisibleWidth;
            ar = (double)mVideoVisibleWidth / (double)mVideoVisibleHeight;
        } else {
            /* Use the specified aspect ratio */
            vw = mVideoVisibleWidth * (double)mVideoSarNum / mVideoSarDen;
            ar = vw / mVideoVisibleHeight;
        }

        // compute the display aspect ratio
        double dar = dw / dh;

        switch (CURRENT_SIZE) {
            case SURFACE_BEST_FIT:
                if (dar < ar) {
                    dh = dw / ar;
                } else {
                    dw = dh * ar;
                }
                break;
            case SURFACE_FIT_SCREEN:
                if (dar >= ar) {
                    dh = dw / ar; /* horizontal */
                } else {
                    dw = dh * ar; /* vertical */
                }
                break;
            case SURFACE_FILL:
                break;
            case SURFACE_16_9:
                ar = 16.0 / 9.0;
                if (dar < ar) {
                    dh = dw / ar;
                } else {
                    dw = dh * ar;
                }
                break;
            case SURFACE_4_3:
                ar = 4.0 / 3.0;
                if (dar < ar) {
                    dh = dw / ar;
                } else {
                    dw = dh * ar;
                }
                break;
            case SURFACE_ORIGINAL:
                dh = mVideoVisibleHeight;
                dw = vw;
                break;
        }

        // set display size
        lp.width  = (int) Math.ceil(dw * mVideoWidth / mVideoVisibleWidth);
        lp.height = (int) Math.ceil(dh * mVideoHeight / mVideoVisibleHeight);
        mVideoView.setLayoutParams(lp);
        if (mSubtitlesSurface != null) {
            mSubtitlesSurface.setLayoutParams(lp);
        }
        // set frame size (crop if necessary)
        lp = mVideoSurfaceFrame.getLayoutParams();
        lp.width = (int) Math.floor(dw);
        lp.height = (int) Math.floor(dh);
        mVideoSurfaceFrame.setLayoutParams(lp);

        mVideoView.invalidate();
        if (mSubtitlesSurface != null) {
            mSubtitlesSurface.invalidate();
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    public void onNewVideoLayout(IVLCVout vlcVout, int width, int height,
                                 int visibleWidth, int visibleHeight,
                                 int sarNum, int sarDen) {
        log.debug("...");
        mVideoWidth = width;
        mVideoHeight = height;
        mVideoVisibleWidth = visibleWidth;
        mVideoVisibleHeight = visibleHeight;
        mVideoSarNum = sarNum;
        mVideoSarDen = sarDen;
        updateVideoSurfaces();
    }

    @Override
    public void onSurfacesCreated(IVLCVout vlcVout) {
        log.debug("vlcVout={}", vlcVout);
    }

    /**
     * This callback is called when surfaces are destroyed.
     */
    public void onSurfacesDestroyed(IVLCVout vlcVout) {
        log.debug("vlcVout={}", vlcVout);
    }

    public void onStopClientMonitoring(View view) {
//        log.info("UI -> Stop monitoring clientId= ...");
//        onBackPressed();
        String androidSDKRelease = Build.VERSION.RELEASE;
        int androidSDKInt = Build.VERSION.SDK_INT;
        String androidInfo = String.format(Locale.getDefault(), "Android %s (Version %d)", androidSDKRelease, androidSDKInt);
        String appVersionName = BuildConfig.VERSION_NAME;
        String appName = getString(R.string.app_name);
        String appInfoTitle = String.format(getString(R.string.app_info_title), appName);
        String infoMsg = String.format(getString(R.string.app_info_message), appVersionName, androidInfo);

        new AlertDialog.Builder(this).setTitle(appInfoTitle)
                .setMessage(infoMsg)
                .setPositiveButton(getString(R.string.button_ok), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Dismiss dialog
                        dialog.dismiss();
                    }
                })
                .create()
                .show();
    }
}