Dana
8 天以前 01d5238738887b1739cede0c6b589b8c4e34031d
1.usb可以录像,颜色不对
1个文件已添加
3个文件已修改
129 ■■■■■ 已修改文件
app/build.gradle.kts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
key/Verify.txt 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
usbcameralib/build.gradle 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle.kts
@@ -21,6 +21,10 @@
        versionName = "1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        ndk {
            abiFilters += listOf("arm64-v8a", "armeabi-v7a")
        }
    }
    buildFeatures{
        aidl =true
@@ -62,6 +66,15 @@
    buildFeatures {
        compose = true
    }
    packaging {
        jniLibs {
            useLegacyPackaging = false
        }
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}
dependencies {
app/src/main/java/com/safeluck/floatwindow/manager/UsbCameraRecordManager.java
@@ -366,6 +366,20 @@
                    Timber.d("音频录制已启动");
                }
                
                // 主动检查音频编码器输出格式(可能在处理数据前就有格式)
                try {
                    MediaFormat audioFormat = audioEncoder.getOutputFormat();
                    if (audioFormat != null) {
                        synchronized (UsbCameraRecordManager.this) {
                            audioTrackIndex = mediaMuxer.addTrack(audioFormat);
                            Timber.d("音频轨道已添加(主动检查): %d", audioTrackIndex);
                            checkAndStartMuxer();
                        }
                    }
                } catch (Exception e) {
                    Timber.d("音频编码器输出格式尚未准备好,将在处理数据时添加");
                }
                // 启动音频编码线程
                audioThread = new AudioThread();
                audioThread.start();
@@ -374,6 +388,8 @@
                
                long frameCount = 0;
                long lastFileChangeTime = System.currentTimeMillis();
                long audioTrackWaitStartTime = System.currentTimeMillis();
                final long AUDIO_TRACK_TIMEOUT_MS = 3000; // 3秒超时
                
                // 循环处理摄像头数据
                while (isRunning && cameraExists) {
@@ -432,6 +448,27 @@
                    if (videoEncoder != null && mediaMuxer != null) {
                        encodeFrame(buffer, frameCount, width, height);
                        frameCount++;
                        // 如果音频轨道超时未准备好,尝试仅用视频轨道启动
                        if (!muxerStarted && videoTrackIndex >= 0 && audioTrackIndex < 0) {
                            // 重用循环中已定义的 currentTime 变量
                            if (currentTime - audioTrackWaitStartTime > AUDIO_TRACK_TIMEOUT_MS) {
                                Timber.w("音频轨道超时未准备好,仅使用视频轨道启动Muxer");
                                synchronized (UsbCameraRecordManager.this) {
                                    // 创建一个假的音频轨道索引,或者直接启动(但MediaMuxer要求至少一个轨道)
                                    // 实际上MediaMuxer需要至少一个轨道,所以如果视频轨道准备好了就可以启动
                                    if (videoTrackIndex >= 0) {
                                        try {
                                            mediaMuxer.start();
                                            muxerStarted = true;
                                            Timber.d("Muxer started with video track only: %d", videoTrackIndex);
                                        } catch (Exception e) {
                                            Timber.e(e, "Failed to start muxer with video only");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    
                    // 控制帧率,约20fps
@@ -482,12 +519,17 @@
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                int outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0);
                
                while (outputBufferIndex >= 0) {
                while (outputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
                    if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        // 输出格式改变,添加视频轨道
                        MediaFormat newFormat = videoEncoder.getOutputFormat();
                        videoTrackIndex = mediaMuxer.addTrack(newFormat);
                        checkAndStartMuxer();
                        synchronized (UsbCameraRecordManager.this) {
                            videoTrackIndex = mediaMuxer.addTrack(newFormat);
                            Timber.d("视频轨道已添加: %d", videoTrackIndex);
                            checkAndStartMuxer();
                        }
                        outputBufferIndex = videoEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        continue;
                    } else if (outputBufferIndex >= 0) {
                        ByteBuffer outputBuffer = videoEncoder.getOutputBuffer(outputBufferIndex);
                        if (outputBuffer != null && muxerStarted && videoTrackIndex >= 0) {
@@ -512,9 +554,15 @@
     */
    private synchronized void checkAndStartMuxer() {
        if (!muxerStarted && videoTrackIndex >= 0 && audioTrackIndex >= 0) {
            mediaMuxer.start();
            muxerStarted = true;
            try {
                mediaMuxer.start();
                muxerStarted = true;
                Timber.d("Muxer started, video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex);
            } catch (Exception e) {
                Timber.e(e, "Failed to start muxer");
            }
        } else {
            Timber.d("Muxer not started yet, video track: %d, audio track: %d", videoTrackIndex, audioTrackIndex);
        }
    }
    
@@ -580,12 +628,17 @@
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                int outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0);
                
                while (outputBufferIndex >= 0) {
                while (outputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
                    if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        // 输出格式改变,添加音频轨道
                        MediaFormat newFormat = audioEncoder.getOutputFormat();
                        audioTrackIndex = mediaMuxer.addTrack(newFormat);
                        checkAndStartMuxer();
                        synchronized (UsbCameraRecordManager.this) {
                            audioTrackIndex = mediaMuxer.addTrack(newFormat);
                            Timber.d("音频轨道已添加: %d", audioTrackIndex);
                            checkAndStartMuxer();
                        }
                        outputBufferIndex = audioEncoder.dequeueOutputBuffer(bufferInfo, 0);
                        continue;
                    } else if (outputBufferIndex >= 0) {
                        ByteBuffer outputBuffer = audioEncoder.getOutputBuffer(outputBufferIndex);
                        if (outputBuffer != null && muxerStarted && audioTrackIndex >= 0) {
key/Verify.txt
New file
@@ -0,0 +1,41 @@
    private static PrivateKey getKey(String key, char[] password) throws Exception {
        byte[] cabuf = new BASE64Decoder().decodeBuffer(key);
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(new ByteArrayInputStream(cabuf), password);
        Enumeration<String> aliases = keyStore.aliases();
        if (!aliases.hasMoreElements()) {
            throw new RuntimeException("no alias found");
        }
        String alias = aliases.nextElement();
        PrivateKey privateKey = (RSAPrivateCrtKeyImpl) keyStore.getKey(alias, password);
        return privateKey;
    }
    public static String sign(String data, PrivateKey key) throws Exception {
//        MessageDigest md = MessageDigest.getInstance("SHA-256");
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(data.getBytes("utf-8"));
        byte[] hash = md.digest();
//        Cipher cipher = Cipher.getInstance("RSA");
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(hash);
        return HexBin.encode(encrypted);
    }
    public static String sign(String data, String keyString ,String pwd ) throws Exception {
        char[] password = pwd.toCharArray();
        return sign(data,getKey(keyString,password));
    }
    public static void main(String[] args) {
        try{
            String sign = sign("xuanborobot123456nullnullnull","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","27U777");
            System.out.println(sign);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
usbcameralib/build.gradle
@@ -16,6 +16,8 @@
            cmake {
                arguments "-DANDROID_ARM_NEON=TRUE"
                cppFlags ""
                // 确保使用 c++_shared STL
                arguments "-DANDROID_STL=c++_shared"
            }
        }
        ndk {
@@ -43,7 +45,9 @@
        pickFirst 'lib/armeabi-v7a/libturbojpeg.so'
        pickFirst 'lib/arm64-v8a/libjpeg.so'
        pickFirst 'lib/arm64-v8a/libturbojpeg.so'
        // 确保 libc++_shared.so 被包含
        pickFirst 'lib/armeabi-v7a/libc++_shared.so'
        pickFirst 'lib/arm64-v8a/libc++_shared.so'
    }
}