Dana
7 天以前 f5e45186eb4daedc7c33216766637d0dcbd1ebcf
1.readme
1个文件已添加
375 ■■■■■ 已修改文件
readMe.md 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
readMe.md
New file
@@ -0,0 +1,375 @@
* MediaMuxer 启动后不能再添加轨道
* 视频和音频的时间戳需要正确同步
现在代码应该:
* 正确等待两个轨道都准备好
* 同步启动 MediaMuxer
* 正确写入视频和音频数据
* 避免状态冲突导致的错误
mp4文件明显时间加快了,现实世界1秒钟,可能mp4文件的视频就是半秒,所有录一分钟的视频mp4文件,视频只播到30秒就不再播了
检查并修复时间戳计算问题,可能是帧率或时间戳计算不正确:
修复时间戳计算:改用实际时间而非帧索引,确保时间戳准确:
修复时间戳计算,使用实际时间而非帧索引:
UsbCameraRecordManager.java
long frameCount = 0;
// 记录开始时间(纳秒,用于精确时间戳)
long recordingStartTimeNs = System.nanoTime();
long frameCount = 0;
UsbCameraRecordManager.java
if (videoEncoder != null && mediaMuxer != null) {
encodeFrame(buffer, frameCount, width, height);
frameCount++;
encodeFrame(buffer, frameCount, width, height);
if (videoEncoder != null && mediaMuxer != null) {
// 计算实际经过的时间(微秒)
long elapsedTimeUs = (System.nanoTime() - recordingStartTimeNs) / 1000;
encodeFrame(buffer, elapsedTimeUs, width, height);
frameCount++;
UsbCameraRecordManager.java
* 编码一帧数据
*/
* 编码一帧数据
* @param yuvData YUV数据
* @param presentationTimeUs 时间戳(微秒),基于实际开始时间
* @param width 宽度
* @param height 高度
*/
修复音频时间戳,使其与视频时间戳同步:
UsbCameraRecordManager.java
+3
/**
// 录制开始时间(纳秒),用于时间戳同步
private volatile long recordingStartTimeNs = 0;
/**
UsbCameraRecordManager.java
+1-1
// 记录开始时间(纳秒,用于精确时间戳)
long recordingStartTimeNs = System.nanoTime();
long frameCount = 0;
long recordingStartTimeNs = System.nanoTime();
// 记录开始时间(纳秒,用于精确时间戳)
recordingStartTimeNs = System.nanoTime();
long frameCount = 0;
UsbCameraRecordManager.java
+3-3
byte[] audioBuffer = new byte[audioBufferSize];
long audioFrameCount = 0;
// 编码音频数据
long audioFrameCount = 0;
byte[] audioBuffer = new byte[audioBufferSize];
long totalSamplesRead = 0; // 总采样数
// 编码音频数据
UsbCameraRecordManager.java
+7-2
* 编码音频数据
*/
private void encodeAudio(byte[] audioData, int size, long frameCount) {
private void encodeAudio(byte[] audioData, int size, long frameCount) {
* 编码音频数据
* @param audioData 音频数据
* @param size 数据大小(字节)
* @param totalSamples 总采样数(从开始到现在的累计采样数)
*/
在创建新文件时重置开始时间:
UsbCameraRecordManager.java
+3
// 重新启动音频录制
// 重置开始时间
recordingStartTimeNs = System.nanoTime();
// 重新启动音频录制
已修复时间戳计算问题。主要修改如下:修复内容:
* * *
1. 视频时间戳使用实际时间:
* 之前:使用 (frameIndex * 1000000) / FRAME_RATE 计算,假设固定帧率
* 现在:使用 (System.nanoTime() - recordingStartTimeNs) / 1000,基于实际经过时间
1. 音频时间戳基于采样数:
* 使用 (totalSamples * 1000000) / SAMPLE_RATE 计算
* totalSamples 是累计采样数,确保时间戳连续
1. 添加了录制开始时间记录:
* 使用 recordingStartTimeNs 记录开始时间(纳秒)
* 在创建新文件时重置,确保每个文件的时间戳从 0 开始
1. 确保时间戳同步:
* 视频和音频都从同一个基准时间开始
* 每个新文件的时间戳都从 0 开始
修复原理:
* 问题原因:使用帧索引计算时间戳,假设固定帧率。实际采集帧率可能不同,导致时间戳不准确
* 解决方案:使用实际经过的时间计算时间戳,确保时间戳与实际时间一致
现在:
* 视频时间戳基于实际经过的时间
* 音频时间戳基于实际读取的采样数
* 两者都从 0 开始,确保同步
#### 使用
          private var mediaAidlInterface: IMediaAidlInterface? = null
        private var serviceConnection: ServiceConnection? = null
        private var isServiceBound = false
        private val isServiceBoundState = mutableStateOf(false)
            serviceConnection = object : ServiceConnection {
                override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                    mediaAidlInterface = IMediaAidlInterface.Stub.asInterface(service)
                    isServiceBound = true
                    isServiceBoundState.value = true
                    Timber.d("FloatingService connected")
                }
                override fun onServiceDisconnected(name: ComponentName?) {
                    mediaAidlInterface = null
                    isServiceBound = false
                    isServiceBoundState.value = false
                    Timber.d("FloatingService disconnected")
           onBindService = {
                                if (!isServiceBoundState.value && serviceConnection != null) {
                                    val intent = Intent(this@MainActivity, FloatingService::class.java)
                                    bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE)
                                    Timber.d("Binding FloatingService")
                                }
                            }, }
            }
        private fun startAndroidRecord() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot start Android record")
                return
            }
            try {
                val mediaArgu = MediaArgu().apply {
                    isPush = false
                    isUsedOutCamera = false // Android 内置摄像头
                    codeRate = 0
                    frameRate = 0
                    m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
                    recordTime = 0
                    tfCardFlag = 0 // 内部存储
                }
                mediaAidlInterface?.registerCallback(callback)
                mediaAidlInterface?.startMedia(mediaArgu)
                Timber.d("Started Android camera record")
            } catch (e: RemoteException) {
                Timber.e(e, "Error starting Android record")
            }
        }
        private fun stopAndroidRecord() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot stop Android record")
                return
            }
            try {
                mediaAidlInterface?.stopMedia()
                Timber.d("Stopped Android camera record")
            } catch (e: RemoteException) {
                Timber.e(e, "Error stopping Android record")
            }
        }
        private fun startUsbRecord() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot start USB record")
                return
            }
            try {
                val mediaArgu = MediaArgu().apply {
                    isPush = false
                    isUsedOutCamera = true // USB 摄像头
                    codeRate = 0
                    frameRate = 0
                    m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
                    recordTime = 0
                    tfCardFlag = 0 // 内部存储
                }
                mediaAidlInterface?.registerCallback(callback)
                mediaAidlInterface?.startMedia(mediaArgu)
                Timber.d("Started USB camera record")
            } catch (e: RemoteException) {
                Timber.e(e, "Error starting USB record")
            }
        }
        private fun stopUsbRecord() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot stop USB record")
                return
            }
            try {
                mediaAidlInterface?.stopMedia()
                Timber.d("Stopped USB camera record")
            } catch (e: RemoteException) {
                Timber.e(e, "Error stopping USB record")
            }
        }
        private fun startUsbPush() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot start USB push")
                return
            }
            try {
                val mediaArgu = MediaArgu().apply {
                    isPush = true
                    isUsedOutCamera = true // USB 摄像头
                    codeRate = 0
                    frameRate = 0
                    m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率
                    url = "rtmp://192.168.16.143/live/livestream" // TODO: 需要设置实际的推流地址
                    userName = ""
                    pwd = ""
                }
                mediaAidlInterface?.registerCallback(callback)
                mediaAidlInterface?.startMedia(mediaArgu)
                Timber.d("Started USB camera push")
            } catch (e: RemoteException) {
                Timber.e(e, "Error starting USB push")
            }
        }
        private fun stopUsbPush() {
            if (mediaAidlInterface == null) {
                Timber.w("Service not bound, cannot stop USB push")
                return
            }
            try {
                mediaAidlInterface?.stopMedia()
                Timber.d("Stopped USB camera push")
            } catch (e: RemoteException) {
                Timber.e(e, "Error stopping USB push")
            }
        }
        private fun unbindServiceInternal() {
            if (isServiceBound && serviceConnection != null) {
                try {
                    mediaAidlInterface?.unregisterCallback(callback)
                } catch (e: RemoteException) {
                    Timber.e(e, "Error unregistering callback")
                }
                try {
                    unbindService(serviceConnection!!)
                    Timber.d("Unbinding FloatingService")
                } catch (e: Exception) {
                    Timber.e(e, "Error unbinding service")
                }
                isServiceBound = false
                isServiceBoundState.value = false
                mediaAidlInterface = null
            }
        }