diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d19981..cbd46bd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -27,16 +27,6 @@ - - - - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml index 25d2a93..75a35bf 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..40d6833 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# VideoEditor-For-Android +一个Android的视频编辑器,包括了视频录制、剪切、增加bgm、美白、加滤镜、加水印等多种功能 + +基于android硬编码的视频编辑器,不支持4.3以下系统,通过android的api完成视频采集,通过OpenGL,完成视频数据帧的处理,通过android的硬编码器MeidaCodec +对采集到的视频流进行硬编码。 +利用OpenGL完成视频的美白、加滤镜、加水印等功能。利用MediaCodec完成音视频的分离和音频的一些混音处理 + +注:该项目属于是一个半成品项目。并没有直接使用的商业价值。我也看到了很多人提的issues,但是因为作者最近事情比较多,以后会补上剩下的通过OpenGl拼接视频,以及给视频增加bgm等功能,也会解决那些issues。 diff --git a/app/build.gradle b/app/build.gradle index 28209dc..417617e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,6 +15,7 @@ android { cppFlags "" } } + } buildTypes { release { @@ -37,4 +38,5 @@ dependencies { compile 'com.android.support:appcompat-v7:26.0.0-alpha1' testCompile 'junit:junit:4.12' compile 'com.github.bumptech.glide:glide:3.7.0' + compile 'com.android.support:cardview-v7:27.0.2' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a41725..2e4dcab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/assets/shader/base_record_fragment.sh b/app/src/main/assets/shader/base_record_fragment.sh new file mode 100644 index 0000000..8c8fe00 --- /dev/null +++ b/app/src/main/assets/shader/base_record_fragment.sh @@ -0,0 +1,7 @@ +#extension GL_OES_EGL_image_external : require +precision mediump float; +varying vec2 vTextureCoord; +uniform samplerExternalOES sTexture; +void main() { + gl_FragColor = texture2D(sTexture, vTextureCoord); +} \ No newline at end of file diff --git a/app/src/main/assets/shader/base_record_vertex.sh b/app/src/main/assets/shader/base_record_vertex.sh new file mode 100644 index 0000000..4930d9c --- /dev/null +++ b/app/src/main/assets/shader/base_record_vertex.sh @@ -0,0 +1,9 @@ +uniform mat4 uMVPMatrix; +uniform mat4 uSTMatrix; +attribute vec4 aPosition; +attribute vec4 aTextureCoord; +varying vec2 vTextureCoord; +void main() { + gl_Position = uMVPMatrix * aPosition; + vTextureCoord = (uSTMatrix * aTextureCoord).xy; +}; \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index b0d2ee8..dfe8aba 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -72,3 +72,17 @@ Java_com_example_cj_videoeditor_jni_AudioJniUtils_audioMix(JNIEnv *env, jclass t } +extern "C" +JNIEXPORT jstring JNICALL +Java_com_example_cj_videoeditor_jni_AudioJniUtils_putString(JNIEnv *env, jclass type, + jstring info_) { + const char *info = env->GetStringUTFChars(info_, 0); + char buf[128]; + if (info == NULL) + return NULL; + sprintf(buf,"From C %s ",info); + + env->ReleaseStringUTFChars(info_, info); + + return env->NewStringUTF(buf); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cj/videoeditor/Constants.java b/app/src/main/java/com/example/cj/videoeditor/Constants.java index aaaa715..aee14b0 100644 --- a/app/src/main/java/com/example/cj/videoeditor/Constants.java +++ b/app/src/main/java/com/example/cj/videoeditor/Constants.java @@ -9,10 +9,54 @@ /** * Created by cj on 2017/6/26 . - * */ public class Constants { + /** + * 屏幕宽高 + */ + public static int screenWidth; + public static int screenHeight; + + /** + * 画幅,视频的样式 9:16 1:1 16:9 + */ + public static final int MODE_POR_9_16 = 0; + public static final int MODE_POR_1_1 = 1; + public static final int MODE_POR_16_9 = 2; + + /** + * 三种画幅的具体显示尺寸 + */ + public static int mode_por_width_9_16; + public static int mode_por_height_9_16; + public static int mode_por_width_1_1; + public static int mode_por_height_1_1; + public static int mode_por_width_16_9; + public static int mode_por_height_16_9; + + /** + * 三种画幅的具体编码尺寸(参考VUE) + */ + public static final int mode_por_encode_width_9_16 = 540; + public static final int mode_por_encode_height_9_16 = 960; + public static final int mode_por_encode_width_1_1 = 540; + public static final int mode_por_encode_height_1_1 = 540; + public static final int mode_por_encode_width_16_9 = 960; + public static final int mode_por_encode_height_16_9 = 540; + + public static void init(Context context) { + DisplayMetrics mDisplayMetrics = context.getResources() + .getDisplayMetrics(); + screenWidth = mDisplayMetrics.widthPixels; + screenHeight = mDisplayMetrics.heightPixels; + mode_por_width_9_16 = screenWidth; + mode_por_height_9_16 = screenHeight; + mode_por_width_1_1 = screenWidth; + mode_por_height_1_1 = screenWidth; + mode_por_width_16_9 = screenWidth; + mode_por_height_16_9 = screenWidth / 16 * 9; + } public static String getBaseFolder() { String baseFolder = Environment.getExternalStorageDirectory() + "/Codec/"; @@ -25,6 +69,7 @@ public static String getBaseFolder() { } return baseFolder; } + //获取VideoPath public static String getPath(String path, String fileName) { String p = getBaseFolder() + path; diff --git a/app/src/main/java/com/example/cj/videoeditor/CustomBgView.java b/app/src/main/java/com/example/cj/videoeditor/CustomBgView.java deleted file mode 100644 index e184317..0000000 --- a/app/src/main/java/com/example/cj/videoeditor/CustomBgView.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.example.cj.videoeditor; - -import android.content.Context; -import android.graphics.BlurMaskFilter; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.LinearLayout; - -/** - * Created by qqche_000 on 2017/12/10. - * 固定背景的view - */ - -public class CustomBgView extends LinearLayout{ - - private Paint paint; - - public CustomBgView(Context context) { - this(context,null); - } - - public CustomBgView(Context context, @Nullable AttributeSet attrs) { - this(context, attrs,0); - } - - public CustomBgView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - setLayerType(LAYER_TYPE_SOFTWARE,null); - paint = new Paint(); - paint.setColor(Color.parseColor("#aaaaaa")); - paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.SOLID)); - - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - Log.e("hero","---"+getWidth()+"---"+getHeight()); - Rect rect = new Rect(0,0,getWidth(),getHeight()-50); - - canvas.drawRect(rect,paint); - } -} diff --git a/app/src/main/java/com/example/cj/videoeditor/MyApplication.java b/app/src/main/java/com/example/cj/videoeditor/MyApplication.java index 85928bc..d35f9d3 100644 --- a/app/src/main/java/com/example/cj/videoeditor/MyApplication.java +++ b/app/src/main/java/com/example/cj/videoeditor/MyApplication.java @@ -2,26 +2,39 @@ import android.app.Application; import android.content.Context; +import android.os.Handler; import android.util.DisplayMetrics; +import android.util.Log; + +import com.example.cj.videoeditor.media.VideoInfo; +import com.example.cj.videoeditor.mediacodec.VideoRunnable; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; /** - * Created by qqche_000 on 2017/8/6. + * Created by cj on 2017/8/6. + * */ public class MyApplication extends Application{ private static Context mContext; - public static int screenWidth; - public static int screenHeight; + @Override public void onCreate() { super.onCreate(); mContext = this; - DisplayMetrics mDisplayMetrics = getApplicationContext().getResources() - .getDisplayMetrics(); - screenWidth = mDisplayMetrics.widthPixels; - screenHeight = mDisplayMetrics.heightPixels; + Log.e("thread"," 线程值 "+Thread.currentThread()); + Constants.init(this); + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + } public static Context getContext() { diff --git a/app/src/main/java/com/example/cj/videoeditor/MyClassLoader.java b/app/src/main/java/com/example/cj/videoeditor/MyClassLoader.java new file mode 100644 index 0000000..b8a7784 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/MyClassLoader.java @@ -0,0 +1,17 @@ +package com.example.cj.videoeditor; + +/** + * Created by cj on 2018/1/9. + * desc + */ + +public class MyClassLoader extends ClassLoader{ + public MyClassLoader(){ + super(); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return super.findClass(name); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/AudioEditorActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/AudioEditorActivity.java index 3cece9f..1abe521 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/AudioEditorActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/AudioEditorActivity.java @@ -2,6 +2,7 @@ import android.content.Intent; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.Nullable; import android.view.View; import android.widget.Toast; @@ -26,6 +27,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { findViewById(R.id.audio_select).setOnClickListener(this); findViewById(R.id.pcm_to_audio).setOnClickListener(this); findViewById(R.id.audio_mix).setOnClickListener(this); + } @Override @@ -33,7 +35,7 @@ public void onClick(View v) { switch (v.getId()){ case R.id.video_select: //去选择视频 - startActivity(new Intent(AudioEditorActivity.this , VideoSelectActivity.class)); + VideoSelectActivity.openActivity(this); break; case R.id.audio_select: startActivity(new Intent(AudioEditorActivity.this , AudioSelectActivity.class)); diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/AudioMixActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/AudioMixActivity.java index 25acc3f..56d1056 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/AudioMixActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/AudioMixActivity.java @@ -18,7 +18,8 @@ import java.io.IOException; /** - * Created by qqche_000 on 2017/11/19. + * Created by cj on 2017/11/19. + * 音频混音的页面 */ public class AudioMixActivity extends BaseActivity implements View.OnClickListener { diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/AudioSelectActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/AudioSelectActivity.java index 021c350..8b0fb52 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/AudioSelectActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/AudioSelectActivity.java @@ -26,7 +26,7 @@ import java.util.List; /** - * Created by qqche_000 on 2017/11/19. + * Created by cj on 2017/11/19. * 本地音频选择界面 */ @@ -57,7 +57,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) Song song = data.get(position); final String path = song.getPath(); Log.e("hero", "---select audio path " + path); - if (TYPE_EX.equals(type)){ + if (TYPE_EX.equals(type) || TextUtils.isEmpty(type)){ showExDialog(path); }else if (TYPE_MIX.equals(type)){ @@ -72,12 +72,12 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } public void showExDialog(final String path){ AlertDialog.Builder mDialog = new AlertDialog.Builder(AudioSelectActivity.this); - mDialog.setMessage("分离音频还是???"); - mDialog.setPositiveButton("加滤镜", new DialogInterface.OnClickListener() { + mDialog.setMessage("音频转原始音频格式"); + mDialog.setPositiveButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - //跳转预览界面 TODO - + //跳转预览界面 + dialog.dismiss(); } }); mDialog.setNegativeButton("音频转PCM", new DialogInterface.OnClickListener() { @@ -117,16 +117,7 @@ private void initData() { data = new ArrayList<>(); Log.e("hero", "--begin read audio data"); - /* Cursor cursor = getContentResolver().query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, - MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, - MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.MIME_TYPE, - MediaStore.Audio.Media.SIZE, MediaStore.Audio.Media.DATA}, - MediaStore.Audio.Media.MIME_TYPE + "=? or " - + MediaStore.Audio.Media.MIME_TYPE + "=? or " + MediaStore.Audio.Media.MIME_TYPE + "=? or " - + MediaStore.Audio.Media.MIME_TYPE + "=?", - new String[]{"audio/mpeg", "audio/x-ms-wma", "audio/mp4a-latm","audio/raw"}, null);*/ + Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.DURATION, diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/BaseActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/BaseActivity.java index eb268e9..2cfe0be 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/BaseActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/BaseActivity.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.os.Build; import android.os.Looper; +import android.support.v7.app.AppCompatActivity; import com.example.cj.videoeditor.widget.LoadingDialog; @@ -11,7 +12,7 @@ * desc */ -public class BaseActivity extends Activity{ +public class BaseActivity extends AppCompatActivity{ public LoadingDialog loading; @Override diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/MainActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/MainActivity.java index 7e0c80c..b1567da 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/MainActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/MainActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.view.View; import android.widget.Button; +import android.widget.Toast; import com.example.cj.videoeditor.R; @@ -17,11 +18,12 @@ protected void onCreate(Bundle savedInstanceState) { Button recordBtn = (Button) findViewById(R.id.record_activity); Button selectBtn = (Button) findViewById(R.id.select_activity); Button audioBtn = (Button) findViewById(R.id.audio_activity); + Button videoBtn = (Button) findViewById(R.id.video_connect); recordBtn.setOnClickListener(this); selectBtn.setOnClickListener(this); audioBtn.setOnClickListener(this); - + videoBtn.setOnClickListener(this); } @Override @@ -31,11 +33,15 @@ public void onClick(View v) { startActivity(new Intent(MainActivity.this , RecordedActivity.class)); break; case R.id.select_activity: - startActivity(new Intent(MainActivity.this , VideoSelectActivity.class)); + VideoSelectActivity.openActivity(this); break; case R.id.audio_activity: startActivity(new Intent(MainActivity.this , AudioEditorActivity.class)); break; + case R.id.video_connect: +// Toast.makeText(this,"该功能还未完成!!!",Toast.LENGTH_SHORT).show(); + startActivity(new Intent(MainActivity.this , VideoConnectActivity.class)); + break; } } } diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/MediaSelectVideoActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/MediaSelectVideoActivity.java new file mode 100644 index 0000000..a32f28e --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/activity/MediaSelectVideoActivity.java @@ -0,0 +1,147 @@ +package com.example.cj.videoeditor.activity; + +import android.app.Activity; +import android.app.LoaderManager; +import android.content.CursorLoader; +import android.content.Intent; +import android.content.Loader; +import android.database.Cursor; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.view.View; +import android.widget.GridView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.example.cj.videoeditor.R; +import com.example.cj.videoeditor.adapter.VideoSelectAdapter; +import com.example.cj.videoeditor.bean.CutBean; +import com.example.cj.videoeditor.widget.TitleView; + +import java.util.ArrayList; +import java.util.List; + +/** + * 选择视频 + */ +public class MediaSelectVideoActivity extends Activity implements LoaderManager.LoaderCallbacks, VideoSelectAdapter.videoOnSelectChangedListener, View.OnClickListener { + GridView gridview; + TitleView title; + + TextView tv_title_bar_right_text; // 右边文字 + + VideoSelectAdapter mMediaAdapter; + + public static final String PROJECT_VIDEO = MediaStore.MediaColumns._ID; + + + int max_size = -1;// 最大size + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_media_select_video); + gridview = (GridView) findViewById(R.id.gridview_media_video); + title = (TitleView) findViewById(R.id.title_media_video); + tv_title_bar_right_text = (TextView) findViewById(R.id.tv_title_bar_right_text); + tv_title_bar_right_text.setOnClickListener(this); + initView(); + initData(); + + } + + private void initView() { + tv_title_bar_right_text.setTextColor(getResources().getColor(R.color.iTextColor5)); + } + + private void initData() { + max_size = getIntent().getIntExtra("max_size", -1); + + getLoaderManager().initLoader(0, null, this); + title.setBtnLeftOnClick(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + String order = MediaStore.MediaColumns.DATE_ADDED + " DESC"; + return new CursorLoader(getApplicationContext(), videoUri, new String[]{MediaStore.Video.Media.DATA, PROJECT_VIDEO}, null, null, order); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + + if (data == null || data.getCount() <= 0) { + return; + } + if (mMediaAdapter == null) { + + mMediaAdapter = new VideoSelectAdapter(getApplicationContext(), data); + mMediaAdapter.setMediaSelectVideoActivity(this); + mMediaAdapter.setOnSelectChangedListener(this); + mMediaAdapter.setMaxSize(max_size); + } else { + mMediaAdapter.swapCursor(data); + } + + + if (gridview.getAdapter() == null) { + gridview.setAdapter(mMediaAdapter); + } + mMediaAdapter.notifyDataSetChanged(); + } + + @Override + public void onLoaderReset(Loader loader) { + if (mMediaAdapter != null) + mMediaAdapter.swapCursor(null); + } + + @Override + protected void onDestroy() { + getLoaderManager().destroyLoader(0); + Glide.get(this).clearMemory(); + super.onDestroy(); + } + + List pathList;//临时保存 + List coverList; + + @Override + public void changed(List pathList, List coverList) { + if (pathList.isEmpty() && coverList.isEmpty()) { + tv_title_bar_right_text.setClickable(false); + tv_title_bar_right_text.setTextColor(getResources().getColor(R.color.iTextColor5)); + title.setTvRightNumVisibile(View.INVISIBLE); + } else { + tv_title_bar_right_text.setClickable(true); + tv_title_bar_right_text.setTextColor(getResources().getColor(R.color.iTextColor6)); + this.pathList = pathList; + this.coverList = coverList; + title.setTvRightNum(pathList.size() + ""); + } + + } + + // 提交 + @Override + public void onClick(View v) { + if ((pathList == null || pathList.isEmpty()) && (coverList == null || coverList.isEmpty())) { + Toast.makeText(this, "不选视频是要怎样", Toast.LENGTH_SHORT).show(); + return; + } + + Intent intent = new Intent(this, VideoConnectActivity.class); + intent.putStringArrayListExtra("path", (ArrayList) pathList); + startActivity(intent); + finish(); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/PreviewActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/PreviewActivity.java index dd8f5a8..41ab851 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/PreviewActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/PreviewActivity.java @@ -6,6 +6,7 @@ import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; @@ -186,6 +187,7 @@ public void onFinish() { } }); try { + Log.e("hero","-----PreviewActivity---clipVideo"); clipper.clipVideo(0,mVideoView.getVideoDuration()*1000); } catch (IOException e) { e.printStackTrace(); diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/RecordedActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/RecordedActivity.java index 1cd4619..7b82543 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/RecordedActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/RecordedActivity.java @@ -13,9 +13,8 @@ import android.widget.Toast; import com.example.cj.videoeditor.Constants; -import com.example.cj.videoeditor.MyApplication; import com.example.cj.videoeditor.R; -import com.example.cj.videoeditor.camera.SensorControler; +import com.example.cj.videoeditor.camera.SensorController; import com.example.cj.videoeditor.gpufilter.SlideGpuFilterGroup; import com.example.cj.videoeditor.gpufilter.helper.MagicFilterType; import com.example.cj.videoeditor.widget.CameraView; @@ -31,7 +30,7 @@ * 主要包括 音视频录制、断点续录、对焦等功能 */ -public class RecordedActivity extends BaseActivity implements View.OnClickListener, View.OnTouchListener, SensorControler.CameraFocusListener, SlideGpuFilterGroup.OnFilterChangeListener { +public class RecordedActivity extends BaseActivity implements View.OnClickListener, View.OnTouchListener, SensorController.CameraFocusListener, SlideGpuFilterGroup.OnFilterChangeListener { private CameraView mCameraView; private CircularProgressView mCapture; @@ -43,20 +42,18 @@ public class RecordedActivity extends BaseActivity implements View.OnClickListen private boolean pausing = false; private boolean recordFlag = false;//是否正在录制 - private int WIDTH = 720,HEIGHT = 1280; - private long timeStep = 50;//进度条刷新的时间 long timeCount = 0;//用于记录录制时间 private boolean autoPausing = false; ExecutorService executorService; - private SensorControler mSensorControler; + private SensorController mSensorControler; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recorde); executorService = Executors.newSingleThreadExecutor(); - mSensorControler = SensorControler.getInstance(); + mSensorControler = SensorController.getInstance(); mSensorControler.setCameraFocusListener(this); initView(); } @@ -87,10 +84,10 @@ public boolean onTouch(View v, MotionEvent event) { case MotionEvent.ACTION_UP: float sRawX = event.getRawX(); float sRawY = event.getRawY(); - float rawY = sRawY * MyApplication.screenWidth / MyApplication.screenHeight; + float rawY = sRawY * Constants.screenWidth / Constants.screenHeight; float temp = sRawX; float rawX = rawY; - rawY = (MyApplication.screenWidth - temp) * MyApplication.screenHeight / MyApplication.screenWidth; + rawY = (Constants.screenWidth - temp) * Constants.screenHeight / Constants.screenWidth; Point point = new Point((int) rawX, (int) rawY); mCameraView.onFocus(point, callback); @@ -117,7 +114,7 @@ public void onFocus() { if (mCameraView.getCameraId() == 1) { return; } - Point point = new Point(MyApplication.screenWidth / 2, MyApplication.screenHeight / 2); + Point point = new Point(Constants.screenWidth / 2, Constants.screenHeight / 2); mCameraView.onFocus(point, callback); } @Override diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/VideoConnectActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/VideoConnectActivity.java new file mode 100644 index 0000000..53216d3 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/activity/VideoConnectActivity.java @@ -0,0 +1,127 @@ +package com.example.cj.videoeditor.activity; + +import android.content.Intent; +import android.media.MediaMetadataRetriever; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.example.cj.videoeditor.Constants; +import com.example.cj.videoeditor.R; +import com.example.cj.videoeditor.media.VideoInfo; +import com.example.cj.videoeditor.mediacodec.MediaMuxerRunnable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by cj on 2018/6/3. + * 视频拼接的类 + */ + +public class VideoConnectActivity extends BaseActivity implements View.OnClickListener { + private TextView mPathOne; + private TextView mPathTwo; + private String path1; + private String path2; + + private ArrayList mInfoList = new ArrayList<>(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_video_connect); + findViewById(R.id.select_one).setOnClickListener(this); + findViewById(R.id.select_two).setOnClickListener(this); + findViewById(R.id.video_connect).setOnClickListener(this); + + mPathOne = (TextView) findViewById(R.id.path_one); + mPathTwo = (TextView) findViewById(R.id.path_two); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.select_one: + VideoSelectActivity.openActivityForResult(this, 100); + break; + case R.id.select_two: + VideoSelectActivity.openActivityForResult(this, 101); + break; + case R.id.video_connect: + if (TextUtils.isEmpty(path1) || TextUtils.isEmpty(path2)) { + Toast.makeText(this, "请先选择视频", Toast.LENGTH_SHORT).show(); + return; + } + String[] data = {path1, path2}; + setDataSource(data); + + + break; + } + } + private String outputPath; + public void setDataSource(String[] dataSource) { + + MediaMetadataRetriever retr = new MediaMetadataRetriever(); + mInfoList.clear(); + for (int i = 0; i < dataSource.length; i++) { + VideoInfo info = new VideoInfo(); + String path = dataSource[i]; + retr.setDataSource(path); + String rotation = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + String width = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + String height = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + String duration = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + + info.path = path; + info.rotation = Integer.parseInt(rotation); + info.width = Integer.parseInt(width); + info.height = Integer.parseInt(height); + info.duration = Integer.parseInt(duration); + + mInfoList.add(info); + } + outputPath = Constants.getPath("video/output/", System.currentTimeMillis() + ""); + showLoading("视频拼接中"); + MediaMuxerRunnable instance = new MediaMuxerRunnable(); + instance.setVideoInfo(mInfoList, outputPath); + instance.addMuxerListener(new MediaMuxerRunnable.MuxerListener() { + @Override + public void onStart() { + + } + + @Override + public void onFinish() { + runOnUiThread(new Runnable() { + @Override + public void run() { + endLoading(); + Toast.makeText(VideoConnectActivity.this," 拼接完成 文件地址 "+outputPath,Toast.LENGTH_SHORT).show(); + } + }); + } + }); + instance.start(); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (data != null) { + if (requestCode == 100) { + path1 = data.getStringExtra("path"); + mPathOne.setText(path1); + } else if (requestCode == 101) { + path2 = data.getStringExtra("path"); + mPathTwo.setText(path2); + } + } + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/activity/VideoSelectActivity.java b/app/src/main/java/com/example/cj/videoeditor/activity/VideoSelectActivity.java index b80dd04..b89ee01 100644 --- a/app/src/main/java/com/example/cj/videoeditor/activity/VideoSelectActivity.java +++ b/app/src/main/java/com/example/cj/videoeditor/activity/VideoSelectActivity.java @@ -1,8 +1,10 @@ package com.example.cj.videoeditor.activity; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.LoaderManager; +import android.content.Context; import android.content.CursorLoader; import android.content.DialogInterface; import android.content.Intent; @@ -32,10 +34,26 @@ */ public class VideoSelectActivity extends BaseActivity implements LoaderManager.LoaderCallbacks,VideoAdapter.OnVideoSelectListener { + public static final int TYPE_SHOW_DIALOG = 1; + public static final int TYPE_BACK_PATH = 2; + ImageView ivClose; GridView gridview; public static final String PROJECT_VIDEO = MediaStore.MediaColumns._ID; private VideoAdapter mVideoAdapter; + private int pageType = TYPE_SHOW_DIALOG; + + public static void openActivity(Context context){ + Intent intent = new Intent(context,VideoSelectActivity.class); + intent.putExtra("type",TYPE_SHOW_DIALOG); + context.startActivity(intent); + } + + public static void openActivityForResult(Activity context,int requestCodde){ + Intent intent = new Intent(context,VideoSelectActivity.class); + intent.putExtra("type",TYPE_BACK_PATH); + context.startActivityForResult(intent,requestCodde); + } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -55,6 +73,9 @@ public void onClick(View v) { gridview=(GridView)findViewById(R.id.gridview_media_video); } private void initData() { + pageType = getIntent().getIntExtra("type", TYPE_SHOW_DIALOG); + + getLoaderManager().initLoader(0,null,this); } @@ -111,7 +132,7 @@ public void onSelect(final String path, String cover) { if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) { videoTrack=i; String videoMime = format.getString(MediaFormat.KEY_MIME); - if(!"video/avc".equals(videoMime)){ + if(!MediaFormat.MIMETYPE_VIDEO_AVC.equals(videoMime) ){ Toast.makeText(this,"视频格式不支持",Toast.LENGTH_SHORT).show(); return; } @@ -120,7 +141,7 @@ public void onSelect(final String path, String cover) { if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) { audioTrack=i; String audioMime = format.getString(MediaFormat.KEY_MIME); - if(!"audio/mp4a-latm".equals(audioMime)){ + if(!MediaFormat.MIMETYPE_AUDIO_AAC.equals(audioMime)){ Toast.makeText(this,"视频格式不支持",Toast.LENGTH_SHORT).show(); return; } @@ -138,12 +159,19 @@ public void onSelect(final String path, String cover) { Toast.makeText(this,"视频格式不支持",Toast.LENGTH_SHORT).show(); return; } + if (pageType == TYPE_BACK_PATH){ + Intent intent = getIntent(); + intent.putExtra("path",path); + setResult(0,intent); + finish(); + return; + } AlertDialog.Builder mDialog = new AlertDialog.Builder(this); mDialog.setMessage("去分离音频还是添加滤镜"); mDialog.setPositiveButton("加滤镜", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - //跳转预览界面 TODO + //跳转预览界面 if(!TextUtils.isEmpty(path)){ Intent intent=new Intent(VideoSelectActivity.this,PreviewActivity.class); intent.putExtra("path",path); diff --git a/app/src/main/java/com/example/cj/videoeditor/adapter/VideoSelectAdapter.java b/app/src/main/java/com/example/cj/videoeditor/adapter/VideoSelectAdapter.java new file mode 100644 index 0000000..295a0c1 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/adapter/VideoSelectAdapter.java @@ -0,0 +1,152 @@ +package com.example.cj.videoeditor.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.example.cj.videoeditor.MyApplication; +import com.example.cj.videoeditor.R; +import com.example.cj.videoeditor.activity.MediaSelectVideoActivity; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 本地视频列表 + */ +public class VideoSelectAdapter extends CursorAdapter { + MediaSelectVideoActivity activity; + + List coverList=new ArrayList(); + List pathList=new ArrayList(); + + int maxSize = -1; // 最大size + + public VideoSelectAdapter(Context context, Cursor c) { + super(context, c); + } + + public VideoSelectAdapter(Context context, Cursor c, boolean autoRequery) { + super(context, c, autoRequery); + } + + public VideoSelectAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + } + + public void setMediaSelectVideoActivity(MediaSelectVideoActivity activity) { + this.activity = activity; + } + + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + ViewHolder holder = new ViewHolder(); + View inflate = View.inflate(context, R.layout.item_media_video, null); + holder.pic = (ImageView) inflate.findViewById(R.id.iv_media_video); + holder.is_true = (ImageView) inflate.findViewById(R.id.is_true); + inflate.setTag(holder); + return inflate; + } + + @Override + public void bindView(View view, final Context context, Cursor cursor) { + final ViewHolder holder = (ViewHolder) view.getTag(); + final Uri uri = getUri(cursor); + final String path = cursor.getString(cursor + .getColumnIndex(MediaStore.Video.Media.DATA)); + + holder.is_true.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // 判断是否超过大小 (粉丝发的大小有限制) + if (isToBigger(path)) { + Toast.makeText(MyApplication.getContext(),"选择的视频不能超过" + maxSize + "M~",Toast.LENGTH_SHORT).show(); + return; + } + + if (coverList.contains(uri.toString()) && pathList.contains(path)) { + coverList.remove(uri.toString()); + pathList.remove(path); + holder.is_true.setImageResource(R.mipmap.icon_choice_nor); + } else { +// if (TextUtils.isEmpty(tempCover) && TextUtils.isEmpty(tempPath)) { +// tempCover = uri.toString(); +// tempPath = path; +// holder.is_true.setImageResource(R.mipmap.icon_choice_selected); +// } else { +// Toast.makeText(context,context.getString(R.string.video_select),Toast.LENGTH_SHORT).show(); +// } + coverList.add(uri.toString()); + pathList.add(path); + holder.is_true.setImageResource(R.mipmap.icon_choice_selected); + } + if (listener != null) { + listener.changed(pathList, coverList); + } + } + }); + holder.is_true.setImageResource(pathList.contains(path) ? R.mipmap.icon_choice_selected : R.mipmap.icon_choice_nor); + + Glide.with(context) + .load(uri) + .placeholder(R.mipmap.editor_img_def_video) + .error(R.mipmap.editor_img_def_video) + .crossFade() + .into(holder.pic); + } + + public Uri getUri(Cursor cursor) { + String id = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); + return Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); + } + @Override + public Object getItem(int position) { + return super.getItem(position); + } + + class ViewHolder { + public ImageView pic; + public ImageView is_true; + } + + public void setOnSelectChangedListener(videoOnSelectChangedListener listener) { + this.listener = listener; + } + + videoOnSelectChangedListener listener; + + public interface videoOnSelectChangedListener { + void changed(List pathList, List coverList); + } + + + private boolean isToBigger(String path) { + if (maxSize == -1) { //没有限制 + return false; + } + try { + File file = new File(path); + long length = file.length(); + return length > maxSize * 1024 * 1024; + } catch (Exception e) { + return false; + } + + + } + +} diff --git a/app/src/main/java/com/example/cj/videoeditor/bean/AudioSettingInfo.java b/app/src/main/java/com/example/cj/videoeditor/bean/AudioSettingInfo.java new file mode 100644 index 0000000..40b3a4a --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/bean/AudioSettingInfo.java @@ -0,0 +1,13 @@ +package com.example.cj.videoeditor.bean; + +/** + * Created by cj on 2017/6/28. + * desc + */ + +public class AudioSettingInfo { + public boolean isSet;//是否设置过 + public String filePath;//bgm的文件路径 + public int volFirst;//原生音量大小 + public int volSecond;//bgm大小 +} diff --git a/app/src/main/java/com/example/cj/videoeditor/bean/CutBean.java b/app/src/main/java/com/example/cj/videoeditor/bean/CutBean.java new file mode 100644 index 0000000..9cee221 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/bean/CutBean.java @@ -0,0 +1,56 @@ +package com.example.cj.videoeditor.bean; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by cj on 2017/7/10. + * desc 以video的path和position为关键key + * 记录 当前视频剪切的选择参数 + * 便于后面剪切视频 + */ + +public class CutBean implements Parcelable { + public int position;//这个选择参数 在当前视频集里的位置 + public String videoPath; + public long startPoint;//开始剪切的时间点 + public long cutDuration;//剪切的时长 + public long videoDuration;//video的总长度 + public CutBean(){ + + } + + protected CutBean(Parcel in) { + position = in.readInt(); + videoPath = in.readString(); + startPoint = in.readLong(); + cutDuration = in.readLong(); + videoDuration = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public CutBean createFromParcel(Parcel in) { + return new CutBean(in); + } + + @Override + public CutBean[] newArray(int size) { + return new CutBean[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(position); + dest.writeString(videoPath); + dest.writeLong(startPoint); + dest.writeLong(cutDuration); + dest.writeLong(videoDuration); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/bean/MediaDecode.java b/app/src/main/java/com/example/cj/videoeditor/bean/MediaDecode.java new file mode 100644 index 0000000..bc4266c --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/bean/MediaDecode.java @@ -0,0 +1,18 @@ +package com.example.cj.videoeditor.bean; + +import android.media.MediaExtractor; + +/** + * Created by cj on 2017/7/11. + * desc 音频解码的info类 包含了音频path 音频的MediaExtractor + * 和本段音频的截取点cutPoint + * 以及剪切时长 cutDuration + */ + +public class MediaDecode { + public String path; + public MediaExtractor extractor; + public int cutPoint; + public int cutDuration; + public int duration; +} diff --git a/app/src/main/java/com/example/cj/videoeditor/camera/CameraController.java b/app/src/main/java/com/example/cj/videoeditor/camera/CameraController.java index 2cb13ea..896e5b7 100644 --- a/app/src/main/java/com/example/cj/videoeditor/camera/CameraController.java +++ b/app/src/main/java/com/example/cj/videoeditor/camera/CameraController.java @@ -6,6 +6,7 @@ import android.hardware.Camera; import android.util.Log; +import com.example.cj.videoeditor.Constants; import com.example.cj.videoeditor.MyApplication; import java.io.IOException; @@ -20,53 +21,54 @@ * 包括预览和录制尺寸、闪光灯、曝光、聚焦、摄像头切换等 */ -public class CameraController implements ICamera{ - /**相机的宽高及比例配置*/ +public class CameraController implements ICamera { + /** + * 相机的宽高及比例配置 + */ private ICamera.Config mConfig; - /**相机实体*/ + /** + * 相机实体 + */ private Camera mCamera; - /**预览的尺寸*/ + /** + * 预览的尺寸 + */ private Camera.Size preSize; - /**实际的尺寸*/ + /** + * 实际的尺寸 + */ private Camera.Size picSize; - private Point mPreSize ; - private Point mPicSize ; - - public CameraController(){ - /**初始化一个默认的格式大小*/ + public CameraController() { + /*初始化一个默认的格式大小*/ mConfig = new ICamera.Config(); - mConfig.minPreviewWidth=720; - mConfig.minPictureWidth=720; - mConfig.rate=1.778f; + mConfig.minPreviewWidth = 720; + mConfig.minPictureWidth = 720; + mConfig.rate = 1.778f; } public void open(int cameraId) { mCamera = Camera.open(cameraId); - if (mCamera != null){ - /**选择当前设备允许的预览尺寸*/ + if (mCamera != null) { + /*选择当前设备允许的预览尺寸*/ Camera.Parameters param = mCamera.getParameters(); preSize = getPropPreviewSize(param.getSupportedPreviewSizes(), mConfig.rate, mConfig.minPreviewWidth); - picSize = getPropPictureSize(param.getSupportedPictureSizes(),mConfig.rate, + picSize = getPropPictureSize(param.getSupportedPictureSizes(), mConfig.rate, mConfig.minPictureWidth); param.setPictureSize(picSize.width, picSize.height); - param.setPreviewSize(preSize.width,preSize.height); + param.setPreviewSize(preSize.width, preSize.height); mCamera.setParameters(param); - Camera.Size pre=param.getPreviewSize(); - Camera.Size pic=param.getPictureSize(); - mPicSize=new Point(pic.height,pic.width); - mPreSize=new Point(pre.height,pre.width); } } @Override public void setPreviewTexture(SurfaceTexture texture) { - if(mCamera!=null){ + if (mCamera != null) { try { - Log.e("hero","----setPreviewTexture"); + Log.e("hero", "----setPreviewTexture"); mCamera.setPreviewTexture(texture); } catch (IOException e) { e.printStackTrace(); @@ -76,16 +78,16 @@ public void setPreviewTexture(SurfaceTexture texture) { @Override public void setConfig(Config config) { - this.mConfig=config; + this.mConfig = config; } @Override public void setOnPreviewFrameCallback(final PreviewFrameCallback callback) { - if(mCamera!=null){ + if (mCamera != null) { mCamera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { - callback.onPreviewFrame(data,mPreSize.x,mPreSize.y); + callback.onPreviewFrame(data, preSize.width, preSize.height); } }); } @@ -93,24 +95,19 @@ public void onPreviewFrame(byte[] data, Camera camera) { @Override public void preview() { - if (mCamera != null){ + if (mCamera != null) { mCamera.startPreview(); } } @Override - public Point getPreviewSize() { - return mPreSize; - } - - @Override - public Point getPictureSize() { - return mPicSize; + public Camera.Size getPreviewSize() { + return preSize; } @Override public boolean close() { - if (mCamera != null){ + if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; @@ -125,20 +122,20 @@ public boolean close() { */ public void onFocus(Point point, Camera.AutoFocusCallback callback) { Camera.Parameters parameters = mCamera.getParameters(); - boolean supportFocus=true; - boolean supportMetering=true; + boolean supportFocus = true; + boolean supportMetering = true; //不支持设置自定义聚焦,则使用自动聚焦,返回 if (parameters.getMaxNumFocusAreas() <= 0) { - supportFocus=false; + supportFocus = false; } - if (parameters.getMaxNumMeteringAreas() <= 0){ - supportMetering=false; + if (parameters.getMaxNumMeteringAreas() <= 0) { + supportMetering = false; } List areas = new ArrayList(); List areas1 = new ArrayList(); //再次进行转换 - point.x= (int) (((float)point.x)/ MyApplication.screenWidth*2000-1000); - point.y= (int) (((float)point.y)/MyApplication.screenHeight*2000-1000); + point.x = (int) (((float) point.x) / Constants.screenWidth * 2000 - 1000); + point.y = (int) (((float) point.y) / Constants.screenHeight * 2000 - 1000); int left = point.x - 300; int top = point.y - 300; @@ -150,10 +147,10 @@ public void onFocus(Point point, Camera.AutoFocusCallback callback) { bottom = bottom > 1000 ? 1000 : bottom; areas.add(new Camera.Area(new Rect(left, top, right, bottom), 100)); areas1.add(new Camera.Area(new Rect(left, top, right, bottom), 100)); - if(supportFocus){ + if (supportFocus) { parameters.setFocusAreas(areas); } - if(supportMetering){ + if (supportMetering) { parameters.setMeteringAreas(areas1); } @@ -165,50 +162,54 @@ public void onFocus(Point point, Camera.AutoFocusCallback callback) { } } - private Camera.Size getPropPictureSize(List list, float th, int minWidth){ + + private Camera.Size getPropPictureSize(List list, float th, int minWidth) { Collections.sort(list, sizeComparator); int i = 0; - for(Camera.Size s:list){ - if((s.height >= minWidth) && equalRate(s, th)){ + for (Camera.Size s : list) { + if ((s.height >= minWidth) && equalRate(s, th)) { break; } i++; } - if(i == list.size()){ + if (i == list.size()) { i = 0; } return list.get(i); } - private Camera.Size getPropPreviewSize(List list, float th, int minWidth){ + + private Camera.Size getPropPreviewSize(List list, float th, int minWidth) { Collections.sort(list, sizeComparator); int i = 0; - for(Camera.Size s:list){ - if((s.height >= minWidth) && equalRate(s, th)){ + for (Camera.Size s : list) { + if ((s.height >= minWidth) && equalRate(s, th)) { break; } i++; } - if(i == list.size()){ + if (i == list.size()) { i = 0; } return list.get(i); } - private static boolean equalRate(Camera.Size s, float rate){ - float r = (float)(s.width)/(float)(s.height); - if(Math.abs(r - rate) <= 0.03) { + + private static boolean equalRate(Camera.Size s, float rate) { + float r = (float) (s.width) / (float) (s.height); + if (Math.abs(r - rate) <= 0.03) { return true; - }else{ + } else { return false; } } - private Comparator sizeComparator=new Comparator(){ + + private final Comparator sizeComparator = new Comparator() { public int compare(Camera.Size lhs, Camera.Size rhs) { - if(lhs.height == rhs.height){ + if (lhs.height == rhs.height) { return 0; - }else if(lhs.height > rhs.height){ + } else if (lhs.height > rhs.height) { return 1; - }else{ + } else { return -1; } } diff --git a/app/src/main/java/com/example/cj/videoeditor/camera/ICamera.java b/app/src/main/java/com/example/cj/videoeditor/camera/ICamera.java index 8df296b..abdfc55 100644 --- a/app/src/main/java/com/example/cj/videoeditor/camera/ICamera.java +++ b/app/src/main/java/com/example/cj/videoeditor/camera/ICamera.java @@ -2,6 +2,7 @@ import android.graphics.Point; import android.graphics.SurfaceTexture; +import android.hardware.Camera; /** * Created by cj on 2017/8/2. @@ -9,30 +10,39 @@ */ public interface ICamera { - /**open the camera*/ + /** + * open the camera + */ void open(int cameraId); + /** + * set the preview texture + */ void setPreviewTexture(SurfaceTexture texture); - /**set the camera config*/ + + /** + * set the camera config + */ void setConfig(Config config); void setOnPreviewFrameCallback(PreviewFrameCallback callback); void preview(); - Point getPreviewSize(); + Camera.Size getPreviewSize(); - Point getPictureSize(); - /**close the camera*/ + /** + * close the camera + */ boolean close(); - class Config{ - public float rate=1.778f; //宽高比 + class Config { + public float rate = 1.778f; //宽高比 public int minPreviewWidth; public int minPictureWidth; } - interface PreviewFrameCallback{ + interface PreviewFrameCallback { void onPreviewFrame(byte[] bytes, int width, int height); } } diff --git a/app/src/main/java/com/example/cj/videoeditor/camera/SensorControler.java b/app/src/main/java/com/example/cj/videoeditor/camera/SensorController.java similarity index 94% rename from app/src/main/java/com/example/cj/videoeditor/camera/SensorControler.java rename to app/src/main/java/com/example/cj/videoeditor/camera/SensorController.java index 9ef06bc..2d77a42 100644 --- a/app/src/main/java/com/example/cj/videoeditor/camera/SensorControler.java +++ b/app/src/main/java/com/example/cj/videoeditor/camera/SensorController.java @@ -5,7 +5,6 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.opengl.GLES20; import android.util.Log; import com.example.cj.videoeditor.MyApplication; @@ -15,7 +14,7 @@ /** * 加速度控制器 用来控制对焦 */ -public class SensorControler implements SensorEventListener { +public class SensorController implements SensorEventListener { public static final String TAG = "SensorControler"; private SensorManager mSensorManager; private Sensor mSensor; @@ -37,18 +36,18 @@ public class SensorControler implements SensorEventListener { private CameraFocusListener mCameraFocusListener; - private static SensorControler mInstance; + private static SensorController mInstance; private int foucsing = 1; //1 表示没有被锁定 0表示被锁定 - private SensorControler() { + private SensorController() { mSensorManager = (SensorManager) MyApplication.getContext().getSystemService(Activity.SENSOR_SERVICE); mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// TYPE_GRAVITY } - public static SensorControler getInstance() { + public static SensorController getInstance() { if (mInstance == null) { - mInstance = new SensorControler(); + mInstance = new SensorController(); } return mInstance; } @@ -75,7 +74,6 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) { } - @Override public void onSensorChanged(SensorEvent event) { if (event.sensor == null) { @@ -157,7 +155,7 @@ private void restParams() { * @return */ public boolean isFocusLocked() { - if(canFocus) { + if (canFocus) { return foucsing <= 0; } return false; @@ -181,7 +179,7 @@ public void unlockFocus() { Log.i(TAG, "unlockFocus"); } - public void restFoucs() { + public void restFocus() { foucsing = 1; } diff --git a/app/src/main/java/com/example/cj/videoeditor/drawer/CameraDrawer.java b/app/src/main/java/com/example/cj/videoeditor/drawer/CameraDrawer.java index 19127dd..6aac78b 100644 --- a/app/src/main/java/com/example/cj/videoeditor/drawer/CameraDrawer.java +++ b/app/src/main/java/com/example/cj/videoeditor/drawer/CameraDrawer.java @@ -128,6 +128,7 @@ public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { } + @Override public void onSurfaceChanged(GL10 gl10, int i, int i1) { width = i; @@ -160,11 +161,28 @@ public void onSurfaceChanged(GL10 gl10, int i, int i1) { MatrixUtils.getShowMatrix(SM,mPreviewWidth, mPreviewHeight, width, height); showFilter.setMatrix(SM); } - + /** + * 切换摄像头的时候 + * 会出现画面颠倒的情况 + * 通过跳帧来解决 + * */ + boolean switchCamera=false; + int skipFrame; + public void switchCamera() { + switchCamera=true; + } @Override public void onDrawFrame(GL10 gl10) { /**更新界面中的数据*/ mSurfaceTextrue.updateTexImage(); + if(switchCamera){ + skipFrame++; + if(skipFrame>1){ + skipFrame=0; + switchCamera=false; + } + return; + } EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]); GLES20.glViewport(0,0,mPreviewWidth,mPreviewHeight); diff --git a/app/src/main/java/com/example/cj/videoeditor/drawer/TextureRender.java b/app/src/main/java/com/example/cj/videoeditor/drawer/TextureRender.java new file mode 100644 index 0000000..03da2bf --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/drawer/TextureRender.java @@ -0,0 +1,396 @@ +package com.example.cj.videoeditor.drawer; + +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.content.res.Resources; +import android.graphics.BitmapFactory; +import android.graphics.SurfaceTexture; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.util.Log; + + +import com.example.cj.videoeditor.MyApplication; +import com.example.cj.videoeditor.R; +import com.example.cj.videoeditor.filter.AFilter; +import com.example.cj.videoeditor.filter.GroupFilter; +import com.example.cj.videoeditor.filter.NoFilter; +import com.example.cj.videoeditor.filter.RotationOESFilter; +import com.example.cj.videoeditor.filter.WaterMarkFilter; +import com.example.cj.videoeditor.gpufilter.basefilter.GPUImageFilter; +import com.example.cj.videoeditor.media.VideoInfo; +import com.example.cj.videoeditor.utils.EasyGlUtils; +import com.example.cj.videoeditor.utils.MatrixUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Code for rendering a texture onto a surface using OpenGL ES 2.0. + */ +public class TextureRender { + private static final String TAG = "TextureRender"; + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + // private final float[] mTriangleVerticesData = {//原来的纹理坐标体系是错误的 +// // X, Y, Z, U, V +// -1.0f, -1.0f, 0, 0.f, 0.f, +// 1.0f, -1.0f, 0, 1.f, 0.f, +// -1.0f, 1.0f, 0, 0.f, 1.f, +// 1.0f, 1.0f, 0, 1.f, 1.f, +// }; + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 1.f, + 1.0f, -1.0f, 0, 1.f, 1.f, + -1.0f, 1.0f, 0, 0.f, 0.f, + 1.0f, 1.0f, 0, 1.f, 0.f, + }; + private FloatBuffer mTriangleVertices; + private static final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;\n" + + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + // highp here doesn't seem to matter + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + private float[] mMVPMatrix = new float[16]; + private float[] mSTMatrix = new float[16]; + private int mProgram; + private int mTextureID = -12345; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + + //======================clip======================== + float[] SM = new float[16]; //用于显示的变换矩阵 + boolean isClipMode; + int clipViewWidth; + int clipViewHeight; + int clipEncodeWidth; + int clipEncodeHeight; + + + //======================zoom======================== + //创建帧缓冲区 + private int[] fFrame = new int[1]; + private int[] fTexture = new int[2]; + AFilter mShow; + RotationOESFilter rotationFilter; + GPUImageFilter mGpuFilter; + GroupFilter mBeFilter; + //第一段视频宽高(旋转后) + int viewWidth; + int viewHeight; + //当前视频宽高(旋转后) + int videoWidth; + int videoHeight; + //最终显示的宽高 + int width; + int height; + int x; + int y; + boolean videoChanged = false; + //第一段视频信息 + VideoInfo info; + /** + * 用于后台绘制的变换矩阵 + */ + private float[] OM; + + public TextureRender(VideoInfo info) { + this.info=info; + mTriangleVertices = ByteBuffer.allocateDirect( + mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + Matrix.setIdentityM(mSTMatrix, 0); + Resources resources = MyApplication.getContext().getResources(); + mShow = new NoFilter(resources); + mShow.setMatrix(MatrixUtils.flip(MatrixUtils.getOriginalMatrix(), false, true)); + rotationFilter = new RotationOESFilter(resources); + mBeFilter = new GroupFilter(resources); + //默认加上水印 可以取消掉 + WaterMarkFilter waterMarkFilter = new WaterMarkFilter(resources); + waterMarkFilter.setWaterMark(BitmapFactory.decodeResource(resources, R.mipmap.watermark)); + + waterMarkFilter.setPosition(0, 70, 0, 0); + mBeFilter.addFilter(waterMarkFilter); + OM = MatrixUtils.getOriginalMatrix(); + MatrixUtils.flip(OM, false, false);//矩阵上下翻转 + mBeFilter.setMatrix(OM); + } + + public int getTextureId() { + return mTextureID; + } + + public void drawFrame(SurfaceTexture st) { + zoomDraw(st); + } + + public void zoomDraw(SurfaceTexture st){ + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[0]); + GLES20.glViewport(0, 0, viewWidth, viewHeight); + rotationFilter.draw(); + EasyGlUtils.unBindFrameBuffer(); + + mBeFilter.setTextureId(fTexture[0]); + mBeFilter.draw(); + + if (mGpuFilter != null) { + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[1]); + mGpuFilter.onDrawFrame(mBeFilter.getOutputTexture()); + EasyGlUtils.unBindFrameBuffer(); + } + + if (videoChanged) { + GLES20.glViewport(x, y, width, height); + } + + mShow.setTextureId(fTexture[mGpuFilter == null ? 0 : 1]); + mShow.draw(); + GLES20.glFinish(); + } + + public void preDraw(SurfaceTexture st) { + checkGlError("onDrawFrame start"); +// st.getTransformMatrix(mSTMatrix);//当视频角度不为0时,这里会导致视频方向不对 + GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + Matrix.setIdentityM(mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + } + + /** + * Initializes GL state. Call this after the EGL surface has been created and made current. + */ + public void surfaceCreated() { + mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + if (maPositionHandle == -1) { + throw new RuntimeException("Could not get attrib location for aPosition"); + } + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + if (maTextureHandle == -1) { + throw new RuntimeException("Could not get attrib location for aTextureCoord"); + } + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkGlError("glGetUniformLocation uMVPMatrix"); + if (muMVPMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uMVPMatrix"); + } + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + if (muSTMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uSTMatrix"); + } + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR);//改为线性过滤,是画面更加平滑(抗锯齿) + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + + mShow.create(); + rotationFilter.create(); + rotationFilter.setTextureId(mTextureID); + + mBeFilter.create(); + GLES20.glGenFramebuffers(1, fFrame, 0); + + if (info.rotation == 0 || info.rotation == 180) { + EasyGlUtils.genTexturesWithParameter(2, fTexture, 0, GLES20.GL_RGBA, info.width, info.height); + viewWidth = info.width; + viewHeight = info.height; + } else { + EasyGlUtils.genTexturesWithParameter(2, fTexture, 0, GLES20.GL_RGBA, info.height, info.width); + viewWidth = info.height; + viewHeight = info.width; + } + + rotationFilter.setRotation(info.rotation); + } + + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + GLES20.glDeleteProgram(mProgram); + mProgram = createProgram(VERTEX_SHADER, fragmentShader); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + + public void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, op + ": glError " + error); + throw new RuntimeException(op + ": glError " + error); + } + } + + public void addGpuFilter(GPUImageFilter filter) { + if (mGpuFilter != null) { + mGpuFilter.destroy(); + } + mGpuFilter = filter; + if (filter != null) { + mGpuFilter.init(); + mGpuFilter.onDisplaySizeChanged(info.width, info.height); + mGpuFilter.onInputSizeChanged(info.width, info.height); + } + } + + public void onVideoSizeChanged(VideoInfo info) { + + setVideoWidthAndHeight(info); + adjustVideoPosition(); + videoChanged = true; + mBeFilter.setSize(viewWidth, viewHeight); + } + + public void setVideoWidthAndHeight(VideoInfo info) { + rotationFilter.setRotation(info.rotation); + if (info.rotation == 0 || info.rotation == 180) { + this.videoWidth = info.width; + this.videoHeight = info.height; + } else { + this.videoWidth = info.height; + this.videoHeight = info.width; + } + } + + private void adjustVideoPosition() { + float w = (float) viewWidth / videoWidth; + float h = (float) viewHeight / videoHeight; + if (w < h) { + width = viewWidth; + height = (int) ((float) videoHeight * w); + } else { + width = (int) ((float) videoWidth * h); + height = viewHeight; + } + x = (viewWidth - width) / 2; + y = (viewHeight - height) / 2; + } + + /** + * 清除掉水印 + * */ + public void clearWaterMark(){ + if (mBeFilter != null){ + mBeFilter.clearAll(); + mShow.setMatrix(OM); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cj/videoeditor/drawer/VideoDrawer.java b/app/src/main/java/com/example/cj/videoeditor/drawer/VideoDrawer.java index 275e6d3..248d2b9 100644 --- a/app/src/main/java/com/example/cj/videoeditor/drawer/VideoDrawer.java +++ b/app/src/main/java/com/example/cj/videoeditor/drawer/VideoDrawer.java @@ -7,6 +7,7 @@ import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.GLSurfaceView; +import android.util.Log; import android.view.MotionEvent; import com.example.cj.videoeditor.R; @@ -134,13 +135,20 @@ public void onSurfaceChanged(GL10 gl, int width, int height) { @Override public void onDrawFrame(GL10 gl) { + surfaceTexture.updateTexImage(); + EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]); + GLES20.glViewport(0,0,viewWidth,viewHeight); + mPreFilter.draw(); + EasyGlUtils.unBindFrameBuffer(); + mBeFilter.setTextureId(fTexture[0]); + mBeFilter.draw(); if (mBeautyFilter != null && isBeauty && mBeautyFilter.getBeautyLevel() != 0){ @@ -150,11 +158,13 @@ public void onDrawFrame(GL10 gl) { EasyGlUtils.unBindFrameBuffer(); mProcessFilter.setTextureId(fTexture[0]); }else { + mProcessFilter.setTextureId(mBeFilter.getOutputTexture()); } mProcessFilter.draw(); mSlideFilterGroup.onDrawFrame(mProcessFilter.getOutputTexture()); + if (mGroupFilter != null){ EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]); GLES20.glViewport(0,0,viewWidth,viewHeight); @@ -164,12 +174,16 @@ public void onDrawFrame(GL10 gl) { }else { mProcessFilter.setTextureId(mSlideFilterGroup.getOutputTexture()); } + mProcessFilter.draw(); + GLES20.glViewport(0,0,viewWidth,viewHeight); mShow.setTextureId(mProcessFilter.getOutputTexture()); + mShow.draw(); + Log.e("videoo", "---卡完了 ?"); } public SurfaceTexture getSurfaceTexture(){ return surfaceTexture; diff --git a/app/src/main/java/com/example/cj/videoeditor/filter/AFilter.java b/app/src/main/java/com/example/cj/videoeditor/filter/AFilter.java index f249e79..e520176 100644 --- a/app/src/main/java/com/example/cj/videoeditor/filter/AFilter.java +++ b/app/src/main/java/com/example/cj/videoeditor/filter/AFilter.java @@ -6,6 +6,7 @@ import android.util.SparseArray; import com.example.cj.videoeditor.utils.MatrixUtils; +import com.example.cj.videoeditor.utils.OpenGlUtils; import java.io.InputStream; import java.nio.ByteBuffer; @@ -106,11 +107,17 @@ public final void setSize(int width,int height){ } public void draw(){ + Log.e("videoo", "---卡主了? 16 "+getClass()); onClear(); + Log.e("videoo", "---卡主了? 17"); onUseProgram(); + Log.e("videoo", "---卡主了? 18"); onSetExpandData(); + Log.e("videoo", "---卡主了? 19"); onBindTexture(); + Log.e("videoo", "---卡主了? 20"); onDraw(); + Log.e("videoo", "---卡主了? 21"); } public final void setMatrix(float[] matrix){ @@ -207,7 +214,7 @@ protected final void createProgram(String vertex, String fragment){ } protected final void createProgramByAssetsFile(String vertex, String fragment){ - createProgram(uRes(mRes,vertex),uRes(mRes,fragment)); + createProgram(OpenGlUtils.uRes(vertex),OpenGlUtils.uRes(fragment)); } /** @@ -247,7 +254,9 @@ protected void onDraw(){ * 清除画布 */ protected void onClear(){ + GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); } @@ -273,22 +282,6 @@ public static void glError(int code,Object index){ } } - //通过路径加载Assets中的文本内容 - public static String uRes(Resources mRes, String path){ - StringBuilder result=new StringBuilder(); - try{ - InputStream is=mRes.getAssets().open(path); - int ch; - byte[] buffer=new byte[1024]; - while (-1!=(ch=is.read(buffer))){ - result.append(new String(buffer,0,ch)); - } - }catch (Exception e){ - return null; - } - return result.toString().replaceAll("\\r\\n","\n"); - } - //创建GL程序 public static int uCreateGlProgram(String vertexSource, String fragmentSource){ int vertex=uLoadShader(GLES20.GL_VERTEX_SHADER,vertexSource); diff --git a/app/src/main/java/com/example/cj/videoeditor/filter/NoFilter.java b/app/src/main/java/com/example/cj/videoeditor/filter/NoFilter.java index 42c3d28..47615e5 100644 --- a/app/src/main/java/com/example/cj/videoeditor/filter/NoFilter.java +++ b/app/src/main/java/com/example/cj/videoeditor/filter/NoFilter.java @@ -2,6 +2,7 @@ import android.content.res.Resources; import android.opengl.GLES20; +import android.util.Log; /** * Description: @@ -14,6 +15,7 @@ public NoFilter(Resources res) { @Override protected void onCreate() { + Log.e("thread", "---初始化NoFilter "+Thread.currentThread()); createProgramByAssetsFile("shader/base_vertex.sh", "shader/base_fragment.sh"); } @@ -23,8 +25,12 @@ protected void onCreate() { */ @Override protected void onClear() { + Log.e("thread", "---onClear? 1 "+Thread.currentThread()); + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + Log.e("thread", "---onClear? 2 "+Thread.currentThread()); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + Log.e("thread", "---onClear? 3 "); } @Override diff --git a/app/src/main/java/com/example/cj/videoeditor/gpufilter/SlideGpuFilterGroup.java b/app/src/main/java/com/example/cj/videoeditor/gpufilter/SlideGpuFilterGroup.java index 33b75d7..ade336f 100644 --- a/app/src/main/java/com/example/cj/videoeditor/gpufilter/SlideGpuFilterGroup.java +++ b/app/src/main/java/com/example/cj/videoeditor/gpufilter/SlideGpuFilterGroup.java @@ -264,11 +264,11 @@ public void onTouchEvent(MotionEvent event) { } locked = true; downX = -1; - if (offset > MyApplication.screenWidth / 3) { - scroller.startScroll(offset, 0, MyApplication.screenWidth - offset, 0, 100 * (1 - offset / MyApplication.screenWidth)); + if (offset > Constants.screenWidth / 3) { + scroller.startScroll(offset, 0, Constants.screenWidth - offset, 0, 100 * (1 - offset / Constants.screenWidth)); needSwitch = true; } else { - scroller.startScroll(offset, 0, -offset, 0, 100 * (offset / MyApplication.screenWidth)); + scroller.startScroll(offset, 0, -offset, 0, 100 * (offset / Constants.screenWidth)); needSwitch = false; } break; diff --git a/app/src/main/java/com/example/cj/videoeditor/jni/AudioJniUtils.java b/app/src/main/java/com/example/cj/videoeditor/jni/AudioJniUtils.java index 519db80..946329e 100644 --- a/app/src/main/java/com/example/cj/videoeditor/jni/AudioJniUtils.java +++ b/app/src/main/java/com/example/cj/videoeditor/jni/AudioJniUtils.java @@ -12,4 +12,8 @@ public class AudioJniUtils { System.loadLibrary("native-lib"); } public static native byte[] audioMix(byte[] sourceA,byte[] sourceB,byte[] dst,float firstVol , float secondVol); + + public static native String putString(String info); + + } diff --git a/app/src/main/java/com/example/cj/videoeditor/media/MediaCodecInfo.java b/app/src/main/java/com/example/cj/videoeditor/media/MediaCodecInfo.java new file mode 100644 index 0000000..a78030b --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/media/MediaCodecInfo.java @@ -0,0 +1,18 @@ +package com.example.cj.videoeditor.media; + +import android.media.MediaExtractor; + +/** + * Created by cj on 2017/7/11. + * desc 音频解码的info类 包含了音频path 音频的MediaExtractor + * 和本段音频的截取点cutPoint + * 以及剪切时长 cutDuration + */ + +public class MediaCodecInfo { + public String path; + public MediaExtractor extractor; + public int cutPoint; + public int cutDuration; + public int duration; +} diff --git a/app/src/main/java/com/example/cj/videoeditor/media/MediaPlayerWrapper.java b/app/src/main/java/com/example/cj/videoeditor/media/MediaPlayerWrapper.java index 4ef7c59..7a22da2 100644 --- a/app/src/main/java/com/example/cj/videoeditor/media/MediaPlayerWrapper.java +++ b/app/src/main/java/com/example/cj/videoeditor/media/MediaPlayerWrapper.java @@ -3,6 +3,7 @@ import android.media.AudioManager; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; +import android.util.Log; import android.view.Surface; import java.io.IOException; @@ -36,7 +37,7 @@ public void setOnCompletionListener(IMediaCallback callback) { /** * get video info and store * - * @param dataSource + * @param dataSource 视频播放的源文件 */ public void setDataSource(List dataSource) { this.mSrcList = dataSource; @@ -55,7 +56,6 @@ public void setDataSource(List dataSource) { info.width = Integer.parseInt(width); info.height = Integer.parseInt(height); info.duration = Integer.parseInt(duration); - mInfoList.add(info); } } diff --git a/app/src/main/java/com/example/cj/videoeditor/media/VideoInfo.java b/app/src/main/java/com/example/cj/videoeditor/media/VideoInfo.java index 7718476..02bd5ea 100644 --- a/app/src/main/java/com/example/cj/videoeditor/media/VideoInfo.java +++ b/app/src/main/java/com/example/cj/videoeditor/media/VideoInfo.java @@ -1,11 +1,13 @@ package com.example.cj.videoeditor.media; +import java.io.Serializable; + /** * Created by Administrator on 2017/6/29 0029. * 视频的信息bean */ -public class VideoInfo { +public class VideoInfo implements Serializable{ public String path;//路径 public int rotation;//旋转角度 public int width;//宽 diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioCodec.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioCodec.java index b2f8994..f1de2a0 100644 --- a/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioCodec.java +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioCodec.java @@ -33,7 +33,6 @@ public class AudioCodec { final static int TIMEOUT_USEC = 0; private static Handler handler = new Handler(Looper.getMainLooper()); - /** * 从视频文件中分离出音频,并保存到本地 * */ @@ -458,7 +457,12 @@ public static void PCM2AAC(String encodeType, String outputFile) throws IOExcept outBitSize=encodeBufferInfo.size; outPacketSize=outBitSize+7;//7为ADTS头部的大小 - outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer + if (Build.VERSION.SDK_INT >= 21){ + outputBuffer = mediaEncode.getOutputBuffer(outputIndex);//拿到输出Buffer + }else { + outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer + } + outputBuffer.position(encodeBufferInfo.offset); outputBuffer.limit(encodeBufferInfo.offset + outBitSize); chunkAudio = new byte[outPacketSize]; diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioRunnable.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioRunnable.java new file mode 100644 index 0000000..505ee4a --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/AudioRunnable.java @@ -0,0 +1,685 @@ +package com.example.cj.videoeditor.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.media.MediaMetadataRetriever; +import android.os.Build; +import android.util.Log; + +import com.example.cj.videoeditor.bean.AudioSettingInfo; +import com.example.cj.videoeditor.media.MediaCodecInfo; +import com.example.cj.videoeditor.media.VideoInfo; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by cj on 2017/6/30. + * desc 音频编解码线程 + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class AudioRunnable extends Thread { + final int TIMEOUT_USEC = 0; + private AudioSettingInfo mSettingInfo;//混音设置 + + private int audioTrackIndex = -1; + private MediaFormat audioMediaFormat; + private MediaMuxerRunnable mMediaMuxer; + private MediaCodecInfo mAudioDecode; + private boolean mIsBgmLong; + + private List mVideoInfos;//多个音频的合并 + private List mTrackIndex = new ArrayList<>();//用于记录不同分离器的audio信道的index + private List mAudioDecodes = new ArrayList<>(); + + + public AudioRunnable(List inputFiles, MediaMuxerRunnable mediaMuxer) { + this.mMediaMuxer = mediaMuxer; + mVideoInfos = inputFiles; + } + + @Override + public void run() { + try { + prepare(); + + simpleAudioMix(); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private void prepare() throws IOException { + + for (int i = 0; i < mVideoInfos.size(); i++) { + //给每一个视频文件都创建一个MediaExtractor + MediaExtractor temp = new MediaExtractor(); + VideoInfo videoInfo = mVideoInfos.get(i); + temp.setDataSource(videoInfo.path); + + MediaCodecInfo decode = new MediaCodecInfo();//音频解码info + decode.extractor = temp; + decode.path = videoInfo.path; + decode.cutPoint = videoInfo.cutPoint; + decode.cutDuration = videoInfo.cutDuration; + decode.duration = videoInfo.duration; + mAudioDecodes.add(decode); + + } + + for (int i = 0; i < mAudioDecodes.size(); i++) {//所有音频Extractor选择信道,并且记录信道 + MediaExtractor extractor = mAudioDecodes.get(i).extractor; + int trackCount = extractor.getTrackCount(); + for (int j = 0; j < trackCount; j++) { + MediaFormat trackFormat = extractor.getTrackFormat(j); + String mime = trackFormat.getString(MediaFormat.KEY_MIME); + + if (mime.startsWith("audio/")) { + extractor.selectTrack(j); + mTrackIndex.add(j); + break; + } + } + + } + } + + + //执行解码音频的代码块 + private AtomicBoolean bgmDecodeOver = new AtomicBoolean(false);//bgm解码完成 + private AtomicBoolean audioDecodeOver = new AtomicBoolean(false);//原声解码完成 + private AtomicInteger bgmCount = new AtomicInteger(0);//背景音pcm文件大小 byte + private AtomicInteger audioCount = new AtomicInteger(0);//原声pcm文件大小 byte + + private long default_time = 23219; + private List totalTime = new ArrayList<>();//每段音频的时长 + private List eachTime = new ArrayList<>();//每段音频的时间间隔(每帧之间的时间戳间隔) + private List frameCount = new ArrayList<>();//每段音频的帧数 + + private int which = 0;//当前是第几段 + private long currentTime = 0;//当前的音频时长 用于记录总时长 + + private class AudioMixAndEncodeRunnable implements Runnable { + private String[] mixFiles; + private AudioRunnable.EncodeListener mListener; + private int muxerCount = 0; + + public AudioMixAndEncodeRunnable(String[] files, AudioRunnable.EncodeListener listener) { + mixFiles = files; + mListener = listener; + } + + @Override + public void run() { + try { + int inputIndex; + ByteBuffer inputBuffer; + int outputIndex; + byte[] chunkPCM; + long start = System.currentTimeMillis(); + //初始化编码器 + MediaFormat encodeFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 2);//mime type 采样率 声道数 + encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);//比特率 + encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, android.media.MediaCodecInfo.CodecProfileLevel.AACObjectLC); + encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 500 * 1024); + + MediaCodec mediaEncode = MediaCodec.createEncoderByType("audio/mp4a-latm"); + mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mediaEncode.start(); + + ByteBuffer[] encodeInputBuffers = mediaEncode.getInputBuffers(); + ByteBuffer[] encodeOutputBuffers = mediaEncode.getOutputBuffers(); + MediaCodec.BufferInfo encodeBufferInfo = new MediaCodec.BufferInfo(); + + + //初始化两个io + int fileNum = mixFiles.length; + FileInputStream[] audioFileStreams = new FileInputStream[fileNum]; + String audioFile; + for (int fileIndex = 0; fileIndex < fileNum; ++fileIndex) { + audioFile = mixFiles[fileIndex]; + audioFileStreams[fileIndex] = new FileInputStream(audioFile); + } + + byte[] readBytes = new byte[8 * 1024];//读取的临时数组 + FileInputStream inputStream; + byte[][] allAudioBytes = new byte[fileNum][];//用于承载读出的数据 + boolean[] streamDoneArray = new boolean[fileNum];//记录两个输入流是否已经读完 + long timeUs = 0; + boolean isFirstOutputFrame = true;//是否是输出的第一帧 + boolean encodeDone = false; + currentTime = totalTime.get(which);//当前段时长 + default_time = eachTime.get(which);//每帧时间戳间隔 + while (!encodeDone) { + if (!streamDoneArray[0] || !streamDoneArray[1]) {//数据没有读完 + inputIndex = mediaEncode.dequeueInputBuffer(TIMEOUT_USEC); + if (inputIndex >= 0) { + inputBuffer = encodeInputBuffers[inputIndex]; + inputBuffer.clear();//同解码器 + //从两个文件中读取数据 + for (int j = 0; j < fileNum; j++) { + inputStream = audioFileStreams[j]; + if (!streamDoneArray[j] && inputStream.read(readBytes) != -1) { + allAudioBytes[j] = Arrays.copyOf(readBytes, readBytes.length); + } else { + //说明某个文件读取完成了 + if (mIsBgmLong) { + //说明bgm比原声长, + if (j == 0) { + //说明是原声读取完毕了,那么bgm也不再读取 + streamDoneArray[j] = true; + streamDoneArray[1] = true;//bgm也不再读取 + //并且跳出去,就不再读数据了 + mediaEncode.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + for (FileInputStream temp : audioFileStreams) { + temp.close(); + } + break; + } + } else { + //说明原声比bgm长 + if (j == 1) { + //说明是bgm文件读取完了 + inputStream.skip(0); + int tempSize = inputStream.read(readBytes); + if (tempSize > 0) { + allAudioBytes[j] = Arrays.copyOf(readBytes, readBytes.length); + } else { + allAudioBytes[j] = new byte[8 * 1024];//那么bgm的部分就用0替代 + } + } else { + //说明是原声读取完了 + streamDoneArray[j] = true; + streamDoneArray[1] = true;//bgm也不再读取 + //结束读取数据和写入数据 + mediaEncode.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + for (FileInputStream temp : audioFileStreams) { + temp.close(); + } + break; + } + } + } + } + if (!streamDoneArray[0] || !streamDoneArray[1]) { + //对两个byte数组进行合并 +// chunkPCM = AudioHardcodeHandler.normalizationMix(allAudioBytes, mSettingInfo.volFirst, mSettingInfo.volSecond); + chunkPCM = AudioCodec.nativeAudioMix(allAudioBytes, 1, 1); + if (chunkPCM == null) { + break; + } + inputBuffer.put(chunkPCM);//PCM数据填充给inputBuffer + mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0);//通知编码器 编码 + } + } + } + + boolean outputDone = false; + while (!outputDone) {//同解码器 + outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, TIMEOUT_USEC);//同解码器 + if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + outputDone = true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat newFormat = mediaEncode.getOutputFormat(); + mMediaMuxer.addMediaFormat(MediaMuxerRunnable.MEDIA_TRACK_AUDIO, newFormat); + } else if (outputIndex < 0) { + } else { + if ((encodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + encodeDone = true; + break; + } + if ((encodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + mediaEncode.releaseOutputBuffer(outputIndex, false); + break; + } + ByteBuffer outputBuffer; + if (Build.VERSION.SDK_INT >= 21) { + outputBuffer = mediaEncode.getOutputBuffer(outputIndex);//拿到输出Buffer + } else { + outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer + } + if (isFirstOutputFrame) { + timeUs = 0; + isFirstOutputFrame = false; + } else { + timeUs += default_time; + } + if (timeUs >= currentTime * 1000) {//说明音频切换了,要用下一段音频的平均帧间隔了 + which++; + + if (which >= totalTime.size()) { + which = 0; + } + currentTime += totalTime.get(which); + //说明更换视频了 + default_time = eachTime.get(which); + } + encodeBufferInfo.presentationTimeUs = timeUs; + muxerCount++; + mMediaMuxer.addMuxerData(MediaMuxerRunnable.MEDIA_TRACK_AUDIO, outputBuffer, encodeBufferInfo); + + mediaEncode.releaseOutputBuffer(outputIndex, false); + } + } + } + mediaEncode.stop(); + mediaEncode.release(); + long end = System.currentTimeMillis(); + //删除掉传入的pcm文件 + for (int i = 0; i < mixFiles.length; i++) { + File file = new File(mixFiles[i]); + if (file.exists()) { + file.delete(); + } + } + if (mListener != null) { + mListener.encodeIsOver(); + } + } catch (IOException e) { + Log.e("hero", " init encoder error "); + } + } + } + + private class AudioDecodeRunnable implements Runnable { + private boolean isBgm; + private String tempPcmFile; + private AudioRunnable.DecodeOverListener mListener; + private List mMediaCodecInfos;//将多个分离器中的音频数据 写入一个pcm文件中 + private List mAudioCodec; + private List audioTrackList;//各个分离器中track的index + private MediaCodec mMediaCodec; + private MediaCodecInfo mMediaCodecInfo; + + public AudioDecodeRunnable(List extractors, List trackList, String pcmFile, boolean isBgm, AudioRunnable.DecodeOverListener listener) { + mMediaCodecInfos = extractors; + audioTrackList = trackList; + this.isBgm = isBgm; + tempPcmFile = pcmFile; + mListener = listener; + } + + @Override + public void run() { + //初始化多个视频源的原声解码器, + mAudioCodec = new ArrayList<>(); + /** + * 1、初始化多个解码器 + * */ + for (int i = 0; i < mMediaCodecInfos.size(); i++) { + MediaExtractor extractor = mMediaCodecInfos.get(i).extractor; + MediaFormat trackFormat = extractor.getTrackFormat(audioTrackList.get(i)); + try { + MediaCodec temp = MediaCodec.createDecoderByType(trackFormat.getString(MediaFormat.KEY_MIME)); + temp.configure(trackFormat, null, null, 0); + mAudioCodec.add(temp); + + } catch (IOException e) { + e.printStackTrace(); + } + } + String s = null; + long ss = 0; + + if (!isBgm) { + + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + + for (int i = 0; i < mMediaCodecInfos.size(); i++) { + retriever.setDataSource(mMediaCodecInfos.get(i).path); + s = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + ss += Long.parseLong(s); + totalTime.add(Long.parseLong(s));//添加每段音频的时长 + } + } + + + /* + * 2、选中第一个解码器 + * */ + mMediaCodec = mAudioCodec.get(0); + mMediaCodec.start(); + mMediaCodecInfo = mMediaCodecInfos.get(0); + if (mAudioDecodes.get(0).cutDuration > 0 && mAudioDecodes.get(0).cutPoint + mAudioDecodes.get(0).cutDuration <= mAudioDecodes.get(0).duration) { + mMediaCodecInfo.extractor.seekTo(mAudioDecodes.get(0).cutPoint * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + /* + * 3、初始化当前解码器的相关参数 + * */ + ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); + ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers(); + MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo inputInfo = new MediaCodec.BufferInfo(); + boolean codeOver = false;//所有音频是否全部解码 + int curIndex = 0;//当前是输入的第几个音频 + boolean isNewStep = false;//音频段落切换标志 + boolean isFirstFrame = true; + long samptime = 0;//记录上一帧音频时间戳,以便于取差值 + boolean inputDone = false;//整体输入结束标志 + int decode = 0;//用以记录每段输入了多少帧音频 + try { + FileOutputStream fos = new FileOutputStream(tempPcmFile); + long stratTime = System.currentTimeMillis(); + while (!codeOver) { + if (!inputDone) { + for (int i = 0; i < inputBuffers.length; i++) { + //遍历所以的编码器 然后将数据传入之后 再去输出端取数据 + int inputIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC); + if (inputIndex >= 0) { + /* + * 从分离器中拿到数据 写入解码器 + * */ + ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到inputBuffer + inputBuffer.clear();//清空之前传入inputBuffer内的数据 + int sampleSize = mMediaCodecInfo.extractor.readSampleData(inputBuffer, 0);//MediaExtractor读取数据到inputBuffer中 + + if (sampleSize < 0) { + mMediaCodec.queueInputBuffer(inputIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputDone = true; + } else { + /*音频对解码前的数据 进行抛弃*/ + long sampleTime = mMediaCodecInfo.extractor.getSampleTime(); + boolean isWrite = true; + if (mMediaCodecInfo.cutDuration > 0 && sampleTime / 1000 < mMediaCodecInfo.cutPoint) { + //说明还没有到剪切点 + isWrite = false; + mMediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, 0);//释放inputBuffer + } + if (mMediaCodecInfo.cutDuration > 0 && sampleTime / 1000 > (mMediaCodecInfo.cutDuration + mMediaCodecInfo.cutPoint)) { + isWrite = false; + mMediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, 0);//虽然跳过了 但是需要释放当前的inputBuffer + } + + if (isWrite) {//当前读取的数据可用,且在裁剪范围内 + inputInfo.offset = 0; + inputInfo.size = sampleSize; + inputInfo.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME; + + if (isNewStep) {//切换到了下一段音频 + if (!isBgm) { + frameCount.add(decode);//保存之前一段的帧数 + decode = 0; + } + inputInfo.presentationTimeUs += 23219; + isNewStep = false; + } else { + if (isFirstFrame) {//当为第一个音频的第一帧时 + inputInfo.presentationTimeUs = 0; + isFirstFrame = false; + } else { + inputInfo.presentationTimeUs += mMediaCodecInfo.extractor.getSampleTime() - samptime; + } + } + decode++; + mMediaCodec.queueInputBuffer(inputIndex, inputInfo.offset, sampleSize, inputInfo.presentationTimeUs, 0);//通知MediaDecode解码刚刚传入的数据 + } + samptime = mMediaCodecInfo.extractor.getSampleTime(); + mMediaCodecInfo.extractor.advance();//MediaExtractor移动到下一取样处 + } + } + } + } + + boolean decodeOutputDone = false; + byte[] chunkPCM; + while (!decodeOutputDone) { + int outputIndex = mMediaCodec.dequeueOutputBuffer(decodeBufferInfo, TIMEOUT_USEC); + if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + /**没有可用的解码器output*/ + decodeOutputDone = true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + outputBuffers = mMediaCodec.getOutputBuffers(); + } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat newFormat = mMediaCodec.getOutputFormat(); + } else if (outputIndex < 0) { + } else { + ByteBuffer outputBuffer; + if (Build.VERSION.SDK_INT >= 21) { + outputBuffer = mMediaCodec.getOutputBuffer(outputIndex); + } else { + outputBuffer = outputBuffers[outputIndex]; + } + boolean isUseful = true; + + if (isUseful) { + chunkPCM = new byte[decodeBufferInfo.size]; + outputBuffer.get(chunkPCM); + outputBuffer.clear(); + byte[] result = chunkPCM; + if (chunkPCM.length < 4096) {//这里看起来应该是16位单声道转16位双声道 + //说明是单声道的,需要转换一下 + byte[] stereoBytes = new byte[decodeBufferInfo.size * 2]; + for (int i = 0; i < chunkPCM.length; i += 2) { + stereoBytes[i * 2 + 0] = chunkPCM[i]; + stereoBytes[i * 2 + 1] = chunkPCM[i + 1]; + stereoBytes[i * 2 + 2] = chunkPCM[i]; + stereoBytes[i * 2 + 3] = chunkPCM[i + 1]; + } + result = stereoBytes; + } + if (isBgm) { + int i = bgmCount.get() + result.length;// + bgmCount.set(i); + } else { + int i = audioCount.get() + result.length; + audioCount.set(i); + } + fos.write(result);//数据写入文件中 + fos.flush(); + } + mMediaCodec.releaseOutputBuffer(outputIndex, false); + if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + curIndex++; + if (curIndex >= mMediaCodecInfos.size()) { + codeOver = true; + inputDone = true; + if (!isBgm) { + frameCount.add(decode);//加入最后一段音频帧数 + } + } else { + /** + * 更换分离器 + * 更换解码器 + * 开始解码下一个音频 + * */ + mMediaCodecInfo.extractor.release(); + mMediaCodecInfo = mMediaCodecInfos.get(curIndex); + if (mMediaCodecInfo.cutDuration > 0) { + mMediaCodecInfo.extractor.seekTo(mMediaCodecInfo.cutPoint * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + mMediaCodec.stop(); + mMediaCodec.release(); + mMediaCodec = mAudioCodec.get(curIndex); + mMediaCodec.start(); + isNewStep = true; + codeOver = false; + inputBuffers = mMediaCodec.getInputBuffers(); + outputBuffers = mMediaCodec.getOutputBuffers(); + inputDone = false; + } + } + } + } + + if (isBgm && mIsBgmLong && audioDecodeOver.get()) { + //如果当前是bgm解码线程、并且是bgm比较长、并且原声解码完成了,就需要判定一下 + //当前bgm解码个数是否大于等于audioCount解码个数,如果大于 就退出线程 + if (bgmCount.get() >= audioCount.get()) { + break; + } + } + }//大循环结束 + + if (isBgm) { + //bgm解码完成 + bgmDecodeOver.set(true); + } else { + audioDecodeOver.set(true); + } + if (!isBgm) { + for (int i = 0; i < frameCount.size(); i++) { + long l = totalTime.get(i) * 1000 / frameCount.get(i);//计算每段音频的帧平均间隔 + eachTime.add(l); + } + } + fos.close();//输出流释放 + //释放最后一个decoder + mMediaCodec.stop(); + mMediaCodec.release(); + if (mListener != null) { + mListener.decodeIsOver(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void log(boolean isBgm, String log) { + if (!isBgm) { + Log.e("audioo", log); + } else { + Log.e("bgm", log); + } + } + + + /** + * 只是将原视频中的音频分离出来,然后进行添加 + * 不需要编解码等操作 + * 支持多个视频文件的音频连续写入 + * 暂时不考虑视频中没有音频的情况 + * 不考虑部分视频的音频有问题 导致音频速度加快或者减慢的情况(主要是因为立体声和单声道引起的 + * stereo和mono) + */ + private void simpleAudioMix() { + MediaExtractor mExtractor = mAudioDecodes.get(0).extractor;//拿到第一个分离器 + mAudioDecode = mAudioDecodes.get(0);//拿到第一个视频的信息 + int trackCount = mExtractor.getTrackCount(); + for (int i = 0; i < trackCount; i++) {//初始化第一段视频的音频format + MediaFormat trackFormat = mExtractor.getTrackFormat(i); + String mime = trackFormat.getString(MediaFormat.KEY_MIME); + if (mime.startsWith("audio/")) { + audioTrackIndex = i; + audioMediaFormat = trackFormat; + break; + } + } + //将第一个视频的audioFormat作为整体音频的audioFormat + mMediaMuxer.addMediaFormat(MediaMuxerRunnable.MEDIA_TRACK_AUDIO, audioMediaFormat); + + ByteBuffer buffer = ByteBuffer.allocate(50 * 1024); + + //分离音频 + int curIndex = 0;//当前是第几段视频,从第0段开始, + boolean isNextStep = false; + boolean isFirstFrame = true; + boolean isFirstSeek = false; + long samptime = 0; + long lastTime = 0;//上一帧的时间戳 + if (audioTrackIndex != -1) { + if (mAudioDecode.cutDuration > 0 && (mAudioDecode.cutPoint + mAudioDecode.cutDuration) <= mAudioDecode.duration) { + //说明是需要剪切的 跳转到剪切点 现在只是跳了第一个视频 + mAudioDecode.extractor.seekTo(mAudioDecode.cutPoint * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + isFirstSeek = true; + } + while (true) { + int readSampleData = mExtractor.readSampleData(buffer, 0); + if (readSampleData < 0) { + //说明 本地读取完毕了 + curIndex++; + if (curIndex < mAudioDecodes.size()) { + //说明还有新的音频要添加 + mAudioDecode.extractor.release();//老的释放掉 + mExtractor = mAudioDecodes.get(curIndex).extractor;//更换extractor + mAudioDecode = mAudioDecodes.get(curIndex);//更换音频的那个啥 + if (mAudioDecode.cutDuration > 0 && (mAudioDecode.cutPoint + mAudioDecode.cutDuration) <= mAudioDecode.duration) { + //说明是需要剪切的 跳转到剪切点 现在只是跳了第一个视频 + mAudioDecode.extractor.seekTo(mAudioDecode.cutPoint * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + isNextStep = true; + } else { + //说明已经没有其他的音频了 就break掉 + break; + } + } else { + long sampleTime = mAudioDecode.extractor.getSampleTime();//当前的时间戳 + //如果这个时间戳以及大于了 就break掉 + if (mAudioDecode.cutDuration > 0 && (mAudioDecode.cutPoint + mAudioDecode.cutDuration) <= sampleTime / 1000) { + /** + * 说明本地视频到了剪切点了,要做以下几件事情 + * 1、跳出本次写入 + * 2、更换源文件 + * */ + curIndex++; + if (curIndex < mAudioDecodes.size()) { + //说明还有源文件 + mAudioDecode.extractor.release(); + mAudioDecode = mAudioDecodes.get(curIndex); + if (mAudioDecode.cutDuration > 0 && (mAudioDecode.cutPoint + mAudioDecode.cutDuration) <= mAudioDecode.duration) { + //说明是需要剪切的 跳转到剪切点 现在只是跳了第一个视频 + mAudioDecode.extractor.seekTo(mAudioDecode.cutPoint * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + isNextStep = true; + continue; + } else { + //说明已经没有了 + break; + } + } + + MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + info.offset = 0; + info.size = readSampleData; + info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME; + if (isNextStep) { + //说明是新的一段音频 + info.presentationTimeUs = lastTime + 23219; + isNextStep = false; + } else { + //当前帧的时间戳 减去上次帧的时间戳 加上 上次数据帧的时间 就是本次帧的时间戳 + if (isFirstSeek) { + //说明第一个视频跳帧过 + info.presentationTimeUs = 0; + isFirstSeek = false; + } else { + if (isFirstFrame) { + info.presentationTimeUs = 0; + isFirstFrame = false; + } else { + info.presentationTimeUs = lastTime + (mAudioDecode.extractor.getSampleTime() - samptime); + } + } + } + lastTime = info.presentationTimeUs; + samptime = mAudioDecode.extractor.getSampleTime(); + mMediaMuxer.addMuxerData(MediaMuxerRunnable.MEDIA_TRACK_AUDIO, buffer, info); + mAudioDecode.extractor.advance(); + } + } + mAudioDecode.extractor.release(); + mMediaMuxer.audioIsOver(); + } + } + + interface DecodeOverListener { + void decodeIsOver(); + } + + interface EncodeListener { + void encodeIsOver(); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/MediaMuxerRunnable.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/MediaMuxerRunnable.java new file mode 100644 index 0000000..c84884d --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/MediaMuxerRunnable.java @@ -0,0 +1,189 @@ +package com.example.cj.videoeditor.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + + +import com.example.cj.videoeditor.gpufilter.helper.MagicFilterType; +import com.example.cj.videoeditor.media.VideoInfo; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Created by cj on 2017/6/30. + * desc 音视频混合器,只要是将音视频的混合分成两条子线程进行处理 + * 音频线程AudioRunnable 进行音频的编解码 以及向混合器中写入数据 + * 视频线程VideoRunnable 进行视频的编解码 以及向混合器中写入数据 + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class MediaMuxerRunnable extends Thread { + public static final int MEDIA_TRACK_AUDIO = 1; + public static final int MEDIA_TRACK_VIDEO = 2; + + private AudioRunnable mAudioRunnable; + private VideoRunnable mVideoRunnable; + private final Object lock = new Object(); + + private String outputFilePath; + private MediaMuxer mMediaMuxer; + private int mVideoTrack; + private int mAudioTrack; + private boolean isVideoAdd = false; + private boolean isAudioAdd = false; + private boolean isMuxerStarted = false; + private boolean isVideoOver = false; + private boolean isAudioOver = false; + + private MagicFilterType mFilterType; + private MuxerListener mListener; + private List mVideoInfos;//视频文件的信息 + int curMode; + + public MediaMuxerRunnable() { + } + + //设置要合并的视频信息和输出文件path + public void setVideoInfo(List inputVideoInfo, String outputFile) { + mVideoInfos = inputVideoInfo; + outputFilePath = outputFile; + } + + public void addMuxerListener(MuxerListener listener) { + mListener = listener; + } + + public void setFilterType(MagicFilterType filterType) { + mFilterType = filterType; + } + + @Override + public void run() { + // + if (mListener != null) { + mListener.onStart(); + } + initMuxer(); + mAudioRunnable = new AudioRunnable(mVideoInfos, this); + mVideoRunnable = new VideoRunnable(mVideoInfos, this); + mAudioRunnable.start(); + mVideoRunnable.start(); + } + + //初始化混合器,编解码线程 + private void initMuxer() { + checkUseful(); + try { + mMediaMuxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + } catch (IOException e) { + e.printStackTrace(); + } + } + + //检查各项参数是否正确 + private void checkUseful() { + if (mVideoInfos == null || mVideoInfos.size() == 0) { + throw new IllegalStateException(" 必须先设置要处理的视频"); + } + if (TextUtils.isEmpty(outputFilePath)) { + throw new IllegalStateException(" 必须设置视频输出路径"); + } + } + + /** + * @param trackIndex audio_track == 1 video_track == 2 + * @param format MediaFormat + */ + public void addMediaFormat(int trackIndex, MediaFormat format) { + synchronized (lock) { + if (mMediaMuxer == null) { + return; + } + if (trackIndex == MEDIA_TRACK_AUDIO) { + mAudioTrack = mMediaMuxer.addTrack(format); + isAudioAdd = true; + + } else if (trackIndex == MEDIA_TRACK_VIDEO) { + mVideoTrack = mMediaMuxer.addTrack(format); + isVideoAdd = true; + } + if (isAudioAdd && isVideoAdd) { + mMediaMuxer.start(); + isMuxerStarted = true; + lock.notify(); + Log.e("muxer", "start media muxer waiting for data..."); + } + } + } + + //往混合器中添加数据 + public void addMuxerData(int trackIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) { + if (!isMuxerStarted) { + synchronized (lock) { + if (!isMuxerStarted) { + try { + lock.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + if (trackIndex == MEDIA_TRACK_AUDIO) { + mMediaMuxer.writeSampleData(mAudioTrack, buffer, info); + } else if (trackIndex == MEDIA_TRACK_VIDEO) { + mMediaMuxer.writeSampleData(mVideoTrack, buffer, info); + } + } + + /** + * 如果当前没有数据的话,会lock一直在wait,如果解码完成 + */ + //音频解码完成 + public void audioIsOver() { + isAudioOver = true; + editorFinish(); + } + + //视频解码编辑完成 + public void videoIsOver() { + isVideoOver = true; + editorFinish(); + } + public void editorFinish(){ + synchronized (lock){ + if(isAudioOver&&isVideoOver){ + stopMediaMuxer(); + if (mListener != null) { + mListener.onFinish(); + } + } + } + } + /** + * 停止MediaMuxer + */ + private void stopMediaMuxer() { + if (mMediaMuxer != null) { + mMediaMuxer.stop(); + mMediaMuxer.release(); + isAudioAdd = false; + isVideoAdd = false; + mMediaMuxer = null; + } + + } + + + public interface MuxerListener { + void onStart(); + + void onFinish(); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurface.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurface.java index 24446f6..27e8445 100644 --- a/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurface.java +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurface.java @@ -22,7 +22,7 @@ import com.example.cj.videoeditor.MyApplication; -import com.example.cj.videoeditor.drawer.VideoDrawer; +import com.example.cj.videoeditor.drawer.TextureRender; import com.example.cj.videoeditor.gpufilter.basefilter.GPUImageFilter; import com.example.cj.videoeditor.media.VideoInfo; @@ -59,8 +59,8 @@ class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { private Surface mSurface; private Object mFrameSyncObject = new Object(); // guards mFrameAvailable private boolean mFrameAvailable; -// private TextureRender mTextureRender; - private VideoDrawer mDrawer; + private TextureRender mTextureRender; +// private VideoDrawer mDrawer; /** * Creates an OutputSurface using the current EGL context. Creates a Surface that can be @@ -81,9 +81,20 @@ private void setup(VideoInfo info) { // mTextureRender = new TextureRender(info); // mTextureRender.surfaceCreated(); - mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources()); + /* mDrawer = new VideoDrawer(MyApplication.getContext(),MyApplication.getContext().getResources()); + mDrawer.onSurfaceCreated(null,null); - mDrawer.onSurfaceChanged(null,info.width,info.height); + if (info.rotation == 0 || info.rotation == 180){ + mDrawer.onSurfaceChanged(null,info.width,info.height); + }else { + mDrawer.onSurfaceChanged(null,info.height,info.width); + } + mDrawer.onVideoChanged(info);*/ + + mTextureRender = new TextureRender(info); + mTextureRender.surfaceCreated(); + mTextureRender.onVideoSizeChanged(info); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); // Even if we don't access the SurfaceTexture after the constructor returns, we // still need to keep a reference to it. The Surface doesn't retain a reference @@ -91,7 +102,7 @@ private void setup(VideoInfo info) { // causes the native finalizer to run. // if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); // mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); - mSurfaceTexture = mDrawer.getSurfaceTexture(); +// mSurfaceTexture = mDrawer.getSurfaceTexture(); // This doesn't work if OutputSurface is created on the thread that CTS started for // these test cases. @@ -198,8 +209,8 @@ public void release() { mEGLContext = null; mEGLSurface = null; mEGL = null; -// mTextureRender = null; - mDrawer = null; + mTextureRender = null; +// mDrawer = null; mSurface = null; mSurfaceTexture = null; } @@ -258,15 +269,15 @@ public void awaitNewImage() { // Latch the data. // mTextureRender.checkGlError("before updateTexImage"); // mDrawer.checkGlError("before updateTexImage"); -// mSurfaceTexture.updateTexImage(); + mSurfaceTexture.updateTexImage(); } /** * Draws the data from SurfaceTexture onto the current EGL surface. */ public void drawImage() { -// mTextureRender.drawFrame(mSurfaceTexture); - mDrawer.onDrawFrame(null); + mTextureRender.drawFrame(mSurfaceTexture); +// mDrawer.onDrawFrame(null); } @Override @@ -297,13 +308,15 @@ private void checkEglError(String msg) { } } public void addGpuFilter(GPUImageFilter filter){ - mDrawer.setGpuFilter(filter); +// mDrawer.setGpuFilter(filter); + mTextureRender.addGpuFilter(filter); } public void isBeauty(boolean isBeauty){ - mDrawer.isOpenBeauty(isBeauty); +// mDrawer.isOpenBeauty(isBeauty); +// mTextureRender.isOpenBeauty(isBeauty); } public void onVideoSizeChanged(VideoInfo info){ -// mTextureRender.onVideoSizeChanged(info); + mTextureRender.onVideoSizeChanged(info); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurfaceTwo.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurfaceTwo.java new file mode 100644 index 0000000..8864974 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/OutputSurfaceTwo.java @@ -0,0 +1,238 @@ +package com.example.cj.videoeditor.mediacodec; +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.graphics.SurfaceTexture; +import android.util.Log; +import android.view.Surface; + + +import com.example.cj.videoeditor.gpufilter.basefilter.GPUImageFilter; +import com.example.cj.videoeditor.media.VideoInfo; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * Holds state associated with a Surface used for MediaCodec decoder output. + *

+ * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, + * and then create a Surface for that SurfaceTexture. The Surface can be passed to + * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the + * texture with updateTexImage, then render the texture with GL to a pbuffer. + *

+ * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. + * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives + * we just draw it on whatever surface is current. + *

+ * By default, the Surface will be using a BufferQueue in asynchronous mode, so we + * can potentially drop frames. + */ +class OutputSurfaceTwo implements SurfaceTexture.OnFrameAvailableListener { + private static final String TAG = "OutputSurface"; + private static final boolean VERBOSE = false; + private static final int EGL_OPENGL_ES2_BIT = 4; + private EGL10 mEGL; + private EGLDisplay mEGLDisplay; + private EGLContext mEGLContext; + private EGLSurface mEGLSurface; + private SurfaceTexture mSurfaceTexture; + private Surface mSurface; + private Object mFrameSyncObject = new Object(); // guards mFrameAvailable + private boolean mFrameAvailable; + private TextureRender mTextureRender; + + /** + * Creates an OutputSurface using the current EGL context. Creates a Surface that can be + * passed to MediaCodec.configure(). + */ + public OutputSurfaceTwo(VideoInfo info) { + if (info.width <= 0 || info.height <= 0) { + throw new IllegalArgumentException(); + } + setup(info); + } + + /** + * Creates instances of TextureRender and SurfaceTexture, and a Surface associated + * with the SurfaceTexture. + */ + private void setup(VideoInfo info) { + mTextureRender = new TextureRender(info); + mTextureRender.surfaceCreated(); + // Even if we don't access the SurfaceTexture after the constructor returns, we + // still need to keep a reference to it. The Surface doesn't retain a reference + // at the Java level, so if we don't either then the object can get GCed, which + // causes the native finalizer to run. + if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + // This doesn't work if OutputSurface is created on the thread that CTS started for + // these test cases. + // + // The CTS-created thread has a Looper, and the SurfaceTexture constructor will + // create a Handler that uses it. The "frame available" message is delivered + // there, but since we're not a Looper-based thread we'll never see it. For + // this to do anything useful, OutputSurface must be created on a thread without + // a Looper, so that SurfaceTexture uses the main application Looper instead. + // + // Java language note: passing "this" out of a constructor is generally unwise, + // but we should be able to get away with it here. + mSurfaceTexture.setOnFrameAvailableListener(this); + mSurface = new Surface(mSurfaceTexture); + } + + /** + * just for clip + * @param info + * @param clipMode + */ + public OutputSurfaceTwo(VideoInfo info, int clipMode) { + if (info.width <= 0 || info.height <= 0) { + throw new IllegalArgumentException(); + } + mTextureRender = new TextureRender(info); + mTextureRender.setClipMode(clipMode); + mTextureRender.surfaceCreated(); + if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + mSurfaceTexture.setOnFrameAvailableListener(this); + mSurface = new Surface(mSurfaceTexture); + } + + /** + * Discard all resources held by this class, notably the EGL context. + */ + public void release() { + if (mEGL != null) { + if (mEGL.eglGetCurrentContext().equals(mEGLContext)) { + // Clear the current context and surface to ensure they are discarded immediately. + mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + } + mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); + mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); + //mEGL.eglTerminate(mEGLDisplay); + } + mSurface.release(); + // this causes a bunch of warnings that appear harmless but might confuse someone: + // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned! + //mSurfaceTexture.release(); + // null everything out so future attempts to use this object will cause an NPE + mEGLDisplay = null; + mEGLContext = null; + mEGLSurface = null; + mEGL = null; + mTextureRender = null; + mSurface = null; + mSurfaceTexture = null; + } + + /** + * Makes our EGL context and surface current. + */ + public void makeCurrent() { + if (mEGL == null) { + throw new RuntimeException("not configured for makeCurrent"); + } + checkEglError("before makeCurrent"); + if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + + /** + * Returns the Surface that we draw onto. + */ + public Surface getSurface() { + return mSurface; + } + + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + mTextureRender.changeFragmentShader(fragmentShader); + } + + /** + * Latches the next buffer into the texture. Must be called from the thread that created + * the OutputSurface object, after the onFrameAvailable callback has signaled that new + * data is available. + */ + public void awaitNewImage() { + final int TIMEOUT_MS = 500; + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + // Wait for onFrameAvailable() to signal us. Use a timeout to avoid + // stalling the test if it doesn't arrive. + mFrameSyncObject.wait(TIMEOUT_MS); + if (!mFrameAvailable) { + // TODO: if "spurious wakeup", continue while loop + throw new RuntimeException("Surface frame wait timed out"); + } + } catch (InterruptedException ie) { + // shouldn't happen + throw new RuntimeException(ie); + } + } + mFrameAvailable = false; + } + // Latch the data. + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + } + + /** + * Draws the data from SurfaceTexture onto the current EGL surface. + */ + public void drawImage() { + mTextureRender.drawFrame(mSurfaceTexture); + } + + @Override + public void onFrameAvailable(SurfaceTexture st) { + if (VERBOSE) Log.d(TAG, "new frame available"); + synchronized (mFrameSyncObject) { + if (mFrameAvailable) { + throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); + } + mFrameAvailable = true; + mFrameSyncObject.notifyAll(); + } + } + + /** + * Che cks for EGL errors. + */ + private void checkEglError(String msg) { + boolean failed = false; + int error; + while ((error = mEGL.eglGetError()) != EGL10.EGL_SUCCESS) { + Log.e(TAG, msg + ": EGL error: 0x" + Integer.toHexString(error)); + failed = true; + } + if (failed) { + throw new RuntimeException("EGL error encountered (see log)"); + } + } + + public void onVideoSizeChanged(VideoInfo info){ + mTextureRender.onVideoSizeChanged(info); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/TextureRender.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/TextureRender.java new file mode 100644 index 0000000..b860b69 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/TextureRender.java @@ -0,0 +1,397 @@ +package com.example.cj.videoeditor.mediacodec; + +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.graphics.SurfaceTexture; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.util.Log; + + +import com.example.cj.videoeditor.Constants; +import com.example.cj.videoeditor.MyApplication; +import com.example.cj.videoeditor.filter.AFilter; +import com.example.cj.videoeditor.filter.NoFilter; +import com.example.cj.videoeditor.filter.RotationOESFilter; +import com.example.cj.videoeditor.gpufilter.basefilter.GPUImageFilter; +import com.example.cj.videoeditor.media.VideoInfo; +import com.example.cj.videoeditor.utils.EasyGlUtils; +import com.example.cj.videoeditor.utils.MatrixUtils; +import com.example.cj.videoeditor.utils.OpenGlUtils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Code for rendering a texture onto a surface using OpenGL ES 2.0. + */ +class TextureRender { + private static final String TAG = "TextureRender"; + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 1.f, + 1.0f, -1.0f, 0, 1.f, 1.f, + -1.0f, 1.0f, 0, 0.f, 0.f, + 1.0f, 1.0f, 0, 1.f, 0.f, + }; + private FloatBuffer mTriangleVertices; + + private float[] mMVPMatrix = new float[16]; + private float[] mSTMatrix = new float[16]; + private int mProgram; + private int mTextureID = -12345; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + + //======================clip======================== + float[] SM = new float[16]; //用于显示的变换矩阵 + boolean isClipMode; + int curMode; + int clipViewWidth; + int clipViewHeight; + int clipEncodeWidth; + int clipEncodeHeight; + + + //======================zoom======================== + //创建帧缓冲区 + private int[] fFrame = new int[1]; + private int[] fTexture = new int[2]; + AFilter mShow; + RotationOESFilter rotationFilter; + GPUImageFilter mGpuFilter; + //第一段视频宽高(旋转后) + int viewWidth; + int viewHeight; + //当前视频宽高(旋转后) + int videoWidth; + int videoHeight; + //最终显示的宽高 + int width; + int height; + int x; + int y; + boolean videoChanged = false; + //第一段视频信息 + VideoInfo info; + + public TextureRender(VideoInfo info) { + this.info=info; + mTriangleVertices = ByteBuffer.allocateDirect( + mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + Matrix.setIdentityM(mSTMatrix, 0); + mShow = new NoFilter(MyApplication.getContext().getResources()); + mShow.setMatrix(MatrixUtils.flip(MatrixUtils.getOriginalMatrix(), false, true)); + rotationFilter = new RotationOESFilter(MyApplication.getContext().getResources()); + } + + public int getTextureId() { + return mTextureID; + } + + public void drawFrame(SurfaceTexture st) { + if(!isClipMode){ + zoomDraw(st); + }else{ + clipDraw(st); + } + + } + public void clipDraw(SurfaceTexture st){ + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[0]); + GLES20.glViewport(0, 0, clipViewWidth, clipViewHeight); + rotationFilter.draw(); + EasyGlUtils.unBindFrameBuffer(); + + if (mGpuFilter != null) { + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[1]); + mGpuFilter.onDrawFrame(fTexture[0]); + EasyGlUtils.unBindFrameBuffer(); + } + GLES20.glViewport(0, 0, clipEncodeWidth, clipEncodeHeight); + mShow.setTextureId(fTexture[mGpuFilter == null ? 0 : 1]); + mShow.draw(); + GLES20.glFinish(); + } + public void zoomDraw(SurfaceTexture st){ + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[0]); + GLES20.glViewport(0, 0, viewWidth, viewHeight); + rotationFilter.draw(); + EasyGlUtils.unBindFrameBuffer(); + + if (mGpuFilter != null) { + EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[1]); + mGpuFilter.onDrawFrame(fTexture[0]); + EasyGlUtils.unBindFrameBuffer(); + } + + if (videoChanged) { + GLES20.glViewport(x, y, width, height); + } + + mShow.setTextureId(fTexture[mGpuFilter == null ? 0 : 1]); + mShow.draw(); + GLES20.glFinish(); + } + + public void preDraw(SurfaceTexture st) { + checkGlError("onDrawFrame start"); +// st.getTransformMatrix(mSTMatrix);//当视频角度不为0时,这里会导致视频方向不对 + GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + Matrix.setIdentityM(mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + } + + /** + * Initializes GL state. Call this after the EGL surface has been created and made current. + */ + public void surfaceCreated() { + mProgram = createProgram(OpenGlUtils.uRes( + "shader/base_record_vertex.sh"),OpenGlUtils.uRes("shader/base_record_fragment")); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + if (maPositionHandle == -1) { + throw new RuntimeException("Could not get attrib location for aPosition"); + } + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + if (maTextureHandle == -1) { + throw new RuntimeException("Could not get attrib location for aTextureCoord"); + } + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkGlError("glGetUniformLocation uMVPMatrix"); + if (muMVPMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uMVPMatrix"); + } + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + if (muSTMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uSTMatrix"); + } + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR);//改为线性过滤,是画面更加平滑(抗锯齿) + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + + mShow.create(); + rotationFilter.create(); + rotationFilter.setTextureId(mTextureID); + GLES20.glGenFramebuffers(1, fFrame, 0); + + if(!isClipMode){ + if (info.rotation == 0 || info.rotation == 180) { + EasyGlUtils.genTexturesWithParameter(2, fTexture, 0, GLES20.GL_RGBA, info.width, info.height); + viewWidth = info.width; + viewHeight = info.height; + } else { + EasyGlUtils.genTexturesWithParameter(2, fTexture, 0, GLES20.GL_RGBA, info.height, info.width); + viewWidth = info.height; + viewHeight = info.width; + } + }else{ + switch (curMode){ + case Constants.MODE_POR_9_16: + clipViewWidth=Constants.mode_por_width_9_16; + clipViewHeight=Constants.mode_por_height_9_16; + clipEncodeWidth=Constants.mode_por_encode_width_9_16; + clipEncodeHeight=Constants.mode_por_encode_height_9_16; + break; + case Constants.MODE_POR_1_1: + clipViewWidth=Constants.mode_por_width_1_1; + clipViewHeight=Constants.mode_por_height_1_1; + clipEncodeWidth=Constants.mode_por_encode_width_1_1; + clipEncodeHeight=Constants.mode_por_encode_height_1_1; + break; + case Constants.MODE_POR_16_9: + clipViewWidth=Constants.mode_por_width_16_9; + clipViewHeight=Constants.mode_por_height_16_9; + clipEncodeWidth=Constants.mode_por_encode_width_16_9; + clipEncodeHeight=Constants.mode_por_encode_height_16_9; + break; + } + EasyGlUtils.genTexturesWithParameter(2, fTexture, 0, GLES20.GL_RGBA, clipViewWidth, clipViewHeight); + if (info.rotation == 0 || info.rotation == 180) { + MatrixUtils.getShowMatrix(SM,info.width,info.height,clipViewWidth,clipViewHeight); + } else { + MatrixUtils.getShowMatrix(SM,info.height,info.width,clipViewWidth,clipViewHeight); + } + rotationFilter.setMatrix(SM); + } + rotationFilter.setRotation(info.rotation); + } + + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + GLES20.glDeleteProgram(mProgram); + mProgram = createProgram(OpenGlUtils.uRes("shader/base_record_vertex.sh"), fragmentShader); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + + public void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, op + ": glError " + error); + throw new RuntimeException(op + ": glError " + error); + } + } + + public void addGpuFilter(GPUImageFilter filter) { + if (mGpuFilter != null) { + mGpuFilter.destroy(); + } + mGpuFilter = filter; + if (filter != null) { + mGpuFilter.init(); + mGpuFilter.onDisplaySizeChanged(info.width, info.height); + mGpuFilter.onInputSizeChanged(info.width, info.height); + } + } + + public void onVideoSizeChanged(VideoInfo info) { + setVideoWidthAndHeight(info); + adjustVideoPosition(); + videoChanged = true; + } + + public void setVideoWidthAndHeight(VideoInfo info) { + rotationFilter.setRotation(info.rotation); + if (info.rotation == 0 || info.rotation == 180) { + this.videoWidth = info.width; + this.videoHeight = info.height; + } else { + this.videoWidth = info.height; + this.videoHeight = info.width; + } + } + + private void adjustVideoPosition() { + float w = (float) viewWidth / videoWidth; + float h = (float) viewHeight / videoHeight; + if (w < h) { + width = viewWidth; + height = (int) ((float) videoHeight * w); + } else { + width = (int) ((float) videoWidth * h); + height = viewHeight; + } + x = (viewWidth - width) / 2; + y = (viewHeight - height) / 2; + } + + /** + * just for clip video + * @param curMode + */ + public void setClipMode(int curMode) { + isClipMode=true; + this.curMode = curMode; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoClipper.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoClipper.java index 252c2d1..cf354ec 100644 --- a/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoClipper.java +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoClipper.java @@ -32,38 +32,39 @@ public class VideoClipper { private String mInputVideoPath; private String mOutputVideoPath; - MediaCodec videoDecoder; - MediaCodec videoEncoder; - MediaCodec audioDecoder; - MediaCodec audioEncoder; - - MediaExtractor mVideoExtractor; - MediaExtractor mAudioExtractor; - MediaMuxer mMediaMuxer; - static ExecutorService executorService = Executors.newFixedThreadPool(4); - int muxVideoTrack = -1; - int muxAudioTrack = -1; - int videoTrackIndex = -1; - int audioTrackIndex = -1; - long startPosition; - long clipDur; - int videoWidth; - int videoHeight; - int videoRotation; - OutputSurface outputSurface = null; - InputSurface inputSurface = null; - MediaFormat videoFormat; - MediaFormat audioFormat; - GPUImageFilter mFilter; - boolean isOpenBeauty; - boolean videoFinish = false; - boolean audioFinish = false; - boolean released = false; - long before; - long after; - Object lock = new Object(); - boolean muxStarted = false; - OnVideoCutFinishListener listener; + private MediaCodec videoDecoder; + private MediaCodec videoDecoder2; + private MediaCodec videoEncoder; + private MediaCodec audioDecoder; + private MediaCodec audioEncoder; + + private MediaExtractor mVideoExtractor; + private MediaExtractor mAudioExtractor; + private MediaMuxer mMediaMuxer; + private static ExecutorService executorService = Executors.newFixedThreadPool(4); + private int muxVideoTrack = -1; + private int muxAudioTrack = -1; + private int videoTrackIndex = -1; + private int audioTrackIndex = -1; + private long startPosition; + private long clipDur; + private int videoWidth; + private int videoHeight; + private int videoRotation; + private OutputSurface outputSurface = null; + private InputSurface inputSurface = null; + private MediaFormat videoFormat; + private MediaFormat audioFormat; + private GPUImageFilter mFilter; + private boolean isOpenBeauty; + private boolean videoFinish = false; + private boolean audioFinish = false; + private boolean released = false; + private long before; + private long after; + private Object lock = new Object(); + private boolean muxStarted = false; + private OnVideoCutFinishListener listener; //初始化音视频解码器和编码器 public VideoClipper() { @@ -90,16 +91,18 @@ public void setOutputVideoPath(String outputPath) { public void setOnVideoCutFinishListener(OnVideoCutFinishListener listener) { this.listener = listener; } + /** * 设置滤镜 - * */ + */ public void setFilter(GPUImageFilter filter) { - if (filter == null ) { + if (filter == null) { mFilter = null; return; } mFilter = filter; } + public void setFilterType(MagicFilterType type) { if (type == null || type == MagicFilterType.NONE) { mFilter = null; @@ -110,8 +113,8 @@ public void setFilterType(MagicFilterType type) { /** * 开启美颜 - * */ - public void showBeauty(){ + */ + public void showBeauty() { isOpenBeauty = true; } @@ -157,7 +160,7 @@ public void run() { long firstVideoTime = mVideoExtractor.getSampleTime(); mVideoExtractor.seekTo(firstVideoTime + startPosition, SEEK_TO_PREVIOUS_SYNC); - + Log.e("hero","_____videoCliper------run"); initVideoCodec();//暂时统一处理,为音频转换采样率做准备 startVideoCodec(videoDecoder, videoEncoder, mVideoExtractor, inputSurface, outputSurface, firstVideoTime, startPosition, clipDur); @@ -210,10 +213,10 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac boolean inputDone = false; boolean decodeDone = false; extractor.seekTo(firstSampleTime + startPosition, SEEK_TO_PREVIOUS_SYNC); - int decodeinput=0; - int encodeinput=0; - int encodeoutput=0; - long lastEncodeOutputTimeStamp=-1; + int decodeinput = 0; + int encodeinput = 0; + int encodeoutput = 0; + long lastEncodeOutputTimeStamp = -1; while (!done) { if (!inputDone) { int inputIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); @@ -225,7 +228,7 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac if ((dur < duration) && readSampleData > 0) { decoder.queueInputBuffer(inputIndex, 0, readSampleData, extractor.getSampleTime(), 0); decodeinput++; - System.out.println("videoCliper audio decodeinput"+decodeinput+" dataSize"+readSampleData+" sampeTime"+extractor.getSampleTime()); + System.out.println("videoCliper audio decodeinput" + decodeinput + " dataSize" + readSampleData + " sampeTime" + extractor.getSampleTime()); extractor.advance(); } else { decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); @@ -247,11 +250,16 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac } else { boolean canEncode = (info.size != 0 && info.presentationTimeUs - firstSampleTime > startPosition); boolean endOfStream = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; - if (canEncode&&!endOfStream) { - ByteBuffer decoderOutputBuffer = decoderOutputBuffers[index]; + if (canEncode && !endOfStream) { + ByteBuffer decoderOutputBuffer; + if (Build.VERSION.SDK_INT >= 21){ + decoderOutputBuffer = decoder.getOutputBuffer(index); + }else { + decoderOutputBuffer = decoderOutputBuffers[index]; + } int encodeInputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); - if(encodeInputIndex>=0){ + if (encodeInputIndex >= 0) { ByteBuffer encoderInputBuffer = encoderInputBuffers[encodeInputIndex]; encoderInputBuffer.clear(); if (info.size < 4096) {//这里看起来应该是16位单声道转16位双声道 @@ -269,16 +277,16 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac encoderInputBuffer.put(stereoBytes); encoder.queueInputBuffer(encodeInputIndex, 0, stereoBytes.length, info.presentationTimeUs, 0); encodeinput++; - System.out.println("videoCliper audio encodeInput"+encodeinput+" dataSize"+info.size+" sampeTime"+info.presentationTimeUs); - }else{ + System.out.println("videoCliper audio encodeInput" + encodeinput + " dataSize" + info.size + " sampeTime" + info.presentationTimeUs); + } else { encoderInputBuffer.put(decoderOutputBuffer); encoder.queueInputBuffer(encodeInputIndex, info.offset, info.size, info.presentationTimeUs, 0); encodeinput++; - System.out.println("videoCliper audio encodeInput"+encodeinput+" dataSize"+info.size+" sampeTime"+info.presentationTimeUs); + System.out.println("videoCliper audio encodeInput" + encodeinput + " dataSize" + info.size + " sampeTime" + info.presentationTimeUs); } } } - if(endOfStream){ + if (endOfStream) { int encodeInputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); encoder.queueInputBuffer(encodeInputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); System.out.println("videoCliper audio encodeInput end"); @@ -309,12 +317,12 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac if (outputInfo.presentationTimeUs == 0 && !done) { continue; } - if (outputInfo.size != 0&&outputInfo.presentationTimeUs>0) { + if (outputInfo.size != 0 && outputInfo.presentationTimeUs > 0) { /*encodedData.position(outputInfo.offset); encodedData.limit(outputInfo.offset + outputInfo.size);*/ - if(!muxStarted){ - synchronized (lock){ - if(!muxStarted){ + if (!muxStarted) { + synchronized (lock) { + if (!muxStarted) { try { lock.wait(); } catch (InterruptedException e) { @@ -323,11 +331,11 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac } } } - if(outputInfo.presentationTimeUs>lastEncodeOutputTimeStamp){//为了避免有问题的数据 + if (outputInfo.presentationTimeUs > lastEncodeOutputTimeStamp) {//为了避免有问题的数据 encodeoutput++; - System.out.println("videoCliper audio encodeOutput"+encodeoutput+" dataSize"+outputInfo.size+" sampeTime"+outputInfo.presentationTimeUs); + System.out.println("videoCliper audio encodeOutput" + encodeoutput + " dataSize" + outputInfo.size + " sampeTime" + outputInfo.presentationTimeUs); mMediaMuxer.writeSampleData(muxAudioTrack, encodedData, outputInfo); - lastEncodeOutputTimeStamp=outputInfo.presentationTimeUs; + lastEncodeOutputTimeStamp = outputInfo.presentationTimeUs; } } @@ -343,10 +351,19 @@ private void startAudioCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac private void initVideoCodec() { //不对视频进行压缩 - int encodeW = videoWidth; - int encodeH = videoHeight; + VideoInfo info = new VideoInfo(); + info.width = videoWidth; + info.height = videoHeight; + info.rotation = videoRotation; + + MediaFormat mediaFormat; + if (info.rotation == 0 || info.rotation == 180) { + mediaFormat = MediaFormat.createVideoFormat("video/avc", info.width, info.height); + }else { + mediaFormat = MediaFormat.createVideoFormat("video/avc", info.height, info.width); + } //设置视频的编码参数 - MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", encodeW, encodeH); + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 3000000); mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); @@ -356,16 +373,11 @@ private void initVideoCodec() { inputSurface.makeCurrent(); videoEncoder.start(); - - VideoInfo info = new VideoInfo(); - info.width = videoWidth; - info.height = videoHeight; - info.rotation = videoRotation; outputSurface = new OutputSurface(info); - outputSurface.isBeauty(isOpenBeauty); +// outputSurface.isBeauty(isOpenBeauty); if (mFilter != null) { - Log.e("hero","---gpuFilter 不为null哟----设置进outputSurface里面"); + Log.e("hero", "---gpuFilter 不为null哟----设置进outputSurface里面"); outputSurface.addGpuFilter(mFilter); } @@ -462,9 +474,9 @@ private void startVideoCodec(MediaCodec decoder, MediaCodec encoder, MediaExtrac if (outputInfo.size != 0) { encodedData.position(outputInfo.offset); encodedData.limit(outputInfo.offset + outputInfo.size); - if(!muxStarted){ - synchronized (lock){ - if(!muxStarted){ + if (!muxStarted) { + synchronized (lock) { + if (!muxStarted) { try { lock.wait(); } catch (InterruptedException e) { diff --git a/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoRunnable.java b/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoRunnable.java new file mode 100644 index 0000000..881e0f9 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/mediacodec/VideoRunnable.java @@ -0,0 +1,368 @@ +package com.example.cj.videoeditor.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.os.Build; +import android.util.Log; + + +import com.example.cj.videoeditor.media.MediaCodecInfo; +import com.example.cj.videoeditor.media.VideoInfo; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by cj on 2017/6/30. + * desc 视频编解码线程 + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class VideoRunnable extends Thread { + private static final String MIME_TYPE = "video/avc"; + private static final int bitRate = 3000000; //视频编码波特率 + private static final int frameRate = 30; //视频编码帧率 + private static final int frameInterval = 1; + private MediaMuxerRunnable mMediaMuxer; + +// private GPUImageFilter filter; + + private MediaFormat videoOutputFormat; + + //处理多段视频 + private List mVideoInfos;//多段视频的信息 + private List mExtractors; + private List mMediaCodecInfos; + private VideoInfo mInfo; + + public VideoRunnable(List inputFiles, MediaMuxerRunnable mediaMuxer) { + this.mMediaMuxer = mediaMuxer; + this.mVideoInfos = inputFiles; +// this.filter = MagicFilterFactory.initFilters(filterType); + } + + @Override + public void run() { + try { + prepare(); + editVideo(videoOutputFormat); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void editVideo(MediaFormat outputFormat) throws IOException { + MediaCodec videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE); + videoEncoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + InputSurface inputSurface = new InputSurface(videoEncoder.createInputSurface()); + inputSurface.makeCurrent(); + videoEncoder.start();//编码器启动 + + OutputSurface outputSurface = new OutputSurface(mInfo); + + List decodeList = new ArrayList<>(); + List formatList = new ArrayList<>(); + try { + //初始化decoder,format,extractor + for (int i = 0; i < mVideoInfos.size(); i++) { + MediaExtractor extractor = mExtractors.get(i); + int trackCount = extractor.getTrackCount(); + for (int j = 0; j < trackCount; j++) { + MediaFormat format = extractor.getTrackFormat(j); + if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) { + MediaCodec videoDecoder = MediaCodec.createDecoderByType(MIME_TYPE); + decodeList.add(videoDecoder); + formatList.add(format); + extractor.selectTrack(j); + break; + } + } + } + + editVideoData(mMediaCodecInfos, decodeList, formatList, outputSurface, inputSurface, videoEncoder); + } finally { + + outputSurface.release(); + inputSurface.release(); + videoEncoder.stop(); + videoEncoder.release(); + } + } + + //重构分离数据 解码数据 编码数据的流程 + private void editVideoData(List mediaCodecInfos, List decodeList, + List formatList, OutputSurface outputSurface, + InputSurface inputSurface, MediaCodec videoEncoder) { + long start = System.currentTimeMillis(); + final int TIMEOUT_USEC = 0; + /* + * 1、初始化第一个解码器 + * */ + MediaCodec decoder = decodeList.get(0); + decoder.configure(formatList.get(0), outputSurface.getSurface(), null, 0); + decoder.start(); + ByteBuffer[] inputBuffers = decoder.getInputBuffers(); + + /* + * 2、初始化第一个分离器 + * */ + MediaCodecInfo mediaCodecInfo = mediaCodecInfos.get(0); + + /* 3、初始化编码器*/ + ByteBuffer[] encoderOutputBuffers = videoEncoder.getOutputBuffers(); + /* + * 4、初始化解码器和编码器的bufferInfo + * */ + MediaCodec.BufferInfo encodeOutputInfo = new MediaCodec.BufferInfo(); + MediaCodec.BufferInfo decodeOutputInfo = new MediaCodec.BufferInfo(); + + /* + * 5、初始化while循环 while循环判断结束的标准在于 所有编码器中的数据 都已经添加到了分离器 + * */ + boolean outputDone = false;//是否整体编解码完毕 + boolean isNextStep = false;//是否新加了一段视频 + boolean isFirstDecodeInputFrame = true;//是否是解码输入的第一帧 + boolean isFirstDecodeOutputFrame = true;//是否是第一段视频的第一帧 + boolean inputDone = false;//是否输入结束 + boolean isChangeVideo = false;//是否切换视频 + + int curVideoIndex = 0;//当前解码视频的位置 + + long decodeInputTimeStamp = 0; + long lastInputTime = 0; + long encodeInputTimeStamp = 0; + long lastVideoTime = 0; + + while (!outputDone) { + /* + * 6、循环的第一步 从分离器中取数据 写入到解码器中 + * */ + if (!inputDone) { + int inputIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); + Log.e("videoo", "---解码器 是否可用 " + inputIndex); + if (inputIndex >= 0) { + /*说明解码器有可用的ByteBuffer*/ + ByteBuffer inputBuffer = inputBuffers[inputIndex]; + inputBuffer.clear(); + /*从分离器中取数据*/ + int readSampleData = mediaCodecInfo.extractor.readSampleData(inputBuffer, 0); + if (readSampleData < 0) { + /*说明该分离器中 没有数据了 发送一个解码流结束的标志位*/ + Log.e("send", "-----发送end--flag"); + inputDone = true; + decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + } else { + /* + * 重写输入数据的时间戳 + * 关键点在于 如果是下一段视频的数据 + * 那么 + 30000 + * */ + if (isNextStep) { + //说明是新增了一段视频 + decodeInputTimeStamp += 30000; + isNextStep = false; + } else { + if (isFirstDecodeInputFrame) { + decodeInputTimeStamp = 0; + isFirstDecodeInputFrame = false; + } else { + decodeInputTimeStamp += mediaCodecInfo.extractor.getSampleTime() - lastInputTime; + } + } + lastInputTime = mediaCodecInfo.extractor.getSampleTime(); + /*将分离器的数据 插入解码器中*/ + decoder.queueInputBuffer(inputIndex, 0, readSampleData, decodeInputTimeStamp, 0); + mediaCodecInfo.extractor.advance(); + } + } + } + + /* + * 7、循环的第二步,轮询取出解码器解码完成的数据 并且加入到编码器中 + * */ + boolean decodeOutputDone = false; + boolean encodeDone = false; + while (!decodeOutputDone || !encodeDone) { + /*说明解码器的输出output有数据*/ + int outputIndex = decoder.dequeueOutputBuffer(decodeOutputInfo, TIMEOUT_USEC); + Log.e("videoo", " 解码器出来的index " + outputIndex); + if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + /*没有可用的解码器output*/ + decodeOutputDone = true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + + } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + MediaFormat newFormat = decoder.getOutputFormat(); + } else if (outputIndex < 0) { + } else { + /* + * 8、判断本次是否有数据 以及本次数据是否需要传入编码器 + * */ + boolean doRender = (decodeOutputInfo.size != 0); + /* + * 9、根据当前解码出来的数据的时间戳 判断 是否需要写入编码器 + * */ + boolean isUseful = true; + if (decodeOutputInfo.presentationTimeUs <= 0) { + doRender = false; + } + + decoder.releaseOutputBuffer(outputIndex, doRender && isUseful); + + if (doRender && isUseful) { + /* + * 是有效数据 让他写到编码器中 + * 并且对时间戳 进行重写 + * */ + Log.e("videoo", "---卡主了? 一 " + decodeOutputInfo.size); + outputSurface.awaitNewImage(); + Log.e("videoo", "---卡住了 === 二"); + outputSurface.drawImage(); + Log.e("videoo", "---卡住了 === 三!!!"); + if (isFirstDecodeOutputFrame) { + /*如果是第一个视频的话,有可能时间戳不是从0 开始的 所以需要初始化*/ + isFirstDecodeOutputFrame = false; + } else { + /* + * 如果是更换了一个视频源 就+30000us + */ + if (isChangeVideo) { + Log.e("videoo", "---更换了一个视频源===+30000"); + isChangeVideo = false; + encodeInputTimeStamp = (encodeInputTimeStamp + 30000); + } else { + encodeInputTimeStamp = (encodeInputTimeStamp + (decodeOutputInfo.presentationTimeUs - lastVideoTime)); + } + } + Log.e("videooo", "---在编码画面帧的时候,重置时间戳===" + encodeInputTimeStamp); + inputSurface.setPresentationTime(encodeInputTimeStamp * 1000); + inputSurface.swapBuffers(); + + } else { + Log.e("videoo", "---解码出来的视频有问题=== " + doRender + " " + isUseful); + } + lastVideoTime = decodeOutputInfo.presentationTimeUs; + + if ((decodeOutputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + /** + * 解码器解码完成了,说明该分离器的数据写入完成了 并且都已经解码完成了 + * 更换分离器和解码器或者结束编解码 + * */ + curVideoIndex++; + if (curVideoIndex < mediaCodecInfos.size()) { + /*说明还有需要解码的*/ + /* + * 1)、更换分离器 + * 2)、更换解码器 + * */ + mediaCodecInfo.extractor.release(); + mediaCodecInfo = mediaCodecInfos.get(curVideoIndex); + + decoder.stop(); + decoder.release(); + decoder = decodeList.get(curVideoIndex); + decoder.configure(formatList.get(curVideoIndex), outputSurface.getSurface(), null, 0); + decoder.start(); + inputBuffers = decoder.getInputBuffers(); + outputSurface.onVideoSizeChanged(mVideoInfos.get(curVideoIndex)); + inputDone = false;//解码器继续写入数据 + isChangeVideo = true; + isNextStep = true; + Log.e("videoo", "---更换分离器 and 解码器---=="); + } else { + /*没有数据了 就给编码器发送一个结束的标志位*/ + videoEncoder.signalEndOfInputStream(); + inputDone = true; + Log.e("videoo", "---所有视频都解码完成了 告诉编码器 可以结束了---=="); + } + } + } + /* + * 10、从编码器中取数据 重写时间戳 添加到混合器中 + * 如果发生了更换视频源 那么 就先把编码器的所有数据读取出来 + * 然后再向解码器输入数据 以确保编码器时间戳的正确性 + * */ + while (!encodeDone) { + int encodeOutputState = videoEncoder.dequeueOutputBuffer(encodeOutputInfo, TIMEOUT_USEC); + if (encodeOutputState == MediaCodec.INFO_TRY_AGAIN_LATER) { + /* 说明没有可用的编码器 */ + encodeDone = true; + } else if (encodeOutputState == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + encoderOutputBuffers = videoEncoder.getOutputBuffers(); + } else if (encodeOutputState == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + /*初始化混合器的MediaFormat*/ + MediaFormat newFormat = videoEncoder.getOutputFormat(); + mMediaMuxer.addMediaFormat(MediaMuxerRunnable.MEDIA_TRACK_VIDEO, newFormat); + Log.e("videoo", "---添加MediaFormat"); + } else if (encodeOutputState < 0) { + } else { + /*判断编码器是否完成了编码*/ + outputDone = (encodeOutputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; + if (outputDone) { + break; + } + if ((encodeOutputInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + encodeOutputInfo.size = 0; + } + ByteBuffer encoderOutputBuffer = encoderOutputBuffers[encodeOutputState]; + if (encodeOutputInfo.size != 0) { + Log.e("videoo", "--写入混合器的数据----presentationTime===" + encodeOutputInfo.presentationTimeUs + "===size===" + encodeOutputInfo.size + "----flags==" + encodeOutputInfo.flags); + mMediaMuxer.addMuxerData(MediaMuxerRunnable.MEDIA_TRACK_VIDEO, encoderOutputBuffer, encodeOutputInfo); + } + videoEncoder.releaseOutputBuffer(encodeOutputState, false); + } + } + } + } + //释放最后一个decoder + decoder.stop(); + decoder.release(); + mMediaMuxer.videoIsOver(); + long end = System.currentTimeMillis(); + Log.e("timee", "---视频编码完成---视频编码耗时-==" + (end - start)); + } + + + private void prepare() throws IOException { + mExtractors = new ArrayList<>(); + mMediaCodecInfos = new ArrayList<>(); + + //初始化所以的分离器 + for (int i = 0; i < mVideoInfos.size(); i++) { + MediaExtractor temp = new MediaExtractor(); + VideoInfo videoInfo = mVideoInfos.get(i); + temp.setDataSource(videoInfo.path); + mExtractors.add(temp); + //多个视频剪切,根据视频所在位置 对本视频剪切点进行调整 + MediaCodecInfo decode = new MediaCodecInfo(); + decode.path = videoInfo.path; + decode.extractor = temp; + decode.duration = videoInfo.duration; + mMediaCodecInfos.add(decode); + } + + MediaExtractor mExtractor = mExtractors.get(0); + int trackCount = mExtractor.getTrackCount(); + for (int i = 0; i < trackCount; i++) {//根据第一个视频信息来确定编码信息 + MediaFormat trackFormat = mExtractor.getTrackFormat(i); + String mime = trackFormat.getString(MediaFormat.KEY_MIME); + if (mime.startsWith("video/")) { + mInfo = mVideoInfos.get(0); + if (mInfo.rotation == 0 || mInfo.rotation == 180) { + videoOutputFormat = MediaFormat.createVideoFormat(MIME_TYPE, mInfo.width, mInfo.height); + } else { + videoOutputFormat = MediaFormat.createVideoFormat(MIME_TYPE, mInfo.height, mInfo.width); + } + videoOutputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, + android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + videoOutputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); + videoOutputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); + videoOutputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, frameInterval); + break; + } + } + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/utils/OpenGlUtils.java b/app/src/main/java/com/example/cj/videoeditor/utils/OpenGlUtils.java new file mode 100644 index 0000000..28cd528 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/utils/OpenGlUtils.java @@ -0,0 +1,31 @@ +package com.example.cj.videoeditor.utils; + +import android.content.res.Resources; + +import com.example.cj.videoeditor.MyApplication; + +import java.io.InputStream; + +/** + * Created by qqche_000 on 2018/6/3. + * 用于OpenGl的工具类 + */ + +public class OpenGlUtils { + //通过资源路径加载shader脚本文件 + public static String uRes(String path) { + Resources resources = MyApplication.getContext().getResources(); + StringBuilder result = new StringBuilder(); + try { + InputStream is = resources.getAssets().open(path); + int ch; + byte[] buffer = new byte[1024]; + while (-1 != (ch = is.read(buffer))) { + result.append(new String(buffer, 0, ch)); + } + } catch (Exception e) { + return null; + } + return result.toString().replaceAll("\\r\\n", "\n"); + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/widget/CameraView.java b/app/src/main/java/com/example/cj/videoeditor/widget/CameraView.java index 7e7a22d..559a2ad 100644 --- a/app/src/main/java/com/example/cj/videoeditor/widget/CameraView.java +++ b/app/src/main/java/com/example/cj/videoeditor/widget/CameraView.java @@ -26,14 +26,14 @@ public class CameraView extends GLSurfaceView implements GLSurfaceView.Renderer, private CameraDrawer mCameraDrawer; private CameraController mCamera; - private int dataWidth=0,dataHeight=0; + private int dataWidth = 0, dataHeight = 0; private boolean isSetParm = false; private int cameraId; public CameraView(Context context) { - this(context,null); + this(context, null); } public CameraView(Context context, AttributeSet attrs) { @@ -56,68 +56,78 @@ private void init() { mCamera = new CameraController(); } - private void open(int cameraId){ + + private void open(int cameraId) { mCamera.close(); mCamera.open(cameraId); mCameraDrawer.setCameraId(cameraId); - final Point previewSize=mCamera.getPreviewSize(); - dataWidth=previewSize.x; - dataHeight=previewSize.y; + final Camera.Size previewSize = mCamera.getPreviewSize(); + dataWidth = previewSize.width; + dataHeight = previewSize.height; SurfaceTexture texture = mCameraDrawer.getTexture(); texture.setOnFrameAvailableListener(this); mCamera.setPreviewTexture(texture); mCamera.preview(); } - public void switchCamera(){ - cameraId = cameraId==0?1:0; + /** + * 切换前后置摄像头 + * */ + public void switchCamera() { + cameraId = cameraId == 0 ? 1 : 0; + mCameraDrawer.switchCamera(); open(cameraId); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { - mCameraDrawer.onSurfaceCreated(gl,config); - if (!isSetParm){ + mCameraDrawer.onSurfaceCreated(gl, config); + if (!isSetParm) { open(cameraId); stickerInit(); } - mCameraDrawer.setPreviewSize(dataWidth,dataHeight); + mCameraDrawer.setPreviewSize(dataWidth, dataHeight); } + @Override public void onSurfaceChanged(GL10 gl, int width, int height) { - mCameraDrawer.onSurfaceChanged(gl,width,height); + mCameraDrawer.onSurfaceChanged(gl, width, height); } + @Override public void onDrawFrame(GL10 gl) { - if (isSetParm){ + if (isSetParm) { mCameraDrawer.onDrawFrame(gl); } } + /** * 每次Activity onResume时被调用,第一次不会打开相机 */ @Override public void onResume() { super.onResume(); - if(isSetParm){ + if (isSetParm) { open(cameraId); } } - public void onDestroy(){ - if (mCamera != null){ + + public void onDestroy() { + if (mCamera != null) { mCamera.close(); } } /** * 摄像头聚焦 - * */ - public void onFocus(Point point, Camera.AutoFocusCallback callback){ - mCamera.onFocus(point,callback); + */ + public void onFocus(Point point, Camera.AutoFocusCallback callback) { + mCamera.onFocus(point, callback); } - public int getCameraId(){ + public int getCameraId() { return cameraId; } + public int getBeautyLevel() { return mCameraDrawer.getBeautyLevel(); } @@ -130,7 +140,8 @@ public void run() { } }); } - public void startRecord(){ + + public void startRecord() { queueEvent(new Runnable() { @Override public void run() { @@ -139,7 +150,7 @@ public void run() { }); } - public void stopRecord(){ + public void stopRecord() { queueEvent(new Runnable() { @Override public void run() { @@ -147,9 +158,11 @@ public void run() { } }); } + public void setSavePath(String path) { mCameraDrawer.setSavePath(path); } + public void resume(final boolean auto) { queueEvent(new Runnable() { @Override @@ -158,6 +171,7 @@ public void run() { } }); } + public void pause(final boolean auto) { queueEvent(new Runnable() { @Override @@ -166,7 +180,8 @@ public void run() { } }); } - public void onTouch(final MotionEvent event){ + + public void onTouch(final MotionEvent event) { queueEvent(new Runnable() { @Override public void run() { @@ -174,12 +189,13 @@ public void run() { } }); } - public void setOnFilterChangeListener(SlideGpuFilterGroup.OnFilterChangeListener listener){ + + public void setOnFilterChangeListener(SlideGpuFilterGroup.OnFilterChangeListener listener) { mCameraDrawer.setOnFilterChangeListener(listener); } - private void stickerInit(){ - if(!isSetParm&&dataWidth>0&&dataHeight>0) { + private void stickerInit() { + if (!isSetParm && dataWidth > 0 && dataHeight > 0) { isSetParm = true; } } diff --git a/app/src/main/java/com/example/cj/videoeditor/widget/CircularProgressView.java b/app/src/main/java/com/example/cj/videoeditor/widget/CircularProgressView.java index c53d656..b660740 100644 --- a/app/src/main/java/com/example/cj/videoeditor/widget/CircularProgressView.java +++ b/app/src/main/java/com/example/cj/videoeditor/widget/CircularProgressView.java @@ -18,7 +18,7 @@ /** * Description: */ -public class CircularProgressView extends ImageView { +public class CircularProgressView extends android.support.v7.widget.AppCompatImageView { private int mStroke=5; private int mProcess=0; diff --git a/app/src/main/java/com/example/cj/videoeditor/widget/TitleView.java b/app/src/main/java/com/example/cj/videoeditor/widget/TitleView.java new file mode 100644 index 0000000..f6d8a46 --- /dev/null +++ b/app/src/main/java/com/example/cj/videoeditor/widget/TitleView.java @@ -0,0 +1,307 @@ +package com.example.cj.videoeditor.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.example.cj.videoeditor.R; + + +/** + * Created by guohuan on 2016/2/20. + * 标题栏控件 + */ +@SuppressWarnings("unused") +public class TitleView extends FrameLayout { + + // 左边返回键 + private ImageButton btn_title_bar_left; + // 右边btn键 + private ImageButton btn_title_bar_right; + + // 标题栏 + private TextView tv_title_bar_title; + + // 右边文字 + private TextView tv_title_bar_right_text; + // 左边文字 + private TextView tv_title_bar_left; + // 右边个数文字 + private TextView tv_title_bar_right_num; + private View title_root; + + public TitleView(Context context) { + super(context); + initView(context); + } + + public TitleView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + resolveAttr(context, attrs); + } + + public TitleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + resolveAttr(context, attrs); + } + + private void initView(Context ctx) { + View.inflate(ctx, R.layout.view_titlebar, this); + title_root = findViewById(R.id.title_root); + btn_title_bar_left = (ImageButton) findViewById(R.id.btn_title_bar_left); + btn_title_bar_right = (ImageButton) findViewById(R.id.btn_title_bar_right); + tv_title_bar_title = (TextView) findViewById(R.id.tv_title_bar_title); + tv_title_bar_right_text = (TextView) findViewById(R.id.tv_title_bar_right_text); + tv_title_bar_left = (TextView) findViewById(R.id.tv_title_bar_left); + tv_title_bar_right_num = (TextView) findViewById(R.id.tv_title_bar_right_num); + } + + // 解析属性 + private void resolveAttr(Context context, AttributeSet attrs) { + TypedArray typedArray = attrs == null ? null : context.obtainStyledAttributes(attrs, R.styleable.TitleView); + if (typedArray != null) { + String title = typedArray.getString(R.styleable.TitleView_title_text);// 标题 + if (!TextUtils.isEmpty(title)) { + tv_title_bar_title.setText(title); + } + String right_str = typedArray.getString(R.styleable.TitleView_right_text); //右侧文字 + if (!TextUtils.isEmpty(right_str)) { + tv_title_bar_right_text.setText(right_str); + tv_title_bar_right_text.setVisibility(View.VISIBLE); + } + String right_num = typedArray.getString(R.styleable.TitleView_right_num_text); //右侧文字 + if (!TextUtils.isEmpty(right_num)) { + tv_title_bar_right_num.setText(right_num); + tv_title_bar_right_num.setVisibility(View.VISIBLE); + } + String left_str = typedArray.getString(R.styleable.TitleView_left_text); //左侧文字 + if (!TextUtils.isEmpty(left_str)) { + tv_title_bar_left.setText(left_str); + tv_title_bar_left.setVisibility(View.VISIBLE); + btn_title_bar_left.setVisibility(View.INVISIBLE); + } + + Drawable right_pic = typedArray.getDrawable(R.styleable.TitleView_right_pic); + if (right_pic != null) { + btn_title_bar_right.setImageDrawable(right_pic); + btn_title_bar_right.setVisibility(View.VISIBLE); + } + Drawable left_pic = typedArray.getDrawable(R.styleable.TitleView_left_pic); + if (left_pic != null) { + btn_title_bar_left.setImageDrawable(left_pic); + btn_title_bar_left.setVisibility(View.VISIBLE); + } + //取消标题栏背景设置 + /*Drawable title_bg = typedArray.getDrawable(R.styleable.TitleView_title_background); + title_root.setBackground(title_bg);*/ + } + } + + // 设置右边图片 + public void setBtnRight(Drawable drawable) { + btn_title_bar_right.setImageDrawable(drawable); + tv_title_bar_right_text.setVisibility(View.INVISIBLE); + btn_title_bar_right.setVisibility(View.VISIBLE); + } + + public void setBtnRight(Bitmap bitmap) { + btn_title_bar_right.setImageBitmap(bitmap); + tv_title_bar_right_text.setVisibility(View.INVISIBLE); + btn_title_bar_right.setVisibility(View.VISIBLE); + } + + public void setBtnRight(int id) { + btn_title_bar_right.setImageResource(id); + tv_title_bar_right_text.setVisibility(View.INVISIBLE); + btn_title_bar_right.setVisibility(View.VISIBLE); + } + + // 设置右边文字 + public void setTvRight(String str) { + tv_title_bar_right_text.setText(str); + tv_title_bar_right_text.setVisibility(View.VISIBLE); + btn_title_bar_right.setVisibility(View.INVISIBLE); + } + + public void setTvRight(int id) { + tv_title_bar_right_text.setText(id); + tv_title_bar_right_text.setVisibility(View.VISIBLE); + btn_title_bar_right.setVisibility(View.INVISIBLE); + } + + // 设置右边个数文字 + public void setTvRightNum(String str) { + tv_title_bar_right_num.setText(str); + tv_title_bar_right_num.setVisibility(View.VISIBLE); + tv_title_bar_right_text.setVisibility(View.VISIBLE); + btn_title_bar_right.setVisibility(View.INVISIBLE); + } + + /** + * 设置右边的个数文字是否显示 + * + * @param visibility + */ + public void setTvRightNumVisibile(int visibility) { + tv_title_bar_right_num.setVisibility(visibility); + } + + // 设置左边图标 + public void setBtnLeft(int id) { + btn_title_bar_left.setImageResource(id); + } + + // 设置左边图标 + public void setBtnLeft(Drawable drawable) { + btn_title_bar_left.setImageDrawable(drawable); + } + + // 设置左边图标是否显示 + public void setBtnLeftVisible(int vis) { + btn_title_bar_left.setVisibility(vis); + } + + // 设置标题 + public void setTitle(String title) { + tv_title_bar_title.setText(title); + } + + public void setTitle(int id) { + tv_title_bar_title.setText(id); + } + + public void setTitleColor(int colorId) { + tv_title_bar_title.setTextColor(colorId); + } + + /** + * 设置左边点击事件 + * + * @param listener + */ + public void setBtnLeftOnClick(OnClickListener listener) { + btn_title_bar_left.setOnClickListener(listener); + } + + /** + * 设置右边btn点击事件 + * + * @param listener + */ + public void setBtnRightOnClick(OnClickListener listener) { + btn_title_bar_right.setOnClickListener(listener); + } + + /** + * 设置右边tv 点击事件 + * + * @param listener + */ + public void setTvRightOnClick(OnClickListener listener) { + tv_title_bar_right_text.setOnClickListener(listener); + } + + /** + * 设置左侧 文字点击事件 + * + * @param listener + */ + public void setTvLeftOnclick(OnClickListener listener) { + tv_title_bar_left.setOnClickListener(listener); + } + + /** + * 设置标题栏点击事件 + * + * @param listener + */ + public void setOnTitleClick(OnClickListener listener) { + tv_title_bar_title.setBackgroundResource(R.drawable.selector_title_bar_btn); + tv_title_bar_title.setOnClickListener(listener); + } + + /** + * 设置右边按钮是否可点击 默认可以 + */ + public void setBtnRightClickable(boolean b) { + btn_title_bar_right.setClickable(b); + } + + /** + * 设置右边文字是否可点击 默认可以 + */ + public void setTvRightClickable(boolean b) { + tv_title_bar_right_text.setClickable(b); + } + + /** + * 获取右边textView 对象 + */ + public TextView getRightTV() { + return tv_title_bar_right_text; + } + + + /** + * @return 获取右侧按钮 + */ + public ImageButton getRightBtn() { + return btn_title_bar_right; + } + + /** + * 获取标题栏textView + */ + public TextView getTitleTV() { + return tv_title_bar_title; + } + + + /** + * 设置右边textView是否可用 + * + * @param enabled + */ + public void setTvRightEnabled(boolean enabled) { + tv_title_bar_right_text.setEnabled(enabled); + } + + + public View getLeftBtn() { + return btn_title_bar_left; + } + + /** + * 设置右边textView是否激活 + * + * @param activated + */ + public void setTvRightActivated(boolean activated) { + tv_title_bar_right_text.setActivated(activated); + } + + public void hideRightBtn() { + btn_title_bar_right.setVisibility(INVISIBLE); + } + + public void hideBackBtn() { + if (btn_title_bar_left != null) { + btn_title_bar_left.setVisibility(GONE); + } + } + + public void showBackBtn() { + if (btn_title_bar_left != null) { + btn_title_bar_left.setVisibility(VISIBLE); + } + } +} diff --git a/app/src/main/java/com/example/cj/videoeditor/widget/VideoPreviewView.java b/app/src/main/java/com/example/cj/videoeditor/widget/VideoPreviewView.java index 33b2b7d..27eb130 100644 --- a/app/src/main/java/com/example/cj/videoeditor/widget/VideoPreviewView.java +++ b/app/src/main/java/com/example/cj/videoeditor/widget/VideoPreviewView.java @@ -56,6 +56,7 @@ private void init(Context context) { public void setVideoPath(List paths){ mMediaPlayer.setDataSource(paths); } + @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { mDrawer.onSurfaceCreated(gl,config); @@ -172,6 +173,12 @@ public void seekTo(int time){ public int getVideoDuration(){ return mMediaPlayer.getCurVideoDuration(); } + /** + * 获取当前播放的视频的列表 + * */ + public List getVideoInfo(){ + return mMediaPlayer.getVideoInfo(); + } /** * 切换美颜状态 diff --git a/app/src/main/res/drawable/app_back_selector.xml b/app/src/main/res/drawable/app_back_selector.xml new file mode 100644 index 0000000..7d13559 --- /dev/null +++ b/app/src/main/res/drawable/app_back_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_circle_yellow.xml b/app/src/main/res/drawable/bg_circle_yellow.xml new file mode 100644 index 0000000..890d461 --- /dev/null +++ b/app/src/main/res/drawable/bg_circle_yellow.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_btn_image_choose.xml b/app/src/main/res/drawable/selector_btn_image_choose.xml new file mode 100644 index 0000000..3617776 --- /dev/null +++ b/app/src/main/res/drawable/selector_btn_image_choose.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_title_bar_btn.xml b/app/src/main/res/drawable/selector_title_bar_btn.xml new file mode 100644 index 0000000..6578072 --- /dev/null +++ b/app/src/main/res/drawable/selector_title_bar_btn.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/setting_text_color_selector.xml b/app/src/main/res/drawable/setting_text_color_selector.xml new file mode 100644 index 0000000..f1e19c8 --- /dev/null +++ b/app/src/main/res/drawable/setting_text_color_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_connect.xml b/app/src/main/res/layout/activity_connect.xml new file mode 100644 index 0000000..d123977 --- /dev/null +++ b/app/src/main/res/layout/activity_connect.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 29b4783..4cf1139 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -2,6 +2,7 @@ - - +