├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── DecodeH264 ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── zjf │ │ └── mediacodecdecodeh264demo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── zjf │ │ │ └── mediacodecdecodeh264demo │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── zjf │ └── mediacodecdecodeh264demo │ └── ExampleUnitTest.java ├── EncodeH264 ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── encodeh264 │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── encodeh264 │ │ │ ├── AvcEncoder.java │ │ │ ├── Camera2Preview.java │ │ │ ├── EncodeYUVToH264Activity2.java │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_encode_yuvto_h264.xml │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── encodeh264 │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DecodeH264/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /DecodeH264/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.example.zjf.mediacodecdecodeh264demo" 7 | minSdkVersion 15 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /DecodeH264/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /DecodeH264/src/androidTest/java/com/example/zjf/mediacodecdecodeh264demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.zjf.mediacodecdecodeh264demo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.zjf.mediacodecdecodeh264demo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DecodeH264/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /DecodeH264/src/main/java/com/example/zjf/mediacodecdecodeh264demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.zjf.mediacodecdecodeh264demo; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.os.Build; 6 | import android.os.Environment; 7 | import android.support.annotation.RequiresApi; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.os.Bundle; 10 | import android.view.SurfaceHolder; 11 | import android.view.SurfaceView; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.DataInputStream; 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.FileNotFoundException; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.nio.ByteBuffer; 22 | 23 | public class MainActivity extends AppCompatActivity { 24 | private static final String TAG = "MainActivity"; 25 | 26 | private SurfaceView mSurfaceView; 27 | private SurfaceHolder mSurfaceHolder; 28 | private Thread mDecodeThread; 29 | private MediaCodec mCodec; 30 | private boolean mStopFlag = false; 31 | private DataInputStream mInputStream; 32 | private boolean UseSPSandPPS = false; 33 | 34 | private final static String SD_PATH = Environment.getExternalStorageDirectory().getPath(); 35 | private final static String H264_FILE = SD_PATH + "/H264.h264"; 36 | 37 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_main); 42 | mSurfaceView = (SurfaceView)findViewById(R.id.mSurfaceView); 43 | //获取文件输入流 44 | getFileInputStream(); 45 | initMediaCodec(); 46 | } 47 | 48 | 49 | /** 50 | * 获取需要解码的文件流 51 | */ 52 | public void getFileInputStream() { 53 | try { 54 | File file = new File(H264_FILE); 55 | mInputStream = new DataInputStream(new FileInputStream(file)); 56 | } catch (FileNotFoundException e) { 57 | e.printStackTrace(); 58 | try { 59 | mInputStream.close(); 60 | } catch (IOException e1) { 61 | e1.printStackTrace(); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * 初始化解码器 68 | */ 69 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 70 | private void initMediaCodec() { 71 | mSurfaceHolder = mSurfaceView.getHolder(); 72 | mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { 73 | @Override 74 | public void surfaceCreated(SurfaceHolder holder) { 75 | try { 76 | //创建编码器 77 | mCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); 78 | } catch (IOException e) { 79 | e.printStackTrace(); 80 | } 81 | //初始化编码器 82 | final MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,holder.getSurfaceFrame().width(),holder.getSurfaceFrame().height()); 83 | /*h264常见的帧头数据为: 84 | 00 00 00 01 67 (SPS) 85 | 00 00 00 01 68 (PPS) 86 | 00 00 00 01 65 (IDR帧) 87 | 00 00 00 01 61 (P帧)*/ 88 | 89 | //获取H264文件中的pps和sps数据 90 | if (UseSPSandPPS) { 91 | byte[] header_sps = {0, 0, 0, 1, 67, 66, 0, 42, (byte) 149, (byte) 168, 30, 0, (byte) 137, (byte) 249, 102, (byte) 224, 32, 32, 32, 64}; 92 | byte[] header_pps = {0, 0, 0, 1, 68, (byte) 206, 60, (byte) 128, 0, 0, 0, 1, 6, (byte) 229, 1, (byte) 151, (byte) 128}; 93 | mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps)); 94 | mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps)); 95 | } 96 | 97 | //设置帧率 98 | mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,40); 99 | mCodec.configure(mediaFormat,holder.getSurface(),null,0); 100 | 101 | startDecodingThread(); 102 | } 103 | 104 | @Override 105 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 106 | 107 | } 108 | 109 | @Override 110 | public void surfaceDestroyed(SurfaceHolder holder) { 111 | 112 | } 113 | }); 114 | } 115 | 116 | /** 117 | * 开启解码器并开启读取文件的线程 118 | */ 119 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 120 | private void startDecodingThread(){ 121 | mCodec.start(); 122 | mDecodeThread = new Thread(new DecodeThread()); 123 | mDecodeThread.start(); 124 | } 125 | 126 | 127 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 128 | private class DecodeThread implements Runnable{ 129 | 130 | @Override 131 | public void run() { 132 | //循环解码 133 | decodeLoop(); 134 | } 135 | 136 | 137 | private void decodeLoop(){ 138 | //获取一组输入缓存区 139 | ByteBuffer[] inputBuffers = mCodec.getInputBuffers(); 140 | //解码后的数据,包含每一个buffer的元数据信息 141 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 142 | long startMs = System.currentTimeMillis(); 143 | long timeoutUs = 10000; 144 | //用于检测文件头 145 | byte[] maker0 = new byte[]{0,0,0,1}; 146 | 147 | byte[] dummyFrame = new byte[]{0x00,0x00,0x01,0x20}; 148 | byte[] streamBuffer = null; 149 | 150 | try { 151 | //返回可用的字节数组 152 | streamBuffer = getBytes(mInputStream); 153 | } catch (IOException e) { 154 | e.printStackTrace(); 155 | } 156 | int bytes_cnt = 0; 157 | while (mStopFlag == false){ 158 | //得到可用字节数组长度 159 | bytes_cnt = streamBuffer.length; 160 | 161 | if (bytes_cnt == 0){ 162 | streamBuffer = dummyFrame; 163 | } 164 | int startIndex = 0; 165 | //定义记录剩余字节的变量 166 | int remaining = bytes_cnt; 167 | while (true) { 168 | //当剩余的字节=0或者开始的读取的字节下标大于可用的字节数时 不在继续读取 169 | if (remaining == 0 || startIndex >= remaining) { 170 | break; 171 | } 172 | //寻找帧头部 173 | int nextFrameStart = KMPMatch(maker0,streamBuffer,startIndex + 2,remaining); 174 | //找不到头部返回-1 175 | if (nextFrameStart == -1) { 176 | nextFrameStart = remaining; 177 | } 178 | //得到可用的缓存区 179 | int inputIndex = mCodec.dequeueInputBuffer(timeoutUs); 180 | //有可用缓存区 181 | if (inputIndex >= 0) { 182 | ByteBuffer byteBuffer = inputBuffers[inputIndex]; 183 | byteBuffer.clear(); 184 | //将可用的字节数组,传入缓冲区 185 | byteBuffer.put(streamBuffer, startIndex, nextFrameStart - startIndex); 186 | //把数据传递给解码器 187 | mCodec.queueInputBuffer(inputIndex, 0, nextFrameStart - startIndex, 0, 0); 188 | //指定下一帧的位置 189 | startIndex = nextFrameStart; 190 | } else { 191 | continue; 192 | } 193 | 194 | int outputIndex = mCodec.dequeueOutputBuffer(info,timeoutUs); 195 | if (outputIndex >= 0) { 196 | //帧控制是不在这种情况下工作,因为没有PTS H264是可用的 197 | while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) { 198 | try { 199 | Thread.sleep(100); 200 | } catch (InterruptedException e) { 201 | e.printStackTrace(); 202 | } 203 | } 204 | boolean doRender = (info.size != 0); 205 | //对outputbuffer的处理完后,调用这个函数把buffer重新返回给codec类。 206 | mCodec.releaseOutputBuffer(outputIndex, doRender); 207 | } else { 208 | 209 | } 210 | } 211 | mStopFlag = true; 212 | } 213 | } 214 | } 215 | /** 216 | * 获得可用的字节数组 217 | * @param is 218 | * @return 219 | * @throws IOException 220 | */ 221 | public static byte[] getBytes(InputStream is) throws IOException { 222 | int len; 223 | int size = 1024; 224 | byte[] buf; 225 | if (is instanceof ByteArrayInputStream) { 226 | //返回可用的剩余字节 227 | size = is.available(); 228 | //创建一个对应可用相应字节的字节数组 229 | buf = new byte[size]; 230 | //读取这个文件并保存读取的长度 231 | len = is.read(buf, 0, size); 232 | } else { 233 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 234 | buf = new byte[size]; 235 | while ((len = is.read(buf, 0, size)) != -1) 236 | //将读取的数据写入到字节输出流 237 | bos.write(buf, 0, len); 238 | //将这个流转换成字节数组 239 | buf = bos.toByteArray(); 240 | } 241 | return buf; 242 | } 243 | 244 | 245 | /** 246 | * 查找帧头部的位置 247 | * @param pattern 文件头字节数组 248 | * @param bytes 可用的字节数组 249 | * @param start 开始读取的下标 250 | * @param remain 可用的字节数量 251 | * @return 252 | */ 253 | private int KMPMatch(byte[] pattern, byte[] bytes, int start, int remain) { 254 | try { 255 | Thread.sleep(30); 256 | } catch (InterruptedException e) { 257 | e.printStackTrace(); 258 | } 259 | int[] lsp = computeLspTable(pattern); 260 | 261 | int j = 0; // Number of chars matched in pattern 262 | for (int i = start; i < remain; i++) { 263 | while (j > 0 && bytes[i] != pattern[j]) { 264 | // Fall back in the pattern 265 | j = lsp[j - 1]; // Strictly decreasing 266 | } 267 | if (bytes[i] == pattern[j]) { 268 | // Next char matched, increment position 269 | j++; 270 | if (j == pattern.length) 271 | return i - (j - 1); 272 | } 273 | } 274 | 275 | return -1; // Not found 276 | } 277 | 278 | // 0 1 2 0 279 | private int[] computeLspTable(byte[] pattern) { 280 | int[] lsp = new int[pattern.length]; 281 | lsp[0] = 0; // Base case 282 | for (int i = 1; i < pattern.length; i++) { 283 | // Start by assuming we're extending the previous LSP 284 | int j = lsp[i - 1]; 285 | while (j > 0 && pattern[i] != pattern[j]) 286 | j = lsp[j - 1]; 287 | if (pattern[i] == pattern[j]) 288 | j++; 289 | lsp[i] = j; 290 | } 291 | return lsp; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xiaoben336/MediaCodecDecodeH264Demo/cbe240a5a0f488f5a3d5704e53e53dc7413f7fbd/DecodeH264/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /DecodeH264/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MediaCodecDecodeH264Demo 3 | 4 | -------------------------------------------------------------------------------- /DecodeH264/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /DecodeH264/src/test/java/com/example/zjf/mediacodecdecodeh264demo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.zjf.mediacodecdecodeh264demo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /EncodeH264/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /EncodeH264/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | 8 | defaultConfig { 9 | applicationId "com.example.encodeh264" 10 | minSdkVersion 15 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | implementation 'com.android.support:appcompat-v7:28.0.0' 32 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 33 | testImplementation 'junit:junit:4.12' 34 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 36 | } 37 | -------------------------------------------------------------------------------- /EncodeH264/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /EncodeH264/src/androidTest/java/com/example/encodeh264/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.encodeh264; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.encodeh264", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /EncodeH264/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /EncodeH264/src/main/java/com/example/encodeh264/AvcEncoder.java: -------------------------------------------------------------------------------- 1 | package com.example.encodeh264; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaCodecInfo; 5 | import android.media.MediaFormat; 6 | import android.os.Build; 7 | import android.support.annotation.RequiresApi; 8 | 9 | import java.io.BufferedOutputStream; 10 | import java.io.File; 11 | import java.io.FileNotFoundException; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | import java.util.concurrent.ArrayBlockingQueue; 16 | 17 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 18 | public class AvcEncoder { 19 | private static final String TAG = "AvcEncoder"; 20 | private int TIMEOUT_USEC = 12000; 21 | private int mYuvQueueSize = 10; 22 | 23 | public ArrayBlockingQueue mYuvQueue = new ArrayBlockingQueue<>(mYuvQueueSize); 24 | 25 | private MediaCodec mMediaCodec; 26 | private int mWidth; 27 | private int mHeight; 28 | private int mFrameRate; 29 | 30 | private File mOutFile; 31 | //true--Camera的预览数据编码 32 | // false--Camera2的预览数据编码 33 | private boolean mIsCamera; 34 | 35 | public byte[] mConfigByte; 36 | 37 | 38 | public AvcEncoder(int width, int height, int frameRate, File outFile, boolean isCamera){ 39 | mIsCamera = isCamera; 40 | mWidth = width; 41 | mHeight = height; 42 | mFrameRate = frameRate; 43 | mOutFile = outFile; 44 | 45 | MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,width,height); 46 | mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); 47 | mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,width * height * 5); 48 | mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30); 49 | mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,1); 50 | 51 | try { 52 | mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | mMediaCodec.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); 57 | mMediaCodec.start(); 58 | createFile(); 59 | } 60 | 61 | private BufferedOutputStream outputStream; 62 | private void createFile() { 63 | try { 64 | outputStream = new BufferedOutputStream(new FileOutputStream(mOutFile)); 65 | } catch (FileNotFoundException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | private void stopEncoder(){ 71 | if (mMediaCodec != null) { 72 | mMediaCodec.stop(); 73 | mMediaCodec.release(); 74 | mMediaCodec = null; 75 | } 76 | } 77 | 78 | public void putYUVData(byte[] buffer) { 79 | if (mYuvQueue.size() >= 10) { 80 | mYuvQueue.poll(); 81 | } 82 | mYuvQueue.add(buffer); 83 | } 84 | 85 | public void stopThread(){ 86 | if (!isRunning) return; 87 | isRunning = false; 88 | try { 89 | stopEncoder(); 90 | if (outputStream != null) { 91 | outputStream.flush(); 92 | outputStream.close(); 93 | outputStream = null; 94 | } 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | } 98 | 99 | } 100 | 101 | public boolean isRunning = false; 102 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 103 | public void startEncoderThread(){ 104 | Thread encoderThread = new Thread(new Runnable() { 105 | @Override 106 | public void run() { 107 | isRunning = true; 108 | byte[] input = null; 109 | long pts = 0; 110 | long generateIndex = 0; 111 | 112 | while (isRunning) { 113 | if (mYuvQueue.size() > 0) { 114 | input = mYuvQueue.poll(); 115 | if (mIsCamera) {//Camera NV21 116 | //NV12数据所需空间为如下,所以建立如下缓冲区 117 | //y=W*h;u=W*H/4;v=W*H/4,so total add is W*H*3/2 (1 + 1/4 + 1/4 = 3/2) 118 | byte[] yuv420sp = new byte[mWidth * mHeight *3 /2]; 119 | NV21ToNV12(input, yuv420sp, mWidth, mHeight); 120 | input = yuv420sp; 121 | } else {//Camera 2 122 | byte[] yuv420sp = new byte[mWidth * mHeight *3 /2]; 123 | YV12toNV12(input, yuv420sp, mWidth, mHeight); 124 | input = yuv420sp; 125 | } 126 | } 127 | 128 | if (input != null) { 129 | int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1); 130 | if (inputBufferIndex >= 0) { 131 | pts = computePresentationTime(generateIndex); 132 | ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex); 133 | inputBuffer.clear(); 134 | inputBuffer.put(input); 135 | mMediaCodec.queueInputBuffer(inputBufferIndex,0,input.length,pts,0); 136 | generateIndex += 1; 137 | } 138 | 139 | MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 140 | int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo,TIMEOUT_USEC); 141 | while (outputBufferIndex >= 0) { 142 | ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex); 143 | byte[] outData = new byte[bufferInfo.size]; 144 | outputBuffer.get(outData); 145 | if (bufferInfo.flags == 2) { 146 | mConfigByte = new byte[bufferInfo.size]; 147 | mConfigByte = outData; 148 | } else if (bufferInfo.flags == 1) { 149 | byte[] keyframe = new byte[bufferInfo.size + mConfigByte.length]; 150 | System.arraycopy(mConfigByte, 0, keyframe, 0, mConfigByte.length); 151 | System.arraycopy(outData, 0, keyframe, mConfigByte.length, outData.length); 152 | try { 153 | outputStream.write(keyframe, 0, keyframe.length); 154 | } catch (IOException e) { 155 | e.printStackTrace(); 156 | } 157 | } else { 158 | try { 159 | outputStream.write(outData, 0, outData.length); 160 | } catch (IOException e) { 161 | e.printStackTrace(); 162 | } 163 | } 164 | 165 | mMediaCodec.releaseOutputBuffer(outputBufferIndex,false); 166 | outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo,TIMEOUT_USEC); 167 | } 168 | } else { 169 | try { 170 | Thread.sleep(500); 171 | } catch (InterruptedException e) { 172 | e.printStackTrace(); 173 | } 174 | } 175 | } 176 | } 177 | }); 178 | encoderThread.start(); 179 | } 180 | 181 | private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) { 182 | if (nv21 == null || nv12 == null) return; 183 | int framesize = width * height; 184 | int i = 0, j = 0; 185 | System.arraycopy(nv21, 0, nv12, 0, framesize); 186 | for (i = 0; i < framesize; i++) { 187 | nv12[i] = nv21[i]; 188 | } 189 | for (j = 0; j < framesize / 2; j += 2) { 190 | nv12[framesize + j - 1] = nv21[j + framesize]; 191 | } 192 | for (j = 0; j < framesize / 2; j += 2) { 193 | nv12[framesize + j] = nv21[j + framesize - 1]; 194 | } 195 | } 196 | 197 | private void YV12toNV12(byte[] yv12bytes, byte[] nv12bytes, int width, int height) { 198 | 199 | int nLenY = width * height; 200 | int nLenU = nLenY / 4; 201 | 202 | 203 | System.arraycopy(yv12bytes, 0, nv12bytes, 0, width * height); 204 | for (int i = 0; i < nLenU; i++) { 205 | nv12bytes[nLenY + 2 * i] = yv12bytes[nLenY + i]; 206 | nv12bytes[nLenY + 2 * i + 1] = yv12bytes[nLenY + nLenU + i]; 207 | } 208 | } 209 | 210 | /** 211 | * Generates the presentation time for frame N, in microseconds. 212 | */ 213 | private long computePresentationTime(long frameIndex) { 214 | return 132 + frameIndex * 1000000 / mFrameRate; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /EncodeH264/src/main/java/com/example/encodeh264/Camera2Preview.java: -------------------------------------------------------------------------------- 1 | package com.example.encodeh264; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import android.graphics.ImageFormat; 8 | import android.graphics.Matrix; 9 | import android.graphics.RectF; 10 | import android.graphics.SurfaceTexture; 11 | import android.hardware.camera2.CameraAccessException; 12 | import android.hardware.camera2.CameraCaptureSession; 13 | import android.hardware.camera2.CameraCharacteristics; 14 | import android.hardware.camera2.CameraDevice; 15 | import android.hardware.camera2.CameraManager; 16 | import android.hardware.camera2.CameraMetadata; 17 | import android.hardware.camera2.CaptureRequest; 18 | import android.hardware.camera2.params.StreamConfigurationMap; 19 | import android.media.Image; 20 | import android.media.ImageReader; 21 | import android.os.Build; 22 | import android.os.Environment; 23 | import android.os.Handler; 24 | import android.os.HandlerThread; 25 | import android.support.annotation.NonNull; 26 | import android.support.annotation.RequiresApi; 27 | import android.support.v4.app.ActivityCompat; 28 | import android.text.TextUtils; 29 | import android.util.AttributeSet; 30 | import android.util.Log; 31 | import android.util.Size; 32 | import android.view.Surface; 33 | import android.view.TextureView; 34 | import android.widget.Toast; 35 | 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.nio.ByteBuffer; 39 | import java.text.SimpleDateFormat; 40 | import java.util.ArrayList; 41 | import java.util.Arrays; 42 | import java.util.Collections; 43 | import java.util.Comparator; 44 | import java.util.Date; 45 | import java.util.List; 46 | 47 | import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; 48 | import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO; 49 | 50 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 51 | public class Camera2Preview extends TextureView { 52 | private static final String TAG = "Camera2Preview"; 53 | private Handler mBackgroundHandler; 54 | private HandlerThread mBackgroundThread; 55 | private CameraManager mCameraManager; 56 | private CameraDevice mCameraDevice; 57 | protected CameraCaptureSession mCameraCaptureSessions; 58 | protected CaptureRequest.Builder mPreviewRequestBuilder; 59 | private ImageReader mImageReader; 60 | private Context mContext; 61 | 62 | private Size mPreviewSize; 63 | 64 | private static final String CAMERA_FONT = "0"; 65 | private static final String CAMERA_BACK = "1"; 66 | private String mCameraId; 67 | public Camera2Preview(Context context) { 68 | this(context, null); 69 | } 70 | 71 | 72 | public Camera2Preview(Context context, AttributeSet attrs) { 73 | this(context, attrs, 0); 74 | } 75 | 76 | 77 | public Camera2Preview(Context context, AttributeSet attrs, int defStyleAttr) { 78 | super(context, attrs, defStyleAttr); 79 | this.mContext = context; 80 | setKeepScreenOn(true); 81 | getDefaultCameraId(); 82 | } 83 | 84 | SurfaceTextureListener textureListener = new SurfaceTextureListener(){ 85 | 86 | @Override 87 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 88 | //开启摄像头 89 | setupCamera(); 90 | } 91 | 92 | @Override 93 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 94 | 95 | } 96 | 97 | @Override 98 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 99 | return false; 100 | } 101 | 102 | @Override 103 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 104 | 105 | } 106 | }; 107 | 108 | public void onResume(){ 109 | Log.e(TAG, "onResume"); 110 | startBackgroundThread(); 111 | if (isAvailable()) { 112 | setupCamera(); 113 | } else { 114 | setSurfaceTextureListener(textureListener); 115 | } 116 | } 117 | 118 | 119 | public void onPause() { 120 | Log.e(TAG, "onPause"); 121 | if (mAvcEncoder != null) { 122 | mAvcEncoder.stopThread(); 123 | mAvcEncoder = null; 124 | } 125 | closeCamera(); 126 | stopBackgroundThread(); 127 | } 128 | 129 | private void stopBackgroundThread() { 130 | mBackgroundThread.quitSafely(); 131 | try { 132 | mBackgroundThread.join(); 133 | mBackgroundThread = null; 134 | mBackgroundHandler = null; 135 | } catch (InterruptedException e) { 136 | e.printStackTrace(); 137 | } 138 | 139 | } 140 | 141 | private void closeCamera() { 142 | closePreviewSession(); 143 | 144 | if (null != mCameraDevice) { 145 | mCameraDevice.close(); 146 | mCameraDevice = null; 147 | } 148 | 149 | if (null != mImageReader) { 150 | mImageReader.close(); 151 | mImageReader = null; 152 | } 153 | } 154 | 155 | private void closePreviewSession() { 156 | if (null != mCameraCaptureSessions) { 157 | mCameraCaptureSessions.close(); 158 | mCameraCaptureSessions = null; 159 | } 160 | } 161 | 162 | /** 163 | * 开启摄像机线程 164 | */ 165 | private void startBackgroundThread() { 166 | mBackgroundThread = new HandlerThread("Camera Background"); 167 | mBackgroundThread.start(); 168 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 169 | 170 | } 171 | 172 | /** 173 | * 开启摄像头 174 | */ 175 | private void setupCamera() { 176 | Log.e(TAG,"setupCamera START"); 177 | if (mCameraManager == null) { 178 | Log.e(TAG,"尚未得到CameraManager"); 179 | return; 180 | } 181 | try { 182 | //获取相机特征对象 183 | CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId); 184 | //获取相机输出流配置 185 | StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 186 | assert map != null; 187 | //获取预览输出尺寸 188 | mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class),getWidth(),getHeight()); 189 | Log.e(TAG, "setupCamera: best preview size width=" + mPreviewSize.getWidth() 190 | + ",height=" + mPreviewSize.getHeight()); 191 | transformImage(getWidth(),getHeight()); 192 | 193 | if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 194 | return; 195 | } 196 | 197 | setupImageReader(); 198 | mCameraManager.openCamera(mCameraId,stateCallback,null); 199 | } catch (CameraAccessException e) { 200 | e.printStackTrace(); 201 | } 202 | Log.e(TAG,"setupCamera END"); 203 | } 204 | 205 | 206 | private Size getPreferredPreviewSize(Size[] mapSizes, int width, int height) { 207 | Log.e(TAG, "getPreferredPreviewSize: surface width=" + width + ",surface height=" + height); 208 | List collectorSizes = new ArrayList<>(); 209 | for (Size option : mapSizes) { 210 | if (width > height) { 211 | if (option.getWidth() > width && 212 | option.getHeight() > height) { 213 | collectorSizes.add(option); 214 | } 215 | } else { 216 | if (option.getWidth() > height && 217 | option.getHeight() > width) { 218 | collectorSizes.add(option); 219 | } 220 | } 221 | } 222 | if (collectorSizes.size() > 0) { 223 | return Collections.min(collectorSizes, new Comparator() { 224 | @Override 225 | public int compare(Size lhs, Size rhs) { 226 | return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight()); 227 | } 228 | }); 229 | } 230 | Log.e(TAG, "getPreferredPreviewSize: best width=" + 231 | mapSizes[0].getWidth() + ",height=" + mapSizes[0].getHeight()); 232 | return mapSizes[0]; 233 | } 234 | 235 | 236 | private void transformImage(int width, int height) { 237 | Matrix matrix = new Matrix(); 238 | int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation(); 239 | RectF textureRectF = new RectF(0, 0, width, height); 240 | RectF previewRectF = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 241 | float centerX = textureRectF.centerX(); 242 | float centerY = textureRectF.centerY(); 243 | if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { 244 | previewRectF.offset(centerX - previewRectF.centerX(), 245 | centerY - previewRectF.centerY()); 246 | matrix.setRectToRect(textureRectF, previewRectF, Matrix.ScaleToFit.FILL); 247 | float scale = Math.max((float) width / mPreviewSize.getWidth(), 248 | (float) height / mPreviewSize.getHeight()); 249 | matrix.postScale(scale, scale, centerX, centerY); 250 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 251 | } 252 | setTransform(matrix); 253 | } 254 | 255 | private static final int STATE_PREVIEW = 0; 256 | private static final int STATE_RECORD = 1; 257 | private int mState = STATE_PREVIEW; 258 | private AvcEncoder mAvcEncoder; 259 | private int mFrameRate = 30; 260 | 261 | private void setupImageReader() { 262 | //2代表ImageReader中最多可以获取两帧图像流 263 | mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),mPreviewSize.getHeight(), ImageFormat.YV12,1); 264 | mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { 265 | @Override 266 | public void onImageAvailable(ImageReader reader) { 267 | Log.e(TAG, "onImageAvailable: "+Thread.currentThread().getName() ); 268 | //这里一定要调用reader.acquireNextImage()和img.close方法否则不会一直回掉了 269 | Image img = reader.acquireNextImage(); 270 | switch (mState){ 271 | case STATE_PREVIEW: 272 | Log.e(TAG, "mState: STATE_PREVIEW"); 273 | if (mAvcEncoder != null) { 274 | mAvcEncoder.stopThread(); 275 | mAvcEncoder = null; 276 | Toast.makeText(mContext,"停止录制视频成功",Toast.LENGTH_SHORT).show(); 277 | } 278 | break; 279 | case STATE_RECORD: 280 | Log.e(TAG, "mState: STATE_RECORD"); 281 | Image.Plane[] planes = img.getPlanes(); 282 | byte[] dataYUV = null; 283 | if (planes.length >= 3) { 284 | ByteBuffer bufferY = planes[0].getBuffer(); 285 | ByteBuffer bufferU = planes[1].getBuffer(); 286 | ByteBuffer bufferV = planes[2].getBuffer(); 287 | int lengthY = bufferY.remaining(); 288 | int lengthU = bufferU.remaining(); 289 | int lengthV = bufferV.remaining(); 290 | dataYUV = new byte[lengthY + lengthU + lengthV]; 291 | bufferY.get(dataYUV, 0, lengthY); 292 | bufferU.get(dataYUV, lengthY, lengthU); 293 | bufferV.get(dataYUV, lengthY + lengthU, lengthV); 294 | } 295 | 296 | if (mAvcEncoder == null) { 297 | mAvcEncoder = new AvcEncoder(mPreviewSize.getWidth(), 298 | mPreviewSize.getHeight(), mFrameRate, 299 | getOutputMediaFile(MEDIA_TYPE_VIDEO), false); 300 | mAvcEncoder.startEncoderThread(); 301 | Toast.makeText(mContext, "开始录制视频成功", Toast.LENGTH_SHORT).show(); 302 | } 303 | mAvcEncoder.putYUVData(dataYUV); 304 | break; 305 | default: 306 | break; 307 | } 308 | img.close(); 309 | } 310 | },mBackgroundHandler); 311 | } 312 | 313 | /** 314 | * 获取输出照片视频路径 315 | * 316 | * @param mediaType 317 | * @return 318 | */ 319 | public File getOutputMediaFile(int mediaType) { 320 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 321 | String fileName = null; 322 | File storageDir = null; 323 | if (mediaType == MEDIA_TYPE_IMAGE) { 324 | fileName = "JPEG_" + timeStamp + "_"; 325 | storageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES); 326 | } else if (mediaType == MEDIA_TYPE_VIDEO) { 327 | fileName = "MP4_" + timeStamp + "_"; 328 | storageDir = mContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES); 329 | } 330 | 331 | // Create the storage directory if it does not exist 332 | if (!storageDir.exists()) { 333 | if (!storageDir.mkdirs()) { 334 | Log.d(TAG, "failed to create directory"); 335 | return null; 336 | } 337 | } 338 | 339 | File file = null; 340 | try { 341 | file = File.createTempFile( 342 | fileName, /* prefix */ 343 | (mediaType == MEDIA_TYPE_IMAGE) ? ".jpg" : ".h264", /* suffix */ 344 | storageDir /* directory */ 345 | ); 346 | Log.d(TAG, "getOutputMediaFile: absolutePath==" + file.getAbsolutePath()); 347 | } catch (IOException e) { 348 | e.printStackTrace(); 349 | } 350 | return file; 351 | } 352 | 353 | 354 | private void getDefaultCameraId() { 355 | mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 356 | try { 357 | String[] cameraList = mCameraManager.getCameraIdList(); 358 | for (int i = 0;i < cameraList.length;i++) { 359 | String cameraId = cameraList[i]; 360 | if (TextUtils.equals(cameraId,CAMERA_FONT)) { 361 | mCameraId = cameraId; 362 | break; 363 | } else if (TextUtils.equals(cameraId,CAMERA_BACK)) { 364 | mCameraId = cameraId; 365 | break; 366 | } 367 | } 368 | } catch (CameraAccessException e) { 369 | e.printStackTrace(); 370 | } 371 | } 372 | 373 | private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { 374 | @Override 375 | public void onOpened(@NonNull CameraDevice camera) { 376 | Log.e(TAG,"onOpened..."); 377 | mCameraDevice = camera; 378 | createCameraPreview(); 379 | } 380 | 381 | @Override 382 | public void onDisconnected(@NonNull CameraDevice camera) { 383 | mCameraDevice.close(); 384 | } 385 | 386 | @Override 387 | public void onError(@NonNull CameraDevice camera, int error) { 388 | mCameraDevice.close(); 389 | mCameraDevice = null; 390 | } 391 | }; 392 | 393 | /** 394 | * 创建预览界面 395 | */ 396 | private void createCameraPreview() { 397 | try { 398 | Log.e(TAG,"createCameraPreview"); 399 | //获取当前TextureView的SurfaceTexture 400 | SurfaceTexture texture = getSurfaceTexture(); 401 | assert texture != null; 402 | //设置SurfaceTexture默认的缓存区大小,为 上面得到的预览的size大小 403 | texture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight()); 404 | Surface surface = new Surface(texture); 405 | //创建CaptureRequest对象,并且声明类型为TEMPLATE_PREVIEW,可以看出是一个预览类型 406 | 407 | mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 408 | 409 | //CaptureRequest. 410 | //设置请求的结果返回到到Surface上 411 | mPreviewRequestBuilder.addTarget(surface); 412 | 413 | mPreviewRequestBuilder.addTarget(mImageReader.getSurface()); 414 | 415 | //创建CaptureSession对象 416 | mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { 417 | @Override 418 | public void onConfigured(@NonNull CameraCaptureSession session) { 419 | //The camera is already closed 420 | if (null == mCameraDevice) { 421 | return; 422 | } 423 | Log.e(TAG, "onConfigured: "); 424 | // When the session is ready, we start displaying the preview. 425 | mCameraCaptureSessions = session; 426 | //更新预览 427 | updatePreview(); 428 | } 429 | 430 | @Override 431 | public void onConfigureFailed(@NonNull CameraCaptureSession session) { 432 | Toast.makeText(mContext, "Configuration change", Toast.LENGTH_SHORT).show(); 433 | } 434 | },null); 435 | }catch (CameraAccessException e){ 436 | e.printStackTrace(); 437 | } 438 | } 439 | 440 | /** 441 | * 更新预览 442 | */ 443 | private void updatePreview() { 444 | if (null == mCameraDevice) { 445 | Log.e(TAG, "updatePreview error, return"); 446 | } 447 | Log.e(TAG, "updatePreview: "); 448 | //设置相机的控制模式为自动,方法具体含义点进去(auto-exposure, auto-white-balance, auto-focus) 449 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); 450 | try { 451 | //设置重复捕获图片信息 452 | mCameraCaptureSessions.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler); 453 | } catch (CameraAccessException e) { 454 | e.printStackTrace(); 455 | } 456 | } 457 | 458 | public boolean toggleVideo() { 459 | if (mState == STATE_PREVIEW) { 460 | mState = STATE_RECORD; 461 | return true; 462 | } else { 463 | mState = STATE_PREVIEW; 464 | return false; 465 | } 466 | } 467 | 468 | } 469 | -------------------------------------------------------------------------------- /EncodeH264/src/main/java/com/example/encodeh264/EncodeYUVToH264Activity2.java: -------------------------------------------------------------------------------- 1 | package com.example.encodeh264; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.RequiresApi; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.support.v4.content.ContextCompat; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.FrameLayout; 16 | import android.widget.Toast; 17 | 18 | public class EncodeYUVToH264Activity2 extends AppCompatActivity { 19 | private static final String TAG = "EncodeYUVToH262Activity2"; 20 | private static final int REQUEST_CAMERA = 1; 21 | private Camera2Preview camera2Preview; 22 | private Button mRecordBtn; 23 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_encode_yuvto_h264); 28 | mRecordBtn = (Button)findViewById(R.id.record_btn); 29 | requestPermissions(); 30 | } 31 | 32 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 33 | private void requestPermissions() { 34 | if (ContextCompat.checkSelfPermission(this, 35 | Manifest.permission.CAMERA) 36 | != PackageManager.PERMISSION_GRANTED) { 37 | ActivityCompat.requestPermissions(this, 38 | new String[]{Manifest.permission.CAMERA}, 39 | REQUEST_CAMERA); 40 | } else { 41 | initCameraView(); 42 | } 43 | } 44 | 45 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 46 | @Override 47 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 48 | switch (requestCode) { 49 | case REQUEST_CAMERA: { 50 | if (grantResults.length > 0 51 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 52 | initCameraView(); 53 | } else { 54 | Toast.makeText(this, "权限请求失败", Toast.LENGTH_SHORT).show(); 55 | finish(); 56 | } 57 | return; 58 | } 59 | } 60 | } 61 | 62 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 63 | private void initCameraView() { 64 | camera2Preview = new Camera2Preview(this); 65 | FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview); 66 | preview.addView(camera2Preview); 67 | } 68 | 69 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 70 | @Override 71 | protected void onResume() { 72 | super.onResume(); 73 | if (camera2Preview != null) { 74 | camera2Preview.onResume(); 75 | } 76 | } 77 | 78 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 79 | @Override 80 | protected void onPause() { 81 | super.onPause(); 82 | if (camera2Preview != null) { 83 | camera2Preview.onPause(); 84 | } 85 | } 86 | 87 | 88 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 89 | public void toggleVideo(View view) { 90 | if (camera2Preview.toggleVideo()) { 91 | mRecordBtn.setText("停止录制视频"); 92 | } else { 93 | mRecordBtn.setText("开始录制视频"); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /EncodeH264/src/main/java/com/example/encodeh264/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.encodeh264; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | private static final String TAG = "MainActivity"; 11 | private Button btnEncode; 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | btnEncode = (Button) findViewById(R.id.btnEncode); 17 | btnEncode.setOnClickListener(new View.OnClickListener() { 18 | @Override 19 | public void onClick(View v) { 20 | startActivity(new Intent(MainActivity.this,EncodeYUVToH264Activity2.class)); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /EncodeH264/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /EncodeH264/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /EncodeH264/src/main/res/layout/activity_encode_yuvto_h264.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 |