| | |
| | | import android.media.MediaCodec; |
| | | import android.media.MediaCodecInfo; |
| | | import android.media.MediaFormat; |
| | | import android.media.MediaMetadataRetriever; |
| | | import android.media.MediaMuxer; |
| | | import android.media.MediaRecorder; |
| | | import android.text.TextUtils; |
| | |
| | | |
| | | // 录制开始时间(纳秒),用于时间戳同步 |
| | | private volatile long recordingStartTimeNs = 0; |
| | | |
| | | // 刚完成的文件(用于重命名) |
| | | private File completedVideoFile; |
| | | |
| | | /** |
| | | * 录像回调接口 |
| | |
| | | |
| | | stopRecordThread(); |
| | | releaseResources(); |
| | | |
| | | // 重命名刚完成的文件(停止录像时) |
| | | if (completedVideoFile != null) { |
| | | renameCompletedFile(completedVideoFile); |
| | | completedVideoFile = null; |
| | | } |
| | | |
| | | if (usbCamera != null) { |
| | | usbCamera.stopCamera(); |
| | | } |
| | |
| | | mediaMuxer = null; |
| | | } |
| | | |
| | | // 保存刚完成的文件路径,用于后续重命名 |
| | | if (currentVideoFile != null && currentVideoFile.exists()) { |
| | | completedVideoFile = currentVideoFile; |
| | | } |
| | | |
| | | muxerStarted = false; |
| | | videoTrackIndex = -1; |
| | | audioTrackIndex = -1; |
| | |
| | | // 释放当前资源 |
| | | releaseResources(); |
| | | |
| | | // 重命名刚完成的文件 |
| | | if (completedVideoFile != null) { |
| | | renameCompletedFile(completedVideoFile); |
| | | completedVideoFile = null; |
| | | } |
| | | |
| | | // 初始化新的编码器和Muxer |
| | | if (!initEncoderAndMuxer()) { |
| | | Timber.e("Failed to create new video file"); |
| | |
| | | } |
| | | |
| | | releaseResources(); |
| | | |
| | | // 重命名刚完成的文件(停止录像时) |
| | | if (completedVideoFile != null) { |
| | | renameCompletedFile(completedVideoFile); |
| | | completedVideoFile = null; |
| | | } |
| | | |
| | | Timber.d("RecordThread ended"); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 重命名完成的视频文件 |
| | | * 格式:HHmmss_学员名_1或2_时长秒.mp4 |
| | | * 例如:132541_学员C_1_58.mp4 |
| | | */ |
| | | private void renameCompletedFile(File originalFile) { |
| | | if (originalFile == null || !originalFile.exists()) { |
| | | Timber.w("原始文件不存在,无法重命名: %s", originalFile); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 1. 从原文件名提取时分秒(例如:132541_P1.mp4 -> 132541) |
| | | String originalName = originalFile.getName(); |
| | | String timePart = originalName; |
| | | // 移除 .mp4 扩展名 |
| | | if (originalName.endsWith(".mp4")) { |
| | | timePart = originalName.substring(0, originalName.length() - 4); |
| | | } |
| | | // 移除 _P1 或 _P2 后缀 |
| | | if (timePart.endsWith("_P1") || timePart.endsWith("_P2")) { |
| | | timePart = timePart.substring(0, timePart.length() - 3); |
| | | } |
| | | |
| | | // 2. 获取学员名字 |
| | | String studentName = GlobalData.getInstance().parseWaterMaskInfo("student", "无", GlobalData.ShareType.STRING); |
| | | if (TextUtils.isEmpty(studentName) || "无".equals(studentName)) { |
| | | studentName = "学员"; |
| | | } |
| | | |
| | | // 3. 获取 P1/P2(1 对应 P1,2 对应 P2) |
| | | int cameraId = (mediaArgu != null) ? mediaArgu.getUsbCameraId() : 1; |
| | | String cameraIdStr = String.valueOf(cameraId); |
| | | |
| | | // 4. 获取视频时长(秒) |
| | | int durationSeconds = getVideoDuration(originalFile); |
| | | |
| | | // 5. 构建新文件名:HHmmss_学员名_1或2_时长秒.mp4 |
| | | String newFileName = String.format("%s_%s_%s_%d.mp4", timePart, studentName, cameraIdStr, durationSeconds); |
| | | File newFile = new File(originalFile.getParent(), newFileName); |
| | | |
| | | // 6. 重命名文件 |
| | | if (originalFile.renameTo(newFile)) { |
| | | Timber.d("文件重命名成功: %s -> %s", originalFile.getName(), newFileName); |
| | | // 更新回调中的文件名 |
| | | |
| | | notifyCallback(2,0,newFileName); |
| | | } else { |
| | | Timber.e("文件重命名失败: %s -> %s", originalFile.getName(), newFileName); |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "重命名文件时发生异常: %s", originalFile.getName()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取视频文件时长(秒) |
| | | */ |
| | | private int getVideoDuration(File videoFile) { |
| | | MediaMetadataRetriever retriever = null; |
| | | try { |
| | | retriever = new MediaMetadataRetriever(); |
| | | retriever.setDataSource(videoFile.getAbsolutePath()); |
| | | String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); |
| | | if (durationStr != null && !durationStr.isEmpty()) { |
| | | long durationMs = Long.parseLong(durationStr); |
| | | int durationSeconds = (int) (durationMs / 1000); |
| | | Timber.d("视频时长: %d 秒", durationSeconds); |
| | | return durationSeconds; |
| | | } |
| | | } catch (Exception e) { |
| | | Timber.e(e, "获取视频时长失败,使用默认值60秒"); |
| | | } finally { |
| | | if (retriever != null) { |
| | | try { |
| | | retriever.release(); |
| | | } catch (Exception e) { |
| | | Timber.e(e, "释放 MediaMetadataRetriever 失败"); |
| | | } |
| | | } |
| | | } |
| | | // 如果获取失败,返回默认值60秒 |
| | | return 60; |
| | | } |
| | | |
| | | /** |
| | | * 检查并启动Muxer |
| | | * P1模式:当视频和音频轨道都准备好时启动 |
| | | * P2模式:当视频轨道准备好时即可启动(无音频轨道) |