Как вы выполняете полноэкранный поток RTSP в Android LibVLC?

Я использую VLC-ANDROID-SDK mrmaffen для разработки приложения для потоковой передачи 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

Черное окно - это общий размер экрана, я хочу, чтобы это видео заполняло экран и, надеюсь, всегда заполнялось от центра, но я не могу понять, как это сделать.

Кто-нибудь сталкивался с подобным и знает, как это исправить?


person Johan Jarvi    schedule 17.12.2017    source источник


Ответы (2)


Я как бы решил проблему, но немного изворотливым способом, он далек от завершения, но, учитывая недостаток знаний и информации по теме, я подумал, что это может помочь кому-то на данный момент.

  1. Найдите размер экрана.
  2. Настройте свой окончательный IVLCOut, чтобы включить размер экрана.
  3. Настройте setScale на «полноэкранный» видеопоток.

Чтобы объяснить каждую задачу:

  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 для «полноэкранного» видео. Тем не менее, это немного хакерский способ сделать это, и я хотел бы попытаться найти способ получить информацию о кодеке, чтобы динамически устанавливать масштаб видео и, таким образом, автоматически полноэкранный видеопоток любого размера на экран любого размера, но на данный момент это будет работать для известных разрешений видеопотока, оно будет автоматически подстраиваться под размер экрана вашего телефона.

В любом случае я обнаружил, что с Samsung Galaxy s8 хороший коэффициент масштабирования для RTSP-потока 640x480p составляет 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);»

Надеюсь, это поможет кому-то!

person Johan Jarvi    schedule 20.12.2017

ваше решение кажется интересным, однако я сталкиваюсь с теми же проблемами, которые я не могу решить (пока) с помощью вашего подхода.

Скриншоты того, что у меня получилось, можно увидеть по адресу: https://photos.app.goo.gl/9nKo22Mkc2SZq4SK9

Я также хочу (по вертикали) центрировать rtsp-видеопоток в альбомном/портретном режиме на Samsung-XCover4 (с разрешением 720x1280 пикселей) и на устройстве с минимальным разрешением 320x480. Минимальная версия Android SDK, которую я бы хотел, чтобы она работала, — это API-22 (Android 5.1.1). Код libvlc, для которого у меня работает (встроенный) VLC-плеер, основан на «de.mrmaffen:libvlc-android:2.1.12@aar».

Учитывая приведенные выше «требования», вы можете увидеть следующее поведение на снимках экрана. Первые два снимка экрана сделаны на Samsung-XCover4 (720x1280), где видно, что устройство-ориентация=альбомная обрезает видео и не масштабирует его, тогда как 3-й и 4-й скриншоты показывают, что один и тот же видеопоток не следует метод 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), может вызывать проблемы.

Мне было интересно, можете ли вы пролить свет на направления, чтобы убедиться, что видеопоток будет автоматически масштабироваться/центрироваться на любой ориентации/разрешении устройства.

Код игрока, который я использую, выглядит следующим образом:

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();
    }
}
person iOS-Coder    schedule 31.10.2018
comment
Нашел решение, изменив в методе updateVideoSurfaces() следующее: int sw = mVideoSurface.getWidth(); int sh = mVideoSurface.getHeight(); - person iOS-Coder; 31.10.2018