Android ExoPlayer:播放单个流到多个曲面

时间:2016-03-29 03:40:04

标签: android surfaceview mediacodec exoplayer

我正在调查如何让Android Exoplayer向多个曲面播放单个内容流。例如,如果内容流在线,则只下载一次,但仍然可以在两个表面上播放。

我调查了这个话题,并且可以分享到目前为止我学到的东西。通常,没有必要实际使用多个表面,因为OpenGL着色器可用于制作“分屏效果”,其中单个表面看起来可播放多个视频。我实际上已经在两个表面上使用OpenGL着色器(它们在GLSurfaceViews中),但使用单个表面似乎不是一个选项,因为其中一个表面使用{{强制低分辨率1}}而另一个不是。

在更低级别的方法中,我调查了是否可以为.setFixedSize() MediaCodec类中包含的DemoPlayer设置多个曲面。但是,似乎MediaCodecVideoTrackRenderer被设计为仅配置一个表面,因此这种方法似乎没有效果。

我考虑的另一种方法是以某种方式与其他玩家共享一个玩家MediaCodec的输出缓冲区,但我不确定这是否切实可行。

如何在多个表面上播放单个流的任何想法或指导将非常感激。谢谢。

2 个答案:

答案 0 :(得分:1)

这里的答案帮助我研究了这个主题,所以这里有一个完整的示例,其中包含一个 PlayerView 和一个 SurfaceView,视频也在其中播放。基于来自 grafika 的 ContinuousCaptureActivity 示例。

此处的 WindowSurfaces(mainDisplaySurfacesecondaryDisplaySurface)共享相同的 egl 上下文(eglCore)。当一个框架可用时,我们通过在它们之间切换来绘制框架。

示例使用 gles 文件夹中的类

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/playerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="160dp"
        android:layout_height="90dp"
        app:layout_constraintTop_toBottomOf="@id/playerView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {

    private lateinit var playerView: PlayerView
    private lateinit var surfaceView: SurfaceView

    private lateinit var player: SimpleExoPlayer

    private var eglCore: EglCore? = null
    private var fullFrameBlit: FullFrameRect? = null
    private var textureId: Int = 0
    private var videoSurfaceTexture: SurfaceTexture? = null
    private val transformMatrix = FloatArray(16)

    private var mainDisplaySurface: WindowSurface? = null
    private var secondaryDisplaySurface: WindowSurface? = null

    private var surface: Surface? = null

    private val surfaceViewHolderCallback = object : SurfaceHolder.Callback {
        override fun surfaceCreated(holder: SurfaceHolder) {
            secondaryDisplaySurface = WindowSurface(eglCore, holder.surface, false)
        }

        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
        override fun surfaceDestroyed(holder: SurfaceHolder) {}
    }

    private val playerViewHolderCallback = object : SurfaceHolder.Callback {
        override fun surfaceCreated(holder: SurfaceHolder) {
            eglCore = EglCore()

            mainDisplaySurface = WindowSurface(eglCore, holder.surface, false).apply {
                makeCurrent()
            }
            fullFrameBlit = FullFrameRect(Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT))
            textureId = fullFrameBlit!!.createTextureObject()
            videoSurfaceTexture = SurfaceTexture(textureId).also {
                it.setOnFrameAvailableListener(this@MainActivity)
            }

            surface = Surface(videoSurfaceTexture)

            player.setVideoSurface(surface)
        }

        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
        override fun surfaceDestroyed(holder: SurfaceHolder) {}
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        playerView = findViewById(R.id.playerView)
        surfaceView = findViewById(R.id.surfaceView)

        player = SimpleExoPlayer.Builder(this).build().apply {
            setMediaItem(MediaItem.fromUri("file:///android_asset/video.mp4"))
            playWhenReady = true
        }

        playerView.player = player

        (playerView.videoSurfaceView as SurfaceView).holder.addCallback(playerViewHolderCallback)
        surfaceView.holder.addCallback(surfaceViewHolderCallback)

        player.prepare()
    }

    override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
        if (eglCore == null) return

        // PlayerView
        mainDisplaySurface?.let {
            drawFrame(it, playerView.width, playerView.height)
        }

        // SurfaceView
        secondaryDisplaySurface?.let {
            drawFrame(it, surfaceView.width, surfaceView.height)
        }
    }

    private fun drawFrame(windowSurface: WindowSurface, viewWidth: Int, viewHeight: Int) {
        windowSurface.makeCurrent()

        videoSurfaceTexture!!.apply {
            updateTexImage()
            getTransformMatrix(transformMatrix)
        }

        GLES20.glViewport(0, 0, viewWidth, viewHeight)

        fullFrameBlit!!.drawFrame(textureId, transformMatrix)

        windowSurface.swapBuffers()
    }

    override fun onPause() {
        surface?.release()
        surface = null

        videoSurfaceTexture?.release()
        videoSurfaceTexture = null

        mainDisplaySurface?.release()
        mainDisplaySurface = null

        secondaryDisplaySurface?.release()
        secondaryDisplaySurface = null

        fullFrameBlit?.release(false)
        fullFrameBlit = null

        eglCore?.release()
        eglCore = null

        super.onPause()
    }
}

答案 1 :(得分:0)

这也是我想要做的事情,但我找到了几个问题区域。

1。传递给MediaCodecVideoTrackRenderer的Surface只需传递给MediaCodec进行&#34;配置&#34;。
将Surface传递给视频渲染器时会发生什么情况的股票Android代码如下:

protected void configureCodec(MediaCodec codec, boolean codecIsAdaptive, android.media.MediaFormat format, MediaCrypto crypto) 
{
    maybeSetMaxInputSize(format, codecIsAdaptive);
    codec.configure(format, surface, crypto, 0); //<-- Surface use
    codec.setVideoScalingMode(videoScalingMode);
}

它用于其他几项检查,以查看编解码器是否应该初始化,但在实际的TrackRenderer中没有更多。
所以那里做的不多。

2。 MediaCodec如何处理输出表面。
通过MediaCodec文档阅读,我发现了几个问题&#34; sharing&#34;输出缓冲区。根据MediaCodec(Documentation)的文档:

  

使用输出表面
  使用输出Surface时,数据处理几乎与ByteBuffer模式相同; 但是,输出缓冲区将无法访问,并表示为空值。例如。 getOutputBuffer / Image(int)将返回null,getOutputBuffers()将返回仅包含null-s的数组。

这意味着如果使用单个Surface作为输出,则没有其他函数可以访问输出缓冲区。


可能的解决方案
最简单的&#39;对此的解决方案可能是解码&#34;视频渲染器和单个ExoPlayer可以控制不同曲面的播放。(不要担心,听起来并不那么疯狂。)

第1步:创建一个仅输出(视频)缓冲区的TrackRenderer。
第2步:让TrackRenderer完成检索和解码视频输入的所有工作,然后将其发送到ByteBuffers,然后可以从TrackRenderer中检索。ByteBuffers。 (连续地或在完全检索和解码输入之后。)
第3步:根据需要制作ByteBuffers的副本(两个表面为2个,三个表面为3个等)。
第4步:将每个单独的uses native video buffers送入所需的曲面(作为曲面ByteBuffers)。

我仍然不确定ExoPlayer可以用来控制所有不同曲面的播放方式,但一个想法可能是“控制”#34;从解码的$meta = array(); while($res = mysqli_fetch_assoc($query)) { // $meta[] = $res; $meta[] = array( 'guid' => $res['guid'], 'name' => $res['name'], 'dob' => $res['date_of_birth'], 'birthplace' => $res['birthplace'], 'height' => $res['height'], 'weight' => $res['weight'], 'position' => $res['position'], 'honours' => $res['honours'] ); } $meta = json_encode(array('players' => $meta), JSON_PRETTY_PRINT); echo $meta 传递给Surfaces的字节。