├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── sample.aac │ │ ├── sample.jpg │ │ ├── sample.mp4 │ │ └── sample.pcm │ ├── java │ │ └── com │ │ │ └── richie │ │ │ └── multimedialearning │ │ │ ├── MainActivity.java │ │ │ ├── MultiMediaApp.java │ │ │ ├── SimplePlayerActivity.java │ │ │ ├── audiorecord │ │ │ ├── AudioRecordActivity.java │ │ │ └── AudioRecorder.java │ │ │ ├── audiotrack │ │ │ ├── AudioTrackActivity.java │ │ │ └── AudioTracker.java │ │ │ ├── camera │ │ │ ├── CameraPreviewActivity.java │ │ │ ├── CameraSurfacePreview.java │ │ │ └── CameraTexturePreview.java │ │ │ ├── ffmpeg │ │ │ ├── FFmpegMenuActivity.java │ │ │ └── FFmpegVideoActivity.java │ │ │ ├── media │ │ │ ├── Frame.java │ │ │ ├── IDecoderProgressListener.java │ │ │ ├── decoder │ │ │ │ ├── AudioDecoder.java │ │ │ │ ├── BaseDecoder.java │ │ │ │ ├── DecodeState.java │ │ │ │ ├── DecoderStateListenerAdapter.java │ │ │ │ ├── IDecodeStateListener.java │ │ │ │ ├── IDecoder.java │ │ │ │ └── VideoDecoder.java │ │ │ └── extractor │ │ │ │ ├── AudioExtractor.java │ │ │ │ ├── IExtractor.java │ │ │ │ ├── MMExtractor.java │ │ │ │ └── VideoExtractor.java │ │ │ ├── mediacodec │ │ │ ├── AacPcmCodec.java │ │ │ ├── AudioRecordEncoder.java │ │ │ ├── AvcRgbaCodec.java │ │ │ ├── CameraOpenGlActivity.java │ │ │ ├── CameraRenderer.java │ │ │ ├── CameraSurfaceCodec.java │ │ │ ├── CodecActivity.java │ │ │ ├── CodecUtils.java │ │ │ └── MediaCodecActivity.java │ │ │ ├── muxerextract │ │ │ ├── CameraSurfaceView.java │ │ │ ├── H264Encoder.java │ │ │ └── MediaMuxerExtractActivity.java │ │ │ ├── opengl │ │ │ ├── CameraHolder.java │ │ │ ├── CommonRenderer.java │ │ │ ├── GLESUtils.java │ │ │ ├── ImageRenderer.java │ │ │ ├── OpenGLActivity.java │ │ │ └── TriangleRenderer.java │ │ │ ├── player │ │ │ ├── AndroidMediaPlayer.java │ │ │ ├── BaseMediaPlayer.java │ │ │ ├── ExoMediaPlayer.java │ │ │ ├── MediaPlayerHandler.java │ │ │ └── PcmPlayer.java │ │ │ ├── surface │ │ │ ├── FUGLSurfaceView.java │ │ │ ├── MySurfaceView.java │ │ │ └── SurfaceActivity.java │ │ │ └── utils │ │ │ ├── BarUtils.java │ │ │ ├── BitmapUtils.java │ │ │ ├── CameraUtils.java │ │ │ ├── ConvertUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── LimitFpsUtil.java │ │ │ ├── MediaUtils.java │ │ │ ├── PermissionHelper.java │ │ │ ├── ThreadHelper.java │ │ │ ├── gles │ │ │ ├── EglCore.java │ │ │ ├── EglSurfaceBase.java │ │ │ ├── FlatShadedProgram.java │ │ │ ├── GlUtil.java │ │ │ ├── OffscreenSurface.java │ │ │ ├── WindowSurface.java │ │ │ ├── drawable │ │ │ │ ├── Drawable2d.java │ │ │ │ └── Drawable2dFull.java │ │ │ └── program │ │ │ │ ├── Program.java │ │ │ │ ├── ProgramTexture2d.java │ │ │ │ ├── ProgramTextureOES.java │ │ │ │ └── TextureProgram.java │ │ │ └── wav │ │ │ ├── PcmToWav.java │ │ │ ├── WavUtils.java │ │ │ └── WaveHeader.java │ └── res │ │ ├── layout │ │ ├── activity_audio_record.xml │ │ ├── activity_audio_track.xml │ │ ├── activity_camera_open_gl.xml │ │ ├── activity_camera_preview.xml │ │ ├── activity_codec.xml │ │ ├── activity_ffmpeg_menu.xml │ │ ├── activity_ffmpeg_video.xml │ │ ├── activity_main.xml │ │ ├── activity_media_codec.xml │ │ ├── activity_media_muxur_extract.xml │ │ ├── activity_open_gl.xml │ │ ├── activity_simple_player.xml │ │ └── activity_surface.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 │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── richie │ └── multimedialearning │ ├── EncodeDecodeTest.java │ └── ExampleUnitTest.java ├── build.gradle ├── ffmpeg ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ ├── ffmpeg │ │ └── include │ │ │ ├── libavcodec │ │ │ ├── ac3_parser.h │ │ │ ├── adts_parser.h │ │ │ ├── avcodec.h │ │ │ ├── avdct.h │ │ │ ├── avfft.h │ │ │ ├── bsf.h │ │ │ ├── codec.h │ │ │ ├── codec_desc.h │ │ │ ├── codec_id.h │ │ │ ├── codec_par.h │ │ │ ├── d3d11va.h │ │ │ ├── dirac.h │ │ │ ├── dv_profile.h │ │ │ ├── dxva2.h │ │ │ ├── jni.h │ │ │ ├── mediacodec.h │ │ │ ├── packet.h │ │ │ ├── qsv.h │ │ │ ├── vaapi.h │ │ │ ├── vdpau.h │ │ │ ├── version.h │ │ │ ├── videotoolbox.h │ │ │ ├── vorbis_parser.h │ │ │ └── xvmc.h │ │ │ ├── libavdevice │ │ │ ├── avdevice.h │ │ │ └── version.h │ │ │ ├── libavfilter │ │ │ ├── avfilter.h │ │ │ ├── buffersink.h │ │ │ ├── buffersrc.h │ │ │ └── version.h │ │ │ ├── libavformat │ │ │ ├── avformat.h │ │ │ ├── avio.h │ │ │ └── version.h │ │ │ ├── libavutil │ │ │ ├── adler32.h │ │ │ ├── aes.h │ │ │ ├── aes_ctr.h │ │ │ ├── attributes.h │ │ │ ├── audio_fifo.h │ │ │ ├── avassert.h │ │ │ ├── avconfig.h │ │ │ ├── avstring.h │ │ │ ├── avutil.h │ │ │ ├── base64.h │ │ │ ├── blowfish.h │ │ │ ├── bprint.h │ │ │ ├── bswap.h │ │ │ ├── buffer.h │ │ │ ├── camellia.h │ │ │ ├── cast5.h │ │ │ ├── channel_layout.h │ │ │ ├── common.h │ │ │ ├── cpu.h │ │ │ ├── crc.h │ │ │ ├── des.h │ │ │ ├── dict.h │ │ │ ├── display.h │ │ │ ├── dovi_meta.h │ │ │ ├── downmix_info.h │ │ │ ├── encryption_info.h │ │ │ ├── error.h │ │ │ ├── eval.h │ │ │ ├── ffversion.h │ │ │ ├── fifo.h │ │ │ ├── file.h │ │ │ ├── film_grain_params.h │ │ │ ├── frame.h │ │ │ ├── hash.h │ │ │ ├── hdr_dynamic_metadata.h │ │ │ ├── hmac.h │ │ │ ├── hwcontext.h │ │ │ ├── hwcontext_cuda.h │ │ │ ├── hwcontext_d3d11va.h │ │ │ ├── hwcontext_drm.h │ │ │ ├── hwcontext_dxva2.h │ │ │ ├── hwcontext_mediacodec.h │ │ │ ├── hwcontext_opencl.h │ │ │ ├── hwcontext_qsv.h │ │ │ ├── hwcontext_vaapi.h │ │ │ ├── hwcontext_vdpau.h │ │ │ ├── hwcontext_videotoolbox.h │ │ │ ├── hwcontext_vulkan.h │ │ │ ├── imgutils.h │ │ │ ├── intfloat.h │ │ │ ├── intreadwrite.h │ │ │ ├── lfg.h │ │ │ ├── log.h │ │ │ ├── lzo.h │ │ │ ├── macros.h │ │ │ ├── mastering_display_metadata.h │ │ │ ├── mathematics.h │ │ │ ├── md5.h │ │ │ ├── mem.h │ │ │ ├── motion_vector.h │ │ │ ├── murmur3.h │ │ │ ├── opt.h │ │ │ ├── parseutils.h │ │ │ ├── pixdesc.h │ │ │ ├── pixelutils.h │ │ │ ├── pixfmt.h │ │ │ ├── random_seed.h │ │ │ ├── rational.h │ │ │ ├── rc4.h │ │ │ ├── replaygain.h │ │ │ ├── ripemd.h │ │ │ ├── samplefmt.h │ │ │ ├── sha.h │ │ │ ├── sha512.h │ │ │ ├── spherical.h │ │ │ ├── stereo3d.h │ │ │ ├── tea.h │ │ │ ├── threadmessage.h │ │ │ ├── time.h │ │ │ ├── timecode.h │ │ │ ├── timestamp.h │ │ │ ├── tree.h │ │ │ ├── twofish.h │ │ │ ├── tx.h │ │ │ ├── version.h │ │ │ ├── video_enc_params.h │ │ │ └── xtea.h │ │ │ ├── libswresample │ │ │ ├── swresample.h │ │ │ └── version.h │ │ │ └── libswscale │ │ │ ├── swscale.h │ │ │ └── version.h │ ├── ffmpeg_native.cpp │ ├── log_util.h │ ├── media │ │ ├── const.h │ │ ├── decoder │ │ │ ├── audio │ │ │ │ ├── audio_decoder.cpp │ │ │ │ └── audio_decoder.h │ │ │ ├── base_decoder.cpp │ │ │ ├── base_decoder.h │ │ │ ├── decode_state.h │ │ │ ├── i_decode_state_cb.cpp │ │ │ ├── i_decode_state_cb.h │ │ │ ├── i_decoder.h │ │ │ └── video │ │ │ │ ├── v_decoder.cpp │ │ │ │ └── v_decoder.h │ │ ├── one_frame.h │ │ ├── player │ │ │ └── default │ │ │ │ ├── player.cpp │ │ │ │ └── player.h │ │ └── render │ │ │ ├── audio │ │ │ ├── audio_render.h │ │ │ ├── opensl_render.cpp │ │ │ └── opensl_render.h │ │ │ └── video │ │ │ ├── native │ │ │ ├── native_render.cpp │ │ │ └── native_render.h │ │ │ └── video_render.h │ └── utils │ │ ├── logger.h │ │ └── timer.h │ ├── java │ └── cn │ │ └── richie │ │ └── ffmpeg │ │ └── FFmpegNative.java │ └── jniLibs │ └── armeabi-v7a │ ├── libavcodec.so │ ├── libavdevice.so │ ├── libavfilter.so │ ├── libavformat.so │ ├── libavutil.so │ ├── libswresample.so │ └── libswscale.so ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | ## Android 多媒体学习示例 2 | 3 | 学习 Android 平台上的图像、音频和视频技术。(Learn Android multimedia, including video, audio and graphics.) 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '29.0.3' 6 | 7 | defaultConfig { 8 | applicationId "cn.richie.multimedialearning" 9 | minSdkVersion 21 10 | targetSdkVersion 28 11 | versionCode 100 12 | versionName "1.0.0" 13 | externalNativeBuild { 14 | ndk { 15 | abiFilters "armeabi-v7a" 16 | } 17 | } 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation 'androidx.appcompat:appcompat:1.2.0' 36 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 37 | implementation 'com.google.android.exoplayer:exoplayer-core:2.9.6' 38 | implementation project(path: ':ffmpeg') 39 | testImplementation 'junit:junit:4.13' 40 | implementation 'com.github.isuperqiang:AndEasyLog:2.0.0' 41 | } 42 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 45 | 46 | 49 | 52 | 55 | 56 | 57 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/assets/sample.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isuperqiang/MultiMediaLearning/17636dfbf243185dfbc47bce9ae4f9d9c59d73d4/app/src/main/assets/sample.aac -------------------------------------------------------------------------------- /app/src/main/assets/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isuperqiang/MultiMediaLearning/17636dfbf243185dfbc47bce9ae4f9d9c59d73d4/app/src/main/assets/sample.jpg -------------------------------------------------------------------------------- /app/src/main/assets/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isuperqiang/MultiMediaLearning/17636dfbf243185dfbc47bce9ae4f9d9c59d73d4/app/src/main/assets/sample.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/sample.pcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isuperqiang/MultiMediaLearning/17636dfbf243185dfbc47bce9ae4f9d9c59d73d4/app/src/main/assets/sample.pcm -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/MultiMediaApp.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.richie.easylog.LoggerConfig; 7 | import com.richie.easylog.LoggerFactory; 8 | import com.richie.multimedialearning.utils.FileUtils; 9 | import com.richie.multimedialearning.utils.ThreadHelper; 10 | 11 | /** 12 | * @author Richie on 2018.10.17 13 | */ 14 | public class MultiMediaApp extends Application { 15 | private static Context sContext; 16 | 17 | public static Context getContext() { 18 | return sContext; 19 | } 20 | 21 | @Override 22 | public void onCreate() { 23 | super.onCreate(); 24 | sContext = this; 25 | // 初始化 log 26 | LoggerFactory.init(new LoggerConfig.Builder() 27 | .context(this) 28 | .logcatEnabled(true) 29 | .logLevel(LoggerConfig.VERBOSE) 30 | .build()); 31 | 32 | // 将 assets 下面的所有文件拷贝到外部存储私有目录下 33 | ThreadHelper.getInstance().execute(new Runnable() { 34 | @Override 35 | public void run() { 36 | FileUtils.copyAssetsToFileDir(sContext); 37 | } 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/camera/CameraPreviewActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.camera; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import androidx.constraintlayout.widget.ConstraintLayout; 7 | 8 | import com.richie.multimedialearning.R; 9 | import com.richie.multimedialearning.utils.BarUtils; 10 | 11 | /** 12 | * 预览相机画面 13 | */ 14 | public class CameraPreviewActivity extends AppCompatActivity { 15 | public static final String PREVIEW_TYPE = "preview_type"; 16 | public static final int TYPE_SURFACE_VIEW_CAMERA = 902; 17 | public static final int TYPE_SURFACE_VIEW_CAMERA2 = 932; 18 | public static final int TYPE_TEXTURE_VIEW_CAMERA = 203; 19 | private int mPreviewType; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_camera_preview); 25 | BarUtils.setStatusBarVisibility(this, false); 26 | 27 | mPreviewType = getIntent().getIntExtra(PREVIEW_TYPE, TYPE_SURFACE_VIEW_CAMERA); 28 | previewCamera(); 29 | } 30 | 31 | private void previewCamera() { 32 | ConstraintLayout layout = findViewById(R.id.cl_root); 33 | ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(0, 0); 34 | // 避免相机画面被拉伸 35 | params.dimensionRatio = "9:16"; 36 | params.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID; 37 | params.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID; 38 | params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; 39 | params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; 40 | if (mPreviewType == TYPE_SURFACE_VIEW_CAMERA) { 41 | CameraSurfacePreview cameraPreview = new CameraSurfacePreview(this); 42 | layout.addView(cameraPreview, params); 43 | } else if (mPreviewType == TYPE_TEXTURE_VIEW_CAMERA) { 44 | CameraTexturePreview cameraPreview = new CameraTexturePreview(this); 45 | layout.addView(cameraPreview, params); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/ffmpeg/FFmpegMenuActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.ffmpeg; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | import com.richie.multimedialearning.R; 12 | 13 | import cn.richie.ffmpeg.FFmpegNative; 14 | 15 | public class FFmpegMenuActivity extends AppCompatActivity implements View.OnClickListener { 16 | private static final String TAG = "FFmpegMenuActivity"; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_ffmpeg_menu); 22 | 23 | findViewById(R.id.btn_ffmpeg_video).setOnClickListener(this); 24 | findViewById(R.id.btn_ffmpeg_audio).setOnClickListener(this); 25 | 26 | String version = FFmpegNative.getVersion(); 27 | Log.d(TAG, "onCreate: ffmpeg version: " + version); 28 | Toast.makeText(this, version, Toast.LENGTH_LONG).show(); 29 | } 30 | 31 | @Override 32 | public void onClick(View v) { 33 | int id = v.getId(); 34 | if (id == R.id.btn_ffmpeg_video) { 35 | Intent intent = new Intent(this, FFmpegVideoActivity.class); 36 | startActivity(intent); 37 | } else if (id == R.id.btn_ffmpeg_audio) { 38 | 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/ffmpeg/FFmpegVideoActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.ffmpeg; 2 | 3 | import android.os.Bundle; 4 | import android.view.SurfaceHolder; 5 | import android.view.SurfaceView; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.richie.easylog.ILogger; 10 | import com.richie.easylog.LoggerFactory; 11 | import com.richie.multimedialearning.R; 12 | import com.richie.multimedialearning.utils.FileUtils; 13 | 14 | import java.io.File; 15 | 16 | import cn.richie.ffmpeg.FFmpegNative; 17 | 18 | public class FFmpegVideoActivity extends AppCompatActivity { 19 | private final ILogger logger = LoggerFactory.getLogger(FFmpegVideoActivity.class); 20 | private FFmpegNative mFFmpegNative; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_ffmpeg_video); 26 | mFFmpegNative = new FFmpegNative(); 27 | SurfaceView surfaceView = findViewById(R.id.video_surface); 28 | File dir = FileUtils.getExternalAssetsDir(FFmpegVideoActivity.this); 29 | final File videoFile = new File(dir, "sample.mp4"); 30 | surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { 31 | @Override 32 | public void surfaceCreated(SurfaceHolder holder) { 33 | logger.debug("surfaceCreated() called with: holder = [" + holder + "]"); 34 | mFFmpegNative.createPlayer(videoFile.getAbsolutePath(), holder.getSurface()); 35 | mFFmpegNative.play(); 36 | } 37 | 38 | @Override 39 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 40 | logger.debug("surfaceChanged() called with: holder = [" + holder + "], format = [" + format + "], width = [" + width + "], height = [" + height + "]"); 41 | } 42 | 43 | @Override 44 | public void surfaceDestroyed(SurfaceHolder holder) { 45 | logger.debug("surfaceDestroyed() called with: holder = [" + holder + "]"); 46 | mFFmpegNative.pause(); 47 | mFFmpegNative.releasePlayer(); 48 | } 49 | }); 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/Frame.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media; 2 | 3 | import android.media.MediaCodec; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * 一帧的解码数据 9 | * 10 | * @author Richie on 2020.12.29 11 | */ 12 | public class Frame { 13 | public ByteBuffer mByteBuffer; 14 | private MediaCodec.BufferInfo mBufferInfo; 15 | 16 | public void setBufferInfo(MediaCodec.BufferInfo bufferInfo) { 17 | mBufferInfo.set(bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/IDecoderProgressListener.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media; 2 | 3 | /** 4 | * @author Richie on 2020.12.30 5 | */ 6 | public interface IDecoderProgressListener { 7 | /** 8 | * 视频宽高变化 9 | * 10 | * @param width 11 | * @param height 12 | * @param rotation 13 | */ 14 | void videoSizeChanged(int width, int height, int rotation); 15 | 16 | /** 17 | * 视频播放进度 18 | * 19 | * @param progress 20 | */ 21 | void videoProgressChanged(long progress); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/decoder/DecodeState.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.decoder; 2 | 3 | /** 4 | * 解码状态 5 | * 6 | * @author Richie on 2020.12.29 7 | */ 8 | public enum DecodeState { 9 | /** 10 | * 开始状态 11 | */ 12 | START, 13 | /** 14 | * 解码中 15 | */ 16 | DECODING, 17 | /** 18 | * 解码暂停 19 | */ 20 | PAUSE, 21 | /** 22 | * 正在快进 23 | */ 24 | SEEKING, 25 | /** 26 | * 解码完成 27 | */ 28 | FINISH, 29 | /** 30 | * 解码器释放 31 | */ 32 | STOP 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/decoder/DecoderStateListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.decoder; 2 | 3 | import com.richie.multimedialearning.media.Frame; 4 | 5 | /** 6 | * @author Richie on 2020.12.30 7 | */ 8 | public abstract class DecoderStateListenerAdapter implements IDecodeStateListener { 9 | 10 | @Override 11 | public void decoderPrepare(BaseDecoder decoder) { 12 | 13 | } 14 | 15 | @Override 16 | public void decoderReady(BaseDecoder decoder) { 17 | 18 | } 19 | 20 | @Override 21 | public void decoderPause(BaseDecoder decoder) { 22 | 23 | } 24 | 25 | @Override 26 | public void decoderRunning(BaseDecoder decoder) { 27 | 28 | } 29 | 30 | @Override 31 | public void decoderOneFrame(BaseDecoder decoder, Frame frame) { 32 | 33 | } 34 | 35 | @Override 36 | public void decoderError(BaseDecoder decoder, String message) { 37 | 38 | } 39 | 40 | @Override 41 | public void decoderFinish(BaseDecoder decoder) { 42 | 43 | } 44 | 45 | @Override 46 | public void decoderDestroy(BaseDecoder decoder) { 47 | 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/decoder/IDecodeStateListener.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.decoder; 2 | 3 | import com.richie.multimedialearning.media.Frame; 4 | 5 | /** 6 | * 解码状态回调接口 7 | * 8 | * @author Richie on 2020.12.29 9 | */ 10 | public interface IDecodeStateListener { 11 | void decoderPrepare(BaseDecoder decoder); 12 | 13 | void decoderReady(BaseDecoder decoder); 14 | 15 | void decoderRunning(BaseDecoder decoder); 16 | 17 | void decoderPause(BaseDecoder decoder); 18 | 19 | void decoderOneFrame(BaseDecoder decoder, Frame frame); 20 | 21 | void decoderFinish(BaseDecoder decoder); 22 | 23 | void decoderDestroy(BaseDecoder decoder); 24 | 25 | void decoderError(BaseDecoder decoder, String message); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/decoder/IDecoder.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.decoder; 2 | 3 | import android.media.MediaFormat; 4 | 5 | import com.richie.multimedialearning.media.IDecoderProgressListener; 6 | 7 | /** 8 | * 解码器接口 9 | * 10 | * @author Richie on 2020.12.29 11 | */ 12 | public interface IDecoder extends Runnable { 13 | /** 14 | * 暂停解码 15 | */ 16 | void pause(); 17 | 18 | /** 19 | * 继续解码 20 | */ 21 | void goOn(); 22 | 23 | /** 24 | * 跳转到指定位置,返回实际帧的时间 25 | * 26 | * @param pos 27 | * @return 28 | */ 29 | long seekTo(long pos); 30 | 31 | /** 32 | * 跳转到指定位置并播放,返回实际帧的时间 33 | * 34 | * @param pos 35 | * @return 36 | */ 37 | long seekAndPlay(long pos); 38 | 39 | /** 40 | * 停止解码 41 | */ 42 | void stop(); 43 | 44 | /** 45 | * 是否正在解码 46 | * 47 | * @return 48 | */ 49 | boolean isDecoding(); 50 | 51 | /** 52 | * 是否正在跳转 53 | * 54 | * @return 55 | */ 56 | boolean isSeeking(); 57 | 58 | /** 59 | * 是否停止解码 60 | * 61 | * @return 62 | */ 63 | boolean isStopped(); 64 | 65 | void setSizeListener(IDecoderProgressListener decoderProgressListener); 66 | 67 | void setStateListener(IDecodeStateListener decodeStateListener); 68 | 69 | /** 70 | * 获取宽度 71 | * 72 | * @return 73 | */ 74 | int getWidth(); 75 | 76 | /** 77 | * 获取高度 78 | * 79 | * @return 80 | */ 81 | int getHeight(); 82 | 83 | /** 84 | * 获取时长 85 | * 86 | * @return 87 | */ 88 | long getDuration(); 89 | 90 | /** 91 | * 获取当前帧时间 92 | * 93 | * @return 94 | */ 95 | long getCurrTimeStamp(); 96 | 97 | /** 98 | * 获取旋转角度 99 | * 100 | * @return 101 | */ 102 | int getRotation(); 103 | 104 | /** 105 | * 获取音视频格式 106 | * 107 | * @return 108 | */ 109 | MediaFormat getMediaFormat(); 110 | 111 | /** 112 | * 获取音视频轨道数 113 | * 114 | * @return 115 | */ 116 | int getTrackCount(); 117 | 118 | /** 119 | * 获取解码的文件路径 120 | * 121 | * @return 122 | */ 123 | String getFilePath(); 124 | 125 | /** 126 | * 无需音视频同步 127 | * 128 | * @return 129 | */ 130 | IDecoder withoutSync(); 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/extractor/AudioExtractor.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.extractor; 2 | 3 | import android.media.MediaFormat; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * 音频数据提取器 9 | * 10 | * @author Richie on 2020.12.30 11 | */ 12 | public class AudioExtractor implements IExtractor { 13 | private static final String TAG = "AudioExtractor"; 14 | private final MMExtractor mMMExtractor; 15 | 16 | public AudioExtractor(String filePath) { 17 | mMMExtractor = new MMExtractor(filePath); 18 | } 19 | 20 | @Override 21 | public MediaFormat getFormat() { 22 | return mMMExtractor.getAudioFormat(); 23 | } 24 | 25 | @Override 26 | public int readBuffer(ByteBuffer buffer) { 27 | return mMMExtractor.readBuffer(buffer); 28 | } 29 | 30 | @Override 31 | public long getSampleTimeStamp() { 32 | return mMMExtractor.getSampleTime(); 33 | } 34 | 35 | @Override 36 | public int getSampleFlags() { 37 | return mMMExtractor.getSampleFlags(); 38 | } 39 | 40 | @Override 41 | public long seek(long position) { 42 | return mMMExtractor.seek(position); 43 | } 44 | 45 | @Override 46 | public void setStartPosition(long position) { 47 | mMMExtractor.setStartPosition(position); 48 | } 49 | 50 | @Override 51 | public void stop() { 52 | mMMExtractor.stop(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/extractor/IExtractor.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.extractor; 2 | 3 | import android.media.MediaFormat; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * @author Richie on 2020.12.29 9 | */ 10 | public interface IExtractor { 11 | /** 12 | * 获取格式 13 | * 14 | * @return 15 | */ 16 | MediaFormat getFormat(); 17 | 18 | /** 19 | * 读取数据 20 | * 21 | * @param buffer 22 | * @return 23 | */ 24 | int readBuffer(ByteBuffer buffer); 25 | 26 | /** 27 | * 获取当前帧时间 28 | * 29 | * @return 30 | */ 31 | long getSampleTimeStamp(); 32 | 33 | /** 34 | * 获取当前帧 flag 35 | * 36 | * @return 37 | */ 38 | int getSampleFlags(); 39 | 40 | /** 41 | * 跳转到指定位置,返回实际帧的时间戳 42 | * 43 | * @param position 44 | * @return 45 | */ 46 | long seek(long position); 47 | 48 | /** 49 | * 设置起始位置 50 | * 51 | * @param position 52 | */ 53 | void setStartPosition(long position); 54 | 55 | /** 56 | * 停止读取数据 57 | */ 58 | void stop(); 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/media/extractor/VideoExtractor.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.media.extractor; 2 | 3 | import android.media.MediaFormat; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * 视频数据提取器 9 | * 10 | * @author Richie on 2020.12.30 11 | */ 12 | public class VideoExtractor implements IExtractor { 13 | private static final String TAG = "VideoExtractor"; 14 | private final MMExtractor mMMExtractor; 15 | 16 | public VideoExtractor(String filePath) { 17 | mMMExtractor = new MMExtractor(filePath); 18 | } 19 | 20 | @Override 21 | public MediaFormat getFormat() { 22 | return mMMExtractor.getVideoFormat(); 23 | } 24 | 25 | @Override 26 | public int readBuffer(ByteBuffer buffer) { 27 | return mMMExtractor.readBuffer(buffer); 28 | } 29 | 30 | @Override 31 | public long getSampleTimeStamp() { 32 | return mMMExtractor.getSampleTime(); 33 | } 34 | 35 | @Override 36 | public int getSampleFlags() { 37 | return mMMExtractor.getSampleFlags(); 38 | } 39 | 40 | @Override 41 | public long seek(long position) { 42 | return mMMExtractor.seek(position); 43 | } 44 | 45 | @Override 46 | public void setStartPosition(long position) { 47 | mMMExtractor.setStartPosition(position); 48 | } 49 | 50 | @Override 51 | public void stop() { 52 | mMMExtractor.stop(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/mediacodec/CameraOpenGlActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.mediacodec; 2 | 3 | import android.opengl.GLSurfaceView; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import com.richie.multimedialearning.R; 11 | import com.richie.multimedialearning.opengl.GLESUtils; 12 | 13 | /** 14 | * 使用 OpenGL 预览相机画面,并用 MediaCodec 编码 H264 15 | * 16 | * @author Richie on 2018.10.22 17 | */ 18 | public class CameraOpenGlActivity extends AppCompatActivity implements View.OnClickListener { 19 | private CameraRenderer mCameraRenderer; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_camera_open_gl); 25 | GLSurfaceView glSurfaceView = findViewById(R.id.gl_surface); 26 | glSurfaceView.setEGLContextClientVersion(GLESUtils.getSupportGLVersion(this)); 27 | mCameraRenderer = new CameraRenderer(this, glSurfaceView); 28 | glSurfaceView.setRenderer(mCameraRenderer); 29 | glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 30 | 31 | findViewById(R.id.btn_take_photo).setOnClickListener(this); 32 | findViewById(R.id.btn_record_video).setOnClickListener(this); 33 | } 34 | 35 | @Override 36 | protected void onStart() { 37 | super.onStart(); 38 | mCameraRenderer.onResume(); 39 | } 40 | 41 | @Override 42 | protected void onStop() { 43 | super.onStop(); 44 | mCameraRenderer.onStop(); 45 | } 46 | 47 | @Override 48 | public void onClick(View v) { 49 | switch (v.getId()) { 50 | case R.id.btn_take_photo: { 51 | mCameraRenderer.setTakePhoto(); 52 | } 53 | break; 54 | case R.id.btn_record_video: { 55 | boolean started = v.getTag() != null && (boolean) v.getTag(); 56 | ((Button) v).setText(started ? "录像" : "停止"); 57 | v.setTag(!started); 58 | mCameraRenderer.setRecordVideo(!started); 59 | } 60 | break; 61 | default: 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/opengl/CommonRenderer.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.opengl; 2 | 3 | import android.opengl.GLES20; 4 | import android.opengl.GLSurfaceView; 5 | 6 | import javax.microedition.khronos.egl.EGLConfig; 7 | import javax.microedition.khronos.opengles.GL10; 8 | 9 | /** 10 | * @author Richie on 2019.05.02 11 | */ 12 | public class CommonRenderer implements GLSurfaceView.Renderer { 13 | 14 | @Override 15 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { 16 | // Set the background frame color 17 | GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 18 | } 19 | 20 | @Override 21 | public void onSurfaceChanged(GL10 gl, int width, int height) { 22 | // set the viewport 23 | GLES20.glViewport(0, 0, width, height); 24 | } 25 | 26 | @Override 27 | public void onDrawFrame(GL10 gl) { 28 | // Redraw background color 29 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/opengl/OpenGLActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.opengl; 2 | 3 | import android.opengl.GLSurfaceView; 4 | import android.os.Bundle; 5 | import android.widget.Toast; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | import com.richie.multimedialearning.R; 10 | import com.richie.multimedialearning.utils.FileUtils; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * 使用 OpenGL 绘制 16 | */ 17 | public class OpenGLActivity extends AppCompatActivity { 18 | public static final String TYPE = "draw_type"; 19 | public static final int TYPE_TRIANGLE = 140; 20 | public static final int TYPE_IMAGE = 100; 21 | private GLSurfaceView mGlSurfaceView; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_open_gl); 27 | int glVersion = GLESUtils.getSupportGLVersion(this); 28 | String msg = "支持 GLES " + glVersion; 29 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 30 | 31 | mGlSurfaceView = findViewById(R.id.gl_surface); 32 | // Create an OpenGL ES 2.0 context 33 | mGlSurfaceView.setEGLContextClientVersion(2); 34 | GLSurfaceView.Renderer renderer = null; 35 | int type = getIntent().getIntExtra(TYPE, TYPE_IMAGE); 36 | if (type == TYPE_IMAGE) { 37 | File imageFile = new File(FileUtils.getExternalAssetsDir(this), "sample.jpg"); 38 | renderer = new ImageRenderer(imageFile.getAbsolutePath()); 39 | } else { 40 | renderer = new TriangleRenderer(); 41 | } 42 | // Set the Renderer for drawing on the GLSurfaceView 43 | mGlSurfaceView.setRenderer(renderer); 44 | // Render the view only when there is a change in the drawing data 45 | mGlSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 46 | } 47 | 48 | @Override 49 | protected void onStart() { 50 | super.onStart(); 51 | mGlSurfaceView.onResume(); 52 | } 53 | 54 | @Override 55 | protected void onStop() { 56 | super.onStop(); 57 | mGlSurfaceView.onPause(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/surface/MySurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.surface; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.view.SurfaceHolder; 9 | import android.view.SurfaceView; 10 | 11 | import com.richie.easylog.ILogger; 12 | import com.richie.easylog.LoggerFactory; 13 | import com.richie.multimedialearning.utils.BitmapUtils; 14 | 15 | import java.io.File; 16 | 17 | /** 18 | * 使用 SurfaceView 绘制一张图片 19 | * 20 | * @author Richie on 2018.10.22 21 | */ 22 | public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { 23 | private final ILogger logger = LoggerFactory.getLogger(MySurfaceView.class); 24 | private volatile boolean mIsDrawing; 25 | private SurfaceHolder mSurfaceHolder; 26 | private Paint mPaint; 27 | private Thread mThread; 28 | private Bitmap mBitmap; 29 | 30 | public MySurfaceView(Context context) { 31 | this(context, null); 32 | } 33 | 34 | public MySurfaceView(Context context, AttributeSet attrs) { 35 | this(context, attrs, 0); 36 | } 37 | 38 | public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | init(); 41 | } 42 | 43 | private void init() { 44 | mSurfaceHolder = getHolder(); 45 | mSurfaceHolder.addCallback(this); 46 | 47 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 48 | mPaint.setStyle(Paint.Style.STROKE); 49 | } 50 | 51 | @Override 52 | public void surfaceCreated(SurfaceHolder holder) { 53 | logger.info("onSurfaceCreated"); 54 | mThread = new Thread(this, "Surface-Renderer"); 55 | } 56 | 57 | @Override 58 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 59 | logger.info("onSurfaceChanged. format:{}, width:{}, height:{}", format, width, height); 60 | mBitmap = BitmapUtils.decodeSampledBitmapFromFile(new File(getContext().getExternalFilesDir(null), 61 | "assets/sample.jpg"), width, height); 62 | mIsDrawing = true; 63 | mThread.start(); 64 | } 65 | 66 | @Override 67 | public void surfaceDestroyed(SurfaceHolder holder) { 68 | logger.info("onSurfaceDestroyed"); 69 | mIsDrawing = false; 70 | mThread.interrupt(); 71 | } 72 | 73 | @Override 74 | public void run() { 75 | while (mIsDrawing) { 76 | //logger.verbose("draw canvas"); 77 | Canvas canvas = mSurfaceHolder.lockCanvas(); 78 | if (canvas != null) { 79 | try { 80 | //使用获得的Canvas做具体的绘制 81 | drawCanvas(canvas); 82 | // 睡眠 100ms 83 | Thread.sleep(100); 84 | } catch (Exception e) { 85 | logger.error(e); 86 | } finally { 87 | mSurfaceHolder.unlockCanvasAndPost(canvas); 88 | } 89 | } 90 | } 91 | } 92 | 93 | private void drawCanvas(Canvas canvas) { 94 | canvas.drawBitmap(mBitmap, 0, 0, mPaint); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/surface/SurfaceActivity.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.surface; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import com.richie.multimedialearning.R; 8 | 9 | /** 10 | * 使用 SurfaceView 绘制一张图片 11 | * 12 | * @author Richie on 2018.10.22 13 | */ 14 | public class SurfaceActivity extends AppCompatActivity { 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_surface); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/ConvertUtils.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | 8 | /** 9 | * @author Richie on 2018.11.26 10 | */ 11 | public class ConvertUtils { 12 | 13 | private ConvertUtils() { 14 | } 15 | 16 | public static int fromByteArrayToInt(byte[] bytes) { 17 | return ByteBuffer.wrap(bytes) 18 | .order(ByteOrder.LITTLE_ENDIAN) 19 | .getInt(); 20 | } 21 | 22 | public static short fromByteArrayToShort(byte[] bytes) { 23 | return ByteBuffer.wrap(bytes) 24 | .order(ByteOrder.LITTLE_ENDIAN) 25 | .getShort(); 26 | } 27 | 28 | public static int byteArrayToInt(byte[] bytes) { 29 | return bytes[0] << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | (bytes[3] & 0xFF); 30 | } 31 | 32 | public static byte[] int2ByteArray(int a) { 33 | byte[] ret = new byte[4]; 34 | ret[3] = (byte) (a & 0xFF); 35 | ret[2] = (byte) ((a >> 8) & 0xFF); 36 | ret[1] = (byte) ((a >> 16) & 0xFF); 37 | ret[0] = (byte) ((a >> 24) & 0xFF); 38 | return ret; 39 | } 40 | 41 | public static float convertString2Float(String s) { 42 | float ret = 0f; 43 | if (!TextUtils.isEmpty(s)) { 44 | try { 45 | ret = Float.parseFloat(s); 46 | } catch (NumberFormatException e) { 47 | // ignored 48 | } 49 | } 50 | return ret; 51 | } 52 | 53 | public static int convertString2Integer(String s) { 54 | int ret = 0; 55 | if (!TextUtils.isEmpty(s)) { 56 | try { 57 | ret = Integer.parseInt(s); 58 | } catch (NumberFormatException e) { 59 | // ignored 60 | } 61 | } 62 | return ret; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/LimitFpsUtil.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils; 2 | 3 | import android.os.SystemClock; 4 | import android.util.Log; 5 | 6 | /** 7 | * 帧率限制 8 | * 9 | * @author Richie on 2019.04.13 10 | */ 11 | public final class LimitFpsUtil { 12 | private static final String TAG = "LimitFpsUtil"; 13 | private static long frameStartTimeMs; 14 | private static long frameCount; 15 | private static long startTimeMs; 16 | 17 | private LimitFpsUtil() { 18 | } 19 | 20 | public static void limitFrameRate(int fps) { 21 | long elapsedFrameTimeUs = SystemClock.elapsedRealtime() - frameStartTimeMs; 22 | long expectedFrameTimeUs = 1000 / fps; 23 | long timeToSleepMs = expectedFrameTimeUs - elapsedFrameTimeUs; 24 | if (timeToSleepMs > 0) { 25 | SystemClock.sleep(timeToSleepMs); 26 | } 27 | frameStartTimeMs = SystemClock.elapsedRealtime(); 28 | } 29 | 30 | public static void logFrameRate() { 31 | long elapsedRealtimeMs = SystemClock.elapsedRealtime(); 32 | double elapsedSeconds = (float) (elapsedRealtimeMs - startTimeMs) / 1000; 33 | if (elapsedSeconds >= 1.0) { 34 | int fps = (int) (frameCount / elapsedSeconds); 35 | Log.v(TAG, "logFrameRate: " + fps); 36 | startTimeMs = SystemClock.elapsedRealtime(); 37 | frameCount = 0; 38 | } 39 | frameCount++; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/MediaUtils.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils; 2 | 3 | import android.media.MediaMetadataRetriever; 4 | import android.util.Log; 5 | 6 | /** 7 | * @author Richie on 2020.12.30 8 | */ 9 | public final class MediaUtils { 10 | private static final String TAG = "MediaUtil"; 11 | 12 | public static MediaMetaData retrieveMediaInfo(String path) { 13 | MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); 14 | MediaMetaData mediaMetaData = new MediaMetaData(); 15 | try { 16 | mediaMetadataRetriever.setDataSource(path); 17 | int videoWidth = Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); 18 | int videoHeight = Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); 19 | int videoRotation = Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)); 20 | mediaMetaData.width = videoWidth; 21 | mediaMetaData.height = videoHeight; 22 | mediaMetaData.rotation = videoRotation; 23 | return mediaMetaData; 24 | } catch (Exception e) { 25 | Log.w(TAG, "retrieveMediaInfo: ", e); 26 | } finally { 27 | mediaMetadataRetriever.release(); 28 | } 29 | return mediaMetaData; 30 | } 31 | 32 | public static final class MediaMetaData { 33 | public int width; 34 | public int height; 35 | public int rotation; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/gles/OffscreenSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.richie.multimedialearning.utils.gles; 18 | 19 | /** 20 | * Off-screen EGL surface (pbuffer). 21 | *

22 | * It's good practice to explicitly release() the surface, preferably from a "finally" block. 23 | */ 24 | public class OffscreenSurface extends EglSurfaceBase { 25 | /** 26 | * Creates an off-screen surface with the specified width and height. 27 | */ 28 | public OffscreenSurface(EglCore eglCore, int width, int height) { 29 | super(eglCore); 30 | createOffscreenSurface(width, height); 31 | } 32 | 33 | /** 34 | * Releases any resources associated with the surface. 35 | */ 36 | public void release() { 37 | releaseEglSurface(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/gles/drawable/Drawable2d.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.richie.multimedialearning.utils.gles.drawable; 18 | 19 | import com.richie.multimedialearning.utils.gles.GlUtil; 20 | 21 | import java.nio.FloatBuffer; 22 | 23 | /** 24 | * Base class for stuff we like to draw. 25 | */ 26 | public class Drawable2d { 27 | public static final int COORDS_PER_VERTEX = 2; 28 | private static final int SIZEOF_FLOAT = 4; 29 | public static final int VERTEX_STRIDE = COORDS_PER_VERTEX * SIZEOF_FLOAT; 30 | public static final int TEX_COORD_STRIDE = 2 * SIZEOF_FLOAT; 31 | 32 | private FloatBuffer mVertexArray; 33 | private FloatBuffer mTexCoordArray; 34 | private int mVertexCount; 35 | 36 | public Drawable2d() { 37 | } 38 | 39 | /** 40 | * Prepares a drawable from a "pre-fabricated" shape definition. 41 | *

42 | * Does no EGL/GL operations, so this can be done at any time. 43 | */ 44 | public Drawable2d(float[] vertexArray, float[] texCoordArray) { 45 | updateVertexArray(vertexArray); 46 | updateTexCoordArray(texCoordArray); 47 | } 48 | 49 | /** 50 | * update vertex array and create buffer 51 | * 52 | * @param vertexArray 53 | */ 54 | public void updateVertexArray(float[] vertexArray) { 55 | mVertexArray = GlUtil.createFloatBuffer(vertexArray); 56 | mVertexCount = vertexArray.length / COORDS_PER_VERTEX; 57 | } 58 | 59 | /** 60 | * update texture coordinate array and create buffer 61 | * 62 | * @param texCoordArray 63 | */ 64 | public void updateTexCoordArray(float[] texCoordArray) { 65 | mTexCoordArray = GlUtil.createFloatBuffer(texCoordArray); 66 | } 67 | 68 | /** 69 | * Returns the array of vertices. 70 | *

71 | * To avoid allocations, this returns internal state. The caller must not modify it. 72 | */ 73 | public FloatBuffer getVertexArray() { 74 | return mVertexArray; 75 | } 76 | 77 | /** 78 | * Returns the array of texture coordinates. 79 | *

80 | * To avoid allocations, this returns internal state. The caller must not modify it. 81 | */ 82 | public FloatBuffer getTexCoordArray() { 83 | return mTexCoordArray; 84 | } 85 | 86 | /** 87 | * Returns the number of vertices stored in the vertex array. 88 | */ 89 | public int getVertexCount() { 90 | return mVertexCount; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/gles/drawable/Drawable2dFull.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils.gles.drawable; 2 | 3 | /** 4 | * @author Richie on 2019.05.09 5 | * Draw reatangle 6 | */ 7 | public class Drawable2dFull extends Drawable2d { 8 | /** 9 | * A "full" square, extending from -1 to +1 in both dimensions. When the model/view/projection 10 | * matrix is identity, this will exactly cover the viewport. 11 | *

12 | * The texture coordinates are Y-inverted relative to RECTANGLE. (This seems to work out 13 | * right with external textures from SurfaceTexture.) 14 | */ 15 | private static final float[] FULL_RECTANGLE_COORDS = { 16 | -1.0f, -1.0f, // 0 bottom left 17 | 1.0f, -1.0f, // 1 bottom right 18 | -1.0f, 1.0f, // 2 top left 19 | 1.0f, 1.0f, // 3 top right 20 | }; 21 | private static final float[] FULL_RECTANGLE_TEX_COORDS = { 22 | 0.0f, 0.0f, // 0 bottom left 23 | 1.0f, 0.0f, // 1 bottom right 24 | 0.0f, 1.0f, // 2 top left 25 | 1.0f, 1.0f // 3 top right 26 | }; 27 | 28 | public Drawable2dFull() { 29 | super(FULL_RECTANGLE_COORDS, FULL_RECTANGLE_TEX_COORDS); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/gles/program/Program.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils.gles.program; 2 | 3 | import android.opengl.GLES20; 4 | 5 | import com.richie.multimedialearning.utils.gles.GlUtil; 6 | import com.richie.multimedialearning.utils.gles.drawable.Drawable2d; 7 | 8 | /** 9 | * Base class for GL program 10 | */ 11 | public abstract class Program { 12 | 13 | // Handles to the GL program and various components of it. 14 | protected int mProgramHandle; 15 | protected Drawable2d mDrawable2d; 16 | 17 | public Program(String vertexShader, String fragmentShader) { 18 | mProgramHandle = GlUtil.createProgram(vertexShader, fragmentShader); 19 | mDrawable2d = createDrawable2d(); 20 | glGetLocations(); 21 | } 22 | 23 | /** 24 | * Draw frame in identity mvp matrix 25 | * 26 | * @param textureId 27 | * @param texMatrix 28 | */ 29 | public void drawFrame(int textureId, float[] texMatrix) { 30 | drawFrame(textureId, texMatrix, GlUtil.IDENTITY_MATRIX); 31 | } 32 | 33 | /** 34 | * Draw frame in specified area 35 | * 36 | * @param textureId 37 | * @param texMatrix 38 | * @param mvpMatrix 39 | * @param x viewport x 40 | * @param y viewport y 41 | * @param width viewport width 42 | * @param height viewport height 43 | */ 44 | public void drawFrame(int textureId, float[] texMatrix, float[] mvpMatrix, int x, int y, int width, int height) { 45 | int[] originalViewport = new int[4]; 46 | GLES20.glGetIntegerv(GLES20.GL_VIEWPORT, originalViewport, 0); 47 | GLES20.glViewport(x, y, width, height); 48 | drawFrame(textureId, texMatrix, mvpMatrix); 49 | GLES20.glViewport(originalViewport[0], originalViewport[1], originalViewport[2], originalViewport[3]); 50 | } 51 | 52 | /** 53 | * Releases the program. 54 | *

55 | * The appropriate EGL context must be current (i.e. the one that was used to create 56 | * the program). 57 | */ 58 | public void release() { 59 | GLES20.glDeleteProgram(mProgramHandle); 60 | mProgramHandle = -1; 61 | } 62 | 63 | /** 64 | * Issues the draw call. Does the full setup on every call. 65 | * 66 | * @param textureId texture ID 67 | * @param mvpMatrix The 4x4 projection matrix. 68 | * @param texMatrix A 4x4 transformation matrix for texture coords. (Primarily intended 69 | * for use with SurfaceTexture.) 70 | */ 71 | public abstract void drawFrame(int textureId, float[] texMatrix, float[] mvpMatrix); 72 | 73 | /** 74 | * Create drawable2d that OpenGL will use 75 | * 76 | * @return 77 | */ 78 | protected abstract Drawable2d createDrawable2d(); 79 | 80 | /** 81 | * Call GLES20.glGetXXXLocation method to get location of attributes and uniforms 82 | */ 83 | protected abstract void glGetLocations(); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/wav/WavUtils.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils.wav; 2 | 3 | import com.richie.multimedialearning.utils.ConvertUtils; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | 10 | /** 11 | * @author Richie on 2019.04.16 12 | */ 13 | public final class WavUtils { 14 | 15 | /** 16 | * 检索 WAV 文件的头信息 17 | * 18 | * @param wavFile 19 | * @return 20 | * @throws IOException 21 | */ 22 | public static WaveHeader retrieveHeader(File wavFile) throws IOException { 23 | try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(wavFile))) { 24 | byte[] headerBytes = new byte[44]; 25 | int readLength = bis.read(headerBytes, 0, headerBytes.length); 26 | if (headerBytes.length != readLength) { 27 | // wrong wav file 28 | throw new IOException("Wrong wav format!"); 29 | } 30 | byte[] tempSize2 = new byte[2]; 31 | byte[] tempSize4 = new byte[4]; 32 | WaveHeader waveHeader = new WaveHeader(); 33 | System.arraycopy(headerBytes, 4, tempSize4, 0, tempSize4.length); 34 | waveHeader.ChunkSize = ConvertUtils.fromByteArrayToInt(tempSize4); 35 | System.arraycopy(headerBytes, 16, tempSize4, 0, tempSize4.length); 36 | waveHeader.Subchunk1Size = ConvertUtils.fromByteArrayToInt(tempSize4); 37 | System.arraycopy(headerBytes, 20, tempSize2, 0, tempSize2.length); 38 | waveHeader.AudioFormat = ConvertUtils.fromByteArrayToShort(tempSize2); 39 | System.arraycopy(headerBytes, 22, tempSize2, 0, tempSize2.length); 40 | waveHeader.NumChannels = ConvertUtils.fromByteArrayToShort(tempSize2); 41 | System.arraycopy(headerBytes, 24, tempSize4, 0, tempSize4.length); 42 | waveHeader.SampleRate = ConvertUtils.fromByteArrayToInt(tempSize4); 43 | System.arraycopy(headerBytes, 28, tempSize4, 0, tempSize4.length); 44 | waveHeader.BitsRate = ConvertUtils.fromByteArrayToInt(tempSize4); 45 | System.arraycopy(headerBytes, 32, tempSize2, 0, tempSize2.length); 46 | waveHeader.BlockAlign = ConvertUtils.fromByteArrayToShort(tempSize2); 47 | System.arraycopy(headerBytes, 34, tempSize2, 0, tempSize2.length); 48 | waveHeader.BitsPerSample = ConvertUtils.fromByteArrayToShort(tempSize2); 49 | System.arraycopy(headerBytes, 40, tempSize4, 0, tempSize4.length); 50 | waveHeader.Subchunk2Size = ConvertUtils.fromByteArrayToInt(tempSize4); 51 | return waveHeader; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/richie/multimedialearning/utils/wav/WaveHeader.java: -------------------------------------------------------------------------------- 1 | package com.richie.multimedialearning.utils.wav; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | 6 | /** 7 | * wav文件头 8 | * https://blog.csdn.net/imxiangzi/article/details/80265978 9 | */ 10 | final class WaveHeader { 11 | private static final char ChunkID[] = {'R', 'I', 'F', 'F'}; 12 | private static final char Format[] = {'W', 'A', 'V', 'E'}; 13 | private static final char Subchunk1ID[] = {'f', 'm', 't', ' '}; 14 | private static final char Subchunk2ID[] = {'d', 'a', 't', 'a'}; 15 | public int ChunkSize; // 文件的长度减去RIFF区块ChunkID和ChunkSize的长度 16 | public int Subchunk1Size = 16; // Format区块数据的长度(不包含ID和Size的长度) 17 | public short AudioFormat; // Data区块的音频数据的格式,PCM音频数据的值为1 18 | public short NumChannels; // 音频数据的声道数,1:单声道,2:双声道 19 | public int SampleRate; // 音频数据的采样率 20 | public int BitsPerSample; // 每个采样点存储的bit数,8,16 21 | public int BitsRate; // 每秒数据字节数 = SampleRate * NumChannels * BitsPerSample / 8 22 | public short BlockAlign; // 每个采样点所需的字节数 = NumChannels * BitsPerSample / 8 23 | public int Subchunk2Size; // 音频数据的长度,N = ByteRate * seconds 24 | 25 | byte[] getHeader() throws IOException { 26 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 27 | writeChar(baos, ChunkID); 28 | writeInt(baos, ChunkSize); 29 | writeChar(baos, Format); 30 | writeChar(baos, Subchunk1ID); 31 | writeInt(baos, Subchunk1Size); 32 | writeShort(baos, AudioFormat); 33 | writeShort(baos, NumChannels); 34 | writeInt(baos, SampleRate); 35 | BitsRate = NumChannels * BitsPerSample * SampleRate / 8; 36 | BlockAlign = (short) (NumChannels * BitsPerSample / 8); 37 | writeInt(baos, BitsRate); 38 | writeShort(baos, BlockAlign); 39 | writeShort(baos, BitsPerSample); 40 | writeChar(baos, Subchunk2ID); 41 | writeInt(baos, Subchunk2Size); 42 | baos.flush(); 43 | byte[] bytesHeader = baos.toByteArray(); 44 | baos.close(); 45 | return bytesHeader; 46 | } 47 | 48 | private void writeShort(ByteArrayOutputStream bos, int s) throws IOException { 49 | byte[] buf = new byte[2]; 50 | buf[1] = (byte) ((s << 16) >> 24); 51 | buf[0] = (byte) ((s << 24) >> 24); 52 | bos.write(buf); 53 | } 54 | 55 | private void writeInt(ByteArrayOutputStream bos, int i) throws IOException { 56 | byte[] buf = new byte[4]; 57 | buf[3] = (byte) (i >> 24); 58 | buf[2] = (byte) ((i << 8) >> 24); 59 | buf[1] = (byte) ((i << 16) >> 24); 60 | buf[0] = (byte) ((i << 24) >> 24); 61 | bos.write(buf); 62 | } 63 | 64 | private void writeChar(ByteArrayOutputStream bos, char[] chars) { 65 | for (char c : chars) { 66 | bos.write(c); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_audio_record.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |