package com.anyun.h264
|
|
import android.os.Bundle
|
import android.os.Handler
|
import android.os.Looper
|
import android.util.Log
|
import androidx.activity.ComponentActivity
|
import androidx.activity.compose.setContent
|
import androidx.activity.enableEdgeToEdge
|
import androidx.compose.foundation.layout.*
|
import androidx.compose.material3.*
|
import androidx.compose.runtime.*
|
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.unit.dp
|
import com.anyun.h264.H264FileTransmitter.OnTransmitProgressCallback
|
import com.anyun.h264.ui.theme.MyApplicationTheme
|
import timber.log.Timber
|
import java.io.File
|
|
class MainActivity : ComponentActivity() {
|
private var h264Encoder: H264Encoder? = null
|
private var transmitter: H264FileTransmitter? = null
|
private var fileList: List<File> = emptyList()
|
private var currentFileIndex: Int = 0
|
private val handler = Handler(Looper.getMainLooper())
|
companion object{
|
const val TAG ="MainActivity"
|
}
|
override fun onCreate(savedInstanceState: Bundle?) {
|
super.onCreate(savedInstanceState)
|
enableEdgeToEdge()
|
|
setContent {
|
var isRunning by remember { mutableStateOf(false) }
|
|
MyApplicationTheme {
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
MainScreen(
|
modifier = Modifier.padding(innerPadding),
|
isRunning = isRunning,
|
onStartH264Click = {
|
val success = startFileTransmitter()
|
if (success) {
|
isRunning = true
|
}
|
},
|
onStopH264Click = {
|
// stopH264Encoder()
|
stopFileTransmitter()
|
isRunning = false
|
}
|
)
|
}
|
}
|
}
|
}
|
|
override fun onDestroy() {
|
super.onDestroy()
|
stopH264Encoder()
|
stopFileTransmitter()
|
}
|
|
private fun startFileTransmitter():Boolean {
|
if (transmitter != null) {
|
Timber.w("FileTransmitter is already running")
|
return false
|
}
|
|
// 获取目录中的所有 .h264 文件
|
val directory = application.applicationContext.getExternalFilesDir(null)
|
Timber.i("视频目录=${directory?.absolutePath}")
|
|
if (directory?.isDirectory != true) {
|
Timber.e("Directory is not valid: ${directory?.absolutePath}")
|
return false
|
}
|
|
// 获取所有 .h264 文件并按文件名排序
|
fileList = directory.listFiles()
|
?.filter { it.isFile && it.name.endsWith(".h264", ignoreCase = true) }
|
?.sortedBy { it.name }
|
?: emptyList()
|
|
if (fileList.isEmpty()) {
|
Timber.w("No .h264 files found in directory")
|
return false
|
}
|
|
Timber.i("Found ${fileList.size} .h264 files to transmit")
|
currentFileIndex = 0
|
|
try {
|
transmitter = H264FileTransmitter()
|
Log.i(TAG,"startFileTransmitter")
|
transmitter?.setServerAddress("192.168.16.138", 1078)
|
transmitter?.setProtocolType(JT1076ProtocolHelper.PROTOCOL_TYPE_TCP) // 或 PROTOCOL_TYPE_UDP
|
|
// 设置协议参数
|
transmitter?.setProtocolParams("013120122580", 1.toByte())
|
|
// 设置帧率(用于计算时间戳间隔)
|
transmitter?.setFrameRate(25)
|
transmitter?.setOnTransmitProgressCallback(object : OnTransmitProgressCallback {
|
override fun onProgress(currentFrame: Int, totalFrames: Int) {
|
val currentFile = if (currentFileIndex < fileList.size) fileList[currentFileIndex] else null
|
Timber.d("Transmitting file ${currentFileIndex + 1}/${fileList.size}: ${currentFile?.name}, frame: $currentFrame")
|
}
|
|
override fun onComplete() {
|
val currentFile = if (currentFileIndex < fileList.size) fileList[currentFileIndex] else null
|
Timber.i("File transmission complete: ${currentFile?.name} (${currentFileIndex + 1}/${fileList.size})")
|
|
// 使用 Handler 延迟调用,确保前一个文件的传输状态已重置
|
currentFileIndex++
|
handler.postDelayed({
|
transmitNextFile()
|
}, 100) // 延迟100ms,确保前一个文件的 finally 块已执行
|
}
|
|
override fun onError(error: String?) {
|
val currentFile = if (currentFileIndex < fileList.size) fileList[currentFileIndex] else null
|
Timber.e("File transmission error: ${currentFile?.name}, error: $error")
|
|
// 即使出错也继续传输下一个文件
|
// 使用 Handler 延迟调用,确保前一个文件的传输状态已重置
|
currentFileIndex++
|
handler.postDelayed({
|
transmitNextFile()
|
}, 100) // 延迟100ms,确保前一个文件的 finally 块已执行
|
}
|
})
|
|
// 初始化Socket
|
if (transmitter?.initialize() == true) {
|
// 开始传输第一个文件
|
transmitNextFile()
|
return true
|
} else {
|
Timber.e("Failed to initialize transmitter")
|
transmitter = null
|
return false
|
}
|
} catch (e: Exception) {
|
Timber.e(e, "Failed to start FileTransmitter")
|
transmitter = null
|
return false
|
}
|
}
|
|
/**
|
* 传输下一个文件
|
*/
|
private fun transmitNextFile() {
|
if (transmitter == null) {
|
Timber.w("Transmitter is null, cannot transmit next file")
|
return
|
}
|
|
if (currentFileIndex >= fileList.size) {
|
Timber.i("All files transmission complete! Total: ${fileList.size} files")
|
// 所有文件传输完成,停止传输器
|
stopFileTransmitter()
|
return
|
}
|
|
val nextFile = fileList[currentFileIndex]
|
Timber.i("Starting transmission of file ${currentFileIndex + 1}/${fileList.size}: ${nextFile.name}")
|
|
// 传输下一个文件
|
transmitter?.transmitFile(nextFile.absolutePath)
|
}
|
|
/**
|
* 停止文件传输器
|
*/
|
private fun stopFileTransmitter() {
|
// 移除所有待处理的 Handler 任务
|
handler.removeCallbacksAndMessages(null)
|
|
transmitter?.let { tx ->
|
try {
|
tx.stop()
|
Timber.d("FileTransmitter stopped")
|
} catch (e: Exception) {
|
Timber.e(e, "Failed to stop FileTransmitter")
|
}
|
}
|
transmitter = null
|
fileList = emptyList()
|
currentFileIndex = 0
|
}
|
|
private fun startH264Encoder(): Boolean {
|
if (h264Encoder != null) {
|
Timber.w("H264Encoder is already running")
|
return false
|
}
|
|
try {
|
// 创建编码器
|
h264Encoder = H264Encoder()
|
|
// 设置编码参数
|
h264Encoder?.setEncoderParams(640, 480, 25, 2000000)
|
|
// 设置输出文件(可选)
|
val outputFile = File(getExternalFilesDir(null), "test2.h264")
|
h264Encoder?.setOutputFile(outputFile.absolutePath)
|
h264Encoder?.setEnableFileOutput(true) // 启用文件输出
|
|
// 设置UDP服务器地址(可选)
|
// h264Encoder?.setServerAddress("58.48.93.67", 11935)
|
h264Encoder?.setEnableNetworkTransmission(false)
|
h264Encoder?.setServerAddress("192.168.16.138", 1078)
|
h264Encoder?.setProtocolParams("013120122580", 1)
|
|
// 初始化并启动
|
val cameraIdRange = intArrayOf(1, 2)
|
val resolution = intArrayOf(640, 480)
|
|
if (h264Encoder?.initialize(cameraIdRange, null, resolution, false) == true) {
|
h264Encoder?.start()
|
Timber.d("H264Encoder started successfully")
|
Timber.d("Output file: %s", outputFile.absolutePath)
|
return true
|
} else {
|
Timber.e("Failed to initialize H264Encoder")
|
h264Encoder = null
|
return false
|
}
|
} catch (e: Exception) {
|
Timber.e(e, "Failed to start H264Encoder")
|
h264Encoder = null
|
return false
|
}
|
}
|
|
private fun stopH264Encoder() {
|
h264Encoder?.let { encoder ->
|
try {
|
encoder.stop()
|
Timber.d("H264Encoder stopped")
|
} catch (e: Exception) {
|
Timber.e(e, "Failed to stop H264Encoder")
|
}
|
h264Encoder = null
|
}
|
}
|
}
|
|
@Composable
|
fun MainScreen(
|
modifier: Modifier = Modifier,
|
isRunning: Boolean,
|
onStartH264Click: () -> Unit,
|
onStopH264Click: () -> Unit
|
) {
|
Column(
|
modifier = modifier
|
.fillMaxSize()
|
.padding(16.dp),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
verticalArrangement = Arrangement.Center
|
) {
|
Text(
|
text = "H264 编码器",
|
style = MaterialTheme.typography.headlineMedium
|
)
|
|
Spacer(modifier = Modifier.height(32.dp))
|
|
Button(
|
onClick = onStartH264Click,
|
enabled = !isRunning,
|
modifier = Modifier
|
.fillMaxWidth()
|
.height(56.dp)
|
) {
|
Text("启动 H264")
|
}
|
|
Spacer(modifier = Modifier.height(16.dp))
|
|
Button(
|
onClick = onStopH264Click,
|
enabled = isRunning,
|
modifier = Modifier
|
.fillMaxWidth()
|
.height(56.dp),
|
colors = ButtonDefaults.buttonColors(
|
containerColor = MaterialTheme.colorScheme.error
|
)
|
) {
|
Text("停止 H264")
|
}
|
|
Spacer(modifier = Modifier.height(32.dp))
|
|
if (isRunning) {
|
Text(
|
text = "编码器运行中...",
|
style = MaterialTheme.typography.bodyMedium,
|
color = MaterialTheme.colorScheme.primary
|
)
|
}
|
}
|
}
|