Thay Đổi Độ Phân Giải Và Kích Thước Video Bằng Thư Viện FFmpeg Trên Android

Trong bài viết này, mình sẽ cùng các bạn tạo một ứng dụng nho nhỏ. Trong ứng dụng này chúng ta sẽ sử dụng một thư viện đặc biệt nó có tên gọi là FFmpeg. Đây là một thư viện được viết dưới tầng C++. Đây là một thư viện ma thuật được rất nhiều các developer trên thế giới sử dụng để xử lý các chức năng liên quan đến Video ví dụ như giảm độ phân giải của video, giảm kích thước của video, lấy thumbnail của video, cắt ghép video ...... Trong ứng dụng chúng ta sắp thực hiện, chúng ta sẽ sử dụng FFmpeg để thay đổi độ phân giải của video. Đầu tiên chúng ta sẽ tạo giao diện của ứng dụng như bên dưới:

Giao diện gồm một VideoView và 2 button dùng để select video và play video sau khi download, và một ProgressBar để cho người dùng biết đang xử lý video. bên dưới là file xml của chúng ta: activitymain.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="Click Me!"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />

    <Button
        android:id="@+id/playbutton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="Play!"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />


    <VideoView
        android:id="@+id/videoView"
        android:layout_width="0dp"
        android:layout_height="300dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/progressbar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Tiếp theo cũng ta sẽ tíck hợp thư viên FFmpeg vào trong project của chúng ta. Như đã nói ở trên FFmpeg là 1 thư viện được viết ở dưới tầng C++ nhưng thật may mắn, thư việc này đã được một số developer đóng gói lại thành một library cho dễ dàng sử dụng hơn. Trong file build.gradle level của app bạn add một dòng bên dưới:

 implementation 'nl.bravobit:android-ffmpeg:1.1.5'

Sau đó Sync lại project của chúng ta. Để có thể select được video trong app trước tiên các bạn phải xin quyền( permission) để có thể truy cập vào trong file video trên device Trong file *AndroidMainifest.xml * bạn add thêm dòng bên dưới:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Ở đây tôi chỉ thêm quyền WRITE bởi vì trong quyền write đã có quyền Read rồi. Nhưng làm như vậy là không đủ. ở đây tôi xử dụng thêm một thư viện ngoài cho mục đích xin quyền. nếu không các bạn có thể tự viết bằng cách tham khảo tài liệu tại đây Vì tôi sử dụng thư viện lên tôi phải thêm nó nó vào trong file *build.gradle *

   implementation "com.github.hotchemi:permissionsdispatcher:3.3.1"
    kapt "com.github.hotchemi:permissionsdispatcher-processor:3.3.1"

Để kiểm tra xem FFmpeg có sẵn sàng trên device của bạn hay không, chúng ta có thể sử dụng method bên dưới:

if (FFmpeg.getInstance(this).isSupported()) {
  // ffmpeg is supported
} else {
  // ffmpeg is not supported
}

Ở đây chúng ta sẽ sử dụng command line như bên dưới

       ffMpeg.execute(compressVideo, object : FFcommandExecuteResponseHandler {
                    override fun onFinish() {
                    
                    }

                    override fun onSuccess(message: String?) {
                       
                    }

                    override fun onFailure(message: String?) {
                       
                    }

                    override fun onProgress(message: String?) {
                        
                    }

                    override fun onStart() {

                    }

                })

Ở đây chúng ta thay đổi độ phân giải cuả file video và làm giảm kích thước của nó nên command line sẽ gồm những tham số như bên dưới:

val compressVideo = arrayOf("-y", "-i", "${originalFile?.absolutePath}", "-vf", "scale=-2:540:","-preset","veryfast", "-vcodec", "libx264", "-b:a"," 128k", "-crf","18","${destinationFile?.absolutePath}")

Bên dưới là code trong file MainActivity.kt. Ngoài ra các bạn có thể download toàn bộ source code tại cuối bài viết.

@RuntimePermissions
class MainActivity : AppCompatActivity() {

    companion object{
        const val  TAG ="ChangeResolution"
    }
    val SELECT_FILES_REQUEST = 1
    var destinationFile: File? = null

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

        playbutton.setOnClickListener {
            val mediaController = MediaController(this)
            val videoView = findViewById<VideoView>(R.id.videoView)
            mediaController.setAnchorView(videoView)

            val uri = Uri.fromFile(destinationFile)
            videoView.setMediaController(mediaController)
            videoView.setVideoURI(uri)
            videoView.requestFocus()
            videoView.start()

        }
    }

    @NeedsPermission(READ_EXTERNAL_STORAGE)
    fun selectFileUpload() {
        val intent: Intent = Intent().apply {
            type = FileUtils.MIME_TYPE_ALL
            action = Intent.ACTION_GET_CONTENT
        }
        startActivityForResult(
                Intent.createChooser(intent, getString(R.string.prompt_select_file)),
                SELECT_FILES_REQUEST
        )
    }

    @OnPermissionDenied(READ_EXTERNAL_STORAGE)
    fun showDeniedForReadExternalStorage() {
        showToast(this.getString(R.string.permission_read_external_storage_denied))
    }

    @OnShowRationale(READ_EXTERNAL_STORAGE)
    fun showRationaleForReadExternalStorage(request: PermissionRequest) {
        AlertDialog.Builder(this)
                .setPositiveButton(android.R.string.ok) { _, _ -> request.proceed() }
                .setNegativeButton(android.R.string.cancel) { _, _ -> request.cancel() }
                .setCancelable(false)
                .setMessage(R.string.permission_read_external_rationale)
                .show()
    }

    @OnNeverAskAgain(READ_EXTERNAL_STORAGE)
    fun showNeverAskForReadExternalStorage() {
        showToast(this.getString(R.string.permission_read_external_storage_never_ask_again))
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        onRequestPermissionsResult(requestCode, grantResults)
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (resultCode == RESULT_OK && requestCode == SELECT_FILES_REQUEST) {
            data?.let {
                uploadFilesWithData(it)
            }
        }
    }

    fun uploadFilesWithData(intent: Intent) {
        showProgress()
        val data = intent.data

        val originalFile = FileUtils.getFile(this, data)
        destinationFile = FileUtils.createTemporaryVideoFile(this)

        val ffMpeg = FFmpeg.getInstance(this)
            if (ffMpeg.isSupported) {
                val compressVideo = arrayOf("-y", "-i", "${originalFile?.absolutePath}", "-vf", "scale=-2:540:","-preset","veryfast", "-vcodec", "libx264", "-b:a"," 128k", "-crf","18","${destinationFile?.absolutePath}")
                ffMpeg.execute(compressVideo, object : FFcommandExecuteResponseHandler {
                    override fun onFinish() {
                        val fileSize = FileUtils.getFileSize(destinationFile)
                        Log.d(TAG, "onFinish: file size: ${FileUtils.convertFileSizeToString(fileSize)}")
                        hideProgress()
                    }

                    override fun onSuccess(message: String?) {
                        val fileSize = FileUtils.getFileSize(destinationFile)
                        Log.d(TAG, "onSuccess: $message $fileSize")
                        Toast.makeText([email protected], "file size: ${FileUtils.getFileSize(destinationFile)}", Toast.LENGTH_LONG).show()
                    }

                    override fun onFailure(message: String?) {
                        Log.d(TAG, "onFailure: $message")
                    }

                    override fun onProgress(message: String?) {
                        Log.d(TAG, "onFailure: $message")
                    }

                    override fun onStart() {

                    }

                })
            }
    }

    private fun showProgress() {
        progressbar.visibility = View.VISIBLE
    }

    private fun hideProgress() {
        progressbar.visibility = View.GONE
    }
}

Ngoài ra còn rất nhiều command line rất hữu ích mà các bạn có thể thử như:

Nén một video

String[] command = {"-y", "-i", inputFileAbsolutePath, "-s", "160x120", "-r", "25", "-vcodec", "mpeg4", "-b:v", "150k", "-b:a", "48000", "-ac", "2", "-ar", "22050", outputFileAbsolutePath}

Cắt một video

String[] complexCommand = {"-ss", "" + startMs / 1000, "-y", "-i", inputFileAbsolutePath, "-t", "" + (endMs - startMs) / 1000, "-s", "320x240", "-r", "15", "-vcodec", "mpeg4", "-b:v", "2097152", "-b:a", "48000", "-ac", "2", "-ar", "22050", outputFileAbsolutePath}

Lấy Image từ video

String[] complexCommand = {"-y", "-i", inputFileAbsolutePath, "-an", "-r", "1/2", "-ss", "" + startMs / 1000, "-t", "" + (endMs - startMs) / 1000, outputFileAbsolutePath}

Đây là một bài viết tóm tắt về việc sử dụng thư viện FFmpeg các bạn có thể download source code tại đây

Tài Liệu tham khảo:

https://androidadvanced.com/2017/03/17/ffmpeg-video-editor/ https://www.bugcodemaster.com/article/changing-resolution-video-using-ffmpeg