我正在调查如何让Android Exoplayer向多个曲面播放单个内容流。例如,如果内容流在线,则只下载一次,但仍然可以在两个表面上播放。
我调查了这个话题,并且可以分享到目前为止我学到的东西。通常,没有必要实际使用多个表面,因为OpenGL着色器可用于制作“分屏效果”,其中单个表面看起来可播放多个视频。我实际上已经在两个表面上使用OpenGL着色器(它们在GLSurfaceViews
中),但使用单个表面似乎不是一个选项,因为其中一个表面使用{{强制低分辨率1}}而另一个不是。
在更低级别的方法中,我调查了是否可以为.setFixedSize()
MediaCodec
类中包含的DemoPlayer
设置多个曲面。但是,似乎MediaCodecVideoTrackRenderer
被设计为仅配置一个表面,因此这种方法似乎没有效果。
我考虑的另一种方法是以某种方式与其他玩家共享一个玩家MediaCodec
的输出缓冲区,但我不确定这是否切实可行。
如何在多个表面上播放单个流的任何想法或指导将非常感激。谢谢。
答案 0 :(得分:1)
这里的答案帮助我研究了这个主题,所以这里有一个完整的示例,其中包含一个 PlayerView 和一个 SurfaceView,视频也在其中播放。基于来自 grafika 的 ContinuousCaptureActivity 示例。
此处的 WindowSurfaces(mainDisplaySurface
和 secondaryDisplaySurface
)共享相同的 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的字节。