Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import com.ai.assistance.operit.core.tools.ToolExecutor
import com.ai.assistance.operit.data.model.AITool
import com.ai.assistance.operit.data.model.ToolResult
import com.ai.assistance.operit.data.model.ToolValidationResult
import com.arthenica.ffmpegkit.FFmpegKit
import com.ai.assistance.operit.util.FFmpegUtil
import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.ReturnCode
import java.io.File

Expand All @@ -35,7 +34,7 @@ class StandardFFmpegToolExecutor(private val context: Context) : ToolExecutor {
val startTime = System.currentTimeMillis()

// 执行FFmpeg命令
val session = FFmpegKit.execute(command)
val session = FFmpegUtil.execute(command)
val returnCode = session.returnCode
val output = session.output ?: ""
val duration = System.currentTimeMillis() - startTime
Expand Down Expand Up @@ -104,7 +103,7 @@ class StandardFFmpegInfoToolExecutor : ToolExecutor {
info.appendLine("Build configuration: ${FFmpegKitConfig.getBuildDate()}")

// 列出支持的编解码器
val codecsSession = FFmpegKit.execute("-codecs")
val codecsSession = FFmpegUtil.execute("-codecs")
val codecsOutput = codecsSession.output ?: ""
val duration = System.currentTimeMillis() - startTime

Expand Down Expand Up @@ -201,14 +200,14 @@ class StandardFFmpegConvertToolExecutor(private val context: Context) : ToolExec
val startTime = System.currentTimeMillis()

// 执行FFmpeg命令
val session = FFmpegKit.execute(command)
val session = FFmpegUtil.execute(command)
val returnCode = session.returnCode
val output = session.output ?: ""
val duration = System.currentTimeMillis() - startTime

if (ReturnCode.isSuccess(returnCode)) {
// 获取输出文件的媒体信息
val mediaSession = FFprobeKit.getMediaInformation(outputPath)
val mediaSession = FFmpegUtil.getMediaInformation(outputPath)
val mediaInfo = mediaSession?.mediaInformation

val ffmpegResult =
Expand Down Expand Up @@ -250,9 +249,7 @@ class StandardFFmpegConvertToolExecutor(private val context: Context) : ToolExec
}
.toMutableList()

// Get additional media information using FFprobe
val ffprobeSession = FFprobeKit.getMediaInformation(outputPath)
val ffprobeInfo = ffprobeSession?.mediaInformation
val ffprobeInfo = mediaInfo

if (ffprobeInfo != null) {
// Update stream information with FFprobe data
Expand Down
29 changes: 18 additions & 11 deletions app/src/main/java/com/ai/assistance/operit/util/FFmpegUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,40 @@ package com.ai.assistance.operit.util

import com.ai.assistance.operit.util.AppLogger
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFmpegSession
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.MediaInformation
import com.arthenica.ffmpegkit.MediaInformationSession
import com.arthenica.ffmpegkit.ReturnCode

/**
* Utility class for FFmpeg operations
* Utility class for FFmpeg operations.
*
* FFmpegKit's bundled libavcodec is not safe for overlapping sessions. Concurrent execute/probe
* calls can destroy the same pthread mutex twice and abort the process on Android 17 FORTIFY.
*/
object FFmpegUtil {
private const val TAG = "FFmpegUtil"
private val nativeSessionLock = Any()

/**
* Build a scale filter string that survives FFmpegKit argument parsing.
* FFmpeg expressions need an escaped comma when passed without a shell.
*/
fun scaleFilterMaxWidth(maxWidth: Int): String = "scale=min(${maxWidth}\\,iw):-2"

/**
* Execute an FFmpeg command and return if it was successful
*/
fun <T> withNativeSession(block: () -> T): T = synchronized(nativeSessionLock) { block() }

fun execute(command: String): FFmpegSession =
withNativeSession { FFmpegKit.execute(command) }

fun getMediaInformation(filePath: String): MediaInformationSession =
withNativeSession { FFprobeKit.getMediaInformation(filePath) }

fun executeCommand(command: String): Boolean {
try {
AppLogger.d(TAG, "Executing FFmpeg command: $command")
val session = FFmpegKit.execute(command)
val session = execute(command)
val returnCode = session.returnCode

if (ReturnCode.isSuccess(returnCode)) {
Expand All @@ -43,16 +54,12 @@ object FFmpegUtil {
}
}

/**
* Get media information for a file
*/
fun getMediaInfo(filePath: String): MediaInformation? {
return try {
val mediaInfoSession = FFprobeKit.getMediaInformation(filePath)
mediaInfoSession.mediaInformation
getMediaInformation(filePath).mediaInformation
} catch (e: Exception) {
AppLogger.e(TAG, "Error getting media info: ${e.message}")
null
}
}
}
}
11 changes: 11 additions & 0 deletions app/src/test/java/com/ai/assistance/operit/util/FFmpegUtilTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ai.assistance.operit.util

import org.junit.Assert.assertEquals
import org.junit.Test

class FFmpegUtilTest {
@Test
fun scaleFilterEscapesCommaForFfmpegKit() {
assertEquals("scale=min(640\\,iw):-2", FFmpegUtil.scaleFilterMaxWidth(640))
}
}