Android SurfaceView 对视频进行捏合/缩放/缩放/背景颜色操作

问题描述 投票:0回答:1

我正在开发一项功能,需要对图库中选定的视频执行一些操作,然后将其上传到后端服务器,我提到了一些我现在遇到的视频编辑功能:

  1. 我必须使用 SurfaceView,因为在项目中,第三方 SDK 需要它,该 SDK 需要 SurfaceView 的实例并处理视频/图像上的一些编辑。
  2. 我可以捏它来放大/缩小,在整个屏幕上移动视频,并将其放置在任何地方,就像Instagram应用程序一样。
  3. 我可以应用背景颜色,该颜色将应用于视频帧外部的整个空间。
  4. 完成上述所有更改后,我想要一个输出视频,其中包含我上面提到的所有更改。

我已经尝试了一些使用TextureView的解决方案并且它正在处理它,但没有使用SurfaceView。

下面是使用TextureView的解决方案:

class VideoEditActivity : ComponentActivity() {
    private lateinit var textureView: TextureView
    private lateinit var mediaPlayer: MediaPlayer
    private lateinit var videoContainer: FrameLayout
    private var scaleGestureDetector: ScaleGestureDetector? = null
    private var gestureDetector: GestureDetector? = null
    private var scaleFactor = 1.0f
    private var translationX = 0f
    private var translationY = 0f
    private var rotationDegrees = 0f
    private var initialRotationDegrees = 0f
    private lateinit var selectedFilePath: String

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

        textureView = findViewById(R.id.textureView)
        videoContainer = findViewById(R.id.videoContainer)
        videoContainer.setBackgroundResource(R.color.purple_200)
        selectedFilePath = getPathFromUri(Uri.parse(intent.getStringExtra(INTENT_DATA)))
        Logger.log("Selected file path: $selectedFilePath")
        findViewById<ImageView>(R.id.ivVideoPlay).setOnClickListener {
//            captureAndCreateVideo()
        }
        setupMediaPlayer()
        setupGestures()
    }

    private fun setupMediaPlayer() {
        textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
                val selectedMediaUri = Uri.parse(intent.getStringExtra(INTENT_DATA))
                selectedMediaUri?.let {
                    mediaPlayer = MediaPlayer.create(this@VideoEditActivity, it) // Adjust the source accordingly
                }
                mediaPlayer.setSurface(Surface(surface))
                mediaPlayer.isLooping = true
                mediaPlayer.start()
            }

            override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                mediaPlayer.release()
                return true
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
        }
    }

    private fun setupGestures() {
        scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScale(detector: ScaleGestureDetector): Boolean {
                scaleFactor *= detector.scaleFactor
                scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
                textureView.scaleX = scaleFactor
                textureView.scaleY = scaleFactor
                return true
            }
        })

        gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
                translationX -= distanceX
                translationY -= distanceY
                textureView.translationX = translationX
                textureView.translationY = translationY
                return true
            }
        })

        videoContainer.setOnTouchListener { _, event ->
            if (event.pointerCount == 2) {
                if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
                    initialRotationDegrees = rotation(event)
                } else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
                    val currentRotationDegrees = rotation(event)
                    val deltaDegrees = initialRotationDegrees - currentRotationDegrees
                    rotationDegrees -= deltaDegrees
                    textureView.rotation = rotationDegrees
                    initialRotationDegrees = currentRotationDegrees
                }
            }
            scaleGestureDetector?.onTouchEvent(event)
            gestureDetector?.onTouchEvent(event)
            true
        }
    }

    private fun rotation(event: MotionEvent): Float {
        val dx = (event.getX(0) - event.getX(1)).toDouble()
        val dy = (event.getY(0) - event.getY(1)).toDouble()
        return Math.toDegrees(atan2(dy, dx)).toFloat()
    }


    private fun getPathFromUri(uri: Uri): String {
        var filePath: String? = null
        val projection = arrayOf(MediaStore.Video.Media.DATA)
        val cursor = contentResolver.query(uri, projection, null, null, null)
        if (cursor != null && cursor.moveToFirst()) {
            val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
            filePath = cursor.getString(columnIndex)
            cursor.close()
        }
        return filePath ?: ""
    }
}

XML 活动_视频_编辑文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/ivSelectedImage"
        android:src="@drawable/app_logo"
        android:layout_centerInParent="true"
        android:foregroundGravity="center"
        android:visibility="gone"
        android:contentDescription="selected_image" />


    <FrameLayout
        android:id="@+id/videoContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"> <!-- Background color -->

        <TextureView
            android:id="@+id/textureView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/play_icon"
        android:layout_centerInParent="true"
        android:visibility="gone"
        android:id="@+id/ivVideoPlay"/>

</RelativeLayout>

预期结果是:https://drive.google.com/file/d/1459VgzTby7P6prwDGkz2aHClhc2VW206/view?usp=sharing

更新:

我可以使用以下解决方案实现捏合/放大/缩小,但旋转不起作用,正如我在预期结果视频中显示的那样(只有容器旋转,而不是 SurfaceView 中的内容):

    private fun setupSurfaceGestures() {
    scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            scaleFactor *= detector.scaleFactor
            scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
            // Update the SurfaceView scale here if possible or handle via custom drawing
            surfaceView.scaleX = scaleFactor
            surfaceView.scaleY = scaleFactor
            return true
        }
    })

    gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
        override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
            translationX -= distanceX
            translationY -= distanceY
            // Update the SurfaceView translation here if possible or handle via custom drawing
            surfaceView.translationX = translationX
            surfaceView.translationY = translationY
            return true
        }
    })

    videoContainer.setOnTouchListener { _, event ->
        if (event.pointerCount == 2) {
            if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
                initialRotationDegrees = rotation(event)
            } else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
                val currentRotationDegrees = rotation(event)
                val deltaDegrees = initialRotationDegrees - currentRotationDegrees
                rotationDegrees -= deltaDegrees
                surfaceView.rotation = rotationDegrees
                initialRotationDegrees = currentRotationDegrees
            }
        }
        scaleGestureDetector?.onTouchEvent(event)
        gestureDetector?.onTouchEvent(event)
        true
    }

更改后输出视频:https://drive.google.com/file/d/1hHAfWf_NnplzrRdHYKgjXk-tf8etZRmJ/view?usp=sharing

android instagram surfaceview android-ffmpeg video-editing
1个回答
0
投票

SurfaceView
与其他视图的不同之处在于,它不会在其
Canvas
上绘制任何内容,而是“在其窗口上打一个洞以允许显示其表面”。尽管它允许您对
Surface
应用某些变换(例如更改位置和比例),但它不支持更改旋转。

另一方面,

TextureView
的行为与常规
View
一样。所以你可以自由改变它的旋转和alpha。

但是,如果您必须使用

SurfaceView
,则任何自定义转换都必须手动完成。为了提高效率,您可能希望利用硬件加速。请注意,这需要编写大量代码。

这是一种可能的工作流程:

MediaCodec
解码视频帧;
SurfaceTexture
使它们可以作为外部纹理供 GPU 使用; OpenGL ES 在应用变换矩阵的同时渲染纹理矩形;
Surface
SurfaceView
用作EGL窗口表面。并且不要忘记根据视频帧速率来控制播放速度。

© www.soinside.com 2019 - 2024. All rights reserved.