Browse Source

增加carame對比差分

chenzubin 3 months ago
parent
commit
1001cc7b9e

+ 20 - 3
app/src/main/AndroidManifest.xml

@@ -2,11 +2,16 @@
2 2
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 3
     xmlns:tools="http://schemas.android.com/tools">
4 4
 
5
+    <uses-feature
6
+        android:name="android.hardware.camera"
7
+        android:required="false" />
8
+
5 9
     <uses-permission android:name="android.permission.INTERNET" />
6 10
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
7 11
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
8 12
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
9
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
13
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
14
+    <uses-permission android:name="android.permission.CAMERA" />
10 15
 
11 16
     <application
12 17
         android:allowBackup="true"
@@ -19,15 +24,27 @@
19 24
         android:theme="@style/Theme.MainTheme"
20 25
         tools:targetApi="31">
21 26
         <activity
22
-            android:name=".Video"
27
+            android:name=".GatherActivity"
28
+            android:configChanges="orientation|keyboardHidden|screenSize"
23 29
             android:exported="true"
24
-            android:screenOrientation="landscape">
30
+            android:label="@string/title_activity_gather"
31
+            android:theme="@style/Theme.MainTheme.Fullscreen" >
25 32
             <intent-filter>
26 33
                 <action android:name="android.intent.action.MAIN" />
27 34
 
28 35
                 <category android:name="android.intent.category.LAUNCHER" />
29 36
             </intent-filter>
30 37
         </activity>
38
+        <activity
39
+            android:name=".Video"
40
+            android:exported="true"
41
+            android:screenOrientation="landscape">
42
+            <!--<intent-filter>
43
+                <action android:name="android.intent.action.MAIN" />
44
+
45
+                <category android:name="android.intent.category.LAUNCHER" />
46
+            </intent-filter>-->
47
+        </activity>
31 48
     </application>
32 49
 
33 50
 </manifest>

+ 199 - 0
app/src/main/java/com/cfmlg/mlg/AvcEncoder.java

@@ -0,0 +1,199 @@
1
+package com.cfmlg.mlg;
2
+
3
+import android.media.MediaCodec;
4
+import android.media.MediaCodecInfo;
5
+import android.media.MediaFormat;
6
+import android.os.Environment;
7
+
8
+import java.io.BufferedOutputStream;
9
+import java.io.File;
10
+import java.io.FileOutputStream;
11
+import java.io.IOException;
12
+import java.nio.ByteBuffer;
13
+
14
+import static android.media.MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
15
+import static android.media.MediaCodec.BUFFER_FLAG_KEY_FRAME;
16
+
17
+
18
+public class AvcEncoder
19
+{
20
+    private final static String TAG = "MeidaCodec";
21
+
22
+    private int TIMEOUT_USEC = 12000;
23
+
24
+    private MediaCodec mediaCodec;
25
+    int m_width;
26
+    int m_height;
27
+    int m_framerate;
28
+
29
+    public byte[] configbyte;
30
+
31
+
32
+    public AvcEncoder(int width, int height, int framerate, int bitrate) {
33
+
34
+        m_width = width;
35
+        m_height = height;
36
+        m_framerate = framerate;
37
+        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
38
+        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
39
+        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width*height*5);
40
+        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
41
+        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
42
+        try {
43
+            mediaCodec = MediaCodec.createEncoderByType("video/avc");
44
+        } catch (IOException e) {
45
+            e.printStackTrace();
46
+        }
47
+        //配置编码器参数
48
+        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
49
+        //启动编码器
50
+        mediaCodec.start();
51
+        //创建保存编码后数据的文件
52
+        createfile();
53
+    }
54
+
55
+    private static String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test1.h264";
56
+    private BufferedOutputStream outputStream;
57
+
58
+    private void createfile(){
59
+        File file = new File(path);
60
+        if(file.exists()){
61
+            file.delete();
62
+        }
63
+        try {
64
+            outputStream = new BufferedOutputStream(new FileOutputStream(file));
65
+        } catch (Exception e){
66
+            e.printStackTrace();
67
+        }
68
+    }
69
+
70
+    private void StopEncoder() {
71
+        try {
72
+            mediaCodec.stop();
73
+            mediaCodec.release();
74
+        } catch (Exception e){
75
+            e.printStackTrace();
76
+        }
77
+    }
78
+
79
+    public boolean isRuning = false;
80
+
81
+    public void StopThread(){
82
+        isRuning = false;
83
+        try {
84
+            StopEncoder();
85
+            outputStream.flush();
86
+            outputStream.close();
87
+        } catch (IOException e) {
88
+            e.printStackTrace();
89
+        }
90
+    }
91
+
92
+    int count = 0;
93
+
94
+    public void StartEncoderThread(){
95
+        Thread EncoderThread = new Thread(new Runnable() {
96
+
97
+            @Override
98
+            public void run() {
99
+                isRuning = true;
100
+                byte[] input = null;
101
+                long pts = 0;
102
+                long generateIndex = 0;
103
+
104
+                while (isRuning) {
105
+                    //访问MainActivity用来缓冲待解码数据的队列
106
+                    if (GatherActivity.YUVQueue.size() >0){
107
+                        //从缓冲队列中取出一帧
108
+                        input = GatherActivity.YUVQueue.poll();
109
+                        byte[] yuv420sp = new byte[m_width*m_height*3/2];
110
+                        //把待编码的视频帧转换为YUV420格式
111
+                        NV21ToNV12(input,yuv420sp,m_width,m_height);
112
+                        input = yuv420sp;
113
+                    }
114
+                    if (input != null) {
115
+                        try {
116
+                            long startMs = System.currentTimeMillis();
117
+                            //编码器输入缓冲区
118
+                            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
119
+                            //编码器输出缓冲区
120
+                            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
121
+                            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
122
+                            if (inputBufferIndex >= 0) {
123
+                                pts = computePresentationTime(generateIndex);
124
+                                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
125
+                                inputBuffer.clear();
126
+                                //把转换后的YUV420格式的视频帧放到编码器输入缓冲区中
127
+                                inputBuffer.put(input);
128
+                                mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
129
+                                generateIndex += 1;
130
+                            }
131
+
132
+                            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
133
+                            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
134
+                            while (outputBufferIndex >= 0) {
135
+                                //Log.i("AvcEncoder", "Get H264 Buffer Success! flag = "+bufferInfo.flags+",pts = "+bufferInfo.presentationTimeUs+"");
136
+                                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
137
+                                byte[] outData = new byte[bufferInfo.size];
138
+                                outputBuffer.get(outData);
139
+                                if(bufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG){
140
+                                    configbyte = new byte[bufferInfo.size];
141
+                                    configbyte = outData;
142
+                                }else if(bufferInfo.flags == BUFFER_FLAG_KEY_FRAME){
143
+                                    byte[] keyframe = new byte[bufferInfo.size + configbyte.length];
144
+                                    System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length);
145
+                                    //把编码后的视频帧从编码器输出缓冲区中拷贝出来
146
+                                    System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length);
147
+
148
+                                    outputStream.write(keyframe, 0, keyframe.length);
149
+                                }else{
150
+                                    //写到文件中
151
+                                    outputStream.write(outData, 0, outData.length);
152
+                                }
153
+
154
+                                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
155
+                                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
156
+                            }
157
+
158
+                        } catch (Throwable t) {
159
+                            t.printStackTrace();
160
+                        }
161
+                    } else {
162
+                        try {
163
+                            Thread.sleep(500);
164
+                        } catch (InterruptedException e) {
165
+                            e.printStackTrace();
166
+                        }
167
+                    }
168
+                }
169
+            }
170
+        });
171
+        EncoderThread.start();
172
+
173
+    }
174
+
175
+    private void NV21ToNV12(byte[] nv21,byte[] nv12,int width,int height){
176
+        if(nv21 == null || nv12 == null)return;
177
+        int framesize = width*height;
178
+        int i = 0,j = 0;
179
+        System.arraycopy(nv21, 0, nv12, 0, framesize);
180
+        for(i = 0; i < framesize; i++){
181
+            nv12[i] = nv21[i];
182
+        }
183
+        for (j = 0; j < framesize/2; j+=2)
184
+        {
185
+            nv12[framesize + j-1] = nv21[j+framesize];
186
+        }
187
+        for (j = 0; j < framesize/2; j+=2)
188
+        {
189
+            nv12[framesize + j] = nv21[j+framesize-1];
190
+        }
191
+    }
192
+
193
+    /**
194
+     * Generates the presentation time for frame N, in microseconds.
195
+     */
196
+    private long computePresentationTime(long frameIndex) {
197
+        return 132 + frameIndex * 1000000 / m_framerate;
198
+    }
199
+}

+ 156 - 0
app/src/main/java/com/cfmlg/mlg/GatherActivity.java

@@ -0,0 +1,156 @@
1
+package com.cfmlg.mlg;
2
+
3
+import android.Manifest;
4
+import android.annotation.SuppressLint;
5
+
6
+import androidx.appcompat.app.ActionBar;
7
+import androidx.appcompat.app.AppCompatActivity;
8
+import androidx.core.app.ActivityCompat;
9
+import androidx.core.content.ContextCompat;
10
+
11
+import android.app.Activity;
12
+import android.content.Context;
13
+import android.content.pm.PackageManager;
14
+import android.graphics.ImageFormat;
15
+import android.hardware.Camera;
16
+import android.os.Build;
17
+import android.os.Bundle;
18
+import android.os.Handler;
19
+import android.os.Looper;
20
+import android.view.MotionEvent;
21
+import android.view.SurfaceHolder;
22
+import android.view.SurfaceView;
23
+import android.view.View;
24
+import android.view.WindowInsets;
25
+
26
+import com.cfmlg.mlg.databinding.ActivityGatherBinding;
27
+
28
+import java.io.IOException;
29
+import java.util.concurrent.ArrayBlockingQueue;
30
+
31
+public class GatherActivity extends Activity implements SurfaceHolder.Callback, Camera.PreviewCallback {
32
+    private SurfaceView surfaceview;
33
+
34
+    private SurfaceHolder surfaceHolder;
35
+
36
+    private Camera camera;
37
+
38
+    private Camera.Parameters parameters;
39
+
40
+    int width = 1280;
41
+
42
+    int height = 720;
43
+
44
+    int framerate = 30;
45
+
46
+    int biterate = 8500 * 1000;
47
+
48
+    private static int yuvqueuesize = 10;
49
+
50
+    //待解码视频缓冲队列,静态成员!
51
+    public static ArrayBlockingQueue<byte[]> YUVQueue = new ArrayBlockingQueue<byte[]>(yuvqueuesize);
52
+
53
+    private AvcEncoder avcCodec;
54
+
55
+
56
+    @Override
57
+    protected void onCreate(Bundle savedInstanceState) {
58
+        super.onCreate(savedInstanceState);
59
+        setContentView(R.layout.activity_gather);
60
+        requestPermissions(this);
61
+        surfaceview = (SurfaceView) findViewById(R.id.surfaceview);
62
+        surfaceHolder = surfaceview.getHolder();
63
+        surfaceHolder.addCallback(this);
64
+    }
65
+
66
+    private void requestPermissions(Context context) {
67
+        if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
68
+            // 摄像头权限已经被授予
69
+        } else {
70
+            // 摄像头权限未被授予
71
+        }
72
+        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
73
+    }
74
+
75
+    @Override
76
+    public void surfaceCreated(SurfaceHolder holder) {
77
+        camera = getBackCamera();
78
+        startcamera(camera);
79
+        //创建AvEncoder对象
80
+        avcCodec = new AvcEncoder(width, height, framerate, biterate);
81
+        //启动编码线程
82
+        avcCodec.StartEncoderThread();
83
+
84
+    }
85
+
86
+    @Override
87
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
88
+
89
+    }
90
+
91
+    @Override
92
+    public void surfaceDestroyed(SurfaceHolder holder) {
93
+        if (null != camera) {
94
+            camera.setPreviewCallback(null);
95
+            camera.stopPreview();
96
+            camera.release();
97
+            camera = null;
98
+            avcCodec.StopThread();
99
+        }
100
+    }
101
+
102
+
103
+    @Override
104
+    public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
105
+        //将当前帧图像保存在队列中
106
+        putYUVData(data, data.length);
107
+    }
108
+
109
+    public void putYUVData(byte[] buffer, int length) {
110
+        if (YUVQueue.size() >= 10) {
111
+            YUVQueue.poll();
112
+        }
113
+        YUVQueue.add(buffer);
114
+    }
115
+
116
+
117
+    private void startcamera(Camera mCamera) {
118
+        if (mCamera != null) {
119
+            try {
120
+                mCamera.setPreviewCallback(this);
121
+                mCamera.setDisplayOrientation(90);
122
+                if (parameters == null) {
123
+                    parameters = mCamera.getParameters();
124
+                }
125
+                //获取默认的camera配置
126
+                parameters = mCamera.getParameters();
127
+                //设置预览格式
128
+                parameters.setPreviewFormat(ImageFormat.NV21);
129
+                //设置预览图像分辨率
130
+                parameters.setPreviewSize(width, height);
131
+                //配置camera参数
132
+                mCamera.setParameters(parameters);
133
+                //将完全初始化的SurfaceHolder传入到setPreviewDisplay(SurfaceHolder)中
134
+                //没有surface的话,相机不会开启preview预览
135
+                mCamera.setPreviewDisplay(surfaceHolder);
136
+                //调用startPreview()用以更新preview的surface,必须要在拍照之前start Preview
137
+                mCamera.startPreview();
138
+
139
+            } catch (IOException e) {
140
+                e.printStackTrace();
141
+            }
142
+        }
143
+    }
144
+
145
+    private Camera getBackCamera() {
146
+        Camera c = null;
147
+        try {
148
+            //获取Camera的实例
149
+            c = Camera.open(0);
150
+        } catch (Exception e) {
151
+            e.printStackTrace();
152
+        }
153
+        //获取Camera的实例失败时返回null
154
+        return c;
155
+    }
156
+}

+ 11 - 0
app/src/main/res/layout/activity_gather.xml

@@ -0,0 +1,11 @@
1
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
+    android:layout_width="match_parent"
3
+    android:layout_height="match_parent" >
4
+
5
+    <SurfaceView
6
+        android:id="@+id/surfaceview"
7
+        android:layout_width="match_parent"
8
+        android:layout_height="match_parent"/>
9
+
10
+
11
+</RelativeLayout>

+ 5 - 0
app/src/main/res/values-night/themes.xml

@@ -13,4 +13,9 @@
13 13
         <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
14 14
         <!-- Customize your theme here. -->
15 15
     </style>
16
+
17
+    <style name="ThemeOverlay.Eitc_erm_dental_android.FullscreenContainer" parent="">
18
+        <item name="fullscreenBackgroundColor">@color/light_blue_900</item>
19
+        <item name="fullscreenTextColor">@color/light_blue_A400</item>
20
+    </style>
16 21
 </resources>

+ 6 - 0
app/src/main/res/values/attrs.xml

@@ -0,0 +1,6 @@
1
+<resources>
2
+    <declare-styleable name="FullscreenAttrs">
3
+        <attr name="fullscreenBackgroundColor" format="color" />
4
+        <attr name="fullscreenTextColor" format="color" />
5
+    </declare-styleable>
6
+</resources>

+ 5 - 0
app/src/main/res/values/colors.xml

@@ -7,4 +7,9 @@
7 7
     <color name="teal_700">#FF018786</color>
8 8
     <color name="black">#FF000000</color>
9 9
     <color name="white">#FFFFFFFF</color>
10
+    <color name="light_blue_600">#FF039BE5</color>
11
+    <color name="light_blue_900">#FF01579B</color>
12
+    <color name="light_blue_A200">#FF40C4FF</color>
13
+    <color name="light_blue_A400">#FF00B0FF</color>
14
+    <color name="black_overlay">#66000000</color>
10 15
 </resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,6 @@
1 1
 <resources>
2 2
     <string name="app_name">口腔内窥镜</string>
3
+    <string name="title_activity_gather">GatherActivity</string>
4
+    <string name="dummy_button">Dummy Button</string>
5
+    <string name="dummy_content">DUMMY\nCONTENT</string>
3 6
 </resources>

+ 11 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,11 @@
1
+<resources>
2
+
3
+    <style name="Widget.Theme.MainTheme.ActionBar.Fullscreen" parent="Widget.AppCompat.ActionBar">
4
+        <item name="android:background">@color/black_overlay</item>
5
+    </style>
6
+
7
+    <style name="Widget.Theme.MainTheme.ButtonBar.Fullscreen" parent="">
8
+        <item name="android:background">@color/black_overlay</item>
9
+        <item name="android:buttonBarStyle">?android:attr/buttonBarStyle</item>
10
+    </style>
11
+</resources>

+ 12 - 0
app/src/main/res/values/themes.xml

@@ -13,4 +13,16 @@
13 13
         <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
14 14
         <!-- Customize your theme here. -->
15 15
     </style>
16
+
17
+    <style name="Theme.MainTheme.Fullscreen" parent="Theme.MainTheme">
18
+        <item name="android:actionBarStyle">@style/Widget.Theme.MainTheme.ActionBar.Fullscreen
19
+        </item>
20
+        <item name="android:windowActionBarOverlay">true</item>
21
+        <item name="android:windowBackground">@null</item>
22
+    </style>
23
+
24
+    <style name="ThemeOverlay.Eitc_erm_dental_android.FullscreenContainer" parent="">
25
+        <item name="fullscreenBackgroundColor">@color/light_blue_600</item>
26
+        <item name="fullscreenTextColor">@color/light_blue_A200</item>
27
+    </style>
16 28
 </resources>